@soleri/core 9.0.4 → 9.2.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 (701) hide show
  1. package/dist/brain/intelligence.d.ts +27 -0
  2. package/dist/brain/intelligence.d.ts.map +1 -1
  3. package/dist/brain/intelligence.js +160 -14
  4. package/dist/brain/intelligence.js.map +1 -1
  5. package/dist/brain/learning-radar.d.ts +4 -0
  6. package/dist/brain/learning-radar.d.ts.map +1 -1
  7. package/dist/brain/learning-radar.js +20 -1
  8. package/dist/brain/learning-radar.js.map +1 -1
  9. package/dist/brain/strength-scorer.d.ts +31 -0
  10. package/dist/brain/strength-scorer.d.ts.map +1 -0
  11. package/dist/brain/strength-scorer.js +264 -0
  12. package/dist/brain/strength-scorer.js.map +1 -0
  13. package/dist/chat/agent-loop.d.ts.map +1 -1
  14. package/dist/chat/agent-loop.js +2 -0
  15. package/dist/chat/agent-loop.js.map +1 -1
  16. package/dist/chat/notifications.d.ts.map +1 -1
  17. package/dist/chat/notifications.js +2 -0
  18. package/dist/chat/notifications.js.map +1 -1
  19. package/dist/claudemd/compose.js +1 -1
  20. package/dist/claudemd/compose.js.map +1 -1
  21. package/dist/control/intent-router.d.ts.map +1 -1
  22. package/dist/control/intent-router.js +12 -4
  23. package/dist/control/intent-router.js.map +1 -1
  24. package/dist/curator/contradiction-detector.d.ts +27 -0
  25. package/dist/curator/contradiction-detector.d.ts.map +1 -0
  26. package/dist/curator/contradiction-detector.js +62 -0
  27. package/dist/curator/contradiction-detector.js.map +1 -0
  28. package/dist/curator/curator.d.ts +3 -4
  29. package/dist/curator/curator.d.ts.map +1 -1
  30. package/dist/curator/curator.js +90 -525
  31. package/dist/curator/curator.js.map +1 -1
  32. package/dist/curator/duplicate-detector.d.ts +14 -0
  33. package/dist/curator/duplicate-detector.d.ts.map +1 -0
  34. package/dist/curator/duplicate-detector.js +77 -0
  35. package/dist/curator/duplicate-detector.js.map +1 -0
  36. package/dist/curator/health-audit.d.ts +15 -0
  37. package/dist/curator/health-audit.d.ts.map +1 -0
  38. package/dist/curator/health-audit.js +97 -0
  39. package/dist/curator/health-audit.js.map +1 -0
  40. package/dist/curator/metadata-enricher.d.ts +17 -0
  41. package/dist/curator/metadata-enricher.d.ts.map +1 -0
  42. package/dist/curator/metadata-enricher.js +60 -0
  43. package/dist/curator/metadata-enricher.js.map +1 -0
  44. package/dist/curator/schema.d.ts +7 -0
  45. package/dist/curator/schema.d.ts.map +1 -0
  46. package/dist/curator/schema.js +62 -0
  47. package/dist/curator/schema.js.map +1 -0
  48. package/dist/curator/tag-manager.d.ts +36 -0
  49. package/dist/curator/tag-manager.d.ts.map +1 -0
  50. package/dist/curator/tag-manager.js +78 -0
  51. package/dist/curator/tag-manager.js.map +1 -0
  52. package/dist/engine/bin/soleri-engine.js +24 -3
  53. package/dist/engine/bin/soleri-engine.js.map +1 -1
  54. package/dist/engine/core-ops.d.ts.map +1 -1
  55. package/dist/engine/core-ops.js +23 -8
  56. package/dist/engine/core-ops.js.map +1 -1
  57. package/dist/engine/module-manifest.d.ts.map +1 -1
  58. package/dist/engine/module-manifest.js +22 -2
  59. package/dist/engine/module-manifest.js.map +1 -1
  60. package/dist/engine/register-engine.d.ts.map +1 -1
  61. package/dist/engine/register-engine.js +26 -2
  62. package/dist/engine/register-engine.js.map +1 -1
  63. package/dist/errors/retry.d.ts.map +1 -1
  64. package/dist/errors/retry.js +2 -0
  65. package/dist/errors/retry.js.map +1 -1
  66. package/dist/facades/types.d.ts +1 -1
  67. package/dist/flows/chain-types.d.ts +18 -18
  68. package/dist/flows/gate-evaluator.d.ts.map +1 -1
  69. package/dist/flows/gate-evaluator.js +22 -0
  70. package/dist/flows/gate-evaluator.js.map +1 -1
  71. package/dist/flows/types.d.ts +157 -28
  72. package/dist/flows/types.d.ts.map +1 -1
  73. package/dist/flows/types.js +4 -0
  74. package/dist/flows/types.js.map +1 -1
  75. package/dist/index.d.ts +10 -2
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +9 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/intake/intake-pipeline.d.ts.map +1 -1
  80. package/dist/intake/intake-pipeline.js +1 -0
  81. package/dist/intake/intake-pipeline.js.map +1 -1
  82. package/dist/intake/text-ingester.d.ts.map +1 -1
  83. package/dist/intake/text-ingester.js +2 -0
  84. package/dist/intake/text-ingester.js.map +1 -1
  85. package/dist/llm/key-pool.d.ts +1 -1
  86. package/dist/llm/key-pool.d.ts.map +1 -1
  87. package/dist/llm/key-pool.js +3 -4
  88. package/dist/llm/key-pool.js.map +1 -1
  89. package/dist/llm/utils.d.ts.map +1 -1
  90. package/dist/llm/utils.js +2 -0
  91. package/dist/llm/utils.js.map +1 -1
  92. package/dist/migrations/migration-runner.test-helpers.d.ts +13 -0
  93. package/dist/migrations/migration-runner.test-helpers.d.ts.map +1 -0
  94. package/dist/migrations/migration-runner.test-helpers.js +47 -0
  95. package/dist/migrations/migration-runner.test-helpers.js.map +1 -0
  96. package/dist/operator/operator-profile.d.ts +44 -0
  97. package/dist/operator/operator-profile.d.ts.map +1 -0
  98. package/dist/operator/operator-profile.js +377 -0
  99. package/dist/operator/operator-profile.js.map +1 -0
  100. package/dist/operator/operator-signals.d.ts +45 -0
  101. package/dist/operator/operator-signals.d.ts.map +1 -0
  102. package/dist/operator/operator-signals.js +228 -0
  103. package/dist/operator/operator-signals.js.map +1 -0
  104. package/dist/operator/operator-types.d.ts +360 -0
  105. package/dist/operator/operator-types.d.ts.map +1 -0
  106. package/dist/operator/operator-types.js +24 -0
  107. package/dist/operator/operator-types.js.map +1 -0
  108. package/dist/packs/types.d.ts +27 -27
  109. package/dist/paths.d.ts +40 -0
  110. package/dist/paths.d.ts.map +1 -0
  111. package/dist/paths.js +98 -0
  112. package/dist/paths.js.map +1 -0
  113. package/dist/persistence/index.d.ts +1 -1
  114. package/dist/persistence/index.d.ts.map +1 -1
  115. package/dist/persistence/index.js +1 -1
  116. package/dist/persistence/index.js.map +1 -1
  117. package/dist/persistence/sqlite-provider.d.ts +2 -0
  118. package/dist/persistence/sqlite-provider.d.ts.map +1 -1
  119. package/dist/persistence/sqlite-provider.js +8 -5
  120. package/dist/persistence/sqlite-provider.js.map +1 -1
  121. package/dist/planning/evidence-collector.d.ts +13 -1
  122. package/dist/planning/evidence-collector.d.ts.map +1 -1
  123. package/dist/planning/evidence-collector.js +33 -0
  124. package/dist/planning/evidence-collector.js.map +1 -1
  125. package/dist/planning/gap-analysis.d.ts +5 -4
  126. package/dist/planning/gap-analysis.d.ts.map +1 -1
  127. package/dist/planning/gap-analysis.js +7 -341
  128. package/dist/planning/gap-analysis.js.map +1 -1
  129. package/dist/planning/gap-passes.d.ts +19 -0
  130. package/dist/planning/gap-passes.d.ts.map +1 -0
  131. package/dist/planning/gap-passes.js +157 -0
  132. package/dist/planning/gap-passes.js.map +1 -0
  133. package/dist/planning/gap-patterns.d.ts +29 -0
  134. package/dist/planning/gap-patterns.d.ts.map +1 -0
  135. package/dist/planning/gap-patterns.js +129 -0
  136. package/dist/planning/gap-patterns.js.map +1 -0
  137. package/dist/planning/gap-types.d.ts +1 -1
  138. package/dist/planning/gap-types.d.ts.map +1 -1
  139. package/dist/planning/gap-types.js +1 -0
  140. package/dist/planning/gap-types.js.map +1 -1
  141. package/dist/planning/github-projection.d.ts +122 -0
  142. package/dist/planning/github-projection.d.ts.map +1 -0
  143. package/dist/planning/github-projection.js +294 -0
  144. package/dist/planning/github-projection.js.map +1 -0
  145. package/dist/planning/impact-analyzer.d.ts +26 -0
  146. package/dist/planning/impact-analyzer.d.ts.map +1 -0
  147. package/dist/planning/impact-analyzer.js +199 -0
  148. package/dist/planning/impact-analyzer.js.map +1 -0
  149. package/dist/planning/plan-lifecycle.d.ts +136 -0
  150. package/dist/planning/plan-lifecycle.d.ts.map +1 -0
  151. package/dist/planning/plan-lifecycle.js +296 -0
  152. package/dist/planning/plan-lifecycle.js.map +1 -0
  153. package/dist/planning/planner-types.d.ts +202 -0
  154. package/dist/planning/planner-types.d.ts.map +1 -0
  155. package/dist/planning/planner-types.js +6 -0
  156. package/dist/planning/planner-types.js.map +1 -0
  157. package/dist/planning/planner.d.ts +31 -383
  158. package/dist/planning/planner.d.ts.map +1 -1
  159. package/dist/planning/planner.js +154 -878
  160. package/dist/planning/planner.js.map +1 -1
  161. package/dist/planning/rationalization-detector.d.ts +32 -0
  162. package/dist/planning/rationalization-detector.d.ts.map +1 -0
  163. package/dist/planning/rationalization-detector.js +89 -0
  164. package/dist/planning/rationalization-detector.js.map +1 -0
  165. package/dist/planning/reconciliation-engine.d.ts +47 -0
  166. package/dist/planning/reconciliation-engine.d.ts.map +1 -0
  167. package/dist/planning/reconciliation-engine.js +128 -0
  168. package/dist/planning/reconciliation-engine.js.map +1 -0
  169. package/dist/planning/task-verifier.d.ts +85 -0
  170. package/dist/planning/task-verifier.d.ts.map +1 -0
  171. package/dist/planning/task-verifier.js +227 -0
  172. package/dist/planning/task-verifier.js.map +1 -0
  173. package/dist/plugins/types.d.ts +4 -4
  174. package/dist/runtime/admin-ops.d.ts +2 -2
  175. package/dist/runtime/admin-ops.d.ts.map +1 -1
  176. package/dist/runtime/admin-ops.js +44 -17
  177. package/dist/runtime/admin-ops.js.map +1 -1
  178. package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
  179. package/dist/runtime/admin-setup-ops.js +21 -46
  180. package/dist/runtime/admin-setup-ops.js.map +1 -1
  181. package/dist/runtime/archive-ops.d.ts +10 -0
  182. package/dist/runtime/archive-ops.d.ts.map +1 -0
  183. package/dist/runtime/archive-ops.js +310 -0
  184. package/dist/runtime/archive-ops.js.map +1 -0
  185. package/dist/runtime/capture-ops.d.ts.map +1 -1
  186. package/dist/runtime/capture-ops.js +42 -7
  187. package/dist/runtime/capture-ops.js.map +1 -1
  188. package/dist/runtime/claude-md-helpers.js +1 -1
  189. package/dist/runtime/claude-md-helpers.js.map +1 -1
  190. package/dist/runtime/context-health.d.ts +31 -0
  191. package/dist/runtime/context-health.d.ts.map +1 -0
  192. package/dist/runtime/context-health.js +57 -0
  193. package/dist/runtime/context-health.js.map +1 -0
  194. package/dist/runtime/facades/archive-facade.d.ts +10 -0
  195. package/dist/runtime/facades/archive-facade.d.ts.map +1 -0
  196. package/dist/runtime/facades/archive-facade.js +11 -0
  197. package/dist/runtime/facades/archive-facade.js.map +1 -0
  198. package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
  199. package/dist/runtime/facades/brain-facade.js +2 -0
  200. package/dist/runtime/facades/brain-facade.js.map +1 -1
  201. package/dist/runtime/facades/chat-facade.d.ts +7 -0
  202. package/dist/runtime/facades/chat-facade.d.ts.map +1 -1
  203. package/dist/runtime/facades/chat-facade.js +15 -800
  204. package/dist/runtime/facades/chat-facade.js.map +1 -1
  205. package/dist/runtime/facades/chat-service-ops.d.ts +9 -0
  206. package/dist/runtime/facades/chat-service-ops.d.ts.map +1 -0
  207. package/dist/runtime/facades/chat-service-ops.js +330 -0
  208. package/dist/runtime/facades/chat-service-ops.js.map +1 -0
  209. package/dist/runtime/facades/chat-session-ops.d.ts +8 -0
  210. package/dist/runtime/facades/chat-session-ops.d.ts.map +1 -0
  211. package/dist/runtime/facades/chat-session-ops.js +136 -0
  212. package/dist/runtime/facades/chat-session-ops.js.map +1 -0
  213. package/dist/runtime/facades/chat-state.d.ts +31 -0
  214. package/dist/runtime/facades/chat-state.d.ts.map +1 -0
  215. package/dist/runtime/facades/chat-state.js +32 -0
  216. package/dist/runtime/facades/chat-state.js.map +1 -0
  217. package/dist/runtime/facades/chat-transport-ops.d.ts +9 -0
  218. package/dist/runtime/facades/chat-transport-ops.d.ts.map +1 -0
  219. package/dist/runtime/facades/chat-transport-ops.js +337 -0
  220. package/dist/runtime/facades/chat-transport-ops.js.map +1 -0
  221. package/dist/runtime/facades/control-facade.d.ts.map +1 -1
  222. package/dist/runtime/facades/control-facade.js +4 -1
  223. package/dist/runtime/facades/control-facade.js.map +1 -1
  224. package/dist/runtime/facades/index.d.ts.map +1 -1
  225. package/dist/runtime/facades/index.js +6 -0
  226. package/dist/runtime/facades/index.js.map +1 -1
  227. package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
  228. package/dist/runtime/facades/memory-facade.js +75 -6
  229. package/dist/runtime/facades/memory-facade.js.map +1 -1
  230. package/dist/runtime/facades/operator-facade.d.ts +8 -0
  231. package/dist/runtime/facades/operator-facade.d.ts.map +1 -0
  232. package/dist/runtime/facades/operator-facade.js +220 -0
  233. package/dist/runtime/facades/operator-facade.js.map +1 -0
  234. package/dist/runtime/facades/orchestrate-facade.js +3 -3
  235. package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
  236. package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
  237. package/dist/runtime/facades/plan-facade.js +39 -6
  238. package/dist/runtime/facades/plan-facade.js.map +1 -1
  239. package/dist/runtime/facades/review-facade.d.ts +7 -0
  240. package/dist/runtime/facades/review-facade.d.ts.map +1 -0
  241. package/dist/runtime/facades/review-facade.js +8 -0
  242. package/dist/runtime/facades/review-facade.js.map +1 -0
  243. package/dist/runtime/facades/sync-facade.d.ts +7 -0
  244. package/dist/runtime/facades/sync-facade.d.ts.map +1 -0
  245. package/dist/runtime/facades/sync-facade.js +8 -0
  246. package/dist/runtime/facades/sync-facade.js.map +1 -0
  247. package/dist/runtime/facades/vault-facade.d.ts +4 -1
  248. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  249. package/dist/runtime/facades/vault-facade.js +13 -66
  250. package/dist/runtime/facades/vault-facade.js.map +1 -1
  251. package/dist/runtime/github-integration.d.ts +49 -0
  252. package/dist/runtime/github-integration.d.ts.map +1 -0
  253. package/dist/runtime/github-integration.js +113 -0
  254. package/dist/runtime/github-integration.js.map +1 -0
  255. package/dist/runtime/grading-ops.js +1 -1
  256. package/dist/runtime/grading-ops.js.map +1 -1
  257. package/dist/runtime/memory-extra-ops.d.ts.map +1 -1
  258. package/dist/runtime/memory-extra-ops.js +6 -2
  259. package/dist/runtime/memory-extra-ops.js.map +1 -1
  260. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  261. package/dist/runtime/orchestrate-ops.js +367 -40
  262. package/dist/runtime/orchestrate-ops.js.map +1 -1
  263. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  264. package/dist/runtime/planning-extra-ops.js +69 -4
  265. package/dist/runtime/planning-extra-ops.js.map +1 -1
  266. package/dist/runtime/review-ops.d.ts +10 -0
  267. package/dist/runtime/review-ops.d.ts.map +1 -0
  268. package/dist/runtime/review-ops.js +97 -0
  269. package/dist/runtime/review-ops.js.map +1 -0
  270. package/dist/runtime/runtime.d.ts.map +1 -1
  271. package/dist/runtime/runtime.js +27 -12
  272. package/dist/runtime/runtime.js.map +1 -1
  273. package/dist/runtime/session-briefing.d.ts +3 -0
  274. package/dist/runtime/session-briefing.d.ts.map +1 -1
  275. package/dist/runtime/session-briefing.js +68 -1
  276. package/dist/runtime/session-briefing.js.map +1 -1
  277. package/dist/runtime/sync-ops.d.ts +12 -0
  278. package/dist/runtime/sync-ops.d.ts.map +1 -0
  279. package/dist/runtime/sync-ops.js +288 -0
  280. package/dist/runtime/sync-ops.js.map +1 -0
  281. package/dist/runtime/types.d.ts +10 -4
  282. package/dist/runtime/types.d.ts.map +1 -1
  283. package/dist/runtime/vault-extra-ops.d.ts +5 -4
  284. package/dist/runtime/vault-extra-ops.d.ts.map +1 -1
  285. package/dist/runtime/vault-extra-ops.js +5 -300
  286. package/dist/runtime/vault-extra-ops.js.map +1 -1
  287. package/dist/runtime/vault-sharing-ops.d.ts +4 -4
  288. package/dist/runtime/vault-sharing-ops.d.ts.map +1 -1
  289. package/dist/runtime/vault-sharing-ops.js +5 -300
  290. package/dist/runtime/vault-sharing-ops.js.map +1 -1
  291. package/dist/skills/sync-skills.d.ts +27 -0
  292. package/dist/skills/sync-skills.d.ts.map +1 -0
  293. package/dist/skills/sync-skills.js +81 -0
  294. package/dist/skills/sync-skills.js.map +1 -0
  295. package/dist/update-check.d.ts +14 -0
  296. package/dist/update-check.d.ts.map +1 -0
  297. package/dist/update-check.js +96 -0
  298. package/dist/update-check.js.map +1 -0
  299. package/dist/vault/linking.d.ts +10 -12
  300. package/dist/vault/linking.d.ts.map +1 -1
  301. package/dist/vault/linking.js +104 -161
  302. package/dist/vault/linking.js.map +1 -1
  303. package/dist/vault/vault-entries.d.ts +69 -0
  304. package/dist/vault/vault-entries.d.ts.map +1 -0
  305. package/dist/vault/vault-entries.js +257 -0
  306. package/dist/vault/vault-entries.js.map +1 -0
  307. package/dist/vault/vault-interfaces.d.ts +153 -0
  308. package/dist/vault/vault-interfaces.d.ts.map +1 -0
  309. package/dist/vault/vault-interfaces.js +2 -0
  310. package/dist/vault/vault-interfaces.js.map +1 -0
  311. package/dist/vault/vault-maintenance.d.ts +40 -0
  312. package/dist/vault/vault-maintenance.d.ts.map +1 -0
  313. package/dist/vault/vault-maintenance.js +142 -0
  314. package/dist/vault/vault-maintenance.js.map +1 -0
  315. package/dist/vault/vault-markdown-sync.d.ts +22 -0
  316. package/dist/vault/vault-markdown-sync.d.ts.map +1 -0
  317. package/dist/vault/vault-markdown-sync.js +143 -0
  318. package/dist/vault/vault-markdown-sync.js.map +1 -0
  319. package/dist/vault/vault-memories.d.ts +61 -0
  320. package/dist/vault/vault-memories.d.ts.map +1 -0
  321. package/dist/vault/vault-memories.js +240 -0
  322. package/dist/vault/vault-memories.js.map +1 -0
  323. package/dist/vault/vault-schema.d.ts +9 -0
  324. package/dist/vault/vault-schema.d.ts.map +1 -0
  325. package/dist/vault/vault-schema.js +179 -0
  326. package/dist/vault/vault-schema.js.map +1 -0
  327. package/dist/vault/vault.d.ts +29 -81
  328. package/dist/vault/vault.d.ts.map +1 -1
  329. package/dist/vault/vault.js +78 -931
  330. package/dist/vault/vault.js.map +1 -1
  331. package/package.json +1 -1
  332. package/src/agency/agency-manager.test.ts +600 -0
  333. package/src/agency/default-rules.test.ts +228 -0
  334. package/src/{__tests__ → brain}/brain-intelligence.test.ts +37 -14
  335. package/src/{__tests__ → brain}/brain.test.ts +1 -1
  336. package/src/brain/intelligence.ts +196 -15
  337. package/src/brain/learning-radar.ts +22 -1
  338. package/src/{__tests__ → brain}/second-brain-features.test.ts +4 -4
  339. package/src/{__tests__ → brain}/session-lifecycle.test.ts +2 -2
  340. package/src/brain/strength-scorer.ts +404 -0
  341. package/src/capabilities/chain-mapping.test.ts +66 -0
  342. package/src/capabilities/registry.test.ts +369 -0
  343. package/src/chat/agent-loop.test.ts +394 -0
  344. package/src/chat/agent-loop.ts +2 -0
  345. package/src/{__tests__ → chat}/chat-differentiators.test.ts +3 -3
  346. package/src/{__tests__ → chat}/chat-enhanced.test.ts +4 -4
  347. package/src/{__tests__ → chat}/chat-transport.test.ts +6 -6
  348. package/src/chat/mcp-bridge.test.ts +173 -0
  349. package/src/chat/notifications.ts +2 -0
  350. package/src/chat/output-compressor.test.ts +164 -0
  351. package/src/claudemd/compose.test.ts +178 -0
  352. package/src/claudemd/compose.ts +1 -1
  353. package/src/claudemd/inject.test.ts +211 -0
  354. package/src/context/context-engine.test.ts +461 -0
  355. package/src/control/identity-manager.test.ts +305 -0
  356. package/src/control/intent-router.test.ts +360 -0
  357. package/src/control/intent-router.ts +13 -4
  358. package/src/curator/classifier.test.ts +104 -0
  359. package/src/curator/contradiction-detector.test.ts +180 -0
  360. package/src/curator/contradiction-detector.ts +87 -0
  361. package/src/{__tests__ → curator}/curator-pipeline-e2e.test.ts +10 -10
  362. package/src/{__tests__ → curator}/curator.test.ts +77 -1
  363. package/src/curator/curator.ts +115 -777
  364. package/src/curator/duplicate-detector.test.ts +183 -0
  365. package/src/curator/duplicate-detector.ts +103 -0
  366. package/src/curator/health-audit.ts +126 -0
  367. package/src/curator/metadata-enricher.ts +84 -0
  368. package/src/curator/quality-gate.test.ts +135 -0
  369. package/src/curator/schema.ts +65 -0
  370. package/src/curator/tag-manager.test.ts +165 -0
  371. package/src/curator/tag-manager.ts +109 -0
  372. package/src/domain-packs/inject-rules.test.ts +117 -0
  373. package/src/domain-packs/knowledge-installer.test.ts +171 -0
  374. package/src/domain-packs/loader.test.ts +86 -0
  375. package/src/domain-packs/pack-runtime.test.ts +140 -0
  376. package/src/domain-packs/skills-installer.test.ts +135 -0
  377. package/src/domain-packs/token-resolver.test.ts +150 -0
  378. package/src/domain-packs/types.test.ts +130 -0
  379. package/src/enforcement/adapters/claude-code.test.ts +216 -0
  380. package/src/enforcement/registry.test.ts +264 -0
  381. package/src/engine/bin/soleri-engine.ts +28 -4
  382. package/src/engine/core-ops.test.ts +254 -0
  383. package/src/engine/core-ops.ts +25 -8
  384. package/src/engine/module-manifest.test.ts +124 -0
  385. package/src/engine/module-manifest.ts +22 -2
  386. package/src/engine/register-engine.test.ts +230 -0
  387. package/src/engine/register-engine.ts +26 -2
  388. package/src/errors/classify.test.ts +199 -0
  389. package/src/errors/retry.test.ts +156 -0
  390. package/src/errors/retry.ts +2 -0
  391. package/src/errors/types.test.ts +108 -0
  392. package/src/events/event-bus.test.ts +149 -0
  393. package/src/extensions/middleware.test.ts +234 -0
  394. package/src/facades/facade-factory.test.ts +424 -0
  395. package/src/flows/chain-runner.test.ts +273 -0
  396. package/src/flows/context-router.test.ts +52 -0
  397. package/src/flows/dispatch-registry.test.ts +128 -0
  398. package/src/flows/epilogue.test.ts +107 -0
  399. package/src/flows/executor.test.ts +263 -0
  400. package/src/flows/gate-evaluator.test.ts +194 -0
  401. package/src/flows/gate-evaluator.ts +25 -0
  402. package/src/flows/types.ts +4 -0
  403. package/src/governance/governance.test.ts +726 -0
  404. package/src/health/health-registry.test.ts +186 -0
  405. package/src/health/vault-integrity.test.ts +110 -0
  406. package/src/index.ts +92 -0
  407. package/src/intake/content-classifier.test.ts +209 -0
  408. package/src/intake/dedup-gate.test.ts +131 -0
  409. package/src/intake/intake-pipeline.test.ts +506 -0
  410. package/src/intake/intake-pipeline.ts +1 -0
  411. package/src/intake/text-ingester.test.ts +194 -0
  412. package/src/intake/text-ingester.ts +2 -0
  413. package/src/llm/key-pool.test.ts +236 -0
  414. package/src/llm/key-pool.ts +3 -4
  415. package/src/llm/llm-client.test.ts +345 -0
  416. package/src/llm/oauth-discovery.test.ts +180 -0
  417. package/src/llm/utils.test.ts +327 -0
  418. package/src/llm/utils.ts +2 -0
  419. package/src/{__tests__ → logging}/logger.test.ts +41 -62
  420. package/src/loop/loop-manager.test.ts +519 -0
  421. package/src/migrations/migration-runner.edge-cases.test.ts +319 -0
  422. package/src/migrations/migration-runner.test-helpers.ts +64 -0
  423. package/src/migrations/migration-runner.test.ts +385 -0
  424. package/src/operator/auto-signal-pipeline.test.ts +207 -0
  425. package/src/operator/operator-profile-extended.test.ts +320 -0
  426. package/src/operator/operator-profile.test.ts +314 -0
  427. package/src/operator/operator-profile.ts +469 -0
  428. package/src/operator/operator-signals-extended.test.ts +245 -0
  429. package/src/operator/operator-signals.test.ts +281 -0
  430. package/src/operator/operator-signals.ts +261 -0
  431. package/src/operator/operator-types.ts +444 -0
  432. package/src/operator/prompts/hook-precompact-operator-dispatch.md +94 -0
  433. package/src/operator/prompts/subagent-soft-signal-extractor.md +125 -0
  434. package/src/operator/prompts/subagent-synthesis-cognition.md +181 -0
  435. package/src/operator/prompts/subagent-synthesis-communication.md +140 -0
  436. package/src/operator/prompts/subagent-synthesis-technical.md +160 -0
  437. package/src/operator/prompts/subagent-synthesis-trust.md +143 -0
  438. package/src/{__tests__ → packs}/pack-lockfile.test.ts +3 -3
  439. package/src/{__tests__ → packs}/pack-system.test.ts +2 -2
  440. package/src/paths.ts +115 -0
  441. package/src/persistence/index.ts +1 -1
  442. package/src/persistence/sqlite-provider.test.ts +540 -0
  443. package/src/persistence/sqlite-provider.ts +8 -5
  444. package/src/persona/defaults.test.ts +59 -0
  445. package/src/persona/loader.test.ts +67 -0
  446. package/src/persona/prompt-generator.test.ts +127 -0
  447. package/src/planning/evidence-collector.test.ts +406 -0
  448. package/src/planning/evidence-collector.ts +50 -0
  449. package/src/planning/gap-analysis-alternatives.test.ts +169 -0
  450. package/src/planning/gap-analysis.ts +21 -636
  451. package/src/planning/gap-passes.test.ts +372 -0
  452. package/src/planning/gap-passes.ts +298 -0
  453. package/src/planning/gap-patterns.test.ts +320 -0
  454. package/src/planning/gap-patterns.ts +234 -0
  455. package/src/planning/gap-types.ts +4 -1
  456. package/src/planning/github-projection.test.ts +177 -0
  457. package/src/planning/github-projection.ts +425 -0
  458. package/src/planning/impact-analyzer.test.ts +180 -0
  459. package/src/planning/impact-analyzer.ts +264 -0
  460. package/src/planning/plan-lifecycle.test.ts +312 -0
  461. package/src/planning/plan-lifecycle.ts +346 -0
  462. package/src/planning/planner-types.ts +215 -0
  463. package/src/{__tests__ → planning}/planner.test.ts +169 -15
  464. package/src/planning/planner.ts +197 -1228
  465. package/src/planning/rationalization-detector.test.ts +171 -0
  466. package/src/planning/rationalization-detector.ts +138 -0
  467. package/src/planning/reconciliation-engine.test.ts +141 -0
  468. package/src/planning/reconciliation-engine.ts +162 -0
  469. package/src/planning/task-verifier.test.ts +235 -0
  470. package/src/planning/task-verifier.ts +303 -0
  471. package/src/planning/verification-protocol.test.ts +201 -0
  472. package/src/playbooks/generic/generic-playbooks.test.ts +438 -0
  473. package/src/playbooks/index.test.ts +77 -0
  474. package/src/playbooks/playbook-executor.test.ts +255 -0
  475. package/src/playbooks/playbook-registry.test.ts +232 -0
  476. package/src/playbooks/playbook-seeder.test.ts +153 -0
  477. package/src/plugins/plugin-loader.test.ts +212 -0
  478. package/src/plugins/plugin-registry.test.ts +272 -0
  479. package/src/project/project-registry.test.ts +428 -0
  480. package/src/prompts/parser.test.ts +100 -0
  481. package/src/prompts/template-manager.test.ts +109 -0
  482. package/src/{__tests__ → queue}/async-infrastructure.test.ts +3 -3
  483. package/src/queue/job-queue.test.ts +331 -0
  484. package/src/queue/pipeline-runner.test.ts +209 -0
  485. package/src/runtime/admin-extra-ops.test.ts +527 -0
  486. package/src/runtime/admin-ops.test.ts +257 -0
  487. package/src/runtime/admin-ops.ts +45 -17
  488. package/src/runtime/admin-setup-ops.test.ts +328 -0
  489. package/src/runtime/admin-setup-ops.ts +20 -43
  490. package/src/runtime/archive-ops.test.ts +269 -0
  491. package/src/runtime/archive-ops.ts +347 -0
  492. package/src/runtime/capture-ops.test.ts +433 -0
  493. package/src/runtime/capture-ops.ts +50 -8
  494. package/src/runtime/chain-ops.test.ts +149 -0
  495. package/src/runtime/claude-md-helpers.test.ts +191 -0
  496. package/src/runtime/claude-md-helpers.ts +1 -1
  497. package/src/runtime/context-health.test.ts +78 -0
  498. package/src/runtime/context-health.ts +85 -0
  499. package/src/runtime/curator-extra-ops.test.ts +202 -0
  500. package/src/runtime/deprecation.test.ts +98 -0
  501. package/src/runtime/domain-ops.test.ts +268 -0
  502. package/src/runtime/facades/admin-facade.test.ts +333 -0
  503. package/src/runtime/facades/agency-facade.test.ts +278 -0
  504. package/src/runtime/facades/archive-facade.test.ts +294 -0
  505. package/src/runtime/facades/archive-facade.ts +14 -0
  506. package/src/runtime/facades/brain-facade.test.ts +714 -0
  507. package/src/runtime/facades/brain-facade.ts +2 -0
  508. package/src/runtime/facades/chat-facade.test.ts +166 -0
  509. package/src/runtime/facades/chat-facade.ts +15 -906
  510. package/src/runtime/facades/chat-service-ops.test.ts +276 -0
  511. package/src/runtime/facades/chat-service-ops.ts +374 -0
  512. package/src/runtime/facades/chat-session-ops.test.ts +197 -0
  513. package/src/runtime/facades/chat-session-ops.ts +146 -0
  514. package/src/runtime/facades/chat-state.ts +60 -0
  515. package/src/runtime/facades/chat-transport-ops.test.ts +269 -0
  516. package/src/runtime/facades/chat-transport-ops.ts +380 -0
  517. package/src/runtime/facades/context-facade.test.ts +108 -0
  518. package/src/runtime/facades/control-facade.test.ts +436 -0
  519. package/src/runtime/facades/control-facade.ts +6 -1
  520. package/src/runtime/facades/curator-facade.test.ts +303 -0
  521. package/src/runtime/facades/index.ts +6 -0
  522. package/src/runtime/facades/loop-facade.test.ts +245 -0
  523. package/src/runtime/facades/memory-facade.test.ts +269 -0
  524. package/src/runtime/facades/memory-facade.ts +78 -6
  525. package/src/runtime/facades/operator-facade.test.ts +208 -0
  526. package/src/runtime/facades/operator-facade.ts +236 -0
  527. package/src/runtime/facades/orchestrate-facade.test.ts +185 -0
  528. package/src/runtime/facades/orchestrate-facade.ts +3 -3
  529. package/src/runtime/facades/plan-facade.test.ts +266 -0
  530. package/src/runtime/facades/plan-facade.ts +42 -6
  531. package/src/runtime/facades/review-facade.test.ts +82 -0
  532. package/src/runtime/facades/review-facade.ts +11 -0
  533. package/src/runtime/facades/sync-facade.test.ts +113 -0
  534. package/src/runtime/facades/sync-facade.ts +11 -0
  535. package/src/runtime/facades/vault-facade.test.ts +631 -0
  536. package/src/runtime/facades/vault-facade.ts +15 -70
  537. package/src/runtime/feature-flags.test.ts +140 -0
  538. package/src/runtime/github-integration.test.ts +89 -0
  539. package/src/runtime/github-integration.ts +159 -0
  540. package/src/runtime/grading-ops.test.ts +141 -0
  541. package/src/runtime/grading-ops.ts +1 -1
  542. package/src/runtime/intake-ops.test.ts +208 -0
  543. package/src/runtime/loop-ops.test.ts +238 -0
  544. package/src/runtime/memory-cross-project-ops.test.ts +177 -0
  545. package/src/runtime/memory-extra-ops.test.ts +453 -0
  546. package/src/runtime/memory-extra-ops.ts +6 -2
  547. package/src/runtime/orchestrate-ops.test.ts +302 -0
  548. package/src/runtime/orchestrate-ops.ts +435 -46
  549. package/src/runtime/pack-ops.test.ts +158 -0
  550. package/src/runtime/planning-extra-ops.test.ts +583 -0
  551. package/src/runtime/planning-extra-ops.ts +72 -4
  552. package/src/{__tests__ → runtime}/playbook-ops-execution.test.ts +3 -3
  553. package/src/runtime/playbook-ops.test.ts +262 -0
  554. package/src/runtime/plugin-ops.test.ts +201 -0
  555. package/src/runtime/project-ops.test.ts +235 -0
  556. package/src/runtime/review-ops.test.ts +142 -0
  557. package/src/runtime/review-ops.ts +99 -0
  558. package/src/runtime/runtime.test.ts +363 -0
  559. package/src/runtime/runtime.ts +39 -12
  560. package/src/runtime/session-briefing.test.ts +302 -0
  561. package/src/runtime/session-briefing.ts +80 -1
  562. package/src/runtime/sync-ops.test.ts +221 -0
  563. package/src/runtime/sync-ops.ts +325 -0
  564. package/src/runtime/telemetry-ops.test.ts +132 -0
  565. package/src/runtime/types.ts +10 -4
  566. package/src/runtime/vault-extra-ops.test.ts +246 -0
  567. package/src/runtime/vault-extra-ops.ts +5 -332
  568. package/src/runtime/vault-linking-ops.test.ts +237 -0
  569. package/src/runtime/vault-sharing-ops.test.ts +130 -0
  570. package/src/runtime/vault-sharing-ops.ts +5 -329
  571. package/src/skills/sync-skills.ts +108 -0
  572. package/src/streams/normalize.test.ts +95 -0
  573. package/src/streams/replayable-stream.test.ts +166 -0
  574. package/src/telemetry/telemetry.test.ts +143 -0
  575. package/src/transport/http-server.test.ts +394 -0
  576. package/src/transport/lsp-server.test.ts +458 -0
  577. package/src/transport/rate-limiter.test.ts +126 -0
  578. package/src/transport/session-manager.test.ts +133 -0
  579. package/src/transport/token-auth.test.ts +136 -0
  580. package/src/transport/ws-server.test.ts +294 -0
  581. package/src/update-check.ts +111 -0
  582. package/src/vault/__tests__/vault-characterization.test.ts +168 -0
  583. package/src/vault/content-hash.test.ts +78 -0
  584. package/src/vault/git-vault-sync.test.ts +234 -0
  585. package/src/vault/knowledge-review.test.ts +269 -0
  586. package/src/vault/linking.test.ts +358 -0
  587. package/src/vault/linking.ts +149 -183
  588. package/src/vault/obsidian-sync.test.ts +342 -0
  589. package/src/vault/playbook.test.ts +152 -0
  590. package/src/vault/scope-detector.test.ts +187 -0
  591. package/src/vault/vault-branching.test.ts +250 -0
  592. package/src/{__tests__ → vault}/vault-connect.test.ts +1 -1
  593. package/src/vault/vault-entries.ts +282 -0
  594. package/src/vault/vault-interfaces.ts +56 -0
  595. package/src/vault/vault-maintenance.ts +205 -0
  596. package/src/vault/vault-manager.test.ts +206 -0
  597. package/src/vault/vault-markdown-sync.test.ts +203 -0
  598. package/src/vault/vault-markdown-sync.ts +160 -0
  599. package/src/vault/vault-memories.ts +339 -0
  600. package/src/{__tests__ → vault}/vault-scaling.test.ts +1 -1
  601. package/src/vault/vault-schema.ts +181 -0
  602. package/src/{__tests__ → vault}/vault-sharing.test.ts +4 -4
  603. package/src/{__tests__ → vault}/vault.test.ts +2 -2
  604. package/src/vault/vault.ts +89 -1171
  605. package/dist/cognee/client.d.ts +0 -43
  606. package/dist/cognee/client.d.ts.map +0 -1
  607. package/dist/cognee/client.js +0 -375
  608. package/dist/cognee/client.js.map +0 -1
  609. package/dist/cognee/sync-manager.d.ts +0 -153
  610. package/dist/cognee/sync-manager.d.ts.map +0 -1
  611. package/dist/cognee/sync-manager.js +0 -390
  612. package/dist/cognee/sync-manager.js.map +0 -1
  613. package/dist/cognee/types.d.ts +0 -62
  614. package/dist/cognee/types.d.ts.map +0 -1
  615. package/dist/cognee/types.js +0 -3
  616. package/dist/cognee/types.js.map +0 -1
  617. package/dist/governance/index.d.ts +0 -3
  618. package/dist/governance/index.d.ts.map +0 -1
  619. package/dist/governance/index.js +0 -2
  620. package/dist/governance/index.js.map +0 -1
  621. package/dist/health/doctor-checks.d.ts +0 -15
  622. package/dist/health/doctor-checks.d.ts.map +0 -1
  623. package/dist/health/doctor-checks.js +0 -98
  624. package/dist/health/doctor-checks.js.map +0 -1
  625. package/dist/persistence/postgres-provider.d.ts +0 -81
  626. package/dist/persistence/postgres-provider.d.ts.map +0 -1
  627. package/dist/persistence/postgres-provider.js +0 -256
  628. package/dist/persistence/postgres-provider.js.map +0 -1
  629. package/dist/runtime/cognee-sync-ops.d.ts +0 -12
  630. package/dist/runtime/cognee-sync-ops.d.ts.map +0 -1
  631. package/dist/runtime/cognee-sync-ops.js +0 -93
  632. package/dist/runtime/cognee-sync-ops.js.map +0 -1
  633. package/dist/runtime/core-ops.d.ts +0 -23
  634. package/dist/runtime/core-ops.d.ts.map +0 -1
  635. package/dist/runtime/core-ops.js +0 -1296
  636. package/dist/runtime/core-ops.js.map +0 -1
  637. package/dist/runtime/facades/cognee-facade.d.ts +0 -8
  638. package/dist/runtime/facades/cognee-facade.d.ts.map +0 -1
  639. package/dist/runtime/facades/cognee-facade.js +0 -156
  640. package/dist/runtime/facades/cognee-facade.js.map +0 -1
  641. package/src/__tests__/admin-extra-ops.test.ts +0 -484
  642. package/src/__tests__/admin-ops.test.ts +0 -268
  643. package/src/__tests__/admin-setup-ops.test.ts +0 -355
  644. package/src/__tests__/agency-manager.test.ts +0 -374
  645. package/src/__tests__/agent-loop.test.ts +0 -256
  646. package/src/__tests__/capture-ops.test.ts +0 -784
  647. package/src/__tests__/claudemd.test.ts +0 -282
  648. package/src/__tests__/content-hash.test.ts +0 -60
  649. package/src/__tests__/context-engine.test.ts +0 -251
  650. package/src/__tests__/core-ops.test.ts +0 -550
  651. package/src/__tests__/curator-extra-ops.test.ts +0 -383
  652. package/src/__tests__/deprecation.test.ts +0 -78
  653. package/src/__tests__/domain-ops.test.ts +0 -226
  654. package/src/__tests__/domain-packs.test.ts +0 -421
  655. package/src/__tests__/enforcement.test.ts +0 -153
  656. package/src/__tests__/errors.test.ts +0 -388
  657. package/src/__tests__/extensions.test.ts +0 -233
  658. package/src/__tests__/facade-factory.test.ts +0 -271
  659. package/src/__tests__/feature-flags.test.ts +0 -137
  660. package/src/__tests__/flows.test.ts +0 -604
  661. package/src/__tests__/git-vault-sync.test.ts +0 -230
  662. package/src/__tests__/governance.test.ts +0 -522
  663. package/src/__tests__/grading-ops.test.ts +0 -361
  664. package/src/__tests__/health-registry.test.ts +0 -173
  665. package/src/__tests__/identity-manager.test.ts +0 -243
  666. package/src/__tests__/intake-pipeline.test.ts +0 -162
  667. package/src/__tests__/intent-router.test.ts +0 -222
  668. package/src/__tests__/knowledge-review.test.ts +0 -104
  669. package/src/__tests__/llm-client.test.ts +0 -69
  670. package/src/__tests__/llm.test.ts +0 -556
  671. package/src/__tests__/loader.test.ts +0 -176
  672. package/src/__tests__/loop-ops.test.ts +0 -469
  673. package/src/__tests__/lsp-transport.test.ts +0 -442
  674. package/src/__tests__/memory-cross-project-ops.test.ts +0 -248
  675. package/src/__tests__/memory-extra-ops.test.ts +0 -352
  676. package/src/__tests__/migration-runner.test.ts +0 -170
  677. package/src/__tests__/module-manifest-drift.test.ts +0 -59
  678. package/src/__tests__/normalize.test.ts +0 -85
  679. package/src/__tests__/obsidian-sync.test.ts +0 -354
  680. package/src/__tests__/orchestrate-ops.test.ts +0 -289
  681. package/src/__tests__/pack-ops.test.ts +0 -146
  682. package/src/__tests__/persistence.test.ts +0 -291
  683. package/src/__tests__/planning-extra-ops.test.ts +0 -706
  684. package/src/__tests__/playbook-executor.test.ts +0 -249
  685. package/src/__tests__/playbook-registry.test.ts +0 -326
  686. package/src/__tests__/playbook-seeder.test.ts +0 -163
  687. package/src/__tests__/playbook.test.ts +0 -389
  688. package/src/__tests__/plugin-ops.test.ts +0 -411
  689. package/src/__tests__/plugin-system.test.ts +0 -509
  690. package/src/__tests__/project-ops.test.ts +0 -381
  691. package/src/__tests__/replayable-stream.test.ts +0 -177
  692. package/src/__tests__/runtime.test.ts +0 -95
  693. package/src/__tests__/scope-detector.test.ts +0 -121
  694. package/src/__tests__/template-manager.test.ts +0 -222
  695. package/src/__tests__/token-resolver.test.ts +0 -79
  696. package/src/__tests__/transport.test.ts +0 -758
  697. package/src/__tests__/vault-branching.test.ts +0 -274
  698. package/src/__tests__/vault-extra-ops.test.ts +0 -482
  699. package/src/__tests__/vault-integrity.test.ts +0 -71
  700. package/src/__tests__/vault-manager.test.ts +0 -238
  701. package/src/__tests__/ws-transport.test.ts +0 -479
@@ -0,0 +1,342 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { mkdirSync, rmSync, existsSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { randomUUID } from 'node:crypto';
6
+ import {
7
+ toObsidianMarkdown,
8
+ fromObsidianMarkdown,
9
+ titleToSlug,
10
+ ObsidianSync,
11
+ } from './obsidian-sync.js';
12
+ import type { IntelligenceEntry } from '../intelligence/types.js';
13
+
14
+ function makeEntry(overrides: Partial<IntelligenceEntry> = {}): IntelligenceEntry {
15
+ return {
16
+ id: `test-${randomUUID().slice(0, 8)}`,
17
+ type: 'pattern',
18
+ domain: 'architecture',
19
+ title: 'Test Pattern',
20
+ severity: 'suggestion',
21
+ description: 'A test pattern description.',
22
+ tags: ['testing', 'unit'],
23
+ ...overrides,
24
+ };
25
+ }
26
+
27
+ // ─── Format Helpers ──────────────────────────────────────────────────
28
+
29
+ describe('toObsidianMarkdown', () => {
30
+ it('produces valid frontmatter with id, type, domain', () => {
31
+ const md = toObsidianMarkdown(makeEntry({ id: 'e1', type: 'pattern', domain: 'arch' }));
32
+ expect(md).toContain('---');
33
+ expect(md).toContain('id: "e1"');
34
+ expect(md).toContain('type: "pattern"');
35
+ expect(md).toContain('domain: "arch"');
36
+ });
37
+
38
+ it('includes tags array in frontmatter', () => {
39
+ const md = toObsidianMarkdown(makeEntry({ tags: ['a', 'b'] }));
40
+ expect(md).toContain('tags: ["a", "b"]');
41
+ });
42
+
43
+ it('includes severity when present', () => {
44
+ const md = toObsidianMarkdown(makeEntry({ severity: 'critical' }));
45
+ expect(md).toContain('severity: "critical"');
46
+ });
47
+
48
+ it('includes title as H1 heading', () => {
49
+ const md = toObsidianMarkdown(makeEntry({ title: 'My Pattern' }));
50
+ expect(md).toContain('# My Pattern');
51
+ });
52
+
53
+ it('includes description as body', () => {
54
+ const md = toObsidianMarkdown(makeEntry({ description: 'Body text here.' }));
55
+ expect(md).toContain('Body text here.');
56
+ });
57
+
58
+ it('omits tags line when tags are empty', () => {
59
+ const md = toObsidianMarkdown(makeEntry({ tags: [] }));
60
+ expect(md).not.toContain('tags:');
61
+ });
62
+
63
+ it('includes updated timestamp', () => {
64
+ const md = toObsidianMarkdown(makeEntry());
65
+ expect(md).toMatch(/updated: \d+/);
66
+ });
67
+ });
68
+
69
+ describe('fromObsidianMarkdown', () => {
70
+ it('parses frontmatter and body', () => {
71
+ const md = [
72
+ '---',
73
+ 'id: "e1"',
74
+ 'type: "pattern"',
75
+ 'domain: "arch"',
76
+ 'severity: "warning"',
77
+ 'tags: ["a", "b"]',
78
+ 'updated: 123456',
79
+ '---',
80
+ '',
81
+ '# My Title',
82
+ '',
83
+ 'Some description text.',
84
+ ].join('\n');
85
+
86
+ const parsed = fromObsidianMarkdown(md);
87
+ expect(parsed.id).toBe('e1');
88
+ expect(parsed.type).toBe('pattern');
89
+ expect(parsed.domain).toBe('arch');
90
+ expect(parsed.severity).toBe('warning');
91
+ expect(parsed.tags).toEqual(['a', 'b']);
92
+ expect(parsed.title).toBe('My Title');
93
+ expect(parsed.description).toBe('Some description text.');
94
+ expect(parsed.updated).toBe(123456);
95
+ });
96
+
97
+ it('handles missing frontmatter gracefully', () => {
98
+ const parsed = fromObsidianMarkdown('# Just a Title\n\nSome text.');
99
+ expect(parsed.title).toBe('Just a Title');
100
+ expect(parsed.description).toBe('Some text.');
101
+ });
102
+
103
+ it('infers anti-pattern type from content', () => {
104
+ const md = '# Test\n\nAvoid using inline styles — anti-pattern detected.';
105
+ const parsed = fromObsidianMarkdown(md);
106
+ expect(parsed.type).toBe('anti-pattern');
107
+ });
108
+
109
+ it('infers pattern type from content', () => {
110
+ const md = '# Test\n\nAlways use semantic tokens in your components.';
111
+ const parsed = fromObsidianMarkdown(md);
112
+ expect(parsed.type).toBe('pattern');
113
+ });
114
+
115
+ it('infers rule type from content starting with Rule:', () => {
116
+ // "Rule:" must not also match anti-pattern keywords like "never"
117
+ const md = '# Test\n\nRule: keep components under 400 lines.';
118
+ const parsed = fromObsidianMarkdown(md);
119
+ expect(parsed.type).toBe('rule');
120
+ });
121
+
122
+ it('defaults to concept for ambiguous content', () => {
123
+ const md = '# Test\n\nThis is a general explanation of something.';
124
+ const parsed = fromObsidianMarkdown(md);
125
+ expect(parsed.type).toBe('concept');
126
+ });
127
+
128
+ it('preserves explicit type from frontmatter over inference', () => {
129
+ const md = '---\ntype: "rule"\n---\n# Test\n\nAvoid using inline styles.';
130
+ const parsed = fromObsidianMarkdown(md);
131
+ expect(parsed.type).toBe('rule');
132
+ });
133
+ });
134
+
135
+ describe('titleToSlug', () => {
136
+ it('converts to lowercase kebab-case', () => {
137
+ expect(titleToSlug('Hello World')).toBe('hello-world');
138
+ });
139
+
140
+ it('removes special characters', () => {
141
+ expect(titleToSlug('Use Semantic Tokens!')).toBe('use-semantic-tokens');
142
+ });
143
+
144
+ it('collapses multiple dashes', () => {
145
+ expect(titleToSlug('A -- B')).toBe('a-b');
146
+ });
147
+
148
+ it('trims leading and trailing dashes', () => {
149
+ expect(titleToSlug(' -Hello- ')).toBe('hello');
150
+ });
151
+
152
+ it('truncates to 80 characters', () => {
153
+ const long = 'A'.repeat(100);
154
+ expect(titleToSlug(long).length).toBeLessThanOrEqual(80);
155
+ });
156
+
157
+ it('returns empty string for empty input', () => {
158
+ expect(titleToSlug('')).toBe('');
159
+ });
160
+
161
+ it('handles whitespace-only input', () => {
162
+ expect(titleToSlug(' ')).toBe('');
163
+ });
164
+ });
165
+
166
+ // ─── Sync Engine ─────────────────────────────────────────────────────
167
+
168
+ describe('ObsidianSync', () => {
169
+ let tmpDir: string;
170
+ let obsidianDir: string;
171
+
172
+ function makeMockVault(entryList: IntelligenceEntry[] = []) {
173
+ return {
174
+ list: vi.fn().mockReturnValue(entryList),
175
+ get: vi.fn().mockImplementation((id: string) => entryList.find((e) => e.id === id) ?? null),
176
+ seed: vi.fn().mockReturnValue(1),
177
+ update: vi.fn().mockReturnValue(null),
178
+ } as unknown;
179
+ }
180
+
181
+ beforeEach(() => {
182
+ tmpDir = join(tmpdir(), `obsidian-sync-${randomUUID().slice(0, 8)}`);
183
+ obsidianDir = join(tmpDir, 'obsidian');
184
+ mkdirSync(obsidianDir, { recursive: true });
185
+ });
186
+
187
+ afterEach(() => {
188
+ rmSync(tmpDir, { recursive: true, force: true });
189
+ });
190
+
191
+ // ── export ──────────────────────────────────────────────────────────
192
+
193
+ it('exports entries as markdown files', () => {
194
+ const entries = [makeEntry({ id: 'e1', title: 'First', domain: 'arch' })];
195
+ const sync = new ObsidianSync({ vault: makeMockVault(entries) });
196
+ const result = sync.export(obsidianDir);
197
+ expect(result.exported).toBe(1);
198
+ expect(result.files).toHaveLength(1);
199
+ expect(existsSync(join(obsidianDir, 'arch', 'first.md'))).toBe(true);
200
+ });
201
+
202
+ it('filters by types when specified', () => {
203
+ const entries = [
204
+ makeEntry({ id: 'e1', type: 'pattern' }),
205
+ makeEntry({ id: 'e2', type: 'anti-pattern' }),
206
+ ];
207
+ const sync = new ObsidianSync({ vault: makeMockVault(entries) });
208
+ const result = sync.export(obsidianDir, { types: ['pattern'] });
209
+ expect(result.exported).toBe(1);
210
+ });
211
+
212
+ it('filters by domains when specified', () => {
213
+ const entries = [
214
+ makeEntry({ id: 'e1', domain: 'arch' }),
215
+ makeEntry({ id: 'e2', domain: 'security' }),
216
+ ];
217
+ const sync = new ObsidianSync({ vault: makeMockVault(entries) });
218
+ const result = sync.export(obsidianDir, { domains: ['security'] });
219
+ expect(result.exported).toBe(1);
220
+ });
221
+
222
+ it('skips entries with empty slugs', () => {
223
+ const entries = [makeEntry({ id: 'e1', title: '' })];
224
+ const sync = new ObsidianSync({ vault: makeMockVault(entries) });
225
+ const result = sync.export(obsidianDir);
226
+ expect(result.exported).toBe(0);
227
+ expect(result.skipped).toBe(1);
228
+ });
229
+
230
+ it('does not write files in dry run mode', () => {
231
+ const entries = [makeEntry({ id: 'e1', title: 'DryRun', domain: 'test' })];
232
+ const sync = new ObsidianSync({ vault: makeMockVault(entries) });
233
+ const result = sync.export(obsidianDir, { dryRun: true });
234
+ expect(result.exported).toBe(1);
235
+ expect(existsSync(join(obsidianDir, 'test', 'dryrun.md'))).toBe(false);
236
+ });
237
+
238
+ // ── import ──────────────────────────────────────────────────────────
239
+
240
+ it('imports markdown files into vault', () => {
241
+ const md = [
242
+ '---',
243
+ 'id: "new-entry"',
244
+ 'type: "pattern"',
245
+ 'domain: "test"',
246
+ '---',
247
+ '',
248
+ '# Imported Pattern',
249
+ '',
250
+ 'Description of imported pattern.',
251
+ ].join('\n');
252
+ mkdirSync(join(obsidianDir, 'test'), { recursive: true });
253
+ writeFileSync(join(obsidianDir, 'test', 'imported.md'), md, 'utf-8');
254
+
255
+ const mockVault = makeMockVault([]);
256
+ const sync = new ObsidianSync({ vault: mockVault });
257
+ const result = sync.import(obsidianDir);
258
+ expect(result.imported).toBe(1);
259
+ expect(mockVault.seed).toHaveBeenCalled();
260
+ });
261
+
262
+ it('updates existing entries on import', () => {
263
+ const existing = makeEntry({ id: 'existing', domain: 'test' });
264
+ const md = [
265
+ '---',
266
+ 'id: "existing"',
267
+ 'type: "pattern"',
268
+ '---',
269
+ '',
270
+ '# Updated Title',
271
+ '',
272
+ 'Updated description.',
273
+ ].join('\n');
274
+ mkdirSync(join(obsidianDir, 'test'), { recursive: true });
275
+ writeFileSync(join(obsidianDir, 'test', 'updated.md'), md, 'utf-8');
276
+
277
+ const mockVault = makeMockVault([existing]);
278
+ const sync = new ObsidianSync({ vault: mockVault });
279
+ const result = sync.import(obsidianDir);
280
+ expect(result.updated).toBe(1);
281
+ expect(mockVault.update).toHaveBeenCalledWith('existing', expect.objectContaining({
282
+ title: 'Updated Title',
283
+ }));
284
+ });
285
+
286
+ it('skips files without title or description', () => {
287
+ writeFileSync(join(obsidianDir, 'empty.md'), '---\nid: "x"\n---\n', 'utf-8');
288
+ const sync = new ObsidianSync({ vault: makeMockVault([]) });
289
+ const result = sync.import(obsidianDir);
290
+ expect(result.skipped).toBe(1);
291
+ expect(result.imported).toBe(0);
292
+ });
293
+
294
+ it('uses directory name as domain fallback', () => {
295
+ const md = '# Title\n\nDescription text.';
296
+ mkdirSync(join(obsidianDir, 'custom-domain'), { recursive: true });
297
+ writeFileSync(join(obsidianDir, 'custom-domain', 'entry.md'), md, 'utf-8');
298
+
299
+ const mockVault = makeMockVault([]);
300
+ const sync = new ObsidianSync({ vault: mockVault });
301
+ sync.import(obsidianDir);
302
+ const seedCall = mockVault.seed.mock.calls[0][0][0];
303
+ expect(seedCall.domain).toBe('custom-domain');
304
+ });
305
+
306
+ // ── sync ────────────────────────────────────────────────────────────
307
+
308
+ it('push mode only exports', () => {
309
+ const entries = [makeEntry({ id: 'e1', title: 'Push', domain: 'test' })];
310
+ const sync = new ObsidianSync({ vault: makeMockVault(entries) });
311
+ const result = sync.sync(obsidianDir, { mode: 'push' });
312
+ expect(result.pushed).toBe(1);
313
+ expect(result.pulled).toBe(0);
314
+ expect(result.mode).toBe('push');
315
+ });
316
+
317
+ it('pull mode only imports', () => {
318
+ const md = '# Pull Entry\n\nSome text.';
319
+ mkdirSync(join(obsidianDir, 'test'), { recursive: true });
320
+ writeFileSync(join(obsidianDir, 'test', 'pull.md'), md, 'utf-8');
321
+
322
+ const sync = new ObsidianSync({ vault: makeMockVault([]) });
323
+ const result = sync.sync(obsidianDir, { mode: 'pull' });
324
+ expect(result.pulled).toBe(1);
325
+ expect(result.pushed).toBe(0);
326
+ expect(result.mode).toBe('pull');
327
+ });
328
+
329
+ it('bidirectional mode exports and imports', () => {
330
+ const entries = [makeEntry({ id: 'e1', title: 'Bidir', domain: 'test' })];
331
+ const sync = new ObsidianSync({ vault: makeMockVault(entries) });
332
+ const result = sync.sync(obsidianDir, { mode: 'bidirectional' });
333
+ expect(result.mode).toBe('bidirectional');
334
+ expect(result.pushed).toBe(1);
335
+ });
336
+
337
+ it('defaults to bidirectional mode', () => {
338
+ const sync = new ObsidianSync({ vault: makeMockVault([]) });
339
+ const result = sync.sync(obsidianDir);
340
+ expect(result.mode).toBe('bidirectional');
341
+ });
342
+ });
@@ -0,0 +1,152 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ validatePlaybook,
4
+ parsePlaybookFromEntry,
5
+ type Playbook,
6
+ type PlaybookStep,
7
+ } from './playbook.js';
8
+ import type { IntelligenceEntry } from '../intelligence/types.js';
9
+
10
+ function makeStep(overrides: Partial<PlaybookStep> = {}, index = 1): PlaybookStep {
11
+ return {
12
+ order: index,
13
+ title: `Step ${index}`,
14
+ description: `Description for step ${index}`,
15
+ ...overrides,
16
+ };
17
+ }
18
+
19
+ function makePlaybook(overrides: Partial<Playbook> = {}): Playbook {
20
+ return {
21
+ id: 'pb-1',
22
+ title: 'Test Playbook',
23
+ domain: 'testing',
24
+ description: 'A test playbook',
25
+ steps: [makeStep({}, 1), makeStep({}, 2)],
26
+ tags: ['test'],
27
+ createdAt: 0,
28
+ updatedAt: 0,
29
+ ...overrides,
30
+ };
31
+ }
32
+
33
+ function makeEntry(overrides: Partial<IntelligenceEntry> = {}): IntelligenceEntry {
34
+ return {
35
+ id: 'entry-1',
36
+ type: 'playbook',
37
+ domain: 'testing',
38
+ title: 'Test Playbook',
39
+ severity: 'suggestion',
40
+ description: 'A test playbook entry',
41
+ tags: ['test'],
42
+ context: JSON.stringify({ steps: [makeStep({}, 1)] }),
43
+ ...overrides,
44
+ };
45
+ }
46
+
47
+ describe('validatePlaybook', () => {
48
+ it('returns valid for a well-formed playbook', () => {
49
+ const result = validatePlaybook(makePlaybook());
50
+ expect(result.valid).toBe(true);
51
+ expect(result.errors).toEqual([]);
52
+ });
53
+
54
+ it('rejects empty title', () => {
55
+ const result = validatePlaybook(makePlaybook({ title: '' }));
56
+ expect(result.valid).toBe(false);
57
+ expect(result.errors).toContain('Playbook title must not be empty');
58
+ });
59
+
60
+ it('rejects whitespace-only title', () => {
61
+ const result = validatePlaybook(makePlaybook({ title: ' ' }));
62
+ expect(result.valid).toBe(false);
63
+ expect(result.errors).toContain('Playbook title must not be empty');
64
+ });
65
+
66
+ it('rejects empty steps array', () => {
67
+ const result = validatePlaybook(makePlaybook({ steps: [] }));
68
+ expect(result.valid).toBe(false);
69
+ expect(result.errors).toContain('Playbook must have at least one step');
70
+ });
71
+
72
+ it('rejects out-of-order steps', () => {
73
+ const steps = [makeStep({ order: 2 }, 1), makeStep({ order: 1 }, 2)];
74
+ // Fix: steps[0] has order=2 but expected=1
75
+ const result = validatePlaybook(makePlaybook({ steps }));
76
+ expect(result.valid).toBe(false);
77
+ expect(result.errors[0]).toContain('Step 1 has order 2, expected 1');
78
+ });
79
+
80
+ it('rejects step with empty title', () => {
81
+ const steps = [makeStep({ title: '' }, 1)];
82
+ const result = validatePlaybook(makePlaybook({ steps }));
83
+ expect(result.valid).toBe(false);
84
+ expect(result.errors).toContain('Step 1 title must not be empty');
85
+ });
86
+
87
+ it('rejects step with empty description', () => {
88
+ const steps = [makeStep({ description: '' }, 1)];
89
+ const result = validatePlaybook(makePlaybook({ steps }));
90
+ expect(result.valid).toBe(false);
91
+ expect(result.errors).toContain('Step 1 description must not be empty');
92
+ });
93
+
94
+ it('collects multiple errors', () => {
95
+ const steps = [makeStep({ title: '', description: '' }, 1)];
96
+ const result = validatePlaybook(makePlaybook({ title: '', steps }));
97
+ expect(result.valid).toBe(false);
98
+ expect(result.errors.length).toBeGreaterThanOrEqual(3);
99
+ });
100
+ });
101
+
102
+ describe('parsePlaybookFromEntry', () => {
103
+ it('parses a valid playbook entry', () => {
104
+ const playbook = parsePlaybookFromEntry(makeEntry());
105
+ expect(playbook).not.toBeNull();
106
+ expect(playbook!.id).toBe('entry-1');
107
+ expect(playbook!.title).toBe('Test Playbook');
108
+ expect(playbook!.steps).toHaveLength(1);
109
+ expect(playbook!.steps[0].order).toBe(1);
110
+ });
111
+
112
+ it('returns null for non-playbook type', () => {
113
+ const result = parsePlaybookFromEntry(makeEntry({ type: 'pattern' }));
114
+ expect(result).toBeNull();
115
+ });
116
+
117
+ it('returns null for missing context', () => {
118
+ const result = parsePlaybookFromEntry(makeEntry({ context: undefined }));
119
+ expect(result).toBeNull();
120
+ });
121
+
122
+ it('returns null for invalid JSON context', () => {
123
+ const result = parsePlaybookFromEntry(makeEntry({ context: 'not-json' }));
124
+ expect(result).toBeNull();
125
+ });
126
+
127
+ it('returns null when context has no steps array', () => {
128
+ const result = parsePlaybookFromEntry(makeEntry({ context: JSON.stringify({ other: true }) }));
129
+ expect(result).toBeNull();
130
+ });
131
+
132
+ it('returns null when steps is not an array', () => {
133
+ const result = parsePlaybookFromEntry(
134
+ makeEntry({ context: JSON.stringify({ steps: 'not-array' }) }),
135
+ );
136
+ expect(result).toBeNull();
137
+ });
138
+
139
+ it('sets createdAt and updatedAt to 0', () => {
140
+ const playbook = parsePlaybookFromEntry(makeEntry());
141
+ expect(playbook!.createdAt).toBe(0);
142
+ expect(playbook!.updatedAt).toBe(0);
143
+ });
144
+
145
+ it('preserves domain and tags from the entry', () => {
146
+ const playbook = parsePlaybookFromEntry(
147
+ makeEntry({ domain: 'security', tags: ['sec', 'audit'] }),
148
+ );
149
+ expect(playbook!.domain).toBe('security');
150
+ expect(playbook!.tags).toEqual(['sec', 'audit']);
151
+ });
152
+ });
@@ -0,0 +1,187 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { detectScope, type ScopeInput } from './scope-detector.js';
3
+
4
+ function makeInput(overrides: Partial<ScopeInput> = {}): ScopeInput {
5
+ return {
6
+ title: 'Test Entry',
7
+ description: 'A generic description.',
8
+ ...overrides,
9
+ };
10
+ }
11
+
12
+ describe('detectScope', () => {
13
+ // ── Default behavior ──────────────────────────────────────────────
14
+
15
+ it('defaults to agent tier when no signals detected', () => {
16
+ const result = detectScope(makeInput());
17
+ expect(result.tier).toBe('agent');
18
+ expect(result.confidence).toBe('LOW');
19
+ expect(result.reason).toContain('defaulting to agent');
20
+ });
21
+
22
+ // ── Team detection via content ─────────────────────────────────────
23
+
24
+ it('detects team tier for accessibility content', () => {
25
+ const result = detectScope(
26
+ makeInput({ description: 'Always use ARIA labels for screen reader support' }),
27
+ );
28
+ expect(result.tier).toBe('team');
29
+ expect(result.signals.some((s) => s.tier === 'team' && s.source === 'content')).toBe(true);
30
+ });
31
+
32
+ it('detects team tier for WCAG/contrast content', () => {
33
+ const result = detectScope(
34
+ makeInput({ description: 'Ensure contrast ratio meets WCAG AA standard' }),
35
+ );
36
+ expect(result.tier).toBe('team');
37
+ });
38
+
39
+ it('detects team tier for design system content', () => {
40
+ const result = detectScope(
41
+ makeInput({ description: 'Use semantic token from the design system' }),
42
+ );
43
+ expect(result.tier).toBe('team');
44
+ });
45
+
46
+ it('detects team tier for security content', () => {
47
+ const result = detectScope(
48
+ makeInput({ description: 'Sanitize all user input to prevent XSS attacks' }),
49
+ );
50
+ expect(result.tier).toBe('team');
51
+ });
52
+
53
+ it('detects team tier for touch target / UX content', () => {
54
+ const result = detectScope(
55
+ makeInput({ description: 'Touch target must be 44px minimum per Fitts law' }),
56
+ );
57
+ expect(result.tier).toBe('team');
58
+ });
59
+
60
+ // ── Project detection via content ──────────────────────────────────
61
+
62
+ it('detects project tier for scoped package references', () => {
63
+ const result = detectScope(
64
+ makeInput({ description: 'Import from @soleri/core for vault operations' }),
65
+ );
66
+ expect(result.tier).toBe('project');
67
+ });
68
+
69
+ it('detects project tier for monorepo path references', () => {
70
+ const result = detectScope(
71
+ makeInput({ description: 'The module lives in packages/core/src/vault.ts' }),
72
+ );
73
+ expect(result.tier).toBe('project');
74
+ });
75
+
76
+ it('detects project tier for explicit project references', () => {
77
+ const result = detectScope(
78
+ makeInput({ description: 'This project uses a custom build pipeline' }),
79
+ );
80
+ expect(result.tier).toBe('project');
81
+ });
82
+
83
+ // ── Agent detection via content ────────────────────────────────────
84
+
85
+ it('detects agent tier for personal preferences', () => {
86
+ const result = detectScope(
87
+ makeInput({ description: 'I prefer using vim keybindings in my editor' }),
88
+ );
89
+ expect(result.tier).toBe('agent');
90
+ });
91
+
92
+ it('detects agent tier for home directory paths', () => {
93
+ const result = detectScope(
94
+ makeInput({ description: 'Config lives in ~/dotfiles/zshrc' }),
95
+ );
96
+ expect(result.tier).toBe('agent');
97
+ });
98
+
99
+ // ── Category signals ───────────────────────────────────────────────
100
+
101
+ it('boosts team tier for team categories', () => {
102
+ const result = detectScope(makeInput({ category: 'accessibility' }));
103
+ expect(result.tier).toBe('team');
104
+ expect(result.signals.some((s) => s.source === 'category')).toBe(true);
105
+ });
106
+
107
+ it('boosts project tier for project categories', () => {
108
+ const result = detectScope(makeInput({ category: 'infrastructure' }));
109
+ expect(result.tier).toBe('project');
110
+ });
111
+
112
+ it('produces no category signal for unknown categories', () => {
113
+ const result = detectScope(makeInput({ category: 'random' }));
114
+ expect(result.signals.filter((s) => s.source === 'category')).toEqual([]);
115
+ });
116
+
117
+ // ── Tag signals ────────────────────────────────────────────────────
118
+
119
+ it('boosts team tier for team tags', () => {
120
+ const result = detectScope(makeInput({ tags: ['universal', 'best-practice'] }));
121
+ expect(result.tier).toBe('team');
122
+ });
123
+
124
+ it('boosts project tier for project tags', () => {
125
+ const result = detectScope(makeInput({ tags: ['project-specific', 'internal'] }));
126
+ expect(result.tier).toBe('project');
127
+ });
128
+
129
+ it('boosts agent tier for agent tags', () => {
130
+ const result = detectScope(makeInput({ tags: ['personal', 'preference'] }));
131
+ expect(result.tier).toBe('agent');
132
+ });
133
+
134
+ it('handles empty tags array', () => {
135
+ const result = detectScope(makeInput({ tags: [] }));
136
+ expect(result.tier).toBe('agent'); // default
137
+ });
138
+
139
+ // ── Confidence levels ──────────────────────────────────────────────
140
+
141
+ it('returns HIGH confidence when strong signals with no competition', () => {
142
+ const result = detectScope(
143
+ makeInput({
144
+ description: 'Accessibility a11y ARIA screen reader WCAG contrast ratio',
145
+ category: 'accessibility',
146
+ tags: ['a11y', 'wcag'],
147
+ }),
148
+ );
149
+ expect(result.tier).toBe('team');
150
+ expect(result.confidence).toBe('HIGH');
151
+ });
152
+
153
+ it('returns MEDIUM confidence for moderate signal competition', () => {
154
+ // Mix of team and project signals
155
+ const result = detectScope(
156
+ makeInput({
157
+ description: 'This project uses type safety patterns from @soleri/core',
158
+ tags: ['pattern'],
159
+ }),
160
+ );
161
+ expect(['HIGH', 'MEDIUM']).toContain(result.confidence);
162
+ });
163
+
164
+ // ── Signal structure ───────────────────────────────────────────────
165
+
166
+ it('includes signals with expected shape', () => {
167
+ const result = detectScope(
168
+ makeInput({ description: 'Use focus ring for keyboard navigation' }),
169
+ );
170
+ for (const signal of result.signals) {
171
+ expect(signal).toHaveProperty('tier');
172
+ expect(signal).toHaveProperty('source');
173
+ expect(signal).toHaveProperty('indicator');
174
+ expect(signal).toHaveProperty('weight');
175
+ expect(signal.weight).toBeGreaterThan(0);
176
+ expect(signal.weight).toBeLessThanOrEqual(1);
177
+ }
178
+ });
179
+
180
+ it('reason summarizes top signals', () => {
181
+ const result = detectScope(
182
+ makeInput({ description: 'Accessibility best practice for a11y compliance' }),
183
+ );
184
+ expect(result.reason.length).toBeGreaterThan(0);
185
+ expect(result.reason).not.toContain('defaulting');
186
+ });
187
+ });