cclaw-cli 0.51.30 → 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 +24 -18
- package/dist/artifact-linter/brainstorm.d.ts +2 -0
- package/dist/artifact-linter/brainstorm.js +289 -0
- package/dist/artifact-linter/design.d.ts +2 -0
- package/dist/artifact-linter/design.js +354 -0
- package/dist/artifact-linter/plan.d.ts +2 -0
- package/dist/artifact-linter/plan.js +183 -0
- package/dist/artifact-linter/review-army.d.ts +24 -0
- package/dist/artifact-linter/review-army.js +365 -0
- package/dist/artifact-linter/review.d.ts +2 -0
- package/dist/artifact-linter/review.js +99 -0
- package/dist/artifact-linter/scope.d.ts +2 -0
- package/dist/artifact-linter/scope.js +125 -0
- package/dist/artifact-linter/shared.d.ts +247 -0
- package/dist/artifact-linter/shared.js +1517 -0
- package/dist/artifact-linter/ship.d.ts +2 -0
- package/dist/artifact-linter/ship.js +82 -0
- package/dist/artifact-linter/spec.d.ts +2 -0
- package/dist/artifact-linter/spec.js +130 -0
- package/dist/artifact-linter/tdd.d.ts +2 -0
- package/dist/artifact-linter/tdd.js +198 -0
- package/dist/artifact-linter.d.ts +4 -76
- package/dist/artifact-linter.js +56 -2949
- package/dist/cli.d.ts +1 -6
- package/dist/cli.js +4 -159
- package/dist/codex-feature-flag.d.ts +1 -1
- package/dist/codex-feature-flag.js +1 -1
- package/dist/config.d.ts +3 -2
- package/dist/config.js +67 -3
- package/dist/constants.d.ts +1 -7
- package/dist/constants.js +10 -15
- package/dist/content/cancel-command.js +2 -2
- package/dist/content/closeout-guidance.d.ts +1 -1
- package/dist/content/closeout-guidance.js +15 -13
- package/dist/content/core-agents.d.ts +46 -29
- package/dist/content/core-agents.js +216 -82
- package/dist/content/decision-protocol.d.ts +1 -1
- package/dist/content/decision-protocol.js +1 -1
- 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/harness-doc.js +20 -2
- package/dist/content/hook-manifest.d.ts +2 -2
- package/dist/content/hook-manifest.js +2 -2
- package/dist/content/hooks.d.ts +1 -0
- package/dist/content/hooks.js +32 -137
- package/dist/content/idea.d.ts +60 -0
- package/dist/content/idea.js +404 -0
- package/dist/content/iron-laws.d.ts +0 -1
- package/dist/content/iron-laws.js +31 -16
- package/dist/content/learnings.d.ts +2 -4
- package/dist/content/learnings.js +11 -27
- package/dist/content/meta-skill.js +7 -7
- package/dist/content/node-hooks.d.ts +10 -0
- package/dist/content/node-hooks.js +163 -95
- package/dist/content/opencode-plugin.js +15 -29
- 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 +69 -7
- package/dist/content/stage-schema.js +147 -61
- package/dist/content/stages/_lint-metadata/index.js +26 -2
- package/dist/content/stages/brainstorm.js +13 -7
- package/dist/content/stages/design.js +16 -11
- package/dist/content/stages/plan.js +7 -4
- package/dist/content/stages/review.js +12 -12
- package/dist/content/stages/schema-types.d.ts +2 -2
- package/dist/content/stages/scope.js +15 -12
- package/dist/content/stages/ship.js +3 -3
- package/dist/content/stages/spec.js +9 -3
- package/dist/content/stages/tdd.js +14 -4
- package/dist/content/start-command.js +11 -10
- package/dist/content/status-command.js +5 -5
- package/dist/content/subagent-context-skills.js +156 -1
- package/dist/content/subagents.d.ts +0 -5
- package/dist/content/subagents.js +65 -81
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +187 -154
- package/dist/content/tree-command.js +2 -2
- package/dist/content/utility-skills.d.ts +2 -2
- package/dist/content/utility-skills.js +28 -99
- package/dist/content/view-command.js +4 -2
- package/dist/delegation.d.ts +2 -0
- package/dist/delegation.js +2 -1
- package/dist/early-loop.d.ts +66 -0
- package/dist/early-loop.js +275 -0
- package/dist/flow-state.d.ts +5 -6
- package/dist/flow-state.js +4 -6
- package/dist/gate-evidence.d.ts +0 -23
- package/dist/gate-evidence.js +111 -153
- package/dist/harness-adapters.d.ts +2 -2
- package/dist/harness-adapters.js +48 -19
- package/dist/install.js +190 -32
- package/dist/internal/advance-stage/advance.d.ts +50 -0
- package/dist/internal/advance-stage/advance.js +479 -0
- package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
- package/dist/internal/advance-stage/cancel-run.js +19 -0
- package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
- package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
- package/dist/internal/advance-stage/helpers.d.ts +14 -0
- package/dist/internal/advance-stage/helpers.js +145 -0
- package/dist/internal/advance-stage/hook.d.ts +8 -0
- package/dist/internal/advance-stage/hook.js +40 -0
- package/dist/internal/advance-stage/parsers.d.ts +54 -0
- package/dist/internal/advance-stage/parsers.js +307 -0
- package/dist/internal/advance-stage/review-loop.d.ts +7 -0
- package/dist/internal/advance-stage/review-loop.js +161 -0
- package/dist/internal/advance-stage/rewind.d.ts +14 -0
- package/dist/internal/advance-stage/rewind.js +108 -0
- package/dist/internal/advance-stage/start-flow.d.ts +11 -0
- package/dist/internal/advance-stage/start-flow.js +136 -0
- package/dist/internal/advance-stage/verify.d.ts +29 -0
- package/dist/internal/advance-stage/verify.js +225 -0
- package/dist/internal/advance-stage.js +21 -1470
- package/dist/internal/compound-readiness.d.ts +1 -1
- package/dist/internal/compound-readiness.js +2 -2
- package/dist/internal/early-loop-status.d.ts +7 -0
- package/dist/internal/early-loop-status.js +90 -0
- package/dist/internal/runtime-integrity.d.ts +7 -0
- package/dist/internal/runtime-integrity.js +288 -0
- package/dist/internal/tdd-red-evidence.js +1 -1
- package/dist/knowledge-store.d.ts +5 -28
- package/dist/knowledge-store.js +57 -84
- package/dist/managed-resources.js +24 -2
- package/dist/policy.js +7 -9
- package/dist/retro-gate.js +8 -90
- package/dist/run-archive.d.ts +1 -1
- package/dist/run-archive.js +13 -16
- package/dist/run-persistence.js +20 -15
- 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/dist/tdd-cycle.d.ts +3 -3
- package/dist/tdd-cycle.js +1 -1
- package/dist/types.d.ts +18 -10
- package/package.json +4 -2
- package/dist/content/hook-inline-snippets.d.ts +0 -83
- package/dist/content/hook-inline-snippets.js +0 -302
- package/dist/content/ideate-command.d.ts +0 -8
- package/dist/content/ideate-command.js +0 -315
- package/dist/content/ideate-frames.d.ts +0 -31
- package/dist/content/ideate-frames.js +0 -140
- package/dist/content/ideate-ranking.d.ts +0 -25
- package/dist/content/ideate-ranking.js +0 -65
- package/dist/content/next-command.d.ts +0 -20
- package/dist/content/next-command.js +0 -298
- package/dist/content/seed-shelf.d.ts +0 -36
- package/dist/content/seed-shelf.js +0 -301
- package/dist/content/stage-common-guidance.d.ts +0 -1
- package/dist/content/stage-common-guidance.js +0 -106
- package/dist/doctor-registry.d.ts +0 -10
- package/dist/doctor-registry.js +0 -186
- package/dist/doctor.d.ts +0 -17
- package/dist/doctor.js +0 -2201
- package/dist/internal/hook-manifest.d.ts +0 -16
- package/dist/internal/hook-manifest.js +0 -77
- package/dist/trace-matrix.d.ts +0 -27
- package/dist/trace-matrix.js +0 -226
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { readDelegationLedger } from "../delegation.js";
|
|
2
|
+
import { sectionBodyByName } from "./shared.js";
|
|
3
|
+
export async function lintShipStage(ctx) {
|
|
4
|
+
const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
|
|
5
|
+
// Universal Layer 2.8 structural checks (superpowers finishing-a-development-branch).
|
|
6
|
+
const optionsBody = sectionBodyByName(sections, "Finalization Options");
|
|
7
|
+
if (optionsBody !== null) {
|
|
8
|
+
const required = ["MERGE_LOCAL", "OPEN_PR", "KEEP_BRANCH", "DISCARD"];
|
|
9
|
+
const missing = required.filter((token) => !optionsBody.includes(token));
|
|
10
|
+
findings.push({
|
|
11
|
+
section: "Finalization Options Coverage",
|
|
12
|
+
required: true,
|
|
13
|
+
rule: "Finalization Options must surface all four canonical options (MERGE_LOCAL, OPEN_PR, KEEP_BRANCH, DISCARD).",
|
|
14
|
+
found: missing.length === 0,
|
|
15
|
+
details: missing.length === 0
|
|
16
|
+
? "All four finalization options surfaced."
|
|
17
|
+
: `Finalization Options is missing token(s): ${missing.join(", ")}.`
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const prBody = sectionBodyByName(sections, "Structured PR Body");
|
|
21
|
+
if (prBody !== null) {
|
|
22
|
+
const required = ["## Summary", "## Test Plan", "## Commits Included"];
|
|
23
|
+
const missing = required.filter((token) => !prBody.includes(token));
|
|
24
|
+
findings.push({
|
|
25
|
+
section: "Structured PR Body Shape",
|
|
26
|
+
required: true,
|
|
27
|
+
rule: "Structured PR Body must include `## Summary`, `## Test Plan`, and `## Commits Included` subsections.",
|
|
28
|
+
found: missing.length === 0,
|
|
29
|
+
details: missing.length === 0
|
|
30
|
+
? "Structured PR Body covers all required subsections."
|
|
31
|
+
: `Structured PR Body is missing subsection(s): ${missing.join(", ")}.`
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const verifyBody = sectionBodyByName(sections, "Verify Tests Gate");
|
|
35
|
+
if (verifyBody !== null) {
|
|
36
|
+
const ok = /\bResult:\s*(PASS|FAIL)\b/iu.test(verifyBody);
|
|
37
|
+
findings.push({
|
|
38
|
+
section: "Verify Tests Gate Result",
|
|
39
|
+
required: true,
|
|
40
|
+
rule: "Verify Tests Gate must declare a Result of PASS or FAIL.",
|
|
41
|
+
found: ok,
|
|
42
|
+
details: ok
|
|
43
|
+
? "Verify Tests Gate result declared."
|
|
44
|
+
: "Verify Tests Gate is missing a `Result: PASS|FAIL` line."
|
|
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
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { evaluateLayeredDocumentReviewStatus, sectionBodyByName, SPEC_MAX_MODULES } from "./shared.js";
|
|
2
|
+
import { CONFIDENCE_FINDING_REGEX_SOURCE } from "../content/skills.js";
|
|
3
|
+
export async function lintSpecStage(ctx) {
|
|
4
|
+
const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
|
|
5
|
+
// Universal Layer 2.4 structural checks (evanflow-prd + superpowers).
|
|
6
|
+
// All checks fire only when the matching section is present so legacy
|
|
7
|
+
// fixtures keep working while v3-template artifacts are validated.
|
|
8
|
+
const synthesisBody = sectionBodyByName(sections, "Synthesis Sources");
|
|
9
|
+
if (synthesisBody !== null) {
|
|
10
|
+
const tableRows = synthesisBody
|
|
11
|
+
.split("\n")
|
|
12
|
+
.filter((line) => /^\|/u.test(line));
|
|
13
|
+
const dataRows = tableRows.length >= 3 ? tableRows.slice(2) : [];
|
|
14
|
+
const populatedRows = dataRows.filter((row) => row
|
|
15
|
+
.split("|")
|
|
16
|
+
.slice(1, -1)
|
|
17
|
+
.some((cell) => cell.trim().length > 0));
|
|
18
|
+
const hasRow = populatedRows.length >= 1;
|
|
19
|
+
findings.push({
|
|
20
|
+
section: "Synthesis Sources Coverage",
|
|
21
|
+
required: true,
|
|
22
|
+
rule: "Synthesis Sources must cite at least one source artifact (synthesize-not-interview).",
|
|
23
|
+
found: hasRow,
|
|
24
|
+
details: hasRow
|
|
25
|
+
? `Detected ${populatedRows.length} populated source row(s).`
|
|
26
|
+
: "Synthesis Sources is empty; spec must cite at least one upstream artifact or context file."
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const behaviorBody = sectionBodyByName(sections, "Behavior Contract");
|
|
30
|
+
if (behaviorBody !== null) {
|
|
31
|
+
const optedOut = /(^|\n)\s*-\s*None\b/iu.test(behaviorBody);
|
|
32
|
+
const userStoryRegex = /(^|\n)\s*-\s*as\s+a\b[\s\S]*?,\s*i\s+can\b[\s\S]*?,\s*so that\b/imu;
|
|
33
|
+
const givenWhenThenRegex = /(^|\n)\s*-\s*given\b[\s\S]*?,\s*when\b[\s\S]*?,\s*then\b/imu;
|
|
34
|
+
const matches = [
|
|
35
|
+
...behaviorBody.matchAll(/(^|\n)\s*-\s*as\s+a\b[\s\S]*?,\s*i\s+can\b[\s\S]*?,\s*so that\b/gimu),
|
|
36
|
+
...behaviorBody.matchAll(/(^|\n)\s*-\s*given\b[\s\S]*?,\s*when\b[\s\S]*?,\s*then\b/gimu)
|
|
37
|
+
];
|
|
38
|
+
const ok = optedOut || matches.length >= 3;
|
|
39
|
+
findings.push({
|
|
40
|
+
section: "Behavior Contract Shape",
|
|
41
|
+
required: true,
|
|
42
|
+
rule: "Behavior Contract must list ≥3 behaviors in user-story (As a/I can/so that) or Given/When/Then form, or declare `- None.` for single-step specs.",
|
|
43
|
+
found: ok,
|
|
44
|
+
details: optedOut
|
|
45
|
+
? "Single-step spec; behaviors opted out via `- None.`."
|
|
46
|
+
: ok
|
|
47
|
+
? `Detected ${matches.length} behavior(s) in canonical form.`
|
|
48
|
+
: `Detected ${matches.length} behavior(s) in canonical form; need ≥3 (or `
|
|
49
|
+
+ "`- None.`).",
|
|
50
|
+
});
|
|
51
|
+
// Bonus: detect if at least one user-story OR given/when/then form is present
|
|
52
|
+
// (mirrors existing helpers).
|
|
53
|
+
void userStoryRegex;
|
|
54
|
+
void givenWhenThenRegex;
|
|
55
|
+
}
|
|
56
|
+
const archModulesBody = sectionBodyByName(sections, "Architecture Modules");
|
|
57
|
+
if (archModulesBody !== null) {
|
|
58
|
+
const codeFenceCount = (archModulesBody.match(/```/gu) ?? []).length;
|
|
59
|
+
const fnSignatureRegex = /\b(function|class|def|fn|method)\b\s+[A-Za-z_]/u;
|
|
60
|
+
const noCode = codeFenceCount === 0 && !fnSignatureRegex.test(archModulesBody);
|
|
61
|
+
findings.push({
|
|
62
|
+
section: "Architecture Modules No-Code",
|
|
63
|
+
required: true,
|
|
64
|
+
rule: "Architecture Modules must not contain code blocks, function signatures, or class definitions — modules listed by responsibility only.",
|
|
65
|
+
found: noCode,
|
|
66
|
+
details: noCode
|
|
67
|
+
? "Architecture Modules is free of code blocks and function/class signatures."
|
|
68
|
+
: "Architecture Modules contains a code fence or function/class signature; remove code-level details."
|
|
69
|
+
});
|
|
70
|
+
const tableRows = archModulesBody.split("\n").filter((line) => /^\|/u.test(line));
|
|
71
|
+
const dataRows = tableRows.length >= 3 ? tableRows.slice(2) : [];
|
|
72
|
+
const moduleNames = dataRows
|
|
73
|
+
.map((row) => row.split("|").slice(1, -1)[0]?.trim() ?? "")
|
|
74
|
+
.filter((name) => name.length > 0 && name !== "-" && !/^module$/iu.test(name));
|
|
75
|
+
const uniqueModuleCount = new Set(moduleNames).size;
|
|
76
|
+
findings.push({
|
|
77
|
+
section: "Single-Subsystem Scope",
|
|
78
|
+
required: false,
|
|
79
|
+
rule: `Architecture Modules should stay within one coherent subsystem boundary (<= ${SPEC_MAX_MODULES} named modules).`,
|
|
80
|
+
found: uniqueModuleCount <= SPEC_MAX_MODULES,
|
|
81
|
+
details: uniqueModuleCount <= SPEC_MAX_MODULES
|
|
82
|
+
? `Module count (${uniqueModuleCount}) stays within single-subsystem guidance.`
|
|
83
|
+
: `Architecture Modules lists ${uniqueModuleCount} modules (> ${SPEC_MAX_MODULES}); split into sub-specs or narrow scope before plan handoff.`
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const selfReviewBody = sectionBodyByName(sections, "Spec Self-Review");
|
|
87
|
+
if (selfReviewBody === null) {
|
|
88
|
+
findings.push({
|
|
89
|
+
section: "Spec Self-Review Coverage",
|
|
90
|
+
required: true,
|
|
91
|
+
rule: "Spec Self-Review must cover placeholder/consistency/scope/ambiguity checks.",
|
|
92
|
+
found: false,
|
|
93
|
+
details: "No ## heading matching required section \"Spec Self-Review\"."
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const required = ["placeholder", "consistency", "scope", "ambiguity"];
|
|
98
|
+
const missing = required.filter((token) => !new RegExp(token, "iu").test(selfReviewBody));
|
|
99
|
+
findings.push({
|
|
100
|
+
section: "Spec Self-Review Coverage",
|
|
101
|
+
required: true,
|
|
102
|
+
rule: "Spec Self-Review must cover placeholder/consistency/scope/ambiguity checks.",
|
|
103
|
+
found: missing.length === 0,
|
|
104
|
+
details: missing.length === 0
|
|
105
|
+
? "Spec Self-Review covers all required checks."
|
|
106
|
+
: `Spec Self-Review is missing check(s): ${missing.join(", ")}.`
|
|
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
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readDelegationLedger } from "../delegation.js";
|
|
4
|
+
import { sectionBodyByName } from "./shared.js";
|
|
5
|
+
export async function lintTddStage(ctx) {
|
|
6
|
+
const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
|
|
7
|
+
// Universal Layer 2.6 structural checks (superpowers TDD + evanflow vertical slices).
|
|
8
|
+
const ironLawBody = sectionBodyByName(sections, "Iron Law Acknowledgement");
|
|
9
|
+
if (ironLawBody === null) {
|
|
10
|
+
findings.push({
|
|
11
|
+
section: "TDD Iron Law Acknowledgement",
|
|
12
|
+
required: true,
|
|
13
|
+
rule: "Iron Law Acknowledgement must affirm `Acknowledged: yes`.",
|
|
14
|
+
found: false,
|
|
15
|
+
details: "No ## heading matching required section \"Iron Law Acknowledgement\"."
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const ack = /acknowledged:\s*(yes|true|y)\b/iu.test(ironLawBody);
|
|
20
|
+
findings.push({
|
|
21
|
+
section: "TDD Iron Law Acknowledgement",
|
|
22
|
+
required: true,
|
|
23
|
+
rule: "Iron Law Acknowledgement must affirm `Acknowledged: yes`.",
|
|
24
|
+
found: ack,
|
|
25
|
+
details: ack
|
|
26
|
+
? "TDD Iron Law acknowledged."
|
|
27
|
+
: "Iron Law Acknowledgement is missing explicit `Acknowledged: yes`."
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const watchedRedBody = sectionBodyByName(sections, "Watched-RED Proof");
|
|
31
|
+
if (watchedRedBody === null) {
|
|
32
|
+
findings.push({
|
|
33
|
+
section: "Watched-RED Proof Shape",
|
|
34
|
+
required: true,
|
|
35
|
+
rule: "Watched-RED Proof must include at least one populated row, and each row must include an ISO timestamp showing when the test was observed failing.",
|
|
36
|
+
found: false,
|
|
37
|
+
details: "No ## heading matching required section \"Watched-RED Proof\"."
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const rows = watchedRedBody.split("\n").filter((line) => /^\|/u.test(line));
|
|
42
|
+
const dataRows = rows.length >= 3 ? rows.slice(2) : [];
|
|
43
|
+
const populatedRows = dataRows.filter((row) => row
|
|
44
|
+
.split("|")
|
|
45
|
+
.slice(1, -1)
|
|
46
|
+
.filter((_, idx) => idx !== 0) // skip slice column
|
|
47
|
+
.some((cell) => cell.trim().length > 0));
|
|
48
|
+
// Each populated row must include an ISO timestamp in column 3.
|
|
49
|
+
const isoRegex = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/u;
|
|
50
|
+
const validProofRows = populatedRows.filter((row) => isoRegex.test(row));
|
|
51
|
+
const hasPopulatedRows = populatedRows.length > 0;
|
|
52
|
+
const allRowsHaveIso = validProofRows.length === populatedRows.length;
|
|
53
|
+
findings.push({
|
|
54
|
+
section: "Watched-RED Proof Shape",
|
|
55
|
+
required: true,
|
|
56
|
+
rule: "Watched-RED Proof must include at least one populated row, and each row must include an ISO timestamp showing when the test was observed failing.",
|
|
57
|
+
found: hasPopulatedRows && allRowsHaveIso,
|
|
58
|
+
details: !hasPopulatedRows
|
|
59
|
+
? "Watched-RED Proof has no populated rows; add at least one slice row with observed RED evidence."
|
|
60
|
+
: allRowsHaveIso
|
|
61
|
+
? `All ${populatedRows.length} watched-RED proof row(s) include an ISO timestamp.`
|
|
62
|
+
: `${populatedRows.length - validProofRows.length} watched-RED proof row(s) lack an ISO timestamp.`
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const sliceCycleBody = sectionBodyByName(sections, "Vertical Slice Cycle");
|
|
66
|
+
if (sliceCycleBody === null) {
|
|
67
|
+
findings.push({
|
|
68
|
+
section: "Vertical Slice Cycle Coverage",
|
|
69
|
+
required: true,
|
|
70
|
+
rule: "Vertical Slice Cycle must include RED, GREEN, and REFACTOR per slice (refactor may be deferred with rationale).",
|
|
71
|
+
found: false,
|
|
72
|
+
details: "No ## heading matching required section \"Vertical Slice Cycle\"."
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const required = ["RED", "GREEN", "REFACTOR"];
|
|
77
|
+
const missing = required.filter((token) => !new RegExp(token, "u").test(sliceCycleBody));
|
|
78
|
+
findings.push({
|
|
79
|
+
section: "Vertical Slice Cycle Coverage",
|
|
80
|
+
required: true,
|
|
81
|
+
rule: "Vertical Slice Cycle must include RED, GREEN, and REFACTOR per slice (refactor may be deferred with rationale).",
|
|
82
|
+
found: missing.length === 0,
|
|
83
|
+
details: missing.length === 0
|
|
84
|
+
? "Vertical Slice Cycle references RED/GREEN/REFACTOR."
|
|
85
|
+
: `Vertical Slice Cycle is missing phase token(s): ${missing.join(", ")}.`
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const assertionBody = sectionBodyByName(sections, "Assertion Correctness Notes");
|
|
89
|
+
if (assertionBody !== null) {
|
|
90
|
+
const tableRows = assertionBody.split("\n").filter((line) => /^\|/u.test(line));
|
|
91
|
+
const dataRows = tableRows.length >= 3 ? tableRows.slice(2) : [];
|
|
92
|
+
const ok = dataRows.length === 0 || dataRows.some((row) => row
|
|
93
|
+
.split("|")
|
|
94
|
+
.slice(1, -1)
|
|
95
|
+
.some((cell) => cell.trim().length > 0));
|
|
96
|
+
findings.push({
|
|
97
|
+
section: "Assertion Correctness Notes Shape",
|
|
98
|
+
required: true,
|
|
99
|
+
rule: "Assertion Correctness Notes must include at least one populated row when the slice has new assertions.",
|
|
100
|
+
found: ok,
|
|
101
|
+
details: ok
|
|
102
|
+
? "Assertion Correctness Notes is populated or absent (single-step slice)."
|
|
103
|
+
: "Assertion Correctness Notes table has no populated rows."
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const testDiscoveryBody = sectionBodyByName(sections, "Test Discovery") ?? "";
|
|
107
|
+
const redEvidenceBody = sectionBodyByName(sections, "RED Evidence") ?? "";
|
|
108
|
+
const mockPreferenceScanBody = `${testDiscoveryBody}\n${redEvidenceBody}`;
|
|
109
|
+
const mockTokenRegex = /\b(jest\.mock|vi\.mock|sinon\.stub|mock\.patch|unittest\.mock|magicmock|spyon|tohavebeencalled)\b/iu;
|
|
110
|
+
if (mockTokenRegex.test(mockPreferenceScanBody)) {
|
|
111
|
+
const boundaryJustificationRegex = /\b(justified\s+by\s+boundary|boundary:\s*[A-Za-z0-9/_ -]*(network|fs|filesystem|time|clock|external)|network|filesystem|clock|external\s+service)\b/iu;
|
|
112
|
+
const hasBoundaryJustification = boundaryJustificationRegex.test(mockPreferenceScanBody);
|
|
113
|
+
const realPathRegex = /\b(?:src|lib|packages|apps)\/[A-Za-z0-9_./-]+\b/u;
|
|
114
|
+
const hasRealPathHint = realPathRegex.test(mockPreferenceScanBody);
|
|
115
|
+
findings.push({
|
|
116
|
+
section: "Mock Preference Heuristic",
|
|
117
|
+
required: false,
|
|
118
|
+
rule: "When mocks/spies appear in Test Discovery or RED Evidence, prefer Real > Fake > Stub > Mock. Mock-heavy slices need explicit boundary justification (network/fs/time/external).",
|
|
119
|
+
found: hasBoundaryJustification,
|
|
120
|
+
details: hasBoundaryJustification
|
|
121
|
+
? "Mock usage is explicitly justified by boundary constraints."
|
|
122
|
+
: hasRealPathHint
|
|
123
|
+
? "Mocks/spies detected while real implementation paths are listed; prefer Real > Fake > Stub > Mock unless a boundary justification is added."
|
|
124
|
+
: "Mocks/spies detected without boundary justification; add explicit trust-boundary rationale or replace with real/fake/stub coverage."
|
|
125
|
+
});
|
|
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
|
+
}
|
|
198
|
+
}
|
|
@@ -1,77 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
rule: string;
|
|
6
|
-
found: boolean;
|
|
7
|
-
details: string;
|
|
8
|
-
}
|
|
9
|
-
export interface LintResult {
|
|
10
|
-
stage: string;
|
|
11
|
-
file: string;
|
|
12
|
-
passed: boolean;
|
|
13
|
-
findings: LintFinding[];
|
|
14
|
-
}
|
|
15
|
-
export declare function extractMarkdownSectionBody(markdown: string, section: string): string | null;
|
|
16
|
-
export type LearningEntryType = "rule" | "pattern" | "lesson" | "compound";
|
|
17
|
-
export type LearningConfidence = "high" | "medium" | "low";
|
|
18
|
-
export type LearningSeverity = "critical" | "important" | "suggestion";
|
|
19
|
-
export type LearningUniversality = "project" | "personal" | "universal";
|
|
20
|
-
export type LearningMaturity = "raw" | "lifted-to-rule" | "lifted-to-enforcement";
|
|
21
|
-
export type LearningSource = "stage" | "retro" | "compound" | "ideate" | "manual";
|
|
22
|
-
export interface LearningSeedEntry {
|
|
23
|
-
type: LearningEntryType;
|
|
24
|
-
trigger: string;
|
|
25
|
-
action: string;
|
|
26
|
-
confidence: LearningConfidence;
|
|
27
|
-
severity?: LearningSeverity;
|
|
28
|
-
domain?: string | null;
|
|
29
|
-
stage?: FlowStage | null;
|
|
30
|
-
origin_stage?: FlowStage | null;
|
|
31
|
-
origin_run?: string | null;
|
|
32
|
-
/** @deprecated Use `origin_run`. Accepted only for legacy learning bullets. */
|
|
33
|
-
origin_feature?: string | null;
|
|
34
|
-
frequency?: number;
|
|
35
|
-
universality?: LearningUniversality;
|
|
36
|
-
maturity?: LearningMaturity;
|
|
37
|
-
created?: string;
|
|
38
|
-
first_seen_ts?: string;
|
|
39
|
-
last_seen_ts?: string;
|
|
40
|
-
project?: string | null;
|
|
41
|
-
source?: LearningSource | null;
|
|
42
|
-
supersedes?: string[];
|
|
43
|
-
superseded_by?: string;
|
|
44
|
-
}
|
|
45
|
-
export interface LearningsParseResult {
|
|
46
|
-
ok: boolean;
|
|
47
|
-
none: boolean;
|
|
48
|
-
entries: LearningSeedEntry[];
|
|
49
|
-
errors: string[];
|
|
50
|
-
details: string;
|
|
51
|
-
}
|
|
52
|
-
export declare function parseLearningsSection(sectionBody: string): LearningsParseResult;
|
|
1
|
+
import type { FlowStage, FlowTrack } from "./types.js";
|
|
2
|
+
import { type LintResult } from "./artifact-linter/shared.js";
|
|
3
|
+
export { validateReviewArmy, checkReviewVerdictConsistency, checkReviewSecurityNoChangeAttestation, type ReviewVerdictConsistencyResult, type ReviewSecurityNoChangeAttestationResult } from "./artifact-linter/review-army.js";
|
|
4
|
+
export { type LintFinding, type LintResult, type LearningEntryType, type LearningConfidence, type LearningSeverity, type LearningSource, type LearningSeedEntry, type LearningsParseResult, extractMarkdownSectionBody, parseLearningsSection } from "./artifact-linter/shared.js";
|
|
53
5
|
export declare function lintArtifact(projectRoot: string, stage: FlowStage, track?: FlowTrack): Promise<LintResult>;
|
|
54
|
-
export declare function validateReviewArmy(projectRoot: string): Promise<{
|
|
55
|
-
valid: boolean;
|
|
56
|
-
errors: string[];
|
|
57
|
-
}>;
|
|
58
|
-
export interface ReviewVerdictConsistencyResult {
|
|
59
|
-
ok: boolean;
|
|
60
|
-
errors: string[];
|
|
61
|
-
finalVerdict: "APPROVED" | "APPROVED_WITH_CONCERNS" | "BLOCKED" | "UNKNOWN";
|
|
62
|
-
openCriticalCount: number;
|
|
63
|
-
shipBlockerCount: number;
|
|
64
|
-
}
|
|
65
|
-
export interface ReviewSecurityNoChangeAttestationResult {
|
|
66
|
-
ok: boolean;
|
|
67
|
-
errors: string[];
|
|
68
|
-
hasSecurityFinding: boolean;
|
|
69
|
-
hasNoChangeAttestation: boolean;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Ensure the narrative verdict in 07-review.md is consistent with the
|
|
73
|
-
* structured review-army reconciliation. A review cannot declare
|
|
74
|
-
* APPROVED while open Critical findings or shipBlockers remain.
|
|
75
|
-
*/
|
|
76
|
-
export declare function checkReviewVerdictConsistency(projectRoot: string): Promise<ReviewVerdictConsistencyResult>;
|
|
77
|
-
export declare function checkReviewSecurityNoChangeAttestation(projectRoot: string): Promise<ReviewSecurityNoChangeAttestationResult>;
|