@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
@@ -75,6 +75,11 @@ export const flowSchema = z.object({
75
75
  'min-confidence': z.enum(['HIGH', 'MEDIUM', 'LOW']).optional(),
76
76
  }),
77
77
  steps: z.array(flowStepSchema),
78
+ /**
79
+ * Scoring weights declared per step — parsed but not yet computed by the executor.
80
+ * Weighted-sum formula is not implemented; gate thresholds in steps are the active enforcement.
81
+ * @see https://github.com/adrozdenko/soleri/issues/632
82
+ */
78
83
  scoring: z
79
84
  .object({
80
85
  weights: z.record(z.number()),
@@ -100,7 +105,7 @@ export const flowSchema = z.object({
100
105
  })
101
106
  .optional(),
102
107
  /** Strategy when a step's capability requirement is not satisfied */
103
- onMissingCapability: z
108
+ 'on-missing-capability': z
104
109
  .object({
105
110
  default: z.enum(['skip-with-warning', 'fail', 'ask-user']).default('skip-with-warning'),
106
111
  blocking: z.array(z.string()).optional().default([]),
@@ -130,7 +135,8 @@ export type ProbeName =
130
135
  | 'designSystem'
131
136
  | 'sessionStore'
132
137
  | 'projectRules'
133
- | 'active';
138
+ | 'active'
139
+ | 'test';
134
140
 
135
141
  export interface ProbeResults {
136
142
  vault: boolean;
@@ -139,6 +145,7 @@ export interface ProbeResults {
139
145
  sessionStore: boolean;
140
146
  projectRules: boolean;
141
147
  active: boolean;
148
+ test: boolean;
142
149
  }
143
150
 
144
151
  // ---------------------------------------------------------------------------
@@ -158,6 +165,8 @@ export interface PlanStep {
158
165
  min?: number;
159
166
  onFail?: { action: string; goto?: string; message?: string };
160
167
  };
168
+ /** Output keys this step produces — merged into stepContext for subsequent steps. */
169
+ output?: string[];
161
170
  status:
162
171
  | 'pending'
163
172
  | 'running'
@@ -175,6 +184,22 @@ export interface SkippedStep {
175
184
  reason: string;
176
185
  }
177
186
 
187
+ /**
188
+ * A vault knowledge entry surfaced as a planning constraint.
189
+ * Replaces gate-step injection — constraints are carried as metadata
190
+ * so the executor can apply judgment rather than mechanical evaluation.
191
+ */
192
+ export interface VaultRecommendation {
193
+ entryId: string;
194
+ title: string;
195
+ context?: string;
196
+ example?: string;
197
+ mandatory: boolean;
198
+ entryType?: 'pattern' | 'anti-pattern' | 'rule' | 'playbook';
199
+ source: 'vault';
200
+ strength: number;
201
+ }
202
+
178
203
  export interface ToolDeviation {
179
204
  stepId: string;
180
205
  expectedTools: string[];
@@ -198,6 +223,10 @@ export interface OrchestrationPlan {
198
223
  workflowPrompt?: string;
199
224
  /** Name of the matched workflow */
200
225
  workflowName?: string;
226
+ /** True when a blocking capability is unavailable — plan cannot run */
227
+ blocked?: boolean;
228
+ /** Vault knowledge constraints relevant to this plan — executor reads these as context */
229
+ recommendations?: VaultRecommendation[];
201
230
  }
202
231
 
203
232
  export interface OrchestrationContext {
@@ -21,6 +21,7 @@ describe('HealthRegistry', () => {
21
21
  });
22
22
 
23
23
  it('tracks transitions: healthy -> degraded -> healthy', () => {
24
+ const before = Date.now();
24
25
  const reg = new HealthRegistry();
25
26
  reg.register('svc');
26
27
  reg.update('svc', 'degraded', 'timeout');
@@ -31,7 +32,8 @@ describe('HealthRegistry', () => {
31
32
  reg.update('svc', 'healthy');
32
33
  expect(reg.get('svc')!.failureCount).toBe(0);
33
34
  expect(reg.get('svc')!.lastError).toBeNull();
34
- expect(reg.get('svc')!.lastHealthyAt).toBeGreaterThan(0);
35
+ expect(reg.get('svc')!.lastHealthyAt).toBeGreaterThanOrEqual(before);
36
+ expect(reg.get('svc')!.lastHealthyAt).toBeLessThanOrEqual(Date.now());
35
37
  });
36
38
 
37
39
  it('auto-registers on update if not previously registered', () => {
package/src/index.ts CHANGED
@@ -18,6 +18,8 @@ export { WorkspaceResolver } from './subagent/workspace-resolver.js';
18
18
  export { ConcurrencyManager } from './subagent/concurrency-manager.js';
19
19
  export { OrphanReaper } from './subagent/orphan-reaper.js';
20
20
  export type { ReapResult } from './subagent/orphan-reaper.js';
21
+ export { worktreeReap, worktreeStatus } from './utils/worktree-reaper.js';
22
+ export type { ReapReport, WorktreeStatus } from './utils/worktree-reaper.js';
21
23
  export { aggregate as aggregateResults } from './subagent/result-aggregator.js';
22
24
  export type {
23
25
  SubagentTask,
@@ -956,3 +958,18 @@ export type { WorkflowGate, WorkflowOverride } from './workflows/index.js';
956
958
  // ─── Update Check ────────────────────────────────────────────────────
957
959
  export { checkForUpdate, buildChangelogUrl, detectBreakingChanges } from './update-check.js';
958
960
  export type { UpdateInfo } from './update-check.js';
961
+
962
+ // ─── Settings Hooks Sync ─────────────────────────────────────────────
963
+ export { syncHooksToClaudeSettings } from './runtime/admin-setup-ops.js';
964
+
965
+ // ─── Scheduler ───────────────────────────────────────────────────────
966
+ export { Scheduler, InMemorySchedulerStore } from './scheduler/scheduler.js';
967
+ export type { SchedulerStore } from './scheduler/scheduler.js';
968
+ export { createSchedulerOps } from './scheduler/scheduler-ops.js';
969
+ export { validateCron, estimateMinIntervalHours } from './scheduler/cron-validator.js';
970
+ export type {
971
+ ScheduledTask,
972
+ CreateTaskInput,
973
+ TaskListEntry,
974
+ PlatformAdapter,
975
+ } from './scheduler/types.js';
@@ -135,13 +135,9 @@ describe('dedupItems — colocated', () => {
135
135
  const results = dedupItems(items, vault);
136
136
 
137
137
  expect(results).toHaveLength(2);
138
- // First should be duplicate or at least high similarity
139
- expect(results[0].similarity).toBeGreaterThan(0.5);
138
+ // First should be a high-similarity match (near-exact duplicate)
139
+ expect(results[0].similarity).toBeGreaterThanOrEqual(DEDUP_THRESHOLD);
140
140
  // Second should be non-duplicate
141
141
  expect(results[1].isDuplicate).toBe(false);
142
142
  });
143
-
144
- it('DEDUP_THRESHOLD is 0.85', () => {
145
- expect(DEDUP_THRESHOLD).toBe(0.85);
146
- });
147
143
  });
@@ -86,7 +86,7 @@ describe('TextIngester — ingestText', () => {
86
86
  expect(result.duplicates).toBe(0);
87
87
  expect(result.entries).toHaveLength(1);
88
88
  expect(result.entries[0].title).toBe('Pattern A');
89
- expect(vault._seeded.length).toBeGreaterThan(0);
89
+ expect(vault._seeded.length).toBe(1);
90
90
  });
91
91
 
92
92
  it('returns empty result when LLM is null', async () => {
@@ -120,8 +120,7 @@ describe('TextIngester — ingestText', () => {
120
120
  const result = await ingester.ingestText('text', { type: 'documentation', title: 'Doc' }, opts);
121
121
 
122
122
  expect(result.ingested).toBe(1);
123
- // Seeded entries should exist
124
- expect(vault._seeded.length).toBeGreaterThan(0);
123
+ expect(vault._seeded.length).toBe(1);
125
124
  });
126
125
 
127
126
  it('splits long text into chunks based on chunkSize option', async () => {
@@ -155,7 +154,7 @@ describe('TextIngester — ingestText', () => {
155
154
  const longText = 'A'.repeat(100);
156
155
  await ingester.ingestText(longText, { type: 'notes', title: 'Test' }, { chunkSize: 30 });
157
156
 
158
- expect(callCount.n).toBeGreaterThan(1);
157
+ expect(callCount.n).toBe(4);
159
158
  });
160
159
  });
161
160
 
@@ -119,7 +119,7 @@ describe('LLMClient — colocated', () => {
119
119
  expect(result.provider).toBe('openai');
120
120
  expect(result.inputTokens).toBe(10);
121
121
  expect(result.outputTokens).toBe(5);
122
- expect(result.durationMs).toBeGreaterThanOrEqual(0);
122
+ expect(typeof result.durationMs).toBe('number');
123
123
  } finally {
124
124
  globalThis.fetch = originalFetch;
125
125
  }
@@ -224,7 +224,7 @@ describe('computeDelay', () => {
224
224
  it('should never return negative delay', () => {
225
225
  vi.spyOn(Math, 'random').mockReturnValue(0);
226
226
  const config = { maxAttempts: 3, baseDelayMs: 1, maxDelayMs: 30000, jitter: 1 };
227
- expect(computeDelay({}, 0, config)).toBeGreaterThanOrEqual(0);
227
+ expect(typeof computeDelay({}, 0, config)).toBe('number');
228
228
  vi.restoreAllMocks();
229
229
  });
230
230
  });
@@ -137,7 +137,6 @@ describe('MigrationRunner', () => {
137
137
 
138
138
  // Assert
139
139
  expect(typeof results[0].durationMs).toBe('number');
140
- expect(results[0].durationMs).toBeGreaterThanOrEqual(0);
141
140
  });
142
141
  });
143
142
 
@@ -51,19 +51,6 @@ describe('OperatorContextStore', () => {
51
51
  vault.close();
52
52
  });
53
53
 
54
- // ─── Table Creation ─────────────────────────────────────────────────
55
-
56
- describe('init', () => {
57
- it('creates table without error on a fresh database', () => {
58
- expect(store).toBeDefined();
59
- });
60
-
61
- it('is idempotent — second init does not throw', () => {
62
- const store2 = new OperatorContextStore(vault.getProvider());
63
- expect(store2).toBeDefined();
64
- });
65
- });
66
-
67
54
  // ─── Empty State ──────────────────────────────────────────────────
68
55
 
69
56
  describe('getContext (empty)', () => {
@@ -47,15 +47,6 @@ describe('OperatorProfileStore', () => {
47
47
  vault.close();
48
48
  });
49
49
 
50
- // ─── Table Creation ─────────────────────────────────────────────
51
-
52
- it('creates tables without error on new runtime', () => {
53
- // Constructor ran initTables — no error means tables exist.
54
- // Creating a second instance also succeeds (IF NOT EXISTS).
55
- const store2 = new OperatorProfileStore(vault);
56
- expect(store2).toBeDefined();
57
- });
58
-
59
50
  // ─── getProfile ─────────────────────────────────────────────────
60
51
 
61
52
  it('returns null when no profile exists', () => {
@@ -186,8 +177,8 @@ describe('OperatorProfileStore', () => {
186
177
  'SELECT trigger, version FROM operator_profile_history WHERE profile_id = ?',
187
178
  [profileBefore!.id],
188
179
  );
189
- expect(history.length).toBeGreaterThanOrEqual(1);
190
- expect(history.some((h) => h.trigger === 'correction')).toBe(true);
180
+ expect(history.length).toBe(1);
181
+ expect(history[0].trigger).toBe('correction');
191
182
  });
192
183
 
193
184
  // ─── snapshot ───────────────────────────────────────────────────
@@ -320,13 +311,4 @@ describe('OperatorProfileStore', () => {
320
311
  expect(section.signalWords).toContain('please');
321
312
  expect(section.adaptationRules).toHaveLength(1);
322
313
  });
323
-
324
- // ─── No `any` types ────────────────────────────────────────────
325
-
326
- it('type system enforced — file compiles with strict TypeScript', () => {
327
- // This test is a compile-time assertion:
328
- // if operator-profile.ts had `any` types, tsc --noEmit would catch it.
329
- // The fact that this test file compiles is the proof.
330
- expect(true).toBe(true);
331
- });
332
314
  });
@@ -9,8 +9,9 @@
9
9
  * 5. Return install summary
10
10
  */
11
11
 
12
- import { existsSync, readdirSync, readFileSync } from 'node:fs';
12
+ import { existsSync, readdirSync, readFileSync, rmSync } from 'node:fs';
13
13
  import { join, basename } from 'node:path';
14
+ import { homedir } from 'node:os';
14
15
  import {
15
16
  packManifestSchema,
16
17
  type InstalledPack,
@@ -200,10 +201,19 @@ export class PackInstaller {
200
201
  facadesRegistered = true;
201
202
  }
202
203
 
203
- // 3. Discover skills
204
+ // 3. Discover skills and sync to .claude/skills/<packId>:<skill>/
204
205
  const skillsDir = join(packDir, manifest.skills?.dir ?? 'skills');
205
206
  const skills = existsSync(skillsDir) ? listMarkdownFiles(skillsDir) : [];
206
207
 
208
+ if (skills.length > 0) {
209
+ try {
210
+ const { syncSkillsToClaudeCode } = await import('../skills/sync-skills.js');
211
+ syncSkillsToClaudeCode([skillsDir], manifest.id, { global: true });
212
+ } catch {
213
+ // Skill sync is best-effort — never blocks install
214
+ }
215
+ }
216
+
207
217
  // 4. Discover hooks
208
218
  const hooksDir = join(packDir, manifest.hooks?.dir ?? 'hooks');
209
219
  const hooks = existsSync(hooksDir) ? listMarkdownFiles(hooksDir) : [];
@@ -281,6 +291,22 @@ export class PackInstaller {
281
291
  this.pluginRegistry.deactivate(packId);
282
292
  }
283
293
 
294
+ // Remove pack skills from .claude/skills/
295
+ if (pack.skills.length > 0) {
296
+ try {
297
+ const claudeSkillsDir = join(homedir(), '.claude', 'skills');
298
+ for (const skillPath of pack.skills) {
299
+ const skillName = basename(skillPath, '.md');
300
+ const registeredPath = join(claudeSkillsDir, `${packId}:${skillName}`);
301
+ if (existsSync(registeredPath)) {
302
+ rmSync(registeredPath, { recursive: true, force: true });
303
+ }
304
+ }
305
+ } catch {
306
+ // Skill cleanup is best-effort
307
+ }
308
+ }
309
+
284
310
  // Transition to uninstalled
285
311
  try {
286
312
  this.lifecycle.transition(packId, 'uninstalled', 'User uninstall');
@@ -233,7 +233,7 @@ describe('PackInstaller', () => {
233
233
 
234
234
  const result = installer.validate(packDir);
235
235
  expect(result.valid).toBe(true);
236
- expect(result.warnings.length).toBeGreaterThan(0);
236
+ expect(result.warnings.length).toBe(2);
237
237
  });
238
238
  });
239
239
 
@@ -271,7 +271,7 @@ describe('PackInstaller', () => {
271
271
 
272
272
  // Verify vault was seeded
273
273
  const entries = vault.list({ domain: 'test' });
274
- expect(entries.length).toBeGreaterThan(0);
274
+ expect(entries.length).toBe(1);
275
275
  });
276
276
 
277
277
  it('should install a pack with facades', async () => {
@@ -12,21 +12,21 @@ describe('ITALIAN_CRAFTSPERSON', () => {
12
12
  });
13
13
 
14
14
  it('has non-empty voice, culture, and inspiration', () => {
15
- expect(ITALIAN_CRAFTSPERSON.voice.length).toBeGreaterThan(0);
15
+ expect(ITALIAN_CRAFTSPERSON.voice).toContain('Italian mentor');
16
16
  expect(ITALIAN_CRAFTSPERSON.culture).toBe('Italian');
17
- expect(ITALIAN_CRAFTSPERSON.inspiration.length).toBeGreaterThan(0);
17
+ expect(ITALIAN_CRAFTSPERSON.inspiration).toContain('Paolo Soleri');
18
18
  });
19
19
 
20
20
  it('provides greetings and signoffs pools', () => {
21
- expect(ITALIAN_CRAFTSPERSON.greetings.length).toBeGreaterThan(0);
22
- expect(ITALIAN_CRAFTSPERSON.signoffs.length).toBeGreaterThan(0);
21
+ expect(ITALIAN_CRAFTSPERSON.greetings).toHaveLength(5);
22
+ expect(ITALIAN_CRAFTSPERSON.signoffs).toHaveLength(5);
23
23
  });
24
24
 
25
25
  it('includes expected trait and quirk arrays', () => {
26
- expect(ITALIAN_CRAFTSPERSON.traits.length).toBeGreaterThan(0);
27
- expect(ITALIAN_CRAFTSPERSON.quirks.length).toBeGreaterThan(0);
28
- expect(ITALIAN_CRAFTSPERSON.metaphors.length).toBeGreaterThan(0);
29
- expect(ITALIAN_CRAFTSPERSON.opinions.length).toBeGreaterThan(0);
26
+ expect(ITALIAN_CRAFTSPERSON.traits).toHaveLength(7);
27
+ expect(ITALIAN_CRAFTSPERSON.quirks).toHaveLength(6);
28
+ expect(ITALIAN_CRAFTSPERSON.metaphors).toHaveLength(8);
29
+ expect(ITALIAN_CRAFTSPERSON.opinions).toHaveLength(6);
30
30
  });
31
31
  });
32
32
 
@@ -36,26 +36,26 @@ describe('NEUTRAL_PERSONA', () => {
36
36
  });
37
37
 
38
38
  it('has non-empty voice and inspiration', () => {
39
- expect(NEUTRAL_PERSONA.voice.length).toBeGreaterThan(0);
40
- expect(NEUTRAL_PERSONA.inspiration.length).toBeGreaterThan(0);
39
+ expect(NEUTRAL_PERSONA.voice).toContain('helpful assistant');
40
+ expect(NEUTRAL_PERSONA.inspiration).toContain('reliable professional');
41
41
  });
42
42
 
43
43
  it('has no cultural flavor', () => {
44
44
  expect(NEUTRAL_PERSONA.culture).toBe('');
45
45
  });
46
46
 
47
- it('has all arrays populated with minimum counts', () => {
48
- expect(NEUTRAL_PERSONA.traits.length).toBeGreaterThanOrEqual(3);
49
- expect(NEUTRAL_PERSONA.quirks.length).toBeGreaterThanOrEqual(3);
50
- expect(NEUTRAL_PERSONA.opinions.length).toBeGreaterThanOrEqual(3);
51
- expect(NEUTRAL_PERSONA.metaphors.length).toBeGreaterThanOrEqual(3);
52
- expect(NEUTRAL_PERSONA.greetings.length).toBeGreaterThanOrEqual(2);
53
- expect(NEUTRAL_PERSONA.signoffs.length).toBeGreaterThanOrEqual(2);
47
+ it('has all arrays populated with exact counts', () => {
48
+ expect(NEUTRAL_PERSONA.traits).toHaveLength(6);
49
+ expect(NEUTRAL_PERSONA.quirks).toHaveLength(4);
50
+ expect(NEUTRAL_PERSONA.opinions).toHaveLength(6);
51
+ expect(NEUTRAL_PERSONA.metaphors).toHaveLength(5);
52
+ expect(NEUTRAL_PERSONA.greetings).toHaveLength(3);
53
+ expect(NEUTRAL_PERSONA.signoffs).toHaveLength(3);
54
54
  });
55
55
 
56
56
  it('has non-empty language and name rules', () => {
57
- expect(NEUTRAL_PERSONA.languageRule.length).toBeGreaterThan(0);
58
- expect(NEUTRAL_PERSONA.nameRule.length).toBeGreaterThan(0);
57
+ expect(NEUTRAL_PERSONA.languageRule).toContain("user's language");
58
+ expect(NEUTRAL_PERSONA.nameRule).toContain('name changes');
59
59
  });
60
60
  });
61
61
 
@@ -10,12 +10,6 @@ import {
10
10
  analyzeSemanticQuality,
11
11
  analyzeKnowledgeDepth,
12
12
  analyzeAlternatives,
13
- AMBIGUOUS_WORDS,
14
- GENERIC_OBJECTIVE_PATTERNS,
15
- RATIONALE_INDICATORS,
16
- SHALLOW_INDICATORS,
17
- KNOWLEDGE_INDICATORS,
18
- NAMED_PATTERN_REGEX,
19
13
  } from './gap-passes.js';
20
14
 
21
15
  function makePlan(overrides: Partial<Plan> = {}): Plan {
@@ -77,46 +71,6 @@ function makeAlternative(overrides: Partial<PlanAlternative> = {}): PlanAlternat
77
71
  };
78
72
  }
79
73
 
80
- describe('Pattern constants (passes 5-8)', () => {
81
- it('AMBIGUOUS_WORDS contains common vague terms', () => {
82
- expect(AMBIGUOUS_WORDS).toContain('maybe');
83
- expect(AMBIGUOUS_WORDS).toContain('probably');
84
- expect(AMBIGUOUS_WORDS).toContain('etc');
85
- });
86
-
87
- it('GENERIC_OBJECTIVE_PATTERNS matches simple verb-noun objectives', () => {
88
- expect(GENERIC_OBJECTIVE_PATTERNS.some((p) => p.test('Create something'))).toBe(true);
89
- expect(GENERIC_OBJECTIVE_PATTERNS.some((p) => p.test('Fix bug'))).toBe(true);
90
- });
91
-
92
- it('GENERIC_OBJECTIVE_PATTERNS does not match detailed objectives', () => {
93
- expect(
94
- GENERIC_OBJECTIVE_PATTERNS.some((p) => p.test('Create a user auth module with JWT')),
95
- ).toBe(false);
96
- });
97
-
98
- it('RATIONALE_INDICATORS contains reasoning words', () => {
99
- expect(RATIONALE_INDICATORS).toContain('because');
100
- expect(RATIONALE_INDICATORS).toContain('due to');
101
- });
102
-
103
- it('SHALLOW_INDICATORS contains subjective words', () => {
104
- expect(SHALLOW_INDICATORS).toContain('better');
105
- expect(SHALLOW_INDICATORS).toContain('good');
106
- });
107
-
108
- it('KNOWLEDGE_INDICATORS match domain patterns', () => {
109
- expect(KNOWLEDGE_INDICATORS.some((p) => p.test('vault pattern'))).toBe(true);
110
- expect(KNOWLEDGE_INDICATORS.some((p) => p.test('WCAG 2.1'))).toBe(true);
111
- expect(KNOWLEDGE_INDICATORS.some((p) => p.test('aria-label'))).toBe(true);
112
- });
113
-
114
- it('NAMED_PATTERN_REGEX matches hyphenated identifiers', () => {
115
- expect(NAMED_PATTERN_REGEX.test('zod-form-validation')).toBe(true);
116
- expect(NAMED_PATTERN_REGEX.test('simple')).toBe(false);
117
- });
118
- });
119
-
120
74
  describe('Pass 5: Clarity', () => {
121
75
  it('flags ambiguous language in objective', () => {
122
76
  const plan = makePlan({
@@ -14,13 +14,6 @@ import {
14
14
  analyzeCompleteness,
15
15
  analyzeFeasibility,
16
16
  analyzeRisk,
17
- METRIC_PATTERNS,
18
- EXCLUSION_KEYWORDS,
19
- OVERLY_BROAD_PATTERNS,
20
- DEPENDENCY_KEYWORDS,
21
- BREAKING_CHANGE_KEYWORDS,
22
- MITIGATION_KEYWORDS,
23
- VERIFICATION_KEYWORDS,
24
17
  } from './gap-patterns.js';
25
18
 
26
19
  function makePlan(overrides: Partial<Plan> = {}): Plan {
@@ -161,41 +154,6 @@ describe('Helper functions', () => {
161
154
  });
162
155
  });
163
156
 
164
- describe('Pattern constants', () => {
165
- it('METRIC_PATTERNS matches numbers', () => {
166
- expect(METRIC_PATTERNS.some((p) => p.test('reduce latency by 50%'))).toBe(true);
167
- });
168
-
169
- it('EXCLUSION_KEYWORDS includes common exclusion words', () => {
170
- expect(EXCLUSION_KEYWORDS).toContain('exclude');
171
- expect(EXCLUSION_KEYWORDS).toContain('not');
172
- });
173
-
174
- it('OVERLY_BROAD_PATTERNS includes dangerous scope terms', () => {
175
- expect(OVERLY_BROAD_PATTERNS).toContain('complete rewrite');
176
- });
177
-
178
- it('DEPENDENCY_KEYWORDS includes ordering terms', () => {
179
- expect(DEPENDENCY_KEYWORDS).toContain('depends');
180
- expect(DEPENDENCY_KEYWORDS).toContain('prerequisite');
181
- });
182
-
183
- it('BREAKING_CHANGE_KEYWORDS includes migration terms', () => {
184
- expect(BREAKING_CHANGE_KEYWORDS).toContain('breaking change');
185
- expect(BREAKING_CHANGE_KEYWORDS).toContain('database migration');
186
- });
187
-
188
- it('MITIGATION_KEYWORDS includes safety terms', () => {
189
- expect(MITIGATION_KEYWORDS).toContain('rollback');
190
- expect(MITIGATION_KEYWORDS).toContain('feature flag');
191
- });
192
-
193
- it('VERIFICATION_KEYWORDS includes testing terms', () => {
194
- expect(VERIFICATION_KEYWORDS).toContain('test');
195
- expect(VERIFICATION_KEYWORDS).toContain('coverage');
196
- });
197
- });
198
-
199
157
  describe('Pass 1: Structure', () => {
200
158
  it('returns no gaps for a well-structured plan', () => {
201
159
  const gaps = analyzeStructure(makePlan());
@@ -287,8 +287,10 @@ describe('JsonGoalRepository', () => {
287
287
  });
288
288
 
289
289
  it('should create and retrieve a goal', () => {
290
+ const before = Date.now();
290
291
  const goal = repo.create({ id: 'g1', title: 'Ship it', level: 'objective', status: 'planned' });
291
- expect(goal.createdAt).toBeGreaterThan(0);
292
+ expect(goal.createdAt).toBeGreaterThanOrEqual(before);
293
+ expect(goal.createdAt).toBeLessThanOrEqual(Date.now());
292
294
  expect(repo.getById('g1')?.title).toBe('Ship it');
293
295
  });
294
296
 
@@ -68,9 +68,11 @@ describe('plan-lifecycle', () => {
68
68
 
69
69
  describe('applyTransition', () => {
70
70
  it('returns new status and timestamp for valid transition', () => {
71
+ const before = Date.now();
71
72
  const result = applyTransition('draft', 'approved');
72
73
  expect(result.status).toBe('approved');
73
- expect(result.updatedAt).toBeGreaterThan(0);
74
+ expect(result.updatedAt).toBeGreaterThanOrEqual(before);
75
+ expect(result.updatedAt).toBeLessThanOrEqual(Date.now());
74
76
  });
75
77
  it('throws for invalid transition', () => {
76
78
  expect(() => applyTransition('draft', 'executing')).toThrow('Invalid transition');
@@ -312,9 +314,11 @@ describe('plan-lifecycle', () => {
312
314
  });
313
315
 
314
316
  it('sets startedAt on first in_progress', () => {
317
+ const before = Date.now();
315
318
  const task = makeTask();
316
319
  applyTaskStatusUpdate(task, 'in_progress');
317
- expect(task.startedAt).toBeGreaterThan(0);
320
+ expect(task.startedAt).toBeGreaterThanOrEqual(before);
321
+ expect(task.startedAt).toBeLessThanOrEqual(Date.now());
318
322
  expect(task.status).toBe('in_progress');
319
323
  });
320
324
  it('does not overwrite startedAt on repeated in_progress', () => {
@@ -324,22 +328,26 @@ describe('plan-lifecycle', () => {
324
328
  expect(task.startedAt).toBe(1000);
325
329
  });
326
330
  it('sets completedAt and durationMs on completed', () => {
331
+ const before = Date.now();
327
332
  const task = makeTask();
328
- task.startedAt = Date.now() - 500;
333
+ task.startedAt = before - 500;
329
334
  applyTaskStatusUpdate(task, 'completed');
330
- expect(task.completedAt).toBeGreaterThan(0);
331
- expect(task.metrics?.durationMs).toBeGreaterThanOrEqual(0);
335
+ expect(task.completedAt).toBeGreaterThanOrEqual(before);
336
+ expect(task.completedAt).toBeLessThanOrEqual(Date.now());
337
+ expect(task.metrics?.durationMs).toBeGreaterThanOrEqual(500);
332
338
  });
333
339
  it('sets completedAt on skipped', () => {
340
+ const before = Date.now();
334
341
  const task = makeTask();
335
342
  applyTaskStatusUpdate(task, 'skipped');
336
- expect(task.completedAt).toBeGreaterThan(0);
343
+ expect(task.completedAt).toBeGreaterThanOrEqual(before);
337
344
  expect(task.status).toBe('skipped');
338
345
  });
339
346
  it('sets completedAt on failed', () => {
347
+ const before = Date.now();
340
348
  const task = makeTask();
341
349
  applyTaskStatusUpdate(task, 'failed');
342
- expect(task.completedAt).toBeGreaterThan(0);
350
+ expect(task.completedAt).toBeGreaterThanOrEqual(before);
343
351
  expect(task.status).toBe('failed');
344
352
  });
345
353
 
@@ -193,6 +193,8 @@ export interface Plan {
193
193
  genericId?: string;
194
194
  domainId?: string;
195
195
  };
196
+ /** Active playbook executor session ID — used to enforce gates during task updates and plan completion. */
197
+ playbookSessionId?: string;
196
198
  /** Source GitHub issue this plan was created from (e.g., #NNN in prompt). */
197
199
  githubIssue?: { owner: string; repo: string; number: number };
198
200
  /** GitHub issue projection — populated by orchestrate_project_to_github. */
@@ -185,6 +185,14 @@ export class Planner {
185
185
  return plan;
186
186
  }
187
187
 
188
+ patchPlan(planId: string, fields: Partial<Plan>): Plan {
189
+ const plan = this.requirePlan(planId);
190
+ Object.assign(plan, fields);
191
+ plan.updatedAt = Date.now();
192
+ this.save();
193
+ return plan;
194
+ }
195
+
188
196
  startExecution(planId: string): Plan {
189
197
  const plan = this.requirePlan(planId);
190
198
  this.transition(plan, 'executing');
@@ -1,6 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import {
3
- DRIFT_WEIGHTS,
4
3
  calculateDriftScore,
5
4
  computeExecutionSummary,
6
5
  buildReconciliationReport,
@@ -9,14 +8,6 @@ import {
9
8
  import type { DriftItem, PlanTask } from './planner-types.js';
10
9
 
11
10
  describe('reconciliation-engine', () => {
12
- describe('DRIFT_WEIGHTS', () => {
13
- it('has correct weights', () => {
14
- expect(DRIFT_WEIGHTS.high).toBe(20);
15
- expect(DRIFT_WEIGHTS.medium).toBe(10);
16
- expect(DRIFT_WEIGHTS.low).toBe(5);
17
- });
18
- });
19
-
20
11
  describe('calculateDriftScore', () => {
21
12
  it('returns 100 for no drift items', () => {
22
13
  expect(calculateDriftScore([])).toBe(100);
@@ -92,6 +83,7 @@ describe('reconciliation-engine', () => {
92
83
 
93
84
  describe('buildReconciliationReport', () => {
94
85
  it('builds a report with accuracy score', () => {
86
+ const before = Date.now();
95
87
  const report = buildReconciliationReport('plan-1', {
96
88
  actualOutcome: 'Done',
97
89
  driftItems: [{ type: 'skipped', description: 'x', impact: 'low', rationale: 'r' }],
@@ -100,7 +92,8 @@ describe('reconciliation-engine', () => {
100
92
  expect(report.accuracy).toBe(95);
101
93
  expect(report.driftItems).toHaveLength(1);
102
94
  expect(report.summary).toBe('Done');
103
- expect(report.reconciledAt).toBeGreaterThan(0);
95
+ expect(report.reconciledAt).toBeGreaterThanOrEqual(before);
96
+ expect(report.reconciledAt).toBeLessThanOrEqual(Date.now());
104
97
  });
105
98
  it('defaults to empty drift items', () => {
106
99
  const report = buildReconciliationReport('plan-2', { actualOutcome: 'OK' });