cclaw-cli 0.51.30 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -18
- package/dist/artifact-linter/brainstorm.d.ts +2 -0
- package/dist/artifact-linter/brainstorm.js +289 -0
- package/dist/artifact-linter/design.d.ts +2 -0
- package/dist/artifact-linter/design.js +354 -0
- package/dist/artifact-linter/plan.d.ts +2 -0
- package/dist/artifact-linter/plan.js +183 -0
- package/dist/artifact-linter/review-army.d.ts +24 -0
- package/dist/artifact-linter/review-army.js +365 -0
- package/dist/artifact-linter/review.d.ts +2 -0
- package/dist/artifact-linter/review.js +99 -0
- package/dist/artifact-linter/scope.d.ts +2 -0
- package/dist/artifact-linter/scope.js +125 -0
- package/dist/artifact-linter/shared.d.ts +247 -0
- package/dist/artifact-linter/shared.js +1517 -0
- package/dist/artifact-linter/ship.d.ts +2 -0
- package/dist/artifact-linter/ship.js +82 -0
- package/dist/artifact-linter/spec.d.ts +2 -0
- package/dist/artifact-linter/spec.js +130 -0
- package/dist/artifact-linter/tdd.d.ts +2 -0
- package/dist/artifact-linter/tdd.js +198 -0
- package/dist/artifact-linter.d.ts +4 -76
- package/dist/artifact-linter.js +56 -2949
- package/dist/cli.d.ts +1 -6
- package/dist/cli.js +4 -159
- package/dist/codex-feature-flag.d.ts +1 -1
- package/dist/codex-feature-flag.js +1 -1
- package/dist/config.d.ts +3 -2
- package/dist/config.js +67 -3
- package/dist/constants.d.ts +1 -7
- package/dist/constants.js +10 -15
- package/dist/content/cancel-command.js +2 -2
- package/dist/content/closeout-guidance.d.ts +1 -1
- package/dist/content/closeout-guidance.js +15 -13
- package/dist/content/core-agents.d.ts +46 -29
- package/dist/content/core-agents.js +216 -82
- package/dist/content/decision-protocol.d.ts +1 -1
- package/dist/content/decision-protocol.js +1 -1
- package/dist/content/diff-command.js +1 -1
- package/dist/content/examples.d.ts +0 -3
- package/dist/content/examples.js +197 -752
- package/dist/content/harness-doc.js +20 -2
- package/dist/content/hook-manifest.d.ts +2 -2
- package/dist/content/hook-manifest.js +2 -2
- package/dist/content/hooks.d.ts +1 -0
- package/dist/content/hooks.js +32 -137
- package/dist/content/idea.d.ts +60 -0
- package/dist/content/idea.js +404 -0
- package/dist/content/iron-laws.d.ts +0 -1
- package/dist/content/iron-laws.js +31 -16
- package/dist/content/learnings.d.ts +2 -4
- package/dist/content/learnings.js +11 -27
- package/dist/content/meta-skill.js +7 -7
- package/dist/content/node-hooks.d.ts +10 -0
- package/dist/content/node-hooks.js +163 -95
- package/dist/content/opencode-plugin.js +15 -29
- package/dist/content/reference-patterns.js +2 -2
- package/dist/content/runtime-shared-snippets.d.ts +8 -0
- package/dist/content/runtime-shared-snippets.js +80 -0
- package/dist/content/session-hooks.js +1 -1
- package/dist/content/skills.d.ts +1 -0
- package/dist/content/skills.js +69 -7
- package/dist/content/stage-schema.js +147 -61
- package/dist/content/stages/_lint-metadata/index.js +26 -2
- package/dist/content/stages/brainstorm.js +13 -7
- package/dist/content/stages/design.js +16 -11
- package/dist/content/stages/plan.js +7 -4
- package/dist/content/stages/review.js +12 -12
- package/dist/content/stages/schema-types.d.ts +2 -2
- package/dist/content/stages/scope.js +15 -12
- package/dist/content/stages/ship.js +3 -3
- package/dist/content/stages/spec.js +9 -3
- package/dist/content/stages/tdd.js +14 -4
- package/dist/content/start-command.js +11 -10
- package/dist/content/status-command.js +5 -5
- package/dist/content/subagent-context-skills.js +156 -1
- package/dist/content/subagents.d.ts +0 -5
- package/dist/content/subagents.js +65 -81
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +187 -154
- package/dist/content/tree-command.js +2 -2
- package/dist/content/utility-skills.d.ts +2 -2
- package/dist/content/utility-skills.js +28 -99
- package/dist/content/view-command.js +4 -2
- package/dist/delegation.d.ts +2 -0
- package/dist/delegation.js +2 -1
- package/dist/early-loop.d.ts +66 -0
- package/dist/early-loop.js +275 -0
- package/dist/flow-state.d.ts +5 -6
- package/dist/flow-state.js +4 -6
- package/dist/gate-evidence.d.ts +0 -23
- package/dist/gate-evidence.js +111 -153
- package/dist/harness-adapters.d.ts +2 -2
- package/dist/harness-adapters.js +48 -19
- package/dist/install.js +190 -32
- package/dist/internal/advance-stage/advance.d.ts +50 -0
- package/dist/internal/advance-stage/advance.js +479 -0
- package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
- package/dist/internal/advance-stage/cancel-run.js +19 -0
- package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
- package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
- package/dist/internal/advance-stage/helpers.d.ts +14 -0
- package/dist/internal/advance-stage/helpers.js +145 -0
- package/dist/internal/advance-stage/hook.d.ts +8 -0
- package/dist/internal/advance-stage/hook.js +40 -0
- package/dist/internal/advance-stage/parsers.d.ts +54 -0
- package/dist/internal/advance-stage/parsers.js +307 -0
- package/dist/internal/advance-stage/review-loop.d.ts +7 -0
- package/dist/internal/advance-stage/review-loop.js +161 -0
- package/dist/internal/advance-stage/rewind.d.ts +14 -0
- package/dist/internal/advance-stage/rewind.js +108 -0
- package/dist/internal/advance-stage/start-flow.d.ts +11 -0
- package/dist/internal/advance-stage/start-flow.js +136 -0
- package/dist/internal/advance-stage/verify.d.ts +29 -0
- package/dist/internal/advance-stage/verify.js +225 -0
- package/dist/internal/advance-stage.js +21 -1470
- package/dist/internal/compound-readiness.d.ts +1 -1
- package/dist/internal/compound-readiness.js +2 -2
- package/dist/internal/early-loop-status.d.ts +7 -0
- package/dist/internal/early-loop-status.js +90 -0
- package/dist/internal/runtime-integrity.d.ts +7 -0
- package/dist/internal/runtime-integrity.js +288 -0
- package/dist/internal/tdd-red-evidence.js +1 -1
- package/dist/knowledge-store.d.ts +5 -28
- package/dist/knowledge-store.js +57 -84
- package/dist/managed-resources.js +24 -2
- package/dist/policy.js +7 -9
- package/dist/retro-gate.js +8 -90
- package/dist/run-archive.d.ts +1 -1
- package/dist/run-archive.js +13 -16
- package/dist/run-persistence.js +20 -15
- package/dist/runtime/run-hook.entry.d.ts +3 -0
- package/dist/runtime/run-hook.entry.js +5 -0
- package/dist/runtime/run-hook.mjs +9477 -0
- package/dist/tdd-cycle.d.ts +3 -3
- package/dist/tdd-cycle.js +1 -1
- package/dist/types.d.ts +18 -10
- package/package.json +4 -2
- package/dist/content/hook-inline-snippets.d.ts +0 -83
- package/dist/content/hook-inline-snippets.js +0 -302
- package/dist/content/ideate-command.d.ts +0 -8
- package/dist/content/ideate-command.js +0 -315
- package/dist/content/ideate-frames.d.ts +0 -31
- package/dist/content/ideate-frames.js +0 -140
- package/dist/content/ideate-ranking.d.ts +0 -25
- package/dist/content/ideate-ranking.js +0 -65
- package/dist/content/next-command.d.ts +0 -20
- package/dist/content/next-command.js +0 -298
- package/dist/content/seed-shelf.d.ts +0 -36
- package/dist/content/seed-shelf.js +0 -301
- package/dist/content/stage-common-guidance.d.ts +0 -1
- package/dist/content/stage-common-guidance.js +0 -106
- package/dist/doctor-registry.d.ts +0 -10
- package/dist/doctor-registry.js +0 -186
- package/dist/doctor.d.ts +0 -17
- package/dist/doctor.js +0 -2201
- package/dist/internal/hook-manifest.d.ts +0 -16
- package/dist/internal/hook-manifest.js +0 -77
- package/dist/trace-matrix.d.ts +0 -27
- package/dist/trace-matrix.js +0 -226
package/dist/knowledge-store.js
CHANGED
|
@@ -32,9 +32,8 @@ export function effectiveCompoundThreshold(baseThreshold, archivedRunsCount) {
|
|
|
32
32
|
*
|
|
33
33
|
* Clustering key: `(type, normalizeText(trigger), normalizeText(action))`
|
|
34
34
|
* which mirrors the compound readiness clustering in runtime state.
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* as ready.
|
|
35
|
+
* The readiness surface intentionally stays simple: no maturity/supersede
|
|
36
|
+
* suppression logic in this pass.
|
|
38
37
|
*/
|
|
39
38
|
export function computeCompoundReadiness(entries, options = {}) {
|
|
40
39
|
const thresholdRaw = options.threshold ?? DEFAULT_COMPOUND_RECURRENCE_THRESHOLD;
|
|
@@ -54,8 +53,6 @@ export function computeCompoundReadiness(entries, options = {}) {
|
|
|
54
53
|
const { threshold, relaxationApplied } = effectiveCompoundThreshold(baseThreshold, archivedRunsCount);
|
|
55
54
|
const buckets = new Map();
|
|
56
55
|
for (const entry of entries) {
|
|
57
|
-
if (entry.maturity === "lifted-to-enforcement" || entry.superseded_by !== undefined)
|
|
58
|
-
continue;
|
|
59
56
|
const key = [
|
|
60
57
|
entry.type,
|
|
61
58
|
normalizeText(entry.trigger),
|
|
@@ -71,15 +68,13 @@ export function computeCompoundReadiness(entries, options = {}) {
|
|
|
71
68
|
entryCount: 1,
|
|
72
69
|
severity: entry.severity,
|
|
73
70
|
lastSeenTs: entry.last_seen_ts,
|
|
74
|
-
types: new Set([entry.type])
|
|
75
|
-
maturity: new Set([entry.maturity])
|
|
71
|
+
types: new Set([entry.type])
|
|
76
72
|
});
|
|
77
73
|
continue;
|
|
78
74
|
}
|
|
79
75
|
bucket.recurrence += frequency;
|
|
80
76
|
bucket.entryCount += 1;
|
|
81
77
|
bucket.types.add(entry.type);
|
|
82
|
-
bucket.maturity.add(entry.maturity);
|
|
83
78
|
if (entry.severity === "critical") {
|
|
84
79
|
bucket.severity = "critical";
|
|
85
80
|
}
|
|
@@ -104,8 +99,7 @@ export function computeCompoundReadiness(entries, options = {}) {
|
|
|
104
99
|
qualification: criticalOverride && !meetsRecurrence ? "critical_override" : "recurrence",
|
|
105
100
|
...(bucket.severity ? { severity: bucket.severity } : {}),
|
|
106
101
|
lastSeenTs: bucket.lastSeenTs,
|
|
107
|
-
types: Array.from(bucket.types).sort()
|
|
108
|
-
maturity: Array.from(bucket.maturity).sort()
|
|
102
|
+
types: Array.from(bucket.types).sort()
|
|
109
103
|
});
|
|
110
104
|
}
|
|
111
105
|
ready.sort((a, b) => {
|
|
@@ -143,13 +137,13 @@ export function computeCompoundReadiness(entries, options = {}) {
|
|
|
143
137
|
const KNOWLEDGE_TYPE_SET = new Set(["rule", "pattern", "lesson", "compound"]);
|
|
144
138
|
const KNOWLEDGE_CONFIDENCE_SET = new Set(["high", "medium", "low"]);
|
|
145
139
|
const KNOWLEDGE_SEVERITY_SET = new Set(["critical", "important", "suggestion"]);
|
|
146
|
-
const
|
|
147
|
-
const
|
|
140
|
+
const LEGACY_KNOWLEDGE_UNIVERSALITY_SET = new Set(["project", "personal", "universal"]);
|
|
141
|
+
const LEGACY_KNOWLEDGE_MATURITY_SET = new Set(["raw", "lifted-to-rule", "lifted-to-enforcement"]);
|
|
148
142
|
const KNOWLEDGE_SOURCE_SET = new Set([
|
|
149
143
|
"stage",
|
|
150
144
|
"retro",
|
|
151
145
|
"compound",
|
|
152
|
-
"
|
|
146
|
+
"idea",
|
|
153
147
|
"manual"
|
|
154
148
|
]);
|
|
155
149
|
const FLOW_STAGE_SET = new Set(FLOW_STAGES);
|
|
@@ -158,25 +152,49 @@ const KNOWLEDGE_REQUIRED_KEYS = [
|
|
|
158
152
|
"trigger",
|
|
159
153
|
"action",
|
|
160
154
|
"confidence",
|
|
161
|
-
"domain",
|
|
162
155
|
"stage",
|
|
163
156
|
"origin_stage",
|
|
164
|
-
"origin_run",
|
|
165
157
|
"frequency",
|
|
166
|
-
"universality",
|
|
167
|
-
"maturity",
|
|
168
158
|
"created",
|
|
169
159
|
"first_seen_ts",
|
|
170
160
|
"last_seen_ts",
|
|
171
161
|
"project"
|
|
172
162
|
];
|
|
173
163
|
const KNOWLEDGE_ALLOWED_KEYS = new Set(KNOWLEDGE_REQUIRED_KEYS);
|
|
164
|
+
// Legacy keys are accepted for backwards compatibility when reading historical
|
|
165
|
+
// knowledge entries, but no longer required for newly materialized rows.
|
|
166
|
+
KNOWLEDGE_ALLOWED_KEYS.add("domain");
|
|
167
|
+
KNOWLEDGE_ALLOWED_KEYS.add("origin_run");
|
|
168
|
+
KNOWLEDGE_ALLOWED_KEYS.add("universality");
|
|
169
|
+
KNOWLEDGE_ALLOWED_KEYS.add("maturity");
|
|
174
170
|
KNOWLEDGE_ALLOWED_KEYS.add("source");
|
|
175
171
|
KNOWLEDGE_ALLOWED_KEYS.add("severity");
|
|
176
172
|
KNOWLEDGE_ALLOWED_KEYS.add("supersedes");
|
|
177
173
|
KNOWLEDGE_ALLOWED_KEYS.add("superseded_by");
|
|
178
|
-
function keyAllowedInKnowledgeEntry(key
|
|
179
|
-
return KNOWLEDGE_ALLOWED_KEYS.has(key)
|
|
174
|
+
function keyAllowedInKnowledgeEntry(key) {
|
|
175
|
+
return KNOWLEDGE_ALLOWED_KEYS.has(key);
|
|
176
|
+
}
|
|
177
|
+
function normalizeParsedKnowledgeEntry(obj) {
|
|
178
|
+
const normalized = {
|
|
179
|
+
type: obj.type,
|
|
180
|
+
trigger: obj.trigger,
|
|
181
|
+
action: obj.action,
|
|
182
|
+
confidence: obj.confidence,
|
|
183
|
+
stage: obj.stage,
|
|
184
|
+
origin_stage: obj.origin_stage,
|
|
185
|
+
frequency: obj.frequency,
|
|
186
|
+
created: obj.created,
|
|
187
|
+
first_seen_ts: obj.first_seen_ts,
|
|
188
|
+
last_seen_ts: obj.last_seen_ts,
|
|
189
|
+
project: obj.project
|
|
190
|
+
};
|
|
191
|
+
if (obj.severity !== undefined) {
|
|
192
|
+
normalized.severity = obj.severity;
|
|
193
|
+
}
|
|
194
|
+
if (obj.source !== undefined) {
|
|
195
|
+
normalized.source = obj.source;
|
|
196
|
+
}
|
|
197
|
+
return normalized;
|
|
180
198
|
}
|
|
181
199
|
function knowledgePath(projectRoot) {
|
|
182
200
|
return path.join(projectRoot, RUNTIME_ROOT, "knowledge.jsonl");
|
|
@@ -198,16 +216,11 @@ function dedupeKey(entry) {
|
|
|
198
216
|
entry.type,
|
|
199
217
|
normalizeText(entry.trigger),
|
|
200
218
|
normalizeText(entry.action),
|
|
201
|
-
entry.domain === null ? "null" : normalizeText(entry.domain),
|
|
202
219
|
entry.stage ?? "null",
|
|
203
220
|
entry.origin_stage ?? "null",
|
|
204
|
-
entry.origin_run === null ? "null" : normalizeText(entry.origin_run),
|
|
205
|
-
entry.universality,
|
|
206
221
|
entry.project === null ? "null" : normalizeText(entry.project),
|
|
207
222
|
entry.source === undefined || entry.source === null ? "null" : entry.source,
|
|
208
|
-
entry.severity === undefined ? "none" : entry.severity
|
|
209
|
-
Array.isArray(entry.supersedes) ? entry.supersedes.map(normalizeText).sort().join(",") : "none",
|
|
210
|
-
entry.superseded_by === undefined ? "none" : normalizeText(entry.superseded_by)
|
|
223
|
+
entry.severity === undefined ? "none" : entry.severity
|
|
211
224
|
].join("|");
|
|
212
225
|
}
|
|
213
226
|
function emptyKnowledgeSnapshot() {
|
|
@@ -219,13 +232,6 @@ function emptyKnowledgeSnapshot() {
|
|
|
219
232
|
entryByIndex: new Map()
|
|
220
233
|
};
|
|
221
234
|
}
|
|
222
|
-
function normalizeLegacyKnowledgeEntry(entry) {
|
|
223
|
-
const { origin_feature: legacyOriginRun, ...rest } = entry;
|
|
224
|
-
return {
|
|
225
|
-
...rest,
|
|
226
|
-
origin_run: entry.origin_run ?? legacyOriginRun ?? null
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
235
|
function parseKnowledgeSnapshot(raw) {
|
|
230
236
|
const lines = stripBom(raw).split(/\r?\n/u);
|
|
231
237
|
const entries = [];
|
|
@@ -238,12 +244,12 @@ function parseKnowledgeSnapshot(raw) {
|
|
|
238
244
|
continue;
|
|
239
245
|
try {
|
|
240
246
|
const parsed = JSON.parse(trimmed);
|
|
241
|
-
const validated = validateKnowledgeEntry(parsed
|
|
247
|
+
const validated = validateKnowledgeEntry(parsed);
|
|
242
248
|
if (!validated.ok) {
|
|
243
249
|
malformedLines += 1;
|
|
244
250
|
continue;
|
|
245
251
|
}
|
|
246
|
-
const entry =
|
|
252
|
+
const entry = normalizeParsedKnowledgeEntry(parsed);
|
|
247
253
|
entries.push(entry);
|
|
248
254
|
const key = dedupeKey(entry);
|
|
249
255
|
if (!keyToIndex.has(key)) {
|
|
@@ -296,24 +302,20 @@ function isNullableString(value) {
|
|
|
296
302
|
function isNullableStage(value) {
|
|
297
303
|
return value === null || (typeof value === "string" && FLOW_STAGE_SET.has(value));
|
|
298
304
|
}
|
|
299
|
-
export function validateKnowledgeEntry(entry
|
|
305
|
+
export function validateKnowledgeEntry(entry) {
|
|
300
306
|
const errors = [];
|
|
301
307
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
302
308
|
return { ok: false, errors: ["Knowledge entry must be a JSON object."] };
|
|
303
309
|
}
|
|
304
310
|
const obj = entry;
|
|
305
311
|
for (const key of Object.keys(obj)) {
|
|
306
|
-
if (!keyAllowedInKnowledgeEntry(key
|
|
312
|
+
if (!keyAllowedInKnowledgeEntry(key)) {
|
|
307
313
|
errors.push(`Unknown key "${key}" in knowledge entry.`);
|
|
308
314
|
}
|
|
309
315
|
}
|
|
310
316
|
for (const key of KNOWLEDGE_REQUIRED_KEYS) {
|
|
311
317
|
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
312
|
-
|
|
313
|
-
options.allowLegacyOriginFeature !== true ||
|
|
314
|
-
!Object.prototype.hasOwnProperty.call(obj, "origin_feature")) {
|
|
315
|
-
errors.push(`Missing required key "${key}".`);
|
|
316
|
-
}
|
|
318
|
+
errors.push(`Missing required key "${key}".`);
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
321
|
if (!KNOWLEDGE_TYPE_SET.has(obj.type)) {
|
|
@@ -332,7 +334,7 @@ export function validateKnowledgeEntry(entry, options = {}) {
|
|
|
332
334
|
(typeof obj.severity !== "string" || !KNOWLEDGE_SEVERITY_SET.has(obj.severity))) {
|
|
333
335
|
errors.push("severity must be one of: critical, important, suggestion.");
|
|
334
336
|
}
|
|
335
|
-
if (!isNullableString(obj.domain)) {
|
|
337
|
+
if (obj.domain !== undefined && !isNullableString(obj.domain)) {
|
|
336
338
|
errors.push("domain must be string or null.");
|
|
337
339
|
}
|
|
338
340
|
if (!isNullableStage(obj.stage)) {
|
|
@@ -341,12 +343,8 @@ export function validateKnowledgeEntry(entry, options = {}) {
|
|
|
341
343
|
if (!isNullableStage(obj.origin_stage)) {
|
|
342
344
|
errors.push(`origin_stage must be one of ${FLOW_STAGES.join(", ")} or null.`);
|
|
343
345
|
}
|
|
344
|
-
const originRun =
|
|
345
|
-
|
|
346
|
-
: options.allowLegacyOriginFeature === true
|
|
347
|
-
? obj.origin_feature
|
|
348
|
-
: undefined;
|
|
349
|
-
if (!isNullableString(originRun)) {
|
|
346
|
+
const originRun = obj.origin_run;
|
|
347
|
+
if (originRun !== undefined && !isNullableString(originRun)) {
|
|
350
348
|
errors.push("origin_run must be string or null.");
|
|
351
349
|
}
|
|
352
350
|
if (typeof obj.frequency !== "number" ||
|
|
@@ -354,10 +352,12 @@ export function validateKnowledgeEntry(entry, options = {}) {
|
|
|
354
352
|
obj.frequency < 1) {
|
|
355
353
|
errors.push("frequency must be an integer >= 1.");
|
|
356
354
|
}
|
|
357
|
-
if (
|
|
355
|
+
if (obj.universality !== undefined &&
|
|
356
|
+
(typeof obj.universality !== "string" || !LEGACY_KNOWLEDGE_UNIVERSALITY_SET.has(obj.universality))) {
|
|
358
357
|
errors.push("universality must be one of: project, personal, universal.");
|
|
359
358
|
}
|
|
360
|
-
if (
|
|
359
|
+
if (obj.maturity !== undefined &&
|
|
360
|
+
(typeof obj.maturity !== "string" || !LEGACY_KNOWLEDGE_MATURITY_SET.has(obj.maturity))) {
|
|
361
361
|
errors.push("maturity must be one of: raw, lifted-to-rule, lifted-to-enforcement.");
|
|
362
362
|
}
|
|
363
363
|
for (const timestampField of ["created", "first_seen_ts", "last_seen_ts"]) {
|
|
@@ -380,10 +380,12 @@ export function validateKnowledgeEntry(entry, options = {}) {
|
|
|
380
380
|
(typeof obj.superseded_by !== "string" || obj.superseded_by.trim().length === 0)) {
|
|
381
381
|
errors.push("superseded_by must be a non-empty string when present.");
|
|
382
382
|
}
|
|
383
|
-
|
|
384
|
-
obj.source
|
|
385
|
-
(typeof obj.source
|
|
386
|
-
|
|
383
|
+
const sourceAllowed = obj.source === undefined ||
|
|
384
|
+
obj.source === null ||
|
|
385
|
+
(typeof obj.source === "string" &&
|
|
386
|
+
KNOWLEDGE_SOURCE_SET.has(obj.source));
|
|
387
|
+
if (!sourceAllowed) {
|
|
388
|
+
errors.push("source must be one of: stage, retro, compound, idea, manual, or null.");
|
|
387
389
|
}
|
|
388
390
|
return { ok: errors.length === 0, errors };
|
|
389
391
|
}
|
|
@@ -391,20 +393,15 @@ export function materializeKnowledgeEntry(seed, defaults = {}) {
|
|
|
391
393
|
const now = normalizeUtcIso(defaults.nowIso ?? nowUtcIso());
|
|
392
394
|
const stage = seed.stage ?? defaults.stage ?? null;
|
|
393
395
|
const originStage = seed.origin_stage ?? defaults.originStage ?? stage ?? null;
|
|
394
|
-
const originRun = seed.origin_run ?? seed.origin_feature ?? defaults.originRun ?? null;
|
|
395
396
|
const source = seed.source ?? defaults.source ?? null;
|
|
396
397
|
const entry = {
|
|
397
398
|
type: seed.type,
|
|
398
399
|
trigger: seed.trigger.trim(),
|
|
399
400
|
action: seed.action.trim(),
|
|
400
401
|
confidence: seed.confidence,
|
|
401
|
-
domain: seed.domain ?? null,
|
|
402
402
|
stage,
|
|
403
403
|
origin_stage: originStage,
|
|
404
|
-
origin_run: originRun,
|
|
405
404
|
frequency: seed.frequency ?? 1,
|
|
406
|
-
universality: seed.universality ?? "project",
|
|
407
|
-
maturity: seed.maturity ?? "raw",
|
|
408
405
|
created: normalizeUtcIso(seed.created ?? now),
|
|
409
406
|
first_seen_ts: normalizeUtcIso(seed.first_seen_ts ?? now),
|
|
410
407
|
last_seen_ts: normalizeUtcIso(seed.last_seen_ts ?? now),
|
|
@@ -413,12 +410,6 @@ export function materializeKnowledgeEntry(seed, defaults = {}) {
|
|
|
413
410
|
if (seed.severity !== undefined) {
|
|
414
411
|
entry.severity = seed.severity;
|
|
415
412
|
}
|
|
416
|
-
if (seed.supersedes !== undefined) {
|
|
417
|
-
entry.supersedes = seed.supersedes.map((value) => value.trim());
|
|
418
|
-
}
|
|
419
|
-
if (seed.superseded_by !== undefined) {
|
|
420
|
-
entry.superseded_by = seed.superseded_by.trim();
|
|
421
|
-
}
|
|
422
413
|
if (source !== null) {
|
|
423
414
|
entry.source = source;
|
|
424
415
|
}
|
|
@@ -527,18 +518,6 @@ function tokenizeText(value) {
|
|
|
527
518
|
function uniqueTokens(values) {
|
|
528
519
|
return [...new Set(values)];
|
|
529
520
|
}
|
|
530
|
-
function supersededTriggerSet(entries) {
|
|
531
|
-
const superseded = new Set();
|
|
532
|
-
for (const entry of entries) {
|
|
533
|
-
for (const trigger of entry.supersedes ?? []) {
|
|
534
|
-
superseded.add(normalizeText(trigger));
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
return superseded;
|
|
538
|
-
}
|
|
539
|
-
function isSupersededLearning(entry, supersededTriggers) {
|
|
540
|
-
return entry.superseded_by !== undefined || supersededTriggers.has(normalizeText(entry.trigger));
|
|
541
|
-
}
|
|
542
521
|
function pathTokens(paths) {
|
|
543
522
|
if (!Array.isArray(paths) || paths.length === 0)
|
|
544
523
|
return [];
|
|
@@ -560,9 +539,7 @@ export async function selectRelevantLearnings(projectRoot, options = {}) {
|
|
|
560
539
|
const limit = typeof options.limit === "number" && Number.isFinite(options.limit) && options.limit > 0
|
|
561
540
|
? Math.floor(options.limit)
|
|
562
541
|
: 8;
|
|
563
|
-
const
|
|
564
|
-
const activeEntries = entries.filter((entry) => !isSupersededLearning(entry, staleTriggers));
|
|
565
|
-
const ranked = activeEntries.map((entry, index) => {
|
|
542
|
+
const ranked = entries.map((entry, index) => {
|
|
566
543
|
let score = 0;
|
|
567
544
|
let stageScore = 0;
|
|
568
545
|
if (stage) {
|
|
@@ -580,13 +557,9 @@ export async function selectRelevantLearnings(projectRoot, options = {}) {
|
|
|
580
557
|
score += 1;
|
|
581
558
|
if (entry.frequency >= 3)
|
|
582
559
|
score += 1;
|
|
583
|
-
if (entry.maturity === "lifted-to-enforcement")
|
|
584
|
-
score -= 1;
|
|
585
560
|
const searchable = [
|
|
586
|
-
...tokenizeText(entry.domain),
|
|
587
561
|
...tokenizeText(entry.trigger),
|
|
588
562
|
...tokenizeText(entry.action),
|
|
589
|
-
...tokenizeText(entry.origin_run),
|
|
590
563
|
...tokenizeText(entry.project)
|
|
591
564
|
];
|
|
592
565
|
const searchSet = new Set(searchable);
|
|
@@ -60,7 +60,7 @@ export function isManagedGeneratedPath(relPath) {
|
|
|
60
60
|
return false;
|
|
61
61
|
if (relPath.startsWith(`${RUNTIME_ROOT}/artifacts/`))
|
|
62
62
|
return false;
|
|
63
|
-
if (relPath.startsWith(`${RUNTIME_ROOT}/
|
|
63
|
+
if (relPath.startsWith(`${RUNTIME_ROOT}/archive/`))
|
|
64
64
|
return false;
|
|
65
65
|
if (relPath === `${RUNTIME_ROOT}/state/flow-state.json`)
|
|
66
66
|
return false;
|
|
@@ -209,7 +209,29 @@ export class ManagedResourceSession {
|
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
static async create(options) {
|
|
212
|
-
const
|
|
212
|
+
const manifestPath = path.join(options.projectRoot, MANAGED_RESOURCE_MANIFEST_REL_PATH);
|
|
213
|
+
let previous = null;
|
|
214
|
+
if (await exists(manifestPath)) {
|
|
215
|
+
let raw;
|
|
216
|
+
try {
|
|
217
|
+
raw = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
throw new Error(`[sync fail-fast] Managed resource manifest is corrupt JSON (${MANAGED_RESOURCE_MANIFEST_REL_PATH}): ${error instanceof Error ? error.message : String(error)}. Fix/remove the manifest and rerun \`npx cclaw-cli sync\`.`);
|
|
221
|
+
}
|
|
222
|
+
const issues = validateManagedResourceManifest(raw);
|
|
223
|
+
if (issues.length > 0) {
|
|
224
|
+
const detail = issues.slice(0, 12)
|
|
225
|
+
.map((issue) => {
|
|
226
|
+
const scope = issue.path ?? (issue.index !== undefined ? `resources[${issue.index}]` : "manifest");
|
|
227
|
+
return `${scope}.${issue.field}: ${issue.message}`;
|
|
228
|
+
})
|
|
229
|
+
.join("; ");
|
|
230
|
+
throw new Error(`[sync fail-fast] Managed resource manifest is malformed (${MANAGED_RESOURCE_MANIFEST_REL_PATH}): ${detail}. ` +
|
|
231
|
+
`Fix/remove the manifest and rerun \`npx cclaw-cli sync\`.`);
|
|
232
|
+
}
|
|
233
|
+
previous = await readManagedResourceManifest(options.projectRoot).catch(() => null);
|
|
234
|
+
}
|
|
213
235
|
return new ManagedResourceSession(options, previous);
|
|
214
236
|
}
|
|
215
237
|
shouldManage(filePath) {
|
package/dist/policy.js
CHANGED
|
@@ -58,22 +58,20 @@ export async function policyChecks(projectRoot, options = {}) {
|
|
|
58
58
|
const utilitySkillChecks = [
|
|
59
59
|
{ file: runtimeFile("skills/learnings/SKILL.md"), needle: "strict JSONL schema", name: "utility_skill:learnings:jsonl_schema" },
|
|
60
60
|
{ file: runtimeFile("skills/learnings/SKILL.md"), needle: "knowledge.jsonl", name: "utility_skill:learnings:jsonl_store" },
|
|
61
|
-
{ file: runtimeFile("skills/learnings/SKILL.md"), needle: "type, trigger, action, confidence,
|
|
61
|
+
{ file: runtimeFile("skills/learnings/SKILL.md"), needle: "type, trigger, action, confidence, stage, origin_stage, frequency, created, first_seen_ts, last_seen_ts, project", name: "utility_skill:learnings:field_order" },
|
|
62
62
|
{ file: runtimeFile("skills/learnings/SKILL.md"), needle: "## Manual Actions", name: "utility_skill:learnings:manual_actions" },
|
|
63
63
|
{ file: runtimeFile("skills/learnings/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:learnings:hard_gate" },
|
|
64
64
|
{ file: runtimeFile("commands/start.md"), needle: "## Algorithm", name: "utility_command:start:algorithm" },
|
|
65
|
-
{ file: runtimeFile("commands/
|
|
66
|
-
{ file: runtimeFile("
|
|
67
|
-
{ file: runtimeFile("
|
|
68
|
-
{ file: runtimeFile("skills/flow-ideate/SKILL.md"), needle: "## Protocol", name: "utility_skill:ideate:protocol" },
|
|
69
|
-
{ file: runtimeFile("skills/flow-ideate/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:ideate:hard_gate" },
|
|
65
|
+
{ file: runtimeFile("commands/idea.md"), needle: "## Algorithm", name: "utility_command:idea:algorithm" },
|
|
66
|
+
{ file: runtimeFile("skills/flow-idea/SKILL.md"), needle: "## Protocol", name: "utility_skill:idea:protocol" },
|
|
67
|
+
{ file: runtimeFile("skills/flow-idea/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:idea:hard_gate" },
|
|
70
68
|
{ file: runtimeFile("commands/view.md"), needle: "## Routing", name: "utility_command:view:routing" },
|
|
71
69
|
{ file: runtimeFile("skills/flow-view/SKILL.md"), needle: "## Status Subcommand", name: "utility_skill:view:status_section" },
|
|
72
70
|
{ file: runtimeFile("skills/flow-view/SKILL.md"), needle: "## Tree Subcommand", name: "utility_skill:view:tree_section" },
|
|
73
71
|
{ file: runtimeFile("skills/flow-view/SKILL.md"), needle: "## Diff Subcommand", name: "utility_skill:view:diff_section" },
|
|
74
72
|
{ file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:sdd:hard_gate" },
|
|
75
73
|
{ file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "## Status Contract", name: "utility_skill:sdd:status_contract" },
|
|
76
|
-
{ file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "
|
|
74
|
+
{ file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "slice-implementer", name: "utility_skill:sdd:implementer_template" },
|
|
77
75
|
{ file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "## Model & Harness Routing Notes", name: "utility_skill:sdd:routing_notes" },
|
|
78
76
|
{ file: runtimeFile("skills/parallel-dispatch/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:parallel:hard_gate" },
|
|
79
77
|
{ file: runtimeFile("skills/parallel-dispatch/SKILL.md"), needle: "Review Army", name: "utility_skill:parallel:review_army" },
|
|
@@ -91,12 +89,12 @@ export async function policyChecks(projectRoot, options = {}) {
|
|
|
91
89
|
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Task classification", name: "meta_skill:task_classification" },
|
|
92
90
|
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Stage quick map", name: "meta_skill:stage_quick_map" },
|
|
93
91
|
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Whole flow map", name: "meta_skill:whole_flow_map" },
|
|
94
|
-
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "
|
|
92
|
+
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "post_ship_review -> archive", name: "meta_skill:closeout_chain" },
|
|
95
93
|
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Contextual Skill Activation", name: "meta_skill:contextual_skills" },
|
|
96
94
|
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Protocol Behavior", name: "meta_skill:protocol_behavior" },
|
|
97
95
|
{ file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Failure guardrails", name: "meta_skill:failure_guardrails" },
|
|
98
96
|
{ file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Resume Protocol", name: "utility_skill:session:resume" },
|
|
99
|
-
{ file: runtimeFile("skills/
|
|
97
|
+
{ file: runtimeFile("skills/brainstorm/SKILL.md"), needle: "## Shared Stage Guidance", name: "stage_skill:shared_guidance_inline" },
|
|
100
98
|
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "activeRunId", name: "hooks:session_start:active_run" },
|
|
101
99
|
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "write_to_cclaw_runtime", name: "hooks:guard:risky_write_advisory" },
|
|
102
100
|
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "stage_invocation_without_recent_flow_read", name: "hooks:workflow_guard:flow_read_reason" },
|
package/dist/retro-gate.js
CHANGED
|
@@ -1,34 +1,19 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { RUNTIME_ROOT } from "./constants.js";
|
|
4
|
-
import { exists
|
|
5
|
-
import { readKnowledgeSafely } from "./knowledge-store.js";
|
|
4
|
+
import { exists } from "./fs-utils.js";
|
|
6
5
|
function activeArtifactsPath(projectRoot) {
|
|
7
6
|
return path.join(projectRoot, RUNTIME_ROOT, "artifacts");
|
|
8
7
|
}
|
|
9
8
|
function retroArtifactPath(projectRoot) {
|
|
10
9
|
return path.join(activeArtifactsPath(projectRoot), "09-retro.md");
|
|
11
10
|
}
|
|
12
|
-
// Fallback window for compound-entry scanning when `retroDraftedAt` /
|
|
13
|
-
// `retroAcceptedAt` are not set (legacy runs or imports): use the retro
|
|
14
|
-
// artifact's mtime ± 7 days. 24h was too narrow for long-running retros
|
|
15
|
-
// that are edited over several days or runs imported from another
|
|
16
|
-
// machine with slightly different clocks; 7 days is still tight enough
|
|
17
|
-
// that entries from an unrelated future run are excluded.
|
|
18
|
-
const RETRO_ARTIFACT_MTIME_FALLBACK_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
|
|
19
11
|
function parseIsoTimestamp(value) {
|
|
20
12
|
if (!value || value.trim().length === 0)
|
|
21
13
|
return null;
|
|
22
14
|
const parsed = Date.parse(value);
|
|
23
15
|
return Number.isFinite(parsed) ? parsed : null;
|
|
24
16
|
}
|
|
25
|
-
function inInclusiveWindow(timestamp, windowStartMs, windowEndMs) {
|
|
26
|
-
if (windowStartMs !== null && timestamp < windowStartMs)
|
|
27
|
-
return false;
|
|
28
|
-
if (windowEndMs !== null && timestamp > windowEndMs)
|
|
29
|
-
return false;
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
17
|
export async function evaluateRetroGate(projectRoot, state) {
|
|
33
18
|
const required = state.completedStages.includes("ship");
|
|
34
19
|
const artifactFile = retroArtifactPath(projectRoot);
|
|
@@ -42,83 +27,16 @@ export async function evaluateRetroGate(projectRoot, state) {
|
|
|
42
27
|
hasRetroArtifact = false;
|
|
43
28
|
}
|
|
44
29
|
}
|
|
45
|
-
let compoundEntries = 0;
|
|
46
|
-
let windowStartMs = parseIsoTimestamp(state.closeout.retroDraftedAt);
|
|
47
|
-
let windowEndMs = parseIsoTimestamp(state.closeout.retroAcceptedAt) ?? parseIsoTimestamp(state.retro.completedAt);
|
|
48
|
-
if (hasRetroArtifact &&
|
|
49
|
-
windowStartMs === null &&
|
|
50
|
-
windowEndMs === null) {
|
|
51
|
-
try {
|
|
52
|
-
const stats = await fs.stat(artifactFile);
|
|
53
|
-
const anchor = stats.mtimeMs;
|
|
54
|
-
if (Number.isFinite(anchor) && anchor > 0) {
|
|
55
|
-
windowStartMs = anchor - RETRO_ARTIFACT_MTIME_FALLBACK_WINDOW_MS;
|
|
56
|
-
windowEndMs = anchor + RETRO_ARTIFACT_MTIME_FALLBACK_WINDOW_MS;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
// fallback scan remains disabled when mtime cannot be read
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const shouldScanCompoundEvidence = windowStartMs !== null || windowEndMs !== null;
|
|
64
|
-
if (shouldScanCompoundEvidence) {
|
|
65
|
-
const countIfEligible = (parsed) => {
|
|
66
|
-
if (parsed.type !== "compound") {
|
|
67
|
-
return 0;
|
|
68
|
-
}
|
|
69
|
-
const created = typeof parsed.created === "string" ? parseIsoTimestamp(parsed.created) : null;
|
|
70
|
-
if (created === null || !inInclusiveWindow(created, windowStartMs, windowEndMs)) {
|
|
71
|
-
return 0;
|
|
72
|
-
}
|
|
73
|
-
const source = typeof parsed.source === "string"
|
|
74
|
-
? parsed.source.trim().toLowerCase()
|
|
75
|
-
: null;
|
|
76
|
-
const legacyRetroStage = parsed.stage === "retro";
|
|
77
|
-
return source === "retro" || legacyRetroStage ? 1 : 0;
|
|
78
|
-
};
|
|
79
|
-
try {
|
|
80
|
-
const knowledgeFile = path.join(projectRoot, RUNTIME_ROOT, "knowledge.jsonl");
|
|
81
|
-
const { entries } = await readKnowledgeSafely(projectRoot);
|
|
82
|
-
for (const parsed of entries) {
|
|
83
|
-
compoundEntries += countIfEligible(parsed);
|
|
84
|
-
}
|
|
85
|
-
// Backward compatibility for historical/hand-edited rows that don't pass
|
|
86
|
-
// strict knowledge schema validation but still carry retro evidence.
|
|
87
|
-
if (compoundEntries === 0 && (await exists(knowledgeFile))) {
|
|
88
|
-
const raw = stripBom(await fs.readFile(knowledgeFile, "utf8"));
|
|
89
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
90
|
-
const trimmed = line.trim();
|
|
91
|
-
if (!trimmed)
|
|
92
|
-
continue;
|
|
93
|
-
try {
|
|
94
|
-
const parsed = JSON.parse(trimmed);
|
|
95
|
-
compoundEntries += countIfEligible(parsed);
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
// ignore malformed lines for retro gate calculation
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
compoundEntries = 0;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
// A retro is considered complete when any of:
|
|
108
|
-
// - the retro artifact exists AND (at least one compound learning was
|
|
109
|
-
// promoted during the retro window OR compound was explicitly skipped
|
|
110
|
-
// after reviewing the draft), or
|
|
111
|
-
// - the operator explicitly skipped the retro step itself
|
|
112
|
-
// (`retroSkipped === true` with a non-empty reason). `retroSkipped` is an
|
|
113
|
-
// operator-level override of the artifact requirement, so it must
|
|
114
|
-
// bypass `hasRetroArtifact` — otherwise a run that legitimately had
|
|
115
|
-
// nothing worth retro-ing dead-locks at closeout waiting for a
|
|
116
|
-
// file that will never exist.
|
|
117
30
|
const retroSkipReason = state.closeout.retroSkipReason?.trim() ?? "";
|
|
118
31
|
const retroSkipped = state.closeout.retroSkipped === true && retroSkipReason.length > 0;
|
|
32
|
+
const retroAccepted = hasRetroArtifact && parseIsoTimestamp(state.closeout.retroAcceptedAt) !== null;
|
|
119
33
|
const compoundSkipped = state.closeout.compoundSkipped === true;
|
|
120
|
-
const
|
|
121
|
-
|
|
34
|
+
const compoundReviewed = parseIsoTimestamp(state.closeout.compoundCompletedAt) !== null ||
|
|
35
|
+
state.closeout.compoundPromoted > 0;
|
|
36
|
+
const compoundEntries = compoundReviewed ? Math.max(0, Math.floor(state.closeout.compoundPromoted)) : 0;
|
|
37
|
+
// Keep retro-gate deterministic from closeout state only:
|
|
38
|
+
// retroComplete = (retroAccepted || retroSkipped) && (compoundReviewed || compoundSkipped)
|
|
39
|
+
const completed = required ? (retroAccepted || retroSkipped) && (compoundReviewed || compoundSkipped) : true;
|
|
122
40
|
return {
|
|
123
41
|
required,
|
|
124
42
|
completed,
|
package/dist/run-archive.d.ts
CHANGED
|
@@ -56,6 +56,6 @@ export declare function archiveRun(projectRoot: string, runName?: string, option
|
|
|
56
56
|
* Counts entries in the canonical JSONL knowledge store. An "active" entry is one
|
|
57
57
|
* non-empty line that parses as JSON with the required `type` field belonging to the
|
|
58
58
|
* allowed set. Malformed lines are ignored (not counted) but do not throw so that a
|
|
59
|
-
* hand-edited file cannot break
|
|
59
|
+
* hand-edited file cannot break sync/archive flows.
|
|
60
60
|
*/
|
|
61
61
|
export declare function countActiveKnowledgeEntries(text: string): number;
|
package/dist/run-archive.js
CHANGED
|
@@ -7,7 +7,7 @@ import { readKnowledgeSafely } from "./knowledge-store.js";
|
|
|
7
7
|
import { evaluateRetroGate } from "./retro-gate.js";
|
|
8
8
|
import { ensureRunSystem, flowStateLockPathFor, readFlowState, writeFlowState } from "./run-persistence.js";
|
|
9
9
|
export const ARCHIVE_DISPOSITIONS = ["completed", "cancelled", "abandoned"];
|
|
10
|
-
const
|
|
10
|
+
const ARCHIVE_DIR_REL_PATH = `${RUNTIME_ROOT}/archive`;
|
|
11
11
|
const ACTIVE_ARTIFACTS_REL_PATH = `${RUNTIME_ROOT}/artifacts`;
|
|
12
12
|
const STATE_DIR_REL_PATH = `${RUNTIME_ROOT}/state`;
|
|
13
13
|
/** State filenames explicitly excluded from the archive snapshot. */
|
|
@@ -17,15 +17,13 @@ const STATE_SNAPSHOT_EXCLUDE = new Set([
|
|
|
17
17
|
]);
|
|
18
18
|
const DELEGATION_LOG_FILE = "delegation-log.json";
|
|
19
19
|
const TDD_CYCLE_LOG_FILE = "tdd-cycle-log.jsonl";
|
|
20
|
-
const RECONCILIATION_NOTICES_FILE = "reconciliation-notices.json";
|
|
21
20
|
const CRITICAL_STATE_SNAPSHOT_FILES = new Set([
|
|
22
21
|
"flow-state.json",
|
|
23
22
|
DELEGATION_LOG_FILE,
|
|
24
|
-
TDD_CYCLE_LOG_FILE
|
|
25
|
-
RECONCILIATION_NOTICES_FILE
|
|
23
|
+
TDD_CYCLE_LOG_FILE
|
|
26
24
|
]);
|
|
27
|
-
function
|
|
28
|
-
return path.join(projectRoot,
|
|
25
|
+
function archiveRoot(projectRoot) {
|
|
26
|
+
return path.join(projectRoot, ARCHIVE_DIR_REL_PATH);
|
|
29
27
|
}
|
|
30
28
|
function activeArtifactsPath(projectRoot) {
|
|
31
29
|
return path.join(projectRoot, ACTIVE_ARTIFACTS_REL_PATH);
|
|
@@ -89,7 +87,6 @@ async function resetCarryoverStateFiles(projectRoot, activeRunId) {
|
|
|
89
87
|
await ensureDir(stateDir);
|
|
90
88
|
await writeFileSafe(path.join(stateDir, DELEGATION_LOG_FILE), `${JSON.stringify({ runId: activeRunId, entries: [] }, null, 2)}\n`, { mode: 0o600 });
|
|
91
89
|
await writeFileSafe(path.join(stateDir, TDD_CYCLE_LOG_FILE), "", { mode: 0o600 });
|
|
92
|
-
await writeFileSafe(path.join(stateDir, RECONCILIATION_NOTICES_FILE), `${JSON.stringify({ schemaVersion: 1, notices: [] }, null, 2)}\n`, { mode: 0o600 });
|
|
93
90
|
}
|
|
94
91
|
async function restoreStateSnapshot(projectRoot, archiveStatePath) {
|
|
95
92
|
if (!(await exists(archiveStatePath)))
|
|
@@ -150,14 +147,14 @@ async function inferRunNameFromArtifacts(projectRoot) {
|
|
|
150
147
|
async function uniqueArchiveId(projectRoot, baseId) {
|
|
151
148
|
let index = 1;
|
|
152
149
|
let candidate = baseId;
|
|
153
|
-
while (await exists(path.join(
|
|
150
|
+
while (await exists(path.join(archiveRoot(projectRoot), candidate))) {
|
|
154
151
|
index += 1;
|
|
155
152
|
candidate = `${baseId}-${index}`;
|
|
156
153
|
}
|
|
157
154
|
return candidate;
|
|
158
155
|
}
|
|
159
156
|
export async function listRuns(projectRoot) {
|
|
160
|
-
const root =
|
|
157
|
+
const root = archiveRoot(projectRoot);
|
|
161
158
|
if (!(await exists(root))) {
|
|
162
159
|
return [];
|
|
163
160
|
}
|
|
@@ -194,15 +191,15 @@ export async function archiveRun(projectRoot, runName, options = {}) {
|
|
|
194
191
|
return withDirectoryLock(archiveLockPath(projectRoot), async () => {
|
|
195
192
|
return withDirectoryLock(flowStateLockPathFor(projectRoot), async () => {
|
|
196
193
|
const artifactsDir = activeArtifactsPath(projectRoot);
|
|
197
|
-
const
|
|
198
|
-
await ensureDir(
|
|
194
|
+
const archiveDir = archiveRoot(projectRoot);
|
|
195
|
+
await ensureDir(archiveDir);
|
|
199
196
|
await ensureDir(artifactsDir);
|
|
200
197
|
const archiveRunName = (runName?.trim() && runName.trim().length > 0)
|
|
201
198
|
? runName.trim()
|
|
202
199
|
: await inferRunNameFromArtifacts(projectRoot);
|
|
203
200
|
const archiveBaseId = `${toArchiveDate()}-${slugifyRunName(archiveRunName)}`;
|
|
204
201
|
const archiveId = await uniqueArchiveId(projectRoot, archiveBaseId);
|
|
205
|
-
const archivePath = path.join(
|
|
202
|
+
const archivePath = path.join(archiveDir, archiveId);
|
|
206
203
|
const archiveArtifactsPath = path.join(archivePath, "artifacts");
|
|
207
204
|
let sourceState = await readFlowState(projectRoot);
|
|
208
205
|
const retroGate = await evaluateRetroGate(projectRoot, sourceState);
|
|
@@ -273,7 +270,7 @@ export async function archiveRun(projectRoot, runName, options = {}) {
|
|
|
273
270
|
await ensureDir(archivePath);
|
|
274
271
|
// Drop an `.archive-in-progress` sentinel immediately so that a crash
|
|
275
272
|
// between the artifact rename and the final manifest write leaves a
|
|
276
|
-
// recoverable marker (
|
|
273
|
+
// recoverable marker (sync reports these; re-running archive on an
|
|
277
274
|
// orphan attempts to complete or roll back). The sentinel is removed
|
|
278
275
|
// only after the manifest lands successfully.
|
|
279
276
|
const sentinelPath = path.join(archivePath, ".archive-in-progress");
|
|
@@ -326,7 +323,7 @@ export async function archiveRun(projectRoot, runName, options = {}) {
|
|
|
326
323
|
// Best-effort rollback: if artifacts were moved but the subsequent
|
|
327
324
|
// steps failed, put artifacts back so the user is not left without
|
|
328
325
|
// a working run. The sentinel is intentionally left behind for
|
|
329
|
-
// inspection;
|
|
326
|
+
// inspection; sync reports it.
|
|
330
327
|
if (artifactsMoved) {
|
|
331
328
|
try {
|
|
332
329
|
await fs.rm(artifactsDir, { recursive: true, force: true });
|
|
@@ -334,7 +331,7 @@ export async function archiveRun(projectRoot, runName, options = {}) {
|
|
|
334
331
|
}
|
|
335
332
|
catch {
|
|
336
333
|
// Rollback failed — sentinel + orphaned archive dir will be
|
|
337
|
-
// surfaced by
|
|
334
|
+
// surfaced by sync and can be reconciled manually.
|
|
338
335
|
}
|
|
339
336
|
}
|
|
340
337
|
if (stateReset) {
|
|
@@ -371,7 +368,7 @@ async function readKnowledgeStats(projectRoot) {
|
|
|
371
368
|
* Counts entries in the canonical JSONL knowledge store. An "active" entry is one
|
|
372
369
|
* non-empty line that parses as JSON with the required `type` field belonging to the
|
|
373
370
|
* allowed set. Malformed lines are ignored (not counted) but do not throw so that a
|
|
374
|
-
* hand-edited file cannot break
|
|
371
|
+
* hand-edited file cannot break sync/archive flows.
|
|
375
372
|
*/
|
|
376
373
|
export function countActiveKnowledgeEntries(text) {
|
|
377
374
|
const allowed = new Set(["rule", "pattern", "lesson", "compound"]);
|