@soleri/core 9.15.0 → 9.16.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/data/flows/deliver.flow.yaml +11 -0
  2. package/data/flows/design.flow.yaml +4 -14
  3. package/data/flows/enhance.flow.yaml +10 -0
  4. package/data/flows/explore.flow.yaml +16 -0
  5. package/data/flows/fix.flow.yaml +1 -1
  6. package/data/flows/review.flow.yaml +13 -4
  7. package/dist/capabilities/chain-mapping.d.ts.map +1 -1
  8. package/dist/capabilities/chain-mapping.js +5 -4
  9. package/dist/capabilities/chain-mapping.js.map +1 -1
  10. package/dist/capabilities/registry.d.ts +6 -0
  11. package/dist/capabilities/registry.d.ts.map +1 -1
  12. package/dist/capabilities/registry.js +3 -2
  13. package/dist/capabilities/registry.js.map +1 -1
  14. package/dist/context/context-engine.js +1 -1
  15. package/dist/context/context-engine.js.map +1 -1
  16. package/dist/engine/core-ops.d.ts.map +1 -1
  17. package/dist/engine/core-ops.js +38 -1
  18. package/dist/engine/core-ops.js.map +1 -1
  19. package/dist/flows/epilogue.d.ts +5 -1
  20. package/dist/flows/epilogue.d.ts.map +1 -1
  21. package/dist/flows/epilogue.js +11 -3
  22. package/dist/flows/epilogue.js.map +1 -1
  23. package/dist/flows/executor.d.ts.map +1 -1
  24. package/dist/flows/executor.js +13 -5
  25. package/dist/flows/executor.js.map +1 -1
  26. package/dist/flows/index.d.ts +1 -2
  27. package/dist/flows/index.d.ts.map +1 -1
  28. package/dist/flows/index.js +1 -0
  29. package/dist/flows/index.js.map +1 -1
  30. package/dist/flows/plan-builder.d.ts +17 -1
  31. package/dist/flows/plan-builder.d.ts.map +1 -1
  32. package/dist/flows/plan-builder.js +67 -6
  33. package/dist/flows/plan-builder.js.map +1 -1
  34. package/dist/flows/probes.d.ts +1 -1
  35. package/dist/flows/probes.d.ts.map +1 -1
  36. package/dist/flows/probes.js +15 -3
  37. package/dist/flows/probes.js.map +1 -1
  38. package/dist/flows/types.d.ts +31 -4
  39. package/dist/flows/types.d.ts.map +1 -1
  40. package/dist/flows/types.js +6 -1
  41. package/dist/flows/types.js.map +1 -1
  42. package/dist/index.d.ts +8 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +7 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/packs/pack-installer.d.ts.map +1 -1
  47. package/dist/packs/pack-installer.js +28 -2
  48. package/dist/packs/pack-installer.js.map +1 -1
  49. package/dist/planning/planner-types.d.ts +2 -0
  50. package/dist/planning/planner-types.d.ts.map +1 -1
  51. package/dist/planning/planner.d.ts +1 -0
  52. package/dist/planning/planner.d.ts.map +1 -1
  53. package/dist/planning/planner.js +7 -0
  54. package/dist/planning/planner.js.map +1 -1
  55. package/dist/playbooks/playbook-executor.d.ts +10 -1
  56. package/dist/playbooks/playbook-executor.d.ts.map +1 -1
  57. package/dist/playbooks/playbook-executor.js +8 -2
  58. package/dist/playbooks/playbook-executor.js.map +1 -1
  59. package/dist/playbooks/playbook-types.d.ts +8 -0
  60. package/dist/playbooks/playbook-types.d.ts.map +1 -1
  61. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  62. package/dist/runtime/admin-extra-ops.js +30 -0
  63. package/dist/runtime/admin-extra-ops.js.map +1 -1
  64. package/dist/runtime/admin-ops.d.ts.map +1 -1
  65. package/dist/runtime/admin-ops.js +60 -21
  66. package/dist/runtime/admin-ops.js.map +1 -1
  67. package/dist/runtime/admin-setup-ops.d.ts +11 -0
  68. package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
  69. package/dist/runtime/admin-setup-ops.js +87 -17
  70. package/dist/runtime/admin-setup-ops.js.map +1 -1
  71. package/dist/runtime/capture-ops.d.ts.map +1 -1
  72. package/dist/runtime/capture-ops.js +38 -12
  73. package/dist/runtime/capture-ops.js.map +1 -1
  74. package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
  75. package/dist/runtime/facades/brain-facade.js +16 -4
  76. package/dist/runtime/facades/brain-facade.js.map +1 -1
  77. package/dist/runtime/facades/context-facade.d.ts.map +1 -1
  78. package/dist/runtime/facades/context-facade.js +9 -3
  79. package/dist/runtime/facades/context-facade.js.map +1 -1
  80. package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
  81. package/dist/runtime/facades/memory-facade.js +20 -7
  82. package/dist/runtime/facades/memory-facade.js.map +1 -1
  83. package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
  84. package/dist/runtime/facades/orchestrate-facade.js +12 -0
  85. package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
  86. package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
  87. package/dist/runtime/facades/plan-facade.js +113 -4
  88. package/dist/runtime/facades/plan-facade.js.map +1 -1
  89. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  90. package/dist/runtime/facades/vault-facade.js +24 -3
  91. package/dist/runtime/facades/vault-facade.js.map +1 -1
  92. package/dist/runtime/orchestrate-ops.d.ts +21 -0
  93. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  94. package/dist/runtime/orchestrate-ops.js +132 -38
  95. package/dist/runtime/orchestrate-ops.js.map +1 -1
  96. package/dist/runtime/schema-helpers.d.ts.map +1 -1
  97. package/dist/runtime/schema-helpers.js +4 -0
  98. package/dist/runtime/schema-helpers.js.map +1 -1
  99. package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
  100. package/dist/runtime/vault-linking-ops.js +16 -3
  101. package/dist/runtime/vault-linking-ops.js.map +1 -1
  102. package/dist/scheduler/cron-validator.d.ts +15 -0
  103. package/dist/scheduler/cron-validator.d.ts.map +1 -0
  104. package/dist/scheduler/cron-validator.js +93 -0
  105. package/dist/scheduler/cron-validator.js.map +1 -0
  106. package/dist/scheduler/platform-linux.d.ts +14 -0
  107. package/dist/scheduler/platform-linux.d.ts.map +1 -0
  108. package/dist/scheduler/platform-linux.js +107 -0
  109. package/dist/scheduler/platform-linux.js.map +1 -0
  110. package/dist/scheduler/platform-macos.d.ts +15 -0
  111. package/dist/scheduler/platform-macos.d.ts.map +1 -0
  112. package/dist/scheduler/platform-macos.js +131 -0
  113. package/dist/scheduler/platform-macos.js.map +1 -0
  114. package/dist/scheduler/scheduler-ops.d.ts +14 -0
  115. package/dist/scheduler/scheduler-ops.d.ts.map +1 -0
  116. package/dist/scheduler/scheduler-ops.js +77 -0
  117. package/dist/scheduler/scheduler-ops.js.map +1 -0
  118. package/dist/scheduler/scheduler.d.ts +55 -0
  119. package/dist/scheduler/scheduler.d.ts.map +1 -0
  120. package/dist/scheduler/scheduler.js +144 -0
  121. package/dist/scheduler/scheduler.js.map +1 -0
  122. package/dist/scheduler/types.d.ts +48 -0
  123. package/dist/scheduler/types.d.ts.map +1 -0
  124. package/dist/scheduler/types.js +6 -0
  125. package/dist/scheduler/types.js.map +1 -0
  126. package/dist/skills/sync-skills.d.ts +11 -0
  127. package/dist/skills/sync-skills.d.ts.map +1 -1
  128. package/dist/skills/sync-skills.js +132 -38
  129. package/dist/skills/sync-skills.js.map +1 -1
  130. package/dist/utils/worktree-reaper.d.ts +38 -0
  131. package/dist/utils/worktree-reaper.d.ts.map +1 -0
  132. package/dist/utils/worktree-reaper.js +85 -0
  133. package/dist/utils/worktree-reaper.js.map +1 -0
  134. package/dist/vault/scope-detector.d.ts.map +1 -1
  135. package/dist/vault/scope-detector.js +37 -4
  136. package/dist/vault/scope-detector.js.map +1 -1
  137. package/dist/vault/vault-entries.d.ts.map +1 -1
  138. package/dist/vault/vault-entries.js +3 -1
  139. package/dist/vault/vault-entries.js.map +1 -1
  140. package/package.json +1 -1
  141. package/src/agency/agency-manager.test.ts +4 -4
  142. package/src/agency/default-rules.test.ts +0 -13
  143. package/src/brain/brain-intelligence.test.ts +0 -5
  144. package/src/brain/second-brain-features.test.ts +2 -14
  145. package/src/capabilities/chain-mapping.test.ts +1 -6
  146. package/src/capabilities/chain-mapping.ts +6 -4
  147. package/src/capabilities/registry.test.ts +1 -1
  148. package/src/capabilities/registry.ts +9 -2
  149. package/src/chat/agent-loop.test.ts +1 -1
  150. package/src/chat/chat-enhanced.test.ts +0 -8
  151. package/src/claudemd/compose.test.ts +0 -5
  152. package/src/context/context-engine.test.ts +0 -1
  153. package/src/context/context-engine.ts +1 -1
  154. package/src/control/intent-router.test.ts +2 -2
  155. package/src/curator/tag-manager.test.ts +0 -4
  156. package/src/domain-packs/types.test.ts +0 -5
  157. package/src/dream/dream.test.ts +0 -7
  158. package/src/enforcement/registry.test.ts +2 -2
  159. package/src/engine/core-ops.test.ts +4 -22
  160. package/src/engine/core-ops.ts +36 -1
  161. package/src/engine/module-manifest.test.ts +1 -31
  162. package/src/engine/register-engine.test.ts +3 -33
  163. package/src/errors/retry.test.ts +3 -1
  164. package/src/flows/chain-runner.test.ts +0 -6
  165. package/src/flows/context-router.test.ts +3 -3
  166. package/src/flows/epilogue.test.ts +40 -2
  167. package/src/flows/epilogue.ts +11 -2
  168. package/src/flows/executor.test.ts +48 -2
  169. package/src/flows/executor.ts +15 -5
  170. package/src/flows/index.ts +1 -3
  171. package/src/flows/plan-builder.test.ts +201 -0
  172. package/src/flows/plan-builder.ts +81 -5
  173. package/src/flows/probes.ts +17 -3
  174. package/src/flows/types.ts +31 -2
  175. package/src/health/health-registry.test.ts +3 -1
  176. package/src/index.ts +17 -0
  177. package/src/intake/dedup-gate.test.ts +2 -6
  178. package/src/intake/text-ingester.test.ts +3 -4
  179. package/src/llm/llm-client.test.ts +1 -1
  180. package/src/llm/utils.test.ts +1 -1
  181. package/src/migrations/migration-runner.test.ts +0 -1
  182. package/src/operator/operator-context-store.test.ts +0 -13
  183. package/src/operator/operator-profile.test.ts +2 -20
  184. package/src/packs/pack-installer.ts +28 -2
  185. package/src/packs/pack-system.test.ts +2 -2
  186. package/src/persona/defaults.test.ts +19 -19
  187. package/src/planning/gap-passes.test.ts +0 -46
  188. package/src/planning/gap-patterns.test.ts +0 -42
  189. package/src/planning/goal-ancestry.test.ts +3 -1
  190. package/src/planning/plan-lifecycle.test.ts +15 -7
  191. package/src/planning/planner-types.ts +2 -0
  192. package/src/planning/planner.ts +8 -0
  193. package/src/planning/reconciliation-engine.test.ts +3 -10
  194. package/src/planning/task-complexity-assessor.test.ts +0 -5
  195. package/src/planning/task-verifier.test.ts +3 -1
  196. package/src/playbooks/generic/generic-playbooks.test.ts +0 -28
  197. package/src/playbooks/index.test.ts +0 -55
  198. package/src/playbooks/playbook-executor.test.ts +76 -0
  199. package/src/playbooks/playbook-executor.ts +24 -3
  200. package/src/playbooks/playbook-types.ts +8 -0
  201. package/src/plugins/plugin-registry.test.ts +6 -2
  202. package/src/project/project-registry.test.ts +2 -0
  203. package/src/queue/async-infrastructure.test.ts +6 -4
  204. package/src/queue/job-queue.test.ts +13 -7
  205. package/src/runtime/admin-extra-ops.test.ts +35 -30
  206. package/src/runtime/admin-extra-ops.ts +30 -0
  207. package/src/runtime/admin-ops.test.ts +0 -4
  208. package/src/runtime/admin-ops.ts +63 -21
  209. package/src/runtime/admin-setup-ops.test.ts +185 -13
  210. package/src/runtime/admin-setup-ops.ts +86 -16
  211. package/src/runtime/archive-ops.test.ts +0 -28
  212. package/src/runtime/branching-ops.test.ts +0 -17
  213. package/src/runtime/capture-ops.test.ts +41 -16
  214. package/src/runtime/capture-ops.ts +78 -46
  215. package/src/runtime/chain-ops.test.ts +0 -21
  216. package/src/runtime/facades/admin-facade.test.ts +0 -34
  217. package/src/runtime/facades/agency-facade.test.ts +0 -39
  218. package/src/runtime/facades/archive-facade.test.ts +0 -43
  219. package/src/runtime/facades/brain-facade.test.ts +8 -99
  220. package/src/runtime/facades/brain-facade.ts +29 -12
  221. package/src/runtime/facades/branching-facade.test.ts +30 -17
  222. package/src/runtime/facades/chat-facade.test.ts +0 -91
  223. package/src/runtime/facades/chat-service-ops.test.ts +0 -24
  224. package/src/runtime/facades/chat-session-ops.test.ts +0 -12
  225. package/src/runtime/facades/chat-transport-ops.test.ts +0 -23
  226. package/src/runtime/facades/context-facade.test.ts +0 -17
  227. package/src/runtime/facades/context-facade.ts +11 -4
  228. package/src/runtime/facades/control-facade.test.ts +0 -30
  229. package/src/runtime/facades/curator-facade.test.ts +0 -33
  230. package/src/runtime/facades/intake-facade.test.ts +0 -33
  231. package/src/runtime/facades/links-facade.test.ts +0 -37
  232. package/src/runtime/facades/loop-facade.test.ts +0 -26
  233. package/src/runtime/facades/memory-facade.test.ts +0 -18
  234. package/src/runtime/facades/memory-facade.ts +27 -11
  235. package/src/runtime/facades/operator-facade.test.ts +0 -31
  236. package/src/runtime/facades/orchestrate-facade.test.ts +0 -21
  237. package/src/runtime/facades/orchestrate-facade.ts +12 -0
  238. package/src/runtime/facades/plan-facade.test.ts +7 -32
  239. package/src/runtime/facades/plan-facade.ts +137 -4
  240. package/src/runtime/facades/review-facade.test.ts +1 -49
  241. package/src/runtime/facades/sync-facade.test.ts +24 -41
  242. package/src/runtime/facades/tier-facade.test.ts +30 -22
  243. package/src/runtime/facades/vault-facade.test.ts +0 -41
  244. package/src/runtime/facades/vault-facade.ts +26 -3
  245. package/src/runtime/grading-ops.test.ts +0 -27
  246. package/src/runtime/intake-ops.test.ts +0 -19
  247. package/src/runtime/loop-ops.test.ts +0 -48
  248. package/src/runtime/memory-cross-project-ops.test.ts +0 -14
  249. package/src/runtime/memory-extra-ops.test.ts +4 -8
  250. package/src/runtime/orchestrate-ops.test.ts +238 -19
  251. package/src/runtime/orchestrate-ops.ts +166 -41
  252. package/src/runtime/pack-ops.test.ts +0 -26
  253. package/src/runtime/planning-extra-ops.test.ts +2 -14
  254. package/src/runtime/playbook-ops-execution.test.ts +9 -20
  255. package/src/runtime/playbook-ops.test.ts +4 -67
  256. package/src/runtime/review-ops.test.ts +0 -15
  257. package/src/runtime/schema-helpers.ts +4 -0
  258. package/src/runtime/sync-ops.test.ts +0 -18
  259. package/src/runtime/tier-ops.test.ts +0 -21
  260. package/src/runtime/vault-extra-ops.test.ts +0 -12
  261. package/src/runtime/vault-linking-ops.test.ts +0 -4
  262. package/src/runtime/vault-linking-ops.ts +26 -8
  263. package/src/runtime/vault-sharing-ops.test.ts +0 -9
  264. package/src/scheduler/cron-validator.ts +101 -0
  265. package/src/scheduler/platform-linux.ts +122 -0
  266. package/src/scheduler/platform-macos.ts +150 -0
  267. package/src/scheduler/scheduler-ops.ts +77 -0
  268. package/src/scheduler/scheduler.test.ts +247 -0
  269. package/src/scheduler/scheduler.ts +174 -0
  270. package/src/scheduler/types.ts +52 -0
  271. package/src/skills/__tests__/sync-skills.test.ts +6 -17
  272. package/src/skills/global-claude-md.test.ts +113 -0
  273. package/src/skills/sync-skills.ts +143 -35
  274. package/src/skills/validate-skills.test.ts +12 -11
  275. package/src/telemetry/telemetry.test.ts +1 -0
  276. package/src/transport/http-server.test.ts +3 -0
  277. package/src/transport/session-manager.test.ts +3 -1
  278. package/src/transport/token-auth.test.ts +6 -9
  279. package/src/transport/ws-server.test.ts +10 -2
  280. package/src/utils/worktree-reaper.ts +113 -0
  281. package/src/vault/__tests__/vault-characterization.test.ts +0 -108
  282. package/src/vault/linking.test.ts +0 -2
  283. package/src/vault/playbook.test.ts +4 -1
  284. package/src/vault/scope-detector.test.ts +3 -1
  285. package/src/vault/scope-detector.ts +42 -4
  286. package/src/vault/vault-connect.test.ts +1 -1
  287. package/src/vault/vault-entries.ts +3 -1
  288. package/src/vault/vault.test.ts +23 -8
@@ -10,6 +10,7 @@ import { createPlanningExtraOps } from '../planning-extra-ops.js';
10
10
  import { createGradingOps } from '../grading-ops.js';
11
11
  import { createChainOps } from '../chain-ops.js';
12
12
  import { PlanGradeRejectionError } from '../../planning/planner.js';
13
+ import { matchPlaybooks } from '../../playbooks/playbook-registry.js';
13
14
 
14
15
  export function createPlanFacadeOps(runtime: AgentRuntime): OpDefinition[] {
15
16
  const { planner, vault } = runtime;
@@ -64,16 +65,73 @@ export function createPlanFacadeOps(runtime: AgentRuntime): OpDefinition[] {
64
65
  // Vault search failed — proceed without enrichment
65
66
  }
66
67
 
68
+ // Playbook matching: inject task templates + start executor session
69
+ const userTasks = (params.tasks as Array<{ title: string; description: string }>) ?? [];
70
+ const matchResult = matchPlaybooks('BUILD', objective);
71
+ const playbook = matchResult.playbook;
72
+ const playbookTasks: Array<{ title: string; description: string }> = [];
73
+ let playbookSessionId: string | undefined;
74
+ let playbookMatch: { label: string; genericId?: string; domainId?: string } | undefined;
75
+
76
+ if (playbook) {
77
+ playbookMatch = {
78
+ label: playbook.label,
79
+ genericId: playbook.generic?.id,
80
+ domainId: playbook.domain?.id,
81
+ };
82
+
83
+ // Inject task templates (before-implementation first, then user tasks)
84
+ for (const tmpl of playbook.mergedTasks) {
85
+ const title = tmpl.titleTemplate.replace('{objective}', objective);
86
+ playbookTasks.push({ title, description: tmpl.acceptanceCriteria.join('\n') });
87
+ }
88
+
89
+ // Start executor session to track gate enforcement
90
+ if (runtime.playbookExecutor) {
91
+ const startResult = runtime.playbookExecutor.start(playbook);
92
+ playbookSessionId = startResult.sessionId;
93
+ }
94
+ }
95
+
96
+ const beforeTasks = playbookTasks.filter((_, i) => {
97
+ const tmpl = playbook?.mergedTasks[i];
98
+ return !tmpl || tmpl.order === 'before-implementation';
99
+ });
100
+ const afterTasks = playbookTasks.filter((_, i) => {
101
+ const tmpl = playbook?.mergedTasks[i];
102
+ return tmpl?.order === 'after-implementation';
103
+ });
104
+
105
+ const allTasks = [...beforeTasks, ...userTasks, ...afterTasks];
106
+
67
107
  const plan = planner.create({
68
108
  objective,
69
109
  scope: params.scope as string,
70
110
  decisions,
71
- tasks: (params.tasks as Array<{ title: string; description: string }>) ?? [],
111
+ tasks: allTasks,
72
112
  alternatives: params.alternatives as
73
113
  | Array<{ approach: string; pros: string[]; cons: string[]; rejected_reason: string }>
74
114
  | undefined,
75
115
  });
76
- return { created: true, plan, vaultEntryIds };
116
+
117
+ if (playbookMatch) plan.playbookMatch = playbookMatch;
118
+ if (playbookSessionId) {
119
+ planner.patchPlan(plan.id, { playbookSessionId });
120
+ plan.playbookSessionId = playbookSessionId;
121
+ }
122
+
123
+ return {
124
+ created: true,
125
+ plan,
126
+ vaultEntryIds,
127
+ playbook: playbook
128
+ ? {
129
+ label: playbook.label,
130
+ tasksInjected: playbookTasks.length,
131
+ sessionId: playbookSessionId ?? null,
132
+ }
133
+ : null,
134
+ };
77
135
  },
78
136
  },
79
137
  {
@@ -144,10 +202,43 @@ export function createPlanFacadeOps(runtime: AgentRuntime): OpDefinition[] {
144
202
  status: z.enum(['pending', 'in_progress', 'completed', 'skipped', 'failed']),
145
203
  }),
146
204
  handler: async (params) => {
205
+ const targetStatus = params.status as
206
+ | 'pending'
207
+ | 'in_progress'
208
+ | 'completed'
209
+ | 'skipped'
210
+ | 'failed';
211
+
212
+ // Gate check: enforce blocking post-task gates before allowing completion
213
+ if (targetStatus === 'completed') {
214
+ const currentPlan = planner.get(params.planId as string);
215
+ if (currentPlan?.playbookSessionId && runtime.playbookExecutor) {
216
+ const session = runtime.playbookExecutor.getSession(currentPlan.playbookSessionId);
217
+ if (session) {
218
+ const blockingPostTaskGates = session.gates.filter(
219
+ (g) =>
220
+ g.phase === 'post-task' &&
221
+ (g.severity === 'blocking' || g.severity === undefined),
222
+ );
223
+ if (blockingPostTaskGates.length > 0) {
224
+ return {
225
+ updated: false,
226
+ blocked: true,
227
+ reason: 'post-task gates unsatisfied',
228
+ gates: blockingPostTaskGates.map((g) => ({
229
+ checkType: g.checkType,
230
+ requirement: g.requirement,
231
+ })),
232
+ };
233
+ }
234
+ }
235
+ }
236
+ }
237
+
147
238
  const plan = planner.updateTask(
148
239
  params.planId as string,
149
240
  params.taskId as string,
150
- params.status as 'pending' | 'in_progress' | 'completed' | 'skipped' | 'failed',
241
+ targetStatus,
151
242
  );
152
243
  const task = plan.tasks.find((t) => t.id === params.taskId);
153
244
  return { updated: true, task, plan: { id: plan.id, status: plan.status } };
@@ -162,6 +253,43 @@ export function createPlanFacadeOps(runtime: AgentRuntime): OpDefinition[] {
162
253
  planId: z.string(),
163
254
  }),
164
255
  handler: async (params) => {
256
+ // Enforce playbook completion gates before allowing plan completion
257
+ const currentPlan = planner.get(params.planId as string);
258
+ if (currentPlan?.playbookSessionId && runtime.playbookExecutor) {
259
+ const completeResult = runtime.playbookExecutor.complete(
260
+ currentPlan.playbookSessionId,
261
+ {},
262
+ );
263
+ if ('error' in completeResult) {
264
+ // Session not found — executor may have been reset; proceed without gate check
265
+ } else if (!completeResult.gatesPassed && completeResult.unsatisfiedGates.length > 0) {
266
+ // Session is consumed after complete() — use the result's unsatisfied gate list
267
+ // All completion gates are blocking unless marked advisory
268
+ return {
269
+ completed: false,
270
+ blocked: true,
271
+ reason: 'completion gates unsatisfied',
272
+ unsatisfiedGates: completeResult.unsatisfiedGates,
273
+ recommendation:
274
+ 'Satisfy the listed playbook gates before completing this plan. Pass gate results via the playbook executor if checks were completed externally.',
275
+ };
276
+ }
277
+ }
278
+
279
+ // Collect unenforced playbook warnings before completing
280
+ const playbookWarnings: string[] = [];
281
+ if (currentPlan?.playbookMatch) {
282
+ if (!currentPlan.playbookSessionId) {
283
+ // Playbook was matched at create_plan time but executor session was never started
284
+ playbookWarnings.push(
285
+ `WARNING: Playbook "${currentPlan.playbookMatch.label}" was matched but its gates were never evaluated — enforcement was entirely skipped.`,
286
+ );
287
+ }
288
+ // If session existed, the blocking gate check above already ran.
289
+ // Advisory gates that were unsatisfied become warnings here (non-blocking).
290
+ // (Future: track per-gate evaluation status for richer reporting)
291
+ }
292
+
165
293
  const plan = planner.complete(params.planId as string);
166
294
  const taskSummary = {
167
295
  completed: plan.tasks.filter((t) => t.status === 'completed').length,
@@ -169,7 +297,12 @@ export function createPlanFacadeOps(runtime: AgentRuntime): OpDefinition[] {
169
297
  failed: plan.tasks.filter((t) => t.status === 'failed').length,
170
298
  total: plan.tasks.length,
171
299
  };
172
- return { completed: true, plan, taskSummary };
300
+ return {
301
+ completed: true,
302
+ plan,
303
+ taskSummary,
304
+ ...(playbookWarnings.length > 0 ? { playbookWarnings } : {}),
305
+ };
173
306
  },
174
307
  },
175
308
 
@@ -1,4 +1,4 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
1
+ import { describe, it, expect, vi } from 'vitest';
2
2
  import { createReviewFacadeOps } from './review-facade.js';
3
3
  import type { AgentRuntime } from '../types.js';
4
4
 
@@ -24,54 +24,6 @@ function mockRuntime(): AgentRuntime {
24
24
  // ---------------------------------------------------------------------------
25
25
 
26
26
  describe('createReviewFacadeOps', () => {
27
- let ops: ReturnType<typeof createReviewFacadeOps>;
28
-
29
- beforeEach(() => {
30
- vi.clearAllMocks();
31
- ops = createReviewFacadeOps(mockRuntime());
32
- });
33
-
34
- it('returns 5 ops matching review-ops', () => {
35
- expect(ops.length).toBe(5);
36
- });
37
-
38
- it('includes all expected op names', () => {
39
- const names = ops.map((o) => o.name);
40
- expect(names).toContain('vault_submit_review');
41
- expect(names).toContain('vault_approve');
42
- expect(names).toContain('vault_reject');
43
- expect(names).toContain('vault_pending_reviews');
44
- expect(names).toContain('vault_review_stats');
45
- });
46
-
47
- it('all ops have required fields', () => {
48
- for (const op of ops) {
49
- expect(op.name).toBeDefined();
50
- expect(op.description).toBeDefined();
51
- expect(op.auth).toBeDefined();
52
- expect(typeof op.handler).toBe('function');
53
- }
54
- });
55
-
56
- it('approve and reject require admin auth', () => {
57
- const approveOp = ops.find((o) => o.name === 'vault_approve');
58
- const rejectOp = ops.find((o) => o.name === 'vault_reject');
59
- expect(approveOp!.auth).toBe('admin');
60
- expect(rejectOp!.auth).toBe('admin');
61
- });
62
-
63
- it('submit uses write auth', () => {
64
- const op = ops.find((o) => o.name === 'vault_submit_review');
65
- expect(op!.auth).toBe('write');
66
- });
67
-
68
- it('pending_reviews and review_stats use read auth', () => {
69
- const pending = ops.find((o) => o.name === 'vault_pending_reviews');
70
- const stats = ops.find((o) => o.name === 'vault_review_stats');
71
- expect(pending!.auth).toBe('read');
72
- expect(stats!.auth).toBe('read');
73
- });
74
-
75
27
  it('handlers are callable', async () => {
76
28
  const rt = mockRuntime();
77
29
  const rtOps = createReviewFacadeOps(rt);
@@ -64,50 +64,33 @@ describe('createSyncFacadeOps', () => {
64
64
  ops = createSyncFacadeOps(mockRuntime());
65
65
  });
66
66
 
67
- it('returns 8 ops matching sync-ops', () => {
68
- expect(ops.length).toBe(8);
67
+ describe('vault_git_push', () => {
68
+ it('pushes vault entries and returns pushed count', async () => {
69
+ const op = ops.find((o) => o.name === 'vault_git_push')!;
70
+ const result = (await op.handler({ remote: 'origin', branch: 'main' })) as {
71
+ pushed: number;
72
+ };
73
+ expect(result.pushed).toBe(5);
74
+ });
69
75
  });
70
76
 
71
- it('includes all expected op names', () => {
72
- const names = ops.map((o) => o.name);
73
- expect(names).toContain('vault_git_push');
74
- expect(names).toContain('vault_git_pull');
75
- expect(names).toContain('vault_git_sync');
76
- expect(names).toContain('obsidian_export');
77
- expect(names).toContain('obsidian_import');
78
- expect(names).toContain('obsidian_sync');
79
- expect(names).toContain('vault_export_pack');
80
- expect(names).toContain('vault_import_pack');
77
+ describe('vault_git_pull', () => {
78
+ it('pulls from remote and returns imported count', async () => {
79
+ const op = ops.find((o) => o.name === 'vault_git_pull')!;
80
+ const result = (await op.handler({ remote: 'origin', branch: 'main' })) as {
81
+ imported: number;
82
+ conflicts: number;
83
+ };
84
+ expect(result.imported).toBe(3);
85
+ expect(result.conflicts).toBe(0);
86
+ });
81
87
  });
82
88
 
83
- it('all ops have required fields', () => {
84
- for (const op of ops) {
85
- expect(op.name).toBeDefined();
86
- expect(op.description).toBeDefined();
87
- expect(op.auth).toBeDefined();
88
- expect(typeof op.handler).toBe('function');
89
- }
90
- });
91
-
92
- it('git ops use write auth', () => {
93
- const gitOps = ops.filter((o) => o.name.startsWith('vault_git_'));
94
- for (const op of gitOps) {
95
- expect(op.auth).toBe('write');
96
- }
97
- });
98
-
99
- it('obsidian_export uses read auth', () => {
100
- const op = ops.find((o) => o.name === 'obsidian_export');
101
- expect(op!.auth).toBe('read');
102
- });
103
-
104
- it('vault_export_pack uses read auth', () => {
105
- const op = ops.find((o) => o.name === 'vault_export_pack');
106
- expect(op!.auth).toBe('read');
107
- });
108
-
109
- it('vault_import_pack uses write auth', () => {
110
- const op = ops.find((o) => o.name === 'vault_import_pack');
111
- expect(op!.auth).toBe('write');
89
+ describe('obsidian_export', () => {
90
+ it('exports entries and returns exported count', async () => {
91
+ const op = ops.find((o) => o.name === 'obsidian_export')!;
92
+ const result = (await op.handler({ vaultPath: '/tmp/obsidian' })) as { exported: number };
93
+ expect(result.exported).toBe(10);
94
+ });
112
95
  });
113
96
  });
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Colocated contract tests for tier-facade.ts.
3
- * Verifies the facade wrapper delegates to tier-ops.
4
3
  */
5
4
 
6
5
  import { describe, it, expect, vi } from 'vitest';
@@ -12,36 +11,45 @@ function makeRuntime(): AgentRuntime {
12
11
  vaultManager: {
13
12
  open: vi.fn(),
14
13
  disconnect: vi.fn().mockReturnValue(true),
15
- listTiers: vi.fn().mockReturnValue([]),
16
- search: vi.fn().mockReturnValue([]),
14
+ listTiers: vi.fn().mockReturnValue([{ name: 'agent', count: 10 }]),
15
+ search: vi.fn().mockReturnValue([{ entry: { id: 'e1' }, score: 0.9 }]),
17
16
  connect: vi.fn(),
18
17
  disconnectNamed: vi.fn().mockReturnValue(true),
19
- listConnected: vi.fn().mockReturnValue([]),
18
+ listConnected: vi.fn().mockReturnValue([{ name: 'team-vault', priority: 2 }]),
20
19
  },
21
20
  config: { agentId: 'test-agent' },
22
21
  } as unknown as AgentRuntime;
23
22
  }
24
23
 
25
24
  describe('tier-facade', () => {
26
- it('returns all 7 tier/source ops', () => {
27
- const ops = createTierFacadeOps(makeRuntime());
28
- expect(ops).toHaveLength(7);
29
- const names = ops.map((o) => o.name);
30
- expect(names).toContain('vault_connect');
31
- expect(names).toContain('vault_disconnect');
32
- expect(names).toContain('vault_tiers');
33
- expect(names).toContain('vault_search_all');
34
- expect(names).toContain('vault_connect_source');
35
- expect(names).toContain('vault_disconnect_source');
36
- expect(names).toContain('vault_list_sources');
25
+ describe('vault_tiers', () => {
26
+ it('returns all vault tiers', async () => {
27
+ const ops = createTierFacadeOps(makeRuntime());
28
+ const op = ops.find((o) => o.name === 'vault_tiers')!;
29
+ const result = (await op.handler({})) as { tiers: unknown[] };
30
+ expect(result.tiers).toHaveLength(1);
31
+ });
37
32
  });
38
33
 
39
- it('every op has name, handler, and auth', () => {
40
- const ops = createTierFacadeOps(makeRuntime());
41
- for (const op of ops) {
42
- expect(typeof op.name).toBe('string');
43
- expect(typeof op.handler).toBe('function');
44
- expect(typeof op.auth).toBe('string');
45
- }
34
+ describe('vault_search_all', () => {
35
+ it('returns search results with count', async () => {
36
+ const ops = createTierFacadeOps(makeRuntime());
37
+ const op = ops.find((o) => o.name === 'vault_search_all')!;
38
+ const result = (await op.handler({ query: 'test', limit: 5 })) as {
39
+ results: unknown[];
40
+ count: number;
41
+ };
42
+ expect(result.count).toBe(1);
43
+ expect(result.results).toHaveLength(1);
44
+ });
45
+ });
46
+
47
+ describe('vault_list_sources', () => {
48
+ it('returns connected sources', async () => {
49
+ const ops = createTierFacadeOps(makeRuntime());
50
+ const op = ops.find((o) => o.name === 'vault_list_sources')!;
51
+ const result = (await op.handler({})) as { sources: unknown[] };
52
+ expect(result.sources).toHaveLength(1);
53
+ });
46
54
  });
47
55
  });
@@ -164,47 +164,6 @@ describe('vault-facade', () => {
164
164
  ops = captureOps(createVaultFacadeOps(runtime));
165
165
  });
166
166
 
167
- // ─── Registration ─────────────────────────────────────────────────
168
-
169
- it('registers ops from all groups', () => {
170
- // The facade includes inline ops + satellite ops from 5 modules.
171
- // We check the inline ops explicitly, satellite ops just need to exist.
172
- expect(ops.size).toBeGreaterThan(20);
173
- });
174
-
175
- it('includes core inline op names', () => {
176
- const coreOps = [
177
- 'search',
178
- 'load_entries',
179
- 'vault_stats',
180
- 'list_all',
181
- 'export',
182
- 'capture_enriched',
183
- ];
184
- for (const name of coreOps) {
185
- expect(ops.has(name), `missing op: ${name}`).toBe(true);
186
- }
187
- });
188
-
189
- it('includes satellite ops', () => {
190
- // Spot-check a few ops from each satellite module
191
- expect(ops.has('vault_get')).toBe(true); // vault-extra-ops
192
- expect(ops.has('capture_knowledge')).toBe(true); // capture-ops
193
- expect(ops.has('search_intelligent')).toBe(true); // capture-ops
194
- // link_entries moved to vault-linking-facade
195
- });
196
-
197
- // ─── Auth levels ─────────────────────────────────────────────────
198
-
199
- it('has correct auth levels for inline ops', () => {
200
- expect(ops.get('search')!.auth).toBe('read');
201
- expect(ops.get('load_entries')!.auth).toBe('read');
202
- expect(ops.get('vault_stats')!.auth).toBe('read');
203
- expect(ops.get('list_all')!.auth).toBe('read');
204
- expect(ops.get('export')!.auth).toBe('read');
205
- expect(ops.get('capture_enriched')!.auth).toBe('write');
206
- });
207
-
208
167
  // ─── search ────────────────────────────────────────────────────────
209
168
 
210
169
  describe('search', () => {
@@ -66,13 +66,36 @@ export function createVaultFacadeOps(runtime: AgentRuntime): OpDefinition[] {
66
66
  {
67
67
  name: 'load_entries',
68
68
  description:
69
- 'Two-pass retrieval — Pass 2: Load full entries by IDs (from a previous scan search).',
69
+ 'Two-pass retrieval — Pass 2: Load full entries by IDs (from a previous scan search). ' +
70
+ 'Alternative: pass domain + limit to list entries by domain without a prior scan.',
70
71
  auth: 'read',
71
72
  schema: z.object({
72
- ids: z.array(z.string()).min(1).describe('Entry IDs from a previous scan search'),
73
+ ids: z
74
+ .array(z.string())
75
+ .min(1)
76
+ .optional()
77
+ .describe('Entry IDs from a previous scan search'),
78
+ domain: z.string().optional().describe('Alternative to ids: filter entries by domain'),
79
+ limit: z
80
+ .number()
81
+ .optional()
82
+ .default(20)
83
+ .describe('Max entries when using domain filter (default: 20)'),
73
84
  }),
74
85
  handler: async (params) => {
75
- return brain.loadEntries(params.ids as string[]);
86
+ const ids = params.ids as string[] | undefined;
87
+ if (ids && ids.length > 0) {
88
+ return brain.loadEntries(ids);
89
+ }
90
+ const domain = params.domain as string | undefined;
91
+ if (domain) {
92
+ const entries = vault.list({ domain, limit: (params.limit as number) ?? 20 });
93
+ return { entries, total: entries.length };
94
+ }
95
+ throw new Error(
96
+ 'Provide ids (from search_intelligent scan) or domain to filter entries. ' +
97
+ 'Example: { ids: ["entry-id-1"] } or { domain: "testing", limit: 10 }',
98
+ );
76
99
  },
77
100
  },
78
101
  {
@@ -52,33 +52,6 @@ describe('createGradingOps', () => {
52
52
  return op;
53
53
  }
54
54
 
55
- it('returns 5 ops', () => {
56
- runtime = makeMockRuntime();
57
- ops = createGradingOps(runtime);
58
- expect(ops).toHaveLength(5);
59
- });
60
-
61
- it('has correct op names', () => {
62
- runtime = makeMockRuntime();
63
- ops = createGradingOps(runtime);
64
- const names = ops.map((o) => o.name);
65
- expect(names).toEqual([
66
- 'plan_grade',
67
- 'plan_check_history',
68
- 'plan_latest_check',
69
- 'plan_meets_grade',
70
- 'plan_auto_improve',
71
- ]);
72
- });
73
-
74
- it('all ops have read auth', () => {
75
- runtime = makeMockRuntime();
76
- ops = createGradingOps(runtime);
77
- for (const op of ops) {
78
- expect(op.auth).toBe('read');
79
- }
80
- });
81
-
82
55
  describe('plan_grade', () => {
83
56
  it('delegates to planner.grade', async () => {
84
57
  runtime = makeMockRuntime();
@@ -28,25 +28,6 @@ describe('createIntakeOps', () => {
28
28
  return op;
29
29
  }
30
30
 
31
- it('returns 7 ops', () => {
32
- const ops = createIntakeOps(makeMockPipeline() as never);
33
- expect(ops).toHaveLength(7);
34
- });
35
-
36
- it('has all expected op names', () => {
37
- const ops = createIntakeOps(makeMockPipeline() as never);
38
- const names = ops.map((o) => o.name);
39
- expect(names).toEqual([
40
- 'intake_ingest_book',
41
- 'intake_process',
42
- 'intake_status',
43
- 'intake_preview',
44
- 'ingest_url',
45
- 'ingest_text',
46
- 'ingest_batch',
47
- ]);
48
- });
49
-
50
31
  describe('when pipeline is null', () => {
51
32
  it('intake_ingest_book returns error', async () => {
52
33
  const ops = createIntakeOps(null);
@@ -50,29 +50,6 @@ describe('createLoopOps', () => {
50
50
  return op;
51
51
  }
52
52
 
53
- it('returns 9 ops', () => {
54
- runtime = makeMockRuntime();
55
- ops = createLoopOps(runtime);
56
- expect(ops).toHaveLength(9);
57
- });
58
-
59
- it('has all expected op names', () => {
60
- runtime = makeMockRuntime();
61
- ops = createLoopOps(runtime);
62
- const names = ops.map((o) => o.name);
63
- expect(names).toEqual([
64
- 'loop_start',
65
- 'loop_iterate',
66
- 'loop_iterate_gate',
67
- 'loop_status',
68
- 'loop_cancel',
69
- 'loop_history',
70
- 'loop_is_active',
71
- 'loop_complete',
72
- 'loop_anomaly_check',
73
- ]);
74
- });
75
-
76
53
  describe('loop_start', () => {
77
54
  it('starts a loop with defaults for custom mode', async () => {
78
55
  runtime = makeMockRuntime();
@@ -125,24 +102,6 @@ describe('createLoopOps', () => {
125
102
  });
126
103
  });
127
104
 
128
- describe('loop_iterate_gate', () => {
129
- it('delegates to loop.iterateWithGate', async () => {
130
- runtime = makeMockRuntime();
131
- ops = createLoopOps(runtime);
132
- const result = await findOp('loop_iterate_gate').handler({
133
- lastOutput: 'some output',
134
- knowledge: { items: ['learned something'] },
135
- durationMs: 5000,
136
- });
137
- expect(runtime.loop.iterateWithGate).toHaveBeenCalledWith(
138
- 'some output',
139
- { items: ['learned something'] },
140
- 5000,
141
- );
142
- expect(result).toEqual({ decision: 'block', reason: 'continue' });
143
- });
144
- });
145
-
146
105
  describe('loop_status', () => {
147
106
  it('returns active loop status', async () => {
148
107
  runtime = makeMockRuntime();
@@ -187,13 +146,6 @@ describe('createLoopOps', () => {
187
146
  });
188
147
 
189
148
  describe('loop_is_active', () => {
190
- it('returns active true', async () => {
191
- runtime = makeMockRuntime();
192
- ops = createLoopOps(runtime);
193
- const result = (await findOp('loop_is_active').handler({})) as Record<string, unknown>;
194
- expect(result.active).toBe(true);
195
- });
196
-
197
149
  it('returns active false when no loop', async () => {
198
150
  runtime = makeMockRuntime();
199
151
  (runtime.loop.isActive as ReturnType<typeof vi.fn>).mockReturnValue(false);
@@ -40,20 +40,6 @@ describe('createMemoryCrossProjectOps', () => {
40
40
  return op;
41
41
  }
42
42
 
43
- it('returns 3 ops', () => {
44
- ops = createMemoryCrossProjectOps(makeMockRuntime());
45
- expect(ops).toHaveLength(3);
46
- });
47
-
48
- it('has correct op names', () => {
49
- ops = createMemoryCrossProjectOps(makeMockRuntime());
50
- expect(ops.map((o) => o.name)).toEqual([
51
- 'memory_promote_to_global',
52
- 'memory_configure',
53
- 'memory_cross_project_search',
54
- ]);
55
- });
56
-
57
43
  describe('memory_promote_to_global', () => {
58
44
  it('promotes entry by adding _global tag', async () => {
59
45
  const runtime = makeMockRuntime();
@@ -73,10 +73,6 @@ describe('createMemoryExtraOps', () => {
73
73
  ops = createMemoryExtraOps(rt);
74
74
  });
75
75
 
76
- it('returns all 18 ops', () => {
77
- expect(ops.length).toBe(18);
78
- });
79
-
80
76
  // ─── memory_delete ────────────────────────────────────────────
81
77
 
82
78
  describe('memory_delete', () => {
@@ -249,8 +245,8 @@ describe('createMemoryExtraOps', () => {
249
245
  it('returns results without archived by default', async () => {
250
246
  const op = findOp(ops, 'session_search');
251
247
  vi.mocked(rt.vault.searchMemories).mockReturnValue([{ id: 's1' }] as never);
252
- const result = (await op.handler({ query: 'test', limit: 10 })) as Record<string, unknown>;
253
- expect(result.results).toBeDefined();
248
+ const result = (await op.handler({ query: 'test', limit: 10 })) as { results: unknown[] };
249
+ expect(result.results).toHaveLength(1); // mock returns exactly one session result
254
250
  });
255
251
 
256
252
  it('includes archived when includeArchived is true', async () => {
@@ -264,8 +260,8 @@ describe('createMemoryExtraOps', () => {
264
260
  includeArchived: true,
265
261
  limit: 10,
266
262
  })) as Record<string, unknown>;
267
- expect(result.active).toBeDefined();
268
- expect(result.archived).toBeDefined();
263
+ expect((result as { active: unknown[] }).active).toHaveLength(1); // mock searchMemories returns 1
264
+ expect((result as { archived: unknown[] }).archived).toHaveLength(1); // mock provider.all returns 1
269
265
  });
270
266
  });
271
267