diff --git a/agents/doc-syncer.md b/agents/doc-syncer.md index fc1c8b7..b443d64 100644 --- a/agents/doc-syncer.md +++ b/agents/doc-syncer.md @@ -719,8 +719,16 @@ CREATED : files REMOVED : files / sections HUMAN PENDING: items (see report above) SKIPPED : (user declined) +PATCHED_FILES: (one real path per LINE below; "(none)" if no write) + + ``` +`PATCHED_FILES` is the machine-readable handle the doc-commit step (`lib/doc-commit.md`) +consumes — every public-doc file created or modified this run, ONE PATH PER LINE. Each line +is passed as a SEPARATE argument to `lib/doc-commit.sh` (newline split, space-safe). It NEVER +lists `.claude/**` or `CLAUDE.md` (never targets, BDR-022). Empty / `(none)` → doc-commit no-ops. + --- ## AUTO MODE @@ -782,8 +790,9 @@ Categorize: ### STEP A4 — ACT -- **NONE** → exit completely silent. No output. -- **MINOR** → patch silently. One-line confirmation: +- **NONE** → exit completely silent. No output (no `PATCHED_FILES` → the doc-commit step + sees an empty list and no-ops). +- **MINOR** → patch silently. One-line confirmation per file: `doc-sync: patched ()` - **SIGNIFICANT** → surface to user before patching: ``` @@ -793,6 +802,16 @@ Categorize: ``` Wait for approval. +After writing in MINOR or approved-SIGNIFICANT, emit the machine-readable handle the +doc-commit step (`lib/doc-commit.md`) consumes — ONE real path PER LINE: +``` +PATCHED_FILES: + + +``` +Emit ONLY when something was written; NONE stays silent. Never lists `.claude/**` or +`CLAUDE.md` (never targets, BDR-022). + --- ## RULES diff --git a/lib/doc-commit.md b/lib/doc-commit.md index a932af5..6aca8da 100644 --- a/lib/doc-commit.md +++ b/lib/doc-commit.md @@ -18,12 +18,13 @@ and any SIGNIFICANT-gated patch), with the code already committed. the doc commit strands outside the merge/PR (the exact bug this fixes). See ORDERING. doc-syncer runs IN-THREAD (the orchestrator loads it), so the list of files it patched is -already in hand — surfaced as `PATCHED_FILES:` in doc-syncer's OUTPUT. Pass that list. +already in hand — surfaced as `PATCHED_FILES:` in doc-syncer's OUTPUT, ONE PATH PER LINE. +Pass each line as a SEPARATE argument (see DO step 3). ## DO 1. Collect `PATCHED_FILES` — the public-doc paths doc-syncer wrote this run (its OUTPUT - block). Empty → nothing to commit; the helper no-ops. + block, ONE PATH PER LINE). Empty → nothing to commit; the helper no-ops. 2. Compose — from the patch context the AGENT holds (doc-syncer ran in-thread, so the agent knows exactly what changed) — BOTH artifacts: @@ -34,11 +35,19 @@ already in hand — surfaced as `PATCHED_FILES:` in doc-syncer's OUTPUT. Pass th Both are the AGENT's to write — the helper produces NEITHER (its only stdout is the hash). This is the load-bearing point of the visible surface: see the rc 0 row. -3. Commit surgically via the helper, passing EXACTLY the patched files, capturing the hash: +3. Commit surgically via the helper, passing EXACTLY the patched files — each path as a + SEPARATE argument (split `PATCHED_FILES` on NEWLINES only), capturing the hash: - doc_hash=$(bash "$HOME/.claude/lib/doc-commit.sh" commit "" ) + doc_hash=$(bash "$HOME/.claude/lib/doc-commit.sh" commit "" "" "" …) rc=$? + SEPARATOR — the helper takes argv (no in-band separator), so a path with spaces + (`docs/My Guide.md`) survives as ONE argument and commits correctly (proven, T7). NEVER + flatten the list into a single space-joined string and re-split it: that mis-splits a + spaced path into garbage args, which the helper's `[ -e ]` filter then silently drops — + the spaced doc would strand. Newline is doc-syncer's OUTPUT format (paths carry no + newlines); argv is the handoff to the helper. + 4. REPORT BY (rc, doc_hash) — handle EVERY exit, not just success: | rc | doc_hash | meaning | what to do | diff --git a/lib/tests/run-doc-commit.sh b/lib/tests/run-doc-commit.sh index 3942c68..73baef2 100755 --- a/lib/tests/run-doc-commit.sh +++ b/lib/tests/run-doc-commit.sh @@ -12,6 +12,7 @@ # T4 stale-staged doc (version A) → commit carries working-tree version B # T5 idempotent — empty list / clean tree → no-op exit 0 # T6 unsafe git state (detached HEAD) → exit 3, no commit +# T7 path WITH A SPACE passed as one arg → committed (argv is space-safe, no separator) # # No -e: run every test and report, even after a failure. set -uo pipefail @@ -158,6 +159,20 @@ if [ "$RC" -eq 3 ]; then ok "detached HEAD → exit 3"; else ko "expected 3, got if [ "$(git -C "$R" rev-parse HEAD)" = "$BEFORE" ]; then ok "no commit created"; else ko "a commit was created"; fi rm -rf "$R" +echo "T7 — path WITH A SPACE passed as one arg → committed (argv is space-safe)" +R="$(new_repo)" +mkdir -p "$R/docs" +printf 'guide baseline\n' >"$R/docs/My Guide.md" +git -C "$R" add -A; git -C "$R" commit -qm "add spaced doc" +printf 'feature added\n' >>"$R/docs/My Guide.md" +run "$R" commit "docs: T7 spaced" "docs/My Guide.md" +printf ' rc=%s out(hash)=[%s]\n' "$RC" "$OUT" +if [ "$RC" -eq 0 ]; then ok "exit 0"; else ko "expected 0, got $RC"; fi +if [ -n "$OUT" ]; then ok "hash printed (commit made)"; else ko "no hash"; fi +if git -C "$R" cat-file -e "HEAD:docs/My Guide.md" 2>/dev/null; then ok "spaced path present in HEAD"; else ko "spaced path not committed"; fi +if [ -z "$(git -C "$R" status --porcelain -- "docs/My Guide.md")" ]; then ok "spaced doc clean (embarked as ONE file, not split)"; else ko "spaced doc still dirty"; fi +rm -rf "$R" + rm -f "$ERRFILE" echo "" printf 'RESULT: %d passed, %d failed\n' "$PASS" "$FAIL"