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.
Files changed (160) hide show
  1. package/README.md +24 -18
  2. package/dist/artifact-linter/brainstorm.d.ts +2 -0
  3. package/dist/artifact-linter/brainstorm.js +289 -0
  4. package/dist/artifact-linter/design.d.ts +2 -0
  5. package/dist/artifact-linter/design.js +354 -0
  6. package/dist/artifact-linter/plan.d.ts +2 -0
  7. package/dist/artifact-linter/plan.js +183 -0
  8. package/dist/artifact-linter/review-army.d.ts +24 -0
  9. package/dist/artifact-linter/review-army.js +365 -0
  10. package/dist/artifact-linter/review.d.ts +2 -0
  11. package/dist/artifact-linter/review.js +99 -0
  12. package/dist/artifact-linter/scope.d.ts +2 -0
  13. package/dist/artifact-linter/scope.js +125 -0
  14. package/dist/artifact-linter/shared.d.ts +247 -0
  15. package/dist/artifact-linter/shared.js +1517 -0
  16. package/dist/artifact-linter/ship.d.ts +2 -0
  17. package/dist/artifact-linter/ship.js +82 -0
  18. package/dist/artifact-linter/spec.d.ts +2 -0
  19. package/dist/artifact-linter/spec.js +130 -0
  20. package/dist/artifact-linter/tdd.d.ts +2 -0
  21. package/dist/artifact-linter/tdd.js +198 -0
  22. package/dist/artifact-linter.d.ts +4 -76
  23. package/dist/artifact-linter.js +56 -2949
  24. package/dist/cli.d.ts +1 -6
  25. package/dist/cli.js +4 -159
  26. package/dist/codex-feature-flag.d.ts +1 -1
  27. package/dist/codex-feature-flag.js +1 -1
  28. package/dist/config.d.ts +3 -2
  29. package/dist/config.js +67 -3
  30. package/dist/constants.d.ts +1 -7
  31. package/dist/constants.js +10 -15
  32. package/dist/content/cancel-command.js +2 -2
  33. package/dist/content/closeout-guidance.d.ts +1 -1
  34. package/dist/content/closeout-guidance.js +15 -13
  35. package/dist/content/core-agents.d.ts +46 -29
  36. package/dist/content/core-agents.js +216 -82
  37. package/dist/content/decision-protocol.d.ts +1 -1
  38. package/dist/content/decision-protocol.js +1 -1
  39. package/dist/content/diff-command.js +1 -1
  40. package/dist/content/examples.d.ts +0 -3
  41. package/dist/content/examples.js +197 -752
  42. package/dist/content/harness-doc.js +20 -2
  43. package/dist/content/hook-manifest.d.ts +2 -2
  44. package/dist/content/hook-manifest.js +2 -2
  45. package/dist/content/hooks.d.ts +1 -0
  46. package/dist/content/hooks.js +32 -137
  47. package/dist/content/idea.d.ts +60 -0
  48. package/dist/content/idea.js +404 -0
  49. package/dist/content/iron-laws.d.ts +0 -1
  50. package/dist/content/iron-laws.js +31 -16
  51. package/dist/content/learnings.d.ts +2 -4
  52. package/dist/content/learnings.js +11 -27
  53. package/dist/content/meta-skill.js +7 -7
  54. package/dist/content/node-hooks.d.ts +10 -0
  55. package/dist/content/node-hooks.js +163 -95
  56. package/dist/content/opencode-plugin.js +15 -29
  57. package/dist/content/reference-patterns.js +2 -2
  58. package/dist/content/runtime-shared-snippets.d.ts +8 -0
  59. package/dist/content/runtime-shared-snippets.js +80 -0
  60. package/dist/content/session-hooks.js +1 -1
  61. package/dist/content/skills.d.ts +1 -0
  62. package/dist/content/skills.js +69 -7
  63. package/dist/content/stage-schema.js +147 -61
  64. package/dist/content/stages/_lint-metadata/index.js +26 -2
  65. package/dist/content/stages/brainstorm.js +13 -7
  66. package/dist/content/stages/design.js +16 -11
  67. package/dist/content/stages/plan.js +7 -4
  68. package/dist/content/stages/review.js +12 -12
  69. package/dist/content/stages/schema-types.d.ts +2 -2
  70. package/dist/content/stages/scope.js +15 -12
  71. package/dist/content/stages/ship.js +3 -3
  72. package/dist/content/stages/spec.js +9 -3
  73. package/dist/content/stages/tdd.js +14 -4
  74. package/dist/content/start-command.js +11 -10
  75. package/dist/content/status-command.js +5 -5
  76. package/dist/content/subagent-context-skills.js +156 -1
  77. package/dist/content/subagents.d.ts +0 -5
  78. package/dist/content/subagents.js +65 -81
  79. package/dist/content/templates.d.ts +1 -1
  80. package/dist/content/templates.js +187 -154
  81. package/dist/content/tree-command.js +2 -2
  82. package/dist/content/utility-skills.d.ts +2 -2
  83. package/dist/content/utility-skills.js +28 -99
  84. package/dist/content/view-command.js +4 -2
  85. package/dist/delegation.d.ts +2 -0
  86. package/dist/delegation.js +2 -1
  87. package/dist/early-loop.d.ts +66 -0
  88. package/dist/early-loop.js +275 -0
  89. package/dist/flow-state.d.ts +5 -6
  90. package/dist/flow-state.js +4 -6
  91. package/dist/gate-evidence.d.ts +0 -23
  92. package/dist/gate-evidence.js +111 -153
  93. package/dist/harness-adapters.d.ts +2 -2
  94. package/dist/harness-adapters.js +48 -19
  95. package/dist/install.js +190 -32
  96. package/dist/internal/advance-stage/advance.d.ts +50 -0
  97. package/dist/internal/advance-stage/advance.js +479 -0
  98. package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
  99. package/dist/internal/advance-stage/cancel-run.js +19 -0
  100. package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
  101. package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
  102. package/dist/internal/advance-stage/helpers.d.ts +14 -0
  103. package/dist/internal/advance-stage/helpers.js +145 -0
  104. package/dist/internal/advance-stage/hook.d.ts +8 -0
  105. package/dist/internal/advance-stage/hook.js +40 -0
  106. package/dist/internal/advance-stage/parsers.d.ts +54 -0
  107. package/dist/internal/advance-stage/parsers.js +307 -0
  108. package/dist/internal/advance-stage/review-loop.d.ts +7 -0
  109. package/dist/internal/advance-stage/review-loop.js +161 -0
  110. package/dist/internal/advance-stage/rewind.d.ts +14 -0
  111. package/dist/internal/advance-stage/rewind.js +108 -0
  112. package/dist/internal/advance-stage/start-flow.d.ts +11 -0
  113. package/dist/internal/advance-stage/start-flow.js +136 -0
  114. package/dist/internal/advance-stage/verify.d.ts +29 -0
  115. package/dist/internal/advance-stage/verify.js +225 -0
  116. package/dist/internal/advance-stage.js +21 -1470
  117. package/dist/internal/compound-readiness.d.ts +1 -1
  118. package/dist/internal/compound-readiness.js +2 -2
  119. package/dist/internal/early-loop-status.d.ts +7 -0
  120. package/dist/internal/early-loop-status.js +90 -0
  121. package/dist/internal/runtime-integrity.d.ts +7 -0
  122. package/dist/internal/runtime-integrity.js +288 -0
  123. package/dist/internal/tdd-red-evidence.js +1 -1
  124. package/dist/knowledge-store.d.ts +5 -28
  125. package/dist/knowledge-store.js +57 -84
  126. package/dist/managed-resources.js +24 -2
  127. package/dist/policy.js +7 -9
  128. package/dist/retro-gate.js +8 -90
  129. package/dist/run-archive.d.ts +1 -1
  130. package/dist/run-archive.js +13 -16
  131. package/dist/run-persistence.js +20 -15
  132. package/dist/runtime/run-hook.entry.d.ts +3 -0
  133. package/dist/runtime/run-hook.entry.js +5 -0
  134. package/dist/runtime/run-hook.mjs +9477 -0
  135. package/dist/tdd-cycle.d.ts +3 -3
  136. package/dist/tdd-cycle.js +1 -1
  137. package/dist/types.d.ts +18 -10
  138. package/package.json +4 -2
  139. package/dist/content/hook-inline-snippets.d.ts +0 -83
  140. package/dist/content/hook-inline-snippets.js +0 -302
  141. package/dist/content/ideate-command.d.ts +0 -8
  142. package/dist/content/ideate-command.js +0 -315
  143. package/dist/content/ideate-frames.d.ts +0 -31
  144. package/dist/content/ideate-frames.js +0 -140
  145. package/dist/content/ideate-ranking.d.ts +0 -25
  146. package/dist/content/ideate-ranking.js +0 -65
  147. package/dist/content/next-command.d.ts +0 -20
  148. package/dist/content/next-command.js +0 -298
  149. package/dist/content/seed-shelf.d.ts +0 -36
  150. package/dist/content/seed-shelf.js +0 -301
  151. package/dist/content/stage-common-guidance.d.ts +0 -1
  152. package/dist/content/stage-common-guidance.js +0 -106
  153. package/dist/doctor-registry.d.ts +0 -10
  154. package/dist/doctor-registry.js +0 -186
  155. package/dist/doctor.d.ts +0 -17
  156. package/dist/doctor.js +0 -2201
  157. package/dist/internal/hook-manifest.d.ts +0 -16
  158. package/dist/internal/hook-manifest.js +0 -77
  159. package/dist/trace-matrix.d.ts +0 -27
  160. package/dist/trace-matrix.js +0 -226
@@ -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
- * Entries with `maturity === "lifted-to-enforcement"` or `superseded_by`
36
- * are excluded they were already promoted/replaced and should not re-appear
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 KNOWLEDGE_UNIVERSALITY_SET = new Set(["project", "personal", "universal"]);
147
- const KNOWLEDGE_MATURITY_SET = new Set(["raw", "lifted-to-rule", "lifted-to-enforcement"]);
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
- "ideate",
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, options) {
179
- return KNOWLEDGE_ALLOWED_KEYS.has(key) || (options.allowLegacyOriginFeature === true && key === "origin_feature");
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, { allowLegacyOriginFeature: true });
247
+ const validated = validateKnowledgeEntry(parsed);
242
248
  if (!validated.ok) {
243
249
  malformedLines += 1;
244
250
  continue;
245
251
  }
246
- const entry = normalizeLegacyKnowledgeEntry(parsed);
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, options = {}) {
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, options)) {
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
- if (key !== "origin_run" ||
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 = Object.prototype.hasOwnProperty.call(obj, "origin_run")
345
- ? obj.origin_run
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 (!KNOWLEDGE_UNIVERSALITY_SET.has(obj.universality)) {
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 (!KNOWLEDGE_MATURITY_SET.has(obj.maturity)) {
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
- if (obj.source !== undefined &&
384
- obj.source !== null &&
385
- (typeof obj.source !== "string" || !KNOWLEDGE_SOURCE_SET.has(obj.source))) {
386
- errors.push("source must be one of: stage, retro, compound, ideate, manual, or null.");
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 staleTriggers = supersededTriggerSet(entries);
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}/runs/`))
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 previous = await readManagedResourceManifest(options.projectRoot).catch(() => null);
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, domain, stage, origin_stage, origin_run, frequency, universality, maturity, created, first_seen_ts, last_seen_ts, project", name: "utility_skill:learnings:field_order" },
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/next.md"), needle: "## Algorithm", name: "utility_command:next:algorithm" },
66
- { file: runtimeFile("commands/next.md"), needle: "closeout.shipSubstate", name: "utility_command:next:closeout_chain" },
67
- { file: runtimeFile("commands/ideate.md"), needle: "## Algorithm", name: "utility_command:ideate:algorithm" },
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: "Implementer", name: "utility_skill:sdd:implementer_template" },
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: "retro -> compound -> archive", name: "meta_skill:closeout_chain" },
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/brainstorming/SKILL.md"), needle: "## Shared Stage Guidance", name: "stage_skill:shared_guidance_inline" },
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" },
@@ -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, stripBom } from "./fs-utils.js";
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 artifactPathComplete = hasRetroArtifact && (compoundEntries > 0 || compoundSkipped);
121
- const completed = required ? retroSkipped || artifactPathComplete : true;
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,
@@ -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 doctor/archive flows.
59
+ * hand-edited file cannot break sync/archive flows.
60
60
  */
61
61
  export declare function countActiveKnowledgeEntries(text: string): number;
@@ -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 RUNS_DIR_REL_PATH = `${RUNTIME_ROOT}/runs`;
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 runsRoot(projectRoot) {
28
- return path.join(projectRoot, RUNS_DIR_REL_PATH);
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(runsRoot(projectRoot), candidate))) {
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 = runsRoot(projectRoot);
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 runsDir = runsRoot(projectRoot);
198
- await ensureDir(runsDir);
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(runsDir, archiveId);
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 (doctor surfaces these; re-running archive on an
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; doctor surfaces it.
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 doctor and can be reconciled manually.
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 doctor/archive flows.
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"]);