@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.
- package/data/flows/deliver.flow.yaml +11 -0
- package/data/flows/design.flow.yaml +4 -14
- package/data/flows/enhance.flow.yaml +10 -0
- package/data/flows/explore.flow.yaml +16 -0
- package/data/flows/fix.flow.yaml +1 -1
- package/data/flows/review.flow.yaml +13 -4
- package/dist/capabilities/chain-mapping.d.ts.map +1 -1
- package/dist/capabilities/chain-mapping.js +5 -4
- package/dist/capabilities/chain-mapping.js.map +1 -1
- package/dist/capabilities/registry.d.ts +6 -0
- package/dist/capabilities/registry.d.ts.map +1 -1
- package/dist/capabilities/registry.js +3 -2
- package/dist/capabilities/registry.js.map +1 -1
- package/dist/context/context-engine.js +1 -1
- package/dist/context/context-engine.js.map +1 -1
- package/dist/engine/core-ops.d.ts.map +1 -1
- package/dist/engine/core-ops.js +38 -1
- package/dist/engine/core-ops.js.map +1 -1
- package/dist/flows/epilogue.d.ts +5 -1
- package/dist/flows/epilogue.d.ts.map +1 -1
- package/dist/flows/epilogue.js +11 -3
- package/dist/flows/epilogue.js.map +1 -1
- package/dist/flows/executor.d.ts.map +1 -1
- package/dist/flows/executor.js +13 -5
- package/dist/flows/executor.js.map +1 -1
- package/dist/flows/index.d.ts +1 -2
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js +1 -0
- package/dist/flows/index.js.map +1 -1
- package/dist/flows/plan-builder.d.ts +17 -1
- package/dist/flows/plan-builder.d.ts.map +1 -1
- package/dist/flows/plan-builder.js +67 -6
- package/dist/flows/plan-builder.js.map +1 -1
- package/dist/flows/probes.d.ts +1 -1
- package/dist/flows/probes.d.ts.map +1 -1
- package/dist/flows/probes.js +15 -3
- package/dist/flows/probes.js.map +1 -1
- package/dist/flows/types.d.ts +31 -4
- package/dist/flows/types.d.ts.map +1 -1
- package/dist/flows/types.js +6 -1
- package/dist/flows/types.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/packs/pack-installer.d.ts.map +1 -1
- package/dist/packs/pack-installer.js +28 -2
- package/dist/packs/pack-installer.js.map +1 -1
- package/dist/planning/planner-types.d.ts +2 -0
- package/dist/planning/planner-types.d.ts.map +1 -1
- package/dist/planning/planner.d.ts +1 -0
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +7 -0
- package/dist/planning/planner.js.map +1 -1
- package/dist/playbooks/playbook-executor.d.ts +10 -1
- package/dist/playbooks/playbook-executor.d.ts.map +1 -1
- package/dist/playbooks/playbook-executor.js +8 -2
- package/dist/playbooks/playbook-executor.js.map +1 -1
- package/dist/playbooks/playbook-types.d.ts +8 -0
- package/dist/playbooks/playbook-types.d.ts.map +1 -1
- package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
- package/dist/runtime/admin-extra-ops.js +30 -0
- package/dist/runtime/admin-extra-ops.js.map +1 -1
- package/dist/runtime/admin-ops.d.ts.map +1 -1
- package/dist/runtime/admin-ops.js +60 -21
- package/dist/runtime/admin-ops.js.map +1 -1
- package/dist/runtime/admin-setup-ops.d.ts +11 -0
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
- package/dist/runtime/admin-setup-ops.js +87 -17
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/capture-ops.d.ts.map +1 -1
- package/dist/runtime/capture-ops.js +38 -12
- package/dist/runtime/capture-ops.js.map +1 -1
- package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
- package/dist/runtime/facades/brain-facade.js +16 -4
- package/dist/runtime/facades/brain-facade.js.map +1 -1
- package/dist/runtime/facades/context-facade.d.ts.map +1 -1
- package/dist/runtime/facades/context-facade.js +9 -3
- package/dist/runtime/facades/context-facade.js.map +1 -1
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
- package/dist/runtime/facades/memory-facade.js +20 -7
- package/dist/runtime/facades/memory-facade.js.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.js +12 -0
- package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
- package/dist/runtime/facades/plan-facade.js +113 -4
- package/dist/runtime/facades/plan-facade.js.map +1 -1
- package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
- package/dist/runtime/facades/vault-facade.js +24 -3
- package/dist/runtime/facades/vault-facade.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts +21 -0
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +132 -38
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/schema-helpers.d.ts.map +1 -1
- package/dist/runtime/schema-helpers.js +4 -0
- package/dist/runtime/schema-helpers.js.map +1 -1
- package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
- package/dist/runtime/vault-linking-ops.js +16 -3
- package/dist/runtime/vault-linking-ops.js.map +1 -1
- package/dist/scheduler/cron-validator.d.ts +15 -0
- package/dist/scheduler/cron-validator.d.ts.map +1 -0
- package/dist/scheduler/cron-validator.js +93 -0
- package/dist/scheduler/cron-validator.js.map +1 -0
- package/dist/scheduler/platform-linux.d.ts +14 -0
- package/dist/scheduler/platform-linux.d.ts.map +1 -0
- package/dist/scheduler/platform-linux.js +107 -0
- package/dist/scheduler/platform-linux.js.map +1 -0
- package/dist/scheduler/platform-macos.d.ts +15 -0
- package/dist/scheduler/platform-macos.d.ts.map +1 -0
- package/dist/scheduler/platform-macos.js +131 -0
- package/dist/scheduler/platform-macos.js.map +1 -0
- package/dist/scheduler/scheduler-ops.d.ts +14 -0
- package/dist/scheduler/scheduler-ops.d.ts.map +1 -0
- package/dist/scheduler/scheduler-ops.js +77 -0
- package/dist/scheduler/scheduler-ops.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +55 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -0
- package/dist/scheduler/scheduler.js +144 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/scheduler/types.d.ts +48 -0
- package/dist/scheduler/types.d.ts.map +1 -0
- package/dist/scheduler/types.js +6 -0
- package/dist/scheduler/types.js.map +1 -0
- package/dist/skills/sync-skills.d.ts +11 -0
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +132 -38
- package/dist/skills/sync-skills.js.map +1 -1
- package/dist/utils/worktree-reaper.d.ts +38 -0
- package/dist/utils/worktree-reaper.d.ts.map +1 -0
- package/dist/utils/worktree-reaper.js +85 -0
- package/dist/utils/worktree-reaper.js.map +1 -0
- package/dist/vault/scope-detector.d.ts.map +1 -1
- package/dist/vault/scope-detector.js +37 -4
- package/dist/vault/scope-detector.js.map +1 -1
- package/dist/vault/vault-entries.d.ts.map +1 -1
- package/dist/vault/vault-entries.js +3 -1
- package/dist/vault/vault-entries.js.map +1 -1
- package/package.json +1 -1
- package/src/agency/agency-manager.test.ts +4 -4
- package/src/agency/default-rules.test.ts +0 -13
- package/src/brain/brain-intelligence.test.ts +0 -5
- package/src/brain/second-brain-features.test.ts +2 -14
- package/src/capabilities/chain-mapping.test.ts +1 -6
- package/src/capabilities/chain-mapping.ts +6 -4
- package/src/capabilities/registry.test.ts +1 -1
- package/src/capabilities/registry.ts +9 -2
- package/src/chat/agent-loop.test.ts +1 -1
- package/src/chat/chat-enhanced.test.ts +0 -8
- package/src/claudemd/compose.test.ts +0 -5
- package/src/context/context-engine.test.ts +0 -1
- package/src/context/context-engine.ts +1 -1
- package/src/control/intent-router.test.ts +2 -2
- package/src/curator/tag-manager.test.ts +0 -4
- package/src/domain-packs/types.test.ts +0 -5
- package/src/dream/dream.test.ts +0 -7
- package/src/enforcement/registry.test.ts +2 -2
- package/src/engine/core-ops.test.ts +4 -22
- package/src/engine/core-ops.ts +36 -1
- package/src/engine/module-manifest.test.ts +1 -31
- package/src/engine/register-engine.test.ts +3 -33
- package/src/errors/retry.test.ts +3 -1
- package/src/flows/chain-runner.test.ts +0 -6
- package/src/flows/context-router.test.ts +3 -3
- package/src/flows/epilogue.test.ts +40 -2
- package/src/flows/epilogue.ts +11 -2
- package/src/flows/executor.test.ts +48 -2
- package/src/flows/executor.ts +15 -5
- package/src/flows/index.ts +1 -3
- package/src/flows/plan-builder.test.ts +201 -0
- package/src/flows/plan-builder.ts +81 -5
- package/src/flows/probes.ts +17 -3
- package/src/flows/types.ts +31 -2
- package/src/health/health-registry.test.ts +3 -1
- package/src/index.ts +17 -0
- package/src/intake/dedup-gate.test.ts +2 -6
- package/src/intake/text-ingester.test.ts +3 -4
- package/src/llm/llm-client.test.ts +1 -1
- package/src/llm/utils.test.ts +1 -1
- package/src/migrations/migration-runner.test.ts +0 -1
- package/src/operator/operator-context-store.test.ts +0 -13
- package/src/operator/operator-profile.test.ts +2 -20
- package/src/packs/pack-installer.ts +28 -2
- package/src/packs/pack-system.test.ts +2 -2
- package/src/persona/defaults.test.ts +19 -19
- package/src/planning/gap-passes.test.ts +0 -46
- package/src/planning/gap-patterns.test.ts +0 -42
- package/src/planning/goal-ancestry.test.ts +3 -1
- package/src/planning/plan-lifecycle.test.ts +15 -7
- package/src/planning/planner-types.ts +2 -0
- package/src/planning/planner.ts +8 -0
- package/src/planning/reconciliation-engine.test.ts +3 -10
- package/src/planning/task-complexity-assessor.test.ts +0 -5
- package/src/planning/task-verifier.test.ts +3 -1
- package/src/playbooks/generic/generic-playbooks.test.ts +0 -28
- package/src/playbooks/index.test.ts +0 -55
- package/src/playbooks/playbook-executor.test.ts +76 -0
- package/src/playbooks/playbook-executor.ts +24 -3
- package/src/playbooks/playbook-types.ts +8 -0
- package/src/plugins/plugin-registry.test.ts +6 -2
- package/src/project/project-registry.test.ts +2 -0
- package/src/queue/async-infrastructure.test.ts +6 -4
- package/src/queue/job-queue.test.ts +13 -7
- package/src/runtime/admin-extra-ops.test.ts +35 -30
- package/src/runtime/admin-extra-ops.ts +30 -0
- package/src/runtime/admin-ops.test.ts +0 -4
- package/src/runtime/admin-ops.ts +63 -21
- package/src/runtime/admin-setup-ops.test.ts +185 -13
- package/src/runtime/admin-setup-ops.ts +86 -16
- package/src/runtime/archive-ops.test.ts +0 -28
- package/src/runtime/branching-ops.test.ts +0 -17
- package/src/runtime/capture-ops.test.ts +41 -16
- package/src/runtime/capture-ops.ts +78 -46
- package/src/runtime/chain-ops.test.ts +0 -21
- package/src/runtime/facades/admin-facade.test.ts +0 -34
- package/src/runtime/facades/agency-facade.test.ts +0 -39
- package/src/runtime/facades/archive-facade.test.ts +0 -43
- package/src/runtime/facades/brain-facade.test.ts +8 -99
- package/src/runtime/facades/brain-facade.ts +29 -12
- package/src/runtime/facades/branching-facade.test.ts +30 -17
- package/src/runtime/facades/chat-facade.test.ts +0 -91
- package/src/runtime/facades/chat-service-ops.test.ts +0 -24
- package/src/runtime/facades/chat-session-ops.test.ts +0 -12
- package/src/runtime/facades/chat-transport-ops.test.ts +0 -23
- package/src/runtime/facades/context-facade.test.ts +0 -17
- package/src/runtime/facades/context-facade.ts +11 -4
- package/src/runtime/facades/control-facade.test.ts +0 -30
- package/src/runtime/facades/curator-facade.test.ts +0 -33
- package/src/runtime/facades/intake-facade.test.ts +0 -33
- package/src/runtime/facades/links-facade.test.ts +0 -37
- package/src/runtime/facades/loop-facade.test.ts +0 -26
- package/src/runtime/facades/memory-facade.test.ts +0 -18
- package/src/runtime/facades/memory-facade.ts +27 -11
- package/src/runtime/facades/operator-facade.test.ts +0 -31
- package/src/runtime/facades/orchestrate-facade.test.ts +0 -21
- package/src/runtime/facades/orchestrate-facade.ts +12 -0
- package/src/runtime/facades/plan-facade.test.ts +7 -32
- package/src/runtime/facades/plan-facade.ts +137 -4
- package/src/runtime/facades/review-facade.test.ts +1 -49
- package/src/runtime/facades/sync-facade.test.ts +24 -41
- package/src/runtime/facades/tier-facade.test.ts +30 -22
- package/src/runtime/facades/vault-facade.test.ts +0 -41
- package/src/runtime/facades/vault-facade.ts +26 -3
- package/src/runtime/grading-ops.test.ts +0 -27
- package/src/runtime/intake-ops.test.ts +0 -19
- package/src/runtime/loop-ops.test.ts +0 -48
- package/src/runtime/memory-cross-project-ops.test.ts +0 -14
- package/src/runtime/memory-extra-ops.test.ts +4 -8
- package/src/runtime/orchestrate-ops.test.ts +238 -19
- package/src/runtime/orchestrate-ops.ts +166 -41
- package/src/runtime/pack-ops.test.ts +0 -26
- package/src/runtime/planning-extra-ops.test.ts +2 -14
- package/src/runtime/playbook-ops-execution.test.ts +9 -20
- package/src/runtime/playbook-ops.test.ts +4 -67
- package/src/runtime/review-ops.test.ts +0 -15
- package/src/runtime/schema-helpers.ts +4 -0
- package/src/runtime/sync-ops.test.ts +0 -18
- package/src/runtime/tier-ops.test.ts +0 -21
- package/src/runtime/vault-extra-ops.test.ts +0 -12
- package/src/runtime/vault-linking-ops.test.ts +0 -4
- package/src/runtime/vault-linking-ops.ts +26 -8
- package/src/runtime/vault-sharing-ops.test.ts +0 -9
- package/src/scheduler/cron-validator.ts +101 -0
- package/src/scheduler/platform-linux.ts +122 -0
- package/src/scheduler/platform-macos.ts +150 -0
- package/src/scheduler/scheduler-ops.ts +77 -0
- package/src/scheduler/scheduler.test.ts +247 -0
- package/src/scheduler/scheduler.ts +174 -0
- package/src/scheduler/types.ts +52 -0
- package/src/skills/__tests__/sync-skills.test.ts +6 -17
- package/src/skills/global-claude-md.test.ts +113 -0
- package/src/skills/sync-skills.ts +143 -35
- package/src/skills/validate-skills.test.ts +12 -11
- package/src/telemetry/telemetry.test.ts +1 -0
- package/src/transport/http-server.test.ts +3 -0
- package/src/transport/session-manager.test.ts +3 -1
- package/src/transport/token-auth.test.ts +6 -9
- package/src/transport/ws-server.test.ts +10 -2
- package/src/utils/worktree-reaper.ts +113 -0
- package/src/vault/__tests__/vault-characterization.test.ts +0 -108
- package/src/vault/linking.test.ts +0 -2
- package/src/vault/playbook.test.ts +4 -1
- package/src/vault/scope-detector.test.ts +3 -1
- package/src/vault/scope-detector.ts +42 -4
- package/src/vault/vault-connect.test.ts +1 -1
- package/src/vault/vault-entries.ts +3 -1
- package/src/vault/vault.test.ts +23 -8
package/src/flows/types.ts
CHANGED
|
@@ -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
|
-
|
|
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).
|
|
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
|
|
139
|
-
expect(results[0].similarity).
|
|
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).
|
|
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
|
-
|
|
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).
|
|
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).
|
|
122
|
+
expect(typeof result.durationMs).toBe('number');
|
|
123
123
|
} finally {
|
|
124
124
|
globalThis.fetch = originalFetch;
|
|
125
125
|
}
|
package/src/llm/utils.test.ts
CHANGED
|
@@ -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)).
|
|
227
|
+
expect(typeof computeDelay({}, 0, config)).toBe('number');
|
|
228
228
|
vi.restoreAllMocks();
|
|
229
229
|
});
|
|
230
230
|
});
|
|
@@ -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).
|
|
190
|
-
expect(history.
|
|
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).
|
|
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).
|
|
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
|
|
15
|
+
expect(ITALIAN_CRAFTSPERSON.voice).toContain('Italian mentor');
|
|
16
16
|
expect(ITALIAN_CRAFTSPERSON.culture).toBe('Italian');
|
|
17
|
-
expect(ITALIAN_CRAFTSPERSON.inspiration
|
|
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
|
|
22
|
-
expect(ITALIAN_CRAFTSPERSON.signoffs
|
|
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
|
|
27
|
-
expect(ITALIAN_CRAFTSPERSON.quirks
|
|
28
|
-
expect(ITALIAN_CRAFTSPERSON.metaphors
|
|
29
|
-
expect(ITALIAN_CRAFTSPERSON.opinions
|
|
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
|
|
40
|
-
expect(NEUTRAL_PERSONA.inspiration
|
|
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
|
|
48
|
-
expect(NEUTRAL_PERSONA.traits
|
|
49
|
-
expect(NEUTRAL_PERSONA.quirks
|
|
50
|
-
expect(NEUTRAL_PERSONA.opinions
|
|
51
|
-
expect(NEUTRAL_PERSONA.metaphors
|
|
52
|
-
expect(NEUTRAL_PERSONA.greetings
|
|
53
|
-
expect(NEUTRAL_PERSONA.signoffs
|
|
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
|
|
58
|
-
expect(NEUTRAL_PERSONA.nameRule
|
|
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).
|
|
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).
|
|
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).
|
|
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 =
|
|
333
|
+
task.startedAt = before - 500;
|
|
329
334
|
applyTaskStatusUpdate(task, 'completed');
|
|
330
|
-
expect(task.completedAt).
|
|
331
|
-
expect(task.
|
|
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).
|
|
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).
|
|
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. */
|
package/src/planning/planner.ts
CHANGED
|
@@ -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).
|
|
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' });
|