cclaw-cli 7.7.1 → 8.1.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 (282) hide show
  1. package/README.md +210 -134
  2. package/dist/artifact-frontmatter.d.ts +51 -0
  3. package/dist/artifact-frontmatter.js +131 -0
  4. package/dist/artifact-paths.d.ts +7 -27
  5. package/dist/artifact-paths.js +20 -249
  6. package/dist/cancel.d.ts +16 -0
  7. package/dist/cancel.js +66 -0
  8. package/dist/cli.d.ts +2 -27
  9. package/dist/cli.js +90 -508
  10. package/dist/compound.d.ts +26 -0
  11. package/dist/compound.js +96 -0
  12. package/dist/config.d.ts +14 -51
  13. package/dist/config.js +23 -359
  14. package/dist/constants.d.ts +11 -18
  15. package/dist/constants.js +19 -106
  16. package/dist/content/antipatterns.d.ts +1 -0
  17. package/dist/content/antipatterns.js +109 -0
  18. package/dist/content/artifact-templates.d.ts +10 -0
  19. package/dist/content/artifact-templates.js +550 -0
  20. package/dist/content/cancel-command.d.ts +2 -2
  21. package/dist/content/cancel-command.js +25 -17
  22. package/dist/content/core-agents.d.ts +9 -233
  23. package/dist/content/core-agents.js +39 -768
  24. package/dist/content/decision-protocol.d.ts +1 -12
  25. package/dist/content/decision-protocol.js +27 -20
  26. package/dist/content/examples.d.ts +8 -42
  27. package/dist/content/examples.js +293 -425
  28. package/dist/content/idea-command.d.ts +2 -0
  29. package/dist/content/idea-command.js +38 -0
  30. package/dist/content/iron-laws.d.ts +4 -138
  31. package/dist/content/iron-laws.js +18 -197
  32. package/dist/content/meta-skill.d.ts +1 -3
  33. package/dist/content/meta-skill.js +57 -134
  34. package/dist/content/node-hooks.d.ts +12 -8
  35. package/dist/content/node-hooks.js +188 -838
  36. package/dist/content/recovery.d.ts +8 -0
  37. package/dist/content/recovery.js +179 -0
  38. package/dist/content/reference-patterns.d.ts +4 -13
  39. package/dist/content/reference-patterns.js +260 -389
  40. package/dist/content/research-playbooks.d.ts +8 -8
  41. package/dist/content/research-playbooks.js +108 -121
  42. package/dist/content/review-loop.d.ts +6 -192
  43. package/dist/content/review-loop.js +29 -731
  44. package/dist/content/skills.d.ts +8 -38
  45. package/dist/content/skills.js +681 -732
  46. package/dist/content/specialist-prompts/architect.d.ts +1 -0
  47. package/dist/content/specialist-prompts/architect.js +225 -0
  48. package/dist/content/specialist-prompts/brainstormer.d.ts +1 -0
  49. package/dist/content/specialist-prompts/brainstormer.js +168 -0
  50. package/dist/content/specialist-prompts/index.d.ts +2 -0
  51. package/dist/content/specialist-prompts/index.js +14 -0
  52. package/dist/content/specialist-prompts/planner.d.ts +1 -0
  53. package/dist/content/specialist-prompts/planner.js +182 -0
  54. package/dist/content/specialist-prompts/reviewer.d.ts +1 -0
  55. package/dist/content/specialist-prompts/reviewer.js +193 -0
  56. package/dist/content/specialist-prompts/security-reviewer.d.ts +1 -0
  57. package/dist/content/specialist-prompts/security-reviewer.js +133 -0
  58. package/dist/content/specialist-prompts/slice-builder.d.ts +1 -0
  59. package/dist/content/specialist-prompts/slice-builder.js +232 -0
  60. package/dist/content/stage-playbooks.d.ts +8 -0
  61. package/dist/content/stage-playbooks.js +404 -0
  62. package/dist/content/start-command.d.ts +2 -12
  63. package/dist/content/start-command.js +221 -207
  64. package/dist/flow-state.d.ts +21 -178
  65. package/dist/flow-state.js +67 -170
  66. package/dist/fs-utils.d.ts +6 -26
  67. package/dist/fs-utils.js +29 -162
  68. package/dist/gitignore.d.ts +2 -1
  69. package/dist/gitignore.js +51 -34
  70. package/dist/harness-detect.d.ts +10 -0
  71. package/dist/harness-detect.js +29 -0
  72. package/dist/install.d.ts +27 -15
  73. package/dist/install.js +230 -1342
  74. package/dist/knowledge-store.d.ts +19 -163
  75. package/dist/knowledge-store.js +56 -590
  76. package/dist/logger.d.ts +8 -3
  77. package/dist/logger.js +13 -4
  78. package/dist/orchestrator-routing.d.ts +29 -0
  79. package/dist/orchestrator-routing.js +156 -0
  80. package/dist/run-persistence.d.ts +7 -118
  81. package/dist/run-persistence.js +29 -845
  82. package/dist/runtime/run-hook.entry.d.ts +1 -3
  83. package/dist/runtime/run-hook.entry.js +19 -4
  84. package/dist/runtime/run-hook.mjs +13 -1024
  85. package/dist/types.d.ts +25 -261
  86. package/dist/types.js +8 -36
  87. package/package.json +6 -3
  88. package/dist/artifact-linter/brainstorm.d.ts +0 -2
  89. package/dist/artifact-linter/brainstorm.js +0 -353
  90. package/dist/artifact-linter/design.d.ts +0 -18
  91. package/dist/artifact-linter/design.js +0 -444
  92. package/dist/artifact-linter/findings-dedup.d.ts +0 -56
  93. package/dist/artifact-linter/findings-dedup.js +0 -232
  94. package/dist/artifact-linter/plan.d.ts +0 -2
  95. package/dist/artifact-linter/plan.js +0 -826
  96. package/dist/artifact-linter/review-army.d.ts +0 -49
  97. package/dist/artifact-linter/review-army.js +0 -520
  98. package/dist/artifact-linter/review.d.ts +0 -2
  99. package/dist/artifact-linter/review.js +0 -113
  100. package/dist/artifact-linter/scope.d.ts +0 -2
  101. package/dist/artifact-linter/scope.js +0 -158
  102. package/dist/artifact-linter/shared.d.ts +0 -637
  103. package/dist/artifact-linter/shared.js +0 -2163
  104. package/dist/artifact-linter/ship.d.ts +0 -2
  105. package/dist/artifact-linter/ship.js +0 -250
  106. package/dist/artifact-linter/spec.d.ts +0 -2
  107. package/dist/artifact-linter/spec.js +0 -176
  108. package/dist/artifact-linter/tdd.d.ts +0 -118
  109. package/dist/artifact-linter/tdd.js +0 -1404
  110. package/dist/artifact-linter.d.ts +0 -15
  111. package/dist/artifact-linter.js +0 -517
  112. package/dist/codex-feature-flag.d.ts +0 -58
  113. package/dist/codex-feature-flag.js +0 -193
  114. package/dist/content/closeout-guidance.d.ts +0 -14
  115. package/dist/content/closeout-guidance.js +0 -44
  116. package/dist/content/diff-command.d.ts +0 -1
  117. package/dist/content/diff-command.js +0 -43
  118. package/dist/content/harness-doc.d.ts +0 -1
  119. package/dist/content/harness-doc.js +0 -65
  120. package/dist/content/hook-events.d.ts +0 -9
  121. package/dist/content/hook-events.js +0 -23
  122. package/dist/content/hook-manifest.d.ts +0 -81
  123. package/dist/content/hook-manifest.js +0 -156
  124. package/dist/content/hooks.d.ts +0 -11
  125. package/dist/content/hooks.js +0 -1972
  126. package/dist/content/idea.d.ts +0 -60
  127. package/dist/content/idea.js +0 -416
  128. package/dist/content/language-policy.d.ts +0 -2
  129. package/dist/content/language-policy.js +0 -13
  130. package/dist/content/learnings.d.ts +0 -6
  131. package/dist/content/learnings.js +0 -141
  132. package/dist/content/observe.d.ts +0 -19
  133. package/dist/content/observe.js +0 -86
  134. package/dist/content/opencode-plugin.d.ts +0 -1
  135. package/dist/content/opencode-plugin.js +0 -635
  136. package/dist/content/review-prompts.d.ts +0 -1
  137. package/dist/content/review-prompts.js +0 -104
  138. package/dist/content/runtime-shared-snippets.d.ts +0 -8
  139. package/dist/content/runtime-shared-snippets.js +0 -80
  140. package/dist/content/session-hooks.d.ts +0 -7
  141. package/dist/content/session-hooks.js +0 -107
  142. package/dist/content/skills-elicitation.d.ts +0 -1
  143. package/dist/content/skills-elicitation.js +0 -167
  144. package/dist/content/stage-command.d.ts +0 -2
  145. package/dist/content/stage-command.js +0 -17
  146. package/dist/content/stage-schema.d.ts +0 -117
  147. package/dist/content/stage-schema.js +0 -955
  148. package/dist/content/stages/_lint-metadata/index.d.ts +0 -2
  149. package/dist/content/stages/_lint-metadata/index.js +0 -97
  150. package/dist/content/stages/brainstorm.d.ts +0 -2
  151. package/dist/content/stages/brainstorm.js +0 -184
  152. package/dist/content/stages/design.d.ts +0 -2
  153. package/dist/content/stages/design.js +0 -288
  154. package/dist/content/stages/index.d.ts +0 -8
  155. package/dist/content/stages/index.js +0 -11
  156. package/dist/content/stages/plan.d.ts +0 -2
  157. package/dist/content/stages/plan.js +0 -191
  158. package/dist/content/stages/review.d.ts +0 -2
  159. package/dist/content/stages/review.js +0 -240
  160. package/dist/content/stages/schema-types.d.ts +0 -203
  161. package/dist/content/stages/schema-types.js +0 -1
  162. package/dist/content/stages/scope.d.ts +0 -2
  163. package/dist/content/stages/scope.js +0 -254
  164. package/dist/content/stages/ship.d.ts +0 -2
  165. package/dist/content/stages/ship.js +0 -159
  166. package/dist/content/stages/spec.d.ts +0 -2
  167. package/dist/content/stages/spec.js +0 -170
  168. package/dist/content/stages/tdd.d.ts +0 -4
  169. package/dist/content/stages/tdd.js +0 -273
  170. package/dist/content/state-contracts.d.ts +0 -1
  171. package/dist/content/state-contracts.js +0 -63
  172. package/dist/content/status-command.d.ts +0 -4
  173. package/dist/content/status-command.js +0 -109
  174. package/dist/content/subagent-context-skills.d.ts +0 -4
  175. package/dist/content/subagent-context-skills.js +0 -279
  176. package/dist/content/subagents.d.ts +0 -3
  177. package/dist/content/subagents.js +0 -997
  178. package/dist/content/templates.d.ts +0 -26
  179. package/dist/content/templates.js +0 -1692
  180. package/dist/content/track-render-context.d.ts +0 -18
  181. package/dist/content/track-render-context.js +0 -53
  182. package/dist/content/tree-command.d.ts +0 -1
  183. package/dist/content/tree-command.js +0 -64
  184. package/dist/content/utility-skills.d.ts +0 -30
  185. package/dist/content/utility-skills.js +0 -160
  186. package/dist/content/view-command.d.ts +0 -2
  187. package/dist/content/view-command.js +0 -92
  188. package/dist/delegation.d.ts +0 -649
  189. package/dist/delegation.js +0 -1539
  190. package/dist/early-loop.d.ts +0 -70
  191. package/dist/early-loop.js +0 -302
  192. package/dist/execution-topology.d.ts +0 -44
  193. package/dist/execution-topology.js +0 -95
  194. package/dist/gate-evidence.d.ts +0 -85
  195. package/dist/gate-evidence.js +0 -631
  196. package/dist/harness-adapters.d.ts +0 -151
  197. package/dist/harness-adapters.js +0 -756
  198. package/dist/harness-selection.d.ts +0 -31
  199. package/dist/harness-selection.js +0 -214
  200. package/dist/hook-schema.d.ts +0 -6
  201. package/dist/hook-schema.js +0 -114
  202. package/dist/hook-schemas/claude-hooks.v1.json +0 -10
  203. package/dist/hook-schemas/codex-hooks.v1.json +0 -10
  204. package/dist/hook-schemas/cursor-hooks.v1.json +0 -13
  205. package/dist/init-detect.d.ts +0 -2
  206. package/dist/init-detect.js +0 -50
  207. package/dist/internal/advance-stage/advance.d.ts +0 -89
  208. package/dist/internal/advance-stage/advance.js +0 -655
  209. package/dist/internal/advance-stage/cancel-run.d.ts +0 -8
  210. package/dist/internal/advance-stage/cancel-run.js +0 -19
  211. package/dist/internal/advance-stage/flow-state-coercion.d.ts +0 -3
  212. package/dist/internal/advance-stage/flow-state-coercion.js +0 -81
  213. package/dist/internal/advance-stage/helpers.d.ts +0 -14
  214. package/dist/internal/advance-stage/helpers.js +0 -145
  215. package/dist/internal/advance-stage/hook.d.ts +0 -8
  216. package/dist/internal/advance-stage/hook.js +0 -40
  217. package/dist/internal/advance-stage/parsers.d.ts +0 -72
  218. package/dist/internal/advance-stage/parsers.js +0 -357
  219. package/dist/internal/advance-stage/proactive-delegation-trace.d.ts +0 -24
  220. package/dist/internal/advance-stage/proactive-delegation-trace.js +0 -56
  221. package/dist/internal/advance-stage/review-loop.d.ts +0 -16
  222. package/dist/internal/advance-stage/review-loop.js +0 -199
  223. package/dist/internal/advance-stage/rewind.d.ts +0 -14
  224. package/dist/internal/advance-stage/rewind.js +0 -108
  225. package/dist/internal/advance-stage/start-flow.d.ts +0 -13
  226. package/dist/internal/advance-stage/start-flow.js +0 -241
  227. package/dist/internal/advance-stage/verify.d.ts +0 -21
  228. package/dist/internal/advance-stage/verify.js +0 -185
  229. package/dist/internal/advance-stage.d.ts +0 -7
  230. package/dist/internal/advance-stage.js +0 -138
  231. package/dist/internal/cohesion-contract-stub.d.ts +0 -24
  232. package/dist/internal/cohesion-contract-stub.js +0 -148
  233. package/dist/internal/compound-readiness.d.ts +0 -23
  234. package/dist/internal/compound-readiness.js +0 -102
  235. package/dist/internal/detect-public-api-changes.d.ts +0 -5
  236. package/dist/internal/detect-public-api-changes.js +0 -45
  237. package/dist/internal/detect-supply-chain-changes.d.ts +0 -6
  238. package/dist/internal/detect-supply-chain-changes.js +0 -138
  239. package/dist/internal/early-loop-status.d.ts +0 -7
  240. package/dist/internal/early-loop-status.js +0 -93
  241. package/dist/internal/envelope-validate.d.ts +0 -7
  242. package/dist/internal/envelope-validate.js +0 -66
  243. package/dist/internal/flow-state-repair.d.ts +0 -20
  244. package/dist/internal/flow-state-repair.js +0 -104
  245. package/dist/internal/plan-split-waves.d.ts +0 -190
  246. package/dist/internal/plan-split-waves.js +0 -764
  247. package/dist/internal/runtime-integrity.d.ts +0 -7
  248. package/dist/internal/runtime-integrity.js +0 -268
  249. package/dist/internal/slice-commit.d.ts +0 -7
  250. package/dist/internal/slice-commit.js +0 -619
  251. package/dist/internal/tdd-loop-status.d.ts +0 -14
  252. package/dist/internal/tdd-loop-status.js +0 -68
  253. package/dist/internal/tdd-red-evidence.d.ts +0 -7
  254. package/dist/internal/tdd-red-evidence.js +0 -153
  255. package/dist/internal/waiver-grant.d.ts +0 -62
  256. package/dist/internal/waiver-grant.js +0 -294
  257. package/dist/internal/wave-status.d.ts +0 -74
  258. package/dist/internal/wave-status.js +0 -506
  259. package/dist/managed-resources.d.ts +0 -53
  260. package/dist/managed-resources.js +0 -313
  261. package/dist/policy.d.ts +0 -10
  262. package/dist/policy.js +0 -167
  263. package/dist/retro-gate.d.ts +0 -9
  264. package/dist/retro-gate.js +0 -47
  265. package/dist/run-archive.d.ts +0 -61
  266. package/dist/run-archive.js +0 -391
  267. package/dist/runs.d.ts +0 -2
  268. package/dist/runs.js +0 -2
  269. package/dist/stack-detection.d.ts +0 -116
  270. package/dist/stack-detection.js +0 -489
  271. package/dist/streaming/event-stream.d.ts +0 -31
  272. package/dist/streaming/event-stream.js +0 -114
  273. package/dist/tdd-cycle.d.ts +0 -107
  274. package/dist/tdd-cycle.js +0 -289
  275. package/dist/tdd-verification-evidence.d.ts +0 -17
  276. package/dist/tdd-verification-evidence.js +0 -122
  277. package/dist/track-heuristics.d.ts +0 -27
  278. package/dist/track-heuristics.js +0 -154
  279. package/dist/util/slice-id.d.ts +0 -58
  280. package/dist/util/slice-id.js +0 -89
  281. package/dist/worktree-manager.d.ts +0 -20
  282. package/dist/worktree-manager.js +0 -108
@@ -1,603 +1,69 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { DEFAULT_COMPOUND_RECURRENCE_THRESHOLD } from "./config.js";
4
- import { RUNTIME_ROOT } from "./constants.js";
5
- import { stripBom, withDirectoryLock } from "./fs-utils.js";
6
- import { FLOW_STAGES } from "./types.js";
7
- const DEFAULT_COMPOUND_READINESS_MAX_READY = 10;
8
- /**
9
- * Single source of truth for the small-project relaxation rule.
10
- *
11
- * Kept exported so the inline hook mirror and CLI/runtime paths all agree on
12
- * the same numbers.
13
- */
14
- export const SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD = 5;
15
- export const SMALL_PROJECT_RECURRENCE_THRESHOLD = 2;
16
- export function effectiveCompoundThreshold(baseThreshold, archivedRunsCount) {
17
- if (typeof archivedRunsCount === "number" &&
18
- Number.isFinite(archivedRunsCount) &&
19
- archivedRunsCount < SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD &&
20
- baseThreshold > SMALL_PROJECT_RECURRENCE_THRESHOLD) {
21
- return {
22
- threshold: SMALL_PROJECT_RECURRENCE_THRESHOLD,
23
- relaxationApplied: true
24
- };
25
- }
26
- return { threshold: baseThreshold, relaxationApplied: false };
27
- }
28
- /**
29
- * Pure function — no filesystem side effects. Callers pass entries from
30
- * `readKnowledgeSafely` and get a derived readiness snapshot suitable
31
- * for persisting to `.cclaw/state/compound-readiness.json`.
32
- *
33
- * Clustering key: `(type, normalizeText(trigger), normalizeText(action))`
34
- * which mirrors the compound readiness clustering in runtime state.
35
- * The readiness surface intentionally stays simple: no maturity/supersede
36
- * suppression logic in this pass.
37
- */
38
- export function computeCompoundReadiness(entries, options = {}) {
39
- const thresholdRaw = options.threshold ?? DEFAULT_COMPOUND_RECURRENCE_THRESHOLD;
40
- const baseThreshold = Number.isInteger(thresholdRaw) && thresholdRaw >= 1
41
- ? thresholdRaw
42
- : DEFAULT_COMPOUND_RECURRENCE_THRESHOLD;
43
- const maxReadyRaw = options.maxReady ?? DEFAULT_COMPOUND_READINESS_MAX_READY;
44
- const maxReady = Number.isInteger(maxReadyRaw) && maxReadyRaw >= 1
45
- ? maxReadyRaw
46
- : DEFAULT_COMPOUND_READINESS_MAX_READY;
47
- const now = options.now ?? new Date();
48
- const archivedRunsCount = typeof options.archivedRunsCount === "number" &&
49
- Number.isFinite(options.archivedRunsCount) &&
50
- options.archivedRunsCount >= 0
51
- ? Math.floor(options.archivedRunsCount)
52
- : undefined;
53
- const { threshold, relaxationApplied } = effectiveCompoundThreshold(baseThreshold, archivedRunsCount);
54
- const buckets = new Map();
55
- for (const entry of entries) {
56
- const key = [
57
- entry.type,
58
- normalizeText(entry.trigger),
59
- normalizeText(entry.action)
60
- ].join("||");
61
- const frequency = Math.max(1, Math.floor(entry.frequency));
62
- const bucket = buckets.get(key);
63
- if (!bucket) {
64
- buckets.set(key, {
65
- trigger: entry.trigger,
66
- action: entry.action,
67
- recurrence: frequency,
68
- entryCount: 1,
69
- severity: entry.severity,
70
- lastSeenTs: entry.last_seen_ts,
71
- types: new Set([entry.type])
72
- });
73
- continue;
74
- }
75
- bucket.recurrence += frequency;
76
- bucket.entryCount += 1;
77
- bucket.types.add(entry.type);
78
- if (entry.severity === "critical") {
79
- bucket.severity = "critical";
80
- }
81
- else if (entry.severity === "important" && bucket.severity !== "critical") {
82
- bucket.severity = "important";
83
- }
84
- if (Date.parse(entry.last_seen_ts) > Date.parse(bucket.lastSeenTs)) {
85
- bucket.lastSeenTs = entry.last_seen_ts;
86
- }
87
- }
88
- const ready = [];
89
- for (const bucket of buckets.values()) {
90
- const criticalOverride = bucket.severity === "critical";
91
- const meetsRecurrence = bucket.recurrence >= threshold;
92
- if (!criticalOverride && !meetsRecurrence)
93
- continue;
94
- ready.push({
95
- trigger: bucket.trigger,
96
- action: bucket.action,
97
- recurrence: bucket.recurrence,
98
- entryCount: bucket.entryCount,
99
- qualification: criticalOverride && !meetsRecurrence ? "critical_override" : "recurrence",
100
- ...(bucket.severity ? { severity: bucket.severity } : {}),
101
- lastSeenTs: bucket.lastSeenTs,
102
- types: Array.from(bucket.types).sort()
103
- });
104
- }
105
- ready.sort((a, b) => {
106
- const severityWeight = (sev) => {
107
- if (sev === "critical")
108
- return 3;
109
- if (sev === "important")
110
- return 2;
111
- if (sev === "suggestion")
112
- return 1;
113
- return 0;
114
- };
115
- const severityDiff = severityWeight(b.severity) - severityWeight(a.severity);
116
- if (severityDiff !== 0)
117
- return severityDiff;
118
- if (b.recurrence !== a.recurrence)
119
- return b.recurrence - a.recurrence;
120
- const recencyDiff = Date.parse(b.lastSeenTs) - Date.parse(a.lastSeenTs);
121
- if (!Number.isNaN(recencyDiff) && recencyDiff !== 0)
122
- return recencyDiff;
123
- return a.trigger.localeCompare(b.trigger);
124
- });
125
- return {
126
- schemaVersion: 2,
127
- threshold,
128
- baseThreshold,
129
- ...(archivedRunsCount !== undefined ? { archivedRunsCount } : {}),
130
- smallProjectRelaxationApplied: relaxationApplied,
131
- clusterCount: buckets.size,
132
- readyCount: ready.length,
133
- ready: ready.slice(0, maxReady),
134
- lastUpdatedAt: normalizeUtcIso(now.toISOString())
135
- };
3
+ import { KNOWLEDGE_LOG_REL_PATH } from "./constants.js";
4
+ import { exists, writeFileSafe } from "./fs-utils.js";
5
+ export class KnowledgeStoreError extends Error {
136
6
  }
137
- const KNOWLEDGE_TYPE_SET = new Set(["rule", "pattern", "lesson", "compound"]);
138
- const KNOWLEDGE_CONFIDENCE_SET = new Set(["high", "medium", "low"]);
139
- const KNOWLEDGE_SEVERITY_SET = new Set(["critical", "important", "suggestion"]);
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"]);
142
- const KNOWLEDGE_SOURCE_SET = new Set([
143
- "stage",
144
- "retro",
145
- "compound",
146
- "idea",
147
- "manual"
148
- ]);
149
- const FLOW_STAGE_SET = new Set(FLOW_STAGES);
150
- const KNOWLEDGE_REQUIRED_KEYS = [
151
- "type",
152
- "trigger",
153
- "action",
154
- "confidence",
155
- "stage",
156
- "origin_stage",
157
- "frequency",
158
- "created",
159
- "first_seen_ts",
160
- "last_seen_ts",
161
- "project"
162
- ];
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");
170
- KNOWLEDGE_ALLOWED_KEYS.add("source");
171
- KNOWLEDGE_ALLOWED_KEYS.add("severity");
172
- KNOWLEDGE_ALLOWED_KEYS.add("supersedes");
173
- KNOWLEDGE_ALLOWED_KEYS.add("superseded_by");
174
- function keyAllowedInKnowledgeEntry(key) {
175
- return KNOWLEDGE_ALLOWED_KEYS.has(key);
7
+ export function knowledgeLogPath(projectRoot) {
8
+ return path.join(projectRoot, KNOWLEDGE_LOG_REL_PATH);
176
9
  }
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;
10
+ function assertEntry(value) {
11
+ if (typeof value !== "object" || value === null) {
12
+ throw new KnowledgeStoreError("Knowledge entry must be an object.");
193
13
  }
194
- if (obj.source !== undefined) {
195
- normalized.source = obj.source;
196
- }
197
- return normalized;
198
- }
199
- function knowledgePath(projectRoot) {
200
- return path.join(projectRoot, RUNTIME_ROOT, "knowledge.jsonl");
201
- }
202
- function knowledgeLockPath(projectRoot) {
203
- return path.join(projectRoot, RUNTIME_ROOT, "state", ".knowledge.lock");
204
- }
205
- function normalizeUtcIso(iso) {
206
- return iso.replace(/\.\d{3}Z$/u, "Z");
207
- }
208
- function nowUtcIso() {
209
- return normalizeUtcIso(new Date().toISOString());
210
- }
211
- function normalizeText(value) {
212
- return value.trim().replace(/\s+/gu, " ").toLowerCase();
213
- }
214
- function dedupeKey(entry) {
215
- return [
216
- entry.type,
217
- normalizeText(entry.trigger),
218
- normalizeText(entry.action),
219
- entry.stage ?? "null",
220
- entry.origin_stage ?? "null",
221
- entry.project === null ? "null" : normalizeText(entry.project),
222
- entry.source === undefined || entry.source === null ? "null" : entry.source,
223
- entry.severity === undefined ? "none" : entry.severity
224
- ].join("|");
225
- }
226
- function emptyKnowledgeSnapshot() {
227
- return {
228
- lines: [],
229
- entries: [],
230
- malformedLines: 0,
231
- keyToIndex: new Map(),
232
- entryByIndex: new Map()
233
- };
234
- }
235
- function parseKnowledgeSnapshot(raw) {
236
- const lines = stripBom(raw).split(/\r?\n/u);
237
- const entries = [];
238
- const keyToIndex = new Map();
239
- const entryByIndex = new Map();
240
- let malformedLines = 0;
241
- for (let i = 0; i < lines.length; i += 1) {
242
- const trimmed = lines[i].trim();
243
- if (trimmed.length === 0)
244
- continue;
245
- try {
246
- const parsed = JSON.parse(trimmed);
247
- const validated = validateKnowledgeEntry(parsed);
248
- if (!validated.ok) {
249
- malformedLines += 1;
250
- continue;
251
- }
252
- const entry = normalizeParsedKnowledgeEntry(parsed);
253
- entries.push(entry);
254
- const key = dedupeKey(entry);
255
- if (!keyToIndex.has(key)) {
256
- keyToIndex.set(key, i);
257
- }
258
- entryByIndex.set(i, entry);
259
- }
260
- catch {
261
- malformedLines += 1;
14
+ const entry = value;
15
+ for (const key of ["slug", "ship_commit", "shipped_at"]) {
16
+ if (typeof entry[key] !== "string" || entry[key].length === 0) {
17
+ throw new KnowledgeStoreError(`Knowledge entry must include string ${key}.`);
262
18
  }
263
19
  }
264
- return {
265
- lines,
266
- entries,
267
- malformedLines,
268
- keyToIndex,
269
- entryByIndex
270
- };
271
- }
272
- async function readKnowledgeSnapshot(filePath) {
273
- try {
274
- const raw = await fs.readFile(filePath, "utf8");
275
- return parseKnowledgeSnapshot(raw);
276
- }
277
- catch (error) {
278
- const code = error?.code;
279
- if (code === "ENOENT") {
280
- return emptyKnowledgeSnapshot();
281
- }
282
- throw error;
20
+ if (typeof entry.signals !== "object" || entry.signals === null) {
21
+ throw new KnowledgeStoreError("Knowledge entry must include a `signals` object.");
283
22
  }
284
23
  }
285
- function mergeKnowledgeOccurrence(target, incoming) {
286
- const mergedFrequency = target.frequency + Math.max(1, incoming.frequency);
287
- const mergedLastSeen = target.last_seen_ts >= incoming.last_seen_ts
288
- ? target.last_seen_ts
289
- : incoming.last_seen_ts;
290
- return {
291
- ...target,
292
- frequency: mergedFrequency,
293
- last_seen_ts: mergedLastSeen
294
- };
295
- }
296
- function isIsoUtcTimestamp(value) {
297
- return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/u.test(value);
298
- }
299
- function isNullableString(value) {
300
- return value === null || typeof value === "string";
301
- }
302
- function isNullableStage(value) {
303
- return value === null || (typeof value === "string" && FLOW_STAGE_SET.has(value));
304
- }
305
- export function validateKnowledgeEntry(entry) {
306
- const errors = [];
307
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
308
- return { ok: false, errors: ["Knowledge entry must be a JSON object."] };
309
- }
310
- const obj = entry;
311
- for (const key of Object.keys(obj)) {
312
- if (!keyAllowedInKnowledgeEntry(key)) {
313
- errors.push(`Unknown key "${key}" in knowledge entry.`);
314
- }
315
- }
316
- for (const key of KNOWLEDGE_REQUIRED_KEYS) {
317
- if (!Object.prototype.hasOwnProperty.call(obj, key)) {
318
- errors.push(`Missing required key "${key}".`);
319
- }
320
- }
321
- if (!KNOWLEDGE_TYPE_SET.has(obj.type)) {
322
- errors.push("type must be one of: rule, pattern, lesson, compound.");
323
- }
324
- if (typeof obj.trigger !== "string" || obj.trigger.trim().length === 0) {
325
- errors.push("trigger must be a non-empty string.");
326
- }
327
- if (typeof obj.action !== "string" || obj.action.trim().length === 0) {
328
- errors.push("action must be a non-empty string.");
329
- }
330
- if (!KNOWLEDGE_CONFIDENCE_SET.has(obj.confidence)) {
331
- errors.push("confidence must be one of: high, medium, low.");
332
- }
333
- if (obj.severity !== undefined &&
334
- (typeof obj.severity !== "string" || !KNOWLEDGE_SEVERITY_SET.has(obj.severity))) {
335
- errors.push("severity must be one of: critical, important, suggestion.");
336
- }
337
- if (obj.domain !== undefined && !isNullableString(obj.domain)) {
338
- errors.push("domain must be string or null.");
339
- }
340
- if (!isNullableStage(obj.stage)) {
341
- errors.push(`stage must be one of ${FLOW_STAGES.join(", ")} or null.`);
342
- }
343
- if (!isNullableStage(obj.origin_stage)) {
344
- errors.push(`origin_stage must be one of ${FLOW_STAGES.join(", ")} or null.`);
345
- }
346
- const originRun = obj.origin_run;
347
- if (originRun !== undefined && !isNullableString(originRun)) {
348
- errors.push("origin_run must be string or null.");
24
+ export async function appendKnowledgeEntry(projectRoot, entry) {
25
+ assertEntry(entry);
26
+ const target = knowledgeLogPath(projectRoot);
27
+ const line = `${JSON.stringify(entry)}\n`;
28
+ if (!(await exists(target))) {
29
+ await writeFileSafe(target, line);
30
+ return;
349
31
  }
350
- if (typeof obj.frequency !== "number" ||
351
- !Number.isInteger(obj.frequency) ||
352
- obj.frequency < 1) {
353
- errors.push("frequency must be an integer >= 1.");
354
- }
355
- if (obj.universality !== undefined &&
356
- (typeof obj.universality !== "string" || !LEGACY_KNOWLEDGE_UNIVERSALITY_SET.has(obj.universality))) {
357
- errors.push("universality must be one of: project, personal, universal.");
358
- }
359
- if (obj.maturity !== undefined &&
360
- (typeof obj.maturity !== "string" || !LEGACY_KNOWLEDGE_MATURITY_SET.has(obj.maturity))) {
361
- errors.push("maturity must be one of: raw, lifted-to-rule, lifted-to-enforcement.");
362
- }
363
- for (const timestampField of ["created", "first_seen_ts", "last_seen_ts"]) {
364
- const value = obj[timestampField];
365
- if (typeof value !== "string" || !isIsoUtcTimestamp(value)) {
366
- errors.push(`${timestampField} must be ISO UTC (YYYY-MM-DDTHH:MM:SSZ).`);
367
- }
368
- }
369
- if (!isNullableString(obj.project)) {
370
- errors.push("project must be string or null.");
371
- }
372
- if (obj.supersedes !== undefined) {
373
- if (!Array.isArray(obj.supersedes) ||
374
- obj.supersedes.length === 0 ||
375
- obj.supersedes.some((value) => typeof value !== "string" || value.trim().length === 0)) {
376
- errors.push("supersedes must be a non-empty array of strings when present.");
377
- }
378
- }
379
- if (obj.superseded_by !== undefined &&
380
- (typeof obj.superseded_by !== "string" || obj.superseded_by.trim().length === 0)) {
381
- errors.push("superseded_by must be a non-empty string when present.");
382
- }
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.");
389
- }
390
- return { ok: errors.length === 0, errors };
32
+ await fs.appendFile(target, line, "utf8");
391
33
  }
392
- export function materializeKnowledgeEntry(seed, defaults = {}) {
393
- const now = normalizeUtcIso(defaults.nowIso ?? nowUtcIso());
394
- const stage = seed.stage ?? defaults.stage ?? null;
395
- const originStage = seed.origin_stage ?? defaults.originStage ?? stage ?? null;
396
- const source = seed.source ?? defaults.source ?? null;
397
- const entry = {
398
- type: seed.type,
399
- trigger: seed.trigger.trim(),
400
- action: seed.action.trim(),
401
- confidence: seed.confidence,
402
- stage,
403
- origin_stage: originStage,
404
- frequency: seed.frequency ?? 1,
405
- created: normalizeUtcIso(seed.created ?? now),
406
- first_seen_ts: normalizeUtcIso(seed.first_seen_ts ?? now),
407
- last_seen_ts: normalizeUtcIso(seed.last_seen_ts ?? now),
408
- project: seed.project ?? defaults.project ?? null
409
- };
410
- if (seed.severity !== undefined) {
411
- entry.severity = seed.severity;
412
- }
413
- if (source !== null) {
414
- entry.source = source;
415
- }
416
- return entry;
417
- }
418
- export async function readKnowledgeSafely(projectRoot, options = {}) {
419
- const filePath = knowledgePath(projectRoot);
420
- const read = async () => {
421
- const snapshot = await readKnowledgeSnapshot(filePath);
422
- return {
423
- entries: snapshot.entries,
424
- malformedLines: snapshot.malformedLines
425
- };
426
- };
427
- if (options.lockAware === false) {
428
- return read();
429
- }
430
- return withDirectoryLock(knowledgeLockPath(projectRoot), read);
431
- }
432
- export async function appendKnowledge(projectRoot, seeds, defaults = {}) {
433
- if (seeds.length === 0) {
434
- return { appended: 0, skippedDuplicates: 0, invalid: 0, errors: [], appendedEntries: [] };
435
- }
436
- const filePath = knowledgePath(projectRoot);
437
- const errors = [];
438
- const materialized = [];
439
- for (let i = 0; i < seeds.length; i += 1) {
440
- const seed = seeds[i];
441
- const entry = materializeKnowledgeEntry(seed, defaults);
442
- const validated = validateKnowledgeEntry(entry);
443
- if (!validated.ok) {
444
- errors.push(`entry #${i + 1}: ${validated.errors.join(" ")}`);
445
- continue;
446
- }
447
- materialized.push(entry);
448
- }
449
- let skippedDuplicates = 0;
450
- const appendedEntries = [];
451
- await withDirectoryLock(knowledgeLockPath(projectRoot), async () => {
452
- await fs.mkdir(path.dirname(filePath), { recursive: true });
453
- const snapshot = await readKnowledgeSnapshot(filePath);
454
- const updatedByIndex = new Map();
455
- const batchEntries = new Map();
456
- for (const entry of materialized) {
457
- const key = dedupeKey(entry);
458
- const existingIndex = snapshot.keyToIndex.get(key);
459
- if (existingIndex !== undefined) {
460
- skippedDuplicates += 1;
461
- const base = updatedByIndex.get(existingIndex) ?? snapshot.entryByIndex.get(existingIndex);
462
- if (base) {
463
- updatedByIndex.set(existingIndex, mergeKnowledgeOccurrence(base, entry));
464
- }
465
- continue;
466
- }
467
- const existingBatchEntry = batchEntries.get(key);
468
- if (existingBatchEntry) {
469
- skippedDuplicates += 1;
470
- batchEntries.set(key, mergeKnowledgeOccurrence(existingBatchEntry, entry));
471
- continue;
472
- }
473
- batchEntries.set(key, { ...entry });
474
- }
475
- appendedEntries.push(...batchEntries.values());
476
- if (updatedByIndex.size === 0 && batchEntries.size === 0) {
477
- return;
478
- }
479
- const rewrittenLines = snapshot.lines.map((line, index) => {
480
- const updated = updatedByIndex.get(index);
481
- return updated ? JSON.stringify(updated) : line;
482
- }).filter((line) => line.trim().length > 0);
483
- const linesToWrite = [
484
- ...rewrittenLines,
485
- ...Array.from(batchEntries.values(), (entry) => JSON.stringify(entry))
486
- ];
487
- if (linesToWrite.length > 0) {
488
- await fs.writeFile(filePath, `${linesToWrite.join("\n")}\n`, "utf8");
489
- }
490
- });
491
- return {
492
- appended: appendedEntries.length,
493
- skippedDuplicates,
494
- invalid: errors.length,
495
- errors,
496
- appendedEntries
497
- };
498
- }
499
- const SHORT_TECHNICAL_TOKEN_SET = new Set(["ci", "db", "ui", "qa", "ux"]);
500
- function tokenizeText(value) {
501
- if (!value)
502
- return [];
503
- const tokens = [];
504
- const matches = value.matchAll(/[A-Za-z0-9]+/gu);
505
- for (const match of matches) {
506
- const raw = match[0] ?? "";
507
- const normalized = raw.toLowerCase();
508
- if (normalized.length >= 3) {
509
- tokens.push(normalized);
510
- continue;
511
- }
512
- if (/^[A-Z]{2}$/u.test(raw) || SHORT_TECHNICAL_TOKEN_SET.has(normalized)) {
513
- tokens.push(normalized);
514
- }
515
- }
516
- return tokens;
517
- }
518
- function uniqueTokens(values) {
519
- return [...new Set(values)];
520
- }
521
- function pathTokens(paths) {
522
- if (!Array.isArray(paths) || paths.length === 0)
34
+ export async function readKnowledgeLog(projectRoot) {
35
+ const target = knowledgeLogPath(projectRoot);
36
+ if (!(await exists(target)))
523
37
  return [];
524
- const tokens = [];
525
- for (const filePath of paths) {
526
- tokens.push(...tokenizeText(filePath));
527
- }
528
- return uniqueTokens(tokens);
529
- }
530
- export async function selectRelevantLearnings(projectRoot, options = {}) {
531
- const { entries } = await readKnowledgeSafely(projectRoot);
532
- if (entries.length === 0) {
533
- return [];
534
- }
535
- const stage = options.stage ?? null;
536
- const branchTokens = tokenizeText(options.branch ?? null);
537
- const diffTokens = pathTokens(options.diffFiles);
538
- const gateTokens = pathTokens(options.openGates);
539
- const limit = typeof options.limit === "number" && Number.isFinite(options.limit) && options.limit > 0
540
- ? Math.floor(options.limit)
541
- : 8;
542
- const ranked = entries.map((entry, index) => {
543
- let score = 0;
544
- let stageScore = 0;
545
- if (stage) {
546
- if (entry.stage === stage) {
547
- stageScore = 4;
548
- }
549
- else if (entry.origin_stage === stage) {
550
- stageScore = 3;
551
- }
552
- score += stageScore;
553
- }
554
- if (entry.confidence === "high")
555
- score += 2;
556
- if (entry.confidence === "medium")
557
- score += 1;
558
- if (entry.frequency >= 3)
559
- score += 1;
560
- const searchable = [
561
- ...tokenizeText(entry.trigger),
562
- ...tokenizeText(entry.action),
563
- ...tokenizeText(entry.project)
564
- ];
565
- const searchSet = new Set(searchable);
566
- let contextualScore = 0;
567
- for (const token of branchTokens) {
568
- if (searchSet.has(token))
569
- contextualScore += 2;
570
- }
571
- for (const token of diffTokens) {
572
- if (searchSet.has(token))
573
- contextualScore += 2;
574
- }
575
- for (const token of gateTokens) {
576
- if (searchSet.has(token))
577
- contextualScore += 2;
578
- }
579
- score += contextualScore;
580
- if (stage && entry.stage === null && stageScore === 0 && contextualScore < 4) {
581
- score = 0;
582
- }
583
- return {
584
- index,
585
- score,
586
- entry
587
- };
588
- });
589
- ranked.sort((a, b) => {
590
- if (b.score !== a.score)
591
- return b.score - a.score;
592
- const bySeen = Date.parse(b.entry.last_seen_ts) - Date.parse(a.entry.last_seen_ts);
593
- if (!Number.isNaN(bySeen) && bySeen !== 0)
594
- return bySeen;
595
- if (b.entry.frequency !== a.entry.frequency)
596
- return b.entry.frequency - a.entry.frequency;
597
- return b.index - a.index;
598
- });
599
- return ranked
600
- .filter((row) => row.score > 0)
601
- .slice(0, limit)
602
- .map((row) => row.entry);
38
+ const raw = await fs.readFile(target, "utf8");
39
+ const lines = raw.split(/\r?\n/u).filter((line) => line.trim().length > 0);
40
+ const entries = [];
41
+ for (const line of lines) {
42
+ let parsed;
43
+ try {
44
+ parsed = JSON.parse(line);
45
+ }
46
+ catch (err) {
47
+ throw new KnowledgeStoreError(`Invalid JSON line in knowledge.jsonl: ${err.message}`);
48
+ }
49
+ assertEntry(parsed);
50
+ entries.push(parsed);
51
+ }
52
+ return entries;
53
+ }
54
+ export async function findRefiningChain(projectRoot, slug) {
55
+ const all = await readKnowledgeLog(projectRoot);
56
+ const bySlug = new Map();
57
+ for (const entry of all)
58
+ bySlug.set(entry.slug, entry);
59
+ const chain = [];
60
+ let cursor = slug;
61
+ const seen = new Set();
62
+ while (cursor !== null && cursor !== undefined && bySlug.has(cursor) && !seen.has(cursor)) {
63
+ const found = bySlug.get(cursor);
64
+ chain.push(found);
65
+ seen.add(cursor);
66
+ cursor = found.refines ?? null;
67
+ }
68
+ return chain;
603
69
  }
package/dist/logger.d.ts CHANGED
@@ -1,3 +1,8 @@
1
- import type { CliContext } from "./types.js";
2
- export declare function info(ctx: CliContext, message: string): void;
3
- export declare function error(ctx: CliContext, message: string): void;
1
+ type Stream = NodeJS.WriteStream | {
2
+ write: (chunk: string) => unknown;
3
+ };
4
+ export declare function configureLogger(out: Stream, err: Stream): void;
5
+ export declare function info(message: string): void;
6
+ export declare function warn(message: string): void;
7
+ export declare function error(message: string): void;
8
+ export {};