@soleri/core 9.2.0 → 9.3.1

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 (316) hide show
  1. package/data/flows/build.flow.yaml +8 -9
  2. package/data/flows/deliver.flow.yaml +9 -10
  3. package/data/flows/design.flow.yaml +3 -4
  4. package/data/flows/enhance.flow.yaml +5 -6
  5. package/data/flows/explore.flow.yaml +3 -4
  6. package/data/flows/fix.flow.yaml +5 -6
  7. package/data/flows/plan.flow.yaml +4 -5
  8. package/data/flows/review.flow.yaml +3 -4
  9. package/dist/curator/curator.d.ts.map +1 -1
  10. package/dist/curator/curator.js +98 -22
  11. package/dist/curator/curator.js.map +1 -1
  12. package/dist/engine/bin/soleri-engine.js.map +1 -1
  13. package/dist/engine/module-manifest.d.ts +2 -0
  14. package/dist/engine/module-manifest.d.ts.map +1 -1
  15. package/dist/engine/module-manifest.js +136 -1
  16. package/dist/engine/module-manifest.js.map +1 -1
  17. package/dist/engine/register-engine.d.ts.map +1 -1
  18. package/dist/engine/register-engine.js +25 -1
  19. package/dist/engine/register-engine.js.map +1 -1
  20. package/dist/flows/gate-evaluator.js.map +1 -1
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/operator/operator-profile.d.ts.map +1 -1
  26. package/dist/operator/operator-profile.js +11 -5
  27. package/dist/operator/operator-profile.js.map +1 -1
  28. package/dist/operator/operator-signals.d.ts.map +1 -1
  29. package/dist/operator/operator-signals.js.map +1 -1
  30. package/dist/planning/evidence-collector.js.map +1 -1
  31. package/dist/planning/gap-passes.d.ts.map +1 -1
  32. package/dist/planning/gap-passes.js +23 -6
  33. package/dist/planning/gap-passes.js.map +1 -1
  34. package/dist/planning/gap-patterns.d.ts.map +1 -1
  35. package/dist/planning/gap-patterns.js +57 -11
  36. package/dist/planning/gap-patterns.js.map +1 -1
  37. package/dist/planning/github-projection.d.ts.map +1 -1
  38. package/dist/planning/github-projection.js +39 -20
  39. package/dist/planning/github-projection.js.map +1 -1
  40. package/dist/planning/impact-analyzer.d.ts.map +1 -1
  41. package/dist/planning/impact-analyzer.js +20 -18
  42. package/dist/planning/impact-analyzer.js.map +1 -1
  43. package/dist/planning/plan-lifecycle.d.ts.map +1 -1
  44. package/dist/planning/plan-lifecycle.js +22 -9
  45. package/dist/planning/plan-lifecycle.js.map +1 -1
  46. package/dist/planning/planner.d.ts.map +1 -1
  47. package/dist/planning/planner.js +60 -17
  48. package/dist/planning/planner.js.map +1 -1
  49. package/dist/planning/rationalization-detector.d.ts.map +1 -1
  50. package/dist/planning/rationalization-detector.js.map +1 -1
  51. package/dist/planning/reconciliation-engine.d.ts.map +1 -1
  52. package/dist/planning/reconciliation-engine.js.map +1 -1
  53. package/dist/planning/task-complexity-assessor.d.ts +42 -0
  54. package/dist/planning/task-complexity-assessor.d.ts.map +1 -0
  55. package/dist/planning/task-complexity-assessor.js +132 -0
  56. package/dist/planning/task-complexity-assessor.js.map +1 -0
  57. package/dist/planning/task-verifier.d.ts.map +1 -1
  58. package/dist/planning/task-verifier.js +14 -6
  59. package/dist/planning/task-verifier.js.map +1 -1
  60. package/dist/runtime/admin-ops.d.ts.map +1 -1
  61. package/dist/runtime/admin-ops.js +18 -0
  62. package/dist/runtime/admin-ops.js.map +1 -1
  63. package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
  64. package/dist/runtime/admin-setup-ops.js +2 -1
  65. package/dist/runtime/admin-setup-ops.js.map +1 -1
  66. package/dist/runtime/branching-ops.d.ts +12 -0
  67. package/dist/runtime/branching-ops.d.ts.map +1 -0
  68. package/dist/runtime/branching-ops.js +100 -0
  69. package/dist/runtime/branching-ops.js.map +1 -0
  70. package/dist/runtime/context-health.d.ts.map +1 -1
  71. package/dist/runtime/context-health.js.map +1 -1
  72. package/dist/runtime/facades/branching-facade.d.ts +7 -0
  73. package/dist/runtime/facades/branching-facade.d.ts.map +1 -0
  74. package/dist/runtime/facades/branching-facade.js +8 -0
  75. package/dist/runtime/facades/branching-facade.js.map +1 -0
  76. package/dist/runtime/facades/chat-service-ops.d.ts.map +1 -1
  77. package/dist/runtime/facades/chat-service-ops.js +3 -1
  78. package/dist/runtime/facades/chat-service-ops.js.map +1 -1
  79. package/dist/runtime/facades/chat-transport-ops.d.ts.map +1 -1
  80. package/dist/runtime/facades/chat-transport-ops.js.map +1 -1
  81. package/dist/runtime/facades/index.d.ts.map +1 -1
  82. package/dist/runtime/facades/index.js +42 -0
  83. package/dist/runtime/facades/index.js.map +1 -1
  84. package/dist/runtime/facades/intake-facade.d.ts +9 -0
  85. package/dist/runtime/facades/intake-facade.d.ts.map +1 -0
  86. package/dist/runtime/facades/intake-facade.js +11 -0
  87. package/dist/runtime/facades/intake-facade.js.map +1 -0
  88. package/dist/runtime/facades/links-facade.d.ts +9 -0
  89. package/dist/runtime/facades/links-facade.d.ts.map +1 -0
  90. package/dist/runtime/facades/links-facade.js +10 -0
  91. package/dist/runtime/facades/links-facade.js.map +1 -0
  92. package/dist/runtime/facades/operator-facade.d.ts.map +1 -1
  93. package/dist/runtime/facades/operator-facade.js.map +1 -1
  94. package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
  95. package/dist/runtime/facades/plan-facade.js +4 -1
  96. package/dist/runtime/facades/plan-facade.js.map +1 -1
  97. package/dist/runtime/facades/tier-facade.d.ts +7 -0
  98. package/dist/runtime/facades/tier-facade.d.ts.map +1 -0
  99. package/dist/runtime/facades/tier-facade.js +8 -0
  100. package/dist/runtime/facades/tier-facade.js.map +1 -0
  101. package/dist/runtime/facades/vault-facade.d.ts +9 -1
  102. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  103. package/dist/runtime/facades/vault-facade.js +44 -187
  104. package/dist/runtime/facades/vault-facade.js.map +1 -1
  105. package/dist/runtime/github-integration.d.ts.map +1 -1
  106. package/dist/runtime/github-integration.js +11 -4
  107. package/dist/runtime/github-integration.js.map +1 -1
  108. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  109. package/dist/runtime/orchestrate-ops.js +75 -42
  110. package/dist/runtime/orchestrate-ops.js.map +1 -1
  111. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  112. package/dist/runtime/planning-extra-ops.js.map +1 -1
  113. package/dist/runtime/runtime.d.ts.map +1 -1
  114. package/dist/runtime/runtime.js +3 -1
  115. package/dist/runtime/runtime.js.map +1 -1
  116. package/dist/runtime/session-briefing.d.ts.map +1 -1
  117. package/dist/runtime/session-briefing.js +5 -1
  118. package/dist/runtime/session-briefing.js.map +1 -1
  119. package/dist/runtime/tier-ops.d.ts +13 -0
  120. package/dist/runtime/tier-ops.d.ts.map +1 -0
  121. package/dist/runtime/tier-ops.js +110 -0
  122. package/dist/runtime/tier-ops.js.map +1 -0
  123. package/dist/skills/sync-skills.d.ts.map +1 -1
  124. package/dist/skills/sync-skills.js +1 -1
  125. package/dist/skills/sync-skills.js.map +1 -1
  126. package/dist/vault/linking.d.ts.map +1 -1
  127. package/dist/vault/linking.js +41 -5
  128. package/dist/vault/linking.js.map +1 -1
  129. package/dist/vault/vault-entries.d.ts.map +1 -1
  130. package/dist/vault/vault-entries.js +68 -26
  131. package/dist/vault/vault-entries.js.map +1 -1
  132. package/dist/vault/vault-maintenance.d.ts.map +1 -1
  133. package/dist/vault/vault-maintenance.js +6 -2
  134. package/dist/vault/vault-maintenance.js.map +1 -1
  135. package/dist/vault/vault-markdown-sync.d.ts.map +1 -1
  136. package/dist/vault/vault-markdown-sync.js.map +1 -1
  137. package/dist/vault/vault-memories.d.ts.map +1 -1
  138. package/dist/vault/vault-memories.js +3 -1
  139. package/dist/vault/vault-memories.js.map +1 -1
  140. package/dist/vault/vault-schema.js +36 -10
  141. package/dist/vault/vault-schema.js.map +1 -1
  142. package/dist/vault/vault.d.ts.map +1 -1
  143. package/dist/vault/vault.js +5 -1
  144. package/dist/vault/vault.js.map +1 -1
  145. package/package.json +7 -7
  146. package/src/agency/agency-manager.test.ts +60 -40
  147. package/src/agency/default-rules.test.ts +17 -9
  148. package/src/capabilities/registry.test.ts +2 -12
  149. package/src/chat/agent-loop.test.ts +33 -43
  150. package/src/chat/mcp-bridge.test.ts +7 -2
  151. package/src/claudemd/inject.test.ts +2 -12
  152. package/src/context/context-engine.test.ts +96 -51
  153. package/src/control/intent-router.test.ts +3 -3
  154. package/src/curator/classifier.test.ts +14 -8
  155. package/src/curator/contradiction-detector.test.ts +30 -5
  156. package/src/curator/curator.ts +278 -56
  157. package/src/curator/duplicate-detector.test.ts +77 -15
  158. package/src/curator/quality-gate.test.ts +71 -31
  159. package/src/curator/tag-manager.test.ts +12 -4
  160. package/src/domain-packs/knowledge-installer.test.ts +2 -10
  161. package/src/domain-packs/token-resolver.test.ts +1 -3
  162. package/src/domain-packs/types.test.ts +16 -2
  163. package/src/enforcement/registry.test.ts +2 -8
  164. package/src/engine/bin/soleri-engine.ts +3 -1
  165. package/src/engine/module-manifest.test.ts +48 -4
  166. package/src/engine/module-manifest.ts +138 -1
  167. package/src/engine/register-engine.test.ts +6 -1
  168. package/src/engine/register-engine.ts +26 -3
  169. package/src/errors/classify.test.ts +6 -2
  170. package/src/errors/retry.test.ts +1 -4
  171. package/src/facades/facade-factory.test.ts +110 -64
  172. package/src/flows/epilogue.test.ts +16 -10
  173. package/src/flows/gate-evaluator.test.ts +12 -6
  174. package/src/flows/gate-evaluator.ts +1 -3
  175. package/src/governance/governance.test.ts +137 -21
  176. package/src/health/health-registry.test.ts +8 -1
  177. package/src/index.ts +8 -0
  178. package/src/intake/content-classifier.test.ts +121 -51
  179. package/src/intake/dedup-gate.test.ts +38 -22
  180. package/src/intake/intake-pipeline.test.ts +5 -3
  181. package/src/intake/text-ingester.test.ts +26 -20
  182. package/src/llm/key-pool.test.ts +1 -3
  183. package/src/llm/llm-client.test.ts +1 -4
  184. package/src/llm/oauth-discovery.test.ts +16 -16
  185. package/src/llm/utils.test.ts +62 -18
  186. package/src/logging/logger.test.ts +4 -1
  187. package/src/loop/loop-manager.test.ts +2 -6
  188. package/src/migrations/migration-runner.edge-cases.test.ts +2 -7
  189. package/src/operator/operator-profile-extended.test.ts +15 -5
  190. package/src/operator/operator-profile.test.ts +26 -8
  191. package/src/operator/operator-profile.ts +38 -22
  192. package/src/operator/operator-signals-extended.test.ts +35 -23
  193. package/src/operator/operator-signals.test.ts +6 -10
  194. package/src/operator/operator-signals.ts +2 -1
  195. package/src/operator/prompts/hook-precompact-operator-dispatch.md +10 -6
  196. package/src/operator/prompts/subagent-soft-signal-extractor.md +5 -0
  197. package/src/operator/prompts/subagent-synthesis-cognition.md +19 -10
  198. package/src/operator/prompts/subagent-synthesis-communication.md +13 -7
  199. package/src/operator/prompts/subagent-synthesis-technical.md +19 -9
  200. package/src/operator/prompts/subagent-synthesis-trust.md +27 -21
  201. package/src/persona/defaults.test.ts +1 -5
  202. package/src/planning/evidence-collector.test.ts +147 -38
  203. package/src/planning/evidence-collector.ts +1 -4
  204. package/src/planning/gap-analysis-alternatives.test.ts +41 -11
  205. package/src/planning/gap-passes.test.ts +215 -33
  206. package/src/planning/gap-passes.ts +115 -46
  207. package/src/planning/gap-patterns.test.ts +87 -13
  208. package/src/planning/gap-patterns.ts +114 -31
  209. package/src/planning/github-projection.test.ts +6 -1
  210. package/src/planning/github-projection.ts +41 -20
  211. package/src/planning/impact-analyzer.test.ts +10 -23
  212. package/src/planning/impact-analyzer.ts +33 -46
  213. package/src/planning/plan-lifecycle.test.ts +103 -36
  214. package/src/planning/plan-lifecycle.ts +49 -18
  215. package/src/planning/planner.test.ts +12 -2
  216. package/src/planning/planner.ts +198 -58
  217. package/src/planning/rationalization-detector.test.ts +5 -20
  218. package/src/planning/rationalization-detector.ts +14 -16
  219. package/src/planning/reconciliation-engine.test.ts +20 -3
  220. package/src/planning/reconciliation-engine.ts +1 -2
  221. package/src/planning/task-complexity-assessor.test.ts +298 -0
  222. package/src/planning/task-complexity-assessor.ts +183 -0
  223. package/src/planning/task-verifier.test.ts +59 -27
  224. package/src/planning/task-verifier.ts +15 -9
  225. package/src/playbooks/playbook-executor.test.ts +1 -3
  226. package/src/plugins/plugin-loader.test.ts +19 -14
  227. package/src/plugins/plugin-registry.test.ts +45 -33
  228. package/src/project/project-registry.test.ts +23 -12
  229. package/src/prompts/template-manager.test.ts +4 -1
  230. package/src/queue/job-queue.test.ts +10 -14
  231. package/src/runtime/admin-extra-ops.test.ts +5 -19
  232. package/src/runtime/admin-ops.test.ts +22 -1
  233. package/src/runtime/admin-ops.ts +19 -0
  234. package/src/runtime/admin-setup-ops.test.ts +3 -4
  235. package/src/runtime/admin-setup-ops.ts +9 -2
  236. package/src/runtime/archive-ops.test.ts +4 -1
  237. package/src/runtime/branching-ops.test.ts +144 -0
  238. package/src/runtime/branching-ops.ts +107 -0
  239. package/src/runtime/capture-ops.test.ts +7 -21
  240. package/src/runtime/chain-ops.test.ts +16 -6
  241. package/src/runtime/claude-md-helpers.test.ts +1 -3
  242. package/src/runtime/context-health.test.ts +1 -3
  243. package/src/runtime/context-health.ts +1 -3
  244. package/src/runtime/curator-extra-ops.test.ts +3 -1
  245. package/src/runtime/domain-ops.test.ts +46 -36
  246. package/src/runtime/facades/admin-facade.test.ts +1 -4
  247. package/src/runtime/facades/archive-facade.test.ts +21 -7
  248. package/src/runtime/facades/brain-facade.test.ts +176 -72
  249. package/src/runtime/facades/branching-facade.test.ts +43 -0
  250. package/src/runtime/facades/branching-facade.ts +11 -0
  251. package/src/runtime/facades/chat-facade.test.ts +81 -28
  252. package/src/runtime/facades/chat-service-ops.test.ts +178 -73
  253. package/src/runtime/facades/chat-service-ops.ts +3 -1
  254. package/src/runtime/facades/chat-session-ops.test.ts +25 -10
  255. package/src/runtime/facades/chat-transport-ops.test.ts +101 -34
  256. package/src/runtime/facades/chat-transport-ops.ts +0 -1
  257. package/src/runtime/facades/context-facade.test.ts +19 -4
  258. package/src/runtime/facades/control-facade.test.ts +3 -3
  259. package/src/runtime/facades/index.ts +42 -0
  260. package/src/runtime/facades/intake-facade.test.ts +215 -0
  261. package/src/runtime/facades/intake-facade.ts +14 -0
  262. package/src/runtime/facades/links-facade.test.ts +203 -0
  263. package/src/runtime/facades/links-facade.ts +13 -0
  264. package/src/runtime/facades/loop-facade.test.ts +22 -5
  265. package/src/runtime/facades/memory-facade.test.ts +19 -5
  266. package/src/runtime/facades/operator-facade.test.ts +17 -4
  267. package/src/runtime/facades/operator-facade.ts +11 -3
  268. package/src/runtime/facades/orchestrate-facade.test.ts +7 -1
  269. package/src/runtime/facades/plan-facade.test.ts +29 -12
  270. package/src/runtime/facades/plan-facade.ts +7 -2
  271. package/src/runtime/facades/tier-facade.test.ts +47 -0
  272. package/src/runtime/facades/tier-facade.ts +11 -0
  273. package/src/runtime/facades/vault-facade.test.ts +174 -242
  274. package/src/runtime/facades/vault-facade.ts +55 -199
  275. package/src/runtime/github-integration.ts +11 -8
  276. package/src/runtime/grading-ops.test.ts +39 -8
  277. package/src/runtime/intake-ops.test.ts +69 -16
  278. package/src/runtime/loop-ops.test.ts +16 -6
  279. package/src/runtime/memory-cross-project-ops.test.ts +25 -14
  280. package/src/runtime/orchestrate-ops.test.ts +204 -0
  281. package/src/runtime/orchestrate-ops.ts +103 -65
  282. package/src/runtime/pack-ops.test.ts +23 -6
  283. package/src/runtime/planning-extra-ops.test.ts +17 -7
  284. package/src/runtime/planning-extra-ops.ts +3 -1
  285. package/src/runtime/playbook-ops.test.ts +26 -3
  286. package/src/runtime/plugin-ops.test.ts +83 -25
  287. package/src/runtime/project-ops.test.ts +26 -6
  288. package/src/runtime/runtime.ts +3 -1
  289. package/src/runtime/session-briefing.test.ts +183 -54
  290. package/src/runtime/session-briefing.ts +8 -2
  291. package/src/runtime/sync-ops.test.ts +3 -12
  292. package/src/runtime/telemetry-ops.test.ts +31 -6
  293. package/src/runtime/tier-ops.test.ts +159 -0
  294. package/src/runtime/tier-ops.ts +119 -0
  295. package/src/runtime/vault-extra-ops.test.ts +32 -8
  296. package/src/runtime/vault-sharing-ops.test.ts +1 -4
  297. package/src/skills/sync-skills.ts +2 -12
  298. package/src/transport/ws-server.test.ts +7 -4
  299. package/src/vault/__tests__/vault-characterization.test.ts +492 -81
  300. package/src/vault/linking.test.ts +50 -17
  301. package/src/vault/linking.ts +48 -7
  302. package/src/vault/obsidian-sync.test.ts +6 -3
  303. package/src/vault/scope-detector.test.ts +1 -3
  304. package/src/vault/vault-branching.test.ts +9 -7
  305. package/src/vault/vault-entries.ts +209 -65
  306. package/src/vault/vault-maintenance.ts +7 -12
  307. package/src/vault/vault-manager.test.ts +10 -10
  308. package/src/vault/vault-markdown-sync.ts +4 -1
  309. package/src/vault/vault-memories.ts +7 -7
  310. package/src/vault/vault-scaling.test.ts +5 -5
  311. package/src/vault/vault-schema.ts +72 -15
  312. package/src/vault/vault.ts +55 -9
  313. package/src/brain/strength-scorer.ts +0 -404
  314. package/src/engine/index.ts +0 -21
  315. package/src/persona/index.ts +0 -9
  316. package/src/vault/vault-interfaces.ts +0 -56
package/src/index.ts CHANGED
@@ -335,6 +335,14 @@ export {
335
335
  } from './planning/gap-types.js';
336
336
  export type { GapSeverity, GapCategory, PlanGap } from './planning/gap-types.js';
337
337
 
338
+ // ─── Task Complexity Assessor ────────────────────────────────────────
339
+ export { assessTaskComplexity } from './planning/task-complexity-assessor.js';
340
+ export type {
341
+ AssessmentInput,
342
+ AssessmentSignal,
343
+ AssessmentResult,
344
+ } from './planning/task-complexity-assessor.js';
345
+
338
346
  // ─── GitHub Projection ───────────────────────────────────────────────
339
347
  export {
340
348
  parseGitHubRemote,
@@ -22,7 +22,9 @@ function mockLLM(response: string): LLMClient {
22
22
 
23
23
  function throwingLLM(error: Error): LLMClient {
24
24
  return {
25
- complete: async () => { throw error; },
25
+ complete: async () => {
26
+ throw error;
27
+ },
26
28
  isAvailable: () => ({ openai: true, anthropic: false }),
27
29
  getRoutes: () => [],
28
30
  } as unknown as LLMClient;
@@ -34,15 +36,17 @@ function throwingLLM(error: Error): LLMClient {
34
36
 
35
37
  describe('classifyChunk', () => {
36
38
  it('parses valid JSON array response into ClassifiedItems', async () => {
37
- const llm = mockLLM(JSON.stringify([
38
- {
39
- type: 'pattern',
40
- title: 'Test Pattern',
41
- description: 'A useful design pattern.',
42
- tags: ['design', 'architecture'],
43
- severity: 'suggestion',
44
- },
45
- ]));
39
+ const llm = mockLLM(
40
+ JSON.stringify([
41
+ {
42
+ type: 'pattern',
43
+ title: 'Test Pattern',
44
+ description: 'A useful design pattern.',
45
+ tags: ['design', 'architecture'],
46
+ severity: 'suggestion',
47
+ },
48
+ ]),
49
+ );
46
50
 
47
51
  const result = await classifyChunk(llm, 'some text', 'page 1');
48
52
 
@@ -56,7 +60,9 @@ describe('classifyChunk', () => {
56
60
  });
57
61
 
58
62
  it('handles markdown fenced JSON responses', async () => {
59
- const llm = mockLLM('```json\n[{"type":"pattern","title":"Fenced","description":"Inside fences.","tags":["test"],"severity":"warning"}]\n```');
63
+ const llm = mockLLM(
64
+ '```json\n[{"type":"pattern","title":"Fenced","description":"Inside fences.","tags":["test"],"severity":"warning"}]\n```',
65
+ );
60
66
 
61
67
  const result = await classifyChunk(llm, 'text', 'cite');
62
68
 
@@ -89,10 +95,24 @@ describe('classifyChunk', () => {
89
95
  });
90
96
 
91
97
  it('filters out items with invalid type', async () => {
92
- const llm = mockLLM(JSON.stringify([
93
- { type: 'invalid-type', title: 'Bad', description: 'Bad type', tags: [], severity: 'suggestion' },
94
- { type: 'pattern', title: 'Good', description: 'Good item.', tags: [], severity: 'suggestion' },
95
- ]));
98
+ const llm = mockLLM(
99
+ JSON.stringify([
100
+ {
101
+ type: 'invalid-type',
102
+ title: 'Bad',
103
+ description: 'Bad type',
104
+ tags: [],
105
+ severity: 'suggestion',
106
+ },
107
+ {
108
+ type: 'pattern',
109
+ title: 'Good',
110
+ description: 'Good item.',
111
+ tags: [],
112
+ severity: 'suggestion',
113
+ },
114
+ ]),
115
+ );
96
116
 
97
117
  const result = await classifyChunk(llm, 'text', 'cite');
98
118
 
@@ -101,11 +121,19 @@ describe('classifyChunk', () => {
101
121
  });
102
122
 
103
123
  it('filters out items missing required fields (title, description)', async () => {
104
- const llm = mockLLM(JSON.stringify([
105
- { type: 'pattern', title: '', description: 'No title', tags: [], severity: 'suggestion' },
106
- { type: 'pattern', title: 'No desc', description: '', tags: [], severity: 'suggestion' },
107
- { type: 'pattern', title: 'Valid', description: 'Valid item.', tags: [], severity: 'suggestion' },
108
- ]));
124
+ const llm = mockLLM(
125
+ JSON.stringify([
126
+ { type: 'pattern', title: '', description: 'No title', tags: [], severity: 'suggestion' },
127
+ { type: 'pattern', title: 'No desc', description: '', tags: [], severity: 'suggestion' },
128
+ {
129
+ type: 'pattern',
130
+ title: 'Valid',
131
+ description: 'Valid item.',
132
+ tags: [],
133
+ severity: 'suggestion',
134
+ },
135
+ ]),
136
+ );
109
137
 
110
138
  const result = await classifyChunk(llm, 'text', 'cite');
111
139
 
@@ -115,9 +143,17 @@ describe('classifyChunk', () => {
115
143
 
116
144
  it('truncates title to 80 characters', async () => {
117
145
  const longTitle = 'A'.repeat(100);
118
- const llm = mockLLM(JSON.stringify([
119
- { type: 'pattern', title: longTitle, description: 'Desc.', tags: [], severity: 'suggestion' },
120
- ]));
146
+ const llm = mockLLM(
147
+ JSON.stringify([
148
+ {
149
+ type: 'pattern',
150
+ title: longTitle,
151
+ description: 'Desc.',
152
+ tags: [],
153
+ severity: 'suggestion',
154
+ },
155
+ ]),
156
+ );
121
157
 
122
158
  const result = await classifyChunk(llm, 'text', 'cite');
123
159
 
@@ -126,26 +162,36 @@ describe('classifyChunk', () => {
126
162
  });
127
163
 
128
164
  it('caps tags at 5 and lowercases them', async () => {
129
- const llm = mockLLM(JSON.stringify([
130
- {
131
- type: 'pattern',
132
- title: 'Tagged',
133
- description: 'Many tags.',
134
- tags: ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven'],
135
- severity: 'suggestion',
136
- },
137
- ]));
165
+ const llm = mockLLM(
166
+ JSON.stringify([
167
+ {
168
+ type: 'pattern',
169
+ title: 'Tagged',
170
+ description: 'Many tags.',
171
+ tags: ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven'],
172
+ severity: 'suggestion',
173
+ },
174
+ ]),
175
+ );
138
176
 
139
177
  const result = await classifyChunk(llm, 'text', 'cite');
140
178
 
141
179
  expect(result[0].tags).toHaveLength(5);
142
- expect(result[0].tags.every(t => t === t.toLowerCase())).toBe(true);
180
+ expect(result[0].tags.every((t) => t === t.toLowerCase())).toBe(true);
143
181
  });
144
182
 
145
183
  it('defaults severity to suggestion for invalid values', async () => {
146
- const llm = mockLLM(JSON.stringify([
147
- { type: 'pattern', title: 'Bad Sev', description: 'Unknown sev.', tags: [], severity: 'unknown' },
148
- ]));
184
+ const llm = mockLLM(
185
+ JSON.stringify([
186
+ {
187
+ type: 'pattern',
188
+ title: 'Bad Sev',
189
+ description: 'Unknown sev.',
190
+ tags: [],
191
+ severity: 'unknown',
192
+ },
193
+ ]),
194
+ );
149
195
 
150
196
  const result = await classifyChunk(llm, 'text', 'cite');
151
197
 
@@ -153,24 +199,40 @@ describe('classifyChunk', () => {
153
199
  });
154
200
 
155
201
  it('handles all valid severity values', async () => {
156
- const llm = mockLLM(JSON.stringify([
157
- { type: 'pattern', title: 'Critical', description: 'Crit.', tags: [], severity: 'critical' },
158
- { type: 'pattern', title: 'Warning', description: 'Warn.', tags: [], severity: 'warning' },
159
- { type: 'pattern', title: 'Suggestion', description: 'Sug.', tags: [], severity: 'suggestion' },
160
- ]));
202
+ const llm = mockLLM(
203
+ JSON.stringify([
204
+ {
205
+ type: 'pattern',
206
+ title: 'Critical',
207
+ description: 'Crit.',
208
+ tags: [],
209
+ severity: 'critical',
210
+ },
211
+ { type: 'pattern', title: 'Warning', description: 'Warn.', tags: [], severity: 'warning' },
212
+ {
213
+ type: 'pattern',
214
+ title: 'Suggestion',
215
+ description: 'Sug.',
216
+ tags: [],
217
+ severity: 'suggestion',
218
+ },
219
+ ]),
220
+ );
161
221
 
162
222
  const result = await classifyChunk(llm, 'text', 'cite');
163
223
 
164
- expect(result.map(r => r.severity)).toEqual(['critical', 'warning', 'suggestion']);
224
+ expect(result.map((r) => r.severity)).toEqual(['critical', 'warning', 'suggestion']);
165
225
  });
166
226
 
167
227
  it('filters out non-object items in the array', async () => {
168
- const llm = mockLLM(JSON.stringify([
169
- null,
170
- 42,
171
- 'string',
172
- { type: 'pattern', title: 'Valid', description: 'OK.', tags: [], severity: 'suggestion' },
173
- ]));
228
+ const llm = mockLLM(
229
+ JSON.stringify([
230
+ null,
231
+ 42,
232
+ 'string',
233
+ { type: 'pattern', title: 'Valid', description: 'OK.', tags: [], severity: 'suggestion' },
234
+ ]),
235
+ );
174
236
 
175
237
  const result = await classifyChunk(llm, 'text', 'cite');
176
238
 
@@ -179,9 +241,17 @@ describe('classifyChunk', () => {
179
241
  });
180
242
 
181
243
  it('filters non-string tags from the tags array', async () => {
182
- const llm = mockLLM(JSON.stringify([
183
- { type: 'pattern', title: 'Mixed Tags', description: 'OK.', tags: ['valid', 42, null, 'also-valid'], severity: 'suggestion' },
184
- ]));
244
+ const llm = mockLLM(
245
+ JSON.stringify([
246
+ {
247
+ type: 'pattern',
248
+ title: 'Mixed Tags',
249
+ description: 'OK.',
250
+ tags: ['valid', 42, null, 'also-valid'],
251
+ severity: 'suggestion',
252
+ },
253
+ ]),
254
+ );
185
255
 
186
256
  const result = await classifyChunk(llm, 'text', 'cite');
187
257
 
@@ -10,7 +10,7 @@ import type { ClassifiedItem } from './types.js';
10
10
  function mockVault(entries: Array<{ id: string; title: string; description: string }> = []): Vault {
11
11
  return {
12
12
  exportAll: () => ({
13
- entries: entries.map(e => ({
13
+ entries: entries.map((e) => ({
14
14
  id: e.id,
15
15
  title: e.title,
16
16
  description: e.description,
@@ -49,17 +49,19 @@ describe('dedupItems — colocated', () => {
49
49
  const results = dedupItems(items, vault);
50
50
 
51
51
  expect(results).toHaveLength(2);
52
- expect(results.every(r => !r.isDuplicate)).toBe(true);
53
- expect(results.every(r => r.similarity === 0)).toBe(true);
54
- expect(results.every(r => r.bestMatchId === undefined)).toBe(true);
52
+ expect(results.every((r) => !r.isDuplicate)).toBe(true);
53
+ expect(results.every((r) => r.similarity === 0)).toBe(true);
54
+ expect(results.every((r) => r.bestMatchId === undefined)).toBe(true);
55
55
  });
56
56
 
57
57
  it('detects exact duplicate text as duplicate', () => {
58
- const vault = mockVault([{
59
- id: 'existing-1',
60
- title: 'Singleton Pattern',
61
- description: 'The singleton pattern ensures a class has only one instance.',
62
- }]);
58
+ const vault = mockVault([
59
+ {
60
+ id: 'existing-1',
61
+ title: 'Singleton Pattern',
62
+ description: 'The singleton pattern ensures a class has only one instance.',
63
+ },
64
+ ]);
63
65
 
64
66
  const items = [
65
67
  makeItem('Singleton Pattern', 'The singleton pattern ensures a class has only one instance.'),
@@ -74,14 +76,19 @@ describe('dedupItems — colocated', () => {
74
76
  });
75
77
 
76
78
  it('does not flag dissimilar items as duplicates', () => {
77
- const vault = mockVault([{
78
- id: 'existing-2',
79
- title: 'Observer Pattern',
80
- description: 'Observer defines one-to-many dependency for event-driven communication.',
81
- }]);
79
+ const vault = mockVault([
80
+ {
81
+ id: 'existing-2',
82
+ title: 'Observer Pattern',
83
+ description: 'Observer defines one-to-many dependency for event-driven communication.',
84
+ },
85
+ ]);
82
86
 
83
87
  const items = [
84
- makeItem('Circuit Breaker', 'A resilience pattern that prevents cascading failures in distributed systems.'),
88
+ makeItem(
89
+ 'Circuit Breaker',
90
+ 'A resilience pattern that prevents cascading failures in distributed systems.',
91
+ ),
85
92
  ];
86
93
 
87
94
  const results = dedupItems(items, vault);
@@ -103,17 +110,26 @@ describe('dedupItems — colocated', () => {
103
110
  });
104
111
 
105
112
  it('handles multiple items — some duplicate, some not', () => {
106
- const vault = mockVault([{
107
- id: 'v1',
108
- title: 'Factory Method Pattern',
109
- description: 'Factory method pattern provides interface for creating objects in a superclass.',
110
- }]);
113
+ const vault = mockVault([
114
+ {
115
+ id: 'v1',
116
+ title: 'Factory Method Pattern',
117
+ description:
118
+ 'Factory method pattern provides interface for creating objects in a superclass.',
119
+ },
120
+ ]);
111
121
 
112
122
  const items = [
113
123
  // Near-duplicate
114
- makeItem('Factory Method Pattern', 'Factory method pattern provides interface for creating objects in a superclass.'),
124
+ makeItem(
125
+ 'Factory Method Pattern',
126
+ 'Factory method pattern provides interface for creating objects in a superclass.',
127
+ ),
115
128
  // Different
116
- makeItem('Adapter Pattern', 'Adapter pattern allows incompatible interfaces to work together via a wrapper.'),
129
+ makeItem(
130
+ 'Adapter Pattern',
131
+ 'Adapter pattern allows incompatible interfaces to work together via a wrapper.',
132
+ ),
117
133
  ];
118
134
 
119
135
  const results = dedupItems(items, vault);
@@ -20,9 +20,11 @@ vi.mock('./content-classifier.js', () => ({
20
20
  }));
21
21
 
22
22
  vi.mock('./dedup-gate.js', () => ({
23
- dedupItems: vi.fn().mockImplementation((items: unknown[]) =>
24
- items.map((item) => ({ item, isDuplicate: false, similarity: 0 })),
25
- ),
23
+ dedupItems: vi
24
+ .fn()
25
+ .mockImplementation((items: unknown[]) =>
26
+ items.map((item) => ({ item, isDuplicate: false, similarity: 0 })),
27
+ ),
26
28
  }));
27
29
 
28
30
  vi.mock('node:fs', () => ({
@@ -13,13 +13,15 @@ import type { ClassifiedItem } from './types.js';
13
13
  function mockLLM(items: ClassifiedItem[]): LLMClient {
14
14
  return {
15
15
  complete: async (): Promise<LLMCallResult> => ({
16
- text: JSON.stringify(items.map(i => ({
17
- type: i.type,
18
- title: i.title,
19
- description: i.description,
20
- tags: i.tags,
21
- severity: i.severity,
22
- }))),
16
+ text: JSON.stringify(
17
+ items.map((i) => ({
18
+ type: i.type,
19
+ title: i.title,
20
+ description: i.description,
21
+ tags: i.tags,
22
+ severity: i.severity,
23
+ })),
24
+ ),
23
25
  model: 'mock',
24
26
  provider: 'openai' as const,
25
27
  durationMs: 0,
@@ -29,11 +31,13 @@ function mockLLM(items: ClassifiedItem[]): LLMClient {
29
31
  } as unknown as LLMClient;
30
32
  }
31
33
 
32
- function mockVault(existingEntries: Array<{ id: string; title: string; description: string }> = []): Vault {
34
+ function mockVault(
35
+ existingEntries: Array<{ id: string; title: string; description: string }> = [],
36
+ ): Vault {
33
37
  const seeded: unknown[] = [];
34
38
  return {
35
39
  exportAll: () => ({
36
- entries: existingEntries.map(e => ({
40
+ entries: existingEntries.map((e) => ({
37
41
  id: e.id,
38
42
  title: e.title,
39
43
  description: e.description,
@@ -43,7 +47,9 @@ function mockVault(existingEntries: Array<{ id: string; title: string; descripti
43
47
  tags: [],
44
48
  })),
45
49
  }),
46
- seed: (entries: unknown[]) => { seeded.push(...entries); },
50
+ seed: (entries: unknown[]) => {
51
+ seeded.push(...entries);
52
+ },
47
53
  add: vi.fn(),
48
54
  _seeded: seeded,
49
55
  } as unknown as Vault & { _seeded: unknown[] };
@@ -67,9 +73,7 @@ function makeItem(overrides: Partial<ClassifiedItem> = {}): ClassifiedItem {
67
73
 
68
74
  describe('TextIngester — ingestText', () => {
69
75
  it('classifies, deduplicates, and stores unique items in vault', async () => {
70
- const items = [
71
- makeItem({ title: 'Pattern A', description: 'Unique pattern about testing.' }),
72
- ];
76
+ const items = [makeItem({ title: 'Pattern A', description: 'Unique pattern about testing.' })];
73
77
  const llm = mockLLM(items);
74
78
  const vault = mockVault() as Vault & { _seeded: unknown[] };
75
79
  const ingester = new TextIngester(vault, llm);
@@ -126,13 +130,15 @@ describe('TextIngester — ingestText', () => {
126
130
  complete: async (): Promise<LLMCallResult> => {
127
131
  callCount.n++;
128
132
  return {
129
- text: JSON.stringify([{
130
- type: 'pattern',
131
- title: `Item ${callCount.n}`,
132
- description: 'A pattern.',
133
- tags: ['test'],
134
- severity: 'suggestion',
135
- }]),
133
+ text: JSON.stringify([
134
+ {
135
+ type: 'pattern',
136
+ title: `Item ${callCount.n}`,
137
+ description: 'A pattern.',
138
+ tags: ['test'],
139
+ severity: 'suggestion',
140
+ },
141
+ ]),
136
142
  model: 'mock',
137
143
  provider: 'openai' as const,
138
144
  durationMs: 0,
@@ -222,9 +222,7 @@ describe('loadKeyPoolConfig', () => {
222
222
 
223
223
  it('should use env vars for providers missing from keys.json', () => {
224
224
  vi.mocked(fs.existsSync).mockReturnValue(true);
225
- vi.mocked(fs.readFileSync).mockReturnValue(
226
- JSON.stringify({ openai: ['sk-file'] }),
227
- );
225
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ openai: ['sk-file'] }));
228
226
  process.env.ANTHROPIC_API_KEY = 'sk-env-ant';
229
227
 
230
228
  const config = loadKeyPoolConfig('test-agent');
@@ -7,10 +7,7 @@ import { LLMError } from './types.js';
7
7
  // HELPERS
8
8
  // =============================================================================
9
9
 
10
- function makeClient(opts?: {
11
- openaiKeys?: string[];
12
- anthropicKeys?: string[];
13
- }): LLMClient {
10
+ function makeClient(opts?: { openaiKeys?: string[]; anthropicKeys?: string[] }): LLMClient {
14
11
  const openai = new KeyPool({ keys: opts?.openaiKeys ?? [] });
15
12
  const anthropic = new KeyPool({ keys: opts?.anthropicKeys ?? [] });
16
13
  return new LLMClient(openai, anthropic);
@@ -37,7 +37,9 @@ describe('discoverAnthropicToken', () => {
37
37
  delete process.env.ANTHROPIC_API_KEY;
38
38
  vi.mocked(existsSync).mockReturnValue(false);
39
39
  vi.mocked(readFileSync).mockReturnValue('{}');
40
- vi.mocked(execFileSync).mockImplementation(() => { throw new Error('not found'); });
40
+ vi.mocked(execFileSync).mockImplementation(() => {
41
+ throw new Error('not found');
42
+ });
41
43
  vi.mocked(platform).mockReturnValue('darwin');
42
44
  });
43
45
 
@@ -69,18 +71,14 @@ describe('discoverAnthropicToken', () => {
69
71
 
70
72
  it('should return token from credentials file with direct accessToken', async () => {
71
73
  vi.mocked(existsSync).mockReturnValue(true);
72
- vi.mocked(readFileSync).mockReturnValue(
73
- JSON.stringify({ accessToken: 'direct-token' }),
74
- );
74
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify({ accessToken: 'direct-token' }));
75
75
  const discover = await freshDiscover();
76
76
  expect(discover()).toBe('direct-token');
77
77
  });
78
78
 
79
79
  it('should return token from credentials file with apiKey field', async () => {
80
80
  vi.mocked(existsSync).mockReturnValue(true);
81
- vi.mocked(readFileSync).mockReturnValue(
82
- JSON.stringify({ apiKey: 'api-key-field' }),
83
- );
81
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify({ apiKey: 'api-key-field' }));
84
82
  const discover = await freshDiscover();
85
83
  expect(discover()).toBe('api-key-field');
86
84
  });
@@ -101,9 +99,7 @@ describe('discoverAnthropicToken', () => {
101
99
 
102
100
  it('should handle keychain returning non-JSON with regex fallback', async () => {
103
101
  vi.mocked(platform).mockReturnValue('darwin');
104
- vi.mocked(execFileSync).mockReturnValue(
105
- '{ truncated "accessToken": "regex-token" garbage',
106
- );
102
+ vi.mocked(execFileSync).mockReturnValue('{ truncated "accessToken": "regex-token" garbage');
107
103
  const discover = await freshDiscover();
108
104
  expect(discover()).toBe('regex-token');
109
105
  });
@@ -124,7 +120,9 @@ describe('discoverAnthropicToken', () => {
124
120
 
125
121
  it('should treat long raw string from Linux keyring as token', async () => {
126
122
  vi.mocked(platform).mockReturnValue('linux');
127
- vi.mocked(execFileSync).mockReturnValue('a-long-raw-token-string-that-is-definitely-over-twenty-chars');
123
+ vi.mocked(execFileSync).mockReturnValue(
124
+ 'a-long-raw-token-string-that-is-definitely-over-twenty-chars',
125
+ );
128
126
  const discover = await freshDiscover();
129
127
  expect(discover()).toBe('a-long-raw-token-string-that-is-definitely-over-twenty-chars');
130
128
  });
@@ -142,14 +140,18 @@ describe('discoverAnthropicToken', () => {
142
140
 
143
141
  it('should handle credentials file read errors gracefully', async () => {
144
142
  vi.mocked(existsSync).mockReturnValue(true);
145
- vi.mocked(readFileSync).mockImplementation(() => { throw new Error('permission denied'); });
143
+ vi.mocked(readFileSync).mockImplementation(() => {
144
+ throw new Error('permission denied');
145
+ });
146
146
  const discover = await freshDiscover();
147
147
  expect(discover()).toBeNull();
148
148
  });
149
149
 
150
150
  it('should handle keychain errors gracefully', async () => {
151
151
  vi.mocked(platform).mockReturnValue('darwin');
152
- vi.mocked(execFileSync).mockImplementation(() => { throw new Error('no keychain entry'); });
152
+ vi.mocked(execFileSync).mockImplementation(() => {
153
+ throw new Error('no keychain entry');
154
+ });
153
155
  const discover = await freshDiscover();
154
156
  expect(discover()).toBeNull();
155
157
  });
@@ -164,9 +166,7 @@ describe('discoverAnthropicToken', () => {
164
166
  it('should prioritize env var over credentials file', async () => {
165
167
  process.env.ANTHROPIC_API_KEY = 'env-key';
166
168
  vi.mocked(existsSync).mockReturnValue(true);
167
- vi.mocked(readFileSync).mockReturnValue(
168
- JSON.stringify({ apiKey: 'file-key' }),
169
- );
169
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify({ apiKey: 'file-key' }));
170
170
  const discover = await freshDiscover();
171
171
  expect(discover()).toBe('env-key');
172
172
  });