cclaw-cli 0.46.11 → 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/next-command.js +3 -0
- package/dist/content/retro-command.js +15 -4
- package/dist/content/stage-schema.js +9 -2
- package/dist/content/stages/review.js +15 -12
- package/dist/content/stages/ship.js +16 -12
- package/dist/content/templates.js +16 -1
- package/dist/content/utility-skills.js +5 -3
- package/dist/gate-evidence.js +18 -0
- 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) {
|
|
@@ -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\`:
|
|
@@ -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
|
|
|
@@ -36,11 +36,12 @@ const REQUIRED_GATE_IDS = {
|
|
|
36
36
|
"tdd_verified_before_complete",
|
|
37
37
|
...(track === "quick" ? [] : ["tdd_traceable_to_plan"])
|
|
38
38
|
],
|
|
39
|
-
review: [
|
|
39
|
+
review: (track) => [
|
|
40
40
|
"review_layer1_spec_compliance",
|
|
41
41
|
"review_layer2_security",
|
|
42
42
|
"review_criticals_resolved",
|
|
43
|
-
"review_army_json_valid"
|
|
43
|
+
"review_army_json_valid",
|
|
44
|
+
...(track === "quick" ? [] : ["review_trace_matrix_clean"])
|
|
44
45
|
],
|
|
45
46
|
ship: [
|
|
46
47
|
"ship_review_verdict_valid",
|
|
@@ -268,6 +269,12 @@ export function buildTransitionRules() {
|
|
|
268
269
|
guards: stageGateIds(schema.stage)
|
|
269
270
|
});
|
|
270
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
|
+
});
|
|
271
278
|
return rules;
|
|
272
279
|
}
|
|
273
280
|
export function stagePolicyNeedles(stage, track = "standard") {
|
|
@@ -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
|
]
|
|
@@ -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/gate-evidence.js
CHANGED
|
@@ -5,6 +5,7 @@ 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
|
+
import { buildTraceMatrix } from "./trace-matrix.js";
|
|
8
9
|
async function currentStageArtifactExists(projectRoot, stage, track) {
|
|
9
10
|
const artifactFile = stageSchema(stage, track).artifactFile;
|
|
10
11
|
const candidates = [
|
|
@@ -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);
|
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,
|