@gobing-ai/spur 0.3.97 → 0.3.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/plugin-scripts.json +5 -0
- package/config/templates/task/brainstorm.md +1 -1
- package/config/templates/task/feature-impl.md +1 -1
- package/config/templates/task/issue.md +1 -1
- package/config/templates/task/meta.md +1 -1
- package/config/templates/task/review.md +1 -1
- package/config/templates/task/standard.md +1 -1
- package/package.json +9 -9
- package/plugins/sp/README.md +9 -9
- package/plugins/sp/lib/idea-handoff.generated.mjs +37 -4
- package/plugins/sp/lib/inline-run.generated.mjs +54 -14
- package/plugins/sp/lib/residual-scan.generated.d.mts +5 -0
- package/plugins/sp/lib/residual-scan.generated.mjs +45 -5
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +3 -3
- package/plugins/sp/scripts/inline-run-setup.mjs +4 -2
- package/plugins/sp/scripts/inline-run-setup.ts +12 -12
- package/plugins/sp/scripts/persist-out-check.mjs +164 -0
- package/plugins/sp/scripts/persist-out-check.ts +185 -0
- package/plugins/sp/scripts/residual-scan.mjs +50 -8
- package/plugins/sp/scripts/residual-scan.ts +4 -3
- package/plugins/sp/scripts/task-diffstat.mjs +17 -5
- package/plugins/sp/scripts/task-diffstat.ts +25 -4
- package/plugins/sp/skills/code-implementation/SKILL.md +1 -1
- package/plugins/sp/skills/code-verification/SKILL.md +3 -0
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +17 -1
- package/plugins/sp/skills/conflict-finding/SKILL.md +1 -1
- package/plugins/sp/skills/conflict-finding/references/authority-resolution.md +5 -3
- package/plugins/sp/skills/conflict-finding/references/remediation-routing.md +9 -8
- package/plugins/sp/skills/doc-evolve/SKILL.md +4 -3
- package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +7 -4
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +7 -4
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +40 -7
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +16 -13
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +217 -38
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +3 -1
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +7 -8
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +110 -3
- package/spur.js +2142 -637
- package/web/_astro/{BoardApp.vveLOdDq.js → BoardApp.BVp-LYi6.js} +101 -101
- package/web/_astro/{BoardApp.Bmv5WkJ9.js → BoardApp.i4eh_fHA.js} +1 -1
- package/web/_astro/{TaskDetail.IKc3uDYV.js → TaskDetail.C5Cu8LdB.js} +1 -1
- package/web/_astro/{arc.C63Ufg35.js → arc.Bu5XpBEz.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.CP8Hyldk.js → architectureDiagram-3BPJPVTR.9vHQTvhF.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.Bo_48MRA.js → blockDiagram-GPEHLZMM.Cn2Xl7D6.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.Ch61sw-O.js → c4Diagram-AAUBKEIU.7o0P_QDo.js} +1 -1
- package/web/_astro/channel.6IFIKBhu.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.CHpzS7xf.js → chunk-2J33WTMH.C3zZFsNt.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.DzMNNWIw.js → chunk-4BX2VUAB.CNy_M7fO.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.BqPrDw_Q.js → chunk-55IACEB6.2TiPnLBL.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.ZzBHhNw-.js → chunk-727SXJPM.5CGSyKAp.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.1_O_LLkx.js → chunk-AQP2D5EJ.w-ZIgfsK.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.GS4UHg7_.js → chunk-FMBD7UC4.BkcEyhKa.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.BnqotN4g.js → chunk-ND2GUHAM.mxKKbBG4.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.Ghuh8zoJ.js → chunk-QZHKN3VN.cyA0z9aT.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.BW9vC3zg.js → classDiagram-4FO5ZUOK.CqtUzwhX.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.BW9vC3zg.js → classDiagram-v2-Q7XG4LA2.CqtUzwhX.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.D6jNC0ti.js → cose-bilkent-S5V4N54A.BExPTopI.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.BOGoCs4L.js → cynefin-OW5HDTMX.Bq2i10Cn.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.8iU5VcWi.js → dagre-BM42HDAG.By_HsOuv.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.CVt88HwH.js → diagram-2AECGRRQ.BtU0b7hz.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.C9HxUUMl.js → diagram-5GNKFQAL.D00ztMdT.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.4ywkQN3t.js → diagram-KO2AKTUF.DMVJWaUN.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.C48A6gpz.js → diagram-LMA3HP47.Cxqrjg5-.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.DxFieQW4.js → diagram-OG6HWLK6.2lz_1B5h.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.CtjtQqYs.js → erDiagram-TEJ5UH35.BVQEuJ3T.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.C5zAiahU.js → flowDiagram-I6XJVG4X.Qlyo90ky.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.D_L6zsny.js → ganttDiagram-6RSMTGT7.DRwb8HHZ.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.Ignnrk3V.js → gitGraphDiagram-PVQCEYII.YfoPthfS.js} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.DLv8-P5u.js → infoDiagram-5YYISTIA.DvFREck1.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.p0OTpwFO.js → ishikawaDiagram-YF4QCWOH.BzCj9Q0-.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.CIaBt-DD.js → journeyDiagram-JHISSGLW.BDMT_AFb.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.DGySIntq.js → kanban-definition-UN3LZRKU.D6Fh3gj9.js} +1 -1
- package/web/_astro/{linear.BATV4RXu.js → linear.Dxw00fGQ.js} +1 -1
- package/web/_astro/{mermaid.core.DzkwM3VX.js → mermaid.core.CsoxUWa0.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.3piBRRzA.js → mindmap-definition-RKZ34NQL.CN8oeHLu.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.DUNueLub.js → pieDiagram-4H26LBE5.C7ADqQ_7.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.9bihVbrt.js → quadrantDiagram-W4KKPZXB.Cw9lKyHP.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.CkjsC-YT.js → requirementDiagram-4Y6WPE33.B1ni-z4q.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.Dbq4vaoQ.js → sankeyDiagram-5OEKKPKP.BhexdjVr.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.BivrH99P.js → sequenceDiagram-3UESZ5HK.C3Rm7dpQ.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.CqLQ1Vzz.js → stateDiagram-AJRCARHV.CkuMt27u.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.DvvoLD3b.js → stateDiagram-v2-BHNVJYJU.Dc_aazUA.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.B-KR78gv.js → timeline-definition-PNZ67QCA.ja7sUFhH.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.Ck0Eieb5.js → vennDiagram-CIIHVFJN.BxIshX_K.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.CRfdd_do.js → wardleyDiagram-YWT4CUSO.DfHlC0sh.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.Ck_Jxk9C.js → xychartDiagram-2RQKCTM6.DSnhHwCh.js} +1 -1
- package/web/index.html +1 -1
- package/web/_astro/channel.BKCmJpWM.js +0 -1
|
@@ -530,7 +530,7 @@ pass before the invoking tree is written → WT-5. Without `--task-file` nothing
|
|
|
530
530
|
The shapes are pinned (task 0975 R1; `record-missing` and citation behavior per 0984): idempotent on re-persist;
|
|
531
531
|
success exits 0 printing
|
|
532
532
|
`{"ok":true,"persisted":<n>,"skipped":[{"id":<run-id>,"reason":"id-exists"|"external-key-conflict"|"record-conflict:<file>"|"record-missing:<file>"|"cited-directory:<name>"|"cited-symlink:<name>"|"cited-non-file:<name>"}]}`
|
|
533
|
-
— an `external-key-conflict` skip leaves the target run unchanged
|
|
533
|
+
— an `external-key-conflict` skip leaves the target run unchanged **and fails the pass (1049)**: the delegate exits 1 printing `{"ok":false,"error":…}` naming the skipped source run ids, because that (workflow, external key) identity already belongs to a different receiving run — the batch was not persisted, so reconcile the source worktree by hand (it stays the provenance owner of record) before any teardown: auditable reconciliation names the original archived DB snapshot and the skipped source run identities, verifies the archive by file hash and by merged-commit ancestry of the source branch, and treats any residual deletion as explicit operator-authorized cleanup under the strict canonical evidence rules above. An `id-exists` replay repairs missing owned artifacts and task links without duplicating them. A
|
|
534
534
|
`record-conflict:<file>` never overwrites a divergent invoking-tree record and causes the delegate to exit 1, retaining the worktree. A
|
|
535
535
|
`record-missing:<file>` skip is a known `task-lifecycle`/`feature-lifecycle` row with no record file
|
|
536
536
|
at all (its inserted DB row still counts in `persisted` — 0984 R5). Any failure
|
|
@@ -626,6 +626,20 @@ resolution runs, the loop executes with the worktree as process cwd, and a termi
|
|
|
626
626
|
retains after the batch report is emitted. Steps 1–5 themselves are not modified — only their cwd
|
|
627
627
|
differs.
|
|
628
628
|
|
|
629
|
+
**Per-call tree pinning (task 1058).** A host shell call starts in an arbitrary directory and
|
|
630
|
+
inherits no cwd from the previous call, so "the loop runs with the worktree as process cwd" is a
|
|
631
|
+
bootstrap convenience, never a carried guarantee. Every host command boundary in this lifecycle —
|
|
632
|
+
corpus reads/writes, the WT-3b commit, WT-4's merge and evidence persistence, the WT-4a
|
|
633
|
+
persist-out — re-selects its intended tree within the same call via the canonical per-call pin
|
|
634
|
+
protocol ([inline-pipeline-driver.md](inline-pipeline-driver.md) § "Per-call execution-tree
|
|
635
|
+
pin"): `cd --` to the quoted absolute tree, verify `pwd -P` against the recorded physical tree,
|
|
636
|
+
verify Git top-level and expected branch, then execute the already-resolved Spur invocation. The
|
|
637
|
+
identity is consumed, not re-derived: it is the WT-3 marker's confirmed `path` + `branch` (or the
|
|
638
|
+
resolved worktree's physical path + checked-out branch in reuse mode). The one deliberate tree
|
|
639
|
+
change is WT-4: the terminal merge actions select the **invoking** tree, not the execution tree —
|
|
640
|
+
and pin it explicitly the same way. FF ancestry, marker and cleanup sequencing remain owned by
|
|
641
|
+
1059.
|
|
642
|
+
|
|
629
643
|
**Portability (R10).** Use portable `git worktree` commands only. Do **not** depend on the Claude
|
|
630
644
|
Code `EnterWorktree`/`ExitWorktree` tools — the `sp` plugin ships to Codex, Gemini CLI, pi, omp, and
|
|
631
645
|
OpenCode. The underlying git mechanics (create / list / remove / prune, sibling-directory naming,
|
|
@@ -765,6 +779,10 @@ the tree's own source:
|
|
|
765
779
|
|
|
766
780
|
cd "<worktree>" && bun apps/cli/src/index.ts task check <wbs> --json
|
|
767
781
|
|
|
782
|
+
Run that as a per-call pin subshell (task 1058) — `cd --` plus the `pwd -P` / git-top-level /
|
|
783
|
+
branch identity checks of the canonical protocol (inline-pipeline-driver.md § "Per-call
|
|
784
|
+
execution-tree pin") — rather than relying on a persistent `cd` between host tool calls.
|
|
785
|
+
|
|
768
786
|
Confirm isolation by making a distinctive change in the worktree and checking that
|
|
769
787
|
the command reflects it.
|
|
770
788
|
|
|
@@ -832,13 +850,19 @@ Reuse mode resolves the marker by the resolved worktree's `path` (not by `comman
|
|
|
832
850
|
### WT-3b — Commit the batch's writes on `$BRANCH` (task 0701 R1)
|
|
833
851
|
|
|
834
852
|
Before any terminal action, commit the batch's corpus writes **on `$BRANCH`, inside the
|
|
835
|
-
worktree** — including the generated task files under `docs/tasks*/` and the kanban index
|
|
853
|
+
worktree** — including the generated task files under `docs/tasks*/` and the kanban index. The
|
|
854
|
+
commit runs as a per-call pinned subshell (task 1058): it selects the execution tree inside the
|
|
855
|
+
same call, verifies the WT-3 identity, and never relies on a persistent `cd` or a trailing
|
|
856
|
+
`cd -` to restore the caller's directory:
|
|
836
857
|
|
|
837
858
|
```bash
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
git
|
|
841
|
-
|
|
859
|
+
(
|
|
860
|
+
cd -- "../<worktree-dir>" || { echo "tree missing: expected ../<worktree-dir>" >&2; exit 1; }
|
|
861
|
+
[ "$(git branch --show-current)" = "$BRANCH" ] \
|
|
862
|
+
|| { echo "branch mismatch: expected $BRANCH, got $(git branch --show-current)" >&2; exit 1; }
|
|
863
|
+
git add <files-the-batch-wrote>
|
|
864
|
+
git commit -m "<type>(<scope>): <command> <selector> batch writes"
|
|
865
|
+
)
|
|
842
866
|
```
|
|
843
867
|
|
|
844
868
|
The FF-only git merge carries only commits — uncommitted writes in the worktree would be left
|
|
@@ -855,18 +879,66 @@ only what it created*):
|
|
|
855
879
|
|
|
856
880
|
```bash
|
|
857
881
|
# Run these from the main tree (not inside the worktree) - you merge the worktree branch
|
|
858
|
-
# back onto the base ref there:
|
|
859
|
-
|
|
882
|
+
# back onto the base ref there. Per-call tree pinning (task 1058, § above): every command
|
|
883
|
+
# below selects the INVOKING tree (where the WT-3 marker lives), re-pinned with the canonical
|
|
884
|
+
# per-call pin protocol in its own subshell — never the execution tree. The whole sequence is
|
|
885
|
+
# fail-stop: checkout, guard, ancestry check, merge, landed verify, persistence, and cleanup
|
|
886
|
+
# each stop explicitly. Every tip/ancestry read uses local refs — no fetch runs anywhere:
|
|
887
|
+
MARKER=".spur/run/worktree-<marker-id>.json"
|
|
888
|
+
git checkout "$BASE_REF" \
|
|
889
|
+
|| { echo "WT-4 halt: checkout $BASE_REF failed - nothing merged, marker stays \"active\"" >&2; exit 1; } # -> WT-5
|
|
860
890
|
# Guard (task 0701 R1): a zero-commit branch makes the FF-only git merge exit 0
|
|
861
|
-
# ("Already up to date") while merging nothing — the
|
|
891
|
+
# ("Already up to date") while merging nothing — the lines below would then delete
|
|
862
892
|
# the worktree holding the only copy of the batch's writes. Refuse instead:
|
|
863
|
-
[ "$(git rev-list --count "$
|
|
864
|
-
|| { echo "halt: branch carries no commits - nothing to merge" >&2;
|
|
893
|
+
[ "$(git rev-list --count "$BASE_REF..$BRANCH")" -gt 0 ] \
|
|
894
|
+
|| { echo "WT-4 halt: branch carries no commits - nothing to merge" >&2; exit 1; } # -> WT-5, marker stays "active"
|
|
895
|
+
# Capture the batch tip BEFORE any cleanup or branch work: every later step (landed verify,
|
|
896
|
+
# mergeCommit, retained report) reads this captured value — a removed branch is never queried.
|
|
897
|
+
BATCH_TIP="$(git rev-parse "$BRANCH")" \
|
|
898
|
+
|| { echo "WT-4 halt: cannot read $BRANCH" >&2; exit 1; }
|
|
899
|
+
# Fresh LOCAL base tip, re-read now; the marker's baseSha may be stale and never governs this
|
|
900
|
+
# decision (an advanced-but-ancestor base still FF-merges, a divergent base halts):
|
|
901
|
+
BASE_TIP="$(git rev-parse "$BASE_REF")" \
|
|
902
|
+
|| { echo "WT-4 halt: cannot read $BASE_REF" >&2; exit 1; }
|
|
903
|
+
# Ancestry check immediately before the sole mutation (the FF merge below). Command/read
|
|
904
|
+
# errors are distinguished from ordinary non-ancestry; both fail closed:
|
|
905
|
+
ANCESTRY_RC=0
|
|
906
|
+
git merge-base --is-ancestor "$BASE_TIP" "$BATCH_TIP" || ANCESTRY_RC=$?
|
|
907
|
+
[ "$ANCESTRY_RC" -gt 1 ] \
|
|
908
|
+
&& { echo "WT-4 halt: ancestry check could not run (git exit $ANCESTRY_RC) - failing closed" >&2; exit 1; }
|
|
909
|
+
[ "$ANCESTRY_RC" -eq 1 ] && {
|
|
910
|
+
echo "WT-4 halt: divergent - batch tip $BATCH_TIP vs base tip $BASE_TIP" >&2
|
|
911
|
+
git log --oneline "$BASE_TIP..$BATCH_TIP" >&2 # batch-side divergent commits
|
|
912
|
+
git log --oneline "$BATCH_TIP..$BASE_TIP" >&2 # base-side divergent commits
|
|
913
|
+
exit 1; } # -> WT-5: tree + branch retained, marker stays "active"
|
|
865
914
|
git merge --ff-only "$BRANCH" # FF-only: never rebase, merge-commit, or resolve conflicts
|
|
866
|
-
|
|
915
|
+
[ $? -eq 0 ] \
|
|
916
|
+
|| { echo "WT-4 halt: FF-merge failed - the concurrent-writer race survives every precheck; marker stays \"active\"" >&2; exit 1; } # -> WT-5
|
|
917
|
+
# Landed verification before anything else runs: the captured BATCH_TIP must be an ancestor of
|
|
918
|
+
# the base ref as it now stands (base tips advancing again later stay valid — this binds the
|
|
919
|
+
# merge result, not the end state):
|
|
920
|
+
LANDED_BASE_TIP="$(git rev-parse "$BASE_REF")" \
|
|
921
|
+
|| { echo "WT-4 halt: cannot read landed base tip" >&2; exit 1; }
|
|
922
|
+
git merge-base --is-ancestor "$BATCH_TIP" "$LANDED_BASE_TIP" \
|
|
923
|
+
|| { echo "WT-4 halt: landed verify failed - $BATCH_TIP is not an ancestor of $LANDED_BASE_TIP" >&2; exit 1; }
|
|
924
|
+
# --- Landed from here on. Any halt below is landed-but-incomplete: it records status
|
|
925
|
+
# --- "retained" + mergeCommit "$BATCH_TIP" via write_marker and stops — never a branch query.
|
|
926
|
+
write_marker () { # $1 = merged | retained; read-modify-write: preserve WT-3 fields (id, command, selector, createdAt, adopted, adoptedAt); jq already assumed by WT-3/task-resolution flows
|
|
927
|
+
mkdir -p "$(dirname "$MARKER")"
|
|
928
|
+
if [ -f "$MARKER" ]; then
|
|
929
|
+
TMP_MARKER="$(mktemp)"
|
|
930
|
+
jq --arg s "$1" --arg m "$BATCH_TIP" '.status = $s | .mergeCommit = $m' "$MARKER" > "$TMP_MARKER" \
|
|
931
|
+
|| { echo "WT-4 halt: cannot rewrite marker $MARKER" >&2; rm -f "$TMP_MARKER"; exit 1; }
|
|
932
|
+
mv "$TMP_MARKER" "$MARKER"
|
|
933
|
+
else
|
|
934
|
+
printf '{ "baseRef": "%s", "baseSha": "%s", "branch": "%s", "path": "%s", "status": "%s", "mergeCommit": "%s" }\n' \
|
|
935
|
+
"$BASE_REF" "$BASE_SHA" "$BRANCH" "$WT_PATH" "$1" "$BATCH_TIP" > "$MARKER"
|
|
936
|
+
fi
|
|
937
|
+
}
|
|
938
|
+
# WT-4a evidence persistence runs FIRST after the landed verify (Step 5, task 0720 R3):
|
|
867
939
|
# persist the batch report + verdict artifacts AND the per-run provenance (task
|
|
868
940
|
# 0975 R1) into the invoking tree before anything below touches the worktree.
|
|
869
|
-
# Any persistence failure routes to WT-5 —
|
|
941
|
+
# Any persistence failure routes to WT-5 — landed-but-incomplete: retained marker + mergeCommit.
|
|
870
942
|
WT_PATH="$(cd "../<worktree-dir>" && pwd)" # hoisted: needed by WT-4a AND WT-4b below
|
|
871
943
|
# WT-4a provenance persist-out (task 0975 R1): copy the worktree DB's run rows plus
|
|
872
944
|
# the .spur/memory/runs/<runId>.md + .state.json records into THIS tree. Run from the main
|
|
@@ -880,7 +952,7 @@ WT_PATH="$(cd "../<worktree-dir>" && pwd)" # hoisted: needed by WT-4a AND WT-4
|
|
|
880
952
|
# NOT proceed to WT-4b removal:
|
|
881
953
|
SETUP_SCRIPT=plugins/sp/scripts/inline-run-setup.ts; [ -f config/plugin-scripts.json -a -f "$SETUP_SCRIPT" ] || SETUP_SCRIPT="$(superskill script path sp inline-run-setup.mjs 2>/dev/null)"
|
|
882
954
|
bun "$SETUP_SCRIPT" --persist-out --from "$WT_PATH" "${TASK_FILE_ARGS[@]}" \
|
|
883
|
-
|| { echo "halt: worktree run-record persist-out failed - worktree retained (WT-5)" >&2; exit 1; }
|
|
955
|
+
|| { echo "halt: worktree run-record persist-out failed - worktree retained (WT-5)" >&2; write_marker retained; exit 1; }
|
|
884
956
|
#
|
|
885
957
|
# WT-4b — bounded CWD-holder cleanup (task 0720 R1). $WT_PATH above is the EXACT
|
|
886
958
|
# absolute worktree path; a relative path or a stale entry matches the wrong processes.
|
|
@@ -911,39 +983,112 @@ if [ -n "$FINAL" ]; then
|
|
|
911
983
|
PORTS="$(lsof -nP -a -p "$(echo "$FINAL" | paste -sd, -)" -iTCP -sTCP:LISTEN 2>/dev/null \
|
|
912
984
|
| awk 'NR>1 {print $9}' | sort -u | paste -sd' ' -)"
|
|
913
985
|
echo "halt: worktree still held by PID(s): $FINAL ${PORTS:+listening: $PORTS}" >&2
|
|
986
|
+
write_marker retained # landed-but-incomplete: retained + mergeCommit
|
|
914
987
|
exit 1 # -> WT-5: retain worktree + branch,
|
|
915
988
|
fi # NO prune/remove/branch delete
|
|
916
|
-
|
|
917
|
-
|
|
989
|
+
# Pre-removal evidence assertion (task 1067 R1, E71 enforcement): refuse to remove the
|
|
990
|
+
# worktree while it still owns evidence absent from (or divergent from) the invoking tree —
|
|
991
|
+
# the D63 landing skipped persist-out and silently lost verdicts + receipts. Task-file
|
|
992
|
+
# WBSes / run ids ride the same TASK_FILE_ARGS resolved for WT-4a (run ids: --run-id <id>).
|
|
993
|
+
CHECK_SCRIPT=plugins/sp/scripts/persist-out-check.ts; [ -f config/plugin-scripts.json -a -f "$CHECK_SCRIPT" ] || CHECK_SCRIPT="$(superskill script path sp persist-out-check.mjs 2>/dev/null)"
|
|
994
|
+
bun "$CHECK_SCRIPT" --from "$WT_PATH" "${TASK_FILE_ARGS[@]}" \
|
|
995
|
+
|| { echo "WT-4 halt: worktree evidence would be abandoned - persist-out missing or stale (WT-5)" >&2; write_marker retained; exit 1; }
|
|
996
|
+
git worktree remove "../<worktree-dir>" \
|
|
997
|
+
|| { echo "WT-4 halt: worktree remove failed after landed merge $BATCH_TIP" >&2; write_marker retained; exit 1; }
|
|
998
|
+
# Branch deletion runs only after the merge landed, persisted, and the worktree is gone —
|
|
999
|
+
# and it is existence-guarded, so a removed branch is never queried. -D over -d: the landed
|
|
1000
|
+
# verify already proved the merge landed, so an unmerged-refusal would only block proven-merged cleanup.
|
|
1001
|
+
if git rev-parse --verify --quiet "$BRANCH" >/dev/null; then
|
|
1002
|
+
git branch -D "$BRANCH" || { echo "WT-4 halt: cannot delete branch $BRANCH after landed merge $BATCH_TIP" >&2; write_marker retained; exit 1; }
|
|
1003
|
+
fi
|
|
918
1004
|
# WT-4c — clean up registry entry in ~/.config/spur/projects.json (task 0924)
|
|
919
1005
|
spur projects remove "$WT_PATH" 2>/dev/null || spur projects clean --json 2>/dev/null || true
|
|
920
|
-
#
|
|
1006
|
+
# Success marker, written once and only here: status "merged" + mergeCommit "$BATCH_TIP" —
|
|
1007
|
+
# after landed verification, required persistence, and cleanup:
|
|
1008
|
+
write_marker merged
|
|
921
1009
|
```
|
|
922
1010
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1011
|
+
Every halt above stops explicitly and falls through to **WT-5** — zero-commit branch (task 0701
|
|
1012
|
+
R1), failed checkout, divergent base, failed FF, persist-out failure (task 0975), or surviving CWD
|
|
1013
|
+
holders (task 0720 R1). Pre-merge failures leave the marker at `status: active` and the worktree +
|
|
1014
|
+
branch untouched. Landed-but-incomplete failures record `status: retained` + `mergeCommit`
|
|
1015
|
+
BATCH_TIP: the merge landed, and the retained report reads the captured tip only. While any holder
|
|
1016
|
+
remains, do **not** run `git worktree prune`, `git worktree remove`, or branch deletion — only an
|
|
1017
|
+
EMPTY holder set may proceed to those steps. The holder
|
|
926
1018
|
halt report names every surviving PID; the listening port is best-effort — a CWD holder may own no
|
|
927
1019
|
socket, and `lsof` port discovery failing must not hide the PIDs.
|
|
928
1020
|
|
|
929
1021
|
#### Reuse mode — merge, retain
|
|
930
1022
|
|
|
931
|
-
The
|
|
932
|
-
|
|
1023
|
+
The fail-stop merge sequence runs identically — same guard, captured `BATCH_TIP`/`BASE_TIP`,
|
|
1024
|
+
ancestry check immediately before FF, `git merge --ff-only`, landed verification (per-call pinned
|
|
1025
|
+
to the invoking tree, task 1058) — but no WT-4a/4b/4c and no removal: the worktree and branch are
|
|
1026
|
+
**retained**. Pre-merge halts leave the marker `active`; a persistence failure after the landed
|
|
1027
|
+
merge records `retained` + `mergeCommit`:
|
|
933
1028
|
|
|
934
1029
|
```bash
|
|
935
|
-
|
|
936
|
-
git merge --ff-only
|
|
937
|
-
|
|
1030
|
+
# Same fail-stop sequence as create mode (zero-commit guard -> BATCH_TIP/BASE_TIP -> ancestry
|
|
1031
|
+
# rc check -> git merge --ff-only -> landed verify); local refs only — no fetch.
|
|
1032
|
+
MARKER=".spur/run/worktree-<marker-id>.json"
|
|
1033
|
+
git checkout "$BASE_REF" \
|
|
1034
|
+
|| { echo "WT-4 halt: checkout $BASE_REF failed - nothing merged, marker stays \"active\"" >&2; exit 1; } # -> WT-5
|
|
1035
|
+
[ "$(git rev-list --count "$BASE_REF..$BRANCH")" -gt 0 ] \
|
|
1036
|
+
|| { echo "WT-4 halt: branch carries no commits - nothing to merge" >&2; exit 1; }
|
|
1037
|
+
BATCH_TIP="$(git rev-parse "$BRANCH")" || { echo "WT-4 halt: cannot read $BRANCH" >&2; exit 1; }
|
|
1038
|
+
BASE_TIP="$(git rev-parse "$BASE_REF")" || { echo "WT-4 halt: cannot read $BASE_REF" >&2; exit 1; }
|
|
1039
|
+
ANCESTRY_RC=0
|
|
1040
|
+
git merge-base --is-ancestor "$BASE_TIP" "$BATCH_TIP" || ANCESTRY_RC=$?
|
|
1041
|
+
[ "$ANCESTRY_RC" -gt 1 ] \
|
|
1042
|
+
&& { echo "WT-4 halt: ancestry check could not run (git exit $ANCESTRY_RC) - failing closed" >&2; exit 1; }
|
|
1043
|
+
[ "$ANCESTRY_RC" -eq 1 ] && {
|
|
1044
|
+
echo "WT-4 halt: divergent - batch tip $BATCH_TIP vs base tip $BASE_TIP" >&2
|
|
1045
|
+
git log --oneline "$BASE_TIP..$BATCH_TIP" >&2
|
|
1046
|
+
git log --oneline "$BATCH_TIP..$BASE_TIP" >&2
|
|
1047
|
+
exit 1; } # -> WT-5: tree + branch retained, marker stays "active"
|
|
1048
|
+
git merge --ff-only "$BRANCH" # FF-only: never rebase, merge-commit, or resolve conflicts
|
|
1049
|
+
[ $? -eq 0 ] \
|
|
1050
|
+
|| { echo "WT-4 halt: FF-merge failed - the concurrent-writer race survives every precheck; marker stays \"active\"" >&2; exit 1; }
|
|
1051
|
+
LANDED_BASE_TIP="$(git rev-parse "$BASE_REF")" \
|
|
1052
|
+
|| { echo "WT-4 halt: cannot read landed base tip" >&2; exit 1; }
|
|
1053
|
+
git merge-base --is-ancestor "$BATCH_TIP" "$LANDED_BASE_TIP" \
|
|
1054
|
+
|| { echo "WT-4 halt: landed verify failed - $BATCH_TIP is not an ancestor of $LANDED_BASE_TIP" >&2; exit 1; }
|
|
1055
|
+
WT_PATH="<resolved-worktree-path>" # reuse mode: the adopted tree's physical path
|
|
1056
|
+
write_marker () { # $1 = merged | retained; read-modify-write: preserve WT-3 fields (id, command, selector, createdAt, adopted, adoptedAt); jq already assumed by WT-3/task-resolution flows
|
|
1057
|
+
mkdir -p "$(dirname "$MARKER")"
|
|
1058
|
+
if [ -f "$MARKER" ]; then
|
|
1059
|
+
TMP_MARKER="$(mktemp)"
|
|
1060
|
+
jq --arg s "$1" --arg m "$BATCH_TIP" '.status = $s | .mergeCommit = $m' "$MARKER" > "$TMP_MARKER" \
|
|
1061
|
+
|| { echo "WT-4 halt: cannot rewrite marker $MARKER" >&2; rm -f "$TMP_MARKER"; exit 1; }
|
|
1062
|
+
mv "$TMP_MARKER" "$MARKER"
|
|
1063
|
+
else
|
|
1064
|
+
printf '{ "baseRef": "%s", "baseSha": "%s", "branch": "%s", "path": "%s", "status": "%s", "mergeCommit": "%s" }\n' \
|
|
1065
|
+
"$BASE_REF" "$BASE_SHA" "$BRANCH" "$WT_PATH" "$1" "$BATCH_TIP" > "$MARKER"
|
|
1066
|
+
fi
|
|
1067
|
+
}
|
|
1068
|
+
# Step 5 evidence persistence (task 0720 R3) — required BEFORE the success marker:
|
|
1069
|
+
if printf 'batch report %s\n' "$BATCH_TIP" > ".spur/run/worktree-<marker-id>-batch-report.md"; then :; else
|
|
1070
|
+
echo "WT-5 halt: persistence failed after landed merge $BATCH_TIP; recovery reads captured tips only" >&2
|
|
1071
|
+
write_marker retained
|
|
1072
|
+
exit 1
|
|
1073
|
+
fi
|
|
1074
|
+
# Success marker: status "merged" + mergeCommit "$BATCH_TIP" — written once, only after landed verification and required persistence.
|
|
1075
|
+
# The worktree and branch are intentionally NOT removed.
|
|
1076
|
+
write_marker merged
|
|
938
1077
|
```
|
|
939
1078
|
|
|
940
1079
|
The operator supplied the tree, so the operator owns its lifetime. After a green reuse batch
|
|
941
1080
|
`baseRef == $BRANCH`, so the same worktree keeps fast-forwarding on the next invocation instead of
|
|
942
1081
|
having to be rebuilt — the continue-the-work loop is stable.
|
|
943
1082
|
|
|
944
|
-
**Fast-forward only.**
|
|
945
|
-
|
|
946
|
-
|
|
1083
|
+
**Fast-forward only.** `git merge --ff-only` is the sole mutation of the terminal sequence; every
|
|
1084
|
+
other step is a read, a check, a persistence write, or a cleanup, and every tip/ancestry read uses
|
|
1085
|
+
local refs — no fetch runs anywhere in this lifecycle. If the fresh `BASE_TIP` is not an ancestor of
|
|
1086
|
+
`BATCH_TIP`, the ancestry check halts before the merge: the report names both tips and both
|
|
1087
|
+
divergent commit ranges (`$BASE_TIP..$BATCH_TIP` and `$BATCH_TIP..$BASE_TIP`) and falls to the
|
|
1088
|
+
retention path (WT-5) with the tree and branch retained. A concurrent writer that lands between the
|
|
1089
|
+
ancestry check and the merge still trips `--ff-only` — the race survives every precheck, and a
|
|
1090
|
+
failed FF leaves the marker at `status: active`. Do **not** rebase, merge-commit, or resolve
|
|
1091
|
+
conflicts. The corpus files (`docs/tasks*/`, kanban/index) are
|
|
947
1092
|
auto-generated and conflict-prone; automated conflict resolution over generated files is exactly the
|
|
948
1093
|
wrong thing to attempt unattended. FF-only means the merge either is trivially correct or does not
|
|
949
1094
|
happen.
|
|
@@ -952,8 +1097,10 @@ happen.
|
|
|
952
1097
|
`--worktree --auto` batch — it does **not** pause even though it performs a merge and a branch
|
|
953
1098
|
deletion. That is the explicit single exception to Auto-Decision Principle #6 (`cross-cutting.md`),
|
|
954
1099
|
which otherwise pauses any `--merge` / branch-deletion action regardless of `--auto`. The exception
|
|
955
|
-
is safe because `git merge --ff-only`
|
|
956
|
-
|
|
1100
|
+
is safe because `git merge --ff-only` fails closed (it refuses rather than risk losing work), the
|
|
1101
|
+
branch deletion runs only after the merge has landed, persisted, and cleaned up — and is
|
|
1102
|
+
existence-guarded (`git rev-parse --verify --quiet`), so a removed branch is never queried;
|
|
1103
|
+
WT-5 retains the worktree and branch whenever FF is impossible or any task fails.
|
|
957
1104
|
Reuse mode is **narrower** than the carve-out (it merges but does not delete the branch), so the
|
|
958
1105
|
carve-out text needs no widening.
|
|
959
1106
|
|
|
@@ -982,22 +1129,44 @@ record and the persisted run artifacts are the evidence record.
|
|
|
982
1129
|
|
|
983
1130
|
### WT-5 — Failure path: retain and report (R5)
|
|
984
1131
|
|
|
985
|
-
On any per-task failure, batch halt, HITL pause that ends the run, or
|
|
986
|
-
worktree directory and branch are left **intact**. No destructive automation on this path under
|
|
987
|
-
flag combination (`--auto`, `--force`, `--keep-going` — all leave the worktree in place).
|
|
988
|
-
marker
|
|
989
|
-
|
|
1132
|
+
On any per-task failure, batch halt, HITL pause that ends the run, or a WT-4 halt, the
|
|
1133
|
+
worktree directory and branch are left **intact**. No destructive automation on this path under
|
|
1134
|
+
any flag combination (`--auto`, `--force`, `--keep-going` — all leave the worktree in place). The
|
|
1135
|
+
marker records which class of halt occurred (frozen vocabulary):
|
|
1136
|
+
|
|
1137
|
+
- **Pre-merge failure** (failed checkout, zero-commit guard, divergent base, failed FF): the merge
|
|
1138
|
+
never landed, so the marker **stays at `status: active`** — the WT-3 marker stands and WT-5 is
|
|
1139
|
+
its disposition. Nothing was merged onto the base ref.
|
|
1140
|
+
- **Landed-but-incomplete failure** (persist-out, holder cleanup, or worktree removal failed after
|
|
1141
|
+
a landed merge): the marker records `status: retained` + `mergeCommit` BATCH_TIP — a truthful
|
|
1142
|
+
report that the merge landed. The retained path reads the captured `BATCH_TIP` only; a removed
|
|
1143
|
+
branch is never queried.
|
|
1144
|
+
|
|
1145
|
+
The worktree's own `.spur` lifecycle DB is retained with the tree,
|
|
1146
|
+
so nothing is lost on either path (see the WT-4 lifecycle-DB disposition for the merged case —
|
|
990
1147
|
task 0701 R2d). Emit a retention report in the existing halt-report shape:
|
|
991
1148
|
|
|
1149
|
+
**Persist-out skip recovery (task 1067 R2, E71).** When evidence was lost to a landing that
|
|
1150
|
+
skipped persist-out (or the pre-removal assertion fired too late), verdicts remain derivable
|
|
1151
|
+
from the task records — `spur task show <wbs> --json` carries the recorded verdict and its
|
|
1152
|
+
proof block, so a lost `<wbs>-verdict.json` is regenerated by re-recording or copying from
|
|
1153
|
+
the task store. Feature run/latest receipts are re-runnable, not recovered: rerun the
|
|
1154
|
+
feature-verification workflow in the invoking tree —
|
|
1155
|
+
`spur workflow run feature-verification.yaml --vars '{"featureId":"<id>"}'` — which rewrites
|
|
1156
|
+
the receipt files it owns. Divergent files are never auto-overwritten; reconcile by hand.
|
|
1157
|
+
|
|
992
1158
|
```
|
|
993
1159
|
## Worktree retained — <command> <selector>
|
|
994
1160
|
|
|
995
|
-
**Halt cause:** <one-line cause — batch halted at task <wbs> / non-FF base ref / HITL pause>
|
|
1161
|
+
**Halt cause:** <one-line cause — batch halted at task <wbs> / non-FF base ref / HITL pause / landed-but-incomplete cleanup failure>
|
|
996
1162
|
**Worktree path:** ../<worktree-dir>
|
|
997
1163
|
**Branch:** sp/<command>-<selector-slug>-<short-id>
|
|
998
1164
|
**Base ref:** <base-ref> (<base-sha>)
|
|
1165
|
+
**Merge state:** pre-merge failure — nothing landed | landed-but-incomplete — mergeCommit <batch-tip-sha>
|
|
999
1166
|
|
|
1000
|
-
The worktree and its branch are intact.
|
|
1167
|
+
The worktree and its branch are intact.
|
|
1168
|
+
pre-merge failure: nothing was merged onto the base ref.
|
|
1169
|
+
landed-but-incomplete: the batch merge LANDED as <batch-tip-sha> on <base-ref>; retention covers post-merge persistence/cleanup only.
|
|
1001
1170
|
Resume, merge, or discard:
|
|
1002
1171
|
|
|
1003
1172
|
resume: cd <worktree-path> && <command> --continue --worktree <worktree-path>
|
|
@@ -1030,6 +1199,16 @@ are long and already resumable via `--continue`; auto-deleting is data loss, aut
|
|
|
1030
1199
|
partial result presented as a whole. The answer to "what happens if it fails" is "nothing happens,
|
|
1031
1200
|
and we tell you where the work is."
|
|
1032
1201
|
|
|
1202
|
+
**Partial worktree-removal recovery is inspection-only.** If a create-mode run halted partway
|
|
1203
|
+
through removal (leftover directory, stale registration), the recovery pass gathers facts and
|
|
1204
|
+
nothing else: `git worktree list` (is the tree still registered?), `ls` of the path (what remains
|
|
1205
|
+
on disk?), and the marker / `~/.config/spur/projects.json` registration state. Before removing
|
|
1206
|
+
anything, verify the leftover path is the batch's **owned** path — match it against the marker's
|
|
1207
|
+
`path`/`branch` (or the resolved `--worktree <name>` identity), never a guessed sibling directory.
|
|
1208
|
+
Recursive deletion of leftover directories that survived `git worktree remove` is **never
|
|
1209
|
+
automatic** — it requires explicit operator authorization. No automatic rebase, recovery merge,
|
|
1210
|
+
`git worktree prune`, or holder kill runs on this path.
|
|
1211
|
+
|
|
1033
1212
|
### WT-6 — `--continue` re-entry (R7)
|
|
1034
1213
|
|
|
1035
1214
|
A `--continue` resume of a batch started with `--worktree` must re-enter the existing worktree via
|
|
@@ -1172,7 +1351,7 @@ integrate(t):
|
|
|
1172
1351
|
├─ rebase fails → git -C "$WT" rebase --abort → conflict path (R4) below
|
|
1173
1352
|
└─ rebase clean → git checkout "$BASE_REF" → zero-commit guard → git merge --ff-only "$BRANCH"
|
|
1174
1353
|
→ WT-4a verdict persist → WT-4b holders → WT-4c registry
|
|
1175
|
-
→ git worktree remove → git branch -
|
|
1354
|
+
→ git worktree remove → existence-guarded git branch -D → marker merged
|
|
1176
1355
|
```
|
|
1177
1356
|
|
|
1178
1357
|
The merge is always `--ff-only`: a base that will not fast-forward after a clean rebase is a
|
|
@@ -203,7 +203,9 @@ When `--next` is set and implementation succeeds:
|
|
|
203
203
|
2. **Record provenance** — `spur task run-link <wbs> --source next-auto --json`. Writes a
|
|
204
204
|
`kind: pipeline` entry into `task_run_links` so the `testing → done` provenance guard
|
|
205
205
|
(lifecycle-adapter.ts L106-131) accepts the in-session implementation path. Idempotent:
|
|
206
|
-
safe to call even when a pipeline link already exists.
|
|
206
|
+
safe to call even when a pipeline link already exists. Run it from the same cwd as the
|
|
207
|
+
`task update` calls in step 1 — per-tree DB isolation makes a link from another tree
|
|
208
|
+
invisible to this tree's guard.
|
|
207
209
|
3. **On a clean transition:** invoke `/sp:dev-verify <wbs> --auto --next` (`--auto` propagates
|
|
208
210
|
down the whole chain). The verify step's `--next` transition to `done` now passes the
|
|
209
211
|
provenance guard because step 2 recorded the link.
|
|
@@ -17,17 +17,16 @@ patterns, skill-backed, inline) remains in `dev-operations.md`.
|
|
|
17
17
|
|
|
18
18
|
Every flag shared by two or more commands has exactly one entry here. This is the single place a
|
|
19
19
|
shared flag's meaning is stated; command argument-hints and table rows cite it and must not
|
|
20
|
-
re-define it. The shared-flag set is derived mechanically from
|
|
20
|
+
re-define it. The shared-flag set is derived mechanically from all live `dev-*` command argument-hints (a flag
|
|
21
21
|
counts as shared when it appears in two or more); flags appearing on a single command stay documented
|
|
22
22
|
in that command's body and are not listed here.
|
|
23
23
|
|
|
24
|
-
**Reference form (mechanically detectable).**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
in the link form) do not count; the reference must be the link.
|
|
24
|
+
**Reference form (mechanically detectable).** Each `dev-*` command carries exactly one canonical
|
|
25
|
+
footer link to `../skills/spur-dev/references/flag-glossary.md`, enforced by
|
|
26
|
+
`scripts/commands/validate-commands.ts` (task 0412). Per-flag deep links are not required.
|
|
27
|
+
`command-flag-parity.test.ts` derives shared flags from all live hints and requires exactly one
|
|
28
|
+
canonical glossary entry per shared flag. Each entry retains its `#flag-<name>` anchor, where
|
|
29
|
+
`<name>` is the flag minus its leading `--` (so `--keep-going` → `#flag-keep-going`), for navigation.
|
|
31
30
|
|
|
32
31
|
**Availability rule.** Two flags are only declared where the underlying capability already exists:
|
|
33
32
|
`--json` where the command already produces a structured result a script could consume; `--auto`
|
|
@@ -75,6 +75,9 @@ the human/native presentation layer — labels are display addresses only, never
|
|
|
75
75
|
2. Allocate a collision-resistant inline run id (`uuidgen`, with a timestamp/pid fallback), create
|
|
76
76
|
`.spur/run/` for attempt staging and `.spur/memory/runs/` for retained records, and use the two-file run record (task 0927) — append lines to
|
|
77
77
|
`.spur/memory/runs/<run-id>.md` and read machine state from `.spur/memory/runs/<run-id>.state.json`.
|
|
78
|
+
The run id lives in those two files and the persisted run row — never stage it in an ad-hoc
|
|
79
|
+
`*.env` file: the `no-env-files` gate rule flags any `.env` name, so run-scoped staging files
|
|
80
|
+
use `<run-id>-*` names with `.md`/`.json`/`.txt`/`.status` extensions only.
|
|
78
81
|
3. **Authoritative run identity (task 0804 R1, fail-closed).** Persist the run row through the
|
|
79
82
|
internal delegate before any stage executes — this is what makes bound `run.artifact` record
|
|
80
83
|
accept the inline run (0785 R3):
|
|
@@ -139,7 +142,10 @@ the human/native presentation layer — labels are display addresses only, never
|
|
|
139
142
|
safety checks succeed, create/adopt and `cd` into the execution tree; confirm absolute cwd,
|
|
140
143
|
branch, base SHA, and ownership. An invalid/empty target, unsupported mode, ambiguous ownership,
|
|
141
144
|
or stale target stops without creating a tree or discarding work. All subsequent tools, agents,
|
|
142
|
-
corpus writes, and run artifacts use the confirmed execution tree
|
|
145
|
+
corpus writes, and run artifacts use the confirmed execution tree — **re-selected per call, not
|
|
146
|
+
inherited**: a host shell call starts in an arbitrary directory and carries no cwd from the
|
|
147
|
+
previous call, so every later call runs inside the **Per-call execution-tree pin** protocol
|
|
148
|
+
below (task 1058), which consumes the identity confirmed here.
|
|
143
149
|
8. **Publish the workflow inventory (R4), BEFORE reading the YAML.** Resolve the selected workflow
|
|
144
150
|
through the same project/bundled resolver as execution and run
|
|
145
151
|
`spur workflow show <resolved-file> --no-logo --format todo --json`. Validate the projection with
|
|
@@ -183,6 +189,12 @@ the human/native presentation layer — labels are display addresses only, never
|
|
|
183
189
|
This is required for the normal `testing → done` provenance guard. Planning pipelines have no
|
|
184
190
|
task lifecycle link and skip this task-specific action.
|
|
185
191
|
|
|
192
|
+
**Run it from the task's execution tree.** Every tree has an isolated `.spur` DB, so a link
|
|
193
|
+
recorded from the invoking tree never satisfies a worktree's guard — the exact failure that
|
|
194
|
+
forced audited `--provenance-bypass` on tasks 1029/1030 (E93). Under `--worktree`, enter the
|
|
195
|
+
worktree first and run this command (and every lifecycle-touching command: `task record`,
|
|
196
|
+
`task update <wbs> testing|done`) from the worktree root.
|
|
197
|
+
|
|
186
198
|
### Idea-pipeline quick start (0887 R1/R2)
|
|
187
199
|
|
|
188
200
|
Minimum files to read for `/sp:dev-idea` inline runs — then drive `idea-pipeline.yaml`:
|
|
@@ -212,6 +224,91 @@ Expected artifacts per stage (all run-scoped under `.spur/run/<run-id>-*`):
|
|
|
212
224
|
| ready-prepare | `-idea-ready.json` |
|
|
213
225
|
| handoff-finalize | `-idea-handoff.md` |
|
|
214
226
|
|
|
227
|
+
## Per-call execution-tree pin (task 1058)
|
|
228
|
+
|
|
229
|
+
A host shell tool call may start in an arbitrary directory; no earlier call's `cd` is input to the
|
|
230
|
+
next call. During inline run `95522d21` that drift silently landed corpus writes in the invoking
|
|
231
|
+
tree instead of the run worktree. Every host-session corpus command — reads, writes, absolute
|
|
232
|
+
section-input files and output artifacts — therefore selects the confirmed execution tree **within
|
|
233
|
+
the same tool call**, in a self-contained subshell. The identity is never re-derived: it is the
|
|
234
|
+
tree path, branch and resolved Spur invocation confirmed at bootstrap isolation (step 7) and, for
|
|
235
|
+
batch worktrees, recorded in the WT-3 marker (execution-batch.md). The resolved invocation is
|
|
236
|
+
field 4 of the dispatch payload — never a bare `spur`.
|
|
237
|
+
|
|
238
|
+
Template — every host shell call follows this shape; paths are quoted because trees may contain
|
|
239
|
+
spaces:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
(
|
|
243
|
+
cd -- "$SPUR_TREE" || { echo "tree missing: expected $SPUR_TREE" >&2; exit 1; }
|
|
244
|
+
ACTUAL_TREE="$(pwd -P)"
|
|
245
|
+
[ "$ACTUAL_TREE" = "$SPUR_TREE" ] || { echo "tree mismatch: expected $SPUR_TREE, got $ACTUAL_TREE" >&2; exit 1; }
|
|
246
|
+
GIT_TOP="$(git rev-parse --show-toplevel)" || exit 1
|
|
247
|
+
[ "$GIT_TOP" = "$SPUR_TREE" ] || { echo "toplevel mismatch: expected $SPUR_TREE, got $GIT_TOP" >&2; exit 1; }
|
|
248
|
+
ACTUAL_BRANCH="$(git branch --show-current)"
|
|
249
|
+
[ "$ACTUAL_BRANCH" = "$SPUR_BRANCH" ] || { echo "branch mismatch: expected $SPUR_BRANCH, got $ACTUAL_BRANCH" >&2; exit 1; }
|
|
250
|
+
exec <resolved-spur-invocation> task show <wbs> --json # the already-resolved CLI command
|
|
251
|
+
)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Rules (R1):
|
|
255
|
+
|
|
256
|
+
- **Pin reads as well as writes.** `task show`, `feature show`, `workflow show` and `task check`
|
|
257
|
+
use the same subshell — a read from the wrong tree is a wrong answer, not a harmless one.
|
|
258
|
+
- **Validate the returned `filePath`.** A pinned `task show --json` must return a `filePath`
|
|
259
|
+
inside the expected configured corpus/tree. Configured task folders may intentionally live
|
|
260
|
+
outside the default `docs/tasks` — check membership in the configured folder set, not one
|
|
261
|
+
literal path.
|
|
262
|
+
- **Prove sections with a fresh pinned read.** After a section write, re-run a pinned
|
|
263
|
+
`task show <wbs> --json` and assert the new body there; a heading-only grep is not proof of
|
|
264
|
+
section contents.
|
|
265
|
+
- **Resolve section input/output paths before changing tree.** `--section <name> --from-file
|
|
266
|
+
<path>`, `answerFile`/`expectFile` and run artifacts are resolved against the confirmed tree
|
|
267
|
+
(dispatch field 5) before the subshell `cd`s.
|
|
268
|
+
- **Fail closed before writing.** A failed `cd` or any identity mismatch exits nonzero naming
|
|
269
|
+
expected vs actual, before the CLI runs — there is no designated exit code; consumers read
|
|
270
|
+
nonzero and the named expected/actual trees. A PWD assertion alone cannot select a tree: `cd`
|
|
271
|
+
first, then verify the physical path and repository identity.
|
|
272
|
+
- **Native tool cwd options are additive.** A tool-level cwd/`cd` pin may additionally set the
|
|
273
|
+
call's directory, but never replaces the identity checks above.
|
|
274
|
+
- **Host-only scope.** Engine `shell` actions already bind `context.workdir`
|
|
275
|
+
(`packages/app/src/workflow/actions/shell.ts:98`) — leave them alone. No service-wide
|
|
276
|
+
`process.chdir`, no new public flag, no helper framework.
|
|
277
|
+
|
|
278
|
+
The `spur …` command examples in this driver (run setup, workflow inventory, `task run-link`,
|
|
279
|
+
fingerprint capture, record/done sequencing) abbreviate the pinned subshell form for readability;
|
|
280
|
+
each executes inside the template above with the confirmed tree, branch and resolved invocation
|
|
281
|
+
substituted. A dispatched subagent inherits the same duty: its own tool calls may each start in
|
|
282
|
+
an arbitrary directory, so every shell call the delegate makes re-pins the execution tree with
|
|
283
|
+
the identity supplied in the dispatch payload — never re-derived.
|
|
284
|
+
|
|
285
|
+
## Chunked implement dispatch contract (cap-limited worker models, task 1066)
|
|
286
|
+
|
|
287
|
+
Cap-limited worker executors die on monolithic implement briefs: the output cap is consumed by
|
|
288
|
+
one giant thinking block before any file action lands. Recognized failure signature —
|
|
289
|
+
consecutive implement dispatches end with `stopReason=length` and **zero output** (no tool
|
|
290
|
+
call, no file write), optionally with a `contact_supervisor` call mid-death. The trigger is
|
|
291
|
+
the observed signature, never a model name: any executor that exhibits it is treated as
|
|
292
|
+
cap-limited for the rest of the run.
|
|
293
|
+
|
|
294
|
+
Before dispatching an `implement` stage to a worker executor:
|
|
295
|
+
|
|
296
|
+
1. **Chunk the brief.** Split the implement scope into small, independent dispatches ordered
|
|
297
|
+
by dependency (e.g. test cases → doc rewrite → task sections), one bounded objective per
|
|
298
|
+
dispatch. Never pack the whole task plan into one dispatch.
|
|
299
|
+
2. **Cap thinking.** Instruct the worker to keep every thinking block under ~150 words and act
|
|
300
|
+
between thoughts; never plan the whole task in one block.
|
|
301
|
+
3. **Fallback ladder.** A worker still dying with the signature: re-chunk the remaining scope
|
|
302
|
+
smaller once; still failing → execute the stage host-inline (the driver's inline stage
|
|
303
|
+
fallback) and record the fallback in the run log.
|
|
304
|
+
|
|
305
|
+
Evidence (2026-10-02, task 1059 implement stage, runall-D63-2ebbd97c): 4/4 single-shot
|
|
306
|
+
implement dispatches died `stopReason=length` on a 16k-output-cap worker; the same scope in
|
|
307
|
+
three chunked dispatches with the thinking cap succeeded 3/3. See the pitfalls 2026-10-02
|
|
308
|
+
entry and `.spur/memory/runs/runall-D63-2ebbd97c.md`.
|
|
309
|
+
|
|
310
|
+
## Comprehensive-check retention and evidence (R7/R8)
|
|
311
|
+
|
|
215
312
|
## Comprehensive-check retention and evidence (R7/R8)
|
|
216
313
|
|
|
217
314
|
**R7 — comprehensive checks stay at their owning boundaries.** Quick readiness and plan projection are
|
|
@@ -293,7 +390,8 @@ Action semantics come from the YAML and the workflow action contract:
|
|
|
293
390
|
`verify-verdict`: verdict `PASS`), `proofBinding: current` is honored against a freshly captured
|
|
294
391
|
proof digest — capture it with `bun "$SETUP_SCRIPT" --fingerprint --task-file <task path>
|
|
295
392
|
[--feature-file <feature path>]`, the same entry point used at Run setup, which prints the engine
|
|
296
|
-
`sha256:<hex>` digest. Run it
|
|
393
|
+
`sha256:<hex>` digest. Run it inside a per-call pin subshell for the confirmed execution tree
|
|
394
|
+
(the cwd feeds the git-tree half of the digest)
|
|
297
395
|
and pass the same `--feature-file` the run folded in — omitting it, or running from elsewhere,
|
|
298
396
|
yields a different digest and the mismatch surfaces later as a refused `run.artifact`
|
|
299
397
|
registration — and the run-scoped review-completion marker exists — then appends one provenance
|
|
@@ -311,6 +409,10 @@ Action semantics come from the YAML and the workflow action contract:
|
|
|
311
409
|
|
|
312
410
|
**Native-subagent dispatch (R2 eligibility, evaluated before each action):**
|
|
313
411
|
|
|
412
|
+
Before dispatching an `implement` stage to a worker executor, apply the chunked implement
|
|
413
|
+
dispatch contract (§ Chunked implement dispatch contract): chunk the brief, cap thinking
|
|
414
|
+
blocks, and know the fallback ladder before spending a dispatch.
|
|
415
|
+
|
|
314
416
|
1. The invocation is one of the two interactive inline full-pipeline surfaces (`dev-run --mode full`
|
|
315
417
|
or sequential `dev-runall`) and the resolved selector is inline — i.e. `--agent` **omitted**
|
|
316
418
|
(0687 R1 default) or `--agent inline` passed explicitly (0687 R2 generalized from omit-only).
|
|
@@ -388,7 +490,10 @@ boundary, and a delegate left to re-derive them re-derives them against its own
|
|
|
388
490
|
that invocation and MUST NOT rely on a bare `spur`: a competing `spur` earlier on the delegate's
|
|
389
491
|
PATH otherwise wins. Setting `SPUR_BIN` alone does **not** change bare-command resolution — only
|
|
390
492
|
using the supplied invocation does. Spur-owned scripted calls take it through the existing
|
|
391
|
-
`--spur-bin` flag rather than a new mechanism.
|
|
493
|
+
`--spur-bin` flag rather than a new mechanism. A delegate's tool calls may each start in an
|
|
494
|
+
arbitrary directory, so every shell call the delegate makes re-pins the execution tree per the
|
|
495
|
+
**Per-call execution-tree pin** protocol using this supplied identity — never an inherited cwd
|
|
496
|
+
and never a re-derivation.
|
|
392
497
|
5. The **resolved absolute output path** (`answerFile`/`expectFile`, resolved as above) and the
|
|
393
498
|
**owning stage's artifact contract** — for a verify stage, the compact contract below.
|
|
394
499
|
6. **The implement-stage acceptance-evidence requirement** — for the implement stage and any stage
|
|
@@ -633,6 +738,8 @@ tasks (0617, 0619) because the sections were hand-written **before** the verdict
|
|
|
633
738
|
```bash
|
|
634
739
|
# verdict artifact first (shape: {wbs, verdict, requirements:[{id,status,evidence}], checks:[], source})
|
|
635
740
|
# then the record hop; then re-write Testing/Solution if record's backfill is thinner than intended.
|
|
741
|
+
# Both calls are per-call pinned (task 1058): the record hop selects the execution tree inside
|
|
742
|
+
# the same tool call — never through a cwd a previous call happened to leave behind.
|
|
636
743
|
spur task update <wbs> wip --no-lifecycle
|
|
637
744
|
spur task record <wbs> --solution-from-diff --transition testing
|
|
638
745
|
```
|