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.
- package/README.md +3 -3
- package/dist/artifact-linter/brainstorm.js +45 -1
- package/dist/artifact-linter/design.js +32 -1
- package/dist/artifact-linter/plan.js +22 -1
- package/dist/artifact-linter/review.js +35 -1
- package/dist/artifact-linter/scope.js +19 -9
- package/dist/artifact-linter/shared.d.ts +11 -10
- package/dist/artifact-linter/shared.js +70 -41
- package/dist/artifact-linter/ship.js +36 -0
- package/dist/artifact-linter/spec.js +23 -1
- package/dist/artifact-linter/tdd.js +74 -0
- package/dist/artifact-linter.d.ts +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -0
- package/dist/content/closeout-guidance.d.ts +1 -1
- package/dist/content/closeout-guidance.js +10 -11
- package/dist/content/core-agents.d.ts +35 -36
- package/dist/content/core-agents.js +189 -99
- package/dist/content/diff-command.js +1 -1
- package/dist/content/examples.d.ts +0 -3
- package/dist/content/examples.js +197 -752
- package/dist/content/idea.d.ts +60 -0
- package/dist/content/idea.js +404 -0
- package/dist/content/learnings.d.ts +2 -4
- package/dist/content/learnings.js +10 -26
- package/dist/content/node-hooks.js +131 -97
- package/dist/content/opencode-plugin.js +12 -26
- package/dist/content/reference-patterns.js +2 -2
- package/dist/content/runtime-shared-snippets.d.ts +8 -0
- package/dist/content/runtime-shared-snippets.js +80 -0
- package/dist/content/session-hooks.js +1 -1
- package/dist/content/skills.d.ts +1 -0
- package/dist/content/skills.js +50 -0
- package/dist/content/stage-schema.js +107 -63
- package/dist/content/stages/review.js +8 -8
- package/dist/content/stages/schema-types.d.ts +2 -2
- package/dist/content/stages/scope.js +1 -1
- package/dist/content/stages/ship.js +1 -1
- package/dist/content/status-command.js +3 -3
- package/dist/content/subagent-context-skills.js +156 -1
- package/dist/content/subagents.d.ts +0 -5
- package/dist/content/subagents.js +12 -82
- package/dist/content/templates.js +87 -6
- package/dist/content/utility-skills.js +26 -97
- package/dist/flow-state.d.ts +5 -6
- package/dist/flow-state.js +4 -6
- package/dist/gate-evidence.d.ts +0 -31
- package/dist/gate-evidence.js +3 -181
- package/dist/harness-adapters.js +1 -1
- package/dist/install.js +38 -4
- package/dist/internal/advance-stage/advance.js +0 -1
- package/dist/internal/advance-stage/review-loop.js +1 -10
- package/dist/knowledge-store.d.ts +2 -20
- package/dist/knowledge-store.js +43 -57
- package/dist/policy.js +3 -3
- package/dist/retro-gate.js +8 -90
- package/dist/run-archive.js +1 -4
- package/dist/run-persistence.js +14 -109
- package/dist/runtime/run-hook.entry.d.ts +3 -0
- package/dist/runtime/run-hook.entry.js +5 -0
- package/dist/runtime/run-hook.mjs +9477 -0
- package/package.json +4 -2
- package/dist/content/hook-inline-snippets.d.ts +0 -96
- package/dist/content/hook-inline-snippets.js +0 -515
- package/dist/content/idea-command.d.ts +0 -8
- package/dist/content/idea-command.js +0 -322
- package/dist/content/idea-frames.d.ts +0 -31
- package/dist/content/idea-frames.js +0 -140
- package/dist/content/idea-ranking.d.ts +0 -25
- package/dist/content/idea-ranking.js +0 -65
- package/dist/trace-matrix.d.ts +0 -27
- package/dist/trace-matrix.js +0 -226
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
+--------+ +------+
|
|
17
17
|
|
|
|
18
18
|
v
|
|
19
|
-
|
|
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
|
|
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 `
|
|
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
|
|
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
|
|
19
|
-
entry.agent === "product-
|
|
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 =
|
|
23
|
-
const hasEvidence =
|
|
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-
|
|
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-
|
|
30
|
+
? `Scope mode ${selectedScopeMode} requires a completed product-discovery delegation row for active run ${delegationLedger.runId}.`
|
|
31
31
|
: hasEvidence
|
|
32
|
-
? `product-
|
|
33
|
-
: "product-
|
|
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
|
}
|