@soleri/core 9.14.4 → 9.16.7

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 (355) hide show
  1. package/data/flows/deliver.flow.yaml +11 -0
  2. package/data/flows/design.flow.yaml +4 -14
  3. package/data/flows/enhance.flow.yaml +10 -0
  4. package/data/flows/explore.flow.yaml +16 -0
  5. package/data/flows/fix.flow.yaml +1 -1
  6. package/data/flows/review.flow.yaml +13 -4
  7. package/dist/brain/brain.d.ts +9 -0
  8. package/dist/brain/brain.d.ts.map +1 -1
  9. package/dist/brain/brain.js +11 -1
  10. package/dist/brain/brain.js.map +1 -1
  11. package/dist/brain/intelligence.d.ts.map +1 -1
  12. package/dist/brain/intelligence.js +24 -0
  13. package/dist/brain/intelligence.js.map +1 -1
  14. package/dist/brain/types.d.ts +1 -0
  15. package/dist/brain/types.d.ts.map +1 -1
  16. package/dist/capabilities/chain-mapping.d.ts.map +1 -1
  17. package/dist/capabilities/chain-mapping.js +5 -4
  18. package/dist/capabilities/chain-mapping.js.map +1 -1
  19. package/dist/capabilities/registry.d.ts +6 -0
  20. package/dist/capabilities/registry.d.ts.map +1 -1
  21. package/dist/capabilities/registry.js +3 -2
  22. package/dist/capabilities/registry.js.map +1 -1
  23. package/dist/chat/chat-session.d.ts +6 -0
  24. package/dist/chat/chat-session.d.ts.map +1 -1
  25. package/dist/chat/chat-session.js +68 -17
  26. package/dist/chat/chat-session.js.map +1 -1
  27. package/dist/context/context-engine.js +1 -1
  28. package/dist/context/context-engine.js.map +1 -1
  29. package/dist/curator/curator.d.ts +6 -0
  30. package/dist/curator/curator.d.ts.map +1 -1
  31. package/dist/curator/curator.js +138 -0
  32. package/dist/curator/curator.js.map +1 -1
  33. package/dist/curator/types.d.ts +10 -0
  34. package/dist/curator/types.d.ts.map +1 -1
  35. package/dist/engine/bin/soleri-engine.js +0 -0
  36. package/dist/engine/core-ops.d.ts.map +1 -1
  37. package/dist/engine/core-ops.js +38 -1
  38. package/dist/engine/core-ops.js.map +1 -1
  39. package/dist/flows/epilogue.d.ts +5 -1
  40. package/dist/flows/epilogue.d.ts.map +1 -1
  41. package/dist/flows/epilogue.js +11 -3
  42. package/dist/flows/epilogue.js.map +1 -1
  43. package/dist/flows/executor.d.ts.map +1 -1
  44. package/dist/flows/executor.js +13 -5
  45. package/dist/flows/executor.js.map +1 -1
  46. package/dist/flows/index.d.ts +1 -2
  47. package/dist/flows/index.d.ts.map +1 -1
  48. package/dist/flows/index.js +1 -0
  49. package/dist/flows/index.js.map +1 -1
  50. package/dist/flows/plan-builder.d.ts +17 -1
  51. package/dist/flows/plan-builder.d.ts.map +1 -1
  52. package/dist/flows/plan-builder.js +67 -6
  53. package/dist/flows/plan-builder.js.map +1 -1
  54. package/dist/flows/probes.d.ts +1 -1
  55. package/dist/flows/probes.d.ts.map +1 -1
  56. package/dist/flows/probes.js +15 -3
  57. package/dist/flows/probes.js.map +1 -1
  58. package/dist/flows/types.d.ts +47 -20
  59. package/dist/flows/types.d.ts.map +1 -1
  60. package/dist/flows/types.js +6 -1
  61. package/dist/flows/types.js.map +1 -1
  62. package/dist/index.d.ts +10 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +9 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/intake/content-classifier.d.ts +10 -4
  67. package/dist/intake/content-classifier.d.ts.map +1 -1
  68. package/dist/intake/content-classifier.js +19 -5
  69. package/dist/intake/content-classifier.js.map +1 -1
  70. package/dist/intake/text-ingester.d.ts +18 -0
  71. package/dist/intake/text-ingester.d.ts.map +1 -1
  72. package/dist/intake/text-ingester.js +37 -13
  73. package/dist/intake/text-ingester.js.map +1 -1
  74. package/dist/packs/pack-installer.d.ts.map +1 -1
  75. package/dist/packs/pack-installer.js +28 -2
  76. package/dist/packs/pack-installer.js.map +1 -1
  77. package/dist/planning/planner-types.d.ts +2 -0
  78. package/dist/planning/planner-types.d.ts.map +1 -1
  79. package/dist/planning/planner.d.ts +4 -0
  80. package/dist/planning/planner.d.ts.map +1 -1
  81. package/dist/planning/planner.js +50 -4
  82. package/dist/planning/planner.js.map +1 -1
  83. package/dist/playbooks/playbook-executor.d.ts +10 -1
  84. package/dist/playbooks/playbook-executor.d.ts.map +1 -1
  85. package/dist/playbooks/playbook-executor.js +8 -2
  86. package/dist/playbooks/playbook-executor.js.map +1 -1
  87. package/dist/playbooks/playbook-types.d.ts +8 -0
  88. package/dist/playbooks/playbook-types.d.ts.map +1 -1
  89. package/dist/plugins/types.d.ts +2 -2
  90. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  91. package/dist/runtime/admin-extra-ops.js +30 -0
  92. package/dist/runtime/admin-extra-ops.js.map +1 -1
  93. package/dist/runtime/admin-ops.d.ts.map +1 -1
  94. package/dist/runtime/admin-ops.js +60 -21
  95. package/dist/runtime/admin-ops.js.map +1 -1
  96. package/dist/runtime/admin-setup-ops.d.ts +11 -0
  97. package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
  98. package/dist/runtime/admin-setup-ops.js +146 -37
  99. package/dist/runtime/admin-setup-ops.js.map +1 -1
  100. package/dist/runtime/capture-ops.d.ts.map +1 -1
  101. package/dist/runtime/capture-ops.js +38 -12
  102. package/dist/runtime/capture-ops.js.map +1 -1
  103. package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
  104. package/dist/runtime/facades/brain-facade.js +16 -4
  105. package/dist/runtime/facades/brain-facade.js.map +1 -1
  106. package/dist/runtime/facades/context-facade.d.ts.map +1 -1
  107. package/dist/runtime/facades/context-facade.js +9 -3
  108. package/dist/runtime/facades/context-facade.js.map +1 -1
  109. package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
  110. package/dist/runtime/facades/memory-facade.js +20 -7
  111. package/dist/runtime/facades/memory-facade.js.map +1 -1
  112. package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
  113. package/dist/runtime/facades/orchestrate-facade.js +40 -1
  114. package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
  115. package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
  116. package/dist/runtime/facades/plan-facade.js +113 -4
  117. package/dist/runtime/facades/plan-facade.js.map +1 -1
  118. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  119. package/dist/runtime/facades/vault-facade.js +24 -3
  120. package/dist/runtime/facades/vault-facade.js.map +1 -1
  121. package/dist/runtime/orchestrate-ops.d.ts +21 -0
  122. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  123. package/dist/runtime/orchestrate-ops.js +132 -38
  124. package/dist/runtime/orchestrate-ops.js.map +1 -1
  125. package/dist/runtime/runtime.d.ts.map +1 -1
  126. package/dist/runtime/runtime.js +16 -0
  127. package/dist/runtime/runtime.js.map +1 -1
  128. package/dist/runtime/schema-helpers.d.ts.map +1 -1
  129. package/dist/runtime/schema-helpers.js +4 -0
  130. package/dist/runtime/schema-helpers.js.map +1 -1
  131. package/dist/runtime/types.d.ts +19 -0
  132. package/dist/runtime/types.d.ts.map +1 -1
  133. package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
  134. package/dist/runtime/vault-linking-ops.js +16 -3
  135. package/dist/runtime/vault-linking-ops.js.map +1 -1
  136. package/dist/scheduler/cron-validator.d.ts +15 -0
  137. package/dist/scheduler/cron-validator.d.ts.map +1 -0
  138. package/dist/scheduler/cron-validator.js +93 -0
  139. package/dist/scheduler/cron-validator.js.map +1 -0
  140. package/dist/scheduler/platform-linux.d.ts +14 -0
  141. package/dist/scheduler/platform-linux.d.ts.map +1 -0
  142. package/dist/scheduler/platform-linux.js +107 -0
  143. package/dist/scheduler/platform-linux.js.map +1 -0
  144. package/dist/scheduler/platform-macos.d.ts +15 -0
  145. package/dist/scheduler/platform-macos.d.ts.map +1 -0
  146. package/dist/scheduler/platform-macos.js +131 -0
  147. package/dist/scheduler/platform-macos.js.map +1 -0
  148. package/dist/scheduler/scheduler-ops.d.ts +14 -0
  149. package/dist/scheduler/scheduler-ops.d.ts.map +1 -0
  150. package/dist/scheduler/scheduler-ops.js +77 -0
  151. package/dist/scheduler/scheduler-ops.js.map +1 -0
  152. package/dist/scheduler/scheduler.d.ts +55 -0
  153. package/dist/scheduler/scheduler.d.ts.map +1 -0
  154. package/dist/scheduler/scheduler.js +144 -0
  155. package/dist/scheduler/scheduler.js.map +1 -0
  156. package/dist/scheduler/types.d.ts +48 -0
  157. package/dist/scheduler/types.d.ts.map +1 -0
  158. package/dist/scheduler/types.js +6 -0
  159. package/dist/scheduler/types.js.map +1 -0
  160. package/dist/skills/sync-skills.d.ts +11 -0
  161. package/dist/skills/sync-skills.d.ts.map +1 -1
  162. package/dist/skills/sync-skills.js +132 -38
  163. package/dist/skills/sync-skills.js.map +1 -1
  164. package/dist/skills/validate-skills.d.ts +32 -0
  165. package/dist/skills/validate-skills.d.ts.map +1 -0
  166. package/dist/skills/validate-skills.js +396 -0
  167. package/dist/skills/validate-skills.js.map +1 -0
  168. package/dist/utils/worktree-reaper.d.ts +38 -0
  169. package/dist/utils/worktree-reaper.d.ts.map +1 -0
  170. package/dist/utils/worktree-reaper.js +85 -0
  171. package/dist/utils/worktree-reaper.js.map +1 -0
  172. package/dist/vault/default-canonical-tags.d.ts +15 -0
  173. package/dist/vault/default-canonical-tags.d.ts.map +1 -0
  174. package/dist/vault/default-canonical-tags.js +65 -0
  175. package/dist/vault/default-canonical-tags.js.map +1 -0
  176. package/dist/vault/scope-detector.d.ts.map +1 -1
  177. package/dist/vault/scope-detector.js +37 -4
  178. package/dist/vault/scope-detector.js.map +1 -1
  179. package/dist/vault/tag-normalizer.d.ts +42 -0
  180. package/dist/vault/tag-normalizer.d.ts.map +1 -0
  181. package/dist/vault/tag-normalizer.js +157 -0
  182. package/dist/vault/tag-normalizer.js.map +1 -0
  183. package/dist/vault/vault-entries.d.ts.map +1 -1
  184. package/dist/vault/vault-entries.js +3 -1
  185. package/dist/vault/vault-entries.js.map +1 -1
  186. package/package.json +5 -1
  187. package/src/__tests__/embeddings.test.ts +3 -3
  188. package/src/agency/agency-manager.test.ts +4 -4
  189. package/src/agency/default-rules.test.ts +0 -13
  190. package/src/brain/brain-intelligence.test.ts +0 -5
  191. package/src/brain/brain.ts +25 -1
  192. package/src/brain/intelligence.ts +25 -0
  193. package/src/brain/second-brain-features.test.ts +2 -14
  194. package/src/brain/types.ts +1 -0
  195. package/src/capabilities/chain-mapping.test.ts +1 -6
  196. package/src/capabilities/chain-mapping.ts +6 -4
  197. package/src/capabilities/registry.test.ts +1 -1
  198. package/src/capabilities/registry.ts +9 -2
  199. package/src/chat/agent-loop.test.ts +1 -1
  200. package/src/chat/chat-enhanced.test.ts +0 -8
  201. package/src/chat/chat-session.ts +75 -17
  202. package/src/chat/chat-transport.test.ts +31 -1
  203. package/src/claudemd/compose.test.ts +0 -5
  204. package/src/context/context-engine.test.ts +0 -1
  205. package/src/context/context-engine.ts +1 -1
  206. package/src/control/intent-router.test.ts +2 -2
  207. package/src/curator/curator.ts +180 -0
  208. package/src/curator/tag-manager.test.ts +0 -4
  209. package/src/curator/types.ts +10 -0
  210. package/src/domain-packs/types.test.ts +0 -5
  211. package/src/dream/dream.test.ts +0 -7
  212. package/src/enforcement/registry.test.ts +2 -2
  213. package/src/engine/core-ops.test.ts +4 -22
  214. package/src/engine/core-ops.ts +36 -1
  215. package/src/engine/module-manifest.test.ts +1 -31
  216. package/src/engine/register-engine.test.ts +3 -33
  217. package/src/errors/retry.test.ts +3 -1
  218. package/src/flows/chain-runner.test.ts +0 -6
  219. package/src/flows/context-router.test.ts +3 -3
  220. package/src/flows/epilogue.test.ts +40 -2
  221. package/src/flows/epilogue.ts +11 -2
  222. package/src/flows/executor.test.ts +48 -2
  223. package/src/flows/executor.ts +15 -5
  224. package/src/flows/index.ts +1 -3
  225. package/src/flows/plan-builder.test.ts +201 -0
  226. package/src/flows/plan-builder.ts +81 -5
  227. package/src/flows/probes.ts +17 -3
  228. package/src/flows/types.ts +31 -2
  229. package/src/health/health-registry.test.ts +3 -1
  230. package/src/index.ts +24 -0
  231. package/src/intake/content-classifier.ts +22 -4
  232. package/src/intake/dedup-gate.test.ts +2 -6
  233. package/src/intake/text-ingester.test.ts +3 -4
  234. package/src/intake/text-ingester.ts +61 -12
  235. package/src/llm/llm-client.test.ts +1 -1
  236. package/src/llm/utils.test.ts +1 -1
  237. package/src/migrations/migration-runner.test.ts +0 -1
  238. package/src/operator/operator-context-store.test.ts +0 -13
  239. package/src/operator/operator-profile.test.ts +2 -20
  240. package/src/packs/pack-installer.ts +28 -2
  241. package/src/packs/pack-system.test.ts +2 -2
  242. package/src/persona/defaults.test.ts +19 -19
  243. package/src/planning/gap-passes.test.ts +0 -46
  244. package/src/planning/gap-patterns.test.ts +0 -42
  245. package/src/planning/goal-ancestry.test.ts +3 -1
  246. package/src/planning/plan-lifecycle.test.ts +15 -7
  247. package/src/planning/planner-types.ts +2 -0
  248. package/src/planning/planner.test.ts +86 -90
  249. package/src/planning/planner.ts +56 -4
  250. package/src/planning/reconciliation-engine.test.ts +3 -10
  251. package/src/planning/task-complexity-assessor.test.ts +0 -5
  252. package/src/planning/task-verifier.test.ts +3 -1
  253. package/src/playbooks/generic/generic-playbooks.test.ts +0 -28
  254. package/src/playbooks/index.test.ts +0 -55
  255. package/src/playbooks/playbook-executor.test.ts +76 -0
  256. package/src/playbooks/playbook-executor.ts +24 -3
  257. package/src/playbooks/playbook-types.ts +8 -0
  258. package/src/plugins/plugin-registry.test.ts +6 -2
  259. package/src/project/project-registry.test.ts +2 -0
  260. package/src/queue/async-infrastructure.test.ts +6 -4
  261. package/src/queue/job-queue.test.ts +13 -7
  262. package/src/runtime/admin-extra-ops.test.ts +35 -30
  263. package/src/runtime/admin-extra-ops.ts +30 -0
  264. package/src/runtime/admin-ops.test.ts +0 -4
  265. package/src/runtime/admin-ops.ts +63 -21
  266. package/src/runtime/admin-setup-ops.test.ts +229 -13
  267. package/src/runtime/admin-setup-ops.ts +145 -36
  268. package/src/runtime/archive-ops.test.ts +0 -28
  269. package/src/runtime/branching-ops.test.ts +0 -17
  270. package/src/runtime/capture-ops.test.ts +41 -16
  271. package/src/runtime/capture-ops.ts +78 -46
  272. package/src/runtime/chain-ops.test.ts +0 -21
  273. package/src/runtime/facades/admin-facade.test.ts +0 -34
  274. package/src/runtime/facades/agency-facade.test.ts +0 -39
  275. package/src/runtime/facades/archive-facade.test.ts +0 -43
  276. package/src/runtime/facades/brain-facade.test.ts +8 -99
  277. package/src/runtime/facades/brain-facade.ts +29 -12
  278. package/src/runtime/facades/branching-facade.test.ts +30 -17
  279. package/src/runtime/facades/chat-facade.test.ts +0 -91
  280. package/src/runtime/facades/chat-service-ops.test.ts +0 -24
  281. package/src/runtime/facades/chat-session-ops.test.ts +0 -12
  282. package/src/runtime/facades/chat-transport-ops.test.ts +0 -23
  283. package/src/runtime/facades/context-facade.test.ts +0 -17
  284. package/src/runtime/facades/context-facade.ts +11 -4
  285. package/src/runtime/facades/control-facade.test.ts +0 -30
  286. package/src/runtime/facades/curator-facade.test.ts +0 -33
  287. package/src/runtime/facades/intake-facade.test.ts +0 -33
  288. package/src/runtime/facades/links-facade.test.ts +0 -37
  289. package/src/runtime/facades/loop-facade.test.ts +0 -26
  290. package/src/runtime/facades/memory-facade.test.ts +0 -18
  291. package/src/runtime/facades/memory-facade.ts +27 -11
  292. package/src/runtime/facades/operator-facade.test.ts +0 -31
  293. package/src/runtime/facades/orchestrate-facade.test.ts +0 -21
  294. package/src/runtime/facades/orchestrate-facade.ts +39 -1
  295. package/src/runtime/facades/plan-facade.test.ts +7 -32
  296. package/src/runtime/facades/plan-facade.ts +137 -4
  297. package/src/runtime/facades/review-facade.test.ts +1 -49
  298. package/src/runtime/facades/sync-facade.test.ts +24 -41
  299. package/src/runtime/facades/tier-facade.test.ts +30 -22
  300. package/src/runtime/facades/vault-facade.test.ts +0 -41
  301. package/src/runtime/facades/vault-facade.ts +26 -3
  302. package/src/runtime/grading-ops.test.ts +0 -27
  303. package/src/runtime/intake-ops.test.ts +0 -19
  304. package/src/runtime/loop-ops.test.ts +0 -48
  305. package/src/runtime/memory-cross-project-ops.test.ts +0 -14
  306. package/src/runtime/memory-extra-ops.test.ts +4 -8
  307. package/src/runtime/orchestrate-ops.test.ts +238 -19
  308. package/src/runtime/orchestrate-ops.ts +166 -41
  309. package/src/runtime/pack-ops.test.ts +0 -26
  310. package/src/runtime/planning-extra-ops.test.ts +2 -14
  311. package/src/runtime/playbook-ops-execution.test.ts +9 -20
  312. package/src/runtime/playbook-ops.test.ts +4 -67
  313. package/src/runtime/review-ops.test.ts +0 -15
  314. package/src/runtime/runtime.ts +18 -0
  315. package/src/runtime/schema-helpers.ts +4 -0
  316. package/src/runtime/sync-ops.test.ts +0 -18
  317. package/src/runtime/tier-ops.test.ts +0 -21
  318. package/src/runtime/types.ts +19 -0
  319. package/src/runtime/vault-extra-ops.test.ts +0 -12
  320. package/src/runtime/vault-linking-ops.test.ts +0 -4
  321. package/src/runtime/vault-linking-ops.ts +26 -8
  322. package/src/runtime/vault-sharing-ops.test.ts +0 -9
  323. package/src/scheduler/cron-validator.ts +101 -0
  324. package/src/scheduler/platform-linux.ts +122 -0
  325. package/src/scheduler/platform-macos.ts +150 -0
  326. package/src/scheduler/scheduler-ops.ts +77 -0
  327. package/src/scheduler/scheduler.test.ts +247 -0
  328. package/src/scheduler/scheduler.ts +174 -0
  329. package/src/scheduler/types.ts +52 -0
  330. package/src/skills/__tests__/sync-skills.test.ts +6 -17
  331. package/src/skills/global-claude-md.test.ts +113 -0
  332. package/src/skills/sync-skills.ts +143 -35
  333. package/src/skills/validate-skills.test.ts +206 -0
  334. package/src/skills/validate-skills.ts +470 -0
  335. package/src/telemetry/telemetry.test.ts +1 -0
  336. package/src/transport/http-server.test.ts +3 -0
  337. package/src/transport/session-manager.test.ts +3 -1
  338. package/src/transport/token-auth.test.ts +6 -9
  339. package/src/transport/ws-server.test.ts +10 -2
  340. package/src/utils/worktree-reaper.ts +113 -0
  341. package/src/vault/__tests__/vault-characterization.test.ts +0 -108
  342. package/src/vault/default-canonical-tags.ts +64 -0
  343. package/src/vault/linking.test.ts +0 -2
  344. package/src/vault/playbook.test.ts +4 -1
  345. package/src/vault/scope-detector.test.ts +3 -1
  346. package/src/vault/scope-detector.ts +42 -4
  347. package/src/vault/tag-normalizer.test.ts +214 -0
  348. package/src/vault/tag-normalizer.ts +188 -0
  349. package/src/vault/vault-connect.test.ts +1 -1
  350. package/src/vault/vault-entries.ts +3 -1
  351. package/src/vault/vault.test.ts +23 -8
  352. package/dist/embeddings/index.d.ts +0 -5
  353. package/dist/embeddings/index.d.ts.map +0 -1
  354. package/dist/embeddings/index.js +0 -3
  355. package/dist/embeddings/index.js.map +0 -1
package/src/index.ts CHANGED
@@ -18,6 +18,8 @@ export { WorkspaceResolver } from './subagent/workspace-resolver.js';
18
18
  export { ConcurrencyManager } from './subagent/concurrency-manager.js';
19
19
  export { OrphanReaper } from './subagent/orphan-reaper.js';
20
20
  export type { ReapResult } from './subagent/orphan-reaper.js';
21
+ export { worktreeReap, worktreeStatus } from './utils/worktree-reaper.js';
22
+ export type { ReapReport, WorktreeStatus } from './utils/worktree-reaper.js';
21
23
  export { aggregate as aggregateResults } from './subagent/result-aggregator.js';
22
24
  export type {
23
25
  SubagentTask,
@@ -101,6 +103,13 @@ export type {
101
103
  } from './vault/vault-types.js';
102
104
  export { validatePlaybook, parsePlaybookFromEntry } from './vault/playbook.js';
103
105
  export type { Playbook, PlaybookStep, PlaybookValidationResult } from './vault/playbook.js';
106
+ export { DEFAULT_CANONICAL_TAGS } from './vault/default-canonical-tags.js';
107
+ export {
108
+ normalizeTag as normalizeTagCanonical,
109
+ normalizeTags as normalizeTagsCanonical,
110
+ isMetadataTag,
111
+ computeEditDistance,
112
+ } from './vault/tag-normalizer.js';
104
113
 
105
114
  // ─── Playbook System (registry, matching, seeding) ─────────────────
106
115
  export {
@@ -949,3 +958,18 @@ export type { WorkflowGate, WorkflowOverride } from './workflows/index.js';
949
958
  // ─── Update Check ────────────────────────────────────────────────────
950
959
  export { checkForUpdate, buildChangelogUrl, detectBreakingChanges } from './update-check.js';
951
960
  export type { UpdateInfo } from './update-check.js';
961
+
962
+ // ─── Settings Hooks Sync ─────────────────────────────────────────────
963
+ export { syncHooksToClaudeSettings } from './runtime/admin-setup-ops.js';
964
+
965
+ // ─── Scheduler ───────────────────────────────────────────────────────
966
+ export { Scheduler, InMemorySchedulerStore } from './scheduler/scheduler.js';
967
+ export type { SchedulerStore } from './scheduler/scheduler.js';
968
+ export { createSchedulerOps } from './scheduler/scheduler-ops.js';
969
+ export { validateCron, estimateMinIntervalHours } from './scheduler/cron-validator.js';
970
+ export type {
971
+ ScheduledTask,
972
+ CreateTaskInput,
973
+ TaskListEntry,
974
+ PlatformAdapter,
975
+ } from './scheduler/types.js';
@@ -45,22 +45,40 @@ Rules:
45
45
  // CLASSIFIER
46
46
  // =============================================================================
47
47
 
48
+ /**
49
+ * Build the classification system prompt, optionally injecting a canonical tag list.
50
+ * When canonical tags are provided, the LLM is guided to prefer them.
51
+ */
52
+ export function buildClassificationPrompt(canonicalTags?: string[]): string {
53
+ if (!canonicalTags || canonicalTags.length === 0) {
54
+ return CLASSIFICATION_PROMPT;
55
+ }
56
+ const tagList = canonicalTags.join(', ');
57
+ return (
58
+ CLASSIFICATION_PROMPT +
59
+ `\n\nTag guidance: Use only tags from this approved list where possible: ${tagList}. Create a new tag only when nothing from the list fits the concept.`
60
+ );
61
+ }
62
+
48
63
  /**
49
64
  * Classify a text chunk into structured knowledge items using an LLM.
50
65
  *
51
- * @param llm - LLMClient instance
52
- * @param chunkText - The text to classify
53
- * @param citation - Source citation (e.g. "book.pdf, pages 12-15")
66
+ * @param llm - LLMClient instance
67
+ * @param chunkText - The text to classify
68
+ * @param citation - Source citation (e.g. "book.pdf, pages 12-15")
69
+ * @param canonicalTags - Optional canonical tag list to inject into the prompt
54
70
  * @returns Classified items, or [] on any error
55
71
  */
56
72
  export async function classifyChunk(
57
73
  llm: LLMClient,
58
74
  chunkText: string,
59
75
  citation: string,
76
+ canonicalTags?: string[],
60
77
  ): Promise<ClassifiedItem[]> {
61
78
  try {
79
+ const systemPrompt = buildClassificationPrompt(canonicalTags);
62
80
  const result = await llm.complete({
63
- systemPrompt: CLASSIFICATION_PROMPT,
81
+ systemPrompt,
64
82
  userPrompt: chunkText,
65
83
  maxTokens: 4096,
66
84
  temperature: 0.3,
@@ -135,13 +135,9 @@ describe('dedupItems — colocated', () => {
135
135
  const results = dedupItems(items, vault);
136
136
 
137
137
  expect(results).toHaveLength(2);
138
- // First should be duplicate or at least high similarity
139
- expect(results[0].similarity).toBeGreaterThan(0.5);
138
+ // First should be a high-similarity match (near-exact duplicate)
139
+ expect(results[0].similarity).toBeGreaterThanOrEqual(DEDUP_THRESHOLD);
140
140
  // Second should be non-duplicate
141
141
  expect(results[1].isDuplicate).toBe(false);
142
142
  });
143
-
144
- it('DEDUP_THRESHOLD is 0.85', () => {
145
- expect(DEDUP_THRESHOLD).toBe(0.85);
146
- });
147
143
  });
@@ -86,7 +86,7 @@ describe('TextIngester — ingestText', () => {
86
86
  expect(result.duplicates).toBe(0);
87
87
  expect(result.entries).toHaveLength(1);
88
88
  expect(result.entries[0].title).toBe('Pattern A');
89
- expect(vault._seeded.length).toBeGreaterThan(0);
89
+ expect(vault._seeded.length).toBe(1);
90
90
  });
91
91
 
92
92
  it('returns empty result when LLM is null', async () => {
@@ -120,8 +120,7 @@ describe('TextIngester — ingestText', () => {
120
120
  const result = await ingester.ingestText('text', { type: 'documentation', title: 'Doc' }, opts);
121
121
 
122
122
  expect(result.ingested).toBe(1);
123
- // Seeded entries should exist
124
- expect(vault._seeded.length).toBeGreaterThan(0);
123
+ expect(vault._seeded.length).toBe(1);
125
124
  });
126
125
 
127
126
  it('splits long text into chunks based on chunkSize option', async () => {
@@ -155,7 +154,7 @@ describe('TextIngester — ingestText', () => {
155
154
  const longText = 'A'.repeat(100);
156
155
  await ingester.ingestText(longText, { type: 'notes', title: 'Test' }, { chunkSize: 30 });
157
156
 
158
- expect(callCount.n).toBeGreaterThan(1);
157
+ expect(callCount.n).toBe(4);
159
158
  });
160
159
  });
161
160
 
@@ -11,6 +11,7 @@ import type { IntelligenceEntry } from '../intelligence/types.js';
11
11
  import type { ClassifiedItem } from './types.js';
12
12
  import { classifyChunk } from './content-classifier.js';
13
13
  import { dedupItems } from './dedup-gate.js';
14
+ import { normalizeTags as normalizeTagsCanonical } from '../vault/tag-normalizer.js';
14
15
 
15
16
  // ─── Types ───────────────────────────────────────────────────────────
16
17
 
@@ -26,6 +27,12 @@ export interface IngestOptions {
26
27
  tags?: string[];
27
28
  /** Max chars per chunk for LLM classification. Default 4000. */
28
29
  chunkSize?: number;
30
+ /** Canonical tag list for normalization. If omitted, no canonical normalization. */
31
+ canonicalTags?: string[];
32
+ /** Tag constraint mode. Default: 'suggest'. */
33
+ tagConstraintMode?: 'enforce' | 'suggest' | 'off';
34
+ /** Metadata tag prefixes exempt from canonical normalization. Default: ['source:']. */
35
+ metadataTagPrefixes?: string[];
29
36
  }
30
37
 
31
38
  export interface IngestResult {
@@ -42,15 +49,30 @@ const FETCH_TIMEOUT_MS = 15000;
42
49
 
43
50
  // ─── Class ───────────────────────────────────────────────────────────
44
51
 
52
+ interface CanonicalTagConfig {
53
+ canonicalTags: string[];
54
+ tagConstraintMode: 'enforce' | 'suggest' | 'off';
55
+ metadataTagPrefixes: string[];
56
+ }
57
+
45
58
  export class TextIngester {
46
59
  private vault: Vault;
47
60
  private llm: LLMClient | null;
61
+ private canonicalTagConfig: CanonicalTagConfig | null = null;
48
62
 
49
63
  constructor(vault: Vault, llm: LLMClient | null) {
50
64
  this.vault = vault;
51
65
  this.llm = llm;
52
66
  }
53
67
 
68
+ /**
69
+ * Wire canonical tag config from runtime — used as defaults for all ingest calls.
70
+ * Caller-provided options in ingestText/ingestUrl/ingestBatch still take precedence.
71
+ */
72
+ setCanonicalTagConfig(cfg: CanonicalTagConfig): void {
73
+ this.canonicalTagConfig = cfg;
74
+ }
75
+
54
76
  /**
55
77
  * Ingest a URL — fetch, strip HTML, classify, dedup, store.
56
78
  */
@@ -101,11 +123,19 @@ export class TextIngester {
101
123
  const domain = opts?.domain ?? 'general';
102
124
  const extraTags = opts?.tags ?? [];
103
125
 
126
+ // Resolve canonical config — caller opts take precedence over runtime-wired config
127
+ const canonicalTagsForClassify = opts?.canonicalTags ?? this.canonicalTagConfig?.canonicalTags;
128
+
104
129
  // Classify all chunks
105
130
  const allItems: ClassifiedItem[] = [];
106
131
  for (const chunk of chunks) {
107
132
  // oxlint-disable-next-line eslint(no-await-in-loop)
108
- const items = await classifyChunk(this.llm, chunk, `${source.type}: ${source.title}`);
133
+ const items = await classifyChunk(
134
+ this.llm,
135
+ chunk,
136
+ `${source.type}: ${source.title}`,
137
+ canonicalTagsForClassify,
138
+ );
109
139
  allItems.push(...items);
110
140
  }
111
141
 
@@ -121,18 +151,37 @@ export class TextIngester {
121
151
  // Build source attribution for context field
122
152
  const attribution = buildAttribution(source);
123
153
 
154
+ // Metadata tags use 'source:' prefix so they're exempt from canonical normalization
155
+ const metadataTags = [`source:ingested`, `source:${source.type}`];
156
+
157
+ // Apply canonical tag normalization if configured
158
+ // Caller-provided options take precedence over runtime-wired config
159
+ const canonicalTags = opts?.canonicalTags ?? this.canonicalTagConfig?.canonicalTags;
160
+ const tagMode =
161
+ opts?.tagConstraintMode ?? this.canonicalTagConfig?.tagConstraintMode ?? 'suggest';
162
+
124
163
  // Store in vault
125
- const entries: IntelligenceEntry[] = unique.map((item, i) => ({
126
- id: `ingest-${source.type}-${Date.now()}-${i}-${Math.random().toString(36).slice(2, 6)}`,
127
- type: mapType(item.type),
128
- domain,
129
- title: item.title,
130
- description: item.description,
131
- severity: mapSeverity(item.severity),
132
- tags: [...(item.tags ?? []), ...extraTags, 'ingested', source.type],
133
- context: attribution,
134
- origin: 'user' as const,
135
- }));
164
+ const entries: IntelligenceEntry[] = unique.map((item, i) => {
165
+ const rawTags = [...(item.tags ?? []), ...extraTags];
166
+ // metaPrefixes not passed here — source: tags are added after normalization,
167
+ // so there is nothing to exempt at this point.
168
+ const normalizedTags =
169
+ canonicalTags && tagMode !== 'off'
170
+ ? normalizeTagsCanonical(rawTags, canonicalTags, tagMode)
171
+ : rawTags;
172
+
173
+ return {
174
+ id: `ingest-${source.type}-${Date.now()}-${i}-${Math.random().toString(36).slice(2, 6)}`,
175
+ type: mapType(item.type),
176
+ domain,
177
+ title: item.title,
178
+ description: item.description,
179
+ severity: mapSeverity(item.severity),
180
+ tags: [...normalizedTags, ...metadataTags],
181
+ context: attribution,
182
+ origin: 'user' as const,
183
+ };
184
+ });
136
185
 
137
186
  if (entries.length > 0) {
138
187
  this.vault.seed(entries);
@@ -119,7 +119,7 @@ describe('LLMClient — colocated', () => {
119
119
  expect(result.provider).toBe('openai');
120
120
  expect(result.inputTokens).toBe(10);
121
121
  expect(result.outputTokens).toBe(5);
122
- expect(result.durationMs).toBeGreaterThanOrEqual(0);
122
+ expect(typeof result.durationMs).toBe('number');
123
123
  } finally {
124
124
  globalThis.fetch = originalFetch;
125
125
  }
@@ -224,7 +224,7 @@ describe('computeDelay', () => {
224
224
  it('should never return negative delay', () => {
225
225
  vi.spyOn(Math, 'random').mockReturnValue(0);
226
226
  const config = { maxAttempts: 3, baseDelayMs: 1, maxDelayMs: 30000, jitter: 1 };
227
- expect(computeDelay({}, 0, config)).toBeGreaterThanOrEqual(0);
227
+ expect(typeof computeDelay({}, 0, config)).toBe('number');
228
228
  vi.restoreAllMocks();
229
229
  });
230
230
  });
@@ -137,7 +137,6 @@ describe('MigrationRunner', () => {
137
137
 
138
138
  // Assert
139
139
  expect(typeof results[0].durationMs).toBe('number');
140
- expect(results[0].durationMs).toBeGreaterThanOrEqual(0);
141
140
  });
142
141
  });
143
142
 
@@ -51,19 +51,6 @@ describe('OperatorContextStore', () => {
51
51
  vault.close();
52
52
  });
53
53
 
54
- // ─── Table Creation ─────────────────────────────────────────────────
55
-
56
- describe('init', () => {
57
- it('creates table without error on a fresh database', () => {
58
- expect(store).toBeDefined();
59
- });
60
-
61
- it('is idempotent — second init does not throw', () => {
62
- const store2 = new OperatorContextStore(vault.getProvider());
63
- expect(store2).toBeDefined();
64
- });
65
- });
66
-
67
54
  // ─── Empty State ──────────────────────────────────────────────────
68
55
 
69
56
  describe('getContext (empty)', () => {
@@ -47,15 +47,6 @@ describe('OperatorProfileStore', () => {
47
47
  vault.close();
48
48
  });
49
49
 
50
- // ─── Table Creation ─────────────────────────────────────────────
51
-
52
- it('creates tables without error on new runtime', () => {
53
- // Constructor ran initTables — no error means tables exist.
54
- // Creating a second instance also succeeds (IF NOT EXISTS).
55
- const store2 = new OperatorProfileStore(vault);
56
- expect(store2).toBeDefined();
57
- });
58
-
59
50
  // ─── getProfile ─────────────────────────────────────────────────
60
51
 
61
52
  it('returns null when no profile exists', () => {
@@ -186,8 +177,8 @@ describe('OperatorProfileStore', () => {
186
177
  'SELECT trigger, version FROM operator_profile_history WHERE profile_id = ?',
187
178
  [profileBefore!.id],
188
179
  );
189
- expect(history.length).toBeGreaterThanOrEqual(1);
190
- expect(history.some((h) => h.trigger === 'correction')).toBe(true);
180
+ expect(history.length).toBe(1);
181
+ expect(history[0].trigger).toBe('correction');
191
182
  });
192
183
 
193
184
  // ─── snapshot ───────────────────────────────────────────────────
@@ -320,13 +311,4 @@ describe('OperatorProfileStore', () => {
320
311
  expect(section.signalWords).toContain('please');
321
312
  expect(section.adaptationRules).toHaveLength(1);
322
313
  });
323
-
324
- // ─── No `any` types ────────────────────────────────────────────
325
-
326
- it('type system enforced — file compiles with strict TypeScript', () => {
327
- // This test is a compile-time assertion:
328
- // if operator-profile.ts had `any` types, tsc --noEmit would catch it.
329
- // The fact that this test file compiles is the proof.
330
- expect(true).toBe(true);
331
- });
332
314
  });
@@ -9,8 +9,9 @@
9
9
  * 5. Return install summary
10
10
  */
11
11
 
12
- import { existsSync, readdirSync, readFileSync } from 'node:fs';
12
+ import { existsSync, readdirSync, readFileSync, rmSync } from 'node:fs';
13
13
  import { join, basename } from 'node:path';
14
+ import { homedir } from 'node:os';
14
15
  import {
15
16
  packManifestSchema,
16
17
  type InstalledPack,
@@ -200,10 +201,19 @@ export class PackInstaller {
200
201
  facadesRegistered = true;
201
202
  }
202
203
 
203
- // 3. Discover skills
204
+ // 3. Discover skills and sync to .claude/skills/<packId>:<skill>/
204
205
  const skillsDir = join(packDir, manifest.skills?.dir ?? 'skills');
205
206
  const skills = existsSync(skillsDir) ? listMarkdownFiles(skillsDir) : [];
206
207
 
208
+ if (skills.length > 0) {
209
+ try {
210
+ const { syncSkillsToClaudeCode } = await import('../skills/sync-skills.js');
211
+ syncSkillsToClaudeCode([skillsDir], manifest.id, { global: true });
212
+ } catch {
213
+ // Skill sync is best-effort — never blocks install
214
+ }
215
+ }
216
+
207
217
  // 4. Discover hooks
208
218
  const hooksDir = join(packDir, manifest.hooks?.dir ?? 'hooks');
209
219
  const hooks = existsSync(hooksDir) ? listMarkdownFiles(hooksDir) : [];
@@ -281,6 +291,22 @@ export class PackInstaller {
281
291
  this.pluginRegistry.deactivate(packId);
282
292
  }
283
293
 
294
+ // Remove pack skills from .claude/skills/
295
+ if (pack.skills.length > 0) {
296
+ try {
297
+ const claudeSkillsDir = join(homedir(), '.claude', 'skills');
298
+ for (const skillPath of pack.skills) {
299
+ const skillName = basename(skillPath, '.md');
300
+ const registeredPath = join(claudeSkillsDir, `${packId}:${skillName}`);
301
+ if (existsSync(registeredPath)) {
302
+ rmSync(registeredPath, { recursive: true, force: true });
303
+ }
304
+ }
305
+ } catch {
306
+ // Skill cleanup is best-effort
307
+ }
308
+ }
309
+
284
310
  // Transition to uninstalled
285
311
  try {
286
312
  this.lifecycle.transition(packId, 'uninstalled', 'User uninstall');
@@ -233,7 +233,7 @@ describe('PackInstaller', () => {
233
233
 
234
234
  const result = installer.validate(packDir);
235
235
  expect(result.valid).toBe(true);
236
- expect(result.warnings.length).toBeGreaterThan(0);
236
+ expect(result.warnings.length).toBe(2);
237
237
  });
238
238
  });
239
239
 
@@ -271,7 +271,7 @@ describe('PackInstaller', () => {
271
271
 
272
272
  // Verify vault was seeded
273
273
  const entries = vault.list({ domain: 'test' });
274
- expect(entries.length).toBeGreaterThan(0);
274
+ expect(entries.length).toBe(1);
275
275
  });
276
276
 
277
277
  it('should install a pack with facades', async () => {
@@ -12,21 +12,21 @@ describe('ITALIAN_CRAFTSPERSON', () => {
12
12
  });
13
13
 
14
14
  it('has non-empty voice, culture, and inspiration', () => {
15
- expect(ITALIAN_CRAFTSPERSON.voice.length).toBeGreaterThan(0);
15
+ expect(ITALIAN_CRAFTSPERSON.voice).toContain('Italian mentor');
16
16
  expect(ITALIAN_CRAFTSPERSON.culture).toBe('Italian');
17
- expect(ITALIAN_CRAFTSPERSON.inspiration.length).toBeGreaterThan(0);
17
+ expect(ITALIAN_CRAFTSPERSON.inspiration).toContain('Paolo Soleri');
18
18
  });
19
19
 
20
20
  it('provides greetings and signoffs pools', () => {
21
- expect(ITALIAN_CRAFTSPERSON.greetings.length).toBeGreaterThan(0);
22
- expect(ITALIAN_CRAFTSPERSON.signoffs.length).toBeGreaterThan(0);
21
+ expect(ITALIAN_CRAFTSPERSON.greetings).toHaveLength(5);
22
+ expect(ITALIAN_CRAFTSPERSON.signoffs).toHaveLength(5);
23
23
  });
24
24
 
25
25
  it('includes expected trait and quirk arrays', () => {
26
- expect(ITALIAN_CRAFTSPERSON.traits.length).toBeGreaterThan(0);
27
- expect(ITALIAN_CRAFTSPERSON.quirks.length).toBeGreaterThan(0);
28
- expect(ITALIAN_CRAFTSPERSON.metaphors.length).toBeGreaterThan(0);
29
- expect(ITALIAN_CRAFTSPERSON.opinions.length).toBeGreaterThan(0);
26
+ expect(ITALIAN_CRAFTSPERSON.traits).toHaveLength(7);
27
+ expect(ITALIAN_CRAFTSPERSON.quirks).toHaveLength(6);
28
+ expect(ITALIAN_CRAFTSPERSON.metaphors).toHaveLength(8);
29
+ expect(ITALIAN_CRAFTSPERSON.opinions).toHaveLength(6);
30
30
  });
31
31
  });
32
32
 
@@ -36,26 +36,26 @@ describe('NEUTRAL_PERSONA', () => {
36
36
  });
37
37
 
38
38
  it('has non-empty voice and inspiration', () => {
39
- expect(NEUTRAL_PERSONA.voice.length).toBeGreaterThan(0);
40
- expect(NEUTRAL_PERSONA.inspiration.length).toBeGreaterThan(0);
39
+ expect(NEUTRAL_PERSONA.voice).toContain('helpful assistant');
40
+ expect(NEUTRAL_PERSONA.inspiration).toContain('reliable professional');
41
41
  });
42
42
 
43
43
  it('has no cultural flavor', () => {
44
44
  expect(NEUTRAL_PERSONA.culture).toBe('');
45
45
  });
46
46
 
47
- it('has all arrays populated with minimum counts', () => {
48
- expect(NEUTRAL_PERSONA.traits.length).toBeGreaterThanOrEqual(3);
49
- expect(NEUTRAL_PERSONA.quirks.length).toBeGreaterThanOrEqual(3);
50
- expect(NEUTRAL_PERSONA.opinions.length).toBeGreaterThanOrEqual(3);
51
- expect(NEUTRAL_PERSONA.metaphors.length).toBeGreaterThanOrEqual(3);
52
- expect(NEUTRAL_PERSONA.greetings.length).toBeGreaterThanOrEqual(2);
53
- expect(NEUTRAL_PERSONA.signoffs.length).toBeGreaterThanOrEqual(2);
47
+ it('has all arrays populated with exact counts', () => {
48
+ expect(NEUTRAL_PERSONA.traits).toHaveLength(6);
49
+ expect(NEUTRAL_PERSONA.quirks).toHaveLength(4);
50
+ expect(NEUTRAL_PERSONA.opinions).toHaveLength(6);
51
+ expect(NEUTRAL_PERSONA.metaphors).toHaveLength(5);
52
+ expect(NEUTRAL_PERSONA.greetings).toHaveLength(3);
53
+ expect(NEUTRAL_PERSONA.signoffs).toHaveLength(3);
54
54
  });
55
55
 
56
56
  it('has non-empty language and name rules', () => {
57
- expect(NEUTRAL_PERSONA.languageRule.length).toBeGreaterThan(0);
58
- expect(NEUTRAL_PERSONA.nameRule.length).toBeGreaterThan(0);
57
+ expect(NEUTRAL_PERSONA.languageRule).toContain("user's language");
58
+ expect(NEUTRAL_PERSONA.nameRule).toContain('name changes');
59
59
  });
60
60
  });
61
61
 
@@ -10,12 +10,6 @@ import {
10
10
  analyzeSemanticQuality,
11
11
  analyzeKnowledgeDepth,
12
12
  analyzeAlternatives,
13
- AMBIGUOUS_WORDS,
14
- GENERIC_OBJECTIVE_PATTERNS,
15
- RATIONALE_INDICATORS,
16
- SHALLOW_INDICATORS,
17
- KNOWLEDGE_INDICATORS,
18
- NAMED_PATTERN_REGEX,
19
13
  } from './gap-passes.js';
20
14
 
21
15
  function makePlan(overrides: Partial<Plan> = {}): Plan {
@@ -77,46 +71,6 @@ function makeAlternative(overrides: Partial<PlanAlternative> = {}): PlanAlternat
77
71
  };
78
72
  }
79
73
 
80
- describe('Pattern constants (passes 5-8)', () => {
81
- it('AMBIGUOUS_WORDS contains common vague terms', () => {
82
- expect(AMBIGUOUS_WORDS).toContain('maybe');
83
- expect(AMBIGUOUS_WORDS).toContain('probably');
84
- expect(AMBIGUOUS_WORDS).toContain('etc');
85
- });
86
-
87
- it('GENERIC_OBJECTIVE_PATTERNS matches simple verb-noun objectives', () => {
88
- expect(GENERIC_OBJECTIVE_PATTERNS.some((p) => p.test('Create something'))).toBe(true);
89
- expect(GENERIC_OBJECTIVE_PATTERNS.some((p) => p.test('Fix bug'))).toBe(true);
90
- });
91
-
92
- it('GENERIC_OBJECTIVE_PATTERNS does not match detailed objectives', () => {
93
- expect(
94
- GENERIC_OBJECTIVE_PATTERNS.some((p) => p.test('Create a user auth module with JWT')),
95
- ).toBe(false);
96
- });
97
-
98
- it('RATIONALE_INDICATORS contains reasoning words', () => {
99
- expect(RATIONALE_INDICATORS).toContain('because');
100
- expect(RATIONALE_INDICATORS).toContain('due to');
101
- });
102
-
103
- it('SHALLOW_INDICATORS contains subjective words', () => {
104
- expect(SHALLOW_INDICATORS).toContain('better');
105
- expect(SHALLOW_INDICATORS).toContain('good');
106
- });
107
-
108
- it('KNOWLEDGE_INDICATORS match domain patterns', () => {
109
- expect(KNOWLEDGE_INDICATORS.some((p) => p.test('vault pattern'))).toBe(true);
110
- expect(KNOWLEDGE_INDICATORS.some((p) => p.test('WCAG 2.1'))).toBe(true);
111
- expect(KNOWLEDGE_INDICATORS.some((p) => p.test('aria-label'))).toBe(true);
112
- });
113
-
114
- it('NAMED_PATTERN_REGEX matches hyphenated identifiers', () => {
115
- expect(NAMED_PATTERN_REGEX.test('zod-form-validation')).toBe(true);
116
- expect(NAMED_PATTERN_REGEX.test('simple')).toBe(false);
117
- });
118
- });
119
-
120
74
  describe('Pass 5: Clarity', () => {
121
75
  it('flags ambiguous language in objective', () => {
122
76
  const plan = makePlan({
@@ -14,13 +14,6 @@ import {
14
14
  analyzeCompleteness,
15
15
  analyzeFeasibility,
16
16
  analyzeRisk,
17
- METRIC_PATTERNS,
18
- EXCLUSION_KEYWORDS,
19
- OVERLY_BROAD_PATTERNS,
20
- DEPENDENCY_KEYWORDS,
21
- BREAKING_CHANGE_KEYWORDS,
22
- MITIGATION_KEYWORDS,
23
- VERIFICATION_KEYWORDS,
24
17
  } from './gap-patterns.js';
25
18
 
26
19
  function makePlan(overrides: Partial<Plan> = {}): Plan {
@@ -161,41 +154,6 @@ describe('Helper functions', () => {
161
154
  });
162
155
  });
163
156
 
164
- describe('Pattern constants', () => {
165
- it('METRIC_PATTERNS matches numbers', () => {
166
- expect(METRIC_PATTERNS.some((p) => p.test('reduce latency by 50%'))).toBe(true);
167
- });
168
-
169
- it('EXCLUSION_KEYWORDS includes common exclusion words', () => {
170
- expect(EXCLUSION_KEYWORDS).toContain('exclude');
171
- expect(EXCLUSION_KEYWORDS).toContain('not');
172
- });
173
-
174
- it('OVERLY_BROAD_PATTERNS includes dangerous scope terms', () => {
175
- expect(OVERLY_BROAD_PATTERNS).toContain('complete rewrite');
176
- });
177
-
178
- it('DEPENDENCY_KEYWORDS includes ordering terms', () => {
179
- expect(DEPENDENCY_KEYWORDS).toContain('depends');
180
- expect(DEPENDENCY_KEYWORDS).toContain('prerequisite');
181
- });
182
-
183
- it('BREAKING_CHANGE_KEYWORDS includes migration terms', () => {
184
- expect(BREAKING_CHANGE_KEYWORDS).toContain('breaking change');
185
- expect(BREAKING_CHANGE_KEYWORDS).toContain('database migration');
186
- });
187
-
188
- it('MITIGATION_KEYWORDS includes safety terms', () => {
189
- expect(MITIGATION_KEYWORDS).toContain('rollback');
190
- expect(MITIGATION_KEYWORDS).toContain('feature flag');
191
- });
192
-
193
- it('VERIFICATION_KEYWORDS includes testing terms', () => {
194
- expect(VERIFICATION_KEYWORDS).toContain('test');
195
- expect(VERIFICATION_KEYWORDS).toContain('coverage');
196
- });
197
- });
198
-
199
157
  describe('Pass 1: Structure', () => {
200
158
  it('returns no gaps for a well-structured plan', () => {
201
159
  const gaps = analyzeStructure(makePlan());
@@ -287,8 +287,10 @@ describe('JsonGoalRepository', () => {
287
287
  });
288
288
 
289
289
  it('should create and retrieve a goal', () => {
290
+ const before = Date.now();
290
291
  const goal = repo.create({ id: 'g1', title: 'Ship it', level: 'objective', status: 'planned' });
291
- expect(goal.createdAt).toBeGreaterThan(0);
292
+ expect(goal.createdAt).toBeGreaterThanOrEqual(before);
293
+ expect(goal.createdAt).toBeLessThanOrEqual(Date.now());
292
294
  expect(repo.getById('g1')?.title).toBe('Ship it');
293
295
  });
294
296