cclaw-cli 0.51.12 → 0.51.14

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.
@@ -1,4 +1,5 @@
1
1
  import fs from "node:fs/promises";
2
+ import { createHash } from "node:crypto";
2
3
  import path from "node:path";
3
4
  import { resolveArtifactPath as resolveStageArtifactPath } from "./artifact-paths.js";
4
5
  import { readConfig } from "./config.js";
@@ -94,6 +95,15 @@ function sectionBodyByAnyName(sections, sectionNames) {
94
95
  return null;
95
96
  return bodies.join("\n");
96
97
  }
98
+ function sectionBodyByHeadingPrefix(sections, prefix) {
99
+ const want = normalizeHeadingTitle(prefix).toLowerCase();
100
+ for (const [heading, body] of sections.entries()) {
101
+ if (heading.toLowerCase().startsWith(want)) {
102
+ return body;
103
+ }
104
+ }
105
+ return null;
106
+ }
97
107
  export function extractMarkdownSectionBody(markdown, section) {
98
108
  return sectionBodyByName(extractH2Sections(markdown), section);
99
109
  }
@@ -675,6 +685,52 @@ function validateRequirementsTaxonomy(sectionBody) {
675
685
  details: "Requirements table uses canonical Priority values."
676
686
  };
677
687
  }
688
+ function validateLockedDecisionAnchors(sectionBody) {
689
+ const rows = getMarkdownTableRows(sectionBody);
690
+ const lines = sectionBody
691
+ .split(/\r?\n/u)
692
+ .map((line) => line.trim())
693
+ .filter((line) => /^[-*]\s+\S/u.test(line));
694
+ const anchors = [];
695
+ const issues = [];
696
+ for (const [index, row] of rows.entries()) {
697
+ const anchor = (row[0] ?? "").trim().toLowerCase();
698
+ const decisionText = (row[1] ?? "").trim();
699
+ if (!/^ld#[0-9a-f]{8}$/u.test(anchor)) {
700
+ issues.push(`row ${index + 1} has invalid anchor "${row[0] ?? ""}"`);
701
+ continue;
702
+ }
703
+ anchors.push(anchor);
704
+ if (decisionText.length > 0) {
705
+ const expected = lockedDecisionHash(decisionText).toLowerCase();
706
+ if (anchor !== expected) {
707
+ issues.push(`row ${index + 1} anchor should be ${expected} for its Decision text`);
708
+ }
709
+ }
710
+ }
711
+ for (const [index, line] of lines.entries()) {
712
+ const anchor = /\bLD#[0-9a-f]{8}\b/iu.exec(line)?.[0]?.toLowerCase();
713
+ if (!anchor) {
714
+ issues.push(`bullet ${index + 1} is missing an LD#<sha8> anchor`);
715
+ continue;
716
+ }
717
+ anchors.push(anchor);
718
+ }
719
+ const duplicateAnchors = [...new Set(anchors.filter((anchor, index) => anchors.indexOf(anchor) !== index))];
720
+ if (duplicateAnchors.length > 0) {
721
+ issues.push(`duplicate anchors: ${duplicateAnchors.join(", ")}`);
722
+ }
723
+ if (anchors.length === 0 && (rows.length > 0 || lines.length > 0)) {
724
+ issues.push("no LD#<sha8> anchors found");
725
+ }
726
+ return {
727
+ ok: issues.length === 0,
728
+ anchors: [...new Set(anchors)],
729
+ details: issues.length === 0
730
+ ? `${anchors.length} LD#hash anchor(s) recorded with no duplicates.`
731
+ : issues.join("; ")
732
+ };
733
+ }
678
734
  const INTERACTION_EDGE_CASE_REQUIREMENTS = [
679
735
  { label: "double-click", pattern: /\bdouble[\s-]?click\b/iu },
680
736
  {
@@ -1286,6 +1342,18 @@ function extractDecisionIds(text) {
1286
1342
  const ids = text.match(/\bD-\d+\b/gu) ?? [];
1287
1343
  return [...new Set(ids)];
1288
1344
  }
1345
+ function extractRequirementIdsFromMarkdown(text) {
1346
+ const ids = text.match(/\bR\d+\b/gu) ?? [];
1347
+ return [...new Set(ids)];
1348
+ }
1349
+ function extractLockedDecisionAnchors(text) {
1350
+ const ids = text.match(/\bLD#[0-9a-f]{8}\b/giu) ?? [];
1351
+ return [...new Set(ids.map((id) => id.replace(/^LD#/iu, "LD#").toLowerCase()))];
1352
+ }
1353
+ function lockedDecisionHash(value) {
1354
+ const normalized = value.replace(/\s+/gu, " ").trim().toLowerCase();
1355
+ return `LD#${createHash("sha256").update(normalized).digest("hex").slice(0, 8)}`;
1356
+ }
1289
1357
  function collectPatternHits(text, patterns) {
1290
1358
  const hits = [];
1291
1359
  for (const pattern of patterns) {
@@ -1401,7 +1469,7 @@ function validateSectionBody(sectionBody, rule, sectionName) {
1401
1469
  if (sectionNameNormalized === "premise challenge") {
1402
1470
  return validatePremiseChallenge(sectionBody);
1403
1471
  }
1404
- if (sectionNameNormalized === "requirements") {
1472
+ if (sectionNameNormalized.startsWith("requirements")) {
1405
1473
  return validateRequirementsTaxonomy(sectionBody);
1406
1474
  }
1407
1475
  if (sectionNameNormalized === "data flow") {
@@ -1823,9 +1891,9 @@ export async function lintArtifact(projectRoot, stage, track = "standard") {
1823
1891
  });
1824
1892
  }
1825
1893
  if (stage === "scope") {
1826
- const lockedDecisionsBody = sectionBodyByName(sections, "Locked Decisions (D-XX)") ?? "";
1894
+ const lockedDecisionsBody = sectionBodyByHeadingPrefix(sections, "Locked Decisions") ?? "";
1827
1895
  const strictScopeGuards = parsedFrontmatter.hasFrontmatter ||
1828
- headingPresent(sections, "Locked Decisions (D-XX)");
1896
+ sectionBodyByHeadingPrefix(sections, "Locked Decisions") !== null;
1829
1897
  const scopeSections = [
1830
1898
  sectionBodyByAnyName(sections, ["In Scope / Out of Scope", "In Scope", "Out of Scope"]) ?? "",
1831
1899
  sectionBodyByName(sections, "Scope Summary") ?? "",
@@ -1841,12 +1909,18 @@ export async function lintArtifact(projectRoot, stage, track = "standard") {
1841
1909
  ? "No scope-reduction phrases detected in scope boundary sections."
1842
1910
  : `Detected scope-reduction phrase(s): ${reductionHits.join(", ")}.`
1843
1911
  });
1844
- // When the Locked Decisions section is present we must enforce the
1845
- // D-XX ID contract at runtime (previously this was prose-only in the
1846
- // artifactValidation rule). Empty body, missing IDs, and duplicate
1847
- // IDs all fail the lint; absence of the section remains advisory so
1848
- // scope stays optional for small/quick tracks.
1849
- if (headingPresent(sections, "Locked Decisions (D-XX)")) {
1912
+ if (sectionBodyByHeadingPrefix(sections, "Locked Decisions") !== null) {
1913
+ const anchorValidation = validateLockedDecisionAnchors(lockedDecisionsBody);
1914
+ findings.push({
1915
+ section: "Locked Decisions Hash Integrity",
1916
+ required: true,
1917
+ rule: "Locked Decisions section must list unique LD#<sha8> content-derived anchors.",
1918
+ found: anchorValidation.ok,
1919
+ details: anchorValidation.details
1920
+ });
1921
+ // Legacy D-XX rows remain advisory for older artifacts, but new templates
1922
+ // use LD#hash anchors. This check keeps D-XX duplicates visible without
1923
+ // making old artifacts the primary contract.
1850
1924
  const listDecisionLines = lockedDecisionsBody
1851
1925
  .split(/\r?\n/u)
1852
1926
  .map((line) => line.trim())
@@ -1881,7 +1955,7 @@ export async function lintArtifact(projectRoot, stage, track = "standard") {
1881
1955
  }
1882
1956
  findings.push({
1883
1957
  section: "Locked Decisions ID Integrity",
1884
- required: true,
1958
+ required: false,
1885
1959
  rule: "Locked Decisions section must list each decision with a unique stable D-XX ID.",
1886
1960
  found: issues.length === 0,
1887
1961
  details: issues.length === 0
@@ -1890,6 +1964,46 @@ export async function lintArtifact(projectRoot, stage, track = "standard") {
1890
1964
  });
1891
1965
  }
1892
1966
  }
1967
+ if (["design", "spec", "plan", "review"].includes(stage)) {
1968
+ const scopeArtifact = await resolveStageArtifactPath("scope", {
1969
+ projectRoot,
1970
+ track,
1971
+ intent: "read"
1972
+ });
1973
+ if (await exists(scopeArtifact.absPath)) {
1974
+ const scopeRaw = await fs.readFile(scopeArtifact.absPath, "utf8");
1975
+ const scopeSections = extractH2Sections(scopeRaw);
1976
+ const requirementsBody = sectionBodyByHeadingPrefix(scopeSections, "Requirements") ?? "";
1977
+ const lockedDecisionsBody = sectionBodyByHeadingPrefix(scopeSections, "Locked Decisions") ?? "";
1978
+ const requirementIds = extractRequirementIdsFromMarkdown(requirementsBody);
1979
+ const lockedDecisionAnchors = extractLockedDecisionAnchors(lockedDecisionsBody);
1980
+ const missingRequirementRefs = requirementIds.filter((id) => !raw.includes(id));
1981
+ const normalizedCurrentRaw = raw.toLowerCase();
1982
+ const missingDecisionRefs = lockedDecisionAnchors.filter((id) => !normalizedCurrentRaw.includes(id));
1983
+ findings.push({
1984
+ section: "Scope Requirement Reference Integrity",
1985
+ required: requirementIds.length > 0,
1986
+ rule: "Every R# requirement ID from scope must be referenced by downstream artifacts.",
1987
+ found: missingRequirementRefs.length === 0,
1988
+ details: requirementIds.length === 0
1989
+ ? "No R# requirement IDs found in scope artifact; reference check skipped."
1990
+ : missingRequirementRefs.length === 0
1991
+ ? `All ${requirementIds.length} scope requirement ID(s) are referenced.`
1992
+ : `Missing scope requirement reference(s): ${missingRequirementRefs.join(", ")}.`
1993
+ });
1994
+ findings.push({
1995
+ section: "Locked Decision Hash Reference Integrity",
1996
+ required: lockedDecisionAnchors.length > 0,
1997
+ rule: "Every LD#hash locked decision anchor from scope must be referenced by downstream artifacts.",
1998
+ found: missingDecisionRefs.length === 0,
1999
+ details: lockedDecisionAnchors.length === 0
2000
+ ? "No LD#hash anchors found in scope artifact; reference check skipped."
2001
+ : missingDecisionRefs.length === 0
2002
+ ? `All ${lockedDecisionAnchors.length} locked decision anchor(s) are referenced.`
2003
+ : `Missing locked decision reference(s): ${missingDecisionRefs.join(", ")}.`
2004
+ });
2005
+ }
2006
+ }
1893
2007
  const passed = findings.every((f) => !f.required || f.found);
1894
2008
  return { stage, file: relFile, passed, findings };
1895
2009
  }
@@ -10,7 +10,7 @@ export declare const FLOW_VERSION = "1.0.0";
10
10
  export declare const SHIP_FINALIZATION_MODES: readonly ["FINALIZE_MERGE_LOCAL", "FINALIZE_OPEN_PR", "FINALIZE_KEEP_BRANCH", "FINALIZE_DISCARD_BRANCH", "FINALIZE_NO_VCS"];
11
11
  export type ShipFinalizationMode = (typeof SHIP_FINALIZATION_MODES)[number];
12
12
  export declare const DEFAULT_HARNESSES: HarnessId[];
13
- export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/agents", ".cclaw/hooks"];
13
+ export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/templates", ".cclaw/templates/state-contracts", ".cclaw/artifacts", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/agents", ".cclaw/hooks", ".cclaw/skills/review-prompts"];
14
14
  export declare const REQUIRED_GITIGNORE_PATTERNS: readonly ["# cclaw generated artifacts", ".cclaw/", ".claude/commands/cc-*.md", ".claude/commands/cc.md", ".cursor/commands/cc-*.md", ".cursor/commands/cc.md", ".opencode/commands/cc-*.md", ".opencode/commands/cc.md", ".agents/skills/cc/SKILL.md", ".agents/skills/cc-*/SKILL.md", ".claude/hooks/hooks.json", ".cursor/hooks.json", ".codex/hooks.json", ".opencode/plugins/cclaw-plugin.mjs", ".cursor/rules/cclaw-workflow.mdc"];
15
15
  /**
16
16
  * Canonical stage -> skill folder mapping.
package/dist/constants.js CHANGED
@@ -57,12 +57,14 @@ export const REQUIRED_DIRS = [
57
57
  `${RUNTIME_ROOT}/commands`,
58
58
  `${RUNTIME_ROOT}/skills`,
59
59
  `${RUNTIME_ROOT}/templates`,
60
+ `${RUNTIME_ROOT}/templates/state-contracts`,
60
61
  `${RUNTIME_ROOT}/artifacts`,
61
62
  `${RUNTIME_ROOT}/state`,
62
63
  `${RUNTIME_ROOT}/runs`,
63
64
  `${RUNTIME_ROOT}/rules`,
64
65
  `${RUNTIME_ROOT}/agents`,
65
- `${RUNTIME_ROOT}/hooks`
66
+ `${RUNTIME_ROOT}/hooks`,
67
+ `${RUNTIME_ROOT}/skills/review-prompts`
66
68
  ];
67
69
  export const REQUIRED_GITIGNORE_PATTERNS = [
68
70
  "# cclaw generated artifacts",
@@ -0,0 +1 @@
1
+ export declare const REVIEW_PROMPTS: Record<string, string>;
@@ -0,0 +1,104 @@
1
+ export const REVIEW_PROMPTS = {
2
+ "brainstorm-self-review.md": `# Brainstorm Self-Review Prompt
3
+
4
+ Use this before asking the user to approve the brainstorm artifact.
5
+
6
+ ## Calibration
7
+
8
+ Only flag issues that would cause a real downstream scope/design mistake:
9
+ wrong problem, no real alternative, hidden scope growth, missing user reaction,
10
+ or a recommendation that does not trace to the user's answer.
11
+
12
+ Do not flag prose style, wording preferences, section length, or missing detail
13
+ that would not change the scope/design decision.
14
+
15
+ ## Checks
16
+
17
+ | Category | What to check |
18
+ |---|---|
19
+ | Premise | Is this the right problem and the direct path? |
20
+ | Alternatives | Are there 2-3 meaningfully different options, including exactly one challenger with high/higher upside? |
21
+ | User reaction | Does the selected direction trace to the user's reaction/concerns? |
22
+ | Scope protection | Does the Not Doing list prevent silent enlargement? |
23
+ | Handoff | Is the next-stage handoff explicit and track-aware? |
24
+
25
+ ## Output
26
+
27
+ Write the result into \`## Self-Review Notes\`:
28
+
29
+ \`\`\`markdown
30
+ - Status: Approved | Issues Found
31
+ - Patches applied:
32
+ - <specific patch or None>
33
+ - Remaining concerns:
34
+ - <concern or None>
35
+ \`\`\`
36
+ `,
37
+ "scope-ceo-review.md": `# Scope CEO Review Prompt
38
+
39
+ Use this after drafting scope boundaries and before user approval.
40
+
41
+ ## Calibration
42
+
43
+ Think like a founder reviewing whether this is the right product slice. Flag
44
+ only issues that would materially change scope, sequencing, leverage, or user
45
+ value. Do not nitpick wording.
46
+
47
+ ## Checks
48
+
49
+ | Category | What to check |
50
+ |---|---|
51
+ | Premise | Are we solving the right problem now? |
52
+ | Leverage | Are we using existing code, constraints, and platform strengths? |
53
+ | 10-star delta | Is there a better high-leverage scope move worth cherry-picking? |
54
+ | Boundary | Are accepted, deferred, and excluded items unambiguous? |
55
+ | Mode fit | Does the selected mode match the evidence: SCOPE EXPANSION, SELECTIVE EXPANSION, HOLD SCOPE, or SCOPE REDUCTION? |
56
+ | Downstream refs | Are R-IDs and LD#hash anchors ready for design/spec/plan? |
57
+
58
+ ## Output
59
+
60
+ Record in \`## Outside Voice Findings\` or \`## Spec Review Loop\`:
61
+
62
+ \`\`\`markdown
63
+ | ID | Dimension | Finding | Disposition | Rationale |
64
+ |---|---|---|---|---|
65
+ | CEO-1 | <dimension> | <issue> | accept/reject/defer | <why> |
66
+ \`\`\`
67
+ `,
68
+ "design-eng-review.md": `# Design Engineering Review Prompt
69
+
70
+ Use this after drafting design and before handing to spec.
71
+
72
+ ## Calibration
73
+
74
+ Think like a senior engineer reviewing whether implementation can proceed
75
+ without hidden architecture risk. Flag only issues that would cause wrong code,
76
+ rework, missing failure behavior, or unverifiable acceptance criteria.
77
+
78
+ ## Checks
79
+
80
+ | Category | What to check |
81
+ |---|---|
82
+ | Architecture | Are component boundaries concrete and aligned with scope? |
83
+ | Data flow | Are inputs, outputs, persistence, and async/sync edges explicit? |
84
+ | Failure modes | Does every meaningful failure have detection, rescue, and user-visible behavior? |
85
+ | Traceability | Do design decisions reference relevant R-IDs and LD#hash anchors? |
86
+ | Verification | Is each risky choice testable by spec/plan/TDD? |
87
+ | Overbuild | Is any architecture stronger than the locked scope actually needs? |
88
+
89
+ ## Output
90
+
91
+ Record findings in the design artifact's review section:
92
+
93
+ \`\`\`markdown
94
+ ## Engineering Review
95
+ **Status:** Approved | Issues Found
96
+
97
+ **Issues:**
98
+ - [R#/LD#hash]: <specific issue> — <why it matters>
99
+
100
+ **Recommendations:**
101
+ - <advisory item or None>
102
+ \`\`\`
103
+ `
104
+ };
@@ -30,16 +30,20 @@ Before execution:
30
30
  2. Load active artifacts from \`.cclaw/artifacts/\`.
31
31
  3. Load upstream artifacts required by this stage:
32
32
  ${readLines}
33
- 4. Extract upstream decisions, constraints, and open questions into the current
33
+ 4. Read the state contract for this stage from \`.cclaw/templates/state-contracts/<stage>.json\`.
34
+ Treat it as the machine-readable skeleton: required top-level fields,
35
+ closed taxonomies, and the derived markdown path. Do not validate natural-language
36
+ prose by regex; put semantic quality checks in the review prompts.
37
+ 5. Extract upstream decisions, constraints, and open questions into the current
34
38
  artifact's \`Upstream Handoff\` section when that section exists.
35
- 5. Before doing stage work, give a compact user-facing drift preamble: "Carrying forward: <1-3 bullets>. Drift since upstream: None / <specific drift>. Recommendation: continue / re-scope."
36
- 6. If you change an upstream decision, record an explicit drift reason in the
39
+ 6. Before doing stage work, give a compact user-facing drift preamble: "Carrying forward: <1-3 bullets>. Drift since upstream: None / <specific drift>. Recommendation: continue / re-scope."
40
+ 7. If you change an upstream decision, record an explicit drift reason in the
37
41
  current artifact before continuing.
38
- 7. Confirm stage inputs:
42
+ 8. Confirm stage inputs:
39
43
  ${inputs}
40
- 8. Confirm required context:
44
+ 9. Confirm required context:
41
45
  ${requiredContext}
42
- 9. Use the injected knowledge digest from session-start; only fall back to full
46
+ 10. Use the injected knowledge digest from session-start; only fall back to full
43
47
  \`.cclaw/knowledge.jsonl\` when the digest is insufficient.
44
48
  `;
45
49
  }
@@ -46,7 +46,7 @@ export const PLAN = {
46
46
  "Slice into vertical tasks — each task targets 2-5 minutes, produces one testable outcome, and touches one coherent area.",
47
47
  "Task Contract — every task has one coherent outcome, AC mapping, exact verification command/manual step, and expected evidence snippet or pass condition. Avoid vague `run tests` wording.",
48
48
  "Annotate slice-review metadata — if `.cclaw/config.yaml::sliceReview.enabled` is true, every task row additionally carries `touchCount` (rough number of files expected to change) and `touchPaths` (glob hints, e.g. `migrations/**`, `src/auth/**`). A task may set `highRisk: true` to force a review pass regardless of thresholds. These fields feed the TDD stage's Per-Slice Review point; when `sliceReview` is disabled they are optional.",
49
- "Map scope Locked Decisions — every D-XX from scope is referenced by at least one plan task (or explicitly marked deferred with reason).",
49
+ "Map scope Locked Decisions — every LD#hash anchor from scope is referenced by at least one plan task (or explicitly marked deferred with reason).",
50
50
  "Run anti-placeholder + anti-scope-reduction scans — block `TODO/TBD/...` and phrasing like `v1`, `for now`, `later` for locked boundaries.",
51
51
  "Define validation points — mark where progress must be checked before continuing, with concrete command and expected evidence.",
52
52
  "Define execution posture — record whether execution should be sequential, dependency-batched, parallel-safe, or blocked; include risk triggers and RED/GREEN/REFACTOR checkpoint/commit expectations when the repo workflow supports them.",
@@ -87,7 +87,7 @@ export const SCOPE = {
87
87
  "In-scope and out-of-scope lists are explicit.",
88
88
  "Discretion areas are explicit (or marked as `None`).",
89
89
  "Selected mode and rationale are documented.",
90
- "Locked Decisions section lists stable D-XX IDs for non-negotiable boundaries.",
90
+ "Locked Decisions section lists stable LD#hash anchors for non-negotiable boundaries.",
91
91
  "Premise challenge findings documented.",
92
92
  "Outside Voice findings and dispositions are recorded (accept/reject/defer with rationale) before final approval.",
93
93
  `Spec review loop summary includes a table with columns Iteration, Quality Score, Findings, plus Stop reason, Target score, and Max iterations. This is outside-voice evidence only; it does not satisfy user approval. ${reviewLoopPolicySummary("scope")}`,
@@ -143,7 +143,7 @@ export const SCOPE = {
143
143
  { section: "Landscape Check", required: false, validationRule: "When mode is EXPAND/SELECTIVE, include at least one external reference insight and its impact on scope." },
144
144
  { section: "Taste Calibration", required: false, validationRule: "Must reference 2-3 strong in-repo modules/files that define the quality bar or explicitly justify omission." },
145
145
  { 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`." },
146
- { 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." },
146
+ { section: "Locked Decisions (LD#hash)", required: false, validationRule: "List of stable locked decisions with unique `LD#<sha8>` anchors. Each anchor is derived from the normalized Decision cell and is referenced downstream for cross-stage traceability." },
147
147
  { 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." },
148
148
  { section: "Scope Mode", required: true, validationRule: "Must state selected mode and rationale with default heuristic justification." },
149
149
  { section: "Mode-Specific Analysis", required: false, validationRule: "Deep/complex scope only: document the analysis matching the selected mode. Default path may record a concise mode rationale instead." },
@@ -0,0 +1 @@
1
+ export declare const STATE_CONTRACTS: Record<string, string>;
@@ -0,0 +1,63 @@
1
+ import { CCLAW_VERSION, RUNTIME_ROOT, SHIP_FINALIZATION_MODES } from "../constants.js";
2
+ import { FLOW_STAGES } from "../types.js";
3
+ const REQUIRED_TOP_LEVEL_FIELDS = {
4
+ brainstorm: ["stage", "selectedDirection", "approachTier", "approaches", "approval", "nextStageHandoff"],
5
+ scope: ["stage", "scopeMode", "requirements", "lockedDecisions", "scopeSummary", "nextStageHandoff"],
6
+ design: ["stage", "architecture", "dataFlow", "failureModes", "requirementRefs", "decisionRefs"],
7
+ spec: ["stage", "acceptanceCriteria", "requirementRefs", "designDecisionRefs"],
8
+ plan: ["stage", "tasks", "acceptanceCriteriaRefs", "requirementRefs", "decisionRefs", "verificationCommands"],
9
+ tdd: ["stage", "redEvidence", "greenEvidence", "acceptanceCriteriaRefs", "verificationCommands"],
10
+ review: ["stage", "finalVerdict", "findings", "acceptanceCriteriaRefs", "requirementRefs", "verificationCommands"],
11
+ ship: ["stage", "finalizationMode", "verificationSummary", "releaseNotesDraft"]
12
+ };
13
+ const STAGE_TAXONOMIES = {
14
+ brainstorm: {
15
+ approachTier: ["Lightweight", "Standard", "Deep"],
16
+ approachRole: ["baseline", "challenger", "wild-card"],
17
+ approachUpside: ["low", "modest", "high", "higher"]
18
+ },
19
+ scope: {
20
+ scopeMode: ["SCOPE EXPANSION", "SELECTIVE EXPANSION", "HOLD SCOPE", "SCOPE REDUCTION"],
21
+ priority: ["P0", "P1", "P2", "P3", "DROPPED"]
22
+ },
23
+ design: {
24
+ diagramTier: ["lightweight", "standard", "deep"],
25
+ edgeKind: ["sync", "async", "failure", "degraded"]
26
+ },
27
+ spec: {
28
+ priority: ["P0", "P1", "P2", "P3", "DROPPED"]
29
+ },
30
+ plan: {
31
+ taskStatus: ["pending", "in_progress", "blocked", "done", "dropped"]
32
+ },
33
+ tdd: {
34
+ cycleState: ["RED", "GREEN", "REFACTOR", "BLOCKED"]
35
+ },
36
+ review: {
37
+ finalVerdict: ["APPROVED", "APPROVED_WITH_CONCERNS", "BLOCKED"],
38
+ findingSeverity: ["Critical", "High", "Medium", "Low", "Info"]
39
+ },
40
+ ship: {
41
+ finalizationMode: [...SHIP_FINALIZATION_MODES]
42
+ }
43
+ };
44
+ function stateContract(stage) {
45
+ const stageIndex = FLOW_STAGES.indexOf(stage) + 1;
46
+ const stageNumber = String(stageIndex).padStart(2, "0");
47
+ return {
48
+ schemaVersion: 1,
49
+ contractId: `cclaw-${stage}-state`,
50
+ stage,
51
+ derivedMarkdownPath: `${RUNTIME_ROOT}/artifacts/${stageNumber}-${stage}.md`,
52
+ requiredTopLevelFields: REQUIRED_TOP_LEVEL_FIELDS[stage],
53
+ taxonomies: STAGE_TAXONOMIES[stage]
54
+ };
55
+ }
56
+ export const STATE_CONTRACTS = Object.fromEntries(FLOW_STAGES.map((stage) => [
57
+ `${stage}.json`,
58
+ `${JSON.stringify({
59
+ ...stateContract(stage),
60
+ generatedBy: "cclaw",
61
+ cclawVersion: CCLAW_VERSION
62
+ }, null, 2)}\n`
63
+ ]));
@@ -183,10 +183,14 @@ ${SEED_SHELF_SECTION}
183
183
  > is later dropped, keep the row and mark Priority \`DROPPED\`; if a new one is
184
184
  > added mid-flow, append with the next free R-number — do NOT reuse numbers.
185
185
 
186
- ## Locked Decisions (D-XX)
187
- | Decision ID | Decision | Why locked now | Downstream impact |
186
+ ## Locked Decisions (LD#hash)
187
+ | Decision Anchor | Decision | Why locked now | Downstream impact |
188
188
  |---|---|---|---|
189
- | D-01 | | | |
189
+ | LD#<sha8> | | | |
190
+
191
+ > Decision Anchor is \`LD#\` + the first 8 lowercase hex chars of SHA-256 over
192
+ > the normalized \`Decision\` cell (trim, collapse whitespace, lowercase). Downstream
193
+ > design/spec/plan/review artifacts reference these anchors verbatim.
190
194
 
191
195
  ## In Scope / Out of Scope
192
196
 
@@ -308,9 +312,9 @@ ${SEED_SHELF_SECTION}
308
312
  | pitfalls-researcher | | | |
309
313
 
310
314
  ## Architecture Boundaries
311
- | Component | Responsibility | Owner |
312
- |---|---|---|
313
- | | | |
315
+ | Component | Responsibility | Requirement Refs (R#) | Decision Refs (LD#hash) | Owner |
316
+ |---|---|---|---|---|
317
+ | | | | | |
314
318
 
315
319
  ## Architecture Diagram
316
320
 
@@ -377,11 +381,11 @@ ${MARKDOWN_CODE_FENCE}
377
381
  ### Interaction Edge Case Matrix
378
382
  | Edge case | Handled? | Design response | Deferred item (if not handled) |
379
383
  |---|---|---|---|
380
- | double-click | yes/no | | None / D-XX |
381
- | nav-away-mid-request | yes/no | | None / D-XX |
382
- | 10K-result dataset | yes/no | | None / D-XX |
383
- | background-job abandonment | yes/no | | None / D-XX |
384
- | zombie connection | yes/no | | None / D-XX |
384
+ | double-click | yes/no | | None / LD#hash |
385
+ | nav-away-mid-request | yes/no | | None / LD#hash |
386
+ | 10K-result dataset | yes/no | | None / LD#hash |
387
+ | background-job abandonment | yes/no | | None / LD#hash |
388
+ | zombie connection | yes/no | | None / LD#hash |
385
389
 
386
390
  ## Security & Threat Model
387
391
  | Boundary | Threat | Mitigation | Owner |
@@ -480,7 +484,7 @@ ${SEED_SHELF_SECTION}
480
484
  - Drift from upstream (or \`None\`):
481
485
 
482
486
  ## Acceptance Criteria
483
- | ID | Requirement Ref (R#) | Criterion (observable/measurable/falsifiable) | Design Decision Ref |
487
+ | ID | Requirement Ref (R#) | Criterion (observable/measurable/falsifiable) | Design Decision Ref (LD#hash) |
484
488
  |---|---|---|---|
485
489
  | AC-1 | R1 | | |
486
490
 
@@ -585,9 +589,9 @@ Execution rule: complete and verify each batch before starting the next batch.
585
589
  - TDD checkpoint plan: RED commit/checkpoint -> GREEN commit/checkpoint -> REFACTOR commit/checkpoint (or deferred because: )
586
590
 
587
591
  ## Locked Decision Coverage
588
- | Decision ID | Source section | Plan tasks implementing decision | Status |
592
+ | Decision Ref (LD#hash) | Source section | Plan tasks implementing decision | Status |
589
593
  |---|---|---|---|
590
- | D-01 | 02-scope.md > Locked Decisions | T-1 | covered |
594
+ | LD#<sha8> | 02-scope.md > Locked Decisions | T-1 | covered |
591
595
 
592
596
  ## Risk Assessment
593
597
  | Task/Batch | Risk | Likelihood | Impact | Mitigation |
@@ -605,7 +609,7 @@ Execution rule: complete and verify each batch before starting the next batch.
605
609
 
606
610
  ## No Scope Reduction Language Scan
607
611
  - Scanned phrases: \`v1\`, \`for now\`, \`later\`, \`temporary\`, \`placeholder\`, \`mock for now\`, \`hardcoded for now\`, \`will improve later\`.
608
- - Hits: 0 (required when Locked Decisions section is non-empty).
612
+ - Hits: 0 (required when Locked Decisions section is non-empty; use LD#hash anchors).
609
613
 
610
614
  ## WAIT_FOR_CONFIRM
611
615
  - Status: pending
@@ -210,6 +210,8 @@ When in doubt, prefer **non-trivial** — the quick track is opt-in and only saf
210
210
 
211
211
  Knowledge capture and curation run automatically as part of stage completion
212
212
  protocols via the internal \`learnings\` skill — no user-facing command.
213
+ Reusable entries land in \`.cclaw/knowledge.jsonl\` as strict JSONL with
214
+ \`type\`, \`trigger\`, \`action\`, and \`origin_run\` metadata.
213
215
 
214
216
  **Stage order:** brainstorm > scope > design > spec > plan > tdd > review > ship, then closeout: retro > compound > archive.
215
217
  \`/cc-next\` loads the right stage skill automatically and also drives post-ship closeout. Gates must pass before handoff.
package/dist/install.js CHANGED
@@ -16,6 +16,8 @@ import { stageCompleteScript, startFlowScript, runHookCmdScript, opencodePluginJ
16
16
  import { nodeHookRuntimeScript } from "./content/node-hooks.js";
17
17
  import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
18
18
  import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
19
+ import { STATE_CONTRACTS } from "./content/state-contracts.js";
20
+ import { REVIEW_PROMPTS } from "./content/review-prompts.js";
19
21
  import { stageSkillFolder, stageSkillMarkdown } from "./content/skills.js";
20
22
  import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LANGUAGE_RULE_PACK_GENERATORS, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./content/utility-skills.js";
21
23
  import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
@@ -357,6 +359,9 @@ async function writeArtifactTemplates(projectRoot) {
357
359
  await Promise.all(Object.entries(ARTIFACT_TEMPLATES).map(async ([fileName, content]) => {
358
360
  await writeFileSafe(runtimePath(projectRoot, "templates", fileName), content);
359
361
  }));
362
+ await Promise.all(Object.entries(STATE_CONTRACTS).map(async ([fileName, content]) => {
363
+ await writeFileSafe(runtimePath(projectRoot, "templates", "state-contracts", fileName), content);
364
+ }));
360
365
  }
361
366
  async function writeSkills(projectRoot, config) {
362
367
  const skillTrack = config?.defaultTrack ?? "standard";
@@ -379,6 +384,9 @@ async function writeSkills(projectRoot, config) {
379
384
  for (const [fileName, markdown] of Object.entries(RESEARCH_PLAYBOOKS)) {
380
385
  await writeFileSafe(runtimePath(projectRoot, "skills", "research", fileName), markdown);
381
386
  }
387
+ for (const [fileName, markdown] of Object.entries(REVIEW_PROMPTS)) {
388
+ await writeFileSafe(runtimePath(projectRoot, "skills", "review-prompts", fileName), markdown);
389
+ }
382
390
  // Language rule packs live under .cclaw/rules/lang/<pack>.md. They are opt-in:
383
391
  // only the packs listed in config.languageRulePacks are materialised. Any
384
392
  // legacy per-language skill folders from v0.7.0 (.cclaw/skills/language-*)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.51.12",
3
+ "version": "0.51.14",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {