cclaw-cli 0.46.10 → 0.46.12
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/dist/artifact-linter.js +88 -1
- package/dist/content/hook-events.js +1 -1
- package/dist/content/next-command.js +3 -0
- package/dist/content/opencode-plugin.js +3 -14
- package/dist/content/retro-command.js +15 -4
- package/dist/content/skills.d.ts +2 -2
- package/dist/content/skills.js +32 -32
- package/dist/content/stage-schema.d.ts +6 -6
- package/dist/content/stage-schema.js +31 -29
- package/dist/content/stages/review.js +15 -12
- package/dist/content/stages/ship.js +16 -12
- package/dist/content/stages/tdd.d.ts +2 -0
- package/dist/content/stages/tdd.js +63 -1
- package/dist/content/templates.js +16 -1
- package/dist/content/utility-skills.js +5 -3
- package/dist/doctor.js +28 -1
- package/dist/flow-state.js +3 -3
- package/dist/gate-evidence.js +26 -7
- package/dist/hook-schemas/codex-hooks.v1.json +1 -0
- package/dist/install.js +2 -1
- package/dist/internal/advance-stage.js +1 -1
- package/dist/run-archive.js +9 -0
- package/package.json +1 -1
package/dist/artifact-linter.js
CHANGED
|
@@ -114,7 +114,8 @@ function tokensFromRule(rule) {
|
|
|
114
114
|
"FINALIZE_MERGE_LOCAL",
|
|
115
115
|
"FINALIZE_OPEN_PR",
|
|
116
116
|
"FINALIZE_KEEP_BRANCH",
|
|
117
|
-
"FINALIZE_DISCARD_BRANCH"
|
|
117
|
+
"FINALIZE_DISCARD_BRANCH",
|
|
118
|
+
"FINALIZE_NO_VCS"
|
|
118
119
|
];
|
|
119
120
|
}
|
|
120
121
|
if (/final verdict/iu.test(rule)) {
|
|
@@ -186,6 +187,9 @@ function getMarkdownTableRows(sectionBody) {
|
|
|
186
187
|
const DIAGRAM_ARROW_PATTERN = /(?:<--?>|<?==?>|--?>|->>|=>|-\.->|→|⟶|↦)/u;
|
|
187
188
|
const DIAGRAM_FAILURE_EDGE_PATTERN = /\b(fail(?:ed|ure)?|error|timeout|fallback|degrad(?:e|ed|ation)|retry|backoff|circuit|unavailable|recover(?:y)?|rescue|mitigat(?:e|ion)|rollback|exception|abort|dead[\s-]?letter|dlq)\b/iu;
|
|
188
189
|
const DIAGRAM_GENERIC_NODE_PATTERN = /\b(service|component|module|system)\s*(?:[A-Z0-9])?\b/iu;
|
|
190
|
+
const TEST_COMMAND_MARKER_PATTERN = /\b(?:npm|pnpm|yarn|bun|vitest|jest|pytest|go test|cargo test|mvn test|gradle test|dotnet test)\b/iu;
|
|
191
|
+
const RED_FAILURE_MARKER_PATTERN = /\b(?:fail|failed|failing|assertionerror|cannot find|exception|error|exit code\s*[:=]?\s*[1-9])\b/iu;
|
|
192
|
+
const GREEN_SUCCESS_MARKER_PATTERN = /\b(?:pass|passed|green|ok|0 failed|exit code\s*[:=]?\s*0)\b/iu;
|
|
189
193
|
function diagramEdgeLines(sectionBody) {
|
|
190
194
|
return sectionBody
|
|
191
195
|
.split(/\r?\n/)
|
|
@@ -219,6 +223,80 @@ function hasSyncDiagramEdge(lines) {
|
|
|
219
223
|
return !/-\.->|-->>|~~>/u.test(line);
|
|
220
224
|
});
|
|
221
225
|
}
|
|
226
|
+
function validateTddRedEvidence(sectionBody) {
|
|
227
|
+
const meaningful = meaningfulLineCount(sectionBody);
|
|
228
|
+
if (meaningful < 2) {
|
|
229
|
+
return {
|
|
230
|
+
ok: false,
|
|
231
|
+
details: "RED Evidence must include at least 2 meaningful lines (command plus failing output context)."
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (!TEST_COMMAND_MARKER_PATTERN.test(sectionBody)) {
|
|
235
|
+
return {
|
|
236
|
+
ok: false,
|
|
237
|
+
details: "RED Evidence must include the test command that produced the failure."
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
if (!RED_FAILURE_MARKER_PATTERN.test(sectionBody)) {
|
|
241
|
+
return {
|
|
242
|
+
ok: false,
|
|
243
|
+
details: "RED Evidence must include explicit failing output markers (FAIL/FAILED/AssertionError/exit code != 0)."
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
ok: true,
|
|
248
|
+
details: "RED Evidence includes command + failing output markers."
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function validateTddGreenEvidence(sectionBody) {
|
|
252
|
+
const meaningful = meaningfulLineCount(sectionBody);
|
|
253
|
+
if (meaningful < 2) {
|
|
254
|
+
return {
|
|
255
|
+
ok: false,
|
|
256
|
+
details: "GREEN Evidence must include at least 2 meaningful lines (command and passing result)."
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (!TEST_COMMAND_MARKER_PATTERN.test(sectionBody)) {
|
|
260
|
+
return {
|
|
261
|
+
ok: false,
|
|
262
|
+
details: "GREEN Evidence must include the full-suite test command."
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
if (!GREEN_SUCCESS_MARKER_PATTERN.test(sectionBody)) {
|
|
266
|
+
return {
|
|
267
|
+
ok: false,
|
|
268
|
+
details: "GREEN Evidence must include explicit passing markers (PASS/PASSED/OK/exit code 0)."
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
ok: true,
|
|
273
|
+
details: "GREEN Evidence includes command + passing output markers."
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function validateVerificationLadder(sectionBody) {
|
|
277
|
+
if (!/highest tier reached/iu.test(sectionBody)) {
|
|
278
|
+
return {
|
|
279
|
+
ok: false,
|
|
280
|
+
details: "Verification Ladder must include a 'Highest tier reached' line."
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (!/\b(static|command|behavioral|human)\b/iu.test(sectionBody)) {
|
|
284
|
+
return {
|
|
285
|
+
ok: false,
|
|
286
|
+
details: "Verification Ladder must name a tier (static | command | behavioral | human)."
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (!/\b(evidence|command|sha|commit)\b/iu.test(sectionBody)) {
|
|
290
|
+
return {
|
|
291
|
+
ok: false,
|
|
292
|
+
details: "Verification Ladder must include evidence details (command output or commit SHA)."
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
ok: true,
|
|
297
|
+
details: "Verification Ladder includes tier + evidence fields."
|
|
298
|
+
};
|
|
299
|
+
}
|
|
222
300
|
const LEARNING_TYPE_SET = new Set(["rule", "pattern", "lesson", "compound"]);
|
|
223
301
|
const LEARNING_CONFIDENCE_SET = new Set(["high", "medium", "low"]);
|
|
224
302
|
const LEARNING_UNIVERSALITY_SET = new Set(["project", "personal", "universal"]);
|
|
@@ -594,6 +672,15 @@ function validateSectionBody(sectionBody, rule, sectionName) {
|
|
|
594
672
|
}
|
|
595
673
|
}
|
|
596
674
|
const sectionNameNormalized = normalizeHeadingTitle(sectionName).toLowerCase();
|
|
675
|
+
if (sectionNameNormalized === "red evidence") {
|
|
676
|
+
return validateTddRedEvidence(sectionBody);
|
|
677
|
+
}
|
|
678
|
+
if (sectionNameNormalized === "green evidence") {
|
|
679
|
+
return validateTddGreenEvidence(sectionBody);
|
|
680
|
+
}
|
|
681
|
+
if (sectionNameNormalized === "verification ladder") {
|
|
682
|
+
return validateVerificationLadder(sectionBody);
|
|
683
|
+
}
|
|
597
684
|
if (sectionNameNormalized === "architecture diagram") {
|
|
598
685
|
const edgeLines = diagramEdgeLines(sectionBody);
|
|
599
686
|
if (edgeLines.length === 0) {
|
|
@@ -29,7 +29,7 @@ export const HOOK_EVENTS_BY_HARNESS = {
|
|
|
29
29
|
pre_tool_workflow_guard: "plugin tool.execute.before -> workflow-guard.sh",
|
|
30
30
|
post_tool_context_monitor: "plugin tool.execute.after -> context-monitor.sh",
|
|
31
31
|
stop_checkpoint: "plugin session.idle -> stop-checkpoint.sh",
|
|
32
|
-
precompact_digest: "plugin session.
|
|
32
|
+
precompact_digest: "plugin session.compacted -> pre-compact.sh"
|
|
33
33
|
},
|
|
34
34
|
codex: {
|
|
35
35
|
// Codex CLI v0.114+ exposes lifecycle hooks via `.codex/hooks.json`,
|
|
@@ -46,6 +46,7 @@ This is the only progression command the user needs to drive the entire flow. St
|
|
|
46
46
|
7. Let \`M\` = \`mandatoryDelegations\` for \`currentStage\`.
|
|
47
47
|
8. If \`M\` is non-empty, inspect **\`${delegationPath}\`**. Treat as satisfied only if each mandatory agent is **completed** or **waived**.
|
|
48
48
|
9. If any mandatory delegation is missing and no waiver exists: **STOP** and ask the user whether to dispatch now or waive with rationale. Do not mark gates passed while delegation is unresolved.
|
|
49
|
+
10. If \`currentStage === "review"\` and \`catalog.blocked\` includes \`review_criticals_resolved\`, treat this as a hard remediation branch: recommend \`/cc-ops rewind tdd "review_blocked_by_critical"\` with the blocking finding IDs, and do not attempt to advance toward ship.
|
|
49
50
|
|
|
50
51
|
### Path A: Current stage is NOT complete (any gate unmet or delegation missing)
|
|
51
52
|
|
|
@@ -166,6 +167,8 @@ Load the current stage's skill and command contract:
|
|
|
166
167
|
|
|
167
168
|
Execute the stage protocol. The stage skill handles interaction, STOP points, gate tracking, and stage completion via \`bash .cclaw/hooks/stage-complete.sh <stage>\` (canonical flow-state mutation path).
|
|
168
169
|
|
|
170
|
+
Special-case for review: if \`review_criticals_resolved\` is in \`blocked\`, route to rework instead of looping review forever — recommend \`/cc-ops rewind tdd "review_blocked_by_critical"\`.
|
|
171
|
+
|
|
169
172
|
**Path B — stage IS complete (all gates met, all delegations done):**
|
|
170
173
|
|
|
171
174
|
If \`next\` is \`done\`:
|
|
@@ -240,23 +240,12 @@ export default function cclawPlugin(ctx) {
|
|
|
240
240
|
// the system transform hook instead.
|
|
241
241
|
refreshBootstrapCache();
|
|
242
242
|
}
|
|
243
|
+
if (eventType === "session.compacted") {
|
|
244
|
+
await runHookScript("pre-compact.sh", eventData ?? {});
|
|
245
|
+
}
|
|
243
246
|
if (eventType === "session.idle") {
|
|
244
247
|
await runHookScript("stop-checkpoint.sh", { loop_count: 0 });
|
|
245
248
|
}
|
|
246
|
-
if (eventType === "tool.execute.before") {
|
|
247
|
-
const toolPayload = normalizeToolPayload(eventData, undefined);
|
|
248
|
-
const promptOk = await runHookScript("prompt-guard.sh", toolPayload);
|
|
249
|
-
const workflowOk = await runHookScript("workflow-guard.sh", toolPayload);
|
|
250
|
-
if (!promptOk || !workflowOk) {
|
|
251
|
-
throw new Error(
|
|
252
|
-
"cclaw OpenCode guard blocked tool.execute.before (prompt/workflow guard non-zero exit)."
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (eventType === "tool.execute.after") {
|
|
257
|
-
const toolPayload = normalizeToolPayload(eventData, undefined);
|
|
258
|
-
await runHookScript("context-monitor.sh", toolPayload);
|
|
259
|
-
}
|
|
260
249
|
},
|
|
261
250
|
"tool.execute.before": async (input, output) => {
|
|
262
251
|
const payload = normalizeToolPayload(input, output);
|
|
@@ -17,7 +17,7 @@ export function retroCommandContract() {
|
|
|
17
17
|
|
|
18
18
|
Auto-triggered retrospective after ship. \`/cc-next\` drafts \`${retroArtifactPath()}\`
|
|
19
19
|
from run artifacts and knowledge, then asks the user exactly ONE structured
|
|
20
|
-
question: **edit / accept / skip**. Default = accept.
|
|
20
|
+
question: **edit / accept / skip / rewind_for_fix**. Default = accept.
|
|
21
21
|
|
|
22
22
|
This command is normally invoked indirectly by \`/cc-next\` when
|
|
23
23
|
\`closeout.shipSubstate === "retro_review"\`. Invoking it directly is still
|
|
@@ -55,7 +55,8 @@ in the structured ask; there is no \`--skip\` flag.
|
|
|
55
55
|
to a plain-text lettered list when the tool is hidden or errors):
|
|
56
56
|
- \`accept\` (default) — keep the draft as-is,
|
|
57
57
|
- \`edit\` — user edits \`${retroArtifactPath()}\` in-place, then re-runs \`/cc-next\`,
|
|
58
|
-
- \`skip\` — record \`retroSkipped: true\` + one-line reason, no compound entry required
|
|
58
|
+
- \`skip\` — record \`retroSkipped: true\` + one-line reason, no compound entry required,
|
|
59
|
+
- \`rewind_for_fix\` — route back to \`plan\` / \`tdd\` / \`review\` with a non-empty reason.
|
|
59
60
|
6. On **accept**:
|
|
60
61
|
- append >=1 strict-schema JSONL line to \`${knowledgePath()}\` with
|
|
61
62
|
\`type: "compound"\`, \`source: "retro"\`, and \`stage: null\`,
|
|
@@ -73,7 +74,12 @@ in the structured ask; there is no \`--skip\` flag.
|
|
|
73
74
|
- set \`retro.completedAt = <ISO>\` (marks gate satisfied for archive), and
|
|
74
75
|
\`retro.compoundEntries = 0\`,
|
|
75
76
|
- set \`closeout.shipSubstate = "compound_review"\`.
|
|
76
|
-
9.
|
|
77
|
+
9. On **rewind_for_fix**:
|
|
78
|
+
- require \`targetStage\` in \`{ plan, tdd, review }\`,
|
|
79
|
+
- require a concise rationale (min 20 chars),
|
|
80
|
+
- instruct \`/cc-ops rewind <targetStage> "<reason>"\`,
|
|
81
|
+
- reset closeout progression by setting \`closeout.shipSubstate = "idle"\`.
|
|
82
|
+
10. Emit a one-line summary: \`retro: accepted|edited|skipped|rewind_for_fix | next: /cc-next\`.
|
|
77
83
|
|
|
78
84
|
## Primary skill
|
|
79
85
|
|
|
@@ -83,7 +89,7 @@ in the structured ask; there is no \`--skip\` flag.
|
|
|
83
89
|
export function retroCommandSkillMarkdown() {
|
|
84
90
|
return `---
|
|
85
91
|
name: ${RETRO_SKILL_NAME}
|
|
86
|
-
description: "Auto-drafted retrospective with a single structured accept/edit/skip ask. Triggered from /cc-next when shipSubstate=retro_review."
|
|
92
|
+
description: "Auto-drafted retrospective with a single structured accept/edit/skip/rewind_for_fix ask. Triggered from /cc-next when shipSubstate=retro_review."
|
|
87
93
|
---
|
|
88
94
|
|
|
89
95
|
# /cc-ops retro
|
|
@@ -119,6 +125,7 @@ Do not silently skip. Do not finalize without updating \`flow-state.json\`.
|
|
|
119
125
|
> - **accept** — keep the draft and continue.
|
|
120
126
|
> - **edit** — I'll edit it, then re-run \`/cc-next\`.
|
|
121
127
|
> - **skip** — no retro this run (requires one-line reason).
|
|
128
|
+
> - **rewind_for_fix** — route back to plan/tdd/review because post-ship issues were found.
|
|
122
129
|
|
|
123
130
|
4. Apply the state transition for the chosen option:
|
|
124
131
|
- \`accept\` → append \`{ "type": "compound", "source": "retro", "stage": null, ... }\` line
|
|
@@ -128,11 +135,15 @@ Do not silently skip. Do not finalize without updating \`flow-state.json\`.
|
|
|
128
135
|
- \`skip\` → set \`closeout.retroSkipped\`, \`closeout.retroSkipReason\`,
|
|
129
136
|
\`closeout.retroAcceptedAt\`, \`retro.completedAt\`,
|
|
130
137
|
\`retro.compoundEntries = 0\`; set \`closeout.shipSubstate = "compound_review"\`.
|
|
138
|
+
- \`rewind_for_fix\` → require \`targetStage ∈ {plan,tdd,review}\` and
|
|
139
|
+
reason (>=20 chars), then instruct \`/cc-ops rewind <targetStage> "<reason>"\`
|
|
140
|
+
and set \`closeout.shipSubstate = "idle"\` to restart closeout after rework.
|
|
131
141
|
|
|
132
142
|
5. Print one-line completion summary:
|
|
133
143
|
- \`retro gate: accepted (<N> compound entries)\`
|
|
134
144
|
- \`retro gate: skipped (reason: <text>)\`
|
|
135
145
|
- \`retro gate: editing (re-run /cc-next when ready)\`
|
|
146
|
+
- \`retro gate: rewind_for_fix (target=<stage>)\`
|
|
136
147
|
|
|
137
148
|
## Resume semantics
|
|
138
149
|
|
package/dist/content/skills.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { FlowStage } from "../types.js";
|
|
1
|
+
import type { FlowStage, FlowTrack } from "../types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Long-form Batch Execution walkthrough. Rendered once into
|
|
4
4
|
* \`.cclaw/references/stages/tdd-batch-walkthrough.md\` by the installer.
|
|
5
5
|
*/
|
|
6
6
|
export declare const TDD_BATCH_WALKTHROUGH_MARKDOWN = "# TDD \u2014 Batch Execution Walkthrough\n\nDetailed RED / GREEN / REFACTOR transcript for a 3-task batch. Illustrative\nonly \u2014 do not copy the command names blindly, match them to your stack.\n\n## Batch 1 example tasks\n\n| Task ID | Description | AC | Verification |\n|---|---|---|---|\n| T-1 `[~3m]` | Add `User.emailNormalized` column | AC-1 | `npm test -- users/schema` |\n| T-2 `[~4m]` | Normalize on write in `UserRepo.save` | AC-1 | `npm test -- users/repo` |\n| T-3 `[~3m]` | Reject duplicates in `UserService.signup` | AC-2 | `npm test -- users/service` |\n\n## Execution transcript\n\n### T-1 \u2014 RED\n\n> Run: `npm test -- users/schema` \u2192 **FAIL** (missing column: `emailNormalized`). Captured the failure stack as RED evidence. No production code touched yet.\n\n### T-1 \u2014 GREEN\n\n> Added the column in the schema module. Re-ran `npm test -- users/schema` \u2192 **PASS**. Ran the full suite `npm test` \u2192 **PASS**. Captured both outputs as GREEN evidence.\n\n### T-1 \u2014 REFACTOR\n\n> Extracted the column definition into a shared `NormalizedEmail` type used by T-2/T-3. Re-ran `npm test` \u2192 **PASS**. Captured REFACTOR note: \"Extracted NormalizedEmail type to keep T-2/T-3 DRY; zero behavior change, all tests still green.\"\n\n### T-2 \u2014 RED / GREEN / REFACTOR\n\nWrite the repo test that expects normalised writes, watch it fail (RED), implement normalisation inside `UserRepo.save` only (GREEN), then refactor the normaliser out of the repo into a helper shared with T-3 (REFACTOR).\n\n### T-3 \u2014 RED / GREEN / REFACTOR\n\nWrite the service-level duplicate test that expects a rejection, watch it fail (RED), add the duplicate check in `UserService.signup` (GREEN), refactor the error message into a named constant (REFACTOR).\n\n## Batch gate check\n\nAfter T-3 REFACTOR, before declaring Batch 1 done:\n\n1. Run the full suite (`npm test`) one final time \u2192 **PASS** captured as batch-exit evidence.\n2. Verify the TDD artifact contains RED, GREEN, and REFACTOR evidence for T-1, T-2, **and** T-3. No partial batches.\n3. Only now mark Batch 1 complete. Batch 2 cannot start until this step.\n\n## When to stop mid-batch (do NOT push through)\n\n- A RED test fails for a reason you did not predict (e.g. an unrelated flaky test) \u2192 **pause**, diagnose, log an operational-self-improvement entry, and decide with the user before proceeding.\n- A GREEN step would require touching code outside the task's acceptance criterion \u2192 **pause**, the task is scoped wrong; adjust the plan or open a follow-up task.\n- The same RED failure reappears after a GREEN change \u2192 **escalate** per the 3-attempts rule; do not keep patching.\n";
|
|
7
7
|
export declare function stageSkillFolder(stage: FlowStage): string;
|
|
8
|
-
export declare function stageSkillMarkdown(stage: FlowStage): string;
|
|
8
|
+
export declare function stageSkillMarkdown(stage: FlowStage, track?: FlowTrack): string;
|
package/dist/content/skills.js
CHANGED
|
@@ -5,8 +5,8 @@ import { stageAutoSubagentDispatch, stageSchema } from "./stage-schema.js";
|
|
|
5
5
|
const VERIFICATION_STAGES = ["tdd", "review", "ship"];
|
|
6
6
|
const DECISION_PROTOCOL_PATH = `${RUNTIME_ROOT}/references/protocols/decision.md`;
|
|
7
7
|
const COMPLETION_PROTOCOL_PATH = `${RUNTIME_ROOT}/references/protocols/completion.md`;
|
|
8
|
-
function whenNotToUseBlock(stage) {
|
|
9
|
-
const schema = stageSchema(stage);
|
|
8
|
+
function whenNotToUseBlock(stage, track) {
|
|
9
|
+
const schema = stageSchema(stage, track);
|
|
10
10
|
if (schema.whenNotToUse.length === 0) {
|
|
11
11
|
return "";
|
|
12
12
|
}
|
|
@@ -15,8 +15,8 @@ ${schema.whenNotToUse.map((item) => `- ${item}`).join("\n")}
|
|
|
15
15
|
|
|
16
16
|
`;
|
|
17
17
|
}
|
|
18
|
-
function contextLoadingBlock(stage) {
|
|
19
|
-
const trace = stageSchema(stage).crossStageTrace;
|
|
18
|
+
function contextLoadingBlock(stage, track) {
|
|
19
|
+
const trace = stageSchema(stage, track).crossStageTrace;
|
|
20
20
|
const readLines = trace.readsFrom.length > 0
|
|
21
21
|
? trace.readsFrom.map((value) => `- \`${value}\``).join("\n")
|
|
22
22
|
: "- (first stage — no upstream artifacts)";
|
|
@@ -31,7 +31,7 @@ ${readLines}
|
|
|
31
31
|
\`.cclaw/knowledge.jsonl\` when the digest is insufficient.
|
|
32
32
|
`;
|
|
33
33
|
}
|
|
34
|
-
function autoSubagentDispatchBlock(stage) {
|
|
34
|
+
function autoSubagentDispatchBlock(stage, track) {
|
|
35
35
|
const rules = stageAutoSubagentDispatch(stage);
|
|
36
36
|
if (rules.length === 0)
|
|
37
37
|
return "";
|
|
@@ -41,7 +41,7 @@ function autoSubagentDispatchBlock(stage) {
|
|
|
41
41
|
return `| ${rule.agent} | ${rule.mode} | ${userGate} | ${rule.when} |`;
|
|
42
42
|
})
|
|
43
43
|
.join("\n");
|
|
44
|
-
const mandatory = stageSchema(stage).mandatoryDelegations;
|
|
44
|
+
const mandatory = stageSchema(stage, track).mandatoryDelegations;
|
|
45
45
|
const mandatoryList = mandatory.length > 0 ? mandatory.map((a) => `\`${a}\``).join(", ") : "none";
|
|
46
46
|
const delegationLogRel = `${RUNTIME_ROOT}/state/delegation-log.json`;
|
|
47
47
|
return `## Automatic Subagent Dispatch
|
|
@@ -54,8 +54,8 @@ Mandatory delegations for this stage: ${mandatoryList}.
|
|
|
54
54
|
Record completion/waiver in \`${delegationLogRel}\` before stage completion.
|
|
55
55
|
`;
|
|
56
56
|
}
|
|
57
|
-
function researchPlaybooksBlock(stage) {
|
|
58
|
-
const playbooks = stageSchema(stage).researchPlaybooks ?? [];
|
|
57
|
+
function researchPlaybooksBlock(stage, track) {
|
|
58
|
+
const playbooks = stageSchema(stage, track).researchPlaybooks ?? [];
|
|
59
59
|
if (playbooks.length === 0)
|
|
60
60
|
return "";
|
|
61
61
|
const rows = playbooks
|
|
@@ -70,8 +70,8 @@ and record outcomes in the stage artifact when relevant.
|
|
|
70
70
|
${rows}
|
|
71
71
|
`;
|
|
72
72
|
}
|
|
73
|
-
function reviewSectionsBlock(stage) {
|
|
74
|
-
const schema = stageSchema(stage);
|
|
73
|
+
function reviewSectionsBlock(stage, track) {
|
|
74
|
+
const schema = stageSchema(stage, track);
|
|
75
75
|
if (schema.reviewSections.length === 0)
|
|
76
76
|
return "";
|
|
77
77
|
const sections = schema.reviewSections
|
|
@@ -103,8 +103,8 @@ Reference utility skill:
|
|
|
103
103
|
\`.cclaw/skills/verification-before-completion/SKILL.md\`
|
|
104
104
|
`;
|
|
105
105
|
}
|
|
106
|
-
function batchExecutionModeBlock(stage) {
|
|
107
|
-
const schema = stageSchema(stage);
|
|
106
|
+
function batchExecutionModeBlock(stage, track) {
|
|
107
|
+
const schema = stageSchema(stage, track);
|
|
108
108
|
if (!schema.batchExecutionAllowed)
|
|
109
109
|
return "";
|
|
110
110
|
return `## Batch Execution Mode
|
|
@@ -118,8 +118,8 @@ Detailed walkthrough:
|
|
|
118
118
|
\`.cclaw/${STAGE_EXAMPLES_REFERENCE_DIR}/tdd-batch-walkthrough.md\`
|
|
119
119
|
`;
|
|
120
120
|
}
|
|
121
|
-
function crossStageTraceBlock(stage) {
|
|
122
|
-
const trace = stageSchema(stage).crossStageTrace;
|
|
121
|
+
function crossStageTraceBlock(stage, track) {
|
|
122
|
+
const trace = stageSchema(stage, track).crossStageTrace;
|
|
123
123
|
const reads = trace.readsFrom.length > 0
|
|
124
124
|
? trace.readsFrom.map((r) => `- ${r}`).join("\n")
|
|
125
125
|
: "- (first stage — no upstream artifacts)";
|
|
@@ -137,8 +137,8 @@ ${writes}
|
|
|
137
137
|
Rule: ${trace.traceabilityRule}
|
|
138
138
|
`;
|
|
139
139
|
}
|
|
140
|
-
function artifactValidationBlock(stage) {
|
|
141
|
-
const validations = stageSchema(stage).artifactValidation;
|
|
140
|
+
function artifactValidationBlock(stage, track) {
|
|
141
|
+
const validations = stageSchema(stage, track).artifactValidation;
|
|
142
142
|
if (validations.length === 0)
|
|
143
143
|
return "";
|
|
144
144
|
const rows = validations
|
|
@@ -206,7 +206,7 @@ function stageSpecificSeeAlso(stage) {
|
|
|
206
206
|
};
|
|
207
207
|
return refs[stage];
|
|
208
208
|
}
|
|
209
|
-
function completionParametersBlock(schema) {
|
|
209
|
+
function completionParametersBlock(schema, track) {
|
|
210
210
|
const gateList = schema.requiredGates.map((g) => `\`${g.id}\``).join(", ");
|
|
211
211
|
const mandatory = schema.mandatoryDelegations.length > 0
|
|
212
212
|
? schema.mandatoryDelegations.map((a) => `\`${a}\``).join(", ")
|
|
@@ -214,7 +214,7 @@ function completionParametersBlock(schema) {
|
|
|
214
214
|
const nextStage = schema.next === "done" ? "done" : schema.next;
|
|
215
215
|
const nextDescription = schema.next === "done"
|
|
216
216
|
? "flow complete"
|
|
217
|
-
: stageSchema(schema.next).skillDescription;
|
|
217
|
+
: stageSchema(schema.next, track).skillDescription;
|
|
218
218
|
return `## Completion Parameters
|
|
219
219
|
|
|
220
220
|
- \`stage\`: \`${schema.stage}\`
|
|
@@ -230,8 +230,8 @@ Apply shared completion logic from:
|
|
|
230
230
|
\`${COMPLETION_PROTOCOL_PATH}\`
|
|
231
231
|
`;
|
|
232
232
|
}
|
|
233
|
-
function quickStartBlock(stage) {
|
|
234
|
-
const schema = stageSchema(stage);
|
|
233
|
+
function quickStartBlock(stage, track) {
|
|
234
|
+
const schema = stageSchema(stage, track);
|
|
235
235
|
const gatePreview = schema.requiredGates.slice(0, 3).map((g) => `\`${g.id}\``).join(", ");
|
|
236
236
|
return `## Quick Start
|
|
237
237
|
|
|
@@ -297,8 +297,8 @@ After T-3 REFACTOR, before declaring Batch 1 done:
|
|
|
297
297
|
export function stageSkillFolder(stage) {
|
|
298
298
|
return stageSchema(stage).skillFolder;
|
|
299
299
|
}
|
|
300
|
-
export function stageSkillMarkdown(stage) {
|
|
301
|
-
const schema = stageSchema(stage);
|
|
300
|
+
export function stageSkillMarkdown(stage, track = "standard") {
|
|
301
|
+
const schema = stageSchema(stage, track);
|
|
302
302
|
const gateList = schema.requiredGates
|
|
303
303
|
.map((g) => `- \`${g.id}\` — ${g.description}`)
|
|
304
304
|
.join("\n");
|
|
@@ -324,7 +324,7 @@ If you are about to violate the Iron Law, STOP. No amount of urgency, partial pr
|
|
|
324
324
|
|
|
325
325
|
</EXTREMELY-IMPORTANT>
|
|
326
326
|
|
|
327
|
-
${quickStartBlock(stage)}
|
|
327
|
+
${quickStartBlock(stage, track)}
|
|
328
328
|
|
|
329
329
|
## Overview
|
|
330
330
|
${schema.purpose}
|
|
@@ -332,16 +332,16 @@ ${schema.purpose}
|
|
|
332
332
|
## When to Use
|
|
333
333
|
${schema.whenToUse.map((item) => `- ${item}`).join("\n")}
|
|
334
334
|
|
|
335
|
-
${whenNotToUseBlock(stage)}
|
|
335
|
+
${whenNotToUseBlock(stage, track)}
|
|
336
336
|
## Inputs
|
|
337
337
|
${schema.inputs.length > 0 ? schema.inputs.map((item) => `- ${item}`).join("\n") : "- (first stage — no required inputs)"}
|
|
338
338
|
|
|
339
339
|
## Required Context
|
|
340
340
|
${schema.requiredContext.length > 0 ? schema.requiredContext.map((item) => `- ${item}`).join("\n") : "- None beyond this skill"}
|
|
341
341
|
|
|
342
|
-
${contextLoadingBlock(stage)}
|
|
343
|
-
${autoSubagentDispatchBlock(stage)}
|
|
344
|
-
${researchPlaybooksBlock(stage)}
|
|
342
|
+
${contextLoadingBlock(stage, track)}
|
|
343
|
+
${autoSubagentDispatchBlock(stage, track)}
|
|
344
|
+
${researchPlaybooksBlock(stage, track)}
|
|
345
345
|
|
|
346
346
|
## Outputs
|
|
347
347
|
${schema.outputs.map((item) => `- ${item}`).join("\n")}
|
|
@@ -365,7 +365,7 @@ ${schema.interactionProtocol.map((item, i) => `${i + 1}. ${item}`).join("\n")}
|
|
|
365
365
|
Shared decision/ask-user protocol:
|
|
366
366
|
\`${DECISION_PROTOCOL_PATH}\`
|
|
367
367
|
|
|
368
|
-
${batchExecutionModeBlock(stage)}
|
|
368
|
+
${batchExecutionModeBlock(stage, track)}
|
|
369
369
|
## Required Gates
|
|
370
370
|
${gateList}
|
|
371
371
|
|
|
@@ -375,10 +375,10 @@ ${evidenceList}
|
|
|
375
375
|
## Process
|
|
376
376
|
${schema.process.map((item, i) => `${i + 1}. ${item}`).join("\n")}
|
|
377
377
|
|
|
378
|
-
${reviewSectionsBlock(stage)}
|
|
378
|
+
${reviewSectionsBlock(stage, track)}
|
|
379
379
|
${verificationBlock(stage)}
|
|
380
|
-
${crossStageTraceBlock(stage)}
|
|
381
|
-
${artifactValidationBlock(stage)}
|
|
380
|
+
${crossStageTraceBlock(stage, track)}
|
|
381
|
+
${artifactValidationBlock(stage, track)}
|
|
382
382
|
|
|
383
383
|
## Anti-Patterns & Red Flags
|
|
384
384
|
${mergedAntiPatterns(schema)}
|
|
@@ -386,7 +386,7 @@ ${mergedAntiPatterns(schema)}
|
|
|
386
386
|
## Verification
|
|
387
387
|
${schema.exitCriteria.map((item) => `- [ ] ${item}`).join("\n")}
|
|
388
388
|
|
|
389
|
-
${completionParametersBlock(schema)}
|
|
389
|
+
${completionParametersBlock(schema, track)}
|
|
390
390
|
## Shared Stage Guidance
|
|
391
391
|
See:
|
|
392
392
|
- \`${STAGE_COMMON_GUIDANCE_REL_PATH}\`
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type { FlowStage, TransitionRule } from "../types.js";
|
|
1
|
+
import type { FlowStage, FlowTrack, TransitionRule } from "../types.js";
|
|
2
2
|
import type { StageAutoSubagentDispatch, StageSchema } from "./stages/schema-types.js";
|
|
3
3
|
export type { ArtifactValidation, CrossStageTrace, ReviewSection, StageAutoSubagentDispatch, StageGate, StageSchema, StageSchemaInput } from "./stages/schema-types.js";
|
|
4
4
|
/** Transition guard: agents with `mode: "mandatory"` in auto-subagent dispatch for this stage. */
|
|
5
5
|
export declare function mandatoryDelegationsForStage(stage: FlowStage): string[];
|
|
6
|
-
export declare function stageSchema(stage: FlowStage): StageSchema;
|
|
7
|
-
export declare function orderedStageSchemas(): StageSchema[];
|
|
8
|
-
export declare function stageGateIds(stage: FlowStage): string[];
|
|
9
|
-
export declare function stageRecommendedGateIds(stage: FlowStage): string[];
|
|
6
|
+
export declare function stageSchema(stage: FlowStage, track?: FlowTrack): StageSchema;
|
|
7
|
+
export declare function orderedStageSchemas(track?: FlowTrack): StageSchema[];
|
|
8
|
+
export declare function stageGateIds(stage: FlowStage, track?: FlowTrack): string[];
|
|
9
|
+
export declare function stageRecommendedGateIds(stage: FlowStage, track?: FlowTrack): string[];
|
|
10
10
|
export declare function nextCclawCommand(stage: FlowStage): string;
|
|
11
11
|
export declare function buildTransitionRules(): TransitionRule[];
|
|
12
|
-
export declare function stagePolicyNeedles(stage: FlowStage): string[];
|
|
12
|
+
export declare function stagePolicyNeedles(stage: FlowStage, track?: FlowTrack): string[];
|
|
13
13
|
export declare function stageAutoSubagentDispatch(stage: FlowStage): StageAutoSubagentDispatch[];
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import { COMMAND_FILE_ORDER } from "../constants.js";
|
|
2
2
|
import { BRAINSTORM, SCOPE, DESIGN, SPEC, PLAN, TDD, REVIEW, SHIP } from "./stages/index.js";
|
|
3
|
-
|
|
4
|
-
// NOTE: The former QUESTION_FORMAT_SPEC / ERROR_BUDGET_SPEC exports were
|
|
5
|
-
// hoisted into `src/content/meta-skill.ts` (Shared Decision + Tool-Use
|
|
6
|
-
// Protocol). They are no longer re-exported from here to avoid duplication
|
|
7
|
-
// and drift. Stage skills cite the meta-skill by path instead.
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
/**
|
|
10
|
-
* Gate tiers:
|
|
11
|
-
* - required: blocking for stage completion.
|
|
12
|
-
* - recommended: quality signal; unmet -> DONE_WITH_CONCERNS, not BLOCKED.
|
|
13
|
-
*/
|
|
3
|
+
import { tddStageForTrack } from "./stages/tdd.js";
|
|
14
4
|
const REQUIRED_GATE_IDS = {
|
|
15
5
|
brainstorm: [
|
|
16
6
|
"brainstorm_approaches_compared",
|
|
@@ -39,17 +29,19 @@ const REQUIRED_GATE_IDS = {
|
|
|
39
29
|
"plan_acceptance_mapped",
|
|
40
30
|
"plan_wait_for_confirm"
|
|
41
31
|
],
|
|
42
|
-
tdd: [
|
|
32
|
+
tdd: (track) => [
|
|
43
33
|
"tdd_red_test_written",
|
|
44
34
|
"tdd_green_full_suite",
|
|
45
35
|
"tdd_refactor_completed",
|
|
46
|
-
"tdd_verified_before_complete"
|
|
36
|
+
"tdd_verified_before_complete",
|
|
37
|
+
...(track === "quick" ? [] : ["tdd_traceable_to_plan"])
|
|
47
38
|
],
|
|
48
|
-
review: [
|
|
39
|
+
review: (track) => [
|
|
49
40
|
"review_layer1_spec_compliance",
|
|
50
41
|
"review_layer2_security",
|
|
51
42
|
"review_criticals_resolved",
|
|
52
|
-
"review_army_json_valid"
|
|
43
|
+
"review_army_json_valid",
|
|
44
|
+
...(track === "quick" ? [] : ["review_trace_matrix_clean"])
|
|
53
45
|
],
|
|
54
46
|
ship: [
|
|
55
47
|
"ship_review_verdict_valid",
|
|
@@ -64,12 +56,16 @@ const REQUIRED_ARTIFACT_SECTIONS = {
|
|
|
64
56
|
design: ["Architecture Boundaries", "Architecture Diagram", "Failure Mode Table", "Completion Dashboard"],
|
|
65
57
|
spec: ["Acceptance Criteria", "Edge Cases", "Testability Map", "Approval"],
|
|
66
58
|
plan: ["Task List", "Dependency Batches", "Acceptance Mapping", "WAIT_FOR_CONFIRM"],
|
|
67
|
-
tdd: ["RED Evidence", "GREEN Evidence", "REFACTOR Notes", "Traceability"],
|
|
59
|
+
tdd: ["RED Evidence", "GREEN Evidence", "REFACTOR Notes", "Traceability", "Verification Ladder"],
|
|
68
60
|
review: ["Layer 1 Verdict", "Review Army Contract", "Severity Summary", "Final Verdict"],
|
|
69
61
|
ship: ["Preflight Results", "Release Notes", "Rollback Plan", "Finalization"]
|
|
70
62
|
};
|
|
71
|
-
function
|
|
72
|
-
const
|
|
63
|
+
function resolveRequiredGateIds(stage, track) {
|
|
64
|
+
const raw = REQUIRED_GATE_IDS[stage];
|
|
65
|
+
return typeof raw === "function" ? raw(track) : raw;
|
|
66
|
+
}
|
|
67
|
+
function tieredStageGates(stage, gates, track) {
|
|
68
|
+
const requiredSet = new Set(resolveRequiredGateIds(stage, track));
|
|
73
69
|
return gates.map((gate) => {
|
|
74
70
|
return {
|
|
75
71
|
...gate,
|
|
@@ -233,9 +229,9 @@ export function mandatoryDelegationsForStage(stage) {
|
|
|
233
229
|
.filter((d) => d.mode === "mandatory")
|
|
234
230
|
.map((d) => d.agent);
|
|
235
231
|
}
|
|
236
|
-
export function stageSchema(stage) {
|
|
237
|
-
const base = STAGE_SCHEMA_MAP[stage];
|
|
238
|
-
const tieredGates = tieredStageGates(stage, base.requiredGates);
|
|
232
|
+
export function stageSchema(stage, track = "standard") {
|
|
233
|
+
const base = stage === "tdd" ? tddStageForTrack(track) : STAGE_SCHEMA_MAP[stage];
|
|
234
|
+
const tieredGates = tieredStageGates(stage, base.requiredGates, track);
|
|
239
235
|
const tieredValidation = tieredArtifactValidation(stage, base.artifactValidation);
|
|
240
236
|
return {
|
|
241
237
|
...base,
|
|
@@ -244,16 +240,16 @@ export function stageSchema(stage) {
|
|
|
244
240
|
mandatoryDelegations: mandatoryDelegationsForStage(stage)
|
|
245
241
|
};
|
|
246
242
|
}
|
|
247
|
-
export function orderedStageSchemas() {
|
|
248
|
-
return COMMAND_FILE_ORDER.map((stage) => stageSchema(stage));
|
|
243
|
+
export function orderedStageSchemas(track = "standard") {
|
|
244
|
+
return COMMAND_FILE_ORDER.map((stage) => stageSchema(stage, track));
|
|
249
245
|
}
|
|
250
|
-
export function stageGateIds(stage) {
|
|
251
|
-
return stageSchema(stage).requiredGates
|
|
246
|
+
export function stageGateIds(stage, track = "standard") {
|
|
247
|
+
return stageSchema(stage, track).requiredGates
|
|
252
248
|
.filter((gate) => gate.tier === "required")
|
|
253
249
|
.map((gate) => gate.id);
|
|
254
250
|
}
|
|
255
|
-
export function stageRecommendedGateIds(stage) {
|
|
256
|
-
return stageSchema(stage).requiredGates
|
|
251
|
+
export function stageRecommendedGateIds(stage, track = "standard") {
|
|
252
|
+
return stageSchema(stage, track).requiredGates
|
|
257
253
|
.filter((gate) => gate.tier === "recommended")
|
|
258
254
|
.map((gate) => gate.id);
|
|
259
255
|
}
|
|
@@ -273,10 +269,16 @@ export function buildTransitionRules() {
|
|
|
273
269
|
guards: stageGateIds(schema.stage)
|
|
274
270
|
});
|
|
275
271
|
}
|
|
272
|
+
// Review can explicitly route back to TDD when the verdict is BLOCKED.
|
|
273
|
+
rules.push({
|
|
274
|
+
from: "review",
|
|
275
|
+
to: "tdd",
|
|
276
|
+
guards: ["review_verdict_blocked"]
|
|
277
|
+
});
|
|
276
278
|
return rules;
|
|
277
279
|
}
|
|
278
|
-
export function stagePolicyNeedles(stage) {
|
|
279
|
-
return stageSchema(stage).policyNeedles;
|
|
280
|
+
export function stagePolicyNeedles(stage, track = "standard") {
|
|
281
|
+
return stageSchema(stage, track).policyNeedles;
|
|
280
282
|
}
|
|
281
283
|
export function stageAutoSubagentDispatch(stage) {
|
|
282
284
|
return STAGE_AUTO_SUBAGENT_DISPATCH[stage];
|
|
@@ -24,6 +24,7 @@ export const REVIEW = {
|
|
|
24
24
|
"Change-Size Check — ~100 lines = normal. ~300 lines = consider splitting. ~1000+ lines = strongly recommend stacked PRs. Flag large diffs to the user.",
|
|
25
25
|
"Adversarial Trigger Check — compute changed-line count (`git diff --shortstat <base>..HEAD`), files-touched count, and whether trust boundaries changed (auth/secrets/external inputs/permissions). If `lines > 100` OR `files > 10` OR `trust boundary changed`, **dispatch a SECOND reviewer agent with the `adversarial-review` skill loaded** and reconcile its findings into the review army (treat the conditional dispatch as mandatory whenever the trigger holds; record the trigger that fired in the dashboard).",
|
|
26
26
|
"Load upstream evidence — read TDD artifact (RED + GREEN + REFACTOR), spec, and plan. Verify evidence chain is unbroken.",
|
|
27
|
+
"Run traceability matrix — execute `cclaw internal trace-matrix` (or equivalent helper) and confirm there are no orphaned criteria/tasks/tests before declaring ship readiness.",
|
|
27
28
|
"Layer 1: Spec Compliance — check every acceptance criterion against implementation. Verdict: pass/fail per criterion.",
|
|
28
29
|
"Layer 2a: Correctness — logic errors, race conditions, boundary violations, null handling.",
|
|
29
30
|
"Layer 2b: Security — input validation, auth boundaries, secrets exposure, injection vectors. **Mandatory:** also load and execute the `.cclaw/skills/security-audit/SKILL.md` utility skill (proactive pattern sweep across diff + touched modules, not just the diff itself) and merge findings into the review army. The Layer 2 security pass is not complete until the audit sweep records a finding count (0 acceptable) with file:line evidence for every Critical.",
|
|
@@ -34,7 +35,8 @@ export const REVIEW = {
|
|
|
34
35
|
"Review Army reconciliation — normalize findings into structured records, dedup by fingerprint, and mark multi-specialist confirmations.",
|
|
35
36
|
"Meta-Review — Were tests actually run? Do test names match what they test? Are there real assertions?",
|
|
36
37
|
"Classify findings — Critical (blocks ship), Important (should fix), Suggestion (optional improvement).",
|
|
37
|
-
"Produce verdict — APPROVED, APPROVED_WITH_CONCERNS, or BLOCKED."
|
|
38
|
+
"Produce verdict — APPROVED, APPROVED_WITH_CONCERNS, or BLOCKED.",
|
|
39
|
+
"If verdict is BLOCKED, emit remediation route token `ROUTE_BACK_TO_TDD` and include `/cc-ops rewind tdd \"review_blocked_by_critical\"` with the blocking finding IDs."
|
|
38
40
|
],
|
|
39
41
|
interactionProtocol: [
|
|
40
42
|
"Run Layer 1 (spec compliance) completely before starting Layer 2.",
|
|
@@ -42,6 +44,7 @@ export const REVIEW = {
|
|
|
42
44
|
"Classify every finding as Critical, Important, or Suggestion.",
|
|
43
45
|
"For each Critical finding: use the Decision Protocol — present resolution options (A/B/C) with trade-offs, and mark one as (recommended). Do NOT use a numeric Completeness rubric; recommend the option that fully closes the finding with no carry-over risk and the smallest blast radius. If the harness's native structured-ask tool is available (`AskUserQuestion` on Claude, `AskQuestion` on Cursor, `question` on OpenCode with `permission.question: \"allow\"`, `request_user_input` on Codex in Plan/Collaboration mode), send exactly ONE question per call, validate fields against the runtime schema, and on schema error immediately fall back to a plain-text lettered list instead of retrying guessed payloads.",
|
|
44
46
|
"Resolve all critical blockers before ship.",
|
|
47
|
+
"When verdict is BLOCKED, do not end with a passive stop: explicitly route remediation to TDD via `ROUTE_BACK_TO_TDD` and point to `/cc-ops rewind tdd` with the blocking IDs.",
|
|
45
48
|
"For final verdict: use the native structured-ask tool (`AskUserQuestion` / `AskQuestion` / `question` / `request_user_input`) only if runtime schema is confirmed; otherwise collect verdict with a plain-text single-choice prompt (APPROVED / APPROVED_WITH_CONCERNS / BLOCKED).",
|
|
46
49
|
"**STOP.** Do NOT proceed to ship until the user provides an explicit verdict."
|
|
47
50
|
],
|
|
@@ -53,21 +56,25 @@ export const REVIEW = {
|
|
|
53
56
|
"Layer 2d: check architecture fit — design compliance, coupling, interfaces.",
|
|
54
57
|
"Reconcile multi-agent findings into `.cclaw/artifacts/07-review-army.json` (dedup + confidence + conflict notes).",
|
|
55
58
|
"Classify and prioritize all findings.",
|
|
56
|
-
"Write review report artifact with explicit verdict."
|
|
59
|
+
"Write review report artifact with explicit verdict.",
|
|
60
|
+
"If verdict is BLOCKED, include the remediation route token `ROUTE_BACK_TO_TDD` and the rewind command payload."
|
|
57
61
|
],
|
|
58
62
|
requiredGates: [
|
|
59
63
|
{ id: "review_layer1_spec_compliance", description: "Spec compliance check completed with per-criterion verdict." },
|
|
60
64
|
{ id: "review_layer2_security", description: "Security review completed." },
|
|
61
65
|
{ id: "review_criticals_resolved", description: "No unresolved critical blockers remain." },
|
|
62
|
-
{ id: "review_army_json_valid", description: "07-review-army.json passes schema validation (validateReviewArmy)." }
|
|
66
|
+
{ id: "review_army_json_valid", description: "07-review-army.json passes schema validation (validateReviewArmy)." },
|
|
67
|
+
{ id: "review_trace_matrix_clean", description: "Trace matrix has no orphaned criteria/tasks/test slices for the active run." }
|
|
63
68
|
],
|
|
64
69
|
requiredEvidence: [
|
|
65
70
|
"Artifact written to `.cclaw/artifacts/07-review.md`.",
|
|
66
71
|
"Artifact written to `.cclaw/artifacts/07-review-army.json`.",
|
|
72
|
+
"Traceability matrix run recorded (no orphaned criteria/tasks/tests for enforced tracks).",
|
|
67
73
|
"Layer 1 verdict captured with per-criterion pass/fail.",
|
|
68
74
|
"Layer 2 sections completed with findings.",
|
|
69
75
|
"Severity log includes critical/important/suggestion buckets.",
|
|
70
|
-
"Explicit final verdict: APPROVED, APPROVED_WITH_CONCERNS, or BLOCKED."
|
|
76
|
+
"Explicit final verdict: APPROVED, APPROVED_WITH_CONCERNS, or BLOCKED.",
|
|
77
|
+
"If BLOCKED: include explicit remediation route (`ROUTE_BACK_TO_TDD`) with blocking finding IDs."
|
|
71
78
|
],
|
|
72
79
|
inputs: ["implementation diff", "spec and plan artifacts", "test/build evidence"],
|
|
73
80
|
requiredContext: ["spec criteria", "tdd artifact", "rulebook constraints"],
|
|
@@ -88,15 +95,9 @@ export const REVIEW = {
|
|
|
88
95
|
"No severity classification",
|
|
89
96
|
"Shipping with open criticals",
|
|
90
97
|
"Batching multiple findings into one report without individual resolution",
|
|
91
|
-
"Skipping Layer 2 sections because Layer 1 passed"
|
|
92
|
-
"No separate Layer 1/Layer 2 outcomes",
|
|
93
|
-
"No structured review-army reconciliation artifact",
|
|
94
|
-
"No critical bucket",
|
|
95
|
-
"No explicit ready/not-ready verdict",
|
|
96
|
-
"Review sections skipped or abbreviated",
|
|
97
|
-
"Findings not classified by severity"
|
|
98
|
+
"Skipping Layer 2 sections because Layer 1 passed"
|
|
98
99
|
],
|
|
99
|
-
policyNeedles: ["Layer 1", "Layer 2", "Critical", "Review Army", "Ready to Ship", "One issue at a time"],
|
|
100
|
+
policyNeedles: ["Layer 1", "Layer 2", "Critical", "Review Army", "Ready to Ship", "ROUTE_BACK_TO_TDD", "One issue at a time"],
|
|
100
101
|
artifactFile: "07-review.md",
|
|
101
102
|
next: "ship",
|
|
102
103
|
reviewSections: [
|
|
@@ -205,6 +206,8 @@ export const REVIEW = {
|
|
|
205
206
|
{ section: "Review Readiness Dashboard", required: false, validationRule: "Includes a per-pass table (Layer 1 / Layer 2 / Adversarial / Schema) with a 'Completed at' column, a Delegation log snapshot block (path .cclaw/state/delegation-log.json with required/completed/waived/pending), a Staleness signal block (commit at last review pass and current commit), and a Headline with open critical blockers + ship recommendation. At minimum, the section text must contain the substrings 'Completed at', 'delegation-log.json', 'commit at last review pass', and 'Ship recommendation'." },
|
|
206
207
|
{ section: "Completeness Score", required: false, validationRule: "Records AC coverage, task coverage, test-slice coverage, and adversarial-review pass status as numeric or boolean values. At minimum, a line like 'AC coverage: N/M' or 'AC coverage: 100%'." },
|
|
207
208
|
{ section: "Incoming Feedback Queue", required: false, validationRule: "When external review feedback exists, include a queue summary with per-item disposition (resolved / accepted-risk / rejected-with-evidence) and evidence refs." },
|
|
209
|
+
{ section: "Trace Matrix Check", required: false, validationRule: "Records criteria/tasks/tests orphan counts (all zero on enforced tracks) with command output reference." },
|
|
210
|
+
{ section: "Blocked Route", required: false, validationRule: "When Final Verdict is BLOCKED: includes `ROUTE_BACK_TO_TDD`, rewind target `tdd`, and blocked finding IDs." },
|
|
208
211
|
{ section: "Severity Summary", required: true, validationRule: "Per-severity count lines for critical, important, and suggestion buckets." },
|
|
209
212
|
{ section: "Final Verdict", required: true, validationRule: "Exactly one of: APPROVED, APPROVED_WITH_CONCERNS, BLOCKED." }
|
|
210
213
|
]
|
|
@@ -5,14 +5,15 @@ export const SHIP = {
|
|
|
5
5
|
stage: "ship",
|
|
6
6
|
skillFolder: "shipping-and-handoff",
|
|
7
7
|
skillName: "shipping-and-handoff",
|
|
8
|
-
skillDescription: "Release handoff stage with preflight checks, rollback readiness, and explicit finalization mode.",
|
|
9
|
-
hardGate: "Do NOT merge, push, or finalize without a passed preflight check, written rollback plan, and exactly one explicit finalization mode selected. No exceptions for urgency.",
|
|
8
|
+
skillDescription: "Release handoff stage with preflight checks, rollback readiness, and explicit finalization mode for both git and non-git workflows.",
|
|
9
|
+
hardGate: "Do NOT merge, push, or finalize without a passed preflight check, written rollback plan, and exactly one explicit finalization mode selected. No exceptions for urgency. If no VCS is available, use FINALIZE_NO_VCS explicitly instead of inventing git steps.",
|
|
10
10
|
ironLaw: "NO MERGE WITHOUT GREEN CI, A WRITTEN ROLLBACK, AND EXACTLY ONE SELECTED FINALIZATION MODE.",
|
|
11
11
|
purpose: "Prepare a safe release handoff with clear rollback and branch finalization decision.",
|
|
12
12
|
whenToUse: [
|
|
13
13
|
"After review passes with APPROVED or APPROVED_WITH_CONCERNS verdict",
|
|
14
14
|
"Before creating PR/merge/final branch action",
|
|
15
|
-
"When release notes and rollback plan are required"
|
|
15
|
+
"When release notes and rollback plan are required",
|
|
16
|
+
"When shipping from non-git environments (docs bundles, script drops, detached artifacts)"
|
|
16
17
|
],
|
|
17
18
|
whenNotToUse: [
|
|
18
19
|
"Review verdict is BLOCKED or unresolved critical findings remain",
|
|
@@ -22,20 +23,21 @@ export const SHIP = {
|
|
|
22
23
|
checklist: [
|
|
23
24
|
"Validate upstream gates — verify review verdict is APPROVED or APPROVED_WITH_CONCERNS. If BLOCKED, stop immediately.",
|
|
24
25
|
"Run preflight checks — tests pass, build succeeds, linter clean, type-check clean, no uncommitted changes. Every check must produce fresh output in this message.",
|
|
25
|
-
"Merge-base detection — identify the correct base branch. Run `git merge-base HEAD <base>`. If the base has diverged significantly, flag for rebase-first.",
|
|
26
|
+
"Merge-base detection (git only) — identify the correct base branch. Run `git merge-base HEAD <base>`. If the base has diverged significantly, flag for rebase-first.",
|
|
26
27
|
"Re-run tests on merged result — if merging locally, run the full test suite AFTER the merge, not just before. Post-merge failures are common.",
|
|
27
28
|
"Generate release notes — summarize what changed, why, and what it affects. Reference spec criteria. Include: breaking changes, new dependencies, migration steps if any.",
|
|
28
29
|
"Write rollback plan — trigger conditions (what tells you it is broken), rollback steps (exact commands/git operations), and verification (how to confirm rollback worked).",
|
|
29
30
|
"Load utility skills — `verification-before-completion` for fresh evidence and `finishing-a-development-branch` for finalization workflow.",
|
|
30
31
|
"Monitoring checklist — what should be watched after deploy? Error rates, latency, key business metrics. If no monitoring exists, flag it as a risk.",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
32
|
+
"Detect repository mode — if `.git/` is absent or inaccessible, lock finalization choices to FINALIZE_NO_VCS only and document manual handoff + rollback.",
|
|
33
|
+
"Select finalization mode — exactly ONE enum: (A) FINALIZE_MERGE_LOCAL, (B) FINALIZE_OPEN_PR, (C) FINALIZE_KEEP_BRANCH, (D) FINALIZE_DISCARD_BRANCH, (E) FINALIZE_NO_VCS. For discard: list what will be deleted, require typed confirmation.",
|
|
34
|
+
"Execute finalization — perform the selected action. For merge: verify clean merge. For PR: include structured body (summary, test plan, rollback). For discard: verify deletion. For NO_VCS: record handoff target, artifact bundle path, and manual rollback owner.",
|
|
35
|
+
"Worktree cleanup — if using git worktrees, clean up the worktree after merge/discard. Keep it only for 'keep branch' mode. Skip for FINALIZE_NO_VCS."
|
|
34
36
|
],
|
|
35
37
|
interactionProtocol: [
|
|
36
38
|
"Run preflight checks before any release action.",
|
|
37
39
|
"Document release notes and rollback plan explicitly.",
|
|
38
|
-
"For finalization mode: use the Decision Protocol — present modes as labeled options (A/B/C/D) with consequences, and mark one as (recommended). Do NOT use a numeric Completeness rubric; recommend the mode that best addresses release blast-radius, rollback readiness, observability, and stakeholder communication — ties go to the most reversible option. If the harness's native structured-ask tool is available (`AskUserQuestion` / `AskQuestion` / `question` / `request_user_input`), send exactly ONE question per call, validate fields against the runtime schema, and on schema error immediately fall back to a plain-text lettered list instead of retrying guessed payloads.",
|
|
40
|
+
"For finalization mode: use the Decision Protocol — present modes as labeled options (A/B/C/D/E) with consequences, and mark one as (recommended). Do NOT use a numeric Completeness rubric; recommend the mode that best addresses release blast-radius, rollback readiness, observability, and stakeholder communication — ties go to the most reversible option. If the harness's native structured-ask tool is available (`AskUserQuestion` / `AskQuestion` / `question` / `request_user_input`), send exactly ONE question per call, validate fields against the runtime schema, and on schema error immediately fall back to a plain-text lettered list instead of retrying guessed payloads.",
|
|
39
41
|
"Do not proceed if critical blockers remain from review.",
|
|
40
42
|
"**STOP.** Present finalization options and wait for user selection before executing any finalization action."
|
|
41
43
|
],
|
|
@@ -43,7 +45,7 @@ export const SHIP = {
|
|
|
43
45
|
"Validate review and test gates.",
|
|
44
46
|
"Run preflight: build, test, lint, uncommitted-changes check.",
|
|
45
47
|
"Generate release notes and rollback procedure.",
|
|
46
|
-
"Choose one finalization enum: FINALIZE_MERGE_LOCAL, FINALIZE_OPEN_PR, FINALIZE_KEEP_BRANCH, or
|
|
48
|
+
"Choose one finalization enum: FINALIZE_MERGE_LOCAL, FINALIZE_OPEN_PR, FINALIZE_KEEP_BRANCH, FINALIZE_DISCARD_BRANCH, or FINALIZE_NO_VCS.",
|
|
47
49
|
"Execute finalization action.",
|
|
48
50
|
"Write ship artifact with decision, rationale, and execution result."
|
|
49
51
|
],
|
|
@@ -84,7 +86,8 @@ export const SHIP = {
|
|
|
84
86
|
"More than one finalization mode implied",
|
|
85
87
|
"No explicit preflight result",
|
|
86
88
|
"Review verdict not referenced",
|
|
87
|
-
"Finalization not executed, only planned"
|
|
89
|
+
"Finalization not executed, only planned",
|
|
90
|
+
"Selecting git-dependent finalization mode when `.git` is unavailable"
|
|
88
91
|
],
|
|
89
92
|
policyNeedles: [
|
|
90
93
|
"Pre-Ship Checks",
|
|
@@ -93,7 +96,8 @@ export const SHIP = {
|
|
|
93
96
|
"FINALIZE_MERGE_LOCAL",
|
|
94
97
|
"FINALIZE_OPEN_PR",
|
|
95
98
|
"FINALIZE_KEEP_BRANCH",
|
|
96
|
-
"FINALIZE_DISCARD_BRANCH"
|
|
99
|
+
"FINALIZE_DISCARD_BRANCH",
|
|
100
|
+
"FINALIZE_NO_VCS"
|
|
97
101
|
],
|
|
98
102
|
artifactFile: "08-ship.md",
|
|
99
103
|
next: "done",
|
|
@@ -131,7 +135,7 @@ export const SHIP = {
|
|
|
131
135
|
{ section: "Release Notes", required: true, validationRule: "What changed, why, impact. References spec criteria. Breaking changes flagged." },
|
|
132
136
|
{ section: "Rollback Plan", required: true, validationRule: "Trigger conditions, rollback steps (exact commands), verification steps." },
|
|
133
137
|
{ section: "Monitoring", required: false, validationRule: "If applicable: what metrics/logs to watch post-deploy. Risk note if no monitoring." },
|
|
134
|
-
{ section: "Finalization", required: true, validationRule: "Exactly one finalization enum token selected. Execution result documented. Worktree cleaned if applicable." },
|
|
138
|
+
{ section: "Finalization", required: true, validationRule: "Exactly one finalization enum token selected (FINALIZE_MERGE_LOCAL | FINALIZE_OPEN_PR | FINALIZE_KEEP_BRANCH | FINALIZE_DISCARD_BRANCH | FINALIZE_NO_VCS). Execution result documented. Worktree cleaned if applicable." },
|
|
135
139
|
{ section: "Completion Status", required: false, validationRule: "If present: exactly one of SHIPPED, SHIPPED_WITH_EXCEPTIONS, BLOCKED. Exceptions documented when applicable." },
|
|
136
140
|
{ section: "Compound Step", required: false, validationRule: "Optional retrospective: at least one bullet of the form 'Insight: ... | Action: append [compound] entry to .cclaw/knowledge.jsonl', or an explicit 'No compound insight this run.' line." }
|
|
137
141
|
]
|
|
@@ -179,7 +179,7 @@ export const TDD = {
|
|
|
179
179
|
{ section: "GREEN Evidence", required: true, validationRule: "Full suite pass output captured." },
|
|
180
180
|
{ section: "REFACTOR Notes", required: true, validationRule: "What changed, why, behavior preservation confirmed." },
|
|
181
181
|
{ section: "Traceability", required: true, validationRule: "Plan task ID and spec criterion linked." },
|
|
182
|
-
{ section: "Verification Ladder", required:
|
|
182
|
+
{ section: "Verification Ladder", required: true, validationRule: "Per-slice verification tier (static, command, behavioral, human) with evidence captured for the highest tier reached this turn." },
|
|
183
183
|
{ section: "Coverage Targets", required: false, validationRule: "If present: per-module or per-code-type coverage thresholds with current values and measurement commands." },
|
|
184
184
|
{ section: "Test Pyramid Shape", required: false, validationRule: "If present: per-slice count of Small/Medium/Large tests added, to let reviewers verify the suite is not drifting top-heavy." },
|
|
185
185
|
{ section: "Prove-It Reproduction", required: false, validationRule: "Required for bug-fix slices: original failing reproduction test (RED without fix), passing output with fix (GREEN), and a note confirming the test fails again if the fix is reverted." },
|
|
@@ -187,3 +187,65 @@ export const TDD = {
|
|
|
187
187
|
],
|
|
188
188
|
batchExecutionAllowed: true
|
|
189
189
|
};
|
|
190
|
+
function quickTrackText(value) {
|
|
191
|
+
return value
|
|
192
|
+
.replace(/\btask from the plan\b/giu, "acceptance criterion from the spec")
|
|
193
|
+
.replace(/\bplan task ID\b/giu, "acceptance criterion ID")
|
|
194
|
+
.replace(/\bplan task\b/giu, "acceptance criterion")
|
|
195
|
+
.replace(/\bplan row\b/giu, "acceptance row")
|
|
196
|
+
.replace(/\bplan slice\b/giu, "acceptance slice")
|
|
197
|
+
.replace(/\bplan artifact\b/giu, "spec artifact")
|
|
198
|
+
.replace(/\btraceable to plan slice\b/giu, "traceable to acceptance criterion")
|
|
199
|
+
.replace(/05-plan\.md/gu, "04-spec.md");
|
|
200
|
+
}
|
|
201
|
+
function tddQuickTrackVariant() {
|
|
202
|
+
return {
|
|
203
|
+
...TDD,
|
|
204
|
+
skillDescription: quickTrackText(TDD.skillDescription),
|
|
205
|
+
hardGate: quickTrackText(TDD.hardGate),
|
|
206
|
+
checklist: TDD.checklist.map(quickTrackText),
|
|
207
|
+
interactionProtocol: TDD.interactionProtocol.map(quickTrackText),
|
|
208
|
+
process: TDD.process.map(quickTrackText),
|
|
209
|
+
requiredGates: TDD.requiredGates.map((gate) => gate.id === "tdd_traceable_to_plan"
|
|
210
|
+
? { ...gate, description: "Change traceability to acceptance criterion is explicit." }
|
|
211
|
+
: gate),
|
|
212
|
+
requiredEvidence: TDD.requiredEvidence.map(quickTrackText),
|
|
213
|
+
inputs: TDD.inputs.map(quickTrackText),
|
|
214
|
+
requiredContext: ["spec artifact", "existing test patterns"],
|
|
215
|
+
policyNeedles: TDD.policyNeedles.map(quickTrackText),
|
|
216
|
+
reviewSections: TDD.reviewSections.map((section) => ({
|
|
217
|
+
...section,
|
|
218
|
+
evaluationPoints: section.evaluationPoints.map(quickTrackText)
|
|
219
|
+
})),
|
|
220
|
+
crossStageTrace: {
|
|
221
|
+
...TDD.crossStageTrace,
|
|
222
|
+
readsFrom: [".cclaw/artifacts/04-spec.md"],
|
|
223
|
+
traceabilityRule: "Every RED test traces to an acceptance criterion. Every GREEN change traces to a RED test. Evidence chain must be unbroken."
|
|
224
|
+
},
|
|
225
|
+
artifactValidation: TDD.artifactValidation.map((row) => {
|
|
226
|
+
if (row.section === "Acceptance Mapping") {
|
|
227
|
+
return {
|
|
228
|
+
...row,
|
|
229
|
+
required: true,
|
|
230
|
+
validationRule: "Each RED test links to a spec acceptance criterion ID (for example AC-1)."
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (row.section === "Traceability") {
|
|
234
|
+
return {
|
|
235
|
+
...row,
|
|
236
|
+
validationRule: "Acceptance criterion IDs are linked to RED/GREEN evidence."
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
...row,
|
|
241
|
+
validationRule: quickTrackText(row.validationRule)
|
|
242
|
+
};
|
|
243
|
+
})
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
export function tddStageForTrack(track) {
|
|
247
|
+
if (track === "quick") {
|
|
248
|
+
return tddQuickTrackVariant();
|
|
249
|
+
}
|
|
250
|
+
return TDD;
|
|
251
|
+
}
|
|
@@ -520,6 +520,19 @@ inputs_hash: sha256:pending
|
|
|
520
520
|
- Adversarial review pass: true | false
|
|
521
521
|
- Overall score: <0-100>
|
|
522
522
|
|
|
523
|
+
## Trace Matrix Check
|
|
524
|
+
- Command: \`cclaw internal trace-matrix\`
|
|
525
|
+
- Orphaned criteria: 0
|
|
526
|
+
- Orphaned tasks: 0
|
|
527
|
+
- Orphaned tests: 0
|
|
528
|
+
- Evidence ref:
|
|
529
|
+
|
|
530
|
+
## Blocked Route
|
|
531
|
+
- ROUTE_BACK_TO_TDD: only when Final Verdict = BLOCKED
|
|
532
|
+
- Target stage: tdd
|
|
533
|
+
- Blocking finding IDs:
|
|
534
|
+
- Rewind command payload:
|
|
535
|
+
|
|
523
536
|
## Severity Summary
|
|
524
537
|
- Critical:
|
|
525
538
|
- Important:
|
|
@@ -585,9 +598,11 @@ inputs_hash: sha256:pending
|
|
|
585
598
|
- FINALIZE_OPEN_PR
|
|
586
599
|
- FINALIZE_KEEP_BRANCH
|
|
587
600
|
- FINALIZE_DISCARD_BRANCH
|
|
588
|
-
-
|
|
601
|
+
- FINALIZE_NO_VCS
|
|
602
|
+
- Selected label (A/B/C/D/E):
|
|
589
603
|
- Execution result:
|
|
590
604
|
- PR URL / merge commit / kept branch / discard confirmation:
|
|
605
|
+
- NO_VCS handoff target + artifact path (if FINALIZE_NO_VCS):
|
|
591
606
|
|
|
592
607
|
## Completion Status
|
|
593
608
|
- SHIPPED | SHIPPED_WITH_EXCEPTIONS | BLOCKED
|
|
@@ -662,9 +662,11 @@ Do not merge, open PR, or discard branch until verification and rollback notes a
|
|
|
662
662
|
- FINALIZE_OPEN_PR
|
|
663
663
|
- FINALIZE_KEEP_BRANCH
|
|
664
664
|
- FINALIZE_DISCARD_BRANCH
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
665
|
+
- FINALIZE_NO_VCS
|
|
666
|
+
3. If \`.git\` is unavailable, use FINALIZE_NO_VCS and record manual handoff + rollback owner.
|
|
667
|
+
4. Execute only the chosen mode and record exact result.
|
|
668
|
+
5. If merge or discard happened in a feature worktree, clean the worktree.
|
|
669
|
+
6. Update ship artifact with release notes, rollback, and finalization evidence.
|
|
668
670
|
|
|
669
671
|
## Rollback minimum
|
|
670
672
|
|
package/dist/doctor.js
CHANGED
|
@@ -623,6 +623,8 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
623
623
|
"session-start.sh",
|
|
624
624
|
"stop-checkpoint.sh",
|
|
625
625
|
"run-hook.cmd",
|
|
626
|
+
"stage-complete.sh",
|
|
627
|
+
"pre-compact.sh",
|
|
626
628
|
"prompt-guard.sh",
|
|
627
629
|
"workflow-guard.sh",
|
|
628
630
|
"context-monitor.sh"
|
|
@@ -816,10 +818,13 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
816
818
|
const codexDoc = await readHookDocument(codexHooksFile);
|
|
817
819
|
const codexHooks = toObject(codexDoc?.hooks) ?? {};
|
|
818
820
|
const codexSessionCmds = collectHookCommands(codexHooks.SessionStart);
|
|
821
|
+
const codexUserPromptCmds = collectHookCommands(codexHooks.UserPromptSubmit);
|
|
819
822
|
const codexPreCmds = collectHookCommands(codexHooks.PreToolUse);
|
|
820
823
|
const codexPostCmds = collectHookCommands(codexHooks.PostToolUse);
|
|
821
824
|
const codexStopCmds = collectHookCommands(codexHooks.Stop);
|
|
822
825
|
const codexWiringOk = codexSessionCmds.some((cmd) => cmd.includes("session-start.sh")) &&
|
|
826
|
+
codexUserPromptCmds.some((cmd) => cmd.includes("prompt-guard.sh")) &&
|
|
827
|
+
codexUserPromptCmds.some((cmd) => cmd.includes("verify-current-state --quiet")) &&
|
|
823
828
|
codexPreCmds.some((cmd) => cmd.includes("prompt-guard.sh")) &&
|
|
824
829
|
codexPreCmds.some((cmd) => cmd.includes("workflow-guard.sh")) &&
|
|
825
830
|
codexPostCmds.some((cmd) => cmd.includes("context-monitor.sh")) &&
|
|
@@ -827,7 +832,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
827
832
|
checks.push({
|
|
828
833
|
name: "hook:wiring:codex",
|
|
829
834
|
ok: codexWiringOk,
|
|
830
|
-
details: `${codexHooksFile} must wire
|
|
835
|
+
details: `${codexHooksFile} must wire SessionStart, UserPromptSubmit(prompt-guard + verify-current-state), PreToolUse(prompt/workflow), PostToolUse(context-monitor), and Stop(stop-checkpoint). PreToolUse/PostToolUse run Bash-only in Codex v0.114+`
|
|
831
836
|
});
|
|
832
837
|
// Feature flag warning: Codex ignores `.codex/hooks.json` unless the
|
|
833
838
|
// user has `[features] codex_hooks = true` in `~/.codex/config.toml`.
|
|
@@ -895,6 +900,8 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
895
900
|
if (configuredHarnesses.includes("opencode")) {
|
|
896
901
|
const file = path.join(projectRoot, ".opencode/plugins/cclaw-plugin.mjs");
|
|
897
902
|
let ok = false;
|
|
903
|
+
let singleHandlerPathOk = false;
|
|
904
|
+
let precompactHookOk = false;
|
|
898
905
|
if (await exists(file)) {
|
|
899
906
|
const content = await fs.readFile(file, "utf8");
|
|
900
907
|
ok =
|
|
@@ -904,16 +911,36 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
904
911
|
content.includes("prompt-guard.sh") &&
|
|
905
912
|
content.includes("workflow-guard.sh") &&
|
|
906
913
|
content.includes("context-monitor.sh") &&
|
|
914
|
+
content.includes("pre-compact.sh") &&
|
|
907
915
|
content.includes('"session.idle"') &&
|
|
908
916
|
content.includes('"session.resumed"') &&
|
|
917
|
+
content.includes('"session.compacted"') &&
|
|
909
918
|
content.includes('"session.cleared"') &&
|
|
910
919
|
content.includes('"experimental.chat.system.transform"');
|
|
920
|
+
singleHandlerPathOk =
|
|
921
|
+
!content.includes('eventType === "tool.execute.before"') &&
|
|
922
|
+
!content.includes('eventType === "tool.execute.after"') &&
|
|
923
|
+
content.includes('"tool.execute.before": async') &&
|
|
924
|
+
content.includes('"tool.execute.after": async');
|
|
925
|
+
precompactHookOk =
|
|
926
|
+
content.includes('eventType === "session.compacted"') &&
|
|
927
|
+
content.includes('runHookScript("pre-compact.sh"');
|
|
911
928
|
}
|
|
912
929
|
checks.push({
|
|
913
930
|
name: "lifecycle:opencode:rehydration_events",
|
|
914
931
|
ok,
|
|
915
932
|
details: `${file} must include event lifecycle handler, tool.execute.before/after with prompt/workflow/context hooks, session.idle checkpoint, and transform rehydration`
|
|
916
933
|
});
|
|
934
|
+
checks.push({
|
|
935
|
+
name: "hook:opencode:single_tool_handler_path",
|
|
936
|
+
ok: singleHandlerPathOk,
|
|
937
|
+
details: `${file} must route tool.execute.before/after through dedicated handlers exactly once (no duplicate event() branches).`
|
|
938
|
+
});
|
|
939
|
+
checks.push({
|
|
940
|
+
name: "hook:opencode:precompact_digest",
|
|
941
|
+
ok: precompactHookOk,
|
|
942
|
+
details: `${file} must run pre-compact.sh on session.compacted before bootstrap refresh.`
|
|
943
|
+
});
|
|
917
944
|
const runtimeShape = await opencodePluginRuntimeShapeCheck(projectRoot);
|
|
918
945
|
checks.push({
|
|
919
946
|
name: "hook:opencode:runtime_shape",
|
package/dist/flow-state.js
CHANGED
|
@@ -59,10 +59,10 @@ export function createInitialFlowState(activeRunIdOrOptions = "active", maybeTra
|
|
|
59
59
|
const track = options.track ?? "standard";
|
|
60
60
|
const skippedStages = skippedStagesForTrack(track);
|
|
61
61
|
const stageGateCatalog = {};
|
|
62
|
-
for (const schema of orderedStageSchemas()) {
|
|
62
|
+
for (const schema of orderedStageSchemas(track)) {
|
|
63
63
|
stageGateCatalog[schema.stage] = {
|
|
64
|
-
required: stageGateIds(schema.stage),
|
|
65
|
-
recommended: stageRecommendedGateIds(schema.stage),
|
|
64
|
+
required: stageGateIds(schema.stage, track),
|
|
65
|
+
recommended: stageRecommendedGateIds(schema.stage, track),
|
|
66
66
|
conditional: [],
|
|
67
67
|
triggered: [],
|
|
68
68
|
passed: [],
|
package/dist/gate-evidence.js
CHANGED
|
@@ -5,8 +5,9 @@ import { RUNTIME_ROOT } from "./constants.js";
|
|
|
5
5
|
import { stageSchema } from "./content/stage-schema.js";
|
|
6
6
|
import { exists } from "./fs-utils.js";
|
|
7
7
|
import { readFlowState, writeFlowState } from "./runs.js";
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import { buildTraceMatrix } from "./trace-matrix.js";
|
|
9
|
+
async function currentStageArtifactExists(projectRoot, stage, track) {
|
|
10
|
+
const artifactFile = stageSchema(stage, track).artifactFile;
|
|
10
11
|
const candidates = [
|
|
11
12
|
path.join(projectRoot, RUNTIME_ROOT, "artifacts", artifactFile),
|
|
12
13
|
path.join(projectRoot, artifactFile)
|
|
@@ -34,7 +35,7 @@ function sameStringArray(a, b) {
|
|
|
34
35
|
}
|
|
35
36
|
export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
36
37
|
const stage = flowState.currentStage;
|
|
37
|
-
const schema = stageSchema(stage);
|
|
38
|
+
const schema = stageSchema(stage, flowState.track);
|
|
38
39
|
const catalog = flowState.stageGateCatalog[stage];
|
|
39
40
|
const required = schema.requiredGates
|
|
40
41
|
.filter((gate) => gate.tier === "required")
|
|
@@ -92,7 +93,7 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
92
93
|
issues.push(`blocked gate "${gateId}" is not defined for stage "${stage}".`);
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
|
-
const artifactPresent = await currentStageArtifactExists(projectRoot, stage);
|
|
96
|
+
const artifactPresent = await currentStageArtifactExists(projectRoot, stage, flowState.track);
|
|
96
97
|
const shouldValidateArtifact = artifactPresent || catalog.passed.length > 0 || flowState.completedStages.includes(stage);
|
|
97
98
|
if (shouldValidateArtifact) {
|
|
98
99
|
const lint = await lintArtifact(projectRoot, stage);
|
|
@@ -113,6 +114,23 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
113
114
|
if (!verdictConsistency.ok) {
|
|
114
115
|
issues.push(`review verdict inconsistency: ${verdictConsistency.errors.join("; ")}`);
|
|
115
116
|
}
|
|
117
|
+
const traceGateRequired = schema.requiredGates.some((gate) => gate.id === "review_trace_matrix_clean" && gate.tier === "required");
|
|
118
|
+
if (traceGateRequired) {
|
|
119
|
+
const trace = await buildTraceMatrix(projectRoot);
|
|
120
|
+
const traceIssues = [];
|
|
121
|
+
if (trace.orphanedCriteria.length > 0) {
|
|
122
|
+
traceIssues.push(`orphaned criteria: ${trace.orphanedCriteria.join(", ")}`);
|
|
123
|
+
}
|
|
124
|
+
if (trace.orphanedTasks.length > 0) {
|
|
125
|
+
traceIssues.push(`orphaned tasks: ${trace.orphanedTasks.join(", ")}`);
|
|
126
|
+
}
|
|
127
|
+
if (trace.orphanedTests.length > 0) {
|
|
128
|
+
traceIssues.push(`orphaned tests: ${trace.orphanedTests.join(", ")}`);
|
|
129
|
+
}
|
|
130
|
+
if (traceIssues.length > 0) {
|
|
131
|
+
issues.push(`review trace-matrix gate blocked (review_trace_matrix_clean): ${traceIssues.join("; ")}.`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
116
134
|
}
|
|
117
135
|
}
|
|
118
136
|
const passedSet = new Set(catalog.passed);
|
|
@@ -149,7 +167,7 @@ export function verifyCompletedStagesGateClosure(flowState) {
|
|
|
149
167
|
const issues = [];
|
|
150
168
|
const openStages = [];
|
|
151
169
|
for (const stage of flowState.completedStages) {
|
|
152
|
-
const schema = stageSchema(stage);
|
|
170
|
+
const schema = stageSchema(stage, flowState.track);
|
|
153
171
|
const catalog = flowState.stageGateCatalog[stage];
|
|
154
172
|
const required = schema.requiredGates
|
|
155
173
|
.filter((gate) => gate.tier === "required")
|
|
@@ -177,10 +195,11 @@ export function verifyCompletedStagesGateClosure(flowState) {
|
|
|
177
195
|
}
|
|
178
196
|
export function reconcileCurrentStageGateCatalog(flowState) {
|
|
179
197
|
const stage = flowState.currentStage;
|
|
180
|
-
const
|
|
198
|
+
const schema = stageSchema(stage, flowState.track);
|
|
199
|
+
const required = schema.requiredGates
|
|
181
200
|
.filter((gate) => gate.tier === "required")
|
|
182
201
|
.map((gate) => gate.id);
|
|
183
|
-
const recommended =
|
|
202
|
+
const recommended = schema.requiredGates
|
|
184
203
|
.filter((gate) => gate.tier === "recommended")
|
|
185
204
|
.map((gate) => gate.id);
|
|
186
205
|
const conditional = [];
|
package/dist/install.js
CHANGED
|
@@ -213,9 +213,10 @@ async function writeEvalScaffold(projectRoot) {
|
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
async function writeSkills(projectRoot, config) {
|
|
216
|
+
const skillTrack = config?.defaultTrack ?? "standard";
|
|
216
217
|
for (const stage of COMMAND_FILE_ORDER) {
|
|
217
218
|
const folder = stageSkillFolder(stage);
|
|
218
|
-
await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), stageSkillMarkdown(stage));
|
|
219
|
+
await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), stageSkillMarkdown(stage, skillTrack));
|
|
219
220
|
// Progressive disclosure (A.2#8): materialize the full example artifact as
|
|
220
221
|
// a sibling reference file. The stage skill only links to it; agents load
|
|
221
222
|
// the reference on demand.
|
|
@@ -354,7 +354,7 @@ async function runAdvanceStage(projectRoot, args, io) {
|
|
|
354
354
|
io.stderr.write(`cclaw internal advance-stage: current stage is "${flowState.currentStage}", not "${args.stage}".\n`);
|
|
355
355
|
return 1;
|
|
356
356
|
}
|
|
357
|
-
const schema = stageSchema(args.stage);
|
|
357
|
+
const schema = stageSchema(args.stage, flowState.track);
|
|
358
358
|
const requiredGateIds = schema.requiredGates
|
|
359
359
|
.filter((gate) => gate.tier === "required")
|
|
360
360
|
.map((gate) => gate.id);
|
package/dist/run-archive.js
CHANGED
|
@@ -14,6 +14,8 @@ const STATE_SNAPSHOT_EXCLUDE = new Set([
|
|
|
14
14
|
".flow-state.lock",
|
|
15
15
|
".delegation.lock"
|
|
16
16
|
]);
|
|
17
|
+
const DELEGATION_LOG_FILE = "delegation-log.json";
|
|
18
|
+
const TDD_CYCLE_LOG_FILE = "tdd-cycle-log.jsonl";
|
|
17
19
|
function runsRoot(projectRoot) {
|
|
18
20
|
return path.join(projectRoot, RUNS_DIR_REL_PATH);
|
|
19
21
|
}
|
|
@@ -60,6 +62,12 @@ async function snapshotStateDirectory(projectRoot, destinationRoot) {
|
|
|
60
62
|
}
|
|
61
63
|
return copied.sort((a, b) => a.localeCompare(b));
|
|
62
64
|
}
|
|
65
|
+
async function resetCarryoverStateFiles(projectRoot, activeRunId) {
|
|
66
|
+
const stateDir = stateDirPath(projectRoot);
|
|
67
|
+
await ensureDir(stateDir);
|
|
68
|
+
await writeFileSafe(path.join(stateDir, DELEGATION_LOG_FILE), `${JSON.stringify({ runId: activeRunId, entries: [] }, null, 2)}\n`);
|
|
69
|
+
await writeFileSafe(path.join(stateDir, TDD_CYCLE_LOG_FILE), "");
|
|
70
|
+
}
|
|
63
71
|
function toArchiveDate(date = new Date()) {
|
|
64
72
|
const yyyy = date.getFullYear().toString();
|
|
65
73
|
const mm = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
@@ -200,6 +208,7 @@ export async function archiveRun(projectRoot, featureName, options = {}) {
|
|
|
200
208
|
const snapshottedStateFiles = await snapshotStateDirectory(projectRoot, archiveStatePath);
|
|
201
209
|
const resetState = createInitialFlowState();
|
|
202
210
|
await writeFlowState(projectRoot, resetState, { allowReset: true });
|
|
211
|
+
await resetCarryoverStateFiles(projectRoot, resetState.activeRunId);
|
|
203
212
|
const archivedAt = new Date().toISOString();
|
|
204
213
|
const manifest = {
|
|
205
214
|
version: 1,
|