Skip to content

Commit f30caa0

Browse files
committed
knowledge-flush: ambient per-user identity + automatic PR path
Two changes to how insights become wiki PRs: 1. Ambient identity — knowledge-flush no longer hardcodes a committer. Each contributor commits and opens the PR under their OWN git/gh identity (falls back to their gh login only if git has none). Branch is knowledge/<user>-<ts> so PRs are attributable at a glance. Still never an assistant, never a Co-Authored-By trailer. This also fixes push auth: each user pushes as themselves instead of requiring one hardcoded account. 2. Automatic PR — new hooks/auto-flush.sh (Stop) runs the same verified pipeline in a detached headless `claude` run when the queue crosses a threshold and the rate-limit window elapsed, so PRs appear without invoking the skill. Heavily guarded: kill switch DEV_LOOP_AUTOFLUSH=0, once per DEV_LOOP_AUTOFLUSH_INTERVAL (default 3600s), >= DEV_LOOP_AUTOFLUSH_MIN pending (default 3), single-flight lock, recursion guards (DEV_LOOP_FLUSHING + flush-repo cwd + stop_hook_active), and fail-safe no-op when claude/gh are absent. The pre-flush-pr-gate still enforces the INGEST_REPORT; no auto-merge — owner reviews every PR. Manual /dev-loop:knowledge-flush remains. Bump 0.2.0 -> 0.3.0.
1 parent 6507c1c commit f30caa0

6 files changed

Lines changed: 138 additions & 16 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"url": "https://github.com/choiyounggi/dev-loop.git"
2121
},
2222
"homepage": "https://github.com/choiyounggi/dev-loop",
23-
"version": "0.2.0",
23+
"version": "0.3.0",
2424
"tags": [
2525
"orchestrator",
2626
"verification-loop",

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "dev-loop",
33
"description": "loop-orchestrator's verification loop (TDD / PDCA / Reflexion) with the plan step FIXED to a wiki-grounded planning methodology (wiki-plan): every design decision is routed to a bundled semantic-layer wiki before code is written. Adds a knowledge-capture loop — sessions emit verified insights, and knowledge-flush researches, dedups, routes, and opens a wiki PR for owner review.",
4-
"version": "0.2.0",
4+
"version": "0.3.0",
55
"author": {
66
"name": "choiyounggi",
77
"url": "https://github.com/choiyounggi"

README.md

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,30 @@ The wiki is meant to grow from what you actually learn. Three moving parts:
8080
session transcript into a local queue (`~/.dev-loop/queue/`). It never edits
8181
the wiki and never opens a PR — harvesting is cheap and non-blocking.
8282

83-
3. **Flush (on-demand, you trigger it).** `/dev-loop:knowledge-flush` drains the
84-
queue. For **each** candidate it must, before any PR:
83+
3. **Flush → verified PR (automatic, or on-demand).** The queue is drained by the
84+
`knowledge-flush` pipeline. For **each** candidate it must, before any PR:
8585
- **research & verify** the best-practice against real sources (official docs,
8686
primary references) and assign a confidence (verified / field-tested /
8787
unverified — never a fabricated citation),
8888
- **check existing layers** for duplicates to merge into and pages to link,
8989
- **decide the target layer/category** (or justify a new category),
9090
- then run `wiki-ingest` and write an `INGEST_REPORT.md`.
9191

92-
It opens **one PR per flush** and **never auto-merges**. You review the open
93-
`dev-loop:knowledge` PRs and merge or reject each one.
92+
It opens **one PR per flush** and **never auto-merges**. Each contributor's PR
93+
is committed and opened under **their own git/gh identity** (never a hardcoded
94+
account, never an assistant); the repo owner reviews the open
95+
`dev-loop:knowledge` PRs and merges or rejects each one.
96+
97+
Two ways it runs:
98+
- **Automatic** — the `hooks/auto-flush.sh` Stop hook fires the pipeline in a
99+
detached, headless `claude` run when the queue crosses a threshold and the
100+
rate-limit window has elapsed, so PRs appear without you doing anything.
101+
Guarded: kill switch `DEV_LOOP_AUTOFLUSH=0`, once per
102+
`DEV_LOOP_AUTOFLUSH_INTERVAL` (default 3600s), only at
103+
`DEV_LOOP_AUTOFLUSH_MIN` (default 3) pending items, single-flight lock, and
104+
recursion-safe. Needs `claude` + `gh` on PATH and gh authenticated; if either
105+
is missing it silently no-ops and you fall back to manual.
106+
- **Manual** — invoke `/dev-loop:knowledge-flush` any time to drain the queue now.
94107

95108
### This ordering is enforced by a hook
96109

@@ -129,6 +142,7 @@ dev-loop/
129142
│ ├── insight-instruction.sh # SessionStart: inject ★ Insight capture instruction (global)
130143
│ ├── loop-gate.sh # Stop: verification-loop integrity gate
131144
│ ├── harvest-insights.sh + harvest.js # Stop: harvest insights → queue
145+
│ ├── auto-flush.sh # Stop: auto-run knowledge-flush (guarded) → PR
132146
│ └── pre-flush-pr-gate.sh # PreToolUse: enforce the flush pre-PR pipeline
133147
├── scripts/resolve-tools.sh # capability-role profile resolver (no `plan` role)
134148
├── references/tool-profile.md
@@ -139,8 +153,10 @@ dev-loop/
139153

140154
## Attribution
141155

142-
Contributions (including auto-opened knowledge PRs) are committed as the repo
143-
owner (`choiyounggi`), not as an assistant, and carry no `Co-Authored-By` trailer.
156+
Knowledge PRs (manual or auto-opened) are committed under **each contributor's own
157+
git/gh identity** — never a hardcoded account and never an assistant, with no
158+
`Co-Authored-By` trailer. Every contributor opens a PR from their own account; the
159+
repo owner reviews and merges/rejects.
144160

145161
## Lineage & license
146162

hooks/auto-flush.sh

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#!/usr/bin/env bash
2+
# dev-loop — Stop hook: automatically flush the insight queue into a reviewed PR,
3+
# with no manual /dev-loop:knowledge-flush needed.
4+
#
5+
# "Verified routing" needs an LLM (real search + dedup + layer/category decision),
6+
# so a pure shell promote can't do it. This hook instead spawns a DETACHED,
7+
# headless `claude` run of the knowledge-flush skill — which does the full
8+
# research→verify→route→ingest→PR pipeline (the pre-flush-pr-gate still enforces
9+
# the INGEST_REPORT) and opens ONE reviewed PR under the user's OWN gh identity.
10+
# No auto-merge; the owner reviews every PR.
11+
#
12+
# Heavily guarded so it never spams or recurses:
13+
# - kill switch: DEV_LOOP_AUTOFLUSH=0
14+
# - recursion: skipped inside the headless flush run (DEV_LOOP_FLUSHING=1)
15+
# and inside the flush checkout (~/.dev-loop/repo)
16+
# - rate limit: at most once per DEV_LOOP_AUTOFLUSH_INTERVAL sec (default 3600)
17+
# - threshold: only when >= DEV_LOOP_AUTOFLUSH_MIN pending items (default 3)
18+
# - single-flight: a TTL lock dir
19+
# - fail-safe: if `claude`/`gh` are missing it silently no-ops; the manual
20+
# /dev-loop:knowledge-flush skill still works.
21+
set +e
22+
23+
# --- kill switch + recursion guards ---------------------------------------
24+
[ "${DEV_LOOP_AUTOFLUSH:-1}" = "0" ] && exit 0
25+
[ -n "${DEV_LOOP_FLUSHING:-}" ] && exit 0
26+
27+
INPUT="$(cat 2>/dev/null)"
28+
# Skip re-entrant Stop events (e.g. the loop-gate re-prompting a managed session);
29+
# only flush on a genuine session end.
30+
printf '%s' "$INPUT" | grep -q '"stop_hook_active"[[:space:]]*:[[:space:]]*true' && exit 0
31+
CWD="$(printf '%s' "$INPUT" | node -e 'let s="";try{s=require("fs").readFileSync(0,"utf8")}catch{};let o={};try{o=JSON.parse(s)}catch{};process.stdout.write(String(o.cwd||""))' 2>/dev/null)"
32+
case "$(cd "${CWD:-$PWD}" 2>/dev/null && pwd)" in
33+
"$HOME/.dev-loop/repo"*) exit 0 ;;
34+
esac
35+
36+
# --- prerequisites (fail-safe: no-op if missing) --------------------------
37+
command -v claude >/dev/null 2>&1 || exit 0
38+
command -v gh >/dev/null 2>&1 || exit 0
39+
40+
DIR="$HOME/.dev-loop"
41+
QUEUE="$DIR/queue"
42+
[ -d "$QUEUE" ] || exit 0
43+
44+
# --- threshold: count PENDING rows (exclude the retired .processed.jsonl) --
45+
PENDING=0
46+
for f in "$QUEUE"/*.jsonl; do
47+
case "$f" in *"/.processed.jsonl") continue ;; esac
48+
[ -f "$f" ] || continue
49+
n=$(grep -c '"status":"pending"' "$f" 2>/dev/null)
50+
PENDING=$((PENDING + ${n:-0}))
51+
done
52+
[ "$PENDING" -ge "${DEV_LOOP_AUTOFLUSH_MIN:-3}" ] || exit 0
53+
54+
# --- rate limit: at most once per interval --------------------------------
55+
STAMP="$DIR/.last-autoflush"
56+
INTERVAL_SEC="${DEV_LOOP_AUTOFLUSH_INTERVAL:-3600}"
57+
INTERVAL_MIN=$(( INTERVAL_SEC / 60 )); [ "$INTERVAL_MIN" -lt 1 ] && INTERVAL_MIN=1
58+
if [ -f "$STAMP" ] && [ -n "$(find "$STAMP" -mmin "-$INTERVAL_MIN" 2>/dev/null)" ]; then
59+
exit 0
60+
fi
61+
62+
# --- single-flight lock (TTL ~15 min via mkdir atomicity) -----------------
63+
LOCK="$DIR/.autoflush.lock"
64+
if ! mkdir "$LOCK" 2>/dev/null; then
65+
# stale lock older than 15 min → reclaim
66+
[ -n "$(find "$LOCK" -mmin -15 2>/dev/null)" ] && exit 0
67+
rmdir "$LOCK" 2>/dev/null; mkdir "$LOCK" 2>/dev/null || exit 0
68+
fi
69+
touch "$STAMP" 2>/dev/null
70+
71+
# --- spawn the detached headless flush ------------------------------------
72+
CLAUDE_BIN="$(command -v claude)"
73+
PROMPT='Run the dev-loop:knowledge-flush skill now. Drain ~/.dev-loop/queue: for each pending insight research and verify the best-practice against real sources, check existing wiki layers for duplicates/links, decide the target layer/category, run wiki-ingest, write the INGEST_REPORT, then open exactly ONE reviewed PR (label dev-loop:knowledge) under my own gh identity. Do NOT merge. If the queue is empty, do nothing.'
74+
75+
(
76+
DEV_LOOP_FLUSHING=1 nohup "$CLAUDE_BIN" -p "$PROMPT" \
77+
--permission-mode bypassPermissions \
78+
> "$DIR/autoflush.log" 2>&1
79+
rmdir "$LOCK" 2>/dev/null
80+
) &
81+
disown 2>/dev/null
82+
83+
exit 0

hooks/hooks.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"description": "dev-loop hooks — dependency preflight (advisory), verification-loop integrity gate, knowledge capture (SessionStart insight instruction + Stop harvest), and the knowledge-flush pre-PR gate.",
2+
"description": "dev-loop hooks — dependency preflight (advisory), verification-loop integrity gate, knowledge capture (SessionStart insight instruction + Stop harvest), automatic queue flush (Stop, guarded), and the knowledge-flush pre-PR gate.",
33
"hooks": {
44
"SessionStart": [
55
{
@@ -37,6 +37,10 @@
3737
{
3838
"type": "command",
3939
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/harvest-insights.sh"
40+
},
41+
{
42+
"type": "command",
43+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/auto-flush.sh"
4044
}
4145
]
4246
}

skills/knowledge-flush/SKILL.md

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,24 @@ an `INGEST_REPORT.md` with three filled sections exists. So do the work first:
2727
mkdir -p "$HOME/.dev-loop"
2828
git clone https://github.com/choiyounggi/dev-loop.git "$REPO"
2929
fi
30-
# Commit as the repo owner — never as an assistant, never add a Co-Authored-By.
31-
git -C "$REPO" config user.name "choiyounggi"
32-
git -C "$REPO" config user.email "74581798+choiyounggi@users.noreply.github.com"
33-
BR="knowledge/$(date +%Y%m%d-%H%M%S)"
30+
# Commit under THIS user's own identity — each contributor's PR carries their
31+
# own account; the owner reviews and approves/rejects. Do NOT hardcode an
32+
# identity, and NEVER commit as an assistant or add a Co-Authored-By trailer.
33+
# Inherit the user's global git identity (fall back to their gh login only if
34+
# git has none configured):
35+
if [ -z "$(git -C "$REPO" config user.email)" ]; then
36+
GH_USER="$(gh api user -q .login 2>/dev/null)"
37+
[ -n "$GH_USER" ] && git -C "$REPO" config user.name "$GH_USER" \
38+
&& git -C "$REPO" config user.email "${GH_USER}@users.noreply.github.com"
39+
fi
40+
# Branch names carry the contributor so PRs are attributable at a glance:
41+
WHO="$(git -C "$REPO" config user.name | tr ' ' '-' | tr -cd 'A-Za-z0-9-')"
42+
BR="knowledge/${WHO:-anon}-$(date +%Y%m%d-%H%M%S)"
3443
git -C "$REPO" checkout -b "$BR"
3544
```
3645
Read/write the wiki inside `$REPO` (its `INDEX.md`, `wiki/`, `templates/`,
37-
`AGENTS.md`), NOT `${CLAUDE_PLUGIN_ROOT}`.
46+
`AGENTS.md`), NOT `${CLAUDE_PLUGIN_ROOT}`. The push + PR use the ambient `gh`
47+
auth, so the PR is opened by whichever account this user is logged in as.
3848

3949
2. **For each queued candidate, run the pre-PR pipeline** (this is the whole point
4050
— a raw harvested block is a *candidate*, not vetted knowledge):
@@ -103,8 +113,17 @@ an `INGEST_REPORT.md` with three filled sections exists. So do the work first:
103113

104114
## Guardrails
105115
- PR-only. Never auto-merge, never push to `main`, never force-push `main`.
106-
- Commit identity is `choiyounggi` — never an assistant; never add a
107-
`Co-Authored-By` trailer.
116+
- Commit under the **user's own ambient git/gh identity**never hardcode an
117+
account, never commit as an assistant, never add a `Co-Authored-By` trailer.
108118
- A candidate you cannot verify does not get quietly upgraded to `verified`.
109119
- If the queue is empty, say so and stop — do not open an empty PR.
110120
- One PR per flush (batched), so review stays a single pass.
121+
122+
## Triggering — manual and automatic
123+
- **Manual:** invoke this skill (`/dev-loop:knowledge-flush`) any time; it drains
124+
the shared queue (`~/.dev-loop/queue/`, keyed off `$HOME` so it spans sessions).
125+
- **Automatic:** the Stop hook `hooks/auto-flush.sh` fires this same pipeline in a
126+
detached headless `claude` run when the queue crosses a threshold and the
127+
rate-limit window has elapsed — so PRs appear without you running anything. It
128+
is guarded (rate-limited, batched, recursion-safe) and opens the same reviewed,
129+
gated PR. Disable with `DEV_LOOP_AUTOFLUSH=0`. See that hook for the knobs.

0 commit comments

Comments
 (0)