cclaw-cli 0.45.0 → 0.46.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -140,7 +140,7 @@ Plus harness-specific shims:
140
140
  `cclaw init` writes five keys, on purpose:
141
141
 
142
142
  ```yaml
143
- version: 0.45.0
143
+ version: 0.46.0
144
144
  flowVersion: 1.0.0
145
145
  harnesses:
146
146
  - codex
@@ -17,6 +17,7 @@ export type LearningEntryType = "rule" | "pattern" | "lesson" | "compound";
17
17
  export type LearningConfidence = "high" | "medium" | "low";
18
18
  export type LearningUniversality = "project" | "personal" | "universal";
19
19
  export type LearningMaturity = "raw" | "lifted-to-rule" | "lifted-to-enforcement";
20
+ export type LearningSource = "stage" | "retro" | "compound" | "ideate" | "manual";
20
21
  export interface LearningSeedEntry {
21
22
  type: LearningEntryType;
22
23
  trigger: string;
@@ -33,6 +34,7 @@ export interface LearningSeedEntry {
33
34
  first_seen_ts?: string;
34
35
  last_seen_ts?: string;
35
36
  project?: string | null;
37
+ source?: LearningSource | null;
36
38
  }
37
39
  export interface LearningsParseResult {
38
40
  ok: boolean;
@@ -43,7 +45,6 @@ export interface LearningsParseResult {
43
45
  }
44
46
  export declare function parseLearningsSection(sectionBody: string): LearningsParseResult;
45
47
  export declare function lintArtifact(projectRoot: string, stage: FlowStage): Promise<LintResult>;
46
- export declare function lintAllArtifacts(projectRoot: string): Promise<LintResult[]>;
47
48
  export declare function validateReviewArmy(projectRoot: string): Promise<{
48
49
  valid: boolean;
49
50
  errors: string[];
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { RUNTIME_ROOT } from "./constants.js";
4
4
  import { exists } from "./fs-utils.js";
5
- import { orderedStageSchemas, stageSchema } from "./content/stage-schema.js";
5
+ import { stageSchema } from "./content/stage-schema.js";
6
6
  import { FLOW_STAGES } from "./types.js";
7
7
  async function resolveArtifactPath(projectRoot, fileName) {
8
8
  const relPath = path.join(RUNTIME_ROOT, "artifacts", fileName);
@@ -183,10 +183,53 @@ function getMarkdownTableRows(sectionBody) {
183
183
  }
184
184
  return rows;
185
185
  }
186
+ const DIAGRAM_ARROW_PATTERN = /(?:<--?>|<?==?>|--?>|->>|=>|-\.->|→|⟶|↦)/u;
187
+ 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
+ const DIAGRAM_GENERIC_NODE_PATTERN = /\b(service|component|module|system)\s*(?:[A-Z0-9])?\b/iu;
189
+ function diagramEdgeLines(sectionBody) {
190
+ return sectionBody
191
+ .split(/\r?\n/)
192
+ .map((line) => line.trim())
193
+ .filter((line) => line.length > 0)
194
+ .filter((line) => !line.startsWith("```"))
195
+ .filter((line) => !line.startsWith("%%"))
196
+ .filter((line) => DIAGRAM_ARROW_PATTERN.test(line));
197
+ }
198
+ function hasFailureEdgeInDiagram(sectionBody) {
199
+ const lines = diagramEdgeLines(sectionBody);
200
+ for (const line of lines) {
201
+ if (DIAGRAM_ARROW_PATTERN.test(line) && DIAGRAM_FAILURE_EDGE_PATTERN.test(line)) {
202
+ return true;
203
+ }
204
+ }
205
+ return false;
206
+ }
207
+ function hasLabeledDiagramArrow(lines) {
208
+ return lines.some((line) => /\|[^|]+\|/u.test(line) || /:\s*[A-Za-z]/u.test(line));
209
+ }
210
+ function hasAsyncDiagramEdge(lines) {
211
+ return lines.some((line) => /-\.->|-->>|~~>|\basync\b/iu.test(line));
212
+ }
213
+ function hasSyncDiagramEdge(lines) {
214
+ return lines.some((line) => {
215
+ if (/\bsync\b/iu.test(line))
216
+ return true;
217
+ if (!/(-->|->|=>|→|⟶|↦)/u.test(line))
218
+ return false;
219
+ return !/-\.->|-->>|~~>/u.test(line);
220
+ });
221
+ }
186
222
  const LEARNING_TYPE_SET = new Set(["rule", "pattern", "lesson", "compound"]);
187
223
  const LEARNING_CONFIDENCE_SET = new Set(["high", "medium", "low"]);
188
224
  const LEARNING_UNIVERSALITY_SET = new Set(["project", "personal", "universal"]);
189
225
  const LEARNING_MATURITY_SET = new Set(["raw", "lifted-to-rule", "lifted-to-enforcement"]);
226
+ const LEARNING_SOURCE_SET = new Set([
227
+ "stage",
228
+ "retro",
229
+ "compound",
230
+ "ideate",
231
+ "manual"
232
+ ]);
190
233
  const FLOW_STAGE_SET = new Set(FLOW_STAGES);
191
234
  const LEARNING_ALLOWED_KEYS = new Set([
192
235
  "type",
@@ -203,7 +246,8 @@ const LEARNING_ALLOWED_KEYS = new Set([
203
246
  "created",
204
247
  "first_seen_ts",
205
248
  "last_seen_ts",
206
- "project"
249
+ "project",
250
+ "source"
207
251
  ]);
208
252
  function isIsoUtcTimestamp(value) {
209
253
  return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/u.test(value);
@@ -276,6 +320,14 @@ function parseLearningSeedEntry(raw, index) {
276
320
  if (obj.project !== undefined && !isNullableString(obj.project)) {
277
321
  return { ok: false, error: `Learnings bullet #${index} field "project" must be string or null.` };
278
322
  }
323
+ if (obj.source !== undefined &&
324
+ obj.source !== null &&
325
+ (typeof obj.source !== "string" || !LEARNING_SOURCE_SET.has(obj.source))) {
326
+ return {
327
+ ok: false,
328
+ error: `Learnings bullet #${index} field "source" must be stage|retro|compound|ideate|manual or null.`
329
+ };
330
+ }
279
331
  if (obj.frequency !== undefined &&
280
332
  (typeof obj.frequency !== "number" || !Number.isInteger(obj.frequency) || obj.frequency < 1)) {
281
333
  return { ok: false, error: `Learnings bullet #${index} field "frequency" must be an integer >= 1.` };
@@ -541,20 +593,57 @@ function validateSectionBody(sectionBody, rule, sectionName) {
541
593
  };
542
594
  }
543
595
  }
544
- const keywords = extractRequiredKeywords(rule);
545
- if (keywords.length > 0) {
546
- const bodyLower = sectionBody.toLowerCase();
547
- const found = keywords.filter((kw) => bodyLower.includes(kw.toLowerCase()));
548
- const threshold = Math.ceil(keywords.length * 0.5);
549
- if (found.length < threshold) {
550
- const missing = keywords.filter((kw) => !bodyLower.includes(kw.toLowerCase()));
596
+ const sectionNameNormalized = normalizeHeadingTitle(sectionName).toLowerCase();
597
+ if (sectionNameNormalized === "architecture diagram") {
598
+ const edgeLines = diagramEdgeLines(sectionBody);
599
+ if (edgeLines.length === 0) {
600
+ return {
601
+ ok: false,
602
+ details: "Architecture Diagram must include at least one directional edge line (for example `A -->|action| B`)."
603
+ };
604
+ }
605
+ if (!hasLabeledDiagramArrow(edgeLines)) {
606
+ return {
607
+ ok: false,
608
+ details: "Architecture Diagram must label each edge with an action/message (for example `A -->|sync: persist| B`)."
609
+ };
610
+ }
611
+ const genericLine = edgeLines.find((line) => DIAGRAM_GENERIC_NODE_PATTERN.test(line));
612
+ if (genericLine) {
613
+ return {
614
+ ok: false,
615
+ details: `Architecture Diagram uses a generic node label in edge "${genericLine}". Use concrete component names instead of placeholders like Service/Component.`
616
+ };
617
+ }
618
+ if (!hasAsyncDiagramEdge(edgeLines) || !hasSyncDiagramEdge(edgeLines)) {
551
619
  return {
552
620
  ok: false,
553
- details: `Rule expects keywords (${threshold}/${keywords.length} minimum): missing ${missing.join(", ")}.`
621
+ details: "Architecture Diagram must distinguish sync vs async edges (for example solid + dotted arrows, or `sync:` and `async:` labels)."
622
+ };
623
+ }
624
+ if (!hasFailureEdgeInDiagram(sectionBody)) {
625
+ return {
626
+ ok: false,
627
+ details: "Architecture Diagram must include at least one failure-edge arrow with a failure keyword (for example: timeout, error, fallback, degraded, retry)."
554
628
  };
555
629
  }
556
630
  }
557
- if (normalizeHeadingTitle(sectionName).toLowerCase() === "acceptance criteria" &&
631
+ if (sectionNameNormalized !== "architecture diagram") {
632
+ const keywords = extractRequiredKeywords(rule);
633
+ if (keywords.length > 0) {
634
+ const bodyLower = sectionBody.toLowerCase();
635
+ const found = keywords.filter((kw) => bodyLower.includes(kw.toLowerCase()));
636
+ const threshold = Math.ceil(keywords.length * 0.5);
637
+ if (found.length < threshold) {
638
+ const missing = keywords.filter((kw) => !bodyLower.includes(kw.toLowerCase()));
639
+ return {
640
+ ok: false,
641
+ details: `Rule expects keywords (${threshold}/${keywords.length} minimum): missing ${missing.join(", ")}.`
642
+ };
643
+ }
644
+ }
645
+ }
646
+ if (sectionNameNormalized === "acceptance criteria" &&
558
647
  /observable[\s,]*measurable[\s,]+(and )?falsifiable/iu.test(rule)) {
559
648
  const rows = getMarkdownTableRows(sectionBody);
560
649
  for (const row of rows) {
@@ -749,13 +838,6 @@ export async function lintArtifact(projectRoot, stage) {
749
838
  const passed = findings.every((f) => !f.required || f.found);
750
839
  return { stage, file: relFile, passed, findings };
751
840
  }
752
- export async function lintAllArtifacts(projectRoot) {
753
- const out = [];
754
- for (const schema of orderedStageSchemas()) {
755
- out.push(await lintArtifact(projectRoot, schema.stage));
756
- }
757
- return out;
758
- }
759
841
  function isNonEmptyString(v) {
760
842
  return typeof v === "string" && v.length > 0;
761
843
  }
@@ -18,7 +18,7 @@ function tierDescription(tier) {
18
18
  return "full native automation";
19
19
  if (tier === "tier2")
20
20
  return "partial automation with waivers";
21
- return "manual fallback only";
21
+ return "compatibility shim";
22
22
  }
23
23
  export function harnessIntegrationDocMarkdown() {
24
24
  const harnesses = Object.keys(HARNESS_ADAPTERS);
@@ -67,7 +67,6 @@ ${hookRows}
67
67
 
68
68
  - \`tier1\`: full native delegation + structured asks + full hook surface.
69
69
  - \`tier2\`: usable flow with capability gaps; mandatory delegation can require waivers.
70
- - \`tier3\`: manual-only fallback; no native automation guarantees.
71
70
  - Codex-specific ceiling: \`PreToolUse\` can only intercept \`Bash\`. Direct
72
71
  \`Write\`/\`Edit\` to \`.cclaw/state/flow-state.json\` cannot be hard-blocked
73
72
  at hook level, so the canonical path is
@@ -82,7 +81,6 @@ All harnesses receive the same utility commands:
82
81
  - \`/cc-next\` - stage progression
83
82
  - \`/cc-ideate\` - discovery mode for ranked repo-improvement backlog
84
83
  - \`/cc-view\` - read-only router for status/tree/diff
85
- - \`/cc-learn\` - knowledge capture/lookup
86
84
  - \`/cc-ops\` - operations router for feature/tdd-log/retro/compound/archive/rewind
87
85
 
88
86
  Read-only subcommands:
@@ -1,9 +1,2 @@
1
1
  export declare function ideateCommandContract(): string;
2
2
  export declare function ideateCommandSkillMarkdown(): string;
3
- /**
4
- * Exposed for tests and docs that need to mention the artifact convention
5
- * without hard-coding the path string in two places.
6
- */
7
- export declare const IDEATION_ARTIFACT_PATH_PATTERN = ".cclaw/artifacts/ideation-<YYYY-MM-DD-slug>.md";
8
- export declare const IDEATION_ARTIFACT_GLOB_PATTERN = ".cclaw/artifacts/ideation-*.md";
9
- export declare const IDEATION_RESUME_WINDOW = 30;
@@ -208,10 +208,3 @@ lettered list with the same four labels. Do not invent extra options.
208
208
  option in the handoff prompt must reference a concrete command.
209
209
  `;
210
210
  }
211
- /**
212
- * Exposed for tests and docs that need to mention the artifact convention
213
- * without hard-coding the path string in two places.
214
- */
215
- export const IDEATION_ARTIFACT_PATH_PATTERN = IDEATION_ARTIFACT_PATTERN;
216
- export const IDEATION_ARTIFACT_GLOB_PATTERN = IDEATION_ARTIFACT_GLOB;
217
- export const IDEATION_RESUME_WINDOW = IDEATION_RESUME_WINDOW_DAYS;
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Canonical JSONL field order (matches the reference spec).
3
- * Exported for tests and any programmatic writer that wants the exact shape.
2
+ * Canonical required JSONL field order (matches strict validator keys).
3
+ * Optional keys (for now: `source`) may be appended after these required fields.
4
+ * Exported for tests and any programmatic writer that wants a stable base shape.
4
5
  */
5
6
  export declare const KNOWLEDGE_JSONL_FIELDS: readonly ["type", "trigger", "action", "confidence", "domain", "stage", "origin_stage", "origin_feature", "frequency", "universality", "maturity", "created", "first_seen_ts", "last_seen_ts", "project"];
6
7
  export declare function learnSkillMarkdown(): string;
@@ -10,8 +10,9 @@ const KNOWLEDGE_ARCHIVE_PATH = ".cclaw/knowledge.archive.jsonl";
10
10
  const LEARN_SKILL_NAME = "learnings";
11
11
  const LEARN_SKILL_DESCRIPTION = "Project-scoped knowledge store: append and query rule/pattern/lesson/compound entries in the canonical JSONL file at .cclaw/knowledge.jsonl. Strict schema, append-only, machine-queryable.";
12
12
  /**
13
- * Canonical JSONL field order (matches the reference spec).
14
- * Exported for tests and any programmatic writer that wants the exact shape.
13
+ * Canonical required JSONL field order (matches strict validator keys).
14
+ * Optional keys (for now: `source`) may be appended after these required fields.
15
+ * Exported for tests and any programmatic writer that wants a stable base shape.
15
16
  */
16
17
  export const KNOWLEDGE_JSONL_FIELDS = [
17
18
  "type",
@@ -71,8 +72,9 @@ Do not invent alternate stores (no markdown mirror, no SQLite, no per-stage file
71
72
 
72
73
  ## Entry format — strict JSONL schema
73
74
 
74
- Exactly one JSON object per line. Fields must appear in the order:
75
+ Exactly one JSON object per line. Required fields must appear in the order:
75
76
  \`type, trigger, action, confidence, domain, stage, origin_stage, origin_feature, frequency, universality, maturity, created, first_seen_ts, last_seen_ts, project\`.
77
+ Optional field \`source\` may be appended after \`project\`.
76
78
 
77
79
  \`\`\`json
78
80
  {"type":"pattern","trigger":"when reviewing external payloads","action":"parse through zod before touching service layer","confidence":"high","domain":"api","stage":"review","origin_stage":"review","origin_feature":"payload-hardening","frequency":1,"universality":"project","maturity":"raw","created":"2026-04-14T12:00:00Z","first_seen_ts":"2026-04-14T12:00:00Z","last_seen_ts":"2026-04-14T12:00:00Z","project":"cclaw"}
@@ -95,9 +97,10 @@ Exactly one JSON object per line. Fields must appear in the order:
95
97
  | \`first_seen_ts\` | ISO 8601 UTC string | yes | First observed timestamp (usually equals \`created\`). |
96
98
  | \`last_seen_ts\` | ISO 8601 UTC string | yes | Last re-confirmed timestamp. |
97
99
  | \`project\` | string \\| null | yes | Repo or scope name. Use \`null\` when the entry crosses projects. |
100
+ | \`source\` | \`"stage" \\| "retro" \\| "compound" \\| "ideate" \\| "manual" \\| null\` | no | Origin channel for the entry when known. |
98
101
 
99
102
  Rules:
100
- - No other fields. Extra keys are forbidden and MUST be rejected by any writer.
103
+ - No other fields beyond the table above. Extra keys are forbidden and MUST be rejected by any writer.
101
104
  - Every required-null field must be emitted explicitly as \`null\` (not omitted). This keeps the file grep-friendly.
102
105
  - Append-only: never rewrite or delete a historical line. Corrections are new
103
106
  entries whose \`trigger\` clearly supersedes the earlier one.
@@ -167,7 +170,7 @@ Do not edit source code from this command. Only operate on \`${KNOWLEDGE_PATH}\`
167
170
  |---|---|---|
168
171
  | (default) | — | Show recent knowledge entries (tail of JSONL, pretty-printed). |
169
172
  | \`search\` | \`<query>\` | Stream-filter the JSONL for matching \`trigger\`, \`action\`, \`domain\`, \`project\`. |
170
- | \`add\` | — | Append one JSON line (\`rule\` / \`pattern\` / \`lesson\` / \`compound\`) with the strict 15-field schema. |
173
+ | \`add\` | — | Append one JSON line (\`rule\` / \`pattern\` / \`lesson\` / \`compound\`) with the strict JSONL schema (15 required fields + optional \`source\`). |
171
174
  | \`curate\` | — | Hand off to the **knowledge-curation** skill: read-only audit + soft-archive plan when the file exceeds the curation threshold. |
172
175
  `;
173
176
  }
@@ -58,7 +58,7 @@ in the structured ask; there is no \`--skip\` flag.
58
58
  - \`skip\` — record \`retroSkipped: true\` + one-line reason, no compound entry required.
59
59
  6. On **accept**:
60
60
  - append >=1 strict-schema JSONL line to \`${knowledgePath()}\` with
61
- \`type: "compound"\` and \`stage: "retro"\`,
61
+ \`type: "compound"\`, \`source: "retro"\`, and \`stage: null\`,
62
62
  - set \`retro.required = true\`, \`retro.completedAt = <ISO>\`,
63
63
  \`retro.compoundEntries = <count>\`,
64
64
  - set \`closeout.retroAcceptedAt = <ISO>\`,
@@ -121,7 +121,7 @@ Do not silently skip. Do not finalize without updating \`flow-state.json\`.
121
121
  > - **skip** — no retro this run (requires one-line reason).
122
122
 
123
123
  4. Apply the state transition for the chosen option:
124
- - \`accept\` → append \`{ "type": "compound", "stage": "retro", ... }\` line
124
+ - \`accept\` → append \`{ "type": "compound", "source": "retro", "stage": null, ... }\` line
125
125
  to \`${knowledgePath()}\`; set \`retro.completedAt\`, \`retro.compoundEntries\`,
126
126
  \`closeout.retroAcceptedAt\`; set \`closeout.shipSubstate = "compound_review"\`.
127
127
  - \`edit\` → leave \`shipSubstate = "retro_review"\`; announce resume path.
@@ -54,8 +54,6 @@ export const BRAINSTORM = {
54
54
  "Handoff to scope only after approval is explicit."
55
55
  ],
56
56
  requiredGates: [
57
- { id: "brainstorm_context_explored", description: "Project context (files, docs, existing patterns) was checked before asking questions." },
58
- { id: "brainstorm_idea_understood", description: "Agent and user share the same understanding of the problem, constraints, and success criteria." },
59
57
  { id: "brainstorm_approaches_compared", description: "2-3 architecturally distinct approaches were compared with real trade-offs and a recommendation." },
60
58
  { id: "brainstorm_direction_approved", description: "User approved a concrete direction and what exactly was approved is stated." },
61
59
  { id: "brainstorm_artifact_reviewed", description: "User reviewed the written brainstorm artifact and confirmed readiness." }
@@ -123,10 +121,10 @@ export const BRAINSTORM = {
123
121
  artifactValidation: [
124
122
  { section: "Context", required: true, validationRule: "Must reference project state and relevant existing code or patterns." },
125
123
  { section: "Problem", required: true, validationRule: "Must define what we're solving, success criteria, and constraints." },
126
- { section: "Clarifying Questions", required: true, validationRule: "Must capture question, answer, and decision impact for each clarifying question." },
124
+ { section: "Clarifying Questions", required: false, validationRule: "Must capture question, answer, and decision impact for each clarifying question." },
127
125
  { section: "Approaches", required: true, validationRule: "Must compare 2-3 architecturally distinct options with real trade-offs and recommendation." },
128
126
  { section: "Selected Direction", required: true, validationRule: "Must include the selected approach, rationale, and explicit approval marker." },
129
- { section: "Design", required: true, validationRule: "Must cover architecture, key components, and data flow scaled to complexity." },
130
- { section: "Assumptions and Open Questions", required: true, validationRule: "Must capture unresolved assumptions/open questions, or explicitly state none." }
127
+ { section: "Design", required: false, validationRule: "Must cover architecture, key components, and data flow scaled to complexity." },
128
+ { section: "Assumptions and Open Questions", required: false, validationRule: "Must capture unresolved assumptions/open questions, or explicitly state none." }
131
129
  ]
132
130
  };
@@ -25,7 +25,7 @@ export const DESIGN = {
25
25
  "Codebase Investigation — Before any design decision, read the actual code in the blast radius. List every file that will be touched, its current responsibilities, and existing patterns (error handling, naming, test style). Design must conform to discovered patterns, not impose new ones without justification.",
26
26
  "Step 0: Scope Challenge — what existing code solves sub-problems? Minimum change set? Complexity check: 8+ files or 2+ new services = complexity smell → flag for possible scope reduction.",
27
27
  "Search Before Building — For each technical choice (library, pattern, architecture), search for existing solutions. Label findings: Layer 1 (exact match), Layer 2 (partial match, needs adaptation), Layer 3 (inspiration only), EUREKA (unexpected perfect solution). Default to existing before custom.",
28
- "Architecture Review — system design, component boundaries, data flow, scaling, security architecture. For each new codepath: one realistic production failure scenario. **Mandatory:** produce at least one architecture diagram (ASCII, Mermaid, or tool-generated) showing component boundaries and data flow direction. Apply the **Visual Communication rules** (see below) — an unlabeled or generic diagram is worse than no diagram, because it pretends to encode decisions it does not.",
28
+ "Architecture Review — system design, component boundaries, data flow, scaling, security architecture. For each new codepath: one realistic production failure scenario. **Mandatory:** produce at least one architecture diagram (ASCII, Mermaid, or tool-generated) showing component boundaries and data flow direction. Include at least one labeled failure edge, e.g. `API -->|timeout| FallbackCache -->|degraded response| User`. Apply the **Visual Communication rules** (see below) — an unlabeled or generic diagram is worse than no diagram, because it pretends to encode decisions it does not.",
29
29
  "Code Quality Review — code organization, DRY violations, error handling patterns, over/under-engineering assessment.",
30
30
  "Test Review — diagram every new flow, data path, error path. For each: what test type covers it? Does one exist? What is the gap? Produce test plan artifact.",
31
31
  "Performance Review — N+1 queries, memory concerns, caching opportunities, slow code paths. What breaks at 10x load? At 100x?",
@@ -62,8 +62,6 @@ export const DESIGN = {
62
62
  "Write design lock artifact for downstream spec/plan."
63
63
  ],
64
64
  requiredGates: [
65
- { id: "design_codebase_investigated", description: "Blast-radius files read and current patterns catalogued." },
66
- { id: "design_scope_challenge_done", description: "Step 0 scope challenge completed with existing-code mapping." },
67
65
  { id: "design_architecture_locked", description: "Architecture boundaries are explicit and approved." },
68
66
  { id: "design_data_flow_mapped", description: "Data/state flow includes edge-case paths." },
69
67
  { id: "design_failure_modes_mapped", description: "Failure modes and mitigations are documented." },
@@ -193,16 +191,16 @@ export const DESIGN = {
193
191
  traceabilityRule: "Every architecture decision must trace to a scope boundary. Every downstream spec requirement must trace to a design decision."
194
192
  },
195
193
  artifactValidation: [
196
- { section: "Codebase Investigation", required: true, validationRule: "Must list blast-radius files with current responsibilities and discovered patterns." },
197
- { section: "Search Before Building", required: true, validationRule: "For each technical choice: Layer 1 (exact match), Layer 2 (partial match), Layer 3 (inspiration), EUREKA labels with reuse-first default." },
194
+ { section: "Codebase Investigation", required: false, validationRule: "Must list blast-radius files with current responsibilities and discovered patterns." },
195
+ { section: "Search Before Building", required: false, validationRule: "For each technical choice: Layer 1 (exact match), Layer 2 (partial match), Layer 3 (inspiration), EUREKA labels with reuse-first default." },
198
196
  { section: "Architecture Boundaries", required: true, validationRule: "Must list component boundaries with ownership." },
199
- { section: "Architecture Diagram", required: true, validationRule: "At least one diagram (ASCII, Mermaid, or image) showing component boundaries and data flow direction. Diagram must: (1) label every node with a concrete component name (no generic 'Service A/B'), (2) label every arrow with the action or message (no unlabeled arrows), (3) mark direction of data flow explicitly, (4) distinguish synchronous from asynchronous edges (e.g. solid vs dashed, or `sync:` / `async:` prefix), (5) show at least one failure edge or degraded-mode branch when the system has one." },
200
- { section: "Data Flow", required: true, validationRule: "Must include happy path, nil input, empty input, upstream error paths." },
197
+ { section: "Architecture Diagram", required: true, validationRule: "At least one diagram (ASCII, Mermaid, or image) showing component boundaries and data flow direction. Diagram must: (1) label every node with a concrete component name (no generic 'Service A/B'), (2) label every arrow with the action or message (no unlabeled arrows), (3) mark direction of data flow explicitly, (4) distinguish synchronous from asynchronous edges (e.g. solid vs dashed, or `sync:` / `async:` prefix), (5) include at least one failure/degraded edge line that contains an arrow plus a failure keyword (`timeout`, `error`, `fallback`, `degraded`, `retry`, etc.)." },
198
+ { section: "Data Flow", required: false, validationRule: "Must include happy path, nil input, empty input, upstream error paths." },
201
199
  { section: "Failure Mode Table", required: true, validationRule: "Each failure mode has: trigger, detection, mitigation, user impact." },
202
- { section: "Test Strategy", required: true, validationRule: "Must define unit/integration/e2e expectations with coverage targets." },
203
- { section: "Performance Budget", required: true, validationRule: "For each critical path: metric name, target threshold, and measurement method." },
204
- { section: "What Already Exists", required: true, validationRule: "For each sub-problem: existing code/library found (Layer 1-3/EUREKA label), reuse decision, and adaptation needed." },
205
- { section: "NOT in scope", required: true, validationRule: "Work considered and explicitly deferred with one-line rationale." },
200
+ { section: "Test Strategy", required: false, validationRule: "Must define unit/integration/e2e expectations with coverage targets." },
201
+ { section: "Performance Budget", required: false, validationRule: "For each critical path: metric name, target threshold, and measurement method." },
202
+ { section: "What Already Exists", required: false, validationRule: "For each sub-problem: existing code/library found (Layer 1-3/EUREKA label), reuse decision, and adaptation needed." },
203
+ { section: "NOT in scope", required: false, validationRule: "Work considered and explicitly deferred with one-line rationale." },
206
204
  { section: "Parallelization Strategy", required: false, validationRule: "If multi-module: dependency table, parallel lanes, conflict flags." },
207
205
  { section: "Unresolved Decisions", required: false, validationRule: "If any: what info is missing, who provides it, default if unanswered." },
208
206
  { section: "Interface Contracts", required: false, validationRule: "If present: for each module boundary list produces (outputs) and consumes (inputs) with data types." },
@@ -51,9 +51,7 @@ export const PLAN = {
51
51
  ],
52
52
  requiredGates: [
53
53
  { id: "plan_tasks_sliced_2_5_min", description: "Tasks are small, executable slices." },
54
- { id: "plan_dependency_graph_written", description: "Dependency graph and order are explicit." },
55
54
  { id: "plan_dependency_batches_defined", description: "Tasks are grouped into executable batches with gate checks." },
56
- { id: "plan_verification_steps_defined", description: "Each task has verification guidance." },
57
55
  { id: "plan_acceptance_mapped", description: "Each task maps to a spec acceptance criterion." },
58
56
  { id: "plan_wait_for_confirm", description: "Execution blocked until explicit user confirmation." }
59
57
  ],
@@ -143,7 +141,7 @@ export const PLAN = {
143
141
  traceabilityRule: "Every task must trace to a spec acceptance criterion. Every locked scope decision (D-XX) must trace to at least one plan task or explicit defer rationale. Every downstream RED test must trace to a plan task."
144
142
  },
145
143
  artifactValidation: [
146
- { section: "Dependency Graph", required: true, validationRule: "Ordering and parallel opportunities explicit. No circular dependencies." },
144
+ { section: "Dependency Graph", required: false, validationRule: "Ordering and parallel opportunities explicit. No circular dependencies." },
147
145
  { section: "Dependency Batches", required: true, validationRule: "Every task belongs to a batch. Each batch has an exit gate and dependency statement." },
148
146
  { section: "Task List", required: true, validationRule: "Each task row includes ID, description, acceptance criterion, verification command, and effort estimate (S/M/L). Every task must also carry a minutes estimate within the 2-5 minute budget. When the sliceReview feature is enabled in the cclaw config, each task row additionally declares touchCount, touchPaths, and an optional highRisk flag so the TDD stage can decide whether a Per-Slice Review pass is required." },
149
147
  { section: "Acceptance Mapping", required: true, validationRule: "Every spec criterion is covered by at least one task." },
@@ -56,15 +56,9 @@ export const REVIEW = {
56
56
  ],
57
57
  requiredGates: [
58
58
  { id: "review_layer1_spec_compliance", description: "Spec compliance check completed with per-criterion verdict." },
59
- { id: "review_layer2_correctness", description: "Correctness review completed." },
60
59
  { id: "review_layer2_security", description: "Security review completed." },
61
- { id: "review_layer2_performance", description: "Performance review completed." },
62
- { id: "review_layer2_architecture", description: "Architecture fit review completed." },
63
- { id: "review_severity_classified", description: "All findings are severity-tagged." },
64
60
  { id: "review_criticals_resolved", description: "No unresolved critical blockers remain." },
65
- { id: "review_army_json_valid", description: "07-review-army.json passes schema validation (validateReviewArmy)." },
66
- { id: "review_completeness_scored", description: "Completeness score is computed and recorded (AC coverage, task coverage, slice coverage, adversarial pass)." },
67
- { id: "review_security_audit_swept", description: "The security-audit utility skill was run against the diff scope and the modules it touches. Finding count (0 if clean) recorded in the review army with file:line evidence for every Critical." }
61
+ { id: "review_army_json_valid", description: "07-review-army.json passes schema validation (validateReviewArmy)." }
68
62
  ],
69
63
  requiredEvidence: [
70
64
  "Artifact written to `.cclaw/artifacts/07-review.md`.",
@@ -205,10 +199,10 @@ export const REVIEW = {
205
199
  },
206
200
  artifactValidation: [
207
201
  { section: "Layer 1 Verdict", required: true, validationRule: "Per-criterion pass/fail with references." },
208
- { section: "Layer 2 Findings", required: true, validationRule: "Each finding has severity, description, and resolution status." },
202
+ { section: "Layer 2 Findings", required: false, validationRule: "Each finding has severity, description, and resolution status." },
209
203
  { section: "Review Army Contract", required: true, validationRule: "Structured findings include id/severity/confidence/fingerprint/reportedBy/status with dedup reconciliation summary." },
210
- { section: "Review Readiness Dashboard", required: true, 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'." },
211
- { section: "Completeness Score", required: true, 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%'." },
204
+ { 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'." },
205
+ { 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%'." },
212
206
  { section: "Severity Summary", required: true, validationRule: "Per-severity count lines for critical, important, and suggestion buckets." },
213
207
  { section: "Final Verdict", required: true, validationRule: "Exactly one of: APPROVED, APPROVED_WITH_CONCERNS, BLOCKED." }
214
208
  ]
@@ -55,11 +55,8 @@ export const SCOPE = {
55
55
  "Produce scope summary plus completion dashboard (checklist findings, number of resolved decisions, unresolved items or `None`)."
56
56
  ],
57
57
  requiredGates: [
58
- { id: "scope_premise_challenged", description: "Problem framing and assumptions were challenged." },
59
- { id: "scope_alternatives_produced", description: "At least 2 implementation alternatives were evaluated with explicit effort/risk and reuse fields." },
60
58
  { id: "scope_mode_selected", description: "One scope mode was explicitly selected." },
61
59
  { id: "scope_contract_written", description: "In-scope/out-of-scope contract is documented." },
62
- { id: "scope_discretion_documented", description: "Discretion areas are documented (or explicitly marked as none)." },
63
60
  { id: "scope_user_approved", description: "User approved the final scope direction." }
64
61
  ],
65
62
  requiredEvidence: [
@@ -172,17 +169,17 @@ export const SCOPE = {
172
169
  traceabilityRule: "Every scope boundary must be traceable to a brainstorm decision. Every downstream design choice must stay within the scope contract."
173
170
  },
174
171
  artifactValidation: [
175
- { section: "Prime Directives", required: true, validationRule: "For each scoped capability: named failure modes, explicit error surface, four data-flow paths, interaction edge cases, observability expectations, and deferred-item handling." },
176
- { section: "Premise Challenge", required: true, validationRule: "Must contain explicit answers to: right problem? direct path? what if nothing?" },
177
- { section: "Requirements", required: true, validationRule: "Table of stable requirement IDs (R1, R2, R3…) one per row with observable outcome, priority, and source. IDs are assigned once and never renumbered across scope/design/spec/plan/review; dropped requirements stay with Priority `DROPPED`." },
172
+ { section: "Prime Directives", required: false, validationRule: "For each scoped capability: named failure modes, explicit error surface, four data-flow paths, interaction edge cases, observability expectations, and deferred-item handling." },
173
+ { section: "Premise Challenge", required: false, validationRule: "Must contain explicit answers to: right problem? direct path? what if nothing?" },
174
+ { section: "Requirements", required: false, validationRule: "Table of stable requirement IDs (R1, R2, R3…) one per row with observable outcome, priority, and source. IDs are assigned once and never renumbered across scope/design/spec/plan/review; dropped requirements stay with Priority `DROPPED`." },
178
175
  { section: "Locked Decisions (D-XX)", required: false, validationRule: "List of stable locked decisions with IDs D-01, D-02... Each ID appears once, includes rationale, and is intended for downstream cross-stage traceability." },
179
- { section: "Implementation Alternatives", required: true, validationRule: "2-3 options with Name, Summary, Effort, Risk, Pros, Cons, and Reuses. Must include minimal viable and ideal architecture options." },
176
+ { section: "Implementation Alternatives", required: false, validationRule: "2-3 options with Name, Summary, Effort, Risk, Pros, Cons, and Reuses. Must include minimal viable and ideal architecture options." },
180
177
  { section: "Scope Mode", required: true, validationRule: "Must state selected mode and rationale with default heuristic justification." },
181
- { section: "Mode-Specific Analysis", required: true, validationRule: "Must document the analysis matching the selected scope mode: EXPAND (10x and delight opportunities), SELECTIVE (hold-scope baseline then cherry-picked expansions), HOLD (minimum-change-set hardening), REDUCE (ruthless cuts and follow-up split)." },
178
+ { section: "Mode-Specific Analysis", required: false, validationRule: "Must document the analysis matching the selected scope mode: EXPAND (10x and delight opportunities), SELECTIVE (hold-scope baseline then cherry-picked expansions), HOLD (minimum-change-set hardening), REDUCE (ruthless cuts and follow-up split)." },
182
179
  { section: "In Scope / Out of Scope", required: true, validationRule: "Two separate explicit lists. Out-of-scope must not be empty." },
183
- { section: "Discretion Areas", required: true, validationRule: "Explicit list of implementer decision zones, or 'None' if scope is fully locked." },
184
- { section: "Deferred Items", required: true, validationRule: "Each item has one-line rationale. If empty, state 'None' explicitly." },
185
- { section: "Error & Rescue Registry", required: true, validationRule: "Each scoped capability has: failure mode, detection method, fallback decision." },
180
+ { section: "Discretion Areas", required: false, validationRule: "Explicit list of implementer decision zones, or 'None' if scope is fully locked." },
181
+ { section: "Deferred Items", required: false, validationRule: "Each item has one-line rationale. If empty, state 'None' explicitly." },
182
+ { section: "Error & Rescue Registry", required: false, validationRule: "Each scoped capability has: failure mode, detection method, fallback decision." },
186
183
  { section: "Completion Dashboard", required: true, validationRule: "Lists checklist findings, count of resolved decisions, and unresolved decisions (or 'None')." },
187
184
  { section: "Scope Summary", required: true, validationRule: "Clean summary: mode, strongest challenges, recommended path, accepted scope, deferred, excluded." },
188
185
  { section: "Dream State Mapping", required: false, validationRule: "If present (complex projects): CURRENT STATE, THIS PLAN, 12-MONTH IDEAL, and alignment verdict." },
@@ -50,11 +50,8 @@ export const SHIP = {
50
50
  requiredGates: [
51
51
  { id: "ship_review_verdict_valid", description: "Review verdict is APPROVED or APPROVED_WITH_CONCERNS." },
52
52
  { id: "ship_preflight_passed", description: "Preflight checks passed or exceptions documented and approved." },
53
- { id: "ship_release_notes_written", description: "Release notes are complete and accurate." },
54
53
  { id: "ship_rollback_plan_ready", description: "Rollback trigger, steps, and verification are documented." },
55
- { id: "ship_finalization_mode_selected", description: "Exactly one finalization action is selected." },
56
- { id: "ship_finalization_executed", description: "Selected finalization action was executed and verified." },
57
- { id: "ship_post_merge_tests", description: "Full test suite re-run on the merged result (not just the branch). Post-merge failures caught before release." }
54
+ { id: "ship_finalization_executed", description: "Selected finalization action was executed and verified." }
58
55
  ],
59
56
  requiredEvidence: [
60
57
  "Artifact written to `.cclaw/artifacts/08-ship.md`.",
@@ -46,8 +46,6 @@ export const SPEC = {
46
46
  ],
47
47
  requiredGates: [
48
48
  { id: "spec_acceptance_measurable", description: "Acceptance criteria are measurable and observable." },
49
- { id: "spec_edge_cases_documented", description: "Boundary and error conditions are defined for each criterion." },
50
- { id: "spec_constraints_documented", description: "Constraints and assumptions are explicit." },
51
49
  { id: "spec_testability_confirmed", description: "Each criterion has a described test method." },
52
50
  { id: "spec_user_approved", description: "User approved the final written spec." }
53
51
  ],
@@ -125,7 +123,7 @@ export const SPEC = {
125
123
  artifactValidation: [
126
124
  { section: "Acceptance Criteria", required: true, validationRule: "Each criterion is observable, measurable, and falsifiable. Table must include a Requirement Ref column linking to R# IDs in 02-scope.md and a Design Decision Ref column tracing back to design artifact. AC IDs (AC-1, AC-2…) are stable across revisions — dropped ACs stay with Priority `DROPPED`." },
127
125
  { section: "Edge Cases", required: true, validationRule: "At least one boundary and one error condition per criterion." },
128
- { section: "Constraints and Assumptions", required: true, validationRule: "All implicit assumptions surfaced. Constraints have sources." },
126
+ { section: "Constraints and Assumptions", required: false, validationRule: "All implicit assumptions surfaced. Constraints have sources." },
129
127
  { section: "Testability Map", required: true, validationRule: "Each criterion maps to a concrete test description with verification approach (unit, integration, e2e, manual) and command or manual steps." },
130
128
  { section: "Vague to Fixed", required: false, validationRule: "If present: table with original vague wording and rewritten observable/testable version for each ambiguous requirement." },
131
129
  { section: "Non-Functional Requirements", required: false, validationRule: "If present: performance thresholds, security constraints, scalability limits, reliability targets with measurable values." },
@@ -57,12 +57,8 @@ export const TDD = {
57
57
  ],
58
58
  requiredGates: [
59
59
  { id: "tdd_red_test_written", description: "Failing tests exist before implementation changes." },
60
- { id: "tdd_red_failure_captured", description: "Failure output is captured as evidence." },
61
- { id: "tdd_trace_to_acceptance", description: "RED tests trace to explicit acceptance criteria." },
62
- { id: "tdd_red_failure_reason_verified", description: "Failure is for the expected reason, not an unrelated error." },
63
60
  { id: "tdd_green_full_suite", description: "Full relevant suite passes in GREEN state." },
64
61
  { id: "tdd_refactor_completed", description: "Refactor pass completed with behavior preservation verified." },
65
- { id: "tdd_refactor_notes_written", description: "Refactor decisions and outcomes are documented." },
66
62
  { id: "tdd_traceable_to_plan", description: "Change traceability to plan slice is explicit." }
67
63
  ],
68
64
  requiredEvidence: [
@@ -173,8 +169,8 @@ export const TDD = {
173
169
  },
174
170
  artifactValidation: [
175
171
  { section: "RED Evidence", required: true, validationRule: "Failing test output captured per slice." },
176
- { section: "Acceptance Mapping", required: true, validationRule: "Each RED test links to a plan task and spec criterion." },
177
- { section: "Failure Analysis", required: true, validationRule: "Failure reason matches expected missing behavior." },
172
+ { section: "Acceptance Mapping", required: false, validationRule: "Each RED test links to a plan task and spec criterion." },
173
+ { section: "Failure Analysis", required: false, validationRule: "Failure reason matches expected missing behavior." },
178
174
  { section: "GREEN Evidence", required: true, validationRule: "Full suite pass output captured." },
179
175
  { section: "REFACTOR Notes", required: true, validationRule: "What changed, why, behavior preservation confirmed." },
180
176
  { section: "Traceability", required: true, validationRule: "Plan task ID and spec criterion linked." },
@@ -19,7 +19,7 @@ export function startCommandContract() {
19
19
  **The unified entry point for the cclaw flow.**
20
20
 
21
21
  - \`/cc\` (no arguments) → behaves exactly like \`/cc-next\`: reads flow state and resumes the current stage, or starts brainstorm if the flow is fresh.
22
- - \`/cc <prompt>\` (with an idea/description) → saves the prompt as brainstorm context and begins the brainstorm stage, regardless of current flow state.
22
+ - \`/cc <prompt>\` (with an idea/description) → saves the prompt as idea context and starts the first stage of the resolved track.
23
23
 
24
24
  This is the **recommended way to start** working with cclaw. Use \`/cc-next\` for subsequent stage progression.
25
25
 
@@ -63,7 +63,7 @@ This is the **recommended way to start** working with cclaw. Use \`/cc-next\` fo
63
63
  Skip detection quietly if no markers are found — do NOT invent a stack.
64
64
 
65
65
  4. Read \`${flowPath}\`.
66
- 5. If flow already has completed stages beyond brainstorm, warn the user that starting a new brainstorm will reset progress. Ask for confirmation before proceeding.
66
+ 5. If flow already has completed stages, warn the user that starting a new tracked flow will reset progress. Ask for confirmation before proceeding.
67
67
  6. **Track heuristic** — classify the idea text and **recommend** a track (the user can override before any state mutation):
68
68
  - First, load \`${RUNTIME_ROOT}/config.yaml\`. If \`trackHeuristics\` is defined, apply those per-track vocabulary hints (\`fallback\`, \`tracks.<id>.{triggers,veto}\`) on top of the built-in defaults. Evaluation order is always \`standard -> medium -> quick\` (narrow-to-broad).
69
69
  - **quick** (\`spec → tdd → review → ship\`) — single-purpose work where the spec is essentially already known.
@@ -137,7 +137,7 @@ Do **not** silently discard an existing flow when the user provides a prompt. If
137
137
  3. **Stack detection (Phase 2).** Inspect \`package.json\` engines, \`pyproject.toml\`, \`go.mod\`, \`Cargo.toml\`, \`pom.xml\`, \`build.gradle*\`, \`Dockerfile\`, \`docker-compose*.yml\`, and CI configs. Record stack + versions on the \`Stack:\` line. Do not invent stack details.
138
138
  4. Read \`${flowPath}\`.
139
139
  5. If \`completedStages\` is non-empty:
140
- - Inform: "You have an active flow at stage **{currentStage}** with {N} completed stages. Starting a new brainstorm will reset progress."
140
+ - Inform: "You have an active flow at stage **{currentStage}** with {N} completed stages. Starting a new tracked flow will reset progress."
141
141
  - Ask: "Continue with reset? (A) Yes, start fresh (B) No, resume current flow"
142
142
  - If (B) → switch to Path B behavior.
143
143
  6. **Classify the idea** using the heuristic below and present a single track recommendation. Wait for explicit confirmation or override before mutating any state.
@@ -149,7 +149,7 @@ Do **not** silently discard an existing flow when the user provides a prompt. If
149
149
  |---|---|---|
150
150
  | \`quick\` | \`bug\`, \`bugfix\`, \`fix\`, \`hotfix\`, \`patch\`, \`typo\`, \`regression\`, \`rename\`, \`bump\`, \`upgrade dep\`, \`docs only\`, \`comment\`, \`lint\`, \`format\`, \`small\`, \`tiny\`, \`one-liner\`, \`revert\`, \`copy change\` | Single-purpose, spec is essentially known, low blast radius |
151
151
  | \`medium\` | \`add endpoint\`, \`add field\`, \`extend existing\`, \`wire integration\`, \`small migration\`, \`new screen following existing pattern\` | Additive work with existing architecture |
152
- | \`standard\` | \`new feature\`, \`build\`, \`design\`, \`refactor\`, \`migration\`, \`platform\`, \`architecture\`, \`schema\`, \`api\`, \`integrate\`, \`workflow\`, \`onboarding\` (or no confident quick/medium match) | New or uncertain multi-module work |
152
+ | \`standard\` | \`new feature\`, \`refactor\`, \`migration\`, \`platform\`, \`architecture\`, \`schema\`, \`integrate\`, \`workflow\`, \`onboarding\` (or no confident quick/medium match) | New or uncertain multi-module work |
153
153
 
154
154
  - On conflict, prefer \`standard\` over \`medium\`, and \`medium\` over \`quick\`.
155
155
  - Always state the recommendation as a one-line reason citing matched triggers.
@@ -180,6 +180,6 @@ Delegate entirely to \`/cc-next\` behavior:
180
180
  | Progressing after completing a stage | \`/cc-next\` |
181
181
  | Starting with a specific idea | \`/cc <idea>\` |
182
182
 
183
- Both commands read the same \`flow-state.json\`. The difference is that \`/cc <prompt>\` always targets brainstorm, while \`/cc\` and \`/cc-next\` follow the state.
183
+ Both commands read the same \`flow-state.json\`. The difference is that \`/cc <prompt>\` resolves class + track and starts that track's first stage, while \`/cc\` and \`/cc-next\` follow the current state.
184
184
  `;
185
185
  }
@@ -380,15 +380,24 @@ async function runAdvanceStage(projectRoot, args, io) {
380
380
  ...nextPassed.filter((gateId) => conditional.has(gateId)),
381
381
  ...nextBlocked.filter((gateId) => conditional.has(gateId))
382
382
  ]);
383
+ const missingGuardEvidence = nextPassed.filter((gateId) => {
384
+ const existing = flowState.guardEvidence[gateId];
385
+ if (typeof existing === "string" && existing.trim().length > 0) {
386
+ return false;
387
+ }
388
+ const provided = args.evidenceByGate[gateId];
389
+ return !(typeof provided === "string" && provided.trim().length > 0);
390
+ });
391
+ if (missingGuardEvidence.length > 0) {
392
+ io.stderr.write(`cclaw internal advance-stage: missing --evidence-json entries for passed gates: ${missingGuardEvidence.join(", ")}.\n`);
393
+ return 1;
394
+ }
383
395
  const nextGuardEvidence = { ...flowState.guardEvidence };
384
396
  for (const gateId of nextPassed) {
385
- const existing = nextGuardEvidence[gateId];
386
- if (typeof existing === "string" && existing.trim().length > 0)
387
- continue;
388
397
  const provided = args.evidenceByGate[gateId];
389
- nextGuardEvidence[gateId] = provided && provided.trim().length > 0
390
- ? provided.trim()
391
- : `stage-complete helper auto-evidence for ${gateId} @ ${new Date().toISOString()} (${schema.artifactFile})`;
398
+ if (typeof provided === "string" && provided.trim().length > 0) {
399
+ nextGuardEvidence[gateId] = provided.trim();
400
+ }
392
401
  }
393
402
  const nextStageCatalog = {
394
403
  required: [...catalog.required],
@@ -3,6 +3,7 @@ export type KnowledgeEntryType = "rule" | "pattern" | "lesson" | "compound";
3
3
  export type KnowledgeEntryConfidence = "high" | "medium" | "low";
4
4
  export type KnowledgeEntryUniversality = "project" | "personal" | "universal";
5
5
  export type KnowledgeEntryMaturity = "raw" | "lifted-to-rule" | "lifted-to-enforcement";
6
+ export type KnowledgeEntrySource = "stage" | "retro" | "compound" | "ideate" | "manual";
6
7
  export interface KnowledgeEntry {
7
8
  type: KnowledgeEntryType;
8
9
  trigger: string;
@@ -19,6 +20,7 @@ export interface KnowledgeEntry {
19
20
  first_seen_ts: string;
20
21
  last_seen_ts: string;
21
22
  project: string | null;
23
+ source?: KnowledgeEntrySource | null;
22
24
  }
23
25
  export interface KnowledgeSeedEntry {
24
26
  type: KnowledgeEntryType;
@@ -36,12 +38,14 @@ export interface KnowledgeSeedEntry {
36
38
  first_seen_ts?: string;
37
39
  last_seen_ts?: string;
38
40
  project?: string | null;
41
+ source?: KnowledgeEntrySource | null;
39
42
  }
40
43
  export interface AppendKnowledgeDefaults {
41
44
  stage?: FlowStage | null;
42
45
  originStage?: FlowStage | null;
43
46
  originFeature?: string | null;
44
47
  project?: string | null;
48
+ source?: KnowledgeEntrySource | null;
45
49
  nowIso?: string;
46
50
  }
47
51
  export interface AppendKnowledgeResult {
@@ -7,6 +7,13 @@ const KNOWLEDGE_TYPE_SET = new Set(["rule", "pattern", "lesson", "compound"]);
7
7
  const KNOWLEDGE_CONFIDENCE_SET = new Set(["high", "medium", "low"]);
8
8
  const KNOWLEDGE_UNIVERSALITY_SET = new Set(["project", "personal", "universal"]);
9
9
  const KNOWLEDGE_MATURITY_SET = new Set(["raw", "lifted-to-rule", "lifted-to-enforcement"]);
10
+ const KNOWLEDGE_SOURCE_SET = new Set([
11
+ "stage",
12
+ "retro",
13
+ "compound",
14
+ "ideate",
15
+ "manual"
16
+ ]);
10
17
  const FLOW_STAGE_SET = new Set(FLOW_STAGES);
11
18
  const KNOWLEDGE_REQUIRED_KEYS = [
12
19
  "type",
@@ -26,6 +33,7 @@ const KNOWLEDGE_REQUIRED_KEYS = [
26
33
  "project"
27
34
  ];
28
35
  const KNOWLEDGE_ALLOWED_KEYS = new Set(KNOWLEDGE_REQUIRED_KEYS);
36
+ KNOWLEDGE_ALLOWED_KEYS.add("source");
29
37
  function knowledgePath(projectRoot) {
30
38
  return path.join(projectRoot, RUNTIME_ROOT, "knowledge.jsonl");
31
39
  }
@@ -51,7 +59,8 @@ function dedupeKey(entry) {
51
59
  entry.origin_stage ?? "null",
52
60
  entry.origin_feature === null ? "null" : normalizeText(entry.origin_feature),
53
61
  entry.universality,
54
- entry.project === null ? "null" : normalizeText(entry.project)
62
+ entry.project === null ? "null" : normalizeText(entry.project),
63
+ entry.source === undefined || entry.source === null ? "null" : entry.source
55
64
  ].join("|");
56
65
  }
57
66
  function isIsoUtcTimestamp(value) {
@@ -123,13 +132,19 @@ export function validateKnowledgeEntry(entry) {
123
132
  if (!isNullableString(obj.project)) {
124
133
  errors.push("project must be string or null.");
125
134
  }
135
+ if (obj.source !== undefined &&
136
+ obj.source !== null &&
137
+ (typeof obj.source !== "string" || !KNOWLEDGE_SOURCE_SET.has(obj.source))) {
138
+ errors.push("source must be one of: stage, retro, compound, ideate, manual, or null.");
139
+ }
126
140
  return { ok: errors.length === 0, errors };
127
141
  }
128
142
  export function materializeKnowledgeEntry(seed, defaults = {}) {
129
143
  const now = normalizeUtcIso(defaults.nowIso ?? nowUtcIso());
130
144
  const stage = seed.stage ?? defaults.stage ?? null;
131
145
  const originStage = seed.origin_stage ?? defaults.originStage ?? stage ?? null;
132
- return {
146
+ const source = seed.source ?? defaults.source ?? null;
147
+ const entry = {
133
148
  type: seed.type,
134
149
  trigger: seed.trigger.trim(),
135
150
  action: seed.action.trim(),
@@ -146,6 +161,10 @@ export function materializeKnowledgeEntry(seed, defaults = {}) {
146
161
  last_seen_ts: normalizeUtcIso(seed.last_seen_ts ?? now),
147
162
  project: seed.project ?? defaults.project ?? null
148
163
  };
164
+ if (source !== null) {
165
+ entry.source = source;
166
+ }
167
+ return entry;
149
168
  }
150
169
  async function readExistingKnowledgeKeys(filePath) {
151
170
  const keys = new Set();
package/dist/runs.js CHANGED
@@ -398,7 +398,14 @@ async function evaluateRetroGate(projectRoot, state) {
398
398
  continue;
399
399
  try {
400
400
  const parsed = JSON.parse(trimmed);
401
- if (parsed.type === "compound") {
401
+ if (parsed.type !== "compound") {
402
+ continue;
403
+ }
404
+ const source = typeof parsed.source === "string"
405
+ ? parsed.source.trim().toLowerCase()
406
+ : null;
407
+ const legacyRetroStage = parsed.stage === "retro";
408
+ if (source === "retro" || legacyRetroStage) {
402
409
  compoundEntries += 1;
403
410
  }
404
411
  }
@@ -563,6 +570,7 @@ export async function archiveRun(projectRoot, featureName, options = {}) {
563
570
  const archiveArtifactsPath = path.join(archivePath, "artifacts");
564
571
  let sourceState = await readFlowState(projectRoot);
565
572
  const retroGate = await evaluateRetroGate(projectRoot, sourceState);
573
+ const shipCompleted = sourceState.completedStages.includes("ship");
566
574
  const skipRetro = options.skipRetro === true;
567
575
  const skipRetroReason = options.skipRetroReason?.trim();
568
576
  if (skipRetro && (!skipRetroReason || skipRetroReason.length === 0)) {
@@ -571,6 +579,12 @@ export async function archiveRun(projectRoot, featureName, options = {}) {
571
579
  const retroSkippedInCloseout = sourceState.closeout.retroSkipped === true &&
572
580
  typeof sourceState.closeout.retroSkipReason === "string" &&
573
581
  sourceState.closeout.retroSkipReason.trim().length > 0;
582
+ const readyForArchive = sourceState.closeout.shipSubstate === "ready_to_archive";
583
+ if (shipCompleted && !readyForArchive && !skipRetro && !retroSkippedInCloseout) {
584
+ throw new Error("Archive blocked: closeout is not ready_to_archive. " +
585
+ "Resume /cc-next until closeout reaches ready_to_archive, " +
586
+ "or run `cclaw archive --skip-retro --retro-reason=<text>` for CLI-only flows.");
587
+ }
574
588
  if (retroGate.required && !retroGate.completed && !skipRetro && !retroSkippedInCloseout) {
575
589
  throw new Error("Archive blocked: retro gate is required after ship completion. " +
576
590
  "Run /cc-next (auto-runs retro) or, for CLI-only flows, re-run `cclaw archive --skip-retro --retro-reason=<text>`.");
@@ -590,8 +604,12 @@ export async function archiveRun(projectRoot, featureName, options = {}) {
590
604
  const retroSummary = {
591
605
  required: retroGate.required,
592
606
  completed: retroGate.completed,
593
- skipped: skipRetro,
594
- skipReason: skipRetro ? skipRetroReason : undefined,
607
+ skipped: skipRetro || retroSkippedInCloseout,
608
+ skipReason: skipRetro
609
+ ? skipRetroReason
610
+ : retroSkippedInCloseout
611
+ ? sourceState.closeout.retroSkipReason
612
+ : undefined,
595
613
  compoundEntries: retroGate.compoundEntries
596
614
  };
597
615
  await ensureDir(archivePath);
package/dist/tdd-cycle.js CHANGED
@@ -48,6 +48,14 @@ export function validateTddCycleOrder(entries, options = {}) {
48
48
  let state = "need_red";
49
49
  for (const entry of sliceEntries) {
50
50
  if (entry.phase === "red") {
51
+ if (entry.exitCode === undefined) {
52
+ issues.push(`slice ${slice}: red entry must record a non-zero exitCode`);
53
+ continue;
54
+ }
55
+ if (entry.exitCode === 0) {
56
+ issues.push(`slice ${slice}: red entry exitCode must be non-zero`);
57
+ continue;
58
+ }
51
59
  if (state === "red_open") {
52
60
  issues.push(`slice ${slice}: duplicate red before green`);
53
61
  continue;
@@ -56,6 +64,14 @@ export function validateTddCycleOrder(entries, options = {}) {
56
64
  continue;
57
65
  }
58
66
  if (entry.phase === "green") {
67
+ if (entry.exitCode === undefined) {
68
+ issues.push(`slice ${slice}: green entry must record exitCode 0`);
69
+ continue;
70
+ }
71
+ if (entry.exitCode !== 0) {
72
+ issues.push(`slice ${slice}: green entry exitCode must be 0`);
73
+ continue;
74
+ }
59
75
  if (state !== "red_open") {
60
76
  issues.push(`slice ${slice}: green logged before red`);
61
77
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.45.0",
3
+ "version": "0.46.1",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {