@gobing-ai/spur 0.4.27 → 0.4.28
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/workflows/task-pipeline.yaml +63 -4
- package/package.json +1 -1
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/code-implementation/SKILL.md +6 -0
- package/plugins/sp/skills/code-testing/SKILL.md +7 -0
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +2 -2
- package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +2 -2
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +11 -0
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +10 -6
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +15 -0
- package/spur.js +627 -131
|
@@ -139,6 +139,15 @@ vars:
|
|
|
139
139
|
# The gate observes, it does not mutate: `implement` already ran `$formatCmd` on its way out,
|
|
140
140
|
# and a formatter inside the gate would rewrite the tree the receipt is keyed by.
|
|
141
141
|
qualityGateCmd: "${spurBin:-spur} self exec check"
|
|
142
|
+
# 1319 R1: per-spawn bound for EVERY command the gate runs (full attempt, recheck probe, light
|
|
143
|
+
# sub-checks, changed-path read). `command.gate` exports workflow vars as env, so this var reaches
|
|
144
|
+
# `spur task gate` as `gateTimeoutMs` with no new flag. A hit is BLOCKED (exit 2), never a red
|
|
145
|
+
# gate, and writes no check receipt. 15 min: above a healthy full gate, far below a hang.
|
|
146
|
+
gateTimeoutMs: "900000"
|
|
147
|
+
# 1319 R2(c): outer backstop on the gate ACTION itself. Only a finite `timeoutMs` gives
|
|
148
|
+
# `ts-runtime` process-group ownership, which reaps grandchildren the gate's own `spawnSync`
|
|
149
|
+
# cannot reach. Must stay above lock wait (2 h, quality-gate.ts) + 5 x gateTimeoutMs + probe.
|
|
150
|
+
gateBackstopMs: "14400000"
|
|
142
151
|
# Cheap red-detector run before the full gate on **recheck visits only**; empty ⇒ no probe.
|
|
143
152
|
# A project overriding qualityGateCmd should override this too. TRUSTED CONFIG ONLY.
|
|
144
153
|
gateProbeCmd: "${spurBin:-spur} self exec lint"
|
|
@@ -163,6 +172,9 @@ vars:
|
|
|
163
172
|
# anchors directly in the /sp:dev-fixall input, so the fix agent starts AT the failing
|
|
164
173
|
# file:line instead of re-deriving it from a fresh gate run (0482 R3).
|
|
165
174
|
gateFindings: ""
|
|
175
|
+
# 1319 R5: lint findings from the previous review visit, echoed into the review prompt on the
|
|
176
|
+
# inline retry. Empty on the happy path, so the prompt pays nothing when nothing is wrong.
|
|
177
|
+
reviewLint: ""
|
|
166
178
|
# The quality-gate mode this visit runs — `run` on the first gate visit of the run,
|
|
167
179
|
# `recheck` after a remediation hop, `deferred` under the opt-in batch policy. Written by
|
|
168
180
|
# the gate's own shell and projected into this var (args cannot branch) — D65 1239 R2.
|
|
@@ -332,9 +344,19 @@ states:
|
|
|
332
344
|
# Best-effort: `; exit 0` (not `&&`) so a missing/failing formatter in a seeded
|
|
333
345
|
# non-Bun project cannot abort a run mid-implement. `${vars.qualityGateCmd}` at
|
|
334
346
|
# `gate` is the gate that actually decides.
|
|
335
|
-
|
|
347
|
+
# 1319 R3: the formatter is a bounded `command.gate` instead of an unbounded `shell`.
|
|
348
|
+
# Best-effort semantics stay (`softFail: true`: a missing/failing/timed-out formatter
|
|
349
|
+
# never aborts the run — the quality gate is the real gate), but a formatter that hangs
|
|
350
|
+
# can no longer hold the step open forever. `shell` cannot carry a bound upstream
|
|
351
|
+
# (ts-runtime PipeProcessOptions has no timeout), so the step kind changes, not the intent.
|
|
352
|
+
- kind: command.gate
|
|
336
353
|
options:
|
|
337
|
-
|
|
354
|
+
id: implement-format
|
|
355
|
+
executable: "sh"
|
|
356
|
+
args: ["-c", "${vars.formatCmd}"]
|
|
357
|
+
resultFile: .spur/run/${vars.__runId}-implement-format.status
|
|
358
|
+
softFail: true
|
|
359
|
+
timeoutMs: "${vars.stepTimeoutMs}"
|
|
338
360
|
# 1109 R3: qualify this task's unique basename anchors to repo-relative paths.
|
|
339
361
|
# Placement is load-bearing: it runs AFTER implement's mutations (and the format
|
|
340
362
|
# above) and BEFORE the gate reads the tree, so the receipt is keyed to the final
|
|
@@ -518,6 +540,9 @@ states:
|
|
|
518
540
|
args: ["task", "gate", "${vars.wbs}", "${vars.gateMode}", "--no-logo"]
|
|
519
541
|
resultFile: .spur/run/${vars.__runId}-gate.status
|
|
520
542
|
softFail: true
|
|
543
|
+
# 1319 R2(c): group-owned backstop. The gate's own per-spawn bound (R1) bounds
|
|
544
|
+
# each command; this bounds the action and reaps any surviving descendant.
|
|
545
|
+
timeoutMs: "${vars.gateBackstopMs}"
|
|
521
546
|
# (d) record what the PREVIOUS gate visit's failure-class answer turned out to be
|
|
522
547
|
# (1263 R3a): a recheck visit means the last class was `fix` and the repair hop ran, so
|
|
523
548
|
# this visit's gate status says whether it was right. First write wins, the step never
|
|
@@ -635,6 +660,18 @@ states:
|
|
|
635
660
|
approval decision point. Cost at this decision point (1122): open P1-P3 review
|
|
636
661
|
findings block `done`; only P4 rows and DEFER-ed P3 rows are wrap residuals.
|
|
637
662
|
onEnter:
|
|
663
|
+
# 1319 R5(b)/Q9: surface the previous visit's verdict-lint findings for THIS visit's prompt.
|
|
664
|
+
# Empty on the first visit (and after a clean lint), capped so the prompt stays inside its
|
|
665
|
+
# composition budget, and it names the declared R/AC ids because the lint already does.
|
|
666
|
+
- kind: shell
|
|
667
|
+
options:
|
|
668
|
+
command: >-
|
|
669
|
+
mkdir -p .spur/run; L=".spur/run/$wbs-verdict-lint.txt"; O=".spur/run/$__runId-review-lint.txt";
|
|
670
|
+
if [ -s "$L" ]; then head -c 600 "$L" > "$O"; else : > "$O"; fi; exit 0
|
|
671
|
+
- kind: file.read.into-var
|
|
672
|
+
options:
|
|
673
|
+
path: .spur/run/${vars.__runId}-review-lint.txt
|
|
674
|
+
var: reviewLint
|
|
638
675
|
- kind: agent.run
|
|
639
676
|
options:
|
|
640
677
|
# 0710 R2: review always runs on a fresh session — no implementation-session
|
|
@@ -664,7 +701,7 @@ states:
|
|
|
664
701
|
Answer contract: super-reviewer Output Format. Emit the machine-linted artifact, not a prose summary.
|
|
665
702
|
`## Review`: deriveReviewConfidence. Captured answer (`spur task verdict`) has one `Confidence: HIGH|MEDIUM|LOW`
|
|
666
703
|
line (deriveVerifyConfidence), never the section's disposition level.
|
|
667
|
-
End with one `Verdict: PASS|PARTIAL|FAIL` line
|
|
704
|
+
End with one `Verdict: PASS|PARTIAL|FAIL` line after
|
|
668
705
|
`| Req | Status | Evidence |` (one row per requirement; MET | PARTIAL | UNMET) and
|
|
669
706
|
`| AC | Status | Evidence Type | Evidence |` (one row per AC; MET | PARTIAL | UNMET | N/A).
|
|
670
707
|
Evidence types (`+` ok): test | command | static-ref | manual-review | llm-judge | n/a.
|
|
@@ -672,6 +709,7 @@ states:
|
|
|
672
709
|
a declared `AC-N` alias; a linked scenario covers its task; a task-local criterion needs no feature credit.
|
|
673
710
|
Open P1-P3 blocks completion.
|
|
674
711
|
Also write it to `.spur/run/${vars.__runId}-review-cert.md`.
|
|
712
|
+
${vars.reviewLint}
|
|
675
713
|
# The captured review answer is the run's certification input (and the
|
|
676
714
|
# `task verdict` input below): expectFile fails the step when no answer landed.
|
|
677
715
|
answerFile: .spur/run/${vars.__runId}-review-answer.txt
|
|
@@ -712,9 +750,12 @@ states:
|
|
|
712
750
|
options:
|
|
713
751
|
id: review-verdict
|
|
714
752
|
executable: "${vars.spurBin}"
|
|
715
|
-
args: ["task", "verdict", "${vars.wbs}", "--from-answer", ".spur/run/${vars.__runId}-review-cert.md"]
|
|
753
|
+
args: ["task", "verdict", "${vars.wbs}", "--from-answer", ".spur/run/${vars.__runId}-review-cert.md", "--replay"]
|
|
716
754
|
resultFile: .spur/run/${vars.__runId}-review-verdict.status
|
|
717
755
|
softFail: true
|
|
756
|
+
# 1319 R4: the replay runs up to 5 cited suites at 120 s each; the action bound is
|
|
757
|
+
# the same headroom the review hop gets, so a slow cited suite cannot pin the visit.
|
|
758
|
+
timeoutMs: "900000"
|
|
718
759
|
# (e) one fold, one token: PASS requires the typed verdict PASS, the artifact's own
|
|
719
760
|
# PASS, and a clean review gate (open P1-P3 finding or an uncited claim blocks it).
|
|
720
761
|
# Fail-closed: any missing/corrupt input leaves FAIL, so the transitions can route a
|
|
@@ -759,6 +800,14 @@ states:
|
|
|
759
800
|
options:
|
|
760
801
|
command: >-
|
|
761
802
|
mkdir -p .spur/run; A=APPROVED; if [ "$profile" = auto ]; then [ "$__hitlAnswer" = no ] && A=DENIED; else [ "$__hitlAnswer" = yes ] || A=DENIED; fi; printf '%s\n' "$A" > ".spur/run/$wbs-approval.status"; exit 0
|
|
803
|
+
# 1319 R5(b): one inline re-review token, decided AFTER every fold above so deleting the
|
|
804
|
+
# stale cert/answer cannot make a fold read the previous visit's verdict. Run-scoped, so a
|
|
805
|
+
# stale file from an earlier run can never trigger a retry; `-retried` makes it once only.
|
|
806
|
+
# A second lint failure leaves the token NONE and routes to remediate exactly as today.
|
|
807
|
+
- kind: shell
|
|
808
|
+
options:
|
|
809
|
+
command: >-
|
|
810
|
+
if [ -s ".spur/run/$wbs-verdict-lint.txt" ] && [ ! -f ".spur/run/$__runId-review-lint-retried" ]; then touch ".spur/run/$__runId-review-lint-retried"; printf 'RETRY\n' > ".spur/run/$__runId-review-lint-retry"; rm -f ".spur/run/$__runId-review-cert.md" ".spur/run/$__runId-review-answer.txt"; else printf 'NONE\n' > ".spur/run/$__runId-review-lint-retry"; fi; exit 0
|
|
762
811
|
|
|
763
812
|
- id: done
|
|
764
813
|
description: >
|
|
@@ -1050,6 +1099,16 @@ transitions:
|
|
|
1050
1099
|
kind: shell
|
|
1051
1100
|
options:
|
|
1052
1101
|
command: 'test "$(cat ".spur/run/$wbs-approval.status" 2>/dev/null)" = DENIED'
|
|
1102
|
+
# 1319 R5(c): one inline re-review when the answer LINT failed, before any remediate hop is
|
|
1103
|
+
# spent. Declared after the denial route and before `retry-exhausted` because a lint retry is
|
|
1104
|
+
# not a repair attempt (it does not consume the shared fix-attempt counter). First match wins.
|
|
1105
|
+
- from: review
|
|
1106
|
+
to: review
|
|
1107
|
+
description: The verdict lint rejected the answer's shape — re-issue the review prompt once with the lint findings instead of buying a remediate hop.
|
|
1108
|
+
guard:
|
|
1109
|
+
kind: shell
|
|
1110
|
+
options:
|
|
1111
|
+
command: 'test "$(cat ".spur/run/$__runId-review-lint-retry" 2>/dev/null)" = RETRY'
|
|
1053
1112
|
- from: review
|
|
1054
1113
|
to: failed
|
|
1055
1114
|
terminalReason: retry-exhausted
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.28",
|
|
4
4
|
"description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spur",
|
package/plugins/sp/plugin.json
CHANGED
|
@@ -191,6 +191,12 @@ Neither step takes the full project gate. The full gate is named only for the pi
|
|
|
191
191
|
creep into every plugin edit.
|
|
192
192
|
- Run the applicable rows, then stop: the single full project check belongs to the pipeline's
|
|
193
193
|
`gate` state (`task-pipeline.yaml` `${vars.qualityGateCmd}`).
|
|
194
|
+
|
|
195
|
+
**Focused runs and file mutation (1319 R6/R9).** Focused verification is `spur task gate <wbs> light` —
|
|
196
|
+
never a hand-rolled `bunfig.toml`, never the root-cwd `bun test`. Mutate with `Edit`/`Write`; a
|
|
197
|
+
`python3 - <<PY`, `sed -i` or `perl -pi` file mutation is a **defect** (it carries scaffolding through
|
|
198
|
+
the context instead of a diff, and a silent no-op replace is indistinguishable from success).
|
|
199
|
+
Contract: `docs/design/gate-bounds-and-pause-attribution.md`.
|
|
194
200
|
- **Mirrored surfaces: an edit lands on all of them.** When the change is one instance of a repeated
|
|
195
201
|
pattern — the same wiring rule on two runbooks, a signature in both a generator and its generated
|
|
196
202
|
`.d.mts`, a command chain in both `spur-check` and `spur-check:full` — grep for the siblings and
|
|
@@ -64,6 +64,13 @@ loop are the dominant verification cost on a long chain.
|
|
|
64
64
|
Do not re-run the full suite per iteration, and do not run a full gate before the narrow target is
|
|
65
65
|
green. Target: full `spur-check` runs ≤2 per task.
|
|
66
66
|
|
|
67
|
+
## Temp dirs: never under `.spur/run` (task 1319 R9)
|
|
68
|
+
|
|
69
|
+
A test that needs a scratch directory creates it under the OS temp dir (`mkdtempSync(join(tmpdir(),
|
|
70
|
+
...))`), never under `.spur/run`. That path is the run's own evidence plane: test fixtures written
|
|
71
|
+
there inflate the run directory (the credit-sentry tree reached 528 MB with 3656 entries in one
|
|
72
|
+
task's directory), slow every glob over it, and re-trigger the pipeline's dirty-tree precheck.
|
|
73
|
+
|
|
67
74
|
## Composition with the discipline + implementation skills
|
|
68
75
|
|
|
69
76
|
- **`sp:test-driven-development`** — they compose: TDD *designs* the tests (red-green-refactor, behavior naming,
|
|
@@ -607,10 +607,10 @@ principles determine which gates route around HITL and which still pause.
|
|
|
607
607
|
`--auto`. Irreversible is irreversible.
|
|
608
608
|
|
|
609
609
|
**Exception — the worktree batch success path (`execution-batch.md` § WT-4).** The
|
|
610
|
-
`--worktree --auto` full-batch success sequence — `git merge --ff-only "$BRANCH"`, `git worktree
|
|
610
|
+
`--worktree --auto` full-batch success sequence — `git merge --ff-only --no-edit "$BRANCH"`, `git worktree
|
|
611
611
|
remove`, `git branch -d "$BRANCH"` — does **not** pause, even though it performs a merge and a
|
|
612
612
|
branch deletion. This is the single carve-out from Principle #6, and it is safe by construction:
|
|
613
|
-
`git merge --ff-only` refuses rather than rewriting history when the base ref has moved, and
|
|
613
|
+
`git merge --ff-only --no-edit` refuses rather than rewriting history when the base ref has moved, and
|
|
614
614
|
`git branch -d` (lowercase) refuses to delete a branch that is not fully merged. Both fail closed,
|
|
615
615
|
so no work can be lost — the property #6 exists to protect. When FF is impossible, WT-4 falls
|
|
616
616
|
through to the WT-5 retention path, which leaves the worktree and branch intact. Every other
|
|
@@ -62,7 +62,7 @@ task from the main tree; integrations are **serialized** (one at a time) in comp
|
|
|
62
62
|
integrate(t):
|
|
63
63
|
git -C "$WT" rebase "$BASE_REF" # replay onto the CURRENT base tip (re-read per integration)
|
|
64
64
|
├─ rebase fails → git -C "$WT" rebase --abort → conflict path (R4) below
|
|
65
|
-
└─ rebase clean → git checkout "$BASE_REF" → zero-commit guard → git merge --ff-only "$BRANCH"
|
|
65
|
+
└─ rebase clean → git checkout "$BASE_REF" → zero-commit guard → git merge --ff-only --no-edit "$BRANCH"
|
|
66
66
|
→ WT-4a verdict persist → WT-4b holders → WT-4c registry
|
|
67
67
|
→ git worktree remove → existence-guarded git branch -D → marker merged
|
|
68
68
|
```
|
|
@@ -82,7 +82,7 @@ commands:
|
|
|
82
82
|
|
|
83
83
|
```
|
|
84
84
|
resume: cd <worktree-path> && git rebase <BASE_REF> # the operator resolves, never the driver
|
|
85
|
-
merge: git checkout <BASE_REF> && git merge --ff-only <branch>
|
|
85
|
+
merge: git checkout <BASE_REF> && git merge --ff-only --no-edit <branch>
|
|
86
86
|
discard: git worktree remove <worktree-path> && git branch -D <branch> && spur projects remove <worktree-path>
|
|
87
87
|
```
|
|
88
88
|
|
|
@@ -64,6 +64,17 @@ default. See the [inline-default execution-surface contract](cross-cutting.md#in
|
|
|
64
64
|
|
|
65
65
|
> **Single-run & parse discipline (suite run cost control).** Run full quality/test suites (`bun run check` / `spur-check`) at most ONCE per task iteration (task 0436 R2). Parse failure details from the single retained command output rather than re-running full suites repeatedly to inspect errors. Re-run targeted/narrow test files (e.g. `bun test <file> --test-name-pattern <pattern>`) while iterating on fixes, and re-run the full suite only when all targeted fixes pass.
|
|
66
66
|
|
|
67
|
+
## File mutation in a driven run (task 1319 R6/R9)
|
|
68
|
+
|
|
69
|
+
The driver and every dispatched implement stage mutate files with `Edit`/`Write`. A `python3 - <<PY`
|
|
70
|
+
heredoc, `sed -i` or `perl -pi` file mutation is a **defect**: it carries scaffolding through the
|
|
71
|
+
context instead of a diff, and a silent no-op replace cannot be told apart from success. The
|
|
72
|
+
credit-sentry run (2026-11-06→08) spent 291 such edits, 248 of them on source and tests, while the
|
|
73
|
+
same work needed one edit call each.
|
|
74
|
+
|
|
75
|
+
Focused verification inside implement is `spur task gate <wbs> light` — never a hand-rolled
|
|
76
|
+
`bunfig.toml` and never the root-cwd `bun test`. The full gate belongs to the pipeline's `gate` state.
|
|
77
|
+
|
|
67
78
|
## Section ownership — one writer per evidence section (F92 0593 R1)
|
|
68
79
|
|
|
69
80
|
| Section | Writer |
|
|
@@ -74,7 +74,9 @@ git merge-base --is-ancestor "$BASE_TIP" "$BATCH_TIP" || ANCESTRY_RC=$?
|
|
|
74
74
|
git log --oneline "$BASE_TIP..$BATCH_TIP" >&2 # batch-side divergent commits
|
|
75
75
|
git log --oneline "$BATCH_TIP..$BASE_TIP" >&2 # base-side divergent commits
|
|
76
76
|
exit 1; } # -> WT-5: tree + branch retained, marker stays "active"
|
|
77
|
-
git merge --ff-only "$BRANCH"
|
|
77
|
+
git merge --ff-only --no-edit "$BRANCH" # FF-only: never rebase, merge-commit, or resolve conflicts
|
|
78
|
+
# `--no-edit` is load-bearing in agent environments (task 1320): a host policy blocks a bare
|
|
79
|
+
# `git merge` because it could open an editor, even for a fast-forward that writes no message.
|
|
78
80
|
[ $? -eq 0 ] \
|
|
79
81
|
|| { echo "WT-4 halt: FF-merge failed - the concurrent-writer race survives every precheck; marker stays \"active\"" >&2; exit 1; } # -> WT-5
|
|
80
82
|
# Landed verification before anything else runs: the captured BATCH_TIP must be an ancestor of
|
|
@@ -231,14 +233,14 @@ socket, and `lsof` port discovery failing must not hide the PIDs.
|
|
|
231
233
|
#### Reuse mode — merge, retain
|
|
232
234
|
|
|
233
235
|
The fail-stop merge sequence runs identically — same guard, captured `BATCH_TIP`/`BASE_TIP`,
|
|
234
|
-
ancestry check immediately before FF, `git merge --ff-only`, landed verification (per-call pinned
|
|
236
|
+
ancestry check immediately before FF, `git merge --ff-only --no-edit`, landed verification (per-call pinned
|
|
235
237
|
to the invoking tree, task 1058) — but no WT-4a/4b/4c and no removal: the worktree and branch are
|
|
236
238
|
**retained**. Pre-merge halts leave the marker `active`; a persistence failure after the landed
|
|
237
239
|
merge records `retained` + `mergeCommit`:
|
|
238
240
|
|
|
239
241
|
```bash
|
|
240
242
|
# Same fail-stop sequence as create mode (zero-commit guard -> BATCH_TIP/BASE_TIP -> ancestry
|
|
241
|
-
# rc check -> git merge --ff-only -> landed verify); local refs only — no fetch.
|
|
243
|
+
# rc check -> git merge --ff-only --no-edit -> landed verify); local refs only — no fetch.
|
|
242
244
|
MARKER=".spur/run/worktree-<marker-id>.json"
|
|
243
245
|
git checkout "$BASE_REF" \
|
|
244
246
|
|| { echo "WT-4 halt: checkout $BASE_REF failed - nothing merged, marker stays \"active\"" >&2; exit 1; } # -> WT-5
|
|
@@ -255,7 +257,9 @@ git merge-base --is-ancestor "$BASE_TIP" "$BATCH_TIP" || ANCESTRY_RC=$?
|
|
|
255
257
|
git log --oneline "$BASE_TIP..$BATCH_TIP" >&2
|
|
256
258
|
git log --oneline "$BATCH_TIP..$BASE_TIP" >&2
|
|
257
259
|
exit 1; } # -> WT-5: tree + branch retained, marker stays "active"
|
|
258
|
-
git merge --ff-only "$BRANCH"
|
|
260
|
+
git merge --ff-only --no-edit "$BRANCH" # FF-only: never rebase, merge-commit, or resolve conflicts
|
|
261
|
+
# `--no-edit` is load-bearing in agent environments (task 1320): a host policy blocks a bare
|
|
262
|
+
# `git merge` because it could open an editor, even for a fast-forward that writes no message.
|
|
259
263
|
[ $? -eq 0 ] \
|
|
260
264
|
|| { echo "WT-4 halt: FF-merge failed - the concurrent-writer race survives every precheck; marker stays \"active\"" >&2; exit 1; }
|
|
261
265
|
LANDED_BASE_TIP="$(git rev-parse "$BASE_REF")" \
|
|
@@ -301,7 +305,7 @@ invoking tree there too — after the Step 5 persistence, before the success mar
|
|
|
301
305
|
appends the same stale-links warning to the persisted batch report, and the success marker still
|
|
302
306
|
records `merged`.
|
|
303
307
|
|
|
304
|
-
**Fast-forward only.** `git merge --ff-only` is the sole mutation of the terminal sequence; every
|
|
308
|
+
**Fast-forward only.** `git merge --ff-only --no-edit` is the sole mutation of the terminal sequence; every
|
|
305
309
|
other step is a read, a check, a persistence write, or a cleanup, and every tip/ancestry read uses
|
|
306
310
|
local refs — no fetch runs anywhere in this lifecycle. If the fresh `BASE_TIP` is not an ancestor of
|
|
307
311
|
`BATCH_TIP`, the ancestry check halts before the merge: the report names both tips and both
|
|
@@ -412,7 +416,7 @@ landed-but-incomplete: the batch merge LANDED as <batch-tip-sha> on <base-ref>;
|
|
|
412
416
|
Resume, merge, or discard:
|
|
413
417
|
|
|
414
418
|
resume: cd <worktree-path> && <command> --continue --worktree <worktree-path>
|
|
415
|
-
merge: git checkout <base-ref> && git merge <branch>
|
|
419
|
+
merge: git checkout <base-ref> && git merge --no-edit <branch> # resolve conflicts manually
|
|
416
420
|
discard: git worktree remove <worktree-path> && git branch -D <branch> && spur projects remove <worktree-path>
|
|
417
421
|
```
|
|
418
422
|
|
|
@@ -187,6 +187,21 @@ generator runs. In this repo that is `apps/cli/config/` (from `bun run bundle:co
|
|
|
187
187
|
directory (observed 2026-10-12, A91 batch). Run the generator once after a create-mode install, and
|
|
188
188
|
again after a rebase or merge that touches the canonical workflow sources it is built from.
|
|
189
189
|
|
|
190
|
+
**A fresh worktree cannot commit until its tool config is trusted (task 1320).** `proto` refuses to
|
|
191
|
+
load a project's `.prototools` in a path it has not seen, so the repo's `cog` commit-msg hook cannot
|
|
192
|
+
run and WT-3b's commit exits 1 with:
|
|
193
|
+
|
|
194
|
+
Unable to proceed, the plugin for cog is configured in <worktree>/.prototools, which has not been
|
|
195
|
+
trusted, so it was not loaded.
|
|
196
|
+
|
|
197
|
+
Trust it once per created tree, best-effort and only when the project ships the file (a missing
|
|
198
|
+
`.prototools` or a `proto` that does not prompt is not an error — the commit is what proves it):
|
|
199
|
+
|
|
200
|
+
cd "../<worktree-dir>" && [ ! -f .prototools ] || proto trust .prototools
|
|
201
|
+
|
|
202
|
+
The trust decision is per absolute path, so a reuse-mode tree that was already trusted needs nothing
|
|
203
|
+
and a re-cut tree always does.
|
|
204
|
+
|
|
190
205
|
#### Reuse mode (`--worktree <name>`)
|
|
191
206
|
|
|
192
207
|
Creation is **skipped entirely**. `$BRANCH` is the resolved worktree's already-checked-out branch; a
|