cclaw-cli 0.48.28 → 0.48.30
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.js +16 -5
- package/dist/artifact-paths.d.ts +28 -0
- package/dist/artifact-paths.js +261 -0
- package/dist/content/stage-schema.js +26 -11
- package/dist/content/stages/brainstorm.js +5 -5
- package/dist/content/stages/design.js +5 -5
- package/dist/content/stages/plan.js +153 -143
- package/dist/content/stages/review.js +212 -202
- package/dist/content/stages/scope.js +4 -4
- package/dist/content/stages/ship.js +132 -122
- package/dist/content/stages/spec.js +131 -121
- package/dist/content/stages/tdd.d.ts +2 -2
- package/dist/content/stages/tdd.js +237 -214
- package/dist/gate-evidence.js +7 -17
- package/dist/internal/advance-stage.js +9 -3
- package/package.json +1 -1
package/dist/artifact-linter.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { resolveArtifactPath as resolveStageArtifactPath } from "./artifact-paths.js";
|
|
3
4
|
import { RUNTIME_ROOT, SHIP_FINALIZATION_MODES } from "./constants.js";
|
|
4
5
|
import { exists } from "./fs-utils.js";
|
|
5
6
|
import { stageSchema } from "./content/stage-schema.js";
|
|
6
7
|
import { FLOW_STAGES } from "./types.js";
|
|
7
|
-
async function
|
|
8
|
+
async function resolveNamedArtifactPath(projectRoot, fileName) {
|
|
8
9
|
const relPath = path.join(RUNTIME_ROOT, "artifacts", fileName);
|
|
9
10
|
const absPath = path.join(projectRoot, relPath);
|
|
10
11
|
return { absPath, relPath };
|
|
@@ -791,7 +792,11 @@ function validateSectionBody(sectionBody, rule, sectionName) {
|
|
|
791
792
|
}
|
|
792
793
|
export async function lintArtifact(projectRoot, stage, track = "standard") {
|
|
793
794
|
const schema = stageSchema(stage, track);
|
|
794
|
-
const { absPath: absFile, relPath: relFile } = await
|
|
795
|
+
const { absPath: absFile, relPath: relFile } = await resolveStageArtifactPath(stage, {
|
|
796
|
+
projectRoot,
|
|
797
|
+
track,
|
|
798
|
+
intent: "read"
|
|
799
|
+
});
|
|
795
800
|
const findings = [];
|
|
796
801
|
if (!(await exists(absFile))) {
|
|
797
802
|
for (const v of schema.artifactValidation) {
|
|
@@ -949,8 +954,14 @@ export async function lintArtifact(projectRoot, stage, track = "standard") {
|
|
|
949
954
|
? "No placeholder tokens detected in Task List."
|
|
950
955
|
: `Detected placeholder token(s) in Task List: ${placeholderHits.join(", ")}.`
|
|
951
956
|
});
|
|
952
|
-
const
|
|
953
|
-
|
|
957
|
+
const scopeArtifact = await resolveStageArtifactPath("scope", {
|
|
958
|
+
projectRoot,
|
|
959
|
+
track,
|
|
960
|
+
intent: "read"
|
|
961
|
+
});
|
|
962
|
+
const scopeRaw = (await exists(scopeArtifact.absPath))
|
|
963
|
+
? await fs.readFile(scopeArtifact.absPath, "utf8")
|
|
964
|
+
: "";
|
|
954
965
|
const scopeDecisionIds = extractDecisionIds(scopeRaw);
|
|
955
966
|
const missingDecisionRefs = scopeDecisionIds.filter((id) => !raw.includes(id));
|
|
956
967
|
findings.push({
|
|
@@ -1053,7 +1064,7 @@ function isStringArray(v) {
|
|
|
1053
1064
|
}
|
|
1054
1065
|
export async function validateReviewArmy(projectRoot) {
|
|
1055
1066
|
const errors = [];
|
|
1056
|
-
const { absPath, relPath } = await
|
|
1067
|
+
const { absPath, relPath } = await resolveNamedArtifactPath(projectRoot, "07-review-army.json");
|
|
1057
1068
|
if (!(await exists(absPath))) {
|
|
1058
1069
|
return { valid: false, errors: [`Missing file: ${relPath}`] };
|
|
1059
1070
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FlowStage, FlowTrack } from "./types.js";
|
|
2
|
+
export type ArtifactPathIntent = "read" | "write";
|
|
3
|
+
export interface ResolveArtifactPathContext {
|
|
4
|
+
projectRoot: string;
|
|
5
|
+
track?: FlowTrack;
|
|
6
|
+
/**
|
|
7
|
+
* Optional brainstorm topic used for `<slug>` interpolation.
|
|
8
|
+
* When omitted, the resolver attempts to infer it from `00-idea.md`.
|
|
9
|
+
*/
|
|
10
|
+
topic?: string;
|
|
11
|
+
/**
|
|
12
|
+
* - read: locate an existing artifact first (new slug shape, then legacy fallback).
|
|
13
|
+
* - write: return a non-colliding writable path for a new artifact.
|
|
14
|
+
*/
|
|
15
|
+
intent?: ArtifactPathIntent;
|
|
16
|
+
}
|
|
17
|
+
export interface ResolvedArtifactPath {
|
|
18
|
+
stage: FlowStage;
|
|
19
|
+
fileName: string;
|
|
20
|
+
relPath: string;
|
|
21
|
+
absPath: string;
|
|
22
|
+
source: "existing" | "generated";
|
|
23
|
+
legacy: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function isSlugArtifactPattern(filePattern: string): boolean;
|
|
26
|
+
export declare function legacyArtifactFileName(filePattern: string): string;
|
|
27
|
+
export declare function slugifyArtifactTopic(topic: string): string;
|
|
28
|
+
export declare function resolveArtifactPath(stage: FlowStage, context: ResolveArtifactPathContext): Promise<ResolvedArtifactPath>;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { stageSchema } from "./content/stage-schema.js";
|
|
4
|
+
import { RUNTIME_ROOT } from "./constants.js";
|
|
5
|
+
import { exists } from "./fs-utils.js";
|
|
6
|
+
const LEGACY_ARTIFACT_GRACE_CYCLES = 2;
|
|
7
|
+
const DEFAULT_TOPIC_SLUG = "topic";
|
|
8
|
+
function escapeRegExp(value) {
|
|
9
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
10
|
+
}
|
|
11
|
+
function splitExt(fileName) {
|
|
12
|
+
const ext = path.extname(fileName);
|
|
13
|
+
if (!ext) {
|
|
14
|
+
return { stem: fileName, ext: "" };
|
|
15
|
+
}
|
|
16
|
+
return { stem: fileName.slice(0, -ext.length), ext };
|
|
17
|
+
}
|
|
18
|
+
function appendCollisionSuffix(fileName, index) {
|
|
19
|
+
const { stem, ext } = splitExt(fileName);
|
|
20
|
+
return `${stem}-${index}${ext}`;
|
|
21
|
+
}
|
|
22
|
+
export function isSlugArtifactPattern(filePattern) {
|
|
23
|
+
return filePattern.includes("<slug>");
|
|
24
|
+
}
|
|
25
|
+
export function legacyArtifactFileName(filePattern) {
|
|
26
|
+
if (!isSlugArtifactPattern(filePattern)) {
|
|
27
|
+
return filePattern;
|
|
28
|
+
}
|
|
29
|
+
return filePattern.replace(/-<slug>/gu, "");
|
|
30
|
+
}
|
|
31
|
+
export function slugifyArtifactTopic(topic) {
|
|
32
|
+
const normalized = topic
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.trim()
|
|
35
|
+
.replace(/[`"'“”‘’()[\]{}<>]/gu, " ")
|
|
36
|
+
.replace(/[^a-z0-9]+/gu, "-")
|
|
37
|
+
.replace(/^-+/u, "")
|
|
38
|
+
.replace(/-+$/u, "");
|
|
39
|
+
if (normalized.length === 0) {
|
|
40
|
+
return DEFAULT_TOPIC_SLUG;
|
|
41
|
+
}
|
|
42
|
+
return normalized.slice(0, 48);
|
|
43
|
+
}
|
|
44
|
+
function slugPatternRegex(filePattern) {
|
|
45
|
+
const [left, right] = filePattern.split("<slug>");
|
|
46
|
+
return new RegExp(`^${escapeRegExp(left ?? "")}[a-z0-9]+(?:-[a-z0-9]+)*(?:-\\d+)?${escapeRegExp(right ?? "")}$`, "u");
|
|
47
|
+
}
|
|
48
|
+
function searchRoots(projectRoot) {
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
absDir: path.join(projectRoot, RUNTIME_ROOT, "artifacts"),
|
|
52
|
+
relPrefix: path.join(RUNTIME_ROOT, "artifacts")
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
absDir: projectRoot,
|
|
56
|
+
relPrefix: ""
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
function candidateFromRoot(root, fileName) {
|
|
61
|
+
return {
|
|
62
|
+
relPath: root.relPrefix ? path.join(root.relPrefix, fileName) : fileName,
|
|
63
|
+
absPath: path.join(root.absDir, fileName)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function inferTopicFromIdeaArtifact(projectRoot) {
|
|
67
|
+
const ideaPath = path.join(projectRoot, RUNTIME_ROOT, "artifacts", "00-idea.md");
|
|
68
|
+
if (!(await exists(ideaPath))) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const raw = await fs.readFile(ideaPath, "utf8");
|
|
73
|
+
const lines = raw.split(/\r?\n/gu);
|
|
74
|
+
const userPromptHeading = lines.findIndex((line) => /^##\s+user prompt\b/iu.test(line.trim()));
|
|
75
|
+
if (userPromptHeading >= 0) {
|
|
76
|
+
for (let i = userPromptHeading + 1; i < lines.length; i += 1) {
|
|
77
|
+
const line = lines[i].trim();
|
|
78
|
+
if (line.length === 0)
|
|
79
|
+
continue;
|
|
80
|
+
if (/^##\s+/u.test(line))
|
|
81
|
+
break;
|
|
82
|
+
const candidate = line.replace(/^[-*>\s#]+/u, "").trim();
|
|
83
|
+
if (candidate.length > 0) {
|
|
84
|
+
return candidate;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const metadataLine = /^(?:class|track|stack|reclassification)\s*:/iu;
|
|
89
|
+
for (const line of lines) {
|
|
90
|
+
const trimmed = line.trim();
|
|
91
|
+
if (trimmed.length === 0)
|
|
92
|
+
continue;
|
|
93
|
+
if (metadataLine.test(trimmed))
|
|
94
|
+
continue;
|
|
95
|
+
if (/^##\s+/u.test(trimmed))
|
|
96
|
+
continue;
|
|
97
|
+
const candidate = trimmed.replace(/^[-*>\s#]+/u, "").trim();
|
|
98
|
+
if (candidate.length > 0) {
|
|
99
|
+
return candidate;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function resolvedTopicSlug(projectRoot, stage, explicitTopic) {
|
|
109
|
+
if (explicitTopic && explicitTopic.trim().length > 0) {
|
|
110
|
+
return slugifyArtifactTopic(explicitTopic);
|
|
111
|
+
}
|
|
112
|
+
const inferred = await inferTopicFromIdeaArtifact(projectRoot);
|
|
113
|
+
if (inferred && inferred.trim().length > 0) {
|
|
114
|
+
return slugifyArtifactTopic(inferred);
|
|
115
|
+
}
|
|
116
|
+
return slugifyArtifactTopic(stage);
|
|
117
|
+
}
|
|
118
|
+
async function collectExistingCandidates(projectRoot, filePattern, legacyFile) {
|
|
119
|
+
const roots = searchRoots(projectRoot);
|
|
120
|
+
const candidates = [];
|
|
121
|
+
const hasSlugPattern = isSlugArtifactPattern(filePattern);
|
|
122
|
+
const matcher = hasSlugPattern ? slugPatternRegex(filePattern) : null;
|
|
123
|
+
for (const root of roots) {
|
|
124
|
+
if (hasSlugPattern && matcher) {
|
|
125
|
+
let entries = [];
|
|
126
|
+
try {
|
|
127
|
+
entries = await fs.readdir(root.absDir);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
entries = [];
|
|
131
|
+
}
|
|
132
|
+
for (const entry of entries) {
|
|
133
|
+
if (!matcher.test(entry))
|
|
134
|
+
continue;
|
|
135
|
+
const { relPath, absPath } = candidateFromRoot(root, entry);
|
|
136
|
+
let mtimeMs = 0;
|
|
137
|
+
try {
|
|
138
|
+
const stat = await fs.stat(absPath);
|
|
139
|
+
mtimeMs = stat.mtimeMs;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
candidates.push({
|
|
145
|
+
fileName: entry,
|
|
146
|
+
relPath,
|
|
147
|
+
absPath,
|
|
148
|
+
mtimeMs,
|
|
149
|
+
legacy: false
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const { relPath, absPath } = candidateFromRoot(root, filePattern);
|
|
155
|
+
if (await exists(absPath)) {
|
|
156
|
+
let mtimeMs = 0;
|
|
157
|
+
try {
|
|
158
|
+
const stat = await fs.stat(absPath);
|
|
159
|
+
mtimeMs = stat.mtimeMs;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
mtimeMs = 0;
|
|
163
|
+
}
|
|
164
|
+
candidates.push({
|
|
165
|
+
fileName: filePattern,
|
|
166
|
+
relPath,
|
|
167
|
+
absPath,
|
|
168
|
+
mtimeMs,
|
|
169
|
+
legacy: false
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (legacyFile && LEGACY_ARTIFACT_GRACE_CYCLES > 0) {
|
|
174
|
+
const { relPath, absPath } = candidateFromRoot(root, legacyFile);
|
|
175
|
+
if (await exists(absPath)) {
|
|
176
|
+
let mtimeMs = 0;
|
|
177
|
+
try {
|
|
178
|
+
const stat = await fs.stat(absPath);
|
|
179
|
+
mtimeMs = stat.mtimeMs;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
mtimeMs = 0;
|
|
183
|
+
}
|
|
184
|
+
candidates.push({
|
|
185
|
+
fileName: legacyFile,
|
|
186
|
+
relPath,
|
|
187
|
+
absPath,
|
|
188
|
+
mtimeMs,
|
|
189
|
+
legacy: true
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
candidates.sort((a, b) => {
|
|
195
|
+
if (b.mtimeMs !== a.mtimeMs) {
|
|
196
|
+
return b.mtimeMs - a.mtimeMs;
|
|
197
|
+
}
|
|
198
|
+
if (a.legacy !== b.legacy) {
|
|
199
|
+
return a.legacy ? 1 : -1;
|
|
200
|
+
}
|
|
201
|
+
return a.fileName.localeCompare(b.fileName);
|
|
202
|
+
});
|
|
203
|
+
return candidates;
|
|
204
|
+
}
|
|
205
|
+
export async function resolveArtifactPath(stage, context) {
|
|
206
|
+
const track = context.track ?? "standard";
|
|
207
|
+
const intent = context.intent ?? "read";
|
|
208
|
+
const filePattern = stageSchema(stage, track).artifactFile;
|
|
209
|
+
const hasSlugPattern = isSlugArtifactPattern(filePattern);
|
|
210
|
+
const legacyFile = hasSlugPattern ? legacyArtifactFileName(filePattern) : null;
|
|
211
|
+
const existing = await collectExistingCandidates(context.projectRoot, filePattern, legacyFile);
|
|
212
|
+
if (intent === "read" && existing.length > 0) {
|
|
213
|
+
const picked = existing[0];
|
|
214
|
+
return {
|
|
215
|
+
stage,
|
|
216
|
+
fileName: picked.fileName,
|
|
217
|
+
relPath: picked.relPath,
|
|
218
|
+
absPath: picked.absPath,
|
|
219
|
+
source: "existing",
|
|
220
|
+
legacy: picked.legacy
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const artifactRoot = path.join(context.projectRoot, RUNTIME_ROOT, "artifacts");
|
|
224
|
+
if (!hasSlugPattern) {
|
|
225
|
+
return {
|
|
226
|
+
stage,
|
|
227
|
+
fileName: filePattern,
|
|
228
|
+
relPath: path.join(RUNTIME_ROOT, "artifacts", filePattern),
|
|
229
|
+
absPath: path.join(artifactRoot, filePattern),
|
|
230
|
+
source: "generated",
|
|
231
|
+
legacy: false
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const topicSlug = await resolvedTopicSlug(context.projectRoot, stage, context.topic);
|
|
235
|
+
const baseFileName = filePattern.replace("<slug>", topicSlug);
|
|
236
|
+
if (intent === "read") {
|
|
237
|
+
return {
|
|
238
|
+
stage,
|
|
239
|
+
fileName: baseFileName,
|
|
240
|
+
relPath: path.join(RUNTIME_ROOT, "artifacts", baseFileName),
|
|
241
|
+
absPath: path.join(artifactRoot, baseFileName),
|
|
242
|
+
source: "generated",
|
|
243
|
+
legacy: false
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
let candidate = baseFileName;
|
|
247
|
+
let index = 2;
|
|
248
|
+
// Keep incrementing while a matching file exists under active artifacts root.
|
|
249
|
+
while (await exists(path.join(artifactRoot, candidate))) {
|
|
250
|
+
candidate = appendCollisionSuffix(baseFileName, index);
|
|
251
|
+
index += 1;
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
stage,
|
|
255
|
+
fileName: candidate,
|
|
256
|
+
relPath: path.join(RUNTIME_ROOT, "artifacts", candidate),
|
|
257
|
+
absPath: path.join(artifactRoot, candidate),
|
|
258
|
+
source: "generated",
|
|
259
|
+
legacy: false
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -67,17 +67,32 @@ export function parseSkillEnvelope(raw) {
|
|
|
67
67
|
}
|
|
68
68
|
return parsed;
|
|
69
69
|
}
|
|
70
|
-
const
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
".cclaw/artifacts/08-ship.md": "ship"
|
|
70
|
+
const ARTIFACT_STAGE_BY_PREFIX = {
|
|
71
|
+
"01": "brainstorm",
|
|
72
|
+
"02": "scope",
|
|
73
|
+
"03": "design",
|
|
74
|
+
"04": "spec",
|
|
75
|
+
"05": "plan",
|
|
76
|
+
"06": "tdd",
|
|
77
|
+
"07": "review",
|
|
78
|
+
"08": "ship"
|
|
80
79
|
};
|
|
80
|
+
const ARTIFACT_STAGE_BY_SPECIAL_FILE = {
|
|
81
|
+
"02a-research.md": "design"
|
|
82
|
+
};
|
|
83
|
+
function stageFromArtifactPath(artifactPath) {
|
|
84
|
+
const normalized = artifactPath.replace(/\\/gu, "/");
|
|
85
|
+
const fileName = normalized.split("/").pop() ?? normalized;
|
|
86
|
+
const special = ARTIFACT_STAGE_BY_SPECIAL_FILE[fileName];
|
|
87
|
+
if (special) {
|
|
88
|
+
return special;
|
|
89
|
+
}
|
|
90
|
+
const match = /^(\d{2})(?:[a-z])?-/u.exec(fileName);
|
|
91
|
+
if (!match) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return ARTIFACT_STAGE_BY_PREFIX[match[1]] ?? null;
|
|
95
|
+
}
|
|
81
96
|
const REQUIRED_GATE_IDS = {
|
|
82
97
|
brainstorm: [
|
|
83
98
|
"brainstorm_approaches_compared",
|
|
@@ -172,7 +187,7 @@ function tieredArtifactValidation(stage, rows) {
|
|
|
172
187
|
function readsFromForTrack(readsFrom, track) {
|
|
173
188
|
const stageSet = new Set(TRACK_STAGES[track]);
|
|
174
189
|
return readsFrom.filter((artifactPath) => {
|
|
175
|
-
const stage =
|
|
190
|
+
const stage = stageFromArtifactPath(artifactPath);
|
|
176
191
|
if (!stage) {
|
|
177
192
|
return true;
|
|
178
193
|
}
|
|
@@ -46,7 +46,7 @@ export const BRAINSTORM = {
|
|
|
46
46
|
"**Recommend only after reaction** — present final recommendation with rationale that explicitly references user feedback.",
|
|
47
47
|
"**Present design by sections** — scale each section to its complexity. Ask after each section whether it looks right so far. Cover: architecture, key components, data flow.",
|
|
48
48
|
"**Optional visual companion** — when architecture/data flow complexity is medium+ offer a compact diagram (ASCII or Mermaid) before artifact write-up.",
|
|
49
|
-
"**Write artifact** to `.cclaw/artifacts/01-brainstorm
|
|
49
|
+
"**Write artifact** to `.cclaw/artifacts/01-brainstorm-<slug>.md`.",
|
|
50
50
|
"**Document-quality pass** — run a brief adversarial review of the artifact (gaps, contradictions, missing trade-offs), then patch before user review.",
|
|
51
51
|
"**Self-review** — scan for placeholders/TODOs, check internal consistency, verify scope is focused, resolve any ambiguity.",
|
|
52
52
|
"**User reviews artifact** — ask the user to review the written artifact and explicitly approve or request changes.",
|
|
@@ -75,7 +75,7 @@ export const BRAINSTORM = {
|
|
|
75
75
|
"Collect user reaction before giving your recommendation.",
|
|
76
76
|
"Recommend after reaction and explain how feedback changed the recommendation.",
|
|
77
77
|
"Present design sections incrementally, get approval after each.",
|
|
78
|
-
"Write approved direction to `.cclaw/artifacts/01-brainstorm
|
|
78
|
+
"Write approved direction to `.cclaw/artifacts/01-brainstorm-<slug>.md`.",
|
|
79
79
|
"Run document-quality pass to close contradictions and weak trade-off reasoning.",
|
|
80
80
|
"Self-review: placeholder scan, internal consistency, scope check, ambiguity check.",
|
|
81
81
|
"Request explicit user approval of the artifact.",
|
|
@@ -87,7 +87,7 @@ export const BRAINSTORM = {
|
|
|
87
87
|
{ id: "brainstorm_artifact_reviewed", description: "User reviewed the written brainstorm artifact and confirmed readiness." }
|
|
88
88
|
],
|
|
89
89
|
requiredEvidence: [
|
|
90
|
-
"Artifact written to `.cclaw/artifacts/01-brainstorm
|
|
90
|
+
"Artifact written to `.cclaw/artifacts/01-brainstorm-<slug>.md`.",
|
|
91
91
|
"Project context was explored (files, docs, or recent activity referenced).",
|
|
92
92
|
"Clarifying questions and their answers are captured.",
|
|
93
93
|
"2-3 approaches with trade-offs are recorded, including one higher-upside challenger option.",
|
|
@@ -119,11 +119,11 @@ export const BRAINSTORM = {
|
|
|
119
119
|
]
|
|
120
120
|
},
|
|
121
121
|
artifactRules: {
|
|
122
|
-
artifactFile: "01-brainstorm
|
|
122
|
+
artifactFile: "01-brainstorm-<slug>.md",
|
|
123
123
|
completionStatus: ["DONE", "DONE_WITH_CONCERNS", "BLOCKED"],
|
|
124
124
|
crossStageTrace: {
|
|
125
125
|
readsFrom: [],
|
|
126
|
-
writesTo: [".cclaw/artifacts/01-brainstorm
|
|
126
|
+
writesTo: [".cclaw/artifacts/01-brainstorm-<slug>.md"],
|
|
127
127
|
traceabilityRule: "Scope and design decisions must trace back to explored context and approved brainstorm direction."
|
|
128
128
|
},
|
|
129
129
|
artifactValidation: [
|
|
@@ -100,7 +100,7 @@ export const DESIGN = {
|
|
|
100
100
|
],
|
|
101
101
|
requiredEvidence: [
|
|
102
102
|
"Research artifact written to `.cclaw/artifacts/02a-research.md` with stack/features/architecture/pitfalls sections plus synthesis.",
|
|
103
|
-
"Artifact written to `.cclaw/artifacts/03-design
|
|
103
|
+
"Artifact written to `.cclaw/artifacts/03-design-<slug>.md`.",
|
|
104
104
|
"Failure-mode table exists in Method/Exception/Rescue/UserSees format.",
|
|
105
105
|
"Data-flow shadow and error-flow diagrams are present for Standard+ complexity.",
|
|
106
106
|
"Security & threat model findings are documented with mitigations.",
|
|
@@ -138,15 +138,15 @@ export const DESIGN = {
|
|
|
138
138
|
]
|
|
139
139
|
},
|
|
140
140
|
artifactRules: {
|
|
141
|
-
artifactFile: "03-design
|
|
141
|
+
artifactFile: "03-design-<slug>.md",
|
|
142
142
|
completionStatus: ["DONE", "DONE_WITH_CONCERNS", "BLOCKED"],
|
|
143
143
|
crossStageTrace: {
|
|
144
144
|
readsFrom: [
|
|
145
|
-
".cclaw/artifacts/01-brainstorm
|
|
146
|
-
".cclaw/artifacts/02-scope
|
|
145
|
+
".cclaw/artifacts/01-brainstorm-<slug>.md",
|
|
146
|
+
".cclaw/artifacts/02-scope-<slug>.md",
|
|
147
147
|
".cclaw/artifacts/02a-research.md"
|
|
148
148
|
],
|
|
149
|
-
writesTo: [".cclaw/artifacts/03-design
|
|
149
|
+
writesTo: [".cclaw/artifacts/03-design-<slug>.md"],
|
|
150
150
|
traceabilityRule: "Every architecture decision must trace to a scope boundary. Every downstream spec requirement must trace to a design decision."
|
|
151
151
|
},
|
|
152
152
|
artifactValidation: [
|