cclaw-cli 0.55.2 → 1.0.0

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.
Files changed (72) hide show
  1. package/README.md +3 -3
  2. package/dist/artifact-linter/brainstorm.js +45 -1
  3. package/dist/artifact-linter/design.js +32 -1
  4. package/dist/artifact-linter/plan.js +22 -1
  5. package/dist/artifact-linter/review.js +35 -1
  6. package/dist/artifact-linter/scope.js +19 -9
  7. package/dist/artifact-linter/shared.d.ts +11 -10
  8. package/dist/artifact-linter/shared.js +70 -41
  9. package/dist/artifact-linter/ship.js +36 -0
  10. package/dist/artifact-linter/spec.js +23 -1
  11. package/dist/artifact-linter/tdd.js +74 -0
  12. package/dist/artifact-linter.d.ts +1 -1
  13. package/dist/constants.d.ts +1 -1
  14. package/dist/constants.js +1 -0
  15. package/dist/content/closeout-guidance.d.ts +1 -1
  16. package/dist/content/closeout-guidance.js +10 -11
  17. package/dist/content/core-agents.d.ts +35 -36
  18. package/dist/content/core-agents.js +189 -99
  19. package/dist/content/diff-command.js +1 -1
  20. package/dist/content/examples.d.ts +0 -3
  21. package/dist/content/examples.js +197 -752
  22. package/dist/content/idea.d.ts +60 -0
  23. package/dist/content/idea.js +404 -0
  24. package/dist/content/learnings.d.ts +2 -4
  25. package/dist/content/learnings.js +10 -26
  26. package/dist/content/node-hooks.js +131 -97
  27. package/dist/content/opencode-plugin.js +12 -26
  28. package/dist/content/reference-patterns.js +2 -2
  29. package/dist/content/runtime-shared-snippets.d.ts +8 -0
  30. package/dist/content/runtime-shared-snippets.js +80 -0
  31. package/dist/content/session-hooks.js +1 -1
  32. package/dist/content/skills.d.ts +1 -0
  33. package/dist/content/skills.js +50 -0
  34. package/dist/content/stage-schema.js +107 -63
  35. package/dist/content/stages/review.js +8 -8
  36. package/dist/content/stages/schema-types.d.ts +2 -2
  37. package/dist/content/stages/scope.js +1 -1
  38. package/dist/content/stages/ship.js +1 -1
  39. package/dist/content/status-command.js +3 -3
  40. package/dist/content/subagent-context-skills.js +156 -1
  41. package/dist/content/subagents.d.ts +0 -5
  42. package/dist/content/subagents.js +12 -82
  43. package/dist/content/templates.js +87 -6
  44. package/dist/content/utility-skills.js +26 -97
  45. package/dist/flow-state.d.ts +5 -6
  46. package/dist/flow-state.js +4 -6
  47. package/dist/gate-evidence.d.ts +0 -31
  48. package/dist/gate-evidence.js +3 -181
  49. package/dist/harness-adapters.js +1 -1
  50. package/dist/install.js +38 -4
  51. package/dist/internal/advance-stage/advance.js +0 -1
  52. package/dist/internal/advance-stage/review-loop.js +1 -10
  53. package/dist/knowledge-store.d.ts +2 -20
  54. package/dist/knowledge-store.js +43 -57
  55. package/dist/policy.js +3 -3
  56. package/dist/retro-gate.js +8 -90
  57. package/dist/run-archive.js +1 -4
  58. package/dist/run-persistence.js +14 -109
  59. package/dist/runtime/run-hook.entry.d.ts +3 -0
  60. package/dist/runtime/run-hook.entry.js +5 -0
  61. package/dist/runtime/run-hook.mjs +9477 -0
  62. package/package.json +4 -2
  63. package/dist/content/hook-inline-snippets.d.ts +0 -96
  64. package/dist/content/hook-inline-snippets.js +0 -515
  65. package/dist/content/idea-command.d.ts +0 -8
  66. package/dist/content/idea-command.js +0 -322
  67. package/dist/content/idea-frames.d.ts +0 -31
  68. package/dist/content/idea-frames.js +0 -140
  69. package/dist/content/idea-ranking.d.ts +0 -25
  70. package/dist/content/idea-ranking.js +0 -65
  71. package/dist/trace-matrix.d.ts +0 -27
  72. package/dist/trace-matrix.js +0 -226
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
  +--------+ +------+
17
17
  |
18
18
  v
19
- retro -> compound -> archive
19
+ post_ship_review -> archive
20
20
  ```
21
21
 
22
22
  The promise is simple: at any point you can ask **where are we, what is blocked, what evidence exists, and what should run next?**
@@ -90,7 +90,7 @@ That gives you:
90
90
  npx cclaw-cli sync
91
91
 
92
92
  5. Close out after ship
93
- /cc continues retro -> compound -> archive
93
+ /cc continues post_ship_review -> archive
94
94
  ```
95
95
 
96
96
  Tracks keep the flow proportional:
@@ -164,7 +164,7 @@ Enforced by generated helpers and state checks:
164
164
  - Mandatory delegations need terminal evidence or explicit waiver.
165
165
  - Stale stages block until redone and acknowledged.
166
166
  - Review criticals route back to TDD.
167
- - Ship continues through `retro -> compound -> archive` with `closeout.shipSubstate`.
167
+ - Ship continues through `post_ship_review -> archive` with `closeout.shipSubstate`.
168
168
 
169
169
  Advisory/model-guided:
170
170
 
@@ -1,4 +1,6 @@
1
- import { sectionBodyByName, validateApproachesTaxonomy, headingLineIndex, meaningfulLineCount, parseShortCircuitStatus, validateCalibratedSelfReview, markdownFieldRegex } from "./shared.js";
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { checkCriticPredictionsContract, sectionBodyByName, validateApproachesTaxonomy, headingLineIndex, meaningfulLineCount, parseShortCircuitStatus, validateCalibratedSelfReview, markdownFieldRegex } from "./shared.js";
2
4
  export async function lintBrainstormStage(ctx) {
3
5
  const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
4
6
  // Brainstorm Iron Law: "NO ARTIFACT IS COMPLETE WITHOUT AN EXPLICITLY
@@ -165,6 +167,16 @@ export async function lintBrainstormStage(ctx) {
165
167
  details: selfReview.details
166
168
  });
167
169
  }
170
+ const criticPredictions = checkCriticPredictionsContract(sections);
171
+ if (criticPredictions !== null) {
172
+ findings.push({
173
+ section: "critic.predictions_missing",
174
+ required: true,
175
+ rule: "[P2] critic.predictions_missing — pre-commitment predictions block missing or empty",
176
+ found: criticPredictions.found,
177
+ details: criticPredictions.details
178
+ });
179
+ }
168
180
  // Universal structural checks (Layer 2.1). Each fires only when the
169
181
  // matching section is present so legacy fixtures keep their current
170
182
  // shape, while artifacts emitted from the v3 template have to satisfy
@@ -242,4 +254,36 @@ export async function lintBrainstormStage(ctx) {
242
254
  : `Outside Voice section is missing field(s): ${missing.join(", ")}.`
243
255
  });
244
256
  }
257
+ const wavePlansDir = path.join(projectRoot, ".cclaw", "wave-plans");
258
+ let wavePlanEntries = [];
259
+ try {
260
+ wavePlanEntries = (await fs.readdir(wavePlansDir))
261
+ .filter((entry) => entry !== ".gitkeep" && !entry.startsWith("."));
262
+ }
263
+ catch {
264
+ wavePlanEntries = [];
265
+ }
266
+ const multiWaveDetected = wavePlanEntries.length >= 2;
267
+ if (multiWaveDetected) {
268
+ const carryForwardBody = sectionBodyByName(sections, "Wave Carry-forward");
269
+ const hasCarryForwardSection = carryForwardBody !== null;
270
+ const hasCarryForwardContent = carryForwardBody !== null && meaningfulLineCount(carryForwardBody) > 0;
271
+ const hasDriftAuditMarkers = carryForwardBody !== null &&
272
+ /\bcarrying\s+forward\b/iu.test(carryForwardBody) &&
273
+ /\bdrift\s+detected\b/iu.test(carryForwardBody);
274
+ const waveDriftAddressed = hasCarryForwardSection && hasCarryForwardContent && hasDriftAuditMarkers;
275
+ findings.push({
276
+ section: "wave.drift_unaddressed",
277
+ required: true,
278
+ rule: "[P1] wave.drift_unaddressed — when `.cclaw/wave-plans/` has >=2 entries, brainstorm must include `## Wave Carry-forward` with carry-forward and drift audit markers.",
279
+ found: waveDriftAddressed,
280
+ details: waveDriftAddressed
281
+ ? `Multi-wave context detected (${wavePlanEntries.length} wave-plan entries); Wave Carry-forward audit is present.`
282
+ : !hasCarryForwardSection
283
+ ? "Multi-wave context detected but `## Wave Carry-forward` section is missing."
284
+ : !hasCarryForwardContent
285
+ ? "`## Wave Carry-forward` exists but has no meaningful content."
286
+ : "Wave Carry-forward section must include both `Carrying forward` and `Drift detected` markers."
287
+ });
288
+ }
245
289
  }
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { resolveArtifactPath as resolveStageArtifactPath } from "../artifact-paths.js";
4
4
  import { exists } from "../fs-utils.js";
5
5
  import { CONFIDENCE_FINDING_REGEX_SOURCE } from "../content/skills.js";
6
- import { extractMarkdownSectionBody, getMarkdownTableRows, meaningfulLineCount, sectionBodyByName, markdownFieldRegex } from "./shared.js";
6
+ import { checkCriticPredictionsContract, evaluateLayeredDocumentReviewStatus, extractMarkdownSectionBody, getMarkdownTableRows, meaningfulLineCount, sectionBodyByName, markdownFieldRegex } from "./shared.js";
7
7
  const DESIGN_DIAGRAM_REQUIREMENTS = {
8
8
  lightweight: [
9
9
  {
@@ -204,6 +204,16 @@ async function runStaleDiagramAudit(projectRoot, artifactPath, artifactRaw, code
204
204
  }
205
205
  export async function lintDesignStage(ctx) {
206
206
  const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
207
+ const criticPredictions = checkCriticPredictionsContract(sections);
208
+ if (criticPredictions !== null) {
209
+ findings.push({
210
+ section: "critic.predictions_missing",
211
+ required: true,
212
+ rule: "[P2] critic.predictions_missing — pre-commitment predictions block missing or empty",
213
+ found: criticPredictions.found,
214
+ details: criticPredictions.details
215
+ });
216
+ }
207
217
  const tierResolution = await resolveDesignDiagramTier(projectRoot, track, raw);
208
218
  const diagramTier = isTrivialOverride
209
219
  ? "lightweight"
@@ -320,4 +330,25 @@ export async function lintDesignStage(ctx) {
320
330
  : "No calibrated findings detected. Use `[P1|P2|P3] (confidence: <n>/10) <repo-path>[:<line>] — <description>`."
321
331
  });
322
332
  }
333
+ const layeredDocumentReview = evaluateLayeredDocumentReviewStatus(sections, CONFIDENCE_FINDING_REGEX_SOURCE);
334
+ if (layeredDocumentReview !== null) {
335
+ findings.push({
336
+ section: "Document Reviewer Structured Findings",
337
+ required: true,
338
+ rule: "When Layered review references coherence-reviewer/scope-guardian-reviewer/feasibility-reviewer, include explicit reviewer status plus calibrated finding lines.",
339
+ found: layeredDocumentReview.missingStructured.length === 0,
340
+ details: layeredDocumentReview.missingStructured.length === 0
341
+ ? `Structured findings present for reviewers: ${layeredDocumentReview.triggeredReviewers.join(", ")}.`
342
+ : `Missing status or calibrated findings for: ${layeredDocumentReview.missingStructured.join(", ")}.`
343
+ });
344
+ findings.push({
345
+ section: "document-review.fail_without_waiver",
346
+ required: true,
347
+ rule: "[P1] document-review.fail_without_waiver — reviewer FAIL/PARTIAL requires fix evidence or explicit waiver.",
348
+ found: layeredDocumentReview.failOrPartialWithoutWaiver.length === 0,
349
+ details: layeredDocumentReview.failOrPartialWithoutWaiver.length === 0
350
+ ? "No unwaived FAIL/PARTIAL reviewer statuses detected."
351
+ : `Unwaived FAIL/PARTIAL statuses: ${layeredDocumentReview.failOrPartialWithoutWaiver.join(", ")}.`
352
+ });
353
+ }
323
354
  }
@@ -1,4 +1,4 @@
1
- import { headingPresent, sectionBodyByName, collectPatternHits, PLACEHOLDER_PATTERNS, extractDecisionIds, SCOPE_REDUCTION_PATTERNS } from "./shared.js";
1
+ import { evaluateLayeredDocumentReviewStatus, headingPresent, sectionBodyByName, collectPatternHits, PLACEHOLDER_PATTERNS, extractDecisionIds, SCOPE_REDUCTION_PATTERNS } from "./shared.js";
2
2
  import { resolveArtifactPath as resolveStageArtifactPath } from "../artifact-paths.js";
3
3
  import { exists } from "../fs-utils.js";
4
4
  import { FORBIDDEN_PLACEHOLDER_TOKENS, CONFIDENCE_FINDING_REGEX_SOURCE } from "../content/skills.js";
@@ -159,4 +159,25 @@ export async function lintPlanStage(ctx) {
159
159
  : "Regression Iron Rule section is present but missing `Iron rule acknowledged: yes`."
160
160
  });
161
161
  }
162
+ const layeredDocumentReview = evaluateLayeredDocumentReviewStatus(sections, CONFIDENCE_FINDING_REGEX_SOURCE);
163
+ if (layeredDocumentReview !== null) {
164
+ findings.push({
165
+ section: "Document Reviewer Structured Findings",
166
+ required: true,
167
+ rule: "When Layered review references coherence-reviewer/scope-guardian-reviewer/feasibility-reviewer, include explicit reviewer status plus calibrated finding lines.",
168
+ found: layeredDocumentReview.missingStructured.length === 0,
169
+ details: layeredDocumentReview.missingStructured.length === 0
170
+ ? `Structured findings present for reviewers: ${layeredDocumentReview.triggeredReviewers.join(", ")}.`
171
+ : `Missing status or calibrated findings for: ${layeredDocumentReview.missingStructured.join(", ")}.`
172
+ });
173
+ findings.push({
174
+ section: "document-review.fail_without_waiver",
175
+ required: true,
176
+ rule: "[P1] document-review.fail_without_waiver — reviewer FAIL/PARTIAL requires fix evidence or explicit waiver.",
177
+ found: layeredDocumentReview.failOrPartialWithoutWaiver.length === 0,
178
+ details: layeredDocumentReview.failOrPartialWithoutWaiver.length === 0
179
+ ? "No unwaived FAIL/PARTIAL reviewer statuses detected."
180
+ : `Unwaived FAIL/PARTIAL statuses: ${layeredDocumentReview.failOrPartialWithoutWaiver.join(", ")}.`
181
+ });
182
+ }
162
183
  }
@@ -1,4 +1,4 @@
1
- import { sectionBodyByName } from "./shared.js";
1
+ import { markdownFieldRegex, sectionBodyByName } from "./shared.js";
2
2
  export async function lintReviewStage(ctx) {
3
3
  const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
4
4
  // Universal Layer 2.7 structural checks (superpowers requesting + receiving).
@@ -62,4 +62,38 @@ export async function lintReviewStage(ctx) {
62
62
  : "Receiving Posture is missing the anti-sycophancy acknowledgement line."
63
63
  });
64
64
  }
65
+ const lensCoverageBody = sectionBodyByName(sections, "Lens Coverage");
66
+ if (lensCoverageBody === null) {
67
+ findings.push({
68
+ section: "reviewer.lens_coverage_missing",
69
+ required: true,
70
+ rule: "[P1] reviewer.lens_coverage_missing — review artifact must include `## Lens Coverage` with Performance/Compatibility/Observability/Security lines.",
71
+ found: false,
72
+ details: "No ## heading matching required section \"Lens Coverage\"."
73
+ });
74
+ }
75
+ else {
76
+ const performance = markdownFieldRegex("Performance", "NO_IMPACT|FOUND_\\d+").test(lensCoverageBody);
77
+ const compatibility = markdownFieldRegex("Compatibility", "NO_IMPACT|FOUND_\\d+").test(lensCoverageBody);
78
+ const observability = markdownFieldRegex("Observability", "NO_IMPACT|FOUND_\\d+").test(lensCoverageBody);
79
+ const security = markdownFieldRegex("Security", "routed\\s+to\\s+security-reviewer").test(lensCoverageBody);
80
+ const missing = [];
81
+ if (!performance)
82
+ missing.push("Performance");
83
+ if (!compatibility)
84
+ missing.push("Compatibility");
85
+ if (!observability)
86
+ missing.push("Observability");
87
+ if (!security)
88
+ missing.push("Security");
89
+ findings.push({
90
+ section: "reviewer.lens_coverage_missing",
91
+ required: true,
92
+ rule: "[P1] reviewer.lens_coverage_missing — `Lens Coverage` must include Performance/Compatibility/Observability (`NO_IMPACT` or `FOUND_<n>`) and Security routing line.",
93
+ found: missing.length === 0,
94
+ details: missing.length === 0
95
+ ? "Lens Coverage includes all required reviewer lens lines."
96
+ : `Lens Coverage missing or malformed line(s): ${missing.join(", ")}.`
97
+ });
98
+ }
65
99
  }
@@ -1,4 +1,4 @@
1
- import { sectionBodyByHeadingPrefix, sectionBodyByName, extractCanonicalScopeMode, sectionBodyByAnyName, collectPatternHits, SCOPE_REDUCTION_PATTERNS, validateLockedDecisionAnchors, getMarkdownTableRows } from "./shared.js";
1
+ import { checkCriticPredictionsContract, sectionBodyByHeadingPrefix, sectionBodyByName, extractCanonicalScopeMode, sectionBodyByAnyName, collectPatternHits, SCOPE_REDUCTION_PATTERNS, validateLockedDecisionAnchors, getMarkdownTableRows } from "./shared.js";
2
2
  import { readDelegationLedger } from "../delegation.js";
3
3
  export async function lintScopeStage(ctx) {
4
4
  const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
@@ -15,22 +15,32 @@ export async function lintScopeStage(ctx) {
15
15
  const strategistRequired = selectedScopeMode === "SCOPE EXPANSION" || selectedScopeMode === "SELECTIVE EXPANSION";
16
16
  if (strategistRequired) {
17
17
  const delegationLedger = await readDelegationLedger(projectRoot);
18
- const strategistRows = delegationLedger.entries.filter((entry) => entry.stage === "scope" &&
19
- entry.agent === "product-strategist" &&
18
+ const discoveryRows = delegationLedger.entries.filter((entry) => entry.stage === "scope" &&
19
+ entry.agent === "product-discovery" &&
20
20
  entry.runId === delegationLedger.runId &&
21
21
  entry.status === "completed");
22
- const hasCompleted = strategistRows.length > 0;
23
- const hasEvidence = strategistRows.some((entry) => Array.isArray(entry.evidenceRefs) && entry.evidenceRefs.length > 0);
22
+ const hasCompleted = discoveryRows.length > 0;
23
+ const hasEvidence = discoveryRows.some((entry) => Array.isArray(entry.evidenceRefs) && entry.evidenceRefs.length > 0);
24
24
  findings.push({
25
25
  section: "Expansion Strategist Delegation",
26
26
  required: true,
27
- rule: "When Scope Summary selects SCOPE EXPANSION or SELECTIVE EXPANSION, a completed `product-strategist` delegation for the active run with non-empty evidenceRefs is required.",
27
+ rule: "When Scope Summary selects SCOPE EXPANSION or SELECTIVE EXPANSION, a completed `product-discovery` delegation for the active run with non-empty evidenceRefs is required.",
28
28
  found: hasCompleted && hasEvidence,
29
29
  details: !hasCompleted
30
- ? `Scope mode ${selectedScopeMode} requires a completed product-strategist delegation row for active run ${delegationLedger.runId}.`
30
+ ? `Scope mode ${selectedScopeMode} requires a completed product-discovery delegation row for active run ${delegationLedger.runId}.`
31
31
  : hasEvidence
32
- ? `product-strategist delegation satisfied for mode ${selectedScopeMode}.`
33
- : "product-strategist delegation exists but evidenceRefs is empty; add at least one artifact/code evidence reference."
32
+ ? `product-discovery delegation satisfied for mode ${selectedScopeMode}.`
33
+ : "product-discovery delegation exists but evidenceRefs is empty; add at least one artifact/code evidence reference."
34
+ });
35
+ }
36
+ const criticPredictions = checkCriticPredictionsContract(sections);
37
+ if (criticPredictions !== null) {
38
+ findings.push({
39
+ section: "critic.predictions_missing",
40
+ required: true,
41
+ rule: "[P2] critic.predictions_missing — pre-commitment predictions block missing or empty",
42
+ found: criticPredictions.found,
43
+ details: criticPredictions.details
34
44
  });
35
45
  }
36
46
  const reductionHits = collectPatternHits(scopeSections, SCOPE_REDUCTION_PATTERNS);
@@ -30,6 +30,17 @@ export declare function headingPresent(sections: H2SectionMap, section: string):
30
30
  export declare function sectionBodyByName(sections: H2SectionMap, section: string): string | null;
31
31
  export declare function sectionBodyByAnyName(sections: H2SectionMap, sectionNames: string[]): string | null;
32
32
  export declare function sectionBodyByHeadingPrefix(sections: H2SectionMap, prefix: string): string | null;
33
+ export interface CriticPredictionsContractCheck {
34
+ found: boolean;
35
+ details: string;
36
+ }
37
+ export declare function checkCriticPredictionsContract(sections: H2SectionMap): CriticPredictionsContractCheck | null;
38
+ export interface LayeredDocumentReviewStatus {
39
+ triggeredReviewers: string[];
40
+ missingStructured: string[];
41
+ failOrPartialWithoutWaiver: string[];
42
+ }
43
+ export declare function evaluateLayeredDocumentReviewStatus(sections: H2SectionMap, confidenceFindingRegexSource: string): LayeredDocumentReviewStatus | null;
33
44
  /**
34
45
  * Build a regex that matches `<field>: <value>` even when the field name
35
46
  * and/or value are wrapped in markdown emphasis (`*`, `**`, `_`, `__`).
@@ -153,8 +164,6 @@ export declare function hasVerificationLadderTableRow(sectionBody: string): bool
153
164
  export type LearningEntryType = "rule" | "pattern" | "lesson" | "compound";
154
165
  export type LearningConfidence = "high" | "medium" | "low";
155
166
  export type LearningSeverity = "critical" | "important" | "suggestion";
156
- export type LearningUniversality = "project" | "personal" | "universal";
157
- export type LearningMaturity = "raw" | "lifted-to-rule" | "lifted-to-enforcement";
158
167
  export type LearningSource = "stage" | "retro" | "compound" | "idea" | "manual";
159
168
  export interface LearningSeedEntry {
160
169
  type: LearningEntryType;
@@ -162,20 +171,14 @@ export interface LearningSeedEntry {
162
171
  action: string;
163
172
  confidence: LearningConfidence;
164
173
  severity?: LearningSeverity;
165
- domain?: string | null;
166
174
  stage?: FlowStage | null;
167
175
  origin_stage?: FlowStage | null;
168
- origin_run?: string | null;
169
176
  frequency?: number;
170
- universality?: LearningUniversality;
171
- maturity?: LearningMaturity;
172
177
  created?: string;
173
178
  first_seen_ts?: string;
174
179
  last_seen_ts?: string;
175
180
  project?: string | null;
176
181
  source?: LearningSource | null;
177
- supersedes?: string[];
178
- superseded_by?: string;
179
182
  }
180
183
  export interface LearningsParseResult {
181
184
  ok: boolean;
@@ -187,8 +190,6 @@ export interface LearningsParseResult {
187
190
  export declare const LEARNING_TYPE_SET: Set<LearningEntryType>;
188
191
  export declare const LEARNING_CONFIDENCE_SET: Set<LearningConfidence>;
189
192
  export declare const LEARNING_SEVERITY_SET: Set<LearningSeverity>;
190
- export declare const LEARNING_UNIVERSALITY_SET: Set<LearningUniversality>;
191
- export declare const LEARNING_MATURITY_SET: Set<LearningMaturity>;
192
193
  export declare const LEARNING_SOURCE_SET: Set<LearningSource>;
193
194
  export declare const FLOW_STAGE_SET: Set<"brainstorm" | "scope" | "design" | "spec" | "plan" | "tdd" | "review" | "ship">;
194
195
  export declare const LEARNING_ALLOWED_KEYS: Set<string>;
@@ -93,6 +93,75 @@ export function sectionBodyByHeadingPrefix(sections, prefix) {
93
93
  }
94
94
  return null;
95
95
  }
96
+ export function checkCriticPredictionsContract(sections) {
97
+ const criticFindingsBody = sectionBodyByName(sections, "Critic Findings");
98
+ const layeredReviewBody = sectionBodyByHeadingPrefix(sections, "Layered review");
99
+ const layeredReviewMentionsCritic = layeredReviewBody !== null && /\bcritic\b/iu.test(layeredReviewBody);
100
+ const sourceBody = criticFindingsBody ?? (layeredReviewMentionsCritic ? layeredReviewBody : null);
101
+ if (sourceBody === null)
102
+ return null;
103
+ const predictionsMatch = /(?:^|\n)#{3,4}\s*Pre-commitment predictions\b([\s\S]*?)(?=\n#{2,4}\s+|$)/iu.exec(sourceBody);
104
+ const predictionsCount = predictionsMatch ? countListItems(predictionsMatch[1] ?? "") : 0;
105
+ const hasPredictions = predictionsCount >= 1;
106
+ const hasValidated = /(?:^|\n)#{3,4}\s*Validated\s*\/\s*Disproven\b/iu.test(sourceBody);
107
+ const hasOpenQuestions = /(?:^|\n)#{3,4}\s*Open Questions\b/iu.test(sourceBody);
108
+ const missing = [];
109
+ if (!hasPredictions) {
110
+ missing.push("`Pre-commitment predictions` subsection is missing or has no list items");
111
+ }
112
+ if (!hasValidated) {
113
+ missing.push("`Validated / Disproven` subsection is missing");
114
+ }
115
+ if (!hasOpenQuestions) {
116
+ missing.push("`Open Questions` subsection is missing");
117
+ }
118
+ return {
119
+ found: missing.length === 0,
120
+ details: missing.length === 0
121
+ ? "Critic pre-commitment predictions contract is present (predictions, validated/disproven mapping, open questions)."
122
+ : missing.join("; ")
123
+ };
124
+ }
125
+ const DOCUMENT_REVIEWER_NAMES = [
126
+ "coherence-reviewer",
127
+ "scope-guardian-reviewer",
128
+ "feasibility-reviewer"
129
+ ];
130
+ export function evaluateLayeredDocumentReviewStatus(sections, confidenceFindingRegexSource) {
131
+ const layeredReviewBody = sectionBodyByHeadingPrefix(sections, "Layered review");
132
+ if (layeredReviewBody === null)
133
+ return null;
134
+ const triggeredReviewers = DOCUMENT_REVIEWER_NAMES.filter((reviewer) => new RegExp(`\\b${reviewer}\\b`, "iu").test(layeredReviewBody));
135
+ if (triggeredReviewers.length === 0)
136
+ return null;
137
+ const findingRegex = new RegExp(confidenceFindingRegexSource, "iu");
138
+ const hasCalibratedFinding = findingRegex.test(layeredReviewBody);
139
+ const missingStructured = [];
140
+ const failOrPartialWithoutWaiver = [];
141
+ const waiverRegex = /(?:explicit\s+waiver|waiver\s*:|waived\s*:|accepted[-\s]?risk)/iu;
142
+ for (const reviewer of triggeredReviewers) {
143
+ const escaped = reviewer.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
144
+ const subsectionMatch = new RegExp(`(?:^|\\n)#{3,4}\\s*${escaped}\\b([\\s\\S]*?)(?=\\n#{2,4}\\s+|$)`, "iu")
145
+ .exec(layeredReviewBody);
146
+ const reviewerBlock = subsectionMatch?.[1] ?? layeredReviewBody;
147
+ const statusMatch = /\b(?:Status|Result|Verdict)\s*:\s*(PASS|PASS_WITH_GAPS|FAIL|PARTIAL|BLOCKED)\b/iu
148
+ .exec(reviewerBlock);
149
+ const inlineStatusMatch = new RegExp(`${escaped}[\\s\\S]{0,120}\\b(PASS|PASS_WITH_GAPS|FAIL|PARTIAL|BLOCKED)\\b`, "iu")
150
+ .exec(layeredReviewBody);
151
+ const status = (statusMatch?.[1] ?? inlineStatusMatch?.[1] ?? "").toUpperCase();
152
+ if (!hasCalibratedFinding || status.length === 0) {
153
+ missingStructured.push(reviewer);
154
+ }
155
+ if ((status === "FAIL" || status === "PARTIAL") && !waiverRegex.test(reviewerBlock) && !waiverRegex.test(layeredReviewBody)) {
156
+ failOrPartialWithoutWaiver.push(`${reviewer}:${status}`);
157
+ }
158
+ }
159
+ return {
160
+ triggeredReviewers,
161
+ missingStructured,
162
+ failOrPartialWithoutWaiver
163
+ };
164
+ }
96
165
  /**
97
166
  * Build a regex that matches `<field>: <value>` even when the field name
98
167
  * and/or value are wrapped in markdown emphasis (`*`, `**`, `_`, `__`).
@@ -991,8 +1060,6 @@ export function hasVerificationLadderTableRow(sectionBody) {
991
1060
  export const LEARNING_TYPE_SET = new Set(["rule", "pattern", "lesson", "compound"]);
992
1061
  export const LEARNING_CONFIDENCE_SET = new Set(["high", "medium", "low"]);
993
1062
  export const LEARNING_SEVERITY_SET = new Set(["critical", "important", "suggestion"]);
994
- export const LEARNING_UNIVERSALITY_SET = new Set(["project", "personal", "universal"]);
995
- export const LEARNING_MATURITY_SET = new Set(["raw", "lifted-to-rule", "lifted-to-enforcement"]);
996
1063
  export const LEARNING_SOURCE_SET = new Set([
997
1064
  "stage",
998
1065
  "retro",
@@ -1007,20 +1074,14 @@ export const LEARNING_ALLOWED_KEYS = new Set([
1007
1074
  "action",
1008
1075
  "confidence",
1009
1076
  "severity",
1010
- "domain",
1011
1077
  "stage",
1012
1078
  "origin_stage",
1013
- "origin_run",
1014
1079
  "frequency",
1015
- "universality",
1016
- "maturity",
1017
1080
  "created",
1018
1081
  "first_seen_ts",
1019
1082
  "last_seen_ts",
1020
1083
  "project",
1021
- "source",
1022
- "supersedes",
1023
- "superseded_by"
1084
+ "source"
1024
1085
  ]);
1025
1086
  export function isIsoUtcTimestamp(value) {
1026
1087
  return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/u.test(value);
@@ -1079,9 +1140,6 @@ export function parseLearningSeedEntry(raw, index) {
1079
1140
  error: `Learnings bullet #${index} field "severity" must be critical|important|suggestion.`
1080
1141
  };
1081
1142
  }
1082
- if (obj.domain !== undefined && !isNullableString(obj.domain)) {
1083
- return { ok: false, error: `Learnings bullet #${index} field "domain" must be string or null.` };
1084
- }
1085
1143
  if (obj.stage !== undefined && !isNullableStage(obj.stage)) {
1086
1144
  return {
1087
1145
  ok: false,
@@ -1094,9 +1152,6 @@ export function parseLearningSeedEntry(raw, index) {
1094
1152
  error: `Learnings bullet #${index} field "origin_stage" must be one of ${FLOW_STAGES.join(", ")} or null.`
1095
1153
  };
1096
1154
  }
1097
- if (obj.origin_run !== undefined && !isNullableString(obj.origin_run)) {
1098
- return { ok: false, error: `Learnings bullet #${index} field "origin_run" must be string or null.` };
1099
- }
1100
1155
  if (obj.project !== undefined && !isNullableString(obj.project)) {
1101
1156
  return { ok: false, error: `Learnings bullet #${index} field "project" must be string or null.` };
1102
1157
  }
@@ -1112,21 +1167,6 @@ export function parseLearningSeedEntry(raw, index) {
1112
1167
  (typeof obj.frequency !== "number" || !Number.isInteger(obj.frequency) || obj.frequency < 1)) {
1113
1168
  return { ok: false, error: `Learnings bullet #${index} field "frequency" must be an integer >= 1.` };
1114
1169
  }
1115
- if (obj.universality !== undefined &&
1116
- (typeof obj.universality !== "string" ||
1117
- !LEARNING_UNIVERSALITY_SET.has(obj.universality))) {
1118
- return {
1119
- ok: false,
1120
- error: `Learnings bullet #${index} field "universality" must be project|personal|universal.`
1121
- };
1122
- }
1123
- if (obj.maturity !== undefined &&
1124
- (typeof obj.maturity !== "string" || !LEARNING_MATURITY_SET.has(obj.maturity))) {
1125
- return {
1126
- ok: false,
1127
- error: `Learnings bullet #${index} field "maturity" must be raw|lifted-to-rule|lifted-to-enforcement.`
1128
- };
1129
- }
1130
1170
  for (const timestampField of ["created", "first_seen_ts", "last_seen_ts"]) {
1131
1171
  const value = obj[timestampField];
1132
1172
  if (value === undefined)
@@ -1138,17 +1178,6 @@ export function parseLearningSeedEntry(raw, index) {
1138
1178
  };
1139
1179
  }
1140
1180
  }
1141
- if (obj.supersedes !== undefined) {
1142
- if (!Array.isArray(obj.supersedes) ||
1143
- obj.supersedes.length === 0 ||
1144
- obj.supersedes.some((value) => typeof value !== "string" || value.trim().length === 0)) {
1145
- return { ok: false, error: `Learnings bullet #${index} field "supersedes" must be a non-empty array of strings.` };
1146
- }
1147
- }
1148
- if (obj.superseded_by !== undefined &&
1149
- (typeof obj.superseded_by !== "string" || obj.superseded_by.trim().length === 0)) {
1150
- return { ok: false, error: `Learnings bullet #${index} field "superseded_by" must be a non-empty string.` };
1151
- }
1152
1181
  return {
1153
1182
  ok: true,
1154
1183
  entry: {
@@ -1,3 +1,4 @@
1
+ import { readDelegationLedger } from "../delegation.js";
1
2
  import { sectionBodyByName } from "./shared.js";
2
3
  export async function lintShipStage(ctx) {
3
4
  const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
@@ -43,4 +44,39 @@ export async function lintShipStage(ctx) {
43
44
  : "Verify Tests Gate is missing a `Result: PASS|FAIL` line."
44
45
  });
45
46
  }
47
+ const delegationLedger = await readDelegationLedger(projectRoot);
48
+ const activeRunRows = delegationLedger.entries.filter((entry) => entry.stage === "ship" &&
49
+ entry.runId === delegationLedger.runId &&
50
+ entry.agent === "architect" &&
51
+ entry.status === "completed");
52
+ const hasCrossStageReferenceInArtifact = /\barchitect-cross-stage-verification\b/iu.test(raw) ||
53
+ /\barchitect\b[\s\S]{0,180}\bcross[-\s]?stage\b/iu.test(raw) ||
54
+ /\bCROSS_STAGE_VERIFIED\b/u.test(raw) ||
55
+ /\bDRIFT_DETECTED\b/u.test(raw);
56
+ findings.push({
57
+ section: "ship.cross_stage_cohesion_missing",
58
+ required: true,
59
+ rule: "Ship artifact must include architect cross-stage verification reference (`architect-cross-stage-verification` / CROSS_STAGE_VERIFIED / DRIFT_DETECTED) before finalization.",
60
+ found: hasCrossStageReferenceInArtifact,
61
+ details: hasCrossStageReferenceInArtifact
62
+ ? "Architect cross-stage verification reference is present in ship artifact."
63
+ : activeRunRows.length > 0
64
+ ? "Completed architect delegation exists in ledger, but ship artifact is missing explicit cross-stage verification reference."
65
+ : "Ship artifact is missing architect cross-stage verification reference."
66
+ });
67
+ const driftDetectedInArtifact = /\bDRIFT_DETECTED\b/u.test(raw);
68
+ const driftDetectedInDelegation = activeRunRows.some((row) => {
69
+ const refs = Array.isArray(row.evidenceRefs) ? row.evidenceRefs.join(" ") : "";
70
+ return /\bDRIFT_DETECTED\b/u.test(refs);
71
+ });
72
+ const driftDetected = driftDetectedInArtifact || driftDetectedInDelegation;
73
+ findings.push({
74
+ section: "ship.cross_stage_drift_detected",
75
+ required: true,
76
+ rule: "If architect cross-stage verification reports DRIFT_DETECTED, ship must be blocked until drift is resolved or explicitly waived.",
77
+ found: !driftDetected,
78
+ details: driftDetected
79
+ ? "Architect cross-stage verification reported DRIFT_DETECTED; ship must not proceed."
80
+ : "No DRIFT_DETECTED signal found in ship artifact or architect delegation evidence."
81
+ });
46
82
  }
@@ -1,4 +1,5 @@
1
- import { sectionBodyByName, SPEC_MAX_MODULES } from "./shared.js";
1
+ import { evaluateLayeredDocumentReviewStatus, sectionBodyByName, SPEC_MAX_MODULES } from "./shared.js";
2
+ import { CONFIDENCE_FINDING_REGEX_SOURCE } from "../content/skills.js";
2
3
  export async function lintSpecStage(ctx) {
3
4
  const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
4
5
  // Universal Layer 2.4 structural checks (evanflow-prd + superpowers).
@@ -105,4 +106,25 @@ export async function lintSpecStage(ctx) {
105
106
  : `Spec Self-Review is missing check(s): ${missing.join(", ")}.`
106
107
  });
107
108
  }
109
+ const layeredDocumentReview = evaluateLayeredDocumentReviewStatus(sections, CONFIDENCE_FINDING_REGEX_SOURCE);
110
+ if (layeredDocumentReview !== null) {
111
+ findings.push({
112
+ section: "Document Reviewer Structured Findings",
113
+ required: true,
114
+ rule: "When Layered review references coherence-reviewer/scope-guardian-reviewer/feasibility-reviewer, include explicit reviewer status plus calibrated finding lines.",
115
+ found: layeredDocumentReview.missingStructured.length === 0,
116
+ details: layeredDocumentReview.missingStructured.length === 0
117
+ ? `Structured findings present for reviewers: ${layeredDocumentReview.triggeredReviewers.join(", ")}.`
118
+ : `Missing status or calibrated findings for: ${layeredDocumentReview.missingStructured.join(", ")}.`
119
+ });
120
+ findings.push({
121
+ section: "document-review.fail_without_waiver",
122
+ required: true,
123
+ rule: "[P1] document-review.fail_without_waiver — reviewer FAIL/PARTIAL requires fix evidence or explicit waiver.",
124
+ found: layeredDocumentReview.failOrPartialWithoutWaiver.length === 0,
125
+ details: layeredDocumentReview.failOrPartialWithoutWaiver.length === 0
126
+ ? "No unwaived FAIL/PARTIAL reviewer statuses detected."
127
+ : `Unwaived FAIL/PARTIAL statuses: ${layeredDocumentReview.failOrPartialWithoutWaiver.join(", ")}.`
128
+ });
129
+ }
108
130
  }
@@ -1,3 +1,6 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { readDelegationLedger } from "../delegation.js";
1
4
  import { sectionBodyByName } from "./shared.js";
2
5
  export async function lintTddStage(ctx) {
3
6
  const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
@@ -121,4 +124,75 @@ export async function lintTddStage(ctx) {
121
124
  : "Mocks/spies detected without boundary justification; add explicit trust-boundary rationale or replace with real/fake/stub coverage."
122
125
  });
123
126
  }
127
+ const delegationLedger = await readDelegationLedger(projectRoot);
128
+ const activeRunEntries = delegationLedger.entries.filter((entry) => entry.stage === "tdd" && entry.runId === delegationLedger.runId);
129
+ const completedSliceImplementers = activeRunEntries.filter((entry) => entry.agent === "slice-implementer" && entry.status === "completed");
130
+ const fanOutDetected = completedSliceImplementers.length > 1;
131
+ if (fanOutDetected) {
132
+ const artifactsDir = path.dirname(absFile);
133
+ const cohesionContractMarkdownPath = path.join(artifactsDir, "cohesion-contract.md");
134
+ const cohesionContractJsonPath = path.join(artifactsDir, "cohesion-contract.json");
135
+ let cohesionContractFound = true;
136
+ const cohesionErrors = [];
137
+ try {
138
+ const markdown = await fs.readFile(cohesionContractMarkdownPath, "utf8");
139
+ if (!/#\s*Cohesion Contract\b/u.test(markdown)) {
140
+ cohesionContractFound = false;
141
+ cohesionErrors.push("cohesion-contract.md exists but missing `# Cohesion Contract` heading.");
142
+ }
143
+ }
144
+ catch {
145
+ cohesionContractFound = false;
146
+ cohesionErrors.push("cohesion-contract.md is missing.");
147
+ }
148
+ try {
149
+ const jsonRaw = await fs.readFile(cohesionContractJsonPath, "utf8");
150
+ const parsed = JSON.parse(jsonRaw);
151
+ const objectLike = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed);
152
+ const parsedRecord = objectLike ? parsed : null;
153
+ const hasRequiredShape = parsedRecord !== null &&
154
+ Array.isArray(parsedRecord.sharedTypes) &&
155
+ Array.isArray(parsedRecord.touchpoints) &&
156
+ Array.isArray(parsedRecord.slices) &&
157
+ parsedRecord.status !== undefined &&
158
+ typeof parsedRecord.status === "object" &&
159
+ parsedRecord.status !== null;
160
+ if (!hasRequiredShape) {
161
+ cohesionContractFound = false;
162
+ cohesionErrors.push("cohesion-contract.json must parse and include `sharedTypes[]`, `touchpoints[]`, `slices[]`, and `status`.");
163
+ }
164
+ }
165
+ catch {
166
+ cohesionContractFound = false;
167
+ cohesionErrors.push("cohesion-contract.json is missing or invalid JSON.");
168
+ }
169
+ findings.push({
170
+ section: "tdd.cohesion_contract_missing",
171
+ required: true,
172
+ rule: "When delegation ledger has >1 completed slice-implementer rows for active TDD run, require `.cclaw/artifacts/cohesion-contract.md` and parseable `.cclaw/artifacts/cohesion-contract.json` sidecar.",
173
+ found: cohesionContractFound,
174
+ details: cohesionContractFound
175
+ ? `Fan-out detected (${completedSliceImplementers.length} completed slice-implementer rows); cohesion contract markdown+JSON sidecar are present and parseable.`
176
+ : cohesionErrors.join(" ")
177
+ });
178
+ const completedOverseerRows = activeRunEntries.filter((entry) => entry.agent === "integration-overseer" && entry.status === "completed");
179
+ const overseerStatusInEvidence = completedOverseerRows.some((entry) => {
180
+ const refs = Array.isArray(entry.evidenceRefs) ? entry.evidenceRefs.join(" ") : "";
181
+ return /\b(?:PASS_WITH_GAPS|PASS)\b/u.test(refs);
182
+ });
183
+ const overseerStatusInArtifact = /\bintegration-overseer\b[\s\S]{0,200}\b(?:PASS_WITH_GAPS|PASS)\b/iu.test(raw);
184
+ const integrationOverseerFound = completedOverseerRows.length > 0 &&
185
+ (overseerStatusInEvidence || overseerStatusInArtifact);
186
+ findings.push({
187
+ section: "tdd.integration_overseer_missing",
188
+ required: true,
189
+ rule: "When fan-out is detected, require completed `integration-overseer` evidence with PASS or PASS_WITH_GAPS.",
190
+ found: integrationOverseerFound,
191
+ details: integrationOverseerFound
192
+ ? "integration-overseer completion recorded with PASS/PASS_WITH_GAPS evidence."
193
+ : completedOverseerRows.length === 0
194
+ ? "Fan-out detected but no completed integration-overseer delegation row exists for active run."
195
+ : "integration-overseer completion exists, but PASS/PASS_WITH_GAPS evidence is missing in delegation evidenceRefs and artifact text."
196
+ });
197
+ }
124
198
  }