@soleri/core 2.0.2 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brain/brain.d.ts +14 -50
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +207 -16
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts +86 -0
- package/dist/brain/intelligence.d.ts.map +1 -0
- package/dist/brain/intelligence.js +771 -0
- package/dist/brain/intelligence.js.map +1 -0
- package/dist/brain/types.d.ts +197 -0
- package/dist/brain/types.d.ts.map +1 -0
- package/dist/brain/types.js +2 -0
- package/dist/brain/types.js.map +1 -0
- package/dist/cognee/client.d.ts +35 -0
- package/dist/cognee/client.d.ts.map +1 -0
- package/dist/cognee/client.js +291 -0
- package/dist/cognee/client.js.map +1 -0
- package/dist/cognee/types.d.ts +46 -0
- package/dist/cognee/types.d.ts.map +1 -0
- package/dist/cognee/types.js +3 -0
- package/dist/cognee/types.js.map +1 -0
- package/dist/control/identity-manager.d.ts +22 -0
- package/dist/control/identity-manager.d.ts.map +1 -0
- package/dist/control/identity-manager.js +233 -0
- package/dist/control/identity-manager.js.map +1 -0
- package/dist/control/intent-router.d.ts +32 -0
- package/dist/control/intent-router.d.ts.map +1 -0
- package/dist/control/intent-router.js +242 -0
- package/dist/control/intent-router.js.map +1 -0
- package/dist/control/types.d.ts +68 -0
- package/dist/control/types.d.ts.map +1 -0
- package/dist/control/types.js +9 -0
- package/dist/control/types.js.map +1 -0
- package/dist/curator/curator.d.ts +29 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +142 -5
- package/dist/curator/curator.js.map +1 -1
- package/dist/facades/types.d.ts +1 -1
- package/dist/governance/governance.d.ts +42 -0
- package/dist/governance/governance.d.ts.map +1 -0
- package/dist/governance/governance.js +488 -0
- package/dist/governance/governance.js.map +1 -0
- package/dist/governance/index.d.ts +3 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/index.js +2 -0
- package/dist/governance/index.js.map +1 -0
- package/dist/governance/types.d.ts +102 -0
- package/dist/governance/types.d.ts.map +1 -0
- package/dist/governance/types.js +3 -0
- package/dist/governance/types.js.map +1 -0
- package/dist/index.d.ts +35 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/llm-client.d.ts.map +1 -1
- package/dist/llm/llm-client.js +9 -2
- package/dist/llm/llm-client.js.map +1 -1
- package/dist/logging/logger.d.ts +37 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +145 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/types.d.ts +19 -0
- package/dist/logging/types.d.ts.map +1 -0
- package/dist/logging/types.js +2 -0
- package/dist/logging/types.js.map +1 -0
- package/dist/loop/loop-manager.d.ts +49 -0
- package/dist/loop/loop-manager.d.ts.map +1 -0
- package/dist/loop/loop-manager.js +105 -0
- package/dist/loop/loop-manager.js.map +1 -0
- package/dist/loop/types.d.ts +35 -0
- package/dist/loop/types.d.ts.map +1 -0
- package/dist/loop/types.js +8 -0
- package/dist/loop/types.js.map +1 -0
- package/dist/planning/gap-analysis.d.ts +29 -0
- package/dist/planning/gap-analysis.d.ts.map +1 -0
- package/dist/planning/gap-analysis.js +265 -0
- package/dist/planning/gap-analysis.js.map +1 -0
- package/dist/planning/gap-types.d.ts +29 -0
- package/dist/planning/gap-types.d.ts.map +1 -0
- package/dist/planning/gap-types.js +28 -0
- package/dist/planning/gap-types.js.map +1 -0
- package/dist/planning/planner.d.ts +150 -1
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +365 -2
- package/dist/planning/planner.js.map +1 -1
- package/dist/project/project-registry.d.ts +79 -0
- package/dist/project/project-registry.d.ts.map +1 -0
- package/dist/project/project-registry.js +276 -0
- package/dist/project/project-registry.js.map +1 -0
- package/dist/project/types.d.ts +28 -0
- package/dist/project/types.d.ts.map +1 -0
- package/dist/project/types.js +5 -0
- package/dist/project/types.js.map +1 -0
- package/dist/runtime/admin-extra-ops.d.ts +13 -0
- package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
- package/dist/runtime/admin-extra-ops.js +284 -0
- package/dist/runtime/admin-extra-ops.js.map +1 -0
- package/dist/runtime/admin-ops.d.ts +15 -0
- package/dist/runtime/admin-ops.d.ts.map +1 -0
- package/dist/runtime/admin-ops.js +322 -0
- package/dist/runtime/admin-ops.js.map +1 -0
- package/dist/runtime/capture-ops.d.ts +15 -0
- package/dist/runtime/capture-ops.d.ts.map +1 -0
- package/dist/runtime/capture-ops.js +345 -0
- package/dist/runtime/capture-ops.js.map +1 -0
- package/dist/runtime/core-ops.d.ts +7 -3
- package/dist/runtime/core-ops.d.ts.map +1 -1
- package/dist/runtime/core-ops.js +646 -15
- package/dist/runtime/core-ops.js.map +1 -1
- package/dist/runtime/curator-extra-ops.d.ts +9 -0
- package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
- package/dist/runtime/curator-extra-ops.js +59 -0
- package/dist/runtime/curator-extra-ops.js.map +1 -0
- package/dist/runtime/domain-ops.d.ts.map +1 -1
- package/dist/runtime/domain-ops.js +59 -13
- package/dist/runtime/domain-ops.js.map +1 -1
- package/dist/runtime/grading-ops.d.ts +14 -0
- package/dist/runtime/grading-ops.d.ts.map +1 -0
- package/dist/runtime/grading-ops.js +105 -0
- package/dist/runtime/grading-ops.js.map +1 -0
- package/dist/runtime/loop-ops.d.ts +13 -0
- package/dist/runtime/loop-ops.d.ts.map +1 -0
- package/dist/runtime/loop-ops.js +179 -0
- package/dist/runtime/loop-ops.js.map +1 -0
- package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
- package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
- package/dist/runtime/memory-cross-project-ops.js +165 -0
- package/dist/runtime/memory-cross-project-ops.js.map +1 -0
- package/dist/runtime/memory-extra-ops.d.ts +13 -0
- package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
- package/dist/runtime/memory-extra-ops.js +173 -0
- package/dist/runtime/memory-extra-ops.js.map +1 -0
- package/dist/runtime/orchestrate-ops.d.ts +17 -0
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
- package/dist/runtime/orchestrate-ops.js +240 -0
- package/dist/runtime/orchestrate-ops.js.map +1 -0
- package/dist/runtime/planning-extra-ops.d.ts +17 -0
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
- package/dist/runtime/planning-extra-ops.js +300 -0
- package/dist/runtime/planning-extra-ops.js.map +1 -0
- package/dist/runtime/project-ops.d.ts +15 -0
- package/dist/runtime/project-ops.d.ts.map +1 -0
- package/dist/runtime/project-ops.js +181 -0
- package/dist/runtime/project-ops.js.map +1 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +48 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +23 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.d.ts +9 -0
- package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
- package/dist/runtime/vault-extra-ops.js +195 -0
- package/dist/runtime/vault-extra-ops.js.map +1 -0
- package/dist/telemetry/telemetry.d.ts +48 -0
- package/dist/telemetry/telemetry.d.ts.map +1 -0
- package/dist/telemetry/telemetry.js +87 -0
- package/dist/telemetry/telemetry.js.map +1 -0
- package/dist/vault/vault.d.ts +94 -0
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +340 -1
- package/dist/vault/vault.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/admin-extra-ops.test.ts +420 -0
- package/src/__tests__/admin-ops.test.ts +271 -0
- package/src/__tests__/brain-intelligence.test.ts +828 -0
- package/src/__tests__/brain.test.ts +396 -27
- package/src/__tests__/capture-ops.test.ts +509 -0
- package/src/__tests__/cognee-client.test.ts +524 -0
- package/src/__tests__/core-ops.test.ts +341 -49
- package/src/__tests__/curator-extra-ops.test.ts +359 -0
- package/src/__tests__/curator.test.ts +126 -31
- package/src/__tests__/domain-ops.test.ts +111 -9
- package/src/__tests__/governance.test.ts +522 -0
- package/src/__tests__/grading-ops.test.ts +340 -0
- package/src/__tests__/identity-manager.test.ts +243 -0
- package/src/__tests__/intent-router.test.ts +222 -0
- package/src/__tests__/logger.test.ts +200 -0
- package/src/__tests__/loop-ops.test.ts +398 -0
- package/src/__tests__/memory-cross-project-ops.test.ts +246 -0
- package/src/__tests__/memory-extra-ops.test.ts +352 -0
- package/src/__tests__/orchestrate-ops.test.ts +284 -0
- package/src/__tests__/planner.test.ts +331 -0
- package/src/__tests__/planning-extra-ops.test.ts +548 -0
- package/src/__tests__/project-ops.test.ts +367 -0
- package/src/__tests__/runtime.test.ts +13 -11
- package/src/__tests__/vault-extra-ops.test.ts +407 -0
- package/src/brain/brain.ts +308 -72
- package/src/brain/intelligence.ts +1230 -0
- package/src/brain/types.ts +214 -0
- package/src/cognee/client.ts +352 -0
- package/src/cognee/types.ts +62 -0
- package/src/control/identity-manager.ts +354 -0
- package/src/control/intent-router.ts +326 -0
- package/src/control/types.ts +102 -0
- package/src/curator/curator.ts +265 -15
- package/src/governance/governance.ts +698 -0
- package/src/governance/index.ts +18 -0
- package/src/governance/types.ts +111 -0
- package/src/index.ts +128 -3
- package/src/llm/llm-client.ts +18 -24
- package/src/logging/logger.ts +154 -0
- package/src/logging/types.ts +21 -0
- package/src/loop/loop-manager.ts +130 -0
- package/src/loop/types.ts +44 -0
- package/src/planning/gap-analysis.ts +506 -0
- package/src/planning/gap-types.ts +58 -0
- package/src/planning/planner.ts +478 -2
- package/src/project/project-registry.ts +358 -0
- package/src/project/types.ts +31 -0
- package/src/runtime/admin-extra-ops.ts +307 -0
- package/src/runtime/admin-ops.ts +329 -0
- package/src/runtime/capture-ops.ts +385 -0
- package/src/runtime/core-ops.ts +747 -26
- package/src/runtime/curator-extra-ops.ts +71 -0
- package/src/runtime/domain-ops.ts +65 -13
- package/src/runtime/grading-ops.ts +121 -0
- package/src/runtime/loop-ops.ts +194 -0
- package/src/runtime/memory-cross-project-ops.ts +192 -0
- package/src/runtime/memory-extra-ops.ts +186 -0
- package/src/runtime/orchestrate-ops.ts +272 -0
- package/src/runtime/planning-extra-ops.ts +327 -0
- package/src/runtime/project-ops.ts +196 -0
- package/src/runtime/runtime.ts +54 -1
- package/src/runtime/types.ts +23 -0
- package/src/runtime/vault-extra-ops.ts +225 -0
- package/src/telemetry/telemetry.ts +118 -0
- package/src/vault/vault.ts +412 -1
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdirSync, rmSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { createAgentRuntime } from '../runtime/runtime.js';
|
|
6
|
+
import { createPlanningExtraOps } from '../runtime/planning-extra-ops.js';
|
|
7
|
+
import type { AgentRuntime } from '../runtime/types.js';
|
|
8
|
+
import type { OpDefinition } from '../facades/types.js';
|
|
9
|
+
|
|
10
|
+
describe('createPlanningExtraOps', () => {
|
|
11
|
+
let runtime: AgentRuntime;
|
|
12
|
+
let ops: OpDefinition[];
|
|
13
|
+
let plannerDir: string;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
plannerDir = join(tmpdir(), 'planning-extra-ops-test-' + Date.now());
|
|
17
|
+
mkdirSync(plannerDir, { recursive: true });
|
|
18
|
+
runtime = createAgentRuntime({
|
|
19
|
+
agentId: 'test-planning-extra',
|
|
20
|
+
vaultPath: ':memory:',
|
|
21
|
+
plansPath: join(plannerDir, 'plans.json'),
|
|
22
|
+
});
|
|
23
|
+
ops = createPlanningExtraOps(runtime);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
runtime.close();
|
|
28
|
+
rmSync(plannerDir, { recursive: true, force: true });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function findOp(name: string): OpDefinition {
|
|
32
|
+
const op = ops.find((o) => o.name === name);
|
|
33
|
+
if (!op) throw new Error(`Op "${name}" not found`);
|
|
34
|
+
return op;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
it('should return 9 ops', () => {
|
|
38
|
+
expect(ops.length).toBe(9);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should have all expected op names', () => {
|
|
42
|
+
const names = ops.map((o) => o.name);
|
|
43
|
+
expect(names).toContain('plan_iterate');
|
|
44
|
+
expect(names).toContain('plan_split');
|
|
45
|
+
expect(names).toContain('plan_reconcile');
|
|
46
|
+
expect(names).toContain('plan_complete_lifecycle');
|
|
47
|
+
expect(names).toContain('plan_dispatch');
|
|
48
|
+
expect(names).toContain('plan_review');
|
|
49
|
+
expect(names).toContain('plan_archive');
|
|
50
|
+
expect(names).toContain('plan_list_tasks');
|
|
51
|
+
expect(names).toContain('plan_stats');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should assign correct auth levels', () => {
|
|
55
|
+
expect(findOp('plan_iterate').auth).toBe('write');
|
|
56
|
+
expect(findOp('plan_split').auth).toBe('write');
|
|
57
|
+
expect(findOp('plan_reconcile').auth).toBe('write');
|
|
58
|
+
expect(findOp('plan_complete_lifecycle').auth).toBe('write');
|
|
59
|
+
expect(findOp('plan_dispatch').auth).toBe('read');
|
|
60
|
+
expect(findOp('plan_review').auth).toBe('write');
|
|
61
|
+
expect(findOp('plan_archive').auth).toBe('admin');
|
|
62
|
+
expect(findOp('plan_list_tasks').auth).toBe('read');
|
|
63
|
+
expect(findOp('plan_stats').auth).toBe('read');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ─── Helper: create a draft plan ─────────────────────────────────
|
|
67
|
+
function createDraftPlan() {
|
|
68
|
+
return runtime.planner.create({
|
|
69
|
+
objective: 'Test objective',
|
|
70
|
+
scope: 'Test scope',
|
|
71
|
+
tasks: [
|
|
72
|
+
{ title: 'Task A', description: 'Do A' },
|
|
73
|
+
{ title: 'Task B', description: 'Do B' },
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── plan_iterate ─────────────────────────────────────────────────
|
|
79
|
+
describe('plan_iterate', () => {
|
|
80
|
+
it('should update objective on a draft plan', async () => {
|
|
81
|
+
const plan = createDraftPlan();
|
|
82
|
+
const result = (await findOp('plan_iterate').handler({
|
|
83
|
+
planId: plan.id,
|
|
84
|
+
objective: 'Updated objective',
|
|
85
|
+
})) as { iterated: boolean; plan: { objective: string } };
|
|
86
|
+
expect(result.iterated).toBe(true);
|
|
87
|
+
expect(result.plan.objective).toBe('Updated objective');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should add tasks to a draft plan', async () => {
|
|
91
|
+
const plan = createDraftPlan();
|
|
92
|
+
const result = (await findOp('plan_iterate').handler({
|
|
93
|
+
planId: plan.id,
|
|
94
|
+
addTasks: [{ title: 'Task C', description: 'Do C' }],
|
|
95
|
+
})) as { iterated: boolean; plan: { tasks: unknown[] } };
|
|
96
|
+
expect(result.iterated).toBe(true);
|
|
97
|
+
expect(result.plan.tasks.length).toBe(3);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should remove tasks from a draft plan', async () => {
|
|
101
|
+
const plan = createDraftPlan();
|
|
102
|
+
const result = (await findOp('plan_iterate').handler({
|
|
103
|
+
planId: plan.id,
|
|
104
|
+
removeTasks: ['task-1'],
|
|
105
|
+
})) as { iterated: boolean; plan: { tasks: Array<{ id: string }> } };
|
|
106
|
+
expect(result.iterated).toBe(true);
|
|
107
|
+
expect(result.plan.tasks.length).toBe(1);
|
|
108
|
+
expect(result.plan.tasks[0].id).toBe('task-2');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should return error for non-draft plan', async () => {
|
|
112
|
+
const plan = createDraftPlan();
|
|
113
|
+
runtime.planner.approve(plan.id);
|
|
114
|
+
const result = (await findOp('plan_iterate').handler({
|
|
115
|
+
planId: plan.id,
|
|
116
|
+
objective: 'Updated',
|
|
117
|
+
})) as { error: string };
|
|
118
|
+
expect(result.error).toContain("must be 'draft'");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should return error for unknown plan', async () => {
|
|
122
|
+
const result = (await findOp('plan_iterate').handler({
|
|
123
|
+
planId: 'nonexistent',
|
|
124
|
+
objective: 'Updated',
|
|
125
|
+
})) as { error: string };
|
|
126
|
+
expect(result.error).toContain('Plan not found');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// ─── plan_split ───────────────────────────────────────────────────
|
|
131
|
+
describe('plan_split', () => {
|
|
132
|
+
it('should replace tasks with dependency tracking', async () => {
|
|
133
|
+
const plan = createDraftPlan();
|
|
134
|
+
const result = (await findOp('plan_split').handler({
|
|
135
|
+
planId: plan.id,
|
|
136
|
+
tasks: [
|
|
137
|
+
{ title: 'Setup', description: 'Environment setup' },
|
|
138
|
+
{ title: 'Implement', description: 'Core implementation', dependsOn: ['task-1'] },
|
|
139
|
+
{ title: 'Test', description: 'Write tests', dependsOn: ['task-2'] },
|
|
140
|
+
],
|
|
141
|
+
})) as { split: boolean; taskCount: number; plan: { tasks: Array<{ dependsOn?: string[] }> } };
|
|
142
|
+
expect(result.split).toBe(true);
|
|
143
|
+
expect(result.taskCount).toBe(3);
|
|
144
|
+
expect(result.plan.tasks[1].dependsOn).toEqual(['task-1']);
|
|
145
|
+
expect(result.plan.tasks[2].dependsOn).toEqual(['task-2']);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should reject invalid dependency references', async () => {
|
|
149
|
+
const plan = createDraftPlan();
|
|
150
|
+
const result = (await findOp('plan_split').handler({
|
|
151
|
+
planId: plan.id,
|
|
152
|
+
tasks: [
|
|
153
|
+
{ title: 'Task', description: 'Depends on nothing that exists', dependsOn: ['task-99'] },
|
|
154
|
+
],
|
|
155
|
+
})) as { error: string };
|
|
156
|
+
expect(result.error).toContain('unknown task');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should work on approved plans', async () => {
|
|
160
|
+
const plan = createDraftPlan();
|
|
161
|
+
runtime.planner.approve(plan.id);
|
|
162
|
+
const result = (await findOp('plan_split').handler({
|
|
163
|
+
planId: plan.id,
|
|
164
|
+
tasks: [
|
|
165
|
+
{ title: 'Only task', description: 'Single task' },
|
|
166
|
+
],
|
|
167
|
+
})) as { split: boolean; taskCount: number };
|
|
168
|
+
expect(result.split).toBe(true);
|
|
169
|
+
expect(result.taskCount).toBe(1);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should return error for executing plan', async () => {
|
|
173
|
+
const plan = createDraftPlan();
|
|
174
|
+
runtime.planner.approve(plan.id);
|
|
175
|
+
runtime.planner.startExecution(plan.id);
|
|
176
|
+
const result = (await findOp('plan_split').handler({
|
|
177
|
+
planId: plan.id,
|
|
178
|
+
tasks: [{ title: 'T', description: 'D' }],
|
|
179
|
+
})) as { error: string };
|
|
180
|
+
expect(result.error).toContain("must be 'draft' or 'approved'");
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// ─── plan_reconcile ───────────────────────────────────────────────
|
|
185
|
+
describe('plan_reconcile', () => {
|
|
186
|
+
it('should reconcile an executing plan', async () => {
|
|
187
|
+
const plan = createDraftPlan();
|
|
188
|
+
runtime.planner.approve(plan.id);
|
|
189
|
+
runtime.planner.startExecution(plan.id);
|
|
190
|
+
|
|
191
|
+
const result = (await findOp('plan_reconcile').handler({
|
|
192
|
+
planId: plan.id,
|
|
193
|
+
actualOutcome: 'Completed as planned with minor adjustments',
|
|
194
|
+
driftItems: [
|
|
195
|
+
{
|
|
196
|
+
type: 'modified',
|
|
197
|
+
description: 'Changed approach for Task B',
|
|
198
|
+
impact: 'low',
|
|
199
|
+
rationale: 'Found a simpler way',
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
})) as { reconciled: boolean; accuracy: number; driftCount: number };
|
|
203
|
+
expect(result.reconciled).toBe(true);
|
|
204
|
+
expect(result.accuracy).toBe(50); // 1 drift out of 2 tasks = 50%
|
|
205
|
+
expect(result.driftCount).toBe(1);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should mark executing plan as completed after reconcile', async () => {
|
|
209
|
+
const plan = createDraftPlan();
|
|
210
|
+
runtime.planner.approve(plan.id);
|
|
211
|
+
runtime.planner.startExecution(plan.id);
|
|
212
|
+
|
|
213
|
+
const result = (await findOp('plan_reconcile').handler({
|
|
214
|
+
planId: plan.id,
|
|
215
|
+
actualOutcome: 'Done',
|
|
216
|
+
})) as { reconciled: boolean; plan: { status: string } };
|
|
217
|
+
expect(result.plan.status).toBe('completed');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should return 100% accuracy with no drift items', async () => {
|
|
221
|
+
const plan = createDraftPlan();
|
|
222
|
+
runtime.planner.approve(plan.id);
|
|
223
|
+
runtime.planner.startExecution(plan.id);
|
|
224
|
+
|
|
225
|
+
const result = (await findOp('plan_reconcile').handler({
|
|
226
|
+
planId: plan.id,
|
|
227
|
+
actualOutcome: 'Perfect execution',
|
|
228
|
+
})) as { accuracy: number; driftCount: number };
|
|
229
|
+
expect(result.accuracy).toBe(100);
|
|
230
|
+
expect(result.driftCount).toBe(0);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should return error for draft plan', async () => {
|
|
234
|
+
const plan = createDraftPlan();
|
|
235
|
+
const result = (await findOp('plan_reconcile').handler({
|
|
236
|
+
planId: plan.id,
|
|
237
|
+
actualOutcome: 'Done',
|
|
238
|
+
})) as { error: string };
|
|
239
|
+
expect(result.error).toContain("must be 'executing' or 'completed'");
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// ─── plan_complete_lifecycle ──────────────────────────────────────
|
|
244
|
+
describe('plan_complete_lifecycle', () => {
|
|
245
|
+
it('should capture patterns and anti-patterns into vault', async () => {
|
|
246
|
+
const plan = createDraftPlan();
|
|
247
|
+
runtime.planner.approve(plan.id);
|
|
248
|
+
runtime.planner.startExecution(plan.id);
|
|
249
|
+
runtime.planner.complete(plan.id);
|
|
250
|
+
|
|
251
|
+
const result = (await findOp('plan_complete_lifecycle').handler({
|
|
252
|
+
planId: plan.id,
|
|
253
|
+
patterns: ['Always write tests first'],
|
|
254
|
+
antiPatterns: ['Do not skip code review'],
|
|
255
|
+
})) as {
|
|
256
|
+
completed: boolean;
|
|
257
|
+
knowledgeCaptured: number;
|
|
258
|
+
patternsAdded: number;
|
|
259
|
+
antiPatternsAdded: number;
|
|
260
|
+
};
|
|
261
|
+
expect(result.completed).toBe(true);
|
|
262
|
+
expect(result.knowledgeCaptured).toBe(2);
|
|
263
|
+
expect(result.patternsAdded).toBe(1);
|
|
264
|
+
expect(result.antiPatternsAdded).toBe(1);
|
|
265
|
+
|
|
266
|
+
// Verify entries are in vault
|
|
267
|
+
const stats = runtime.vault.stats();
|
|
268
|
+
expect(stats.totalEntries).toBeGreaterThanOrEqual(2);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should work with no patterns or anti-patterns', async () => {
|
|
272
|
+
const plan = createDraftPlan();
|
|
273
|
+
runtime.planner.approve(plan.id);
|
|
274
|
+
runtime.planner.startExecution(plan.id);
|
|
275
|
+
runtime.planner.complete(plan.id);
|
|
276
|
+
|
|
277
|
+
const result = (await findOp('plan_complete_lifecycle').handler({
|
|
278
|
+
planId: plan.id,
|
|
279
|
+
})) as { completed: boolean; knowledgeCaptured: number };
|
|
280
|
+
expect(result.completed).toBe(true);
|
|
281
|
+
expect(result.knowledgeCaptured).toBe(0);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should return error for non-completed plan', async () => {
|
|
285
|
+
const plan = createDraftPlan();
|
|
286
|
+
const result = (await findOp('plan_complete_lifecycle').handler({
|
|
287
|
+
planId: plan.id,
|
|
288
|
+
patterns: ['Test'],
|
|
289
|
+
})) as { error: string };
|
|
290
|
+
expect(result.error).toContain('must be completed');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should return error for unknown plan', async () => {
|
|
294
|
+
const result = (await findOp('plan_complete_lifecycle').handler({
|
|
295
|
+
planId: 'nonexistent',
|
|
296
|
+
})) as { error: string };
|
|
297
|
+
expect(result.error).toContain('Plan not found');
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// ─── plan_dispatch ────────────────────────────────────────────────
|
|
302
|
+
describe('plan_dispatch', () => {
|
|
303
|
+
it('should return task and ready status when no dependencies', async () => {
|
|
304
|
+
const plan = createDraftPlan();
|
|
305
|
+
const result = (await findOp('plan_dispatch').handler({
|
|
306
|
+
planId: plan.id,
|
|
307
|
+
taskId: 'task-1',
|
|
308
|
+
})) as { task: { id: string }; unmetDependencies: unknown[]; ready: boolean };
|
|
309
|
+
expect(result.task.id).toBe('task-1');
|
|
310
|
+
expect(result.unmetDependencies).toEqual([]);
|
|
311
|
+
expect(result.ready).toBe(true);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should report unmet dependencies', async () => {
|
|
315
|
+
const plan = createDraftPlan();
|
|
316
|
+
// Split with dependencies
|
|
317
|
+
runtime.planner.splitTasks(plan.id, [
|
|
318
|
+
{ title: 'First', description: 'Do first' },
|
|
319
|
+
{ title: 'Second', description: 'Do second', dependsOn: ['task-1'] },
|
|
320
|
+
]);
|
|
321
|
+
|
|
322
|
+
const result = (await findOp('plan_dispatch').handler({
|
|
323
|
+
planId: plan.id,
|
|
324
|
+
taskId: 'task-2',
|
|
325
|
+
})) as { task: { id: string }; unmetDependencies: Array<{ id: string }>; ready: boolean };
|
|
326
|
+
expect(result.ready).toBe(false);
|
|
327
|
+
expect(result.unmetDependencies.length).toBe(1);
|
|
328
|
+
expect(result.unmetDependencies[0].id).toBe('task-1');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should report ready when dependencies are completed', async () => {
|
|
332
|
+
const plan = createDraftPlan();
|
|
333
|
+
runtime.planner.splitTasks(plan.id, [
|
|
334
|
+
{ title: 'First', description: 'Do first' },
|
|
335
|
+
{ title: 'Second', description: 'Do second', dependsOn: ['task-1'] },
|
|
336
|
+
]);
|
|
337
|
+
runtime.planner.approve(plan.id);
|
|
338
|
+
runtime.planner.startExecution(plan.id);
|
|
339
|
+
runtime.planner.updateTask(plan.id, 'task-1', 'completed');
|
|
340
|
+
|
|
341
|
+
const result = (await findOp('plan_dispatch').handler({
|
|
342
|
+
planId: plan.id,
|
|
343
|
+
taskId: 'task-2',
|
|
344
|
+
})) as { ready: boolean; unmetDependencies: unknown[] };
|
|
345
|
+
expect(result.ready).toBe(true);
|
|
346
|
+
expect(result.unmetDependencies).toEqual([]);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('should return error for unknown task', async () => {
|
|
350
|
+
const plan = createDraftPlan();
|
|
351
|
+
const result = (await findOp('plan_dispatch').handler({
|
|
352
|
+
planId: plan.id,
|
|
353
|
+
taskId: 'task-99',
|
|
354
|
+
})) as { error: string };
|
|
355
|
+
expect(result.error).toContain('Task not found');
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// ─── plan_review ──────────────────────────────────────────────────
|
|
360
|
+
describe('plan_review', () => {
|
|
361
|
+
it('should add a review to a plan', async () => {
|
|
362
|
+
const plan = createDraftPlan();
|
|
363
|
+
const result = (await findOp('plan_review').handler({
|
|
364
|
+
planId: plan.id,
|
|
365
|
+
reviewer: 'Alice',
|
|
366
|
+
outcome: 'approved',
|
|
367
|
+
comments: 'Looks good',
|
|
368
|
+
})) as { reviewed: boolean; totalReviews: number };
|
|
369
|
+
expect(result.reviewed).toBe(true);
|
|
370
|
+
expect(result.totalReviews).toBe(1);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('should add a task-level review', async () => {
|
|
374
|
+
const plan = createDraftPlan();
|
|
375
|
+
const result = (await findOp('plan_review').handler({
|
|
376
|
+
planId: plan.id,
|
|
377
|
+
taskId: 'task-1',
|
|
378
|
+
reviewer: 'Bob',
|
|
379
|
+
outcome: 'needs_changes',
|
|
380
|
+
comments: 'Needs more detail',
|
|
381
|
+
})) as { reviewed: boolean; totalReviews: number };
|
|
382
|
+
expect(result.reviewed).toBe(true);
|
|
383
|
+
expect(result.totalReviews).toBe(1);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('should accumulate multiple reviews', async () => {
|
|
387
|
+
const plan = createDraftPlan();
|
|
388
|
+
await findOp('plan_review').handler({
|
|
389
|
+
planId: plan.id,
|
|
390
|
+
reviewer: 'Alice',
|
|
391
|
+
outcome: 'approved',
|
|
392
|
+
comments: 'LGTM',
|
|
393
|
+
});
|
|
394
|
+
const result = (await findOp('plan_review').handler({
|
|
395
|
+
planId: plan.id,
|
|
396
|
+
reviewer: 'Bob',
|
|
397
|
+
outcome: 'approved',
|
|
398
|
+
comments: 'Also LGTM',
|
|
399
|
+
})) as { totalReviews: number };
|
|
400
|
+
expect(result.totalReviews).toBe(2);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('should return error for unknown task', async () => {
|
|
404
|
+
const plan = createDraftPlan();
|
|
405
|
+
const result = (await findOp('plan_review').handler({
|
|
406
|
+
planId: plan.id,
|
|
407
|
+
taskId: 'task-99',
|
|
408
|
+
reviewer: 'Alice',
|
|
409
|
+
outcome: 'approved',
|
|
410
|
+
comments: 'Ok',
|
|
411
|
+
})) as { error: string };
|
|
412
|
+
expect(result.error).toContain('Task not found');
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// ─── plan_archive ─────────────────────────────────────────────────
|
|
417
|
+
describe('plan_archive', () => {
|
|
418
|
+
it('should archive old completed plans', async () => {
|
|
419
|
+
const plan = createDraftPlan();
|
|
420
|
+
runtime.planner.approve(plan.id);
|
|
421
|
+
runtime.planner.startExecution(plan.id);
|
|
422
|
+
runtime.planner.complete(plan.id);
|
|
423
|
+
|
|
424
|
+
// Hack: set updatedAt to 60 days ago
|
|
425
|
+
const stored = runtime.planner.get(plan.id)!;
|
|
426
|
+
(stored as { updatedAt: number }).updatedAt = Date.now() - 60 * 24 * 60 * 60 * 1000;
|
|
427
|
+
// Force save by calling a no-op iterate (not possible on completed plan)
|
|
428
|
+
// Instead, create another plan and archive to trigger save
|
|
429
|
+
// We need to directly manipulate — use archive with 0 days to catch it
|
|
430
|
+
const result = (await findOp('plan_archive').handler({
|
|
431
|
+
olderThanDays: 0,
|
|
432
|
+
})) as { archived: number; plans: Array<{ id: string }> };
|
|
433
|
+
expect(result.archived).toBe(1);
|
|
434
|
+
expect(result.plans[0].id).toBe(plan.id);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('should not archive non-completed plans', async () => {
|
|
438
|
+
createDraftPlan(); // draft plan
|
|
439
|
+
const result = (await findOp('plan_archive').handler({
|
|
440
|
+
olderThanDays: 0,
|
|
441
|
+
})) as { archived: number };
|
|
442
|
+
expect(result.archived).toBe(0);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('should not archive recent completed plans', async () => {
|
|
446
|
+
const plan = createDraftPlan();
|
|
447
|
+
runtime.planner.approve(plan.id);
|
|
448
|
+
runtime.planner.startExecution(plan.id);
|
|
449
|
+
runtime.planner.complete(plan.id);
|
|
450
|
+
|
|
451
|
+
const result = (await findOp('plan_archive').handler({
|
|
452
|
+
olderThanDays: 30,
|
|
453
|
+
})) as { archived: number };
|
|
454
|
+
expect(result.archived).toBe(0);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// ─── plan_list_tasks ──────────────────────────────────────────────
|
|
459
|
+
describe('plan_list_tasks', () => {
|
|
460
|
+
it('should list all tasks for a plan', async () => {
|
|
461
|
+
const plan = createDraftPlan();
|
|
462
|
+
const result = (await findOp('plan_list_tasks').handler({
|
|
463
|
+
planId: plan.id,
|
|
464
|
+
})) as { planId: string; total: number; filtered: number; tasks: unknown[] };
|
|
465
|
+
expect(result.planId).toBe(plan.id);
|
|
466
|
+
expect(result.total).toBe(2);
|
|
467
|
+
expect(result.filtered).toBe(2);
|
|
468
|
+
expect(result.tasks.length).toBe(2);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it('should filter tasks by status', async () => {
|
|
472
|
+
const plan = createDraftPlan();
|
|
473
|
+
runtime.planner.approve(plan.id);
|
|
474
|
+
runtime.planner.startExecution(plan.id);
|
|
475
|
+
runtime.planner.updateTask(plan.id, 'task-1', 'completed');
|
|
476
|
+
|
|
477
|
+
const result = (await findOp('plan_list_tasks').handler({
|
|
478
|
+
planId: plan.id,
|
|
479
|
+
status: 'completed',
|
|
480
|
+
})) as { total: number; filtered: number; tasks: Array<{ id: string }> };
|
|
481
|
+
expect(result.total).toBe(2);
|
|
482
|
+
expect(result.filtered).toBe(1);
|
|
483
|
+
expect(result.tasks[0].id).toBe('task-1');
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('should return empty when no tasks match filter', async () => {
|
|
487
|
+
const plan = createDraftPlan();
|
|
488
|
+
const result = (await findOp('plan_list_tasks').handler({
|
|
489
|
+
planId: plan.id,
|
|
490
|
+
status: 'failed',
|
|
491
|
+
})) as { filtered: number; tasks: unknown[] };
|
|
492
|
+
expect(result.filtered).toBe(0);
|
|
493
|
+
expect(result.tasks).toEqual([]);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('should return error for unknown plan', async () => {
|
|
497
|
+
const result = (await findOp('plan_list_tasks').handler({
|
|
498
|
+
planId: 'nonexistent',
|
|
499
|
+
})) as { error: string };
|
|
500
|
+
expect(result.error).toContain('Plan not found');
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// ─── plan_stats ───────────────────────────────────────────────────
|
|
505
|
+
describe('plan_stats', () => {
|
|
506
|
+
it('should return zero stats when no plans exist', async () => {
|
|
507
|
+
const result = (await findOp('plan_stats').handler({})) as {
|
|
508
|
+
total: number;
|
|
509
|
+
byStatus: Record<string, number>;
|
|
510
|
+
avgTasksPerPlan: number;
|
|
511
|
+
totalTasks: number;
|
|
512
|
+
tasksByStatus: Record<string, number>;
|
|
513
|
+
};
|
|
514
|
+
expect(result.total).toBe(0);
|
|
515
|
+
expect(result.byStatus.draft).toBe(0);
|
|
516
|
+
expect(result.avgTasksPerPlan).toBe(0);
|
|
517
|
+
expect(result.totalTasks).toBe(0);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('should return correct stats with plans', async () => {
|
|
521
|
+
createDraftPlan(); // draft with 2 tasks
|
|
522
|
+
const plan2 = runtime.planner.create({
|
|
523
|
+
objective: 'Plan 2',
|
|
524
|
+
scope: 'Scope 2',
|
|
525
|
+
tasks: [{ title: 'T1', description: 'D1' }],
|
|
526
|
+
});
|
|
527
|
+
runtime.planner.approve(plan2.id);
|
|
528
|
+
runtime.planner.startExecution(plan2.id);
|
|
529
|
+
runtime.planner.updateTask(plan2.id, 'task-1', 'completed');
|
|
530
|
+
runtime.planner.complete(plan2.id);
|
|
531
|
+
|
|
532
|
+
const result = (await findOp('plan_stats').handler({})) as {
|
|
533
|
+
total: number;
|
|
534
|
+
byStatus: Record<string, number>;
|
|
535
|
+
avgTasksPerPlan: number;
|
|
536
|
+
totalTasks: number;
|
|
537
|
+
tasksByStatus: Record<string, number>;
|
|
538
|
+
};
|
|
539
|
+
expect(result.total).toBe(2);
|
|
540
|
+
expect(result.byStatus.draft).toBe(1);
|
|
541
|
+
expect(result.byStatus.completed).toBe(1);
|
|
542
|
+
expect(result.totalTasks).toBe(3);
|
|
543
|
+
expect(result.avgTasksPerPlan).toBe(1.5);
|
|
544
|
+
expect(result.tasksByStatus.pending).toBe(2);
|
|
545
|
+
expect(result.tasksByStatus.completed).toBe(1);
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
});
|