cclaw-cli 0.5.4 → 0.5.5
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 +8 -11
- package/dist/artifact-linter.js +3 -13
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +12 -1
- package/dist/config.js +0 -19
- package/dist/content/contracts.js +7 -12
- package/dist/content/examples.js +34 -34
- package/dist/content/hooks.d.ts +4 -6
- package/dist/content/hooks.js +104 -523
- package/dist/content/learnings.js +55 -203
- package/dist/content/meta-skill.js +8 -11
- package/dist/content/next-command.js +3 -3
- package/dist/content/observe.d.ts +4 -7
- package/dist/content/observe.js +10 -48
- package/dist/content/session-hooks.js +8 -8
- package/dist/content/skills.js +9 -16
- package/dist/content/stage-schema.js +80 -121
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +27 -48
- package/dist/delegation.js +7 -7
- package/dist/doctor.js +16 -47
- package/dist/flow-state.js +1 -1
- package/dist/harness-adapters.js +1 -1
- package/dist/install.js +22 -48
- package/dist/policy.js +1 -4
- package/dist/runs.d.ts +9 -9
- package/dist/runs.js +107 -320
- package/dist/trace-matrix.js +8 -18
- package/dist/types.d.ts +0 -4
- package/package.json +1 -1
- package/dist/learnings-summarizer.d.ts +0 -25
- package/dist/learnings-summarizer.js +0 -201
package/dist/runs.js
CHANGED
|
@@ -6,8 +6,6 @@ import { ensureDir, exists, withDirectoryLock, writeFileSafe } from "./fs-utils.
|
|
|
6
6
|
const FLOW_STATE_REL_PATH = `${RUNTIME_ROOT}/state/flow-state.json`;
|
|
7
7
|
const RUNS_DIR_REL_PATH = `${RUNTIME_ROOT}/runs`;
|
|
8
8
|
const ACTIVE_ARTIFACTS_REL_PATH = `${RUNTIME_ROOT}/artifacts`;
|
|
9
|
-
const RUN_META_FILE = "run.json";
|
|
10
|
-
const RUN_HANDOFF_FILE = "handoff.md";
|
|
11
9
|
const FLOW_STAGE_SET = new Set(COMMAND_FILE_ORDER);
|
|
12
10
|
function flowStatePath(projectRoot) {
|
|
13
11
|
return path.join(projectRoot, FLOW_STATE_REL_PATH);
|
|
@@ -21,65 +19,6 @@ function runsRoot(projectRoot) {
|
|
|
21
19
|
function activeArtifactsPath(projectRoot) {
|
|
22
20
|
return path.join(projectRoot, ACTIVE_ARTIFACTS_REL_PATH);
|
|
23
21
|
}
|
|
24
|
-
function runRoot(projectRoot, runId) {
|
|
25
|
-
return path.join(runsRoot(projectRoot), requireSafeRunId(runId));
|
|
26
|
-
}
|
|
27
|
-
function runArtifactsPath(projectRoot, runId) {
|
|
28
|
-
return path.join(runRoot(projectRoot, runId), "artifacts");
|
|
29
|
-
}
|
|
30
|
-
function runMetaPath(projectRoot, runId) {
|
|
31
|
-
return path.join(runRoot(projectRoot, runId), RUN_META_FILE);
|
|
32
|
-
}
|
|
33
|
-
function runHandoffPath(projectRoot, runId) {
|
|
34
|
-
return path.join(runRoot(projectRoot, runId), RUN_HANDOFF_FILE);
|
|
35
|
-
}
|
|
36
|
-
function nowIso() {
|
|
37
|
-
return new Date().toISOString();
|
|
38
|
-
}
|
|
39
|
-
function pad2(value) {
|
|
40
|
-
return value.toString().padStart(2, "0");
|
|
41
|
-
}
|
|
42
|
-
function buildRunId(date = new Date()) {
|
|
43
|
-
const yyyy = date.getUTCFullYear();
|
|
44
|
-
const mm = pad2(date.getUTCMonth() + 1);
|
|
45
|
-
const dd = pad2(date.getUTCDate());
|
|
46
|
-
const hh = pad2(date.getUTCHours());
|
|
47
|
-
const min = pad2(date.getUTCMinutes());
|
|
48
|
-
const ss = pad2(date.getUTCSeconds());
|
|
49
|
-
const random = Math.random().toString(36).slice(2, 6);
|
|
50
|
-
return `run-${yyyy}${mm}${dd}-${hh}${min}${ss}-${random}`;
|
|
51
|
-
}
|
|
52
|
-
function normalizeTitle(title) {
|
|
53
|
-
const trimmed = (title ?? "").trim();
|
|
54
|
-
if (trimmed.length === 0) {
|
|
55
|
-
return "New feature run";
|
|
56
|
-
}
|
|
57
|
-
return trimmed;
|
|
58
|
-
}
|
|
59
|
-
function isSafeRunId(value) {
|
|
60
|
-
return /^[A-Za-z0-9_-]{1,128}$/u.test(value);
|
|
61
|
-
}
|
|
62
|
-
function sanitizeRunId(value) {
|
|
63
|
-
if (typeof value !== "string")
|
|
64
|
-
return undefined;
|
|
65
|
-
const trimmed = value.trim();
|
|
66
|
-
return isSafeRunId(trimmed) ? trimmed : undefined;
|
|
67
|
-
}
|
|
68
|
-
function requireSafeRunId(runId) {
|
|
69
|
-
const safe = sanitizeRunId(runId);
|
|
70
|
-
if (!safe) {
|
|
71
|
-
throw new Error(`Invalid run id "${runId}"`);
|
|
72
|
-
}
|
|
73
|
-
return safe;
|
|
74
|
-
}
|
|
75
|
-
function snapshotState(state) {
|
|
76
|
-
return {
|
|
77
|
-
currentStage: state.currentStage,
|
|
78
|
-
completedStages: [...state.completedStages],
|
|
79
|
-
guardEvidence: { ...state.guardEvidence },
|
|
80
|
-
stageGateCatalog: JSON.parse(JSON.stringify(state.stageGateCatalog))
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
22
|
function isFlowStage(value) {
|
|
84
23
|
return typeof value === "string" && FLOW_STAGE_SET.has(value);
|
|
85
24
|
}
|
|
@@ -144,300 +83,148 @@ function sanitizeStageGateCatalog(value, fallback) {
|
|
|
144
83
|
}
|
|
145
84
|
return next;
|
|
146
85
|
}
|
|
147
|
-
function coerceFlowState(parsed
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
86
|
+
function coerceFlowState(parsed) {
|
|
87
|
+
const next = createInitialFlowState();
|
|
88
|
+
const activeRunIdRaw = parsed.activeRunId;
|
|
89
|
+
const activeRunId = typeof activeRunIdRaw === "string" && activeRunIdRaw.trim().length > 0
|
|
90
|
+
? activeRunIdRaw.trim()
|
|
91
|
+
: next.activeRunId;
|
|
152
92
|
return {
|
|
153
|
-
activeRunId
|
|
93
|
+
activeRunId,
|
|
154
94
|
currentStage: isFlowStage(parsed.currentStage) ? parsed.currentStage : next.currentStage,
|
|
155
95
|
completedStages: sanitizeCompletedStages(parsed.completedStages),
|
|
156
96
|
guardEvidence: sanitizeGuardEvidence(parsed.guardEvidence),
|
|
157
97
|
stageGateCatalog: sanitizeStageGateCatalog(parsed.stageGateCatalog, next.stageGateCatalog)
|
|
158
98
|
};
|
|
159
99
|
}
|
|
160
|
-
function
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
100
|
+
function toArchiveDate(date = new Date()) {
|
|
101
|
+
const yyyy = date.getFullYear().toString();
|
|
102
|
+
const mm = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
103
|
+
const dd = date.getDate().toString().padStart(2, "0");
|
|
104
|
+
return `${yyyy}-${mm}-${dd}`;
|
|
105
|
+
}
|
|
106
|
+
function slugifyFeatureName(value) {
|
|
107
|
+
const slug = value
|
|
108
|
+
.toLowerCase()
|
|
109
|
+
.trim()
|
|
110
|
+
.replace(/[^a-z0-9]+/gu, "-")
|
|
111
|
+
.replace(/^-+/u, "")
|
|
112
|
+
.replace(/-+$/u, "");
|
|
113
|
+
if (slug.length === 0) {
|
|
114
|
+
return "feature";
|
|
115
|
+
}
|
|
116
|
+
return slug.slice(0, 64);
|
|
117
|
+
}
|
|
118
|
+
async function inferFeatureNameFromArtifacts(projectRoot) {
|
|
119
|
+
const ideaPath = path.join(projectRoot, ACTIVE_ARTIFACTS_REL_PATH, "00-idea.md");
|
|
120
|
+
if (!(await exists(ideaPath))) {
|
|
121
|
+
return "feature";
|
|
164
122
|
}
|
|
165
|
-
const [, year, month, day, hour, minute, second] = match;
|
|
166
|
-
const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second)));
|
|
167
|
-
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
168
|
-
}
|
|
169
|
-
async function readJsonFile(filePath) {
|
|
170
|
-
if (!(await exists(filePath)))
|
|
171
|
-
return null;
|
|
172
123
|
try {
|
|
173
|
-
|
|
124
|
+
const raw = await fs.readFile(ideaPath, "utf8");
|
|
125
|
+
const firstMeaningful = raw
|
|
126
|
+
.split(/\r?\n/gu)
|
|
127
|
+
.map((line) => line.trim())
|
|
128
|
+
.find((line) => line.length > 0);
|
|
129
|
+
if (!firstMeaningful) {
|
|
130
|
+
return "feature";
|
|
131
|
+
}
|
|
132
|
+
return firstMeaningful.replace(/^[-#*\s]+/u, "").trim() || "feature";
|
|
174
133
|
}
|
|
175
134
|
catch {
|
|
176
|
-
return
|
|
135
|
+
return "feature";
|
|
177
136
|
}
|
|
178
137
|
}
|
|
179
|
-
async function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
async function clearImmediateFiles(dirPath) {
|
|
186
|
-
if (!(await exists(dirPath)))
|
|
187
|
-
return;
|
|
188
|
-
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
189
|
-
for (const entry of entries) {
|
|
190
|
-
if (entry.isFile()) {
|
|
191
|
-
await fs.rm(path.join(dirPath, entry.name), { force: true });
|
|
192
|
-
}
|
|
138
|
+
async function uniqueArchiveId(projectRoot, baseId) {
|
|
139
|
+
let index = 1;
|
|
140
|
+
let candidate = baseId;
|
|
141
|
+
while (await exists(path.join(runsRoot(projectRoot), candidate))) {
|
|
142
|
+
index += 1;
|
|
143
|
+
candidate = `${baseId}-${index}`;
|
|
193
144
|
}
|
|
194
|
-
|
|
195
|
-
async function copyImmediateFiles(fromDir, toDir) {
|
|
196
|
-
await ensureDir(toDir);
|
|
197
|
-
const fileNames = await listImmediateFiles(fromDir);
|
|
198
|
-
for (const fileName of fileNames) {
|
|
199
|
-
const sourcePath = path.join(fromDir, fileName);
|
|
200
|
-
const targetPath = path.join(toDir, fileName);
|
|
201
|
-
await fs.copyFile(sourcePath, targetPath);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
function handoffMarkdown(runMeta, state) {
|
|
205
|
-
return `# Run Handoff
|
|
206
|
-
|
|
207
|
-
## Run
|
|
208
|
-
- ID: ${runMeta.id}
|
|
209
|
-
- Title: ${runMeta.title}
|
|
210
|
-
- Created: ${runMeta.createdAt}
|
|
211
|
-
- Archived: ${runMeta.archivedAt ?? "active"}
|
|
212
|
-
|
|
213
|
-
## Flow Snapshot
|
|
214
|
-
- Active stage: ${state.currentStage}
|
|
215
|
-
- Completed stages: ${state.completedStages.join(", ") || "(none)"}
|
|
216
|
-
- Active run ID in flow-state: ${state.activeRunId}
|
|
217
|
-
|
|
218
|
-
## Paths
|
|
219
|
-
- Active artifacts: \`${RUNTIME_ROOT}/artifacts/\`
|
|
220
|
-
- Canonical run artifacts: \`${RUNTIME_ROOT}/runs/${runMeta.id}/artifacts/\`
|
|
221
|
-
|
|
222
|
-
## Resume
|
|
223
|
-
1. Continue with the stage command for \`${state.currentStage}\`
|
|
224
|
-
2. If needed, sync artifacts from \`${RUNTIME_ROOT}/runs/${runMeta.id}/artifacts/\`
|
|
225
|
-
`;
|
|
145
|
+
return candidate;
|
|
226
146
|
}
|
|
227
147
|
export async function readFlowState(projectRoot) {
|
|
228
148
|
const statePath = flowStatePath(projectRoot);
|
|
229
|
-
|
|
230
|
-
|
|
149
|
+
if (!(await exists(statePath))) {
|
|
150
|
+
return createInitialFlowState();
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const parsed = JSON.parse(await fs.readFile(statePath, "utf8"));
|
|
154
|
+
return coerceFlowState(parsed);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
231
157
|
return createInitialFlowState();
|
|
232
158
|
}
|
|
233
|
-
return coerceFlowState(parsed);
|
|
234
159
|
}
|
|
235
160
|
export async function writeFlowState(projectRoot, state) {
|
|
236
161
|
await withDirectoryLock(flowStateLockPath(projectRoot), async () => {
|
|
237
|
-
const safe = coerceFlowState({ ...state }
|
|
162
|
+
const safe = coerceFlowState({ ...state });
|
|
238
163
|
await writeFileSafe(flowStatePath(projectRoot), `${JSON.stringify(safe, null, 2)}\n`);
|
|
239
164
|
});
|
|
240
165
|
}
|
|
166
|
+
export async function ensureRunSystem(projectRoot, _options = {}) {
|
|
167
|
+
await ensureDir(runsRoot(projectRoot));
|
|
168
|
+
await ensureDir(activeArtifactsPath(projectRoot));
|
|
169
|
+
const statePath = flowStatePath(projectRoot);
|
|
170
|
+
const state = await readFlowState(projectRoot);
|
|
171
|
+
if (!(await exists(statePath))) {
|
|
172
|
+
await writeFlowState(projectRoot, state);
|
|
173
|
+
}
|
|
174
|
+
return state;
|
|
175
|
+
}
|
|
241
176
|
export async function listRuns(projectRoot) {
|
|
242
177
|
const root = runsRoot(projectRoot);
|
|
243
|
-
if (!(await exists(root)))
|
|
178
|
+
if (!(await exists(root))) {
|
|
244
179
|
return [];
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const runId = dir.name;
|
|
251
|
-
if (!isSafeRunId(runId))
|
|
252
|
-
continue;
|
|
253
|
-
const meta = await readJsonFile(runMetaPath(projectRoot, runId));
|
|
254
|
-
if (meta && typeof meta.id === "string" && meta.id === runId) {
|
|
255
|
-
metas.push(meta);
|
|
180
|
+
}
|
|
181
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
182
|
+
const runs = [];
|
|
183
|
+
for (const entry of entries) {
|
|
184
|
+
if (!entry.isDirectory()) {
|
|
256
185
|
continue;
|
|
257
186
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
catch {
|
|
265
|
-
fallbackCreatedAt = null;
|
|
266
|
-
}
|
|
187
|
+
const runPath = path.join(root, entry.name);
|
|
188
|
+
let createdAt = new Date().toISOString();
|
|
189
|
+
try {
|
|
190
|
+
const stat = await fs.stat(runPath);
|
|
191
|
+
createdAt = stat.birthtime?.toISOString?.() ?? stat.mtime.toISOString();
|
|
267
192
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
title: runId,
|
|
271
|
-
createdAt: fallbackCreatedAt ?? nowIso()
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
return metas.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
275
|
-
}
|
|
276
|
-
async function ensureRunMetadata(projectRoot, meta) {
|
|
277
|
-
await writeFileSafe(runMetaPath(projectRoot, meta.id), `${JSON.stringify(meta, null, 2)}\n`);
|
|
278
|
-
}
|
|
279
|
-
async function persistRunStateSnapshot(projectRoot, runId, state) {
|
|
280
|
-
const meta = await readJsonFile(runMetaPath(projectRoot, runId));
|
|
281
|
-
if (!meta)
|
|
282
|
-
return;
|
|
283
|
-
const safeState = coerceFlowState({ ...state }, state.activeRunId);
|
|
284
|
-
await ensureRunMetadata(projectRoot, {
|
|
285
|
-
...meta,
|
|
286
|
-
stateSnapshot: snapshotState(safeState)
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
async function syncActiveArtifactsToRun(projectRoot, runId) {
|
|
290
|
-
const fromDir = activeArtifactsPath(projectRoot);
|
|
291
|
-
const toDir = runArtifactsPath(projectRoot, runId);
|
|
292
|
-
await ensureDir(toDir);
|
|
293
|
-
await clearImmediateFiles(toDir);
|
|
294
|
-
await copyImmediateFiles(fromDir, toDir);
|
|
295
|
-
}
|
|
296
|
-
async function loadRunArtifactsToActive(projectRoot, runId) {
|
|
297
|
-
const fromDir = runArtifactsPath(projectRoot, runId);
|
|
298
|
-
const toDir = activeArtifactsPath(projectRoot);
|
|
299
|
-
await ensureDir(toDir);
|
|
300
|
-
await clearImmediateFiles(toDir);
|
|
301
|
-
await copyImmediateFiles(fromDir, toDir);
|
|
302
|
-
}
|
|
303
|
-
async function createRun(projectRoot, options) {
|
|
304
|
-
const runId = buildRunId();
|
|
305
|
-
const meta = {
|
|
306
|
-
id: runId,
|
|
307
|
-
title: normalizeTitle(options?.title),
|
|
308
|
-
createdAt: nowIso()
|
|
309
|
-
};
|
|
310
|
-
await ensureDir(runRoot(projectRoot, runId));
|
|
311
|
-
await ensureRunMetadata(projectRoot, meta);
|
|
312
|
-
const runArtifactsDir = runArtifactsPath(projectRoot, runId);
|
|
313
|
-
await ensureDir(runArtifactsDir);
|
|
314
|
-
if (options?.seedFromActiveArtifacts && (await exists(activeArtifactsPath(projectRoot)))) {
|
|
315
|
-
await copyImmediateFiles(activeArtifactsPath(projectRoot), runArtifactsDir);
|
|
316
|
-
}
|
|
317
|
-
return meta;
|
|
318
|
-
}
|
|
319
|
-
async function ensureRunHandoff(projectRoot, runId) {
|
|
320
|
-
const state = await readFlowState(projectRoot);
|
|
321
|
-
const meta = await readJsonFile(runMetaPath(projectRoot, runId));
|
|
322
|
-
if (!meta)
|
|
323
|
-
return;
|
|
324
|
-
await writeFileSafe(runHandoffPath(projectRoot, runId), handoffMarkdown(meta, state));
|
|
325
|
-
}
|
|
326
|
-
export async function ensureRunSystem(projectRoot, options = {}) {
|
|
327
|
-
await ensureDir(runsRoot(projectRoot));
|
|
328
|
-
await ensureDir(activeArtifactsPath(projectRoot));
|
|
329
|
-
let state = await readFlowState(projectRoot);
|
|
330
|
-
let activeRunId = state.activeRunId;
|
|
331
|
-
const createIfMissing = options.createIfMissing !== false;
|
|
332
|
-
const activeRunExists = activeRunId.trim().length > 0 && (await exists(runArtifactsPath(projectRoot, activeRunId)));
|
|
333
|
-
if (!activeRunExists) {
|
|
334
|
-
if (!createIfMissing) {
|
|
335
|
-
return state;
|
|
193
|
+
catch {
|
|
194
|
+
// keep fallback timestamp
|
|
336
195
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
title:
|
|
340
|
-
|
|
196
|
+
runs.push({
|
|
197
|
+
id: entry.name,
|
|
198
|
+
title: entry.name,
|
|
199
|
+
createdAt
|
|
341
200
|
});
|
|
342
|
-
activeRunId = initialRun.id;
|
|
343
|
-
state = { ...state, activeRunId };
|
|
344
|
-
await writeFlowState(projectRoot, state);
|
|
345
|
-
}
|
|
346
|
-
const runArtifactsDir = runArtifactsPath(projectRoot, activeRunId);
|
|
347
|
-
await ensureDir(runArtifactsDir);
|
|
348
|
-
if ((await listImmediateFiles(activeArtifactsPath(projectRoot))).length === 0) {
|
|
349
|
-
await loadRunArtifactsToActive(projectRoot, activeRunId);
|
|
350
201
|
}
|
|
351
|
-
|
|
352
|
-
await syncActiveArtifactsToRun(projectRoot, activeRunId);
|
|
353
|
-
}
|
|
354
|
-
await persistRunStateSnapshot(projectRoot, activeRunId, state);
|
|
355
|
-
await ensureRunHandoff(projectRoot, activeRunId);
|
|
356
|
-
return state;
|
|
357
|
-
}
|
|
358
|
-
export async function startNewFeatureRun(projectRoot, title) {
|
|
359
|
-
await ensureRunSystem(projectRoot);
|
|
360
|
-
const state = await readFlowState(projectRoot);
|
|
361
|
-
await syncActiveArtifactsToRun(projectRoot, state.activeRunId);
|
|
362
|
-
await persistRunStateSnapshot(projectRoot, state.activeRunId, state);
|
|
363
|
-
await ensureRunHandoff(projectRoot, state.activeRunId);
|
|
364
|
-
const nextRun = await createRun(projectRoot, {
|
|
365
|
-
title,
|
|
366
|
-
seedFromActiveArtifacts: false
|
|
367
|
-
});
|
|
368
|
-
const nextState = {
|
|
369
|
-
...createInitialFlowState(nextRun.id),
|
|
370
|
-
activeRunId: nextRun.id
|
|
371
|
-
};
|
|
372
|
-
await writeFlowState(projectRoot, nextState);
|
|
373
|
-
await persistRunStateSnapshot(projectRoot, nextRun.id, nextState);
|
|
374
|
-
await loadRunArtifactsToActive(projectRoot, nextRun.id);
|
|
375
|
-
await ensureRunHandoff(projectRoot, nextRun.id);
|
|
376
|
-
return nextRun;
|
|
202
|
+
return runs.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
377
203
|
}
|
|
378
|
-
export async function
|
|
204
|
+
export async function archiveRun(projectRoot, featureName) {
|
|
379
205
|
await ensureRunSystem(projectRoot);
|
|
380
|
-
const
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
await
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return targetMeta;
|
|
404
|
-
}
|
|
405
|
-
export async function archiveRun(projectRoot, runId) {
|
|
406
|
-
await ensureRunSystem(projectRoot);
|
|
407
|
-
const state = await readFlowState(projectRoot);
|
|
408
|
-
const targetRunId = runId ? requireSafeRunId(runId) : state.activeRunId;
|
|
409
|
-
const targetMeta = await readJsonFile(runMetaPath(projectRoot, targetRunId));
|
|
410
|
-
if (!targetMeta) {
|
|
411
|
-
throw new Error(`Run "${targetRunId}" not found under ${RUNTIME_ROOT}/runs/`);
|
|
412
|
-
}
|
|
413
|
-
if (targetRunId === state.activeRunId) {
|
|
414
|
-
await syncActiveArtifactsToRun(projectRoot, targetRunId);
|
|
415
|
-
await persistRunStateSnapshot(projectRoot, targetRunId, state);
|
|
416
|
-
}
|
|
417
|
-
const archivedMeta = {
|
|
418
|
-
...targetMeta,
|
|
419
|
-
archivedAt: nowIso()
|
|
420
|
-
};
|
|
421
|
-
await ensureRunMetadata(projectRoot, archivedMeta);
|
|
422
|
-
await ensureRunHandoff(projectRoot, targetRunId);
|
|
423
|
-
if (targetRunId !== state.activeRunId) {
|
|
424
|
-
const activeMeta = await readJsonFile(runMetaPath(projectRoot, state.activeRunId));
|
|
425
|
-
if (!activeMeta) {
|
|
426
|
-
throw new Error(`Active run "${state.activeRunId}" is missing metadata`);
|
|
427
|
-
}
|
|
428
|
-
return { archived: archivedMeta, active: activeMeta };
|
|
429
|
-
}
|
|
430
|
-
const nextRun = await createRun(projectRoot, {
|
|
431
|
-
title: "Post-archive run",
|
|
432
|
-
seedFromActiveArtifacts: false
|
|
433
|
-
});
|
|
434
|
-
const nextState = {
|
|
435
|
-
...createInitialFlowState(nextRun.id),
|
|
436
|
-
activeRunId: nextRun.id
|
|
206
|
+
const artifactsDir = activeArtifactsPath(projectRoot);
|
|
207
|
+
const runsDir = runsRoot(projectRoot);
|
|
208
|
+
await ensureDir(runsDir);
|
|
209
|
+
await ensureDir(artifactsDir);
|
|
210
|
+
const feature = (featureName?.trim() && featureName.trim().length > 0)
|
|
211
|
+
? featureName.trim()
|
|
212
|
+
: await inferFeatureNameFromArtifacts(projectRoot);
|
|
213
|
+
const archiveBaseId = `${toArchiveDate()}-${slugifyFeatureName(feature)}`;
|
|
214
|
+
const archiveId = await uniqueArchiveId(projectRoot, archiveBaseId);
|
|
215
|
+
const archivePath = path.join(runsDir, archiveId);
|
|
216
|
+
const archiveArtifactsPath = path.join(archivePath, "artifacts");
|
|
217
|
+
await ensureDir(archivePath);
|
|
218
|
+
await fs.rename(artifactsDir, archiveArtifactsPath);
|
|
219
|
+
await ensureDir(artifactsDir);
|
|
220
|
+
const resetState = createInitialFlowState();
|
|
221
|
+
await writeFlowState(projectRoot, resetState);
|
|
222
|
+
const archivedAt = new Date().toISOString();
|
|
223
|
+
return {
|
|
224
|
+
archiveId,
|
|
225
|
+
archivePath,
|
|
226
|
+
archivedAt,
|
|
227
|
+
featureName: feature,
|
|
228
|
+
resetState
|
|
437
229
|
};
|
|
438
|
-
await writeFlowState(projectRoot, nextState);
|
|
439
|
-
await persistRunStateSnapshot(projectRoot, nextRun.id, nextState);
|
|
440
|
-
await loadRunArtifactsToActive(projectRoot, nextRun.id);
|
|
441
|
-
await ensureRunHandoff(projectRoot, nextRun.id);
|
|
442
|
-
return { archived: archivedMeta, active: nextRun };
|
|
443
230
|
}
|
package/dist/trace-matrix.js
CHANGED
|
@@ -2,22 +2,13 @@ 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 { readFlowState } from "./runs.js";
|
|
6
5
|
function activeArtifactPath(projectRoot, name) {
|
|
7
6
|
return path.join(projectRoot, RUNTIME_ROOT, "artifacts", name);
|
|
8
7
|
}
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const runId = activeRunId.trim();
|
|
14
|
-
const candidates = runId.length > 0
|
|
15
|
-
? [canonicalRunArtifactPath(projectRoot, runId, name), activeArtifactPath(projectRoot, name)]
|
|
16
|
-
: [activeArtifactPath(projectRoot, name)];
|
|
17
|
-
for (const candidate of candidates) {
|
|
18
|
-
if (await exists(candidate)) {
|
|
19
|
-
return fs.readFile(candidate, "utf8");
|
|
20
|
-
}
|
|
8
|
+
async function readArtifact(projectRoot, name) {
|
|
9
|
+
const candidate = activeArtifactPath(projectRoot, name);
|
|
10
|
+
if (await exists(candidate)) {
|
|
11
|
+
return fs.readFile(candidate, "utf8");
|
|
21
12
|
}
|
|
22
13
|
return null;
|
|
23
14
|
}
|
|
@@ -120,11 +111,10 @@ function layer1LinesForCriterion(layer1, criterionId) {
|
|
|
120
111
|
return out;
|
|
121
112
|
}
|
|
122
113
|
export async function buildTraceMatrix(projectRoot) {
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
const review = await readArtifact(projectRoot, "07-review.md", activeRunId);
|
|
114
|
+
const spec = await readArtifact(projectRoot, "04-spec.md");
|
|
115
|
+
const plan = await readArtifact(projectRoot, "05-plan.md");
|
|
116
|
+
const tdd = await readArtifact(projectRoot, "06-tdd.md");
|
|
117
|
+
const review = await readArtifact(projectRoot, "07-review.md");
|
|
128
118
|
const criterionIds = spec ? parseAcceptanceCriterionIds(spec) : [];
|
|
129
119
|
const taskToAcs = plan ? parsePlanTaskAcLinks(plan) : new Map();
|
|
130
120
|
const allTaskIds = plan ? parsePlanTaskIds(plan) : [];
|
package/dist/types.d.ts
CHANGED
|
@@ -8,10 +8,6 @@ export interface VibyConfig {
|
|
|
8
8
|
harnesses: HarnessId[];
|
|
9
9
|
/** When true, stage skills instruct the agent to continue to the following stage after gates pass. */
|
|
10
10
|
autoAdvance?: boolean;
|
|
11
|
-
/** Merge project bootstrap learnings with a global learnings file. */
|
|
12
|
-
globalLearnings?: boolean;
|
|
13
|
-
/** Optional absolute or project-relative path to global learnings JSONL. */
|
|
14
|
-
globalLearningsPath?: string;
|
|
15
11
|
/** Prompt guard behavior for runtime write-risk detection hooks. */
|
|
16
12
|
promptGuardMode?: "advisory" | "strict";
|
|
17
13
|
/** When true, cclaw installs managed git pre-commit/pre-push wrappers. */
|
package/package.json
CHANGED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export interface ObservationRecord {
|
|
2
|
-
ts?: string;
|
|
3
|
-
event?: string;
|
|
4
|
-
tool?: string;
|
|
5
|
-
phase?: string;
|
|
6
|
-
stage?: string;
|
|
7
|
-
runId?: string;
|
|
8
|
-
data?: unknown;
|
|
9
|
-
}
|
|
10
|
-
export type LearningSource = "observed" | "user-stated" | "inferred";
|
|
11
|
-
export type LearningType = "pitfall" | "pattern" | "preference";
|
|
12
|
-
export interface LearningRecord {
|
|
13
|
-
ts: string;
|
|
14
|
-
skill: string;
|
|
15
|
-
type: LearningType;
|
|
16
|
-
key: string;
|
|
17
|
-
insight: string;
|
|
18
|
-
confidence: number;
|
|
19
|
-
source: LearningSource;
|
|
20
|
-
}
|
|
21
|
-
export interface SummarizeOutcome {
|
|
22
|
-
candidates: LearningRecord[];
|
|
23
|
-
appendable: LearningRecord[];
|
|
24
|
-
}
|
|
25
|
-
export declare function summarizeObservationLearnings(observationJsonl: string, existingLearningsJsonl: string, timestamp: string): SummarizeOutcome;
|