@soleri/core 2.1.0 → 2.5.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 (377) hide show
  1. package/dist/brain/brain.d.ts +10 -1
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +116 -13
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/brain/intelligence.d.ts +36 -1
  6. package/dist/brain/intelligence.d.ts.map +1 -1
  7. package/dist/brain/intelligence.js +119 -14
  8. package/dist/brain/intelligence.js.map +1 -1
  9. package/dist/brain/types.d.ts +34 -2
  10. package/dist/brain/types.d.ts.map +1 -1
  11. package/dist/cognee/client.d.ts +3 -0
  12. package/dist/cognee/client.d.ts.map +1 -1
  13. package/dist/cognee/client.js +17 -0
  14. package/dist/cognee/client.js.map +1 -1
  15. package/dist/cognee/sync-manager.d.ts +94 -0
  16. package/dist/cognee/sync-manager.d.ts.map +1 -0
  17. package/dist/cognee/sync-manager.js +293 -0
  18. package/dist/cognee/sync-manager.js.map +1 -0
  19. package/dist/control/identity-manager.d.ts +22 -0
  20. package/dist/control/identity-manager.d.ts.map +1 -0
  21. package/dist/control/identity-manager.js +233 -0
  22. package/dist/control/identity-manager.js.map +1 -0
  23. package/dist/control/intent-router.d.ts +32 -0
  24. package/dist/control/intent-router.d.ts.map +1 -0
  25. package/dist/control/intent-router.js +242 -0
  26. package/dist/control/intent-router.js.map +1 -0
  27. package/dist/control/types.d.ts +68 -0
  28. package/dist/control/types.d.ts.map +1 -0
  29. package/dist/control/types.js +9 -0
  30. package/dist/control/types.js.map +1 -0
  31. package/dist/curator/curator.d.ts +37 -1
  32. package/dist/curator/curator.d.ts.map +1 -1
  33. package/dist/curator/curator.js +199 -1
  34. package/dist/curator/curator.js.map +1 -1
  35. package/dist/errors/classify.d.ts +13 -0
  36. package/dist/errors/classify.d.ts.map +1 -0
  37. package/dist/errors/classify.js +97 -0
  38. package/dist/errors/classify.js.map +1 -0
  39. package/dist/errors/index.d.ts +6 -0
  40. package/dist/errors/index.d.ts.map +1 -0
  41. package/dist/errors/index.js +4 -0
  42. package/dist/errors/index.js.map +1 -0
  43. package/dist/errors/retry.d.ts +40 -0
  44. package/dist/errors/retry.d.ts.map +1 -0
  45. package/dist/errors/retry.js +97 -0
  46. package/dist/errors/retry.js.map +1 -0
  47. package/dist/errors/types.d.ts +48 -0
  48. package/dist/errors/types.d.ts.map +1 -0
  49. package/dist/errors/types.js +59 -0
  50. package/dist/errors/types.js.map +1 -0
  51. package/dist/facades/types.d.ts +1 -1
  52. package/dist/governance/governance.d.ts +42 -0
  53. package/dist/governance/governance.d.ts.map +1 -0
  54. package/dist/governance/governance.js +488 -0
  55. package/dist/governance/governance.js.map +1 -0
  56. package/dist/governance/index.d.ts +3 -0
  57. package/dist/governance/index.d.ts.map +1 -0
  58. package/dist/governance/index.js +2 -0
  59. package/dist/governance/index.js.map +1 -0
  60. package/dist/governance/types.d.ts +102 -0
  61. package/dist/governance/types.d.ts.map +1 -0
  62. package/dist/governance/types.js +3 -0
  63. package/dist/governance/types.js.map +1 -0
  64. package/dist/index.d.ts +52 -3
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +47 -1
  67. package/dist/index.js.map +1 -1
  68. package/dist/intake/content-classifier.d.ts +14 -0
  69. package/dist/intake/content-classifier.d.ts.map +1 -0
  70. package/dist/intake/content-classifier.js +125 -0
  71. package/dist/intake/content-classifier.js.map +1 -0
  72. package/dist/intake/dedup-gate.d.ts +17 -0
  73. package/dist/intake/dedup-gate.d.ts.map +1 -0
  74. package/dist/intake/dedup-gate.js +66 -0
  75. package/dist/intake/dedup-gate.js.map +1 -0
  76. package/dist/intake/intake-pipeline.d.ts +63 -0
  77. package/dist/intake/intake-pipeline.d.ts.map +1 -0
  78. package/dist/intake/intake-pipeline.js +373 -0
  79. package/dist/intake/intake-pipeline.js.map +1 -0
  80. package/dist/intake/types.d.ts +65 -0
  81. package/dist/intake/types.d.ts.map +1 -0
  82. package/dist/intake/types.js +3 -0
  83. package/dist/intake/types.js.map +1 -0
  84. package/dist/intelligence/loader.js +1 -1
  85. package/dist/intelligence/loader.js.map +1 -1
  86. package/dist/intelligence/types.d.ts +3 -1
  87. package/dist/intelligence/types.d.ts.map +1 -1
  88. package/dist/logging/logger.d.ts +37 -0
  89. package/dist/logging/logger.d.ts.map +1 -0
  90. package/dist/logging/logger.js +145 -0
  91. package/dist/logging/logger.js.map +1 -0
  92. package/dist/logging/types.d.ts +19 -0
  93. package/dist/logging/types.d.ts.map +1 -0
  94. package/dist/logging/types.js +2 -0
  95. package/dist/logging/types.js.map +1 -0
  96. package/dist/loop/loop-manager.d.ts +100 -0
  97. package/dist/loop/loop-manager.d.ts.map +1 -0
  98. package/dist/loop/loop-manager.js +379 -0
  99. package/dist/loop/loop-manager.js.map +1 -0
  100. package/dist/loop/types.d.ts +103 -0
  101. package/dist/loop/types.d.ts.map +1 -0
  102. package/dist/loop/types.js +11 -0
  103. package/dist/loop/types.js.map +1 -0
  104. package/dist/persistence/index.d.ts +3 -0
  105. package/dist/persistence/index.d.ts.map +1 -0
  106. package/dist/persistence/index.js +2 -0
  107. package/dist/persistence/index.js.map +1 -0
  108. package/dist/persistence/sqlite-provider.d.ts +25 -0
  109. package/dist/persistence/sqlite-provider.d.ts.map +1 -0
  110. package/dist/persistence/sqlite-provider.js +59 -0
  111. package/dist/persistence/sqlite-provider.js.map +1 -0
  112. package/dist/persistence/types.d.ts +36 -0
  113. package/dist/persistence/types.d.ts.map +1 -0
  114. package/dist/persistence/types.js +8 -0
  115. package/dist/persistence/types.js.map +1 -0
  116. package/dist/planning/gap-analysis.d.ts +72 -0
  117. package/dist/planning/gap-analysis.d.ts.map +1 -0
  118. package/dist/planning/gap-analysis.js +442 -0
  119. package/dist/planning/gap-analysis.js.map +1 -0
  120. package/dist/planning/gap-types.d.ts +29 -0
  121. package/dist/planning/gap-types.d.ts.map +1 -0
  122. package/dist/planning/gap-types.js +28 -0
  123. package/dist/planning/gap-types.js.map +1 -0
  124. package/dist/planning/planner.d.ts +421 -4
  125. package/dist/planning/planner.d.ts.map +1 -1
  126. package/dist/planning/planner.js +949 -21
  127. package/dist/planning/planner.js.map +1 -1
  128. package/dist/playbooks/generic/brainstorming.d.ts +9 -0
  129. package/dist/playbooks/generic/brainstorming.d.ts.map +1 -0
  130. package/dist/playbooks/generic/brainstorming.js +105 -0
  131. package/dist/playbooks/generic/brainstorming.js.map +1 -0
  132. package/dist/playbooks/generic/code-review.d.ts +11 -0
  133. package/dist/playbooks/generic/code-review.d.ts.map +1 -0
  134. package/dist/playbooks/generic/code-review.js +176 -0
  135. package/dist/playbooks/generic/code-review.js.map +1 -0
  136. package/dist/playbooks/generic/subagent-execution.d.ts +9 -0
  137. package/dist/playbooks/generic/subagent-execution.d.ts.map +1 -0
  138. package/dist/playbooks/generic/subagent-execution.js +68 -0
  139. package/dist/playbooks/generic/subagent-execution.js.map +1 -0
  140. package/dist/playbooks/generic/systematic-debugging.d.ts +9 -0
  141. package/dist/playbooks/generic/systematic-debugging.d.ts.map +1 -0
  142. package/dist/playbooks/generic/systematic-debugging.js +87 -0
  143. package/dist/playbooks/generic/systematic-debugging.js.map +1 -0
  144. package/dist/playbooks/generic/tdd.d.ts +9 -0
  145. package/dist/playbooks/generic/tdd.d.ts.map +1 -0
  146. package/dist/playbooks/generic/tdd.js +70 -0
  147. package/dist/playbooks/generic/tdd.js.map +1 -0
  148. package/dist/playbooks/generic/verification.d.ts +9 -0
  149. package/dist/playbooks/generic/verification.d.ts.map +1 -0
  150. package/dist/playbooks/generic/verification.js +74 -0
  151. package/dist/playbooks/generic/verification.js.map +1 -0
  152. package/dist/playbooks/index.d.ts +4 -0
  153. package/dist/playbooks/index.d.ts.map +1 -0
  154. package/dist/playbooks/index.js +5 -0
  155. package/dist/playbooks/index.js.map +1 -0
  156. package/dist/playbooks/playbook-registry.d.ts +42 -0
  157. package/dist/playbooks/playbook-registry.d.ts.map +1 -0
  158. package/dist/playbooks/playbook-registry.js +227 -0
  159. package/dist/playbooks/playbook-registry.js.map +1 -0
  160. package/dist/playbooks/playbook-seeder.d.ts +47 -0
  161. package/dist/playbooks/playbook-seeder.d.ts.map +1 -0
  162. package/dist/playbooks/playbook-seeder.js +104 -0
  163. package/dist/playbooks/playbook-seeder.js.map +1 -0
  164. package/dist/playbooks/playbook-types.d.ts +132 -0
  165. package/dist/playbooks/playbook-types.d.ts.map +1 -0
  166. package/dist/playbooks/playbook-types.js +12 -0
  167. package/dist/playbooks/playbook-types.js.map +1 -0
  168. package/dist/project/project-registry.d.ts +79 -0
  169. package/dist/project/project-registry.d.ts.map +1 -0
  170. package/dist/project/project-registry.js +274 -0
  171. package/dist/project/project-registry.js.map +1 -0
  172. package/dist/project/types.d.ts +28 -0
  173. package/dist/project/types.d.ts.map +1 -0
  174. package/dist/project/types.js +5 -0
  175. package/dist/project/types.js.map +1 -0
  176. package/dist/prompts/index.d.ts +4 -0
  177. package/dist/prompts/index.d.ts.map +1 -0
  178. package/dist/prompts/index.js +3 -0
  179. package/dist/prompts/index.js.map +1 -0
  180. package/dist/prompts/parser.d.ts +17 -0
  181. package/dist/prompts/parser.d.ts.map +1 -0
  182. package/dist/prompts/parser.js +47 -0
  183. package/dist/prompts/parser.js.map +1 -0
  184. package/dist/prompts/template-manager.d.ts +25 -0
  185. package/dist/prompts/template-manager.d.ts.map +1 -0
  186. package/dist/prompts/template-manager.js +71 -0
  187. package/dist/prompts/template-manager.js.map +1 -0
  188. package/dist/prompts/types.d.ts +26 -0
  189. package/dist/prompts/types.d.ts.map +1 -0
  190. package/dist/prompts/types.js +5 -0
  191. package/dist/prompts/types.js.map +1 -0
  192. package/dist/runtime/admin-extra-ops.d.ts +15 -0
  193. package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
  194. package/dist/runtime/admin-extra-ops.js +595 -0
  195. package/dist/runtime/admin-extra-ops.js.map +1 -0
  196. package/dist/runtime/admin-ops.d.ts +15 -0
  197. package/dist/runtime/admin-ops.d.ts.map +1 -0
  198. package/dist/runtime/admin-ops.js +329 -0
  199. package/dist/runtime/admin-ops.js.map +1 -0
  200. package/dist/runtime/capture-ops.d.ts +15 -0
  201. package/dist/runtime/capture-ops.d.ts.map +1 -0
  202. package/dist/runtime/capture-ops.js +363 -0
  203. package/dist/runtime/capture-ops.js.map +1 -0
  204. package/dist/runtime/cognee-sync-ops.d.ts +12 -0
  205. package/dist/runtime/cognee-sync-ops.d.ts.map +1 -0
  206. package/dist/runtime/cognee-sync-ops.js +55 -0
  207. package/dist/runtime/cognee-sync-ops.js.map +1 -0
  208. package/dist/runtime/core-ops.d.ts +9 -3
  209. package/dist/runtime/core-ops.d.ts.map +1 -1
  210. package/dist/runtime/core-ops.js +693 -10
  211. package/dist/runtime/core-ops.js.map +1 -1
  212. package/dist/runtime/curator-extra-ops.d.ts +9 -0
  213. package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
  214. package/dist/runtime/curator-extra-ops.js +71 -0
  215. package/dist/runtime/curator-extra-ops.js.map +1 -0
  216. package/dist/runtime/domain-ops.d.ts.map +1 -1
  217. package/dist/runtime/domain-ops.js +61 -15
  218. package/dist/runtime/domain-ops.js.map +1 -1
  219. package/dist/runtime/grading-ops.d.ts +14 -0
  220. package/dist/runtime/grading-ops.d.ts.map +1 -0
  221. package/dist/runtime/grading-ops.js +105 -0
  222. package/dist/runtime/grading-ops.js.map +1 -0
  223. package/dist/runtime/intake-ops.d.ts +14 -0
  224. package/dist/runtime/intake-ops.d.ts.map +1 -0
  225. package/dist/runtime/intake-ops.js +110 -0
  226. package/dist/runtime/intake-ops.js.map +1 -0
  227. package/dist/runtime/loop-ops.d.ts +14 -0
  228. package/dist/runtime/loop-ops.d.ts.map +1 -0
  229. package/dist/runtime/loop-ops.js +251 -0
  230. package/dist/runtime/loop-ops.js.map +1 -0
  231. package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
  232. package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
  233. package/dist/runtime/memory-cross-project-ops.js +165 -0
  234. package/dist/runtime/memory-cross-project-ops.js.map +1 -0
  235. package/dist/runtime/memory-extra-ops.d.ts +13 -0
  236. package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
  237. package/dist/runtime/memory-extra-ops.js +173 -0
  238. package/dist/runtime/memory-extra-ops.js.map +1 -0
  239. package/dist/runtime/orchestrate-ops.d.ts +17 -0
  240. package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
  241. package/dist/runtime/orchestrate-ops.js +246 -0
  242. package/dist/runtime/orchestrate-ops.js.map +1 -0
  243. package/dist/runtime/planning-extra-ops.d.ts +25 -0
  244. package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
  245. package/dist/runtime/planning-extra-ops.js +663 -0
  246. package/dist/runtime/planning-extra-ops.js.map +1 -0
  247. package/dist/runtime/playbook-ops.d.ts +14 -0
  248. package/dist/runtime/playbook-ops.d.ts.map +1 -0
  249. package/dist/runtime/playbook-ops.js +141 -0
  250. package/dist/runtime/playbook-ops.js.map +1 -0
  251. package/dist/runtime/project-ops.d.ts +15 -0
  252. package/dist/runtime/project-ops.d.ts.map +1 -0
  253. package/dist/runtime/project-ops.js +186 -0
  254. package/dist/runtime/project-ops.js.map +1 -0
  255. package/dist/runtime/runtime.d.ts.map +1 -1
  256. package/dist/runtime/runtime.js +65 -3
  257. package/dist/runtime/runtime.js.map +1 -1
  258. package/dist/runtime/types.d.ts +29 -0
  259. package/dist/runtime/types.d.ts.map +1 -1
  260. package/dist/runtime/vault-extra-ops.d.ts +10 -0
  261. package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
  262. package/dist/runtime/vault-extra-ops.js +536 -0
  263. package/dist/runtime/vault-extra-ops.js.map +1 -0
  264. package/dist/telemetry/telemetry.d.ts +48 -0
  265. package/dist/telemetry/telemetry.d.ts.map +1 -0
  266. package/dist/telemetry/telemetry.js +87 -0
  267. package/dist/telemetry/telemetry.js.map +1 -0
  268. package/dist/vault/playbook.d.ts +34 -0
  269. package/dist/vault/playbook.d.ts.map +1 -0
  270. package/dist/vault/playbook.js +60 -0
  271. package/dist/vault/playbook.js.map +1 -0
  272. package/dist/vault/vault.d.ts +97 -4
  273. package/dist/vault/vault.d.ts.map +1 -1
  274. package/dist/vault/vault.js +424 -65
  275. package/dist/vault/vault.js.map +1 -1
  276. package/package.json +7 -3
  277. package/src/__tests__/admin-extra-ops.test.ts +467 -0
  278. package/src/__tests__/admin-ops.test.ts +271 -0
  279. package/src/__tests__/brain-intelligence.test.ts +205 -0
  280. package/src/__tests__/brain.test.ts +134 -3
  281. package/src/__tests__/capture-ops.test.ts +509 -0
  282. package/src/__tests__/cognee-integration.test.ts +80 -0
  283. package/src/__tests__/cognee-sync-manager.test.ts +103 -0
  284. package/src/__tests__/core-ops.test.ts +292 -2
  285. package/src/__tests__/curator-extra-ops.test.ts +381 -0
  286. package/src/__tests__/domain-ops.test.ts +66 -0
  287. package/src/__tests__/errors.test.ts +388 -0
  288. package/src/__tests__/governance.test.ts +522 -0
  289. package/src/__tests__/grading-ops.test.ts +361 -0
  290. package/src/__tests__/identity-manager.test.ts +243 -0
  291. package/src/__tests__/intake-pipeline.test.ts +162 -0
  292. package/src/__tests__/intent-router.test.ts +222 -0
  293. package/src/__tests__/logger.test.ts +200 -0
  294. package/src/__tests__/loop-ops.test.ts +469 -0
  295. package/src/__tests__/memory-cross-project-ops.test.ts +248 -0
  296. package/src/__tests__/memory-extra-ops.test.ts +352 -0
  297. package/src/__tests__/orchestrate-ops.test.ts +289 -0
  298. package/src/__tests__/persistence.test.ts +225 -0
  299. package/src/__tests__/planner.test.ts +416 -7
  300. package/src/__tests__/planning-extra-ops.test.ts +706 -0
  301. package/src/__tests__/playbook-registry.test.ts +326 -0
  302. package/src/__tests__/playbook-seeder.test.ts +163 -0
  303. package/src/__tests__/playbook.test.ts +389 -0
  304. package/src/__tests__/project-ops.test.ts +381 -0
  305. package/src/__tests__/template-manager.test.ts +222 -0
  306. package/src/__tests__/vault-extra-ops.test.ts +482 -0
  307. package/src/brain/brain.ts +185 -16
  308. package/src/brain/intelligence.ts +179 -10
  309. package/src/brain/types.ts +40 -2
  310. package/src/cognee/client.ts +18 -0
  311. package/src/cognee/sync-manager.ts +389 -0
  312. package/src/control/identity-manager.ts +354 -0
  313. package/src/control/intent-router.ts +326 -0
  314. package/src/control/types.ts +102 -0
  315. package/src/curator/curator.ts +295 -1
  316. package/src/errors/classify.ts +102 -0
  317. package/src/errors/index.ts +5 -0
  318. package/src/errors/retry.ts +132 -0
  319. package/src/errors/types.ts +81 -0
  320. package/src/governance/governance.ts +698 -0
  321. package/src/governance/index.ts +18 -0
  322. package/src/governance/types.ts +111 -0
  323. package/src/index.ts +213 -2
  324. package/src/intake/content-classifier.ts +146 -0
  325. package/src/intake/dedup-gate.ts +92 -0
  326. package/src/intake/intake-pipeline.ts +503 -0
  327. package/src/intake/types.ts +69 -0
  328. package/src/intelligence/loader.ts +1 -1
  329. package/src/intelligence/types.ts +3 -1
  330. package/src/logging/logger.ts +154 -0
  331. package/src/logging/types.ts +21 -0
  332. package/src/loop/loop-manager.ts +448 -0
  333. package/src/loop/types.ts +115 -0
  334. package/src/persistence/index.ts +7 -0
  335. package/src/persistence/sqlite-provider.ts +62 -0
  336. package/src/persistence/types.ts +44 -0
  337. package/src/planning/gap-analysis.ts +775 -0
  338. package/src/planning/gap-types.ts +61 -0
  339. package/src/planning/planner.ts +1273 -24
  340. package/src/playbooks/generic/brainstorming.ts +110 -0
  341. package/src/playbooks/generic/code-review.ts +181 -0
  342. package/src/playbooks/generic/subagent-execution.ts +74 -0
  343. package/src/playbooks/generic/systematic-debugging.ts +92 -0
  344. package/src/playbooks/generic/tdd.ts +75 -0
  345. package/src/playbooks/generic/verification.ts +79 -0
  346. package/src/playbooks/index.ts +27 -0
  347. package/src/playbooks/playbook-registry.ts +284 -0
  348. package/src/playbooks/playbook-seeder.ts +119 -0
  349. package/src/playbooks/playbook-types.ts +162 -0
  350. package/src/project/project-registry.ts +370 -0
  351. package/src/project/types.ts +31 -0
  352. package/src/prompts/index.ts +3 -0
  353. package/src/prompts/parser.ts +59 -0
  354. package/src/prompts/template-manager.ts +77 -0
  355. package/src/prompts/types.ts +28 -0
  356. package/src/runtime/admin-extra-ops.ts +652 -0
  357. package/src/runtime/admin-ops.ts +340 -0
  358. package/src/runtime/capture-ops.ts +404 -0
  359. package/src/runtime/cognee-sync-ops.ts +63 -0
  360. package/src/runtime/core-ops.ts +787 -9
  361. package/src/runtime/curator-extra-ops.ts +85 -0
  362. package/src/runtime/domain-ops.ts +67 -15
  363. package/src/runtime/grading-ops.ts +130 -0
  364. package/src/runtime/intake-ops.ts +126 -0
  365. package/src/runtime/loop-ops.ts +277 -0
  366. package/src/runtime/memory-cross-project-ops.ts +191 -0
  367. package/src/runtime/memory-extra-ops.ts +186 -0
  368. package/src/runtime/orchestrate-ops.ts +278 -0
  369. package/src/runtime/planning-extra-ops.ts +718 -0
  370. package/src/runtime/playbook-ops.ts +169 -0
  371. package/src/runtime/project-ops.ts +202 -0
  372. package/src/runtime/runtime.ts +77 -3
  373. package/src/runtime/types.ts +29 -0
  374. package/src/runtime/vault-extra-ops.ts +606 -0
  375. package/src/telemetry/telemetry.ts +118 -0
  376. package/src/vault/playbook.ts +87 -0
  377. package/src/vault/vault.ts +575 -98
@@ -0,0 +1,706 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdirSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { createAgentRuntime } from '../runtime/runtime.js';
6
+ import { createPlanningExtraOps } from '../runtime/planning-extra-ops.js';
7
+ import type { AgentRuntime } from '../runtime/types.js';
8
+ import type { OpDefinition } from '../facades/types.js';
9
+
10
+ describe('createPlanningExtraOps', () => {
11
+ let runtime: AgentRuntime;
12
+ let ops: OpDefinition[];
13
+ let plannerDir: string;
14
+
15
+ beforeEach(() => {
16
+ plannerDir = join(tmpdir(), 'planning-extra-ops-test-' + Date.now());
17
+ mkdirSync(plannerDir, { recursive: true });
18
+ runtime = createAgentRuntime({
19
+ agentId: 'test-planning-extra',
20
+ vaultPath: ':memory:',
21
+ plansPath: join(plannerDir, 'plans.json'),
22
+ });
23
+ ops = createPlanningExtraOps(runtime);
24
+ });
25
+
26
+ afterEach(() => {
27
+ runtime.close();
28
+ rmSync(plannerDir, { recursive: true, force: true });
29
+ });
30
+
31
+ function findOp(name: string): OpDefinition {
32
+ const op = ops.find((o) => o.name === name);
33
+ if (!op) throw new Error(`Op "${name}" not found`);
34
+ return op;
35
+ }
36
+
37
+ it('should return 22 ops', () => {
38
+ expect(ops.length).toBe(22);
39
+ });
40
+
41
+ it('should have all expected op names', () => {
42
+ const names = ops.map((o) => o.name);
43
+ expect(names).toContain('plan_iterate');
44
+ expect(names).toContain('plan_split');
45
+ expect(names).toContain('plan_reconcile');
46
+ expect(names).toContain('plan_complete_lifecycle');
47
+ expect(names).toContain('plan_dispatch');
48
+ expect(names).toContain('plan_review');
49
+ expect(names).toContain('plan_archive');
50
+ expect(names).toContain('plan_list_tasks');
51
+ expect(names).toContain('plan_stats');
52
+ // #148: Evidence
53
+ expect(names).toContain('plan_submit_evidence');
54
+ expect(names).toContain('plan_verify_task');
55
+ expect(names).toContain('plan_verify_plan');
56
+ // #149: Subagent dispatch
57
+ expect(names).toContain('plan_review_spec');
58
+ expect(names).toContain('plan_review_quality');
59
+ expect(names).toContain('plan_review_outcome');
60
+ // #150: Brainstorm
61
+ expect(names).toContain('plan_brainstorm');
62
+ // #151: Auto-reconcile
63
+ expect(names).toContain('plan_auto_reconcile');
64
+ // #152: Validate
65
+ expect(names).toContain('plan_validate');
66
+ // #80: Execution metrics
67
+ expect(names).toContain('plan_execution_metrics');
68
+ expect(names).toContain('plan_record_task_metrics');
69
+ // #83: Deliverables
70
+ expect(names).toContain('plan_submit_deliverable');
71
+ expect(names).toContain('plan_verify_deliverables');
72
+ });
73
+
74
+ it('should assign correct auth levels', () => {
75
+ expect(findOp('plan_iterate').auth).toBe('write');
76
+ expect(findOp('plan_split').auth).toBe('write');
77
+ expect(findOp('plan_reconcile').auth).toBe('write');
78
+ expect(findOp('plan_complete_lifecycle').auth).toBe('write');
79
+ expect(findOp('plan_dispatch').auth).toBe('read');
80
+ expect(findOp('plan_review').auth).toBe('write');
81
+ expect(findOp('plan_archive').auth).toBe('admin');
82
+ expect(findOp('plan_list_tasks').auth).toBe('read');
83
+ expect(findOp('plan_stats').auth).toBe('read');
84
+ });
85
+
86
+ // ─── Helper: create a draft plan ─────────────────────────────────
87
+ function createDraftPlan() {
88
+ return runtime.planner.create({
89
+ objective: 'Test objective',
90
+ scope: 'Test scope',
91
+ tasks: [
92
+ { title: 'Task A', description: 'Do A' },
93
+ { title: 'Task B', description: 'Do B' },
94
+ ],
95
+ });
96
+ }
97
+
98
+ // ─── plan_iterate ─────────────────────────────────────────────────
99
+ describe('plan_iterate', () => {
100
+ it('should update objective on a draft plan', async () => {
101
+ const plan = createDraftPlan();
102
+ const result = (await findOp('plan_iterate').handler({
103
+ planId: plan.id,
104
+ objective: 'Updated objective',
105
+ })) as { iterated: boolean; plan: { objective: string } };
106
+ expect(result.iterated).toBe(true);
107
+ expect(result.plan.objective).toBe('Updated objective');
108
+ });
109
+
110
+ it('should add tasks to a draft plan', async () => {
111
+ const plan = createDraftPlan();
112
+ const result = (await findOp('plan_iterate').handler({
113
+ planId: plan.id,
114
+ addTasks: [{ title: 'Task C', description: 'Do C' }],
115
+ })) as { iterated: boolean; plan: { tasks: unknown[] } };
116
+ expect(result.iterated).toBe(true);
117
+ expect(result.plan.tasks.length).toBe(3);
118
+ });
119
+
120
+ it('should remove tasks from a draft plan', async () => {
121
+ const plan = createDraftPlan();
122
+ const result = (await findOp('plan_iterate').handler({
123
+ planId: plan.id,
124
+ removeTasks: ['task-1'],
125
+ })) as { iterated: boolean; plan: { tasks: Array<{ id: string }> } };
126
+ expect(result.iterated).toBe(true);
127
+ expect(result.plan.tasks.length).toBe(1);
128
+ expect(result.plan.tasks[0].id).toBe('task-2');
129
+ });
130
+
131
+ it('should return error for non-draft plan', async () => {
132
+ const plan = createDraftPlan();
133
+ runtime.planner.approve(plan.id);
134
+ const result = (await findOp('plan_iterate').handler({
135
+ planId: plan.id,
136
+ objective: 'Updated',
137
+ })) as { error: string };
138
+ expect(result.error).toContain("must be 'draft' or 'brainstorming'");
139
+ });
140
+
141
+ it('should return error for unknown plan', async () => {
142
+ const result = (await findOp('plan_iterate').handler({
143
+ planId: 'nonexistent',
144
+ objective: 'Updated',
145
+ })) as { error: string };
146
+ expect(result.error).toContain('Plan not found');
147
+ });
148
+ });
149
+
150
+ // ─── plan_split ───────────────────────────────────────────────────
151
+ describe('plan_split', () => {
152
+ it('should replace tasks with dependency tracking', async () => {
153
+ const plan = createDraftPlan();
154
+ const result = (await findOp('plan_split').handler({
155
+ planId: plan.id,
156
+ tasks: [
157
+ { title: 'Setup', description: 'Environment setup' },
158
+ { title: 'Implement', description: 'Core implementation', dependsOn: ['task-1'] },
159
+ { title: 'Test', description: 'Write tests', dependsOn: ['task-2'] },
160
+ ],
161
+ })) as {
162
+ split: boolean;
163
+ taskCount: number;
164
+ plan: { tasks: Array<{ dependsOn?: string[] }> };
165
+ };
166
+ expect(result.split).toBe(true);
167
+ expect(result.taskCount).toBe(3);
168
+ expect(result.plan.tasks[1].dependsOn).toEqual(['task-1']);
169
+ expect(result.plan.tasks[2].dependsOn).toEqual(['task-2']);
170
+ });
171
+
172
+ it('should reject invalid dependency references', async () => {
173
+ const plan = createDraftPlan();
174
+ const result = (await findOp('plan_split').handler({
175
+ planId: plan.id,
176
+ tasks: [
177
+ { title: 'Task', description: 'Depends on nothing that exists', dependsOn: ['task-99'] },
178
+ ],
179
+ })) as { error: string };
180
+ expect(result.error).toContain('unknown task');
181
+ });
182
+
183
+ it('should work on approved plans', async () => {
184
+ const plan = createDraftPlan();
185
+ runtime.planner.approve(plan.id);
186
+ const result = (await findOp('plan_split').handler({
187
+ planId: plan.id,
188
+ tasks: [{ title: 'Only task', description: 'Single task' }],
189
+ })) as { split: boolean; taskCount: number };
190
+ expect(result.split).toBe(true);
191
+ expect(result.taskCount).toBe(1);
192
+ });
193
+
194
+ it('should return error for executing plan', async () => {
195
+ const plan = createDraftPlan();
196
+ runtime.planner.approve(plan.id);
197
+ runtime.planner.startExecution(plan.id);
198
+ const result = (await findOp('plan_split').handler({
199
+ planId: plan.id,
200
+ tasks: [{ title: 'T', description: 'D' }],
201
+ })) as { error: string };
202
+ expect(result.error).toContain("must be 'brainstorming', 'draft', or 'approved'");
203
+ });
204
+ });
205
+
206
+ // ─── plan_reconcile ───────────────────────────────────────────────
207
+ describe('plan_reconcile', () => {
208
+ it('should reconcile an executing plan', async () => {
209
+ const plan = createDraftPlan();
210
+ runtime.planner.approve(plan.id);
211
+ runtime.planner.startExecution(plan.id);
212
+
213
+ const result = (await findOp('plan_reconcile').handler({
214
+ planId: plan.id,
215
+ actualOutcome: 'Completed as planned with minor adjustments',
216
+ driftItems: [
217
+ {
218
+ type: 'modified',
219
+ description: 'Changed approach for Task B',
220
+ impact: 'low',
221
+ rationale: 'Found a simpler way',
222
+ },
223
+ ],
224
+ })) as { reconciled: boolean; accuracy: number; driftCount: number };
225
+ expect(result.reconciled).toBe(true);
226
+ expect(result.accuracy).toBe(95); // 1 low-impact drift = 100 - 5 = 95
227
+ expect(result.driftCount).toBe(1);
228
+ });
229
+
230
+ it('should mark executing plan as completed after reconcile', async () => {
231
+ const plan = createDraftPlan();
232
+ runtime.planner.approve(plan.id);
233
+ runtime.planner.startExecution(plan.id);
234
+
235
+ const result = (await findOp('plan_reconcile').handler({
236
+ planId: plan.id,
237
+ actualOutcome: 'Done',
238
+ })) as { reconciled: boolean; plan: { status: string } };
239
+ expect(result.plan.status).toBe('completed');
240
+ });
241
+
242
+ it('should return 100% accuracy with no drift items', async () => {
243
+ const plan = createDraftPlan();
244
+ runtime.planner.approve(plan.id);
245
+ runtime.planner.startExecution(plan.id);
246
+
247
+ const result = (await findOp('plan_reconcile').handler({
248
+ planId: plan.id,
249
+ actualOutcome: 'Perfect execution',
250
+ })) as { accuracy: number; driftCount: number };
251
+ expect(result.accuracy).toBe(100);
252
+ expect(result.driftCount).toBe(0);
253
+ });
254
+
255
+ it('should return error for draft plan', async () => {
256
+ const plan = createDraftPlan();
257
+ const result = (await findOp('plan_reconcile').handler({
258
+ planId: plan.id,
259
+ actualOutcome: 'Done',
260
+ })) as { error: string };
261
+ expect(result.error).toContain("must be 'executing', 'validating', or 'reconciling'");
262
+ });
263
+ });
264
+
265
+ // ─── plan_complete_lifecycle ──────────────────────────────────────
266
+ describe('plan_complete_lifecycle', () => {
267
+ it('should capture patterns and anti-patterns into vault', async () => {
268
+ const plan = createDraftPlan();
269
+ runtime.planner.approve(plan.id);
270
+ runtime.planner.startExecution(plan.id);
271
+ runtime.planner.startReconciliation(plan.id);
272
+ runtime.planner.complete(plan.id);
273
+
274
+ const result = (await findOp('plan_complete_lifecycle').handler({
275
+ planId: plan.id,
276
+ patterns: ['Always write tests first'],
277
+ antiPatterns: ['Do not skip code review'],
278
+ })) as {
279
+ completed: boolean;
280
+ knowledgeCaptured: number;
281
+ patternsAdded: number;
282
+ antiPatternsAdded: number;
283
+ };
284
+ expect(result.completed).toBe(true);
285
+ expect(result.knowledgeCaptured).toBe(2);
286
+ expect(result.patternsAdded).toBe(1);
287
+ expect(result.antiPatternsAdded).toBe(1);
288
+
289
+ // Verify entries are in vault
290
+ const stats = runtime.vault.stats();
291
+ expect(stats.totalEntries).toBeGreaterThanOrEqual(2);
292
+ });
293
+
294
+ it('should work with no patterns or anti-patterns', async () => {
295
+ const plan = createDraftPlan();
296
+ runtime.planner.approve(plan.id);
297
+ runtime.planner.startExecution(plan.id);
298
+ runtime.planner.startReconciliation(plan.id);
299
+ runtime.planner.complete(plan.id);
300
+
301
+ const result = (await findOp('plan_complete_lifecycle').handler({
302
+ planId: plan.id,
303
+ })) as { completed: boolean; knowledgeCaptured: number };
304
+ expect(result.completed).toBe(true);
305
+ expect(result.knowledgeCaptured).toBe(0);
306
+ });
307
+
308
+ it('should return error for non-completed plan', async () => {
309
+ const plan = createDraftPlan();
310
+ const result = (await findOp('plan_complete_lifecycle').handler({
311
+ planId: plan.id,
312
+ patterns: ['Test'],
313
+ })) as { error: string };
314
+ expect(result.error).toContain('must be completed');
315
+ });
316
+
317
+ it('should return error for unknown plan', async () => {
318
+ const result = (await findOp('plan_complete_lifecycle').handler({
319
+ planId: 'nonexistent',
320
+ })) as { error: string };
321
+ expect(result.error).toContain('Plan not found');
322
+ });
323
+ });
324
+
325
+ // ─── plan_dispatch ────────────────────────────────────────────────
326
+ describe('plan_dispatch', () => {
327
+ it('should return task and ready status when no dependencies', async () => {
328
+ const plan = createDraftPlan();
329
+ const result = (await findOp('plan_dispatch').handler({
330
+ planId: plan.id,
331
+ taskId: 'task-1',
332
+ })) as { task: { id: string }; unmetDependencies: unknown[]; ready: boolean };
333
+ expect(result.task.id).toBe('task-1');
334
+ expect(result.unmetDependencies).toEqual([]);
335
+ expect(result.ready).toBe(true);
336
+ });
337
+
338
+ it('should report unmet dependencies', async () => {
339
+ const plan = createDraftPlan();
340
+ // Split with dependencies
341
+ runtime.planner.splitTasks(plan.id, [
342
+ { title: 'First', description: 'Do first' },
343
+ { title: 'Second', description: 'Do second', dependsOn: ['task-1'] },
344
+ ]);
345
+
346
+ const result = (await findOp('plan_dispatch').handler({
347
+ planId: plan.id,
348
+ taskId: 'task-2',
349
+ })) as { task: { id: string }; unmetDependencies: Array<{ id: string }>; ready: boolean };
350
+ expect(result.ready).toBe(false);
351
+ expect(result.unmetDependencies.length).toBe(1);
352
+ expect(result.unmetDependencies[0].id).toBe('task-1');
353
+ });
354
+
355
+ it('should report ready when dependencies are completed', async () => {
356
+ const plan = createDraftPlan();
357
+ runtime.planner.splitTasks(plan.id, [
358
+ { title: 'First', description: 'Do first' },
359
+ { title: 'Second', description: 'Do second', dependsOn: ['task-1'] },
360
+ ]);
361
+ runtime.planner.approve(plan.id);
362
+ runtime.planner.startExecution(plan.id);
363
+ runtime.planner.updateTask(plan.id, 'task-1', 'completed');
364
+
365
+ const result = (await findOp('plan_dispatch').handler({
366
+ planId: plan.id,
367
+ taskId: 'task-2',
368
+ })) as { ready: boolean; unmetDependencies: unknown[] };
369
+ expect(result.ready).toBe(true);
370
+ expect(result.unmetDependencies).toEqual([]);
371
+ });
372
+
373
+ it('should return error for unknown task', async () => {
374
+ const plan = createDraftPlan();
375
+ const result = (await findOp('plan_dispatch').handler({
376
+ planId: plan.id,
377
+ taskId: 'task-99',
378
+ })) as { error: string };
379
+ expect(result.error).toContain('Task not found');
380
+ });
381
+ });
382
+
383
+ // ─── plan_review ──────────────────────────────────────────────────
384
+ describe('plan_review', () => {
385
+ it('should add a review to a plan', async () => {
386
+ const plan = createDraftPlan();
387
+ const result = (await findOp('plan_review').handler({
388
+ planId: plan.id,
389
+ reviewer: 'Alice',
390
+ outcome: 'approved',
391
+ comments: 'Looks good',
392
+ })) as { reviewed: boolean; totalReviews: number };
393
+ expect(result.reviewed).toBe(true);
394
+ expect(result.totalReviews).toBe(1);
395
+ });
396
+
397
+ it('should add a task-level review', async () => {
398
+ const plan = createDraftPlan();
399
+ const result = (await findOp('plan_review').handler({
400
+ planId: plan.id,
401
+ taskId: 'task-1',
402
+ reviewer: 'Bob',
403
+ outcome: 'needs_changes',
404
+ comments: 'Needs more detail',
405
+ })) as { reviewed: boolean; totalReviews: number };
406
+ expect(result.reviewed).toBe(true);
407
+ expect(result.totalReviews).toBe(1);
408
+ });
409
+
410
+ it('should accumulate multiple reviews', async () => {
411
+ const plan = createDraftPlan();
412
+ await findOp('plan_review').handler({
413
+ planId: plan.id,
414
+ reviewer: 'Alice',
415
+ outcome: 'approved',
416
+ comments: 'LGTM',
417
+ });
418
+ const result = (await findOp('plan_review').handler({
419
+ planId: plan.id,
420
+ reviewer: 'Bob',
421
+ outcome: 'approved',
422
+ comments: 'Also LGTM',
423
+ })) as { totalReviews: number };
424
+ expect(result.totalReviews).toBe(2);
425
+ });
426
+
427
+ it('should return error for unknown task', async () => {
428
+ const plan = createDraftPlan();
429
+ const result = (await findOp('plan_review').handler({
430
+ planId: plan.id,
431
+ taskId: 'task-99',
432
+ reviewer: 'Alice',
433
+ outcome: 'approved',
434
+ comments: 'Ok',
435
+ })) as { error: string };
436
+ expect(result.error).toContain('Task not found');
437
+ });
438
+ });
439
+
440
+ // ─── plan_archive ─────────────────────────────────────────────────
441
+ describe('plan_archive', () => {
442
+ it('should archive old completed plans', async () => {
443
+ const plan = createDraftPlan();
444
+ runtime.planner.approve(plan.id);
445
+ runtime.planner.startExecution(plan.id);
446
+ runtime.planner.startReconciliation(plan.id);
447
+ runtime.planner.complete(plan.id);
448
+
449
+ // Hack: set updatedAt to 60 days ago
450
+ const stored = runtime.planner.get(plan.id)!;
451
+ (stored as { updatedAt: number }).updatedAt = Date.now() - 60 * 24 * 60 * 60 * 1000;
452
+ // Force save by calling a no-op iterate (not possible on completed plan)
453
+ // Instead, create another plan and archive to trigger save
454
+ // We need to directly manipulate — use archive with 0 days to catch it
455
+ const result = (await findOp('plan_archive').handler({
456
+ olderThanDays: 0,
457
+ })) as { archived: number; plans: Array<{ id: string }> };
458
+ expect(result.archived).toBe(1);
459
+ expect(result.plans[0].id).toBe(plan.id);
460
+ });
461
+
462
+ it('should not archive non-completed plans', async () => {
463
+ createDraftPlan(); // draft plan
464
+ const result = (await findOp('plan_archive').handler({
465
+ olderThanDays: 0,
466
+ })) as { archived: number };
467
+ expect(result.archived).toBe(0);
468
+ });
469
+
470
+ it('should not archive recent completed plans', async () => {
471
+ const plan = createDraftPlan();
472
+ runtime.planner.approve(plan.id);
473
+ runtime.planner.startExecution(plan.id);
474
+ runtime.planner.startReconciliation(plan.id);
475
+ runtime.planner.complete(plan.id);
476
+
477
+ const result = (await findOp('plan_archive').handler({
478
+ olderThanDays: 30,
479
+ })) as { archived: number };
480
+ expect(result.archived).toBe(0);
481
+ });
482
+ });
483
+
484
+ // ─── plan_list_tasks ──────────────────────────────────────────────
485
+ describe('plan_list_tasks', () => {
486
+ it('should list all tasks for a plan', async () => {
487
+ const plan = createDraftPlan();
488
+ const result = (await findOp('plan_list_tasks').handler({
489
+ planId: plan.id,
490
+ })) as { planId: string; total: number; filtered: number; tasks: unknown[] };
491
+ expect(result.planId).toBe(plan.id);
492
+ expect(result.total).toBe(2);
493
+ expect(result.filtered).toBe(2);
494
+ expect(result.tasks.length).toBe(2);
495
+ });
496
+
497
+ it('should filter tasks by status', async () => {
498
+ const plan = createDraftPlan();
499
+ runtime.planner.approve(plan.id);
500
+ runtime.planner.startExecution(plan.id);
501
+ runtime.planner.updateTask(plan.id, 'task-1', 'completed');
502
+
503
+ const result = (await findOp('plan_list_tasks').handler({
504
+ planId: plan.id,
505
+ status: 'completed',
506
+ })) as { total: number; filtered: number; tasks: Array<{ id: string }> };
507
+ expect(result.total).toBe(2);
508
+ expect(result.filtered).toBe(1);
509
+ expect(result.tasks[0].id).toBe('task-1');
510
+ });
511
+
512
+ it('should return empty when no tasks match filter', async () => {
513
+ const plan = createDraftPlan();
514
+ const result = (await findOp('plan_list_tasks').handler({
515
+ planId: plan.id,
516
+ status: 'failed',
517
+ })) as { filtered: number; tasks: unknown[] };
518
+ expect(result.filtered).toBe(0);
519
+ expect(result.tasks).toEqual([]);
520
+ });
521
+
522
+ it('should return error for unknown plan', async () => {
523
+ const result = (await findOp('plan_list_tasks').handler({
524
+ planId: 'nonexistent',
525
+ })) as { error: string };
526
+ expect(result.error).toContain('Plan not found');
527
+ });
528
+ });
529
+
530
+ // ─── plan_execution_metrics ─────────────────────────────────────
531
+ describe('plan_execution_metrics', () => {
532
+ it('should return metrics for a plan with timed tasks', async () => {
533
+ const plan = createDraftPlan();
534
+ runtime.planner.approve(plan.id);
535
+ runtime.planner.startExecution(plan.id);
536
+ runtime.planner.updateTask(plan.id, 'task-1', 'in_progress');
537
+ runtime.planner.updateTask(plan.id, 'task-1', 'completed');
538
+
539
+ const result = (await findOp('plan_execution_metrics').handler({
540
+ planId: plan.id,
541
+ })) as {
542
+ planId: string;
543
+ taskMetrics: Array<{ id: string; startedAt: number | null; completedAt: number | null }>;
544
+ executionSummary: { tasksCompleted: number } | null;
545
+ };
546
+
547
+ expect(result.planId).toBe(plan.id);
548
+ expect(result.taskMetrics).toHaveLength(2);
549
+ // task-1 was transitioned, should have timing
550
+ const task1 = result.taskMetrics.find((t) => t.id === 'task-1')!;
551
+ expect(task1.startedAt).not.toBeNull();
552
+ expect(task1.completedAt).not.toBeNull();
553
+ });
554
+
555
+ it('should return error for unknown plan', async () => {
556
+ const result = (await findOp('plan_execution_metrics').handler({
557
+ planId: 'nonexistent',
558
+ })) as { error: string };
559
+ expect(result.error).toContain('Plan not found');
560
+ });
561
+ });
562
+
563
+ // ─── plan_record_task_metrics ─────────────────────────────────
564
+ describe('plan_record_task_metrics', () => {
565
+ it('should record tool calls and model tier on a task', async () => {
566
+ const plan = createDraftPlan();
567
+ runtime.planner.approve(plan.id);
568
+ runtime.planner.startExecution(plan.id);
569
+
570
+ const result = (await findOp('plan_record_task_metrics').handler({
571
+ planId: plan.id,
572
+ taskId: 'task-1',
573
+ toolCalls: 15,
574
+ modelTier: 'opus',
575
+ })) as {
576
+ recorded: boolean;
577
+ taskId: string;
578
+ metrics: { toolCalls: number; modelTier: string };
579
+ };
580
+
581
+ expect(result.recorded).toBe(true);
582
+ expect(result.taskId).toBe('task-1');
583
+ expect(result.metrics.toolCalls).toBe(15);
584
+ expect(result.metrics.modelTier).toBe('opus');
585
+ });
586
+
587
+ it('should return error for unknown task', async () => {
588
+ const plan = createDraftPlan();
589
+ const result = (await findOp('plan_record_task_metrics').handler({
590
+ planId: plan.id,
591
+ taskId: 'task-99',
592
+ toolCalls: 5,
593
+ })) as { error: string };
594
+ expect(result.error).toContain('Task not found');
595
+ });
596
+ });
597
+
598
+ // ─── plan_submit_deliverable ──────────────────────────────────
599
+ describe('plan_submit_deliverable', () => {
600
+ it('should record a vault_entry deliverable on a task', async () => {
601
+ const plan = createDraftPlan();
602
+
603
+ const result = (await findOp('plan_submit_deliverable').handler({
604
+ planId: plan.id,
605
+ taskId: 'task-1',
606
+ type: 'vault_entry',
607
+ path: 'patterns/my-pattern',
608
+ })) as { submitted: boolean; taskId: string; deliverableCount: number };
609
+
610
+ expect(result.submitted).toBe(true);
611
+ expect(result.taskId).toBe('task-1');
612
+ expect(result.deliverableCount).toBe(1);
613
+ });
614
+
615
+ it('should return error for unknown plan', async () => {
616
+ const result = (await findOp('plan_submit_deliverable').handler({
617
+ planId: 'nonexistent',
618
+ taskId: 'task-1',
619
+ type: 'file',
620
+ path: '/tmp/test.txt',
621
+ })) as { error: string };
622
+ expect(result.error).toContain('Plan not found');
623
+ });
624
+ });
625
+
626
+ // ─── plan_verify_deliverables ─────────────────────────────────
627
+ describe('plan_verify_deliverables', () => {
628
+ it('should verify deliverables on a task', async () => {
629
+ const plan = createDraftPlan();
630
+ // Submit a vault_entry deliverable
631
+ await runtime.planner.submitDeliverable(plan.id, 'task-1', {
632
+ type: 'vault_entry',
633
+ path: 'nonexistent-entry',
634
+ });
635
+
636
+ const result = (await findOp('plan_verify_deliverables').handler({
637
+ planId: plan.id,
638
+ taskId: 'task-1',
639
+ })) as { verified: boolean; deliverables: Array<{ stale: boolean }>; staleCount: number };
640
+
641
+ expect(result.verified).toBe(false); // vault entry doesn't exist
642
+ expect(result.deliverables).toHaveLength(1);
643
+ expect(result.staleCount).toBe(1);
644
+ });
645
+
646
+ it('should return error for task with no deliverables', async () => {
647
+ const plan = createDraftPlan();
648
+
649
+ const result = (await findOp('plan_verify_deliverables').handler({
650
+ planId: plan.id,
651
+ taskId: 'task-1',
652
+ })) as { verified: boolean; deliverables: unknown[]; staleCount: number };
653
+
654
+ // No deliverables means verified = true (nothing to check)
655
+ expect(result.verified).toBe(true);
656
+ expect(result.deliverables).toHaveLength(0);
657
+ expect(result.staleCount).toBe(0);
658
+ });
659
+ });
660
+
661
+ // ─── plan_stats ───────────────────────────────────────────────────
662
+ describe('plan_stats', () => {
663
+ it('should return zero stats when no plans exist', async () => {
664
+ const result = (await findOp('plan_stats').handler({})) as {
665
+ total: number;
666
+ byStatus: Record<string, number>;
667
+ avgTasksPerPlan: number;
668
+ totalTasks: number;
669
+ tasksByStatus: Record<string, number>;
670
+ };
671
+ expect(result.total).toBe(0);
672
+ expect(result.byStatus.draft).toBe(0);
673
+ expect(result.avgTasksPerPlan).toBe(0);
674
+ expect(result.totalTasks).toBe(0);
675
+ });
676
+
677
+ it('should return correct stats with plans', async () => {
678
+ createDraftPlan(); // draft with 2 tasks
679
+ const plan2 = runtime.planner.create({
680
+ objective: 'Plan 2',
681
+ scope: 'Scope 2',
682
+ tasks: [{ title: 'T1', description: 'D1' }],
683
+ });
684
+ runtime.planner.approve(plan2.id);
685
+ runtime.planner.startExecution(plan2.id);
686
+ runtime.planner.updateTask(plan2.id, 'task-1', 'completed');
687
+ runtime.planner.startReconciliation(plan2.id);
688
+ runtime.planner.complete(plan2.id);
689
+
690
+ const result = (await findOp('plan_stats').handler({})) as {
691
+ total: number;
692
+ byStatus: Record<string, number>;
693
+ avgTasksPerPlan: number;
694
+ totalTasks: number;
695
+ tasksByStatus: Record<string, number>;
696
+ };
697
+ expect(result.total).toBe(2);
698
+ expect(result.byStatus.draft).toBe(1);
699
+ expect(result.byStatus.completed).toBe(1);
700
+ expect(result.totalTasks).toBe(3);
701
+ expect(result.avgTasksPerPlan).toBe(1.5);
702
+ expect(result.tasksByStatus.pending).toBe(2);
703
+ expect(result.tasksByStatus.completed).toBe(1);
704
+ });
705
+ });
706
+ });