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