okstra 0.165.4 → 0.166.1
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/docs/for-ai/skills/okstra-run.md +2 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/prompts/host-orchestration/implementation.md +3 -0
- package/runtime/prompts/profiles/_implementation-executor.md +2 -1
- package/runtime/prompts/profiles/implementation-planning.md +1 -1
- package/runtime/python/okstra_ctl/conformance.py +23 -0
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +24 -0
- package/runtime/python/okstra_ctl/path_hints.py +5 -3
- package/runtime/python/okstra_ctl/render.py +7 -0
- package/runtime/python/okstra_ctl/stage_fix_carry.py +4 -4
- package/runtime/skills/okstra-run/SKILL.md +5 -2
- package/src/commands/lifecycle/install.mjs +17 -1
|
@@ -225,12 +225,13 @@ When `task-type == implementation` and the render-args `chain-stages` CSV has 2
|
|
|
225
225
|
|
|
226
226
|
1. Re-call render-bundle with the same arguments but `--stage N` (the base commit is auto-computed by prepare from the predecessor's done `head_commit` — do not pass it by hand). The `io`-only conformance waiver·concurrent-run·git-reconcile gates apply identically to each stage's render-bundle.
|
|
227
227
|
2. As in Step 6, become the host-native Okstra lead and run that stage's Phase 1–7 inline. Phase 6's lead persistence appends that stage's `status:"done"` row to `runs/<plan-task-key>/consumers.jsonl`.
|
|
228
|
-
3. After confirming the `done` row was written, move to the next stage. Clean up context (leftover panes·finished teammates) at each stage boundary.
|
|
228
|
+
3. After confirming the `done` row was written, move to the next stage. Clean up context (leftover panes·finished teammates) at each stage boundary. A `status:"failed"` row in place of `done` means the stage ended `FAIL` — stop the queue per the FAIL branch below.
|
|
229
229
|
4. One-line report at each stage start/finish: `stage N/<total> start` / `stage N done → next K`.
|
|
230
230
|
|
|
231
231
|
Once the whole queue is consumed, end the chain and report completion.
|
|
232
232
|
|
|
233
233
|
- **Next stage not yet ready — normal termination:** When a stage in the queue is occupied by another implementation run as started/reserved and render-bundle is rejected with `--stage N already in progress or reserved by another run` (StageTargetError), this is not an exception — **terminate the chain normally** and report the remaining queue (e.g. `remaining queue: stage 4, 5 — resume with okstra-run after occupancy is released`).
|
|
234
|
+
- **Stage ended FAIL — stop the queue and report:** When a stage's synthesised verdict is `FAIL`, Phase 6 writes no carry sidecar and appends a `status:"failed"` row instead of `done`. **Stop the queue there** and report the failed stage, its report path, and the remaining queue. Do not continue to the next stage even when it is dependency-independent — later work must not be stacked on a confirmed regression. The `failed` row frees the occupancy, so `--stage <N>` re-enters that stage on its preserved worktree and branch.
|
|
234
235
|
- **Exception gate during chaining:** If render-bundle raises a concurrent-run conflict or git stale-SHA reconciliation, **stop the chain at that stage** and present the gate to the user per the Step 5 procedure. Once the user resolves it, resume the remaining queue in place. Data corruption·concurrent-occupancy conflicts are confirmed by a human — this is the safety boundary of unattended chaining.
|
|
235
236
|
|
|
236
237
|
## Forbidden patterns
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -53,5 +53,8 @@ If the anchor (`implementation_base_commit`) is reported unresolvable, run the s
|
|
|
53
53
|
### Next stage not yet ready — normal termination (not an exception gate)
|
|
54
54
|
Because of the dependency closure, the chain queue **may include a stage that another implementation run has occupied as started/reserved.** That stage's `render-bundle` is rejected with `--stage N already in progress or reserved by another run` (StageTargetError). This is **not** an exception gate needing human judgment but a "next stage not yet ready" situation. On this rejection, **terminate the chain normally** and report the remaining queue to the user (e.g. `remaining queue: stage 4, 5 — resume with okstra-run after occupancy is released`). This is a different branch from the exception gate below (data corruption·concurrent-occupancy conflict confirmation).
|
|
55
55
|
|
|
56
|
+
### Stage ended FAIL — stop the queue and report (not an exception gate)
|
|
57
|
+
When a stage's synthesised verdict is `FAIL`, Phase 6 writes no carry sidecar and appends a `status:"failed"` row in place of `done` (`prompts/profiles/_implementation-deliverable.md` "Lead post-stage persistence"). **Stop the queue at that stage** and report the failed stage, its report path, and the remaining queue (e.g. `stage 1 FAIL — remaining queue: stage 2, 3, 5; re-enter with okstra-run --stage 1 after the fix`). Do **not** continue to the next stage even when that stage is dependency-independent: an unattended chain that keeps building past a confirmed regression stacks later work on top of it. The `failed` row releases the stage's occupancy, so `--stage <N>` re-enters the same stage on its preserved worktree and branch — there is nothing to unblock by hand.
|
|
58
|
+
|
|
56
59
|
### Exception gate during chaining
|
|
57
60
|
If `render-bundle` raises Step 5's concurrent-run conflict detection (concurrent-run branch) or git stale-SHA reconciliation (git-reconcile branch), **stop the chain at that stage** and present the gate to the user exactly as Step 5 prescribes. Once the user resolves the gate, resume the chain in place (continue with the remaining queue). Data corruption·concurrent-occupancy conflicts are confirmed by a human — this is the safety boundary of unattended chaining. (Unlike the "not ready" rejection above, these two branches do not discard the queue; they wait for user resolution.)
|
|
@@ -82,7 +82,8 @@ persisted prompt lacks the heading `Coding-conventions preflight`
|
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
The file MUST NOT exist before the run starts (overwrite is refused — see `--force-stage` non-goal). **Enforced:** `validators/validate-run.py` `_validate_stage_carry_sidecar_exists` fails a run that declares `stageSidecarEvidence` without the file on disk. Transcribing the JSON into the report is not the same as writing it: `consumers` treats the carry file as the source of truth for marking the stage `done`, so a missing file leaves the stage permanently incomplete and blocks every dependent stage with a `PrepareError` — while this run reports success.
|
|
85
|
-
- **
|
|
85
|
+
- **An external Tier 3 non-PASS does NOT withhold the carry evidence.** A Tier 3 entry whose `requires` include `http`, `external`, or `db` is advisory. Its FAIL, MISSING, no result, startup failure, or credential / network / service absence gets recorded honestly — exact command, exit code, output tail, marked `ADVISORY` in `Validation evidence` — and you emit the carry evidence anyway. Only Tier 1 and Tier 2 failures withhold it. Withholding on an external result is what actually blocks the stage: the carry file is the only thing that can mark a stage `done`, the verifier re-runs that same command from the host (where a call your sandbox could not complete often passes), and a stage the verifier then PASSes can never be closed because its evidence was never written.
|
|
86
|
+
- **Reverse link (BLOCKING).** The runtime already appended a `status:"started"` row for this stage before the run began. The terminal row belongs to the lead's post-stage persistence and is verdict-gated — `status:"done"` with `carry_path` on a non-`FAIL` verdict, `status:"failed"` on `FAIL` (`_implementation-deliverable.md` §"Lead post-stage persistence").
|
|
86
87
|
- **No PR / push in this phase.** This run produces local commits, carry sidecar evidence, verifier results, and the implementation final report only. Push and PR creation belong exclusively to the later `release-handoff` phase after `final-verification` returns `accepted`.
|
|
87
88
|
|
|
88
89
|
## Allowed actions during the run
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
unavailable environment is a user-owned follow-up, never a plan approval or
|
|
133
133
|
later run blocker. `requires=[]` and `requires=[io]` remain blocking.
|
|
134
134
|
Remote IO should also declare `external`.
|
|
135
|
-
The manifest lives at the **task level** (`<task_root>/qa/`, path token `TASK_QA_PATH`) and is shared across planning → implementation → final-verification. Layout split: executable scripts (conformance + any real-IO test) live under `<task_root>/qa/scripts/`; data sidecars (`conformance-manifest.json`, `result-*.json`) stay at the `qa/` root. This declaration is enforced at four layers: `validators/validate-implementation-plan-stages.py` check **S11** forces every stage to carry one of the two lines; at the planning boundary `validators/validate-run.py` `_validate_planning_conformance_declared` fails when a stage that declared `Conformance tests:` has no matching `-stage-<N>` entry in the shared manifest (a declaration that was never materialized); the manifest JSON structure — including each entry's `script` living under `qa/scripts/` — is enforced by `validate_conformance_manifest` (called from both the run path and validate-run); and the result policy is evaluated by `conformance.py` and `validate-run.py`.
|
|
135
|
+
The manifest lives at the **task level** (`<task_root>/qa/`, path token `TASK_QA_PATH`) and is shared across planning → implementation → final-verification. The verifier runs `runCommand` from the **worktree cwd**, and that cwd is the tree under test — it is what makes the script see this stage's diff. So `runCommand` MUST NOT repoint it: a leading `cd <checkout> &&` sends the script at whichever tree it names, which at implementation time is a checkout without the stage's changes and at final-verification is not the integrated tree either. Absolute paths are fine and usually necessary — the script and its `tsconfig` live under `<task_root>/qa/scripts/`, i.e. under `.okstra/`, and a worktree does not carry `.okstra/` (worker preamble: "`.okstra/**` artifacts remain anchored at `**Project Root:**`; the worktree may not contain them"). Point at those by absolute path; leave the cwd alone. **Enforced:** `scripts/okstra_ctl/conformance.py` `_check_entry` rejects a `runCommand` whose first word in any `&&` / `;` segment changes directory. Layout split: executable scripts (conformance + any real-IO test) live under `<task_root>/qa/scripts/`; data sidecars (`conformance-manifest.json`, `result-*.json`) stay at the `qa/` root. This declaration is enforced at four layers: `validators/validate-implementation-plan-stages.py` check **S11** forces every stage to carry one of the two lines; at the planning boundary `validators/validate-run.py` `_validate_planning_conformance_declared` fails when a stage that declared `Conformance tests:` has no matching `-stage-<N>` entry in the shared manifest (a declaration that was never materialized); the manifest JSON structure — including each entry's `script` living under `qa/scripts/` — is enforced by `validate_conformance_manifest` (called from both the run path and validate-run); and the result policy is evaluated by `conformance.py` and `validate-run.py`.
|
|
136
136
|
- `### Stage Exit Contract` — predicted added/modified files, newly exposed identifiers/types/endpoints, downstream-usable resources.
|
|
137
137
|
- `### Stage Validation` — pre / mid / post exact commands or observable outcomes for this stage only.
|
|
138
138
|
- **Dependency precondition (stages that run the project toolchain).** The planning worktree is created without installed dependencies, so a stage whose steps call `npm` / `yarn` / `pytest` / `cargo` / equivalent cannot have those commands succeed at plan time — they exit `127`, not RED/GREEN. Declare the install **once** as a `phase: pre` row in `### Validation Checklist` (e.g. `VC-008 — the implementation run's stage worktree has workspace dependencies installed`) and have every such stage's `Stage Validation` cite that `VC-NNN` in its `pre:` line. Do not repeat the install commands per stage, and do not silently assume the tooling is present: a plan that never states the precondition produces steps whose commands never resolve, which the §5.5.9 round then reports as unverifiable. **Enforced (advisory):** `validators/validate-run.py` `_detect_missing_dependency_precondition` warns when a toolchain-invoking stage cites no `VC-NNN`, or cites one that is not `phase: pre`. Whether the cited row genuinely covers dependencies is a §5.5.9 judgement, not a machine check. Detection uses the token allowlist in `scripts/okstra_ctl/build_tools.py`; a project overrides it with `buildToolTokens` in `.okstra/project.json`.
|
|
@@ -14,6 +14,10 @@ import fnmatch
|
|
|
14
14
|
import re
|
|
15
15
|
from dataclasses import dataclass
|
|
16
16
|
|
|
17
|
+
# 셸에서 프로세스의 cwd 를 바꾸는 명령. verifier 가 워크트리 cwd 에서 실행하는
|
|
18
|
+
# 계약이 이것들로 무력화된다.
|
|
19
|
+
_CWD_CHANGING_COMMANDS: frozenset[str] = frozenset({"cd", "pushd", "popd", "chdir"})
|
|
20
|
+
|
|
17
21
|
# diff 가 건드린 표면과 대조할 capability 태그 화이트리스트.
|
|
18
22
|
CAPABILITY_WHITELIST: tuple[str, ...] = ("db", "io", "http", "external")
|
|
19
23
|
EXTERNAL_ADVISORY_CAPABILITIES: frozenset[str] = frozenset(
|
|
@@ -89,6 +93,25 @@ def _check_entry(entry: object, idx: int, errors: list[str]) -> None:
|
|
|
89
93
|
if isinstance(script, str) and script.strip() and "qa/scripts/" not in script:
|
|
90
94
|
errors.append(f"{path}.script must live under the task qa scripts dir (qa/scripts/), got {script!r}")
|
|
91
95
|
_check_nonempty_str(entry.get("runCommand"), f"{path}.runCommand", errors)
|
|
96
|
+
run_command = entry.get("runCommand")
|
|
97
|
+
# 이 명령은 워크트리 cwd 에서 verbatim 실행되고(_implementation-verifier.md
|
|
98
|
+
# "Otherwise run runCommand in the worktree cwd"), **그 cwd 가 곧 검사 대상**이다.
|
|
99
|
+
# 스크립트·tsconfig 는 `.okstra/` 아래 사는데 워크트리에는 `.okstra/` 가 없으므로
|
|
100
|
+
# (implementation-worker-preamble.md "the worktree may not contain them")
|
|
101
|
+
# 그것들을 절대경로로 가리키는 것은 정상이고 사실상 필수다. 금지되는 것은 cwd
|
|
102
|
+
# 를 옮기는 일뿐이다 — 선행 `cd <메인 체크아웃>` 은 stage diff 가 없는 트리에서
|
|
103
|
+
# 검사를 돌려 미변경 코드를 통과시킨다.
|
|
104
|
+
if isinstance(run_command, str):
|
|
105
|
+
for segment in re.split(r"&&|\|\||;|\|", run_command):
|
|
106
|
+
words = segment.split()
|
|
107
|
+
if words and words[0] in _CWD_CHANGING_COMMANDS:
|
|
108
|
+
errors.append(
|
|
109
|
+
f"{path}.runCommand must run in the worktree cwd — that cwd is "
|
|
110
|
+
f"the tree under test; a leading `{words[0]}` repoints it, so "
|
|
111
|
+
"the script checks whichever checkout it lands in instead of "
|
|
112
|
+
"this stage's diff"
|
|
113
|
+
)
|
|
114
|
+
break
|
|
92
115
|
_check_nonempty_str(entry.get("passContract"), f"{path}.passContract", errors)
|
|
93
116
|
req_ids = entry.get("requirementIds")
|
|
94
117
|
if (
|
|
@@ -392,9 +392,33 @@ def _resource_lines(
|
|
|
392
392
|
)
|
|
393
393
|
if clarification:
|
|
394
394
|
bodies.append(clarification)
|
|
395
|
+
carry = _stage_fix_carry_body(context, item.plan)
|
|
396
|
+
if carry:
|
|
397
|
+
bodies.append(carry)
|
|
395
398
|
return ["", "\n\n".join(bodies)]
|
|
396
399
|
|
|
397
400
|
|
|
401
|
+
def _stage_fix_carry_body(
|
|
402
|
+
context: _MaterializationContext,
|
|
403
|
+
plan: PromptPlan,
|
|
404
|
+
) -> str:
|
|
405
|
+
"""The fix-run carry block, for the two audiences that act on it.
|
|
406
|
+
|
|
407
|
+
The executor's scope is the carried blocking findings, and the verifier MUST
|
|
408
|
+
cite each of them as resolved or still-failing — a fix-run verifier result
|
|
409
|
+
citing none is recorded as a contract violation. Both facts live in the
|
|
410
|
+
rendered analysis profile, which no CLI worker can read, so the block used to
|
|
411
|
+
reach them only if the lead transcribed it by hand. A missed transcription
|
|
412
|
+
made the worker answerable for a list it was never given.
|
|
413
|
+
"""
|
|
414
|
+
if plan.audience not in ("implementation-executor", "implementation-verifier"):
|
|
415
|
+
return ""
|
|
416
|
+
run = context.active_context.get("run")
|
|
417
|
+
if not isinstance(run, Mapping):
|
|
418
|
+
return ""
|
|
419
|
+
return _string_value(run.get("fixRunCarry")).strip()
|
|
420
|
+
|
|
421
|
+
|
|
398
422
|
def _resolve_clarification_input(
|
|
399
423
|
context: _MaterializationContext,
|
|
400
424
|
plan: PromptPlan,
|
|
@@ -58,6 +58,7 @@ def compact_active_run_context(
|
|
|
58
58
|
"workflow": dict(_mapping(payload.get("workflow"))),
|
|
59
59
|
"run": {
|
|
60
60
|
"stage": ctx.get("RUN_STAGE", ""),
|
|
61
|
+
"fixRunCarry": ctx.get("FIX_RUN_CONTEXT", ""),
|
|
61
62
|
},
|
|
62
63
|
"inputs": _compact_active_inputs(payload),
|
|
63
64
|
"workers": _compact_active_workers(payload),
|
|
@@ -186,12 +187,13 @@ def _hydrate_active_run(
|
|
|
186
187
|
payload: Mapping[str, Any],
|
|
187
188
|
ctx: Mapping[str, str],
|
|
188
189
|
) -> dict[str, str]:
|
|
189
|
-
# `stage`
|
|
190
|
-
#
|
|
191
|
-
# payload.
|
|
190
|
+
# `stage` and `fixRunCarry` are run inputs / derived content, not paths, so
|
|
191
|
+
# pathHints cannot rebuild them — they survive the round trip only by being
|
|
192
|
+
# read back off the compact payload.
|
|
192
193
|
run = _mapping(payload.get("run"))
|
|
193
194
|
return {
|
|
194
195
|
"stage": str(run.get("stage", "") or ""),
|
|
196
|
+
"fixRunCarry": str(run.get("fixRunCarry", "") or ""),
|
|
195
197
|
"runDirectoryPath": ctx.get("RUN_DIR_RELATIVE_PATH", ""),
|
|
196
198
|
"runManifestPath": ctx.get("RUN_MANIFEST_RELATIVE_PATH", ""),
|
|
197
199
|
"teamStatePath": ctx.get("TEAM_STATE_RELATIVE_PATH", ""),
|
|
@@ -511,6 +511,13 @@ def _active_run(ctx: dict) -> dict:
|
|
|
511
511
|
# stage from `consumers.jsonl`. Feeds the implementation prompt anchor
|
|
512
512
|
# in `initial_prompt_materialization`.
|
|
513
513
|
"stage": ctx.get("RUN_STAGE", ""),
|
|
514
|
+
# A fix run's carried findings decide what the executor fixes and what
|
|
515
|
+
# the verifier MUST cite as resolved / still-failing. The rendered
|
|
516
|
+
# analysis profile holds the same block, but no CLI worker can read that
|
|
517
|
+
# file, so this copy is what `initial_prompt_materialization` inlines
|
|
518
|
+
# into their prompts. Derived once at prepare: re-deriving it later would
|
|
519
|
+
# read a worktree HEAD the executor may have already moved.
|
|
520
|
+
"fixRunCarry": ctx.get("FIX_RUN_CONTEXT", ""),
|
|
514
521
|
"runDirectoryPath": ctx.get("RUN_DIR_RELATIVE_PATH", ""),
|
|
515
522
|
"runManifestPath": ctx.get("RUN_MANIFEST_RELATIVE_PATH", ""),
|
|
516
523
|
"teamStatePath": ctx.get("TEAM_STATE_RELATIVE_PATH", ""),
|
|
@@ -45,10 +45,10 @@ class StageFixCarry:
|
|
|
45
45
|
"Scope rules for this fix run live in the implementation sidecars "
|
|
46
46
|
"(`_implementation-verifier.md` § Fix-run incremental scope, "
|
|
47
47
|
"`report-writer.md` § Fix-run incremental authoring).",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"dispatch so it authors incrementally.",
|
|
48
|
+
"This block is inlined into the executor and verifier prompts by "
|
|
49
|
+
"`initial_prompt_materialization`; do not transcribe it by hand. "
|
|
50
|
+
"Lead duties: pass the previous data.json path above to the "
|
|
51
|
+
"report-writer dispatch so it authors incrementally.",
|
|
52
52
|
])
|
|
53
53
|
return "\n".join(lines)
|
|
54
54
|
|
|
@@ -320,6 +320,9 @@ If the anchor (`implementation_base_commit`) is reported unresolvable, run the s
|
|
|
320
320
|
### Next stage not yet ready — normal termination (not an exception gate)
|
|
321
321
|
Because of the dependency closure, the chain queue **may include a stage that another implementation run has occupied as started/reserved.** That stage's `render-bundle` is rejected with `--stage N already in progress or reserved by another run` (StageTargetError). This is **not** an exception gate needing human judgment but a "next stage not yet ready" situation. On this rejection, **terminate the chain normally** and report the remaining queue to the user (e.g. `remaining queue: stage 4, 5 — resume with okstra-run after occupancy is released`). This is a different branch from the exception gate below (data corruption·concurrent-occupancy conflict confirmation).
|
|
322
322
|
|
|
323
|
+
### Stage ended FAIL — stop the queue and report (not an exception gate)
|
|
324
|
+
When a stage's synthesised verdict is `FAIL`, Phase 6 writes no carry sidecar and appends a `status:"failed"` row in place of `done` (`prompts/profiles/_implementation-deliverable.md` "Lead post-stage persistence"). **Stop the queue at that stage** and report the failed stage, its report path, and the remaining queue (e.g. `stage 1 FAIL — remaining queue: stage 2, 3, 5; re-enter with okstra-run --stage 1 after the fix`). Do **not** continue to the next stage even when that stage is dependency-independent: an unattended chain that keeps building past a confirmed regression stacks later work on top of it. The `failed` row releases the stage's occupancy, so `--stage <N>` re-enters the same stage on its preserved worktree and branch — there is nothing to unblock by hand.
|
|
325
|
+
|
|
323
326
|
### Exception gate during chaining
|
|
324
327
|
If `render-bundle` raises Step 5's concurrent-run conflict detection (concurrent-run branch) or git stale-SHA reconciliation (git-reconcile branch), **stop the chain at that stage** and present the gate to the user exactly as Step 5 prescribes. Once the user resolves the gate, resume the chain in place (continue with the remaining queue). Data corruption·concurrent-occupancy conflicts are confirmed by a human — this is the safety boundary of unattended chaining. (Unlike the "not ready" rejection above, these two branches do not discard the queue; they wait for user resolution.)
|
|
325
328
|
<!-- END FRAGMENT: host-orchestration-implementation -->
|
|
@@ -341,12 +344,12 @@ Queue = the topologically-sorted stage list from splitting `orchestration.chainS
|
|
|
341
344
|
|
|
342
345
|
1. Call Step 5's `render-bundle` with the same arguments but `--stage N` (the base commit is auto-computed by prepare from the predecessor's done `head_commit`, so do not pass it by hand). Step 5's blocking local conformance waiver offer·concurrent-run detection·git-reconcile gates apply identically to each stage's `render-bundle`.
|
|
343
346
|
2. As in Step 6, become the host-native Okstra lead and run that stage's Phase 1–7 inline. Phase 6's lead post-stage persistence appends that stage's `status:"done"` row to `runs/<plan-task-key>/consumers.jsonl` (per the implementation profile directive).
|
|
344
|
-
3. After confirming that `done` row was written, reclaim the completed teammate panes of the stage you just finished: run `$HOME/.okstra/bin/okstra-trace-cleanup.sh --run-dir "<the run dir of the stage you just completed>"` (add `--keep report-writer-worker` if the report writer is still in flight). Then move to the next stage.
|
|
347
|
+
3. After confirming that `done` row was written, reclaim the completed teammate panes of the stage you just finished: run `$HOME/.okstra/bin/okstra-trace-cleanup.sh --run-dir "<the run dir of the stage you just completed>"` (add `--keep report-writer-worker` if the report writer is still in flight). Then move to the next stage. A `status:"failed"` row instead of `done` means the stage ended `FAIL` — reclaim the panes the same way, then stop the queue per "Stage ended FAIL" above.
|
|
345
348
|
4. One-line report at each stage start/finish: `stage N/<total> start` / `stage N done → next K`.
|
|
346
349
|
|
|
347
350
|
Once the whole queue is consumed, end the chain and report completion to the user.
|
|
348
351
|
|
|
349
|
-
The
|
|
352
|
+
The three branches that end or pause the queue — "Next stage not yet ready", "Stage ended FAIL", and "Exception gate during chaining" — are in the host orchestration rules block above.
|
|
350
353
|
|
|
351
354
|
## Persisting the PR template scope (release-handoff)
|
|
352
355
|
|
|
@@ -915,7 +915,14 @@ export async function runInstall(args) {
|
|
|
915
915
|
await writeFileAtomic(join(paths.home, "version"), paths.package + "\n", 0o644);
|
|
916
916
|
}
|
|
917
917
|
if (!opts.quiet) {
|
|
918
|
-
|
|
918
|
+
// The source is already named at the top of the run; what the stamp line
|
|
919
|
+
// could not say is whether this install moved anything. `copied=0 skipped=N`
|
|
920
|
+
// plus a bare version reads identically for "already current" and "an older
|
|
921
|
+
// payload just landed over a newer home" — the latter cost a full run,
|
|
922
|
+
// caught only by hand-diffing installed files.
|
|
923
|
+
process.stdout.write(
|
|
924
|
+
` version stamp: ${formatVersionTransition(paths.version, paths.package)}\n`,
|
|
925
|
+
);
|
|
919
926
|
process.stdout.write("done.\n");
|
|
920
927
|
process.stdout.write(
|
|
921
928
|
"\nNext step: register the current project.\n" +
|
|
@@ -1011,6 +1018,15 @@ async function agentDriftReasons(paths) {
|
|
|
1011
1018
|
return reasons;
|
|
1012
1019
|
}
|
|
1013
1020
|
|
|
1021
|
+
// What the stamp did, not just where it landed: `0.165.3 -> 0.166.0` and
|
|
1022
|
+
// `0.166.0 (unchanged)` are the two cases a bare version line cannot tell apart,
|
|
1023
|
+
// and they mean opposite things about whether the install did anything.
|
|
1024
|
+
export function formatVersionTransition(previous, next) {
|
|
1025
|
+
const from = String(previous ?? "").trim();
|
|
1026
|
+
if (!from) return next;
|
|
1027
|
+
return from === next ? `${next} (unchanged)` : `${from} -> ${next}`;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1014
1030
|
function summarise(label, result, target) {
|
|
1015
1031
|
if (result.missingSource) {
|
|
1016
1032
|
process.stdout.write(` ${label}: source directory missing — skipped\n`);
|