@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
package/src/planning/planner.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
|
+
import type { PlanGap } from './gap-types.js';
|
|
4
|
+
import { SEVERITY_WEIGHTS, CATEGORY_PENALTY_CAPS } from './gap-types.js';
|
|
5
|
+
import { runGapAnalysis } from './gap-analysis.js';
|
|
6
|
+
import type { GapAnalysisOptions } from './gap-analysis.js';
|
|
3
7
|
|
|
4
8
|
export type PlanStatus = 'draft' | 'approved' | 'executing' | 'completed';
|
|
5
9
|
export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'skipped' | 'failed';
|
|
@@ -9,9 +13,81 @@ export interface PlanTask {
|
|
|
9
13
|
title: string;
|
|
10
14
|
description: string;
|
|
11
15
|
status: TaskStatus;
|
|
16
|
+
/** Optional dependency IDs — tasks that must complete before this one. */
|
|
17
|
+
dependsOn?: string[];
|
|
12
18
|
updatedAt: number;
|
|
13
19
|
}
|
|
14
20
|
|
|
21
|
+
export interface DriftItem {
|
|
22
|
+
type: 'skipped' | 'added' | 'modified' | 'reordered';
|
|
23
|
+
description: string;
|
|
24
|
+
impact: 'low' | 'medium' | 'high';
|
|
25
|
+
rationale: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ReconciliationReport {
|
|
29
|
+
planId: string;
|
|
30
|
+
accuracy: number;
|
|
31
|
+
driftItems: DriftItem[];
|
|
32
|
+
summary: string;
|
|
33
|
+
reconciledAt: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ReviewEvidence {
|
|
37
|
+
planId: string;
|
|
38
|
+
taskId?: string;
|
|
39
|
+
reviewer: string;
|
|
40
|
+
outcome: 'approved' | 'rejected' | 'needs_changes';
|
|
41
|
+
comments: string;
|
|
42
|
+
reviewedAt: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type PlanGrade = 'A+' | 'A' | 'B' | 'C' | 'D' | 'F';
|
|
46
|
+
|
|
47
|
+
export interface PlanCheck {
|
|
48
|
+
checkId: string;
|
|
49
|
+
planId: string;
|
|
50
|
+
grade: PlanGrade;
|
|
51
|
+
score: number; // 0-100
|
|
52
|
+
gaps: PlanGap[];
|
|
53
|
+
iteration: number;
|
|
54
|
+
checkedAt: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Calculate score from gaps with severity-weighted deductions and iteration leniency.
|
|
59
|
+
* Ported from Salvador MCP's plan-grading.ts.
|
|
60
|
+
*
|
|
61
|
+
* - Minor gaps: weight=0 on iteration 1 (free sketching), weight=1 on iteration 2, full weight on 3+
|
|
62
|
+
* - Per-category deductions are capped before summing (prevents one category from tanking the score)
|
|
63
|
+
* - Score = max(0, 100 - totalDeductions)
|
|
64
|
+
*/
|
|
65
|
+
export function calculateScore(gaps: PlanGap[], iteration: number = 1): number {
|
|
66
|
+
const categoryTotals = new Map<string, number>();
|
|
67
|
+
|
|
68
|
+
for (const gap of gaps) {
|
|
69
|
+
let weight: number = SEVERITY_WEIGHTS[gap.severity];
|
|
70
|
+
|
|
71
|
+
// Iteration leniency for minor gaps
|
|
72
|
+
if (gap.severity === 'minor') {
|
|
73
|
+
if (iteration === 1) weight = 0;
|
|
74
|
+
else if (iteration === 2) weight = 1;
|
|
75
|
+
// iteration 3+: full weight (2)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const category = gap.category;
|
|
79
|
+
categoryTotals.set(category, (categoryTotals.get(category) ?? 0) + weight);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let deductions = 0;
|
|
83
|
+
for (const [category, total] of categoryTotals) {
|
|
84
|
+
const cap = CATEGORY_PENALTY_CAPS[category];
|
|
85
|
+
deductions += cap !== undefined ? Math.min(total, cap) : total;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return Math.max(0, 100 - deductions);
|
|
89
|
+
}
|
|
90
|
+
|
|
15
91
|
export interface Plan {
|
|
16
92
|
id: string;
|
|
17
93
|
objective: string;
|
|
@@ -19,6 +95,14 @@ export interface Plan {
|
|
|
19
95
|
status: PlanStatus;
|
|
20
96
|
decisions: string[];
|
|
21
97
|
tasks: PlanTask[];
|
|
98
|
+
/** Reconciliation report — populated by reconcile(). */
|
|
99
|
+
reconciliation?: ReconciliationReport;
|
|
100
|
+
/** Review evidence — populated by addReview(). */
|
|
101
|
+
reviews?: ReviewEvidence[];
|
|
102
|
+
/** Latest grading check. */
|
|
103
|
+
latestCheck?: PlanCheck;
|
|
104
|
+
/** All check history. */
|
|
105
|
+
checks: PlanCheck[];
|
|
22
106
|
createdAt: number;
|
|
23
107
|
updatedAt: number;
|
|
24
108
|
}
|
|
@@ -31,9 +115,11 @@ export interface PlanStore {
|
|
|
31
115
|
export class Planner {
|
|
32
116
|
private filePath: string;
|
|
33
117
|
private store: PlanStore;
|
|
118
|
+
private gapOptions?: GapAnalysisOptions;
|
|
34
119
|
|
|
35
|
-
constructor(filePath: string) {
|
|
120
|
+
constructor(filePath: string, gapOptions?: GapAnalysisOptions) {
|
|
36
121
|
this.filePath = filePath;
|
|
122
|
+
this.gapOptions = gapOptions;
|
|
37
123
|
this.store = this.load();
|
|
38
124
|
}
|
|
39
125
|
|
|
@@ -43,7 +129,12 @@ export class Planner {
|
|
|
43
129
|
}
|
|
44
130
|
try {
|
|
45
131
|
const data = readFileSync(this.filePath, 'utf-8');
|
|
46
|
-
|
|
132
|
+
const store = JSON.parse(data) as PlanStore;
|
|
133
|
+
// Backward compatibility: ensure every plan has a checks array
|
|
134
|
+
for (const plan of store.plans) {
|
|
135
|
+
plan.checks = plan.checks ?? [];
|
|
136
|
+
}
|
|
137
|
+
return store;
|
|
47
138
|
} catch {
|
|
48
139
|
return { version: '1.0', plans: [] };
|
|
49
140
|
}
|
|
@@ -74,6 +165,7 @@ export class Planner {
|
|
|
74
165
|
status: 'pending' as TaskStatus,
|
|
75
166
|
updatedAt: now,
|
|
76
167
|
})),
|
|
168
|
+
checks: [],
|
|
77
169
|
createdAt: now,
|
|
78
170
|
updatedAt: now,
|
|
79
171
|
};
|
|
@@ -148,4 +240,388 @@ export class Planner {
|
|
|
148
240
|
(p) => p.status === 'draft' || p.status === 'approved' || p.status === 'executing',
|
|
149
241
|
);
|
|
150
242
|
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Iterate on a draft plan — modify objective, scope, decisions, or tasks.
|
|
246
|
+
* Only allowed on plans in 'draft' status.
|
|
247
|
+
*/
|
|
248
|
+
iterate(
|
|
249
|
+
planId: string,
|
|
250
|
+
changes: {
|
|
251
|
+
objective?: string;
|
|
252
|
+
scope?: string;
|
|
253
|
+
decisions?: string[];
|
|
254
|
+
addTasks?: Array<{ title: string; description: string }>;
|
|
255
|
+
removeTasks?: string[];
|
|
256
|
+
},
|
|
257
|
+
): Plan {
|
|
258
|
+
const plan = this.get(planId);
|
|
259
|
+
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
260
|
+
if (plan.status !== 'draft')
|
|
261
|
+
throw new Error(`Cannot iterate plan in '${plan.status}' status — must be 'draft'`);
|
|
262
|
+
|
|
263
|
+
const now = Date.now();
|
|
264
|
+
if (changes.objective !== undefined) plan.objective = changes.objective;
|
|
265
|
+
if (changes.scope !== undefined) plan.scope = changes.scope;
|
|
266
|
+
if (changes.decisions !== undefined) plan.decisions = changes.decisions;
|
|
267
|
+
|
|
268
|
+
// Remove tasks by ID
|
|
269
|
+
if (changes.removeTasks && changes.removeTasks.length > 0) {
|
|
270
|
+
const removeSet = new Set(changes.removeTasks);
|
|
271
|
+
plan.tasks = plan.tasks.filter((t) => !removeSet.has(t.id));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Add new tasks
|
|
275
|
+
if (changes.addTasks && changes.addTasks.length > 0) {
|
|
276
|
+
const maxIndex = plan.tasks.reduce((max, t) => {
|
|
277
|
+
const num = parseInt(t.id.replace('task-', ''), 10);
|
|
278
|
+
return isNaN(num) ? max : Math.max(max, num);
|
|
279
|
+
}, 0);
|
|
280
|
+
for (let i = 0; i < changes.addTasks.length; i++) {
|
|
281
|
+
plan.tasks.push({
|
|
282
|
+
id: `task-${maxIndex + i + 1}`,
|
|
283
|
+
title: changes.addTasks[i].title,
|
|
284
|
+
description: changes.addTasks[i].description,
|
|
285
|
+
status: 'pending',
|
|
286
|
+
updatedAt: now,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
plan.updatedAt = now;
|
|
292
|
+
this.save();
|
|
293
|
+
return plan;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Split a plan's tasks into sub-tasks with dependency tracking.
|
|
298
|
+
* Replaces existing tasks with a new set that includes dependency references.
|
|
299
|
+
* Only allowed on 'draft' or 'approved' plans.
|
|
300
|
+
*/
|
|
301
|
+
splitTasks(
|
|
302
|
+
planId: string,
|
|
303
|
+
tasks: Array<{ title: string; description: string; dependsOn?: string[] }>,
|
|
304
|
+
): Plan {
|
|
305
|
+
const plan = this.get(planId);
|
|
306
|
+
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
307
|
+
if (plan.status !== 'draft' && plan.status !== 'approved')
|
|
308
|
+
throw new Error(
|
|
309
|
+
`Cannot split tasks on plan in '${plan.status}' status — must be 'draft' or 'approved'`,
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
const now = Date.now();
|
|
313
|
+
plan.tasks = tasks.map((t, i) => ({
|
|
314
|
+
id: `task-${i + 1}`,
|
|
315
|
+
title: t.title,
|
|
316
|
+
description: t.description,
|
|
317
|
+
status: 'pending' as TaskStatus,
|
|
318
|
+
dependsOn: t.dependsOn,
|
|
319
|
+
updatedAt: now,
|
|
320
|
+
}));
|
|
321
|
+
|
|
322
|
+
// Validate dependency references
|
|
323
|
+
const taskIds = new Set(plan.tasks.map((t) => t.id));
|
|
324
|
+
for (const task of plan.tasks) {
|
|
325
|
+
if (task.dependsOn) {
|
|
326
|
+
for (const dep of task.dependsOn) {
|
|
327
|
+
if (!taskIds.has(dep)) {
|
|
328
|
+
throw new Error(`Task '${task.id}' depends on unknown task '${dep}'`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
plan.updatedAt = now;
|
|
335
|
+
this.save();
|
|
336
|
+
return plan;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Reconcile a plan — compare what was planned vs what actually happened.
|
|
341
|
+
* Only allowed on 'executing' or 'completed' plans.
|
|
342
|
+
*/
|
|
343
|
+
reconcile(
|
|
344
|
+
planId: string,
|
|
345
|
+
report: {
|
|
346
|
+
actualOutcome: string;
|
|
347
|
+
driftItems?: DriftItem[];
|
|
348
|
+
},
|
|
349
|
+
): Plan {
|
|
350
|
+
const plan = this.get(planId);
|
|
351
|
+
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
352
|
+
if (plan.status !== 'executing' && plan.status !== 'completed')
|
|
353
|
+
throw new Error(
|
|
354
|
+
`Cannot reconcile plan in '${plan.status}' status — must be 'executing' or 'completed'`,
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const driftItems = report.driftItems ?? [];
|
|
358
|
+
const totalTasks = plan.tasks.length;
|
|
359
|
+
const driftCount = driftItems.length;
|
|
360
|
+
const accuracy = totalTasks > 0 ? Math.round(((totalTasks - driftCount) / totalTasks) * 100) : 100;
|
|
361
|
+
|
|
362
|
+
plan.reconciliation = {
|
|
363
|
+
planId,
|
|
364
|
+
accuracy: Math.max(0, Math.min(100, accuracy)),
|
|
365
|
+
driftItems,
|
|
366
|
+
summary: report.actualOutcome,
|
|
367
|
+
reconciledAt: Date.now(),
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// If still executing, mark completed
|
|
371
|
+
if (plan.status === 'executing') {
|
|
372
|
+
plan.status = 'completed';
|
|
373
|
+
}
|
|
374
|
+
plan.updatedAt = Date.now();
|
|
375
|
+
this.save();
|
|
376
|
+
return plan;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Add review evidence to a plan or specific task.
|
|
381
|
+
*/
|
|
382
|
+
addReview(
|
|
383
|
+
planId: string,
|
|
384
|
+
review: {
|
|
385
|
+
taskId?: string;
|
|
386
|
+
reviewer: string;
|
|
387
|
+
outcome: 'approved' | 'rejected' | 'needs_changes';
|
|
388
|
+
comments: string;
|
|
389
|
+
},
|
|
390
|
+
): Plan {
|
|
391
|
+
const plan = this.get(planId);
|
|
392
|
+
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
393
|
+
|
|
394
|
+
if (review.taskId) {
|
|
395
|
+
const task = plan.tasks.find((t) => t.id === review.taskId);
|
|
396
|
+
if (!task) throw new Error(`Task not found: ${review.taskId}`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!plan.reviews) plan.reviews = [];
|
|
400
|
+
plan.reviews.push({
|
|
401
|
+
planId,
|
|
402
|
+
taskId: review.taskId,
|
|
403
|
+
reviewer: review.reviewer,
|
|
404
|
+
outcome: review.outcome,
|
|
405
|
+
comments: review.comments,
|
|
406
|
+
reviewedAt: Date.now(),
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
plan.updatedAt = Date.now();
|
|
410
|
+
this.save();
|
|
411
|
+
return plan;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Get dispatch instructions for a specific task — returns the task and its
|
|
416
|
+
* unmet dependencies so a subagent knows what to work on and what to wait for.
|
|
417
|
+
*/
|
|
418
|
+
getDispatch(
|
|
419
|
+
planId: string,
|
|
420
|
+
taskId: string,
|
|
421
|
+
): { task: PlanTask; unmetDependencies: PlanTask[]; ready: boolean } {
|
|
422
|
+
const plan = this.get(planId);
|
|
423
|
+
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
424
|
+
const task = plan.tasks.find((t) => t.id === taskId);
|
|
425
|
+
if (!task) throw new Error(`Task not found: ${taskId}`);
|
|
426
|
+
|
|
427
|
+
const unmetDependencies: PlanTask[] = [];
|
|
428
|
+
if (task.dependsOn) {
|
|
429
|
+
for (const depId of task.dependsOn) {
|
|
430
|
+
const dep = plan.tasks.find((t) => t.id === depId);
|
|
431
|
+
if (dep && dep.status !== 'completed') {
|
|
432
|
+
unmetDependencies.push(dep);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return { task, unmetDependencies, ready: unmetDependencies.length === 0 };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Archive completed plans older than the given number of days.
|
|
442
|
+
* Removes them from the active store and returns the archived plans.
|
|
443
|
+
*/
|
|
444
|
+
archive(olderThanDays: number): Plan[] {
|
|
445
|
+
const cutoff = Date.now() - olderThanDays * 24 * 60 * 60 * 1000;
|
|
446
|
+
const toArchive = this.store.plans.filter(
|
|
447
|
+
(p) => p.status === 'completed' && p.updatedAt < cutoff,
|
|
448
|
+
);
|
|
449
|
+
if (toArchive.length > 0) {
|
|
450
|
+
this.store.plans = this.store.plans.filter(
|
|
451
|
+
(p) => !(p.status === 'completed' && p.updatedAt < cutoff),
|
|
452
|
+
);
|
|
453
|
+
this.save();
|
|
454
|
+
}
|
|
455
|
+
return toArchive;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Get statistics about all plans.
|
|
460
|
+
*/
|
|
461
|
+
stats(): {
|
|
462
|
+
total: number;
|
|
463
|
+
byStatus: Record<PlanStatus, number>;
|
|
464
|
+
avgTasksPerPlan: number;
|
|
465
|
+
totalTasks: number;
|
|
466
|
+
tasksByStatus: Record<TaskStatus, number>;
|
|
467
|
+
} {
|
|
468
|
+
const plans = this.store.plans;
|
|
469
|
+
const byStatus: Record<PlanStatus, number> = { draft: 0, approved: 0, executing: 0, completed: 0 };
|
|
470
|
+
const tasksByStatus: Record<TaskStatus, number> = {
|
|
471
|
+
pending: 0,
|
|
472
|
+
in_progress: 0,
|
|
473
|
+
completed: 0,
|
|
474
|
+
skipped: 0,
|
|
475
|
+
failed: 0,
|
|
476
|
+
};
|
|
477
|
+
let totalTasks = 0;
|
|
478
|
+
|
|
479
|
+
for (const p of plans) {
|
|
480
|
+
byStatus[p.status]++;
|
|
481
|
+
totalTasks += p.tasks.length;
|
|
482
|
+
for (const t of p.tasks) {
|
|
483
|
+
tasksByStatus[t.status]++;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return {
|
|
488
|
+
total: plans.length,
|
|
489
|
+
byStatus,
|
|
490
|
+
avgTasksPerPlan: plans.length > 0 ? Math.round((totalTasks / plans.length) * 100) / 100 : 0,
|
|
491
|
+
totalTasks,
|
|
492
|
+
tasksByStatus,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// ─── Grading ──────────────────────────────────────────────────────
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Grade a plan using 6-pass gap analysis with severity-weighted scoring.
|
|
500
|
+
* Ported from Salvador MCP's multi-pass grading engine.
|
|
501
|
+
*
|
|
502
|
+
* Scoring:
|
|
503
|
+
* - Each gap has a severity (critical=30, major=15, minor=2, info=0)
|
|
504
|
+
* - Deductions are per-category with optional caps
|
|
505
|
+
* - Iteration leniency: minor gaps free on iter 1, half on iter 2, full on 3+
|
|
506
|
+
* - Score = max(0, 100 - deductions)
|
|
507
|
+
*
|
|
508
|
+
* Grade thresholds: A+=95, A=90, B=80, C=70, D=60, F=<60
|
|
509
|
+
*/
|
|
510
|
+
grade(planId: string): PlanCheck {
|
|
511
|
+
const plan = this.get(planId);
|
|
512
|
+
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
513
|
+
|
|
514
|
+
// Run 6-pass gap analysis
|
|
515
|
+
const gaps = runGapAnalysis(plan, this.gapOptions);
|
|
516
|
+
|
|
517
|
+
// Add circular dependency check (structural, not covered by gap-analysis passes)
|
|
518
|
+
if (this.hasCircularDependencies(plan)) {
|
|
519
|
+
gaps.push({
|
|
520
|
+
id: `gap_${Date.now()}_circ`,
|
|
521
|
+
severity: 'critical',
|
|
522
|
+
category: 'structure',
|
|
523
|
+
description: 'Circular dependencies detected among tasks.',
|
|
524
|
+
recommendation: 'Remove circular dependency chains so tasks can be executed in order.',
|
|
525
|
+
location: 'tasks',
|
|
526
|
+
_trigger: 'circular_dependencies',
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Iteration = number of previous checks + 1
|
|
531
|
+
const iteration = plan.checks.length + 1;
|
|
532
|
+
const score = calculateScore(gaps, iteration);
|
|
533
|
+
const grade = this.scoreToGrade(score);
|
|
534
|
+
|
|
535
|
+
const check: PlanCheck = {
|
|
536
|
+
checkId: `chk-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
537
|
+
planId,
|
|
538
|
+
grade,
|
|
539
|
+
score,
|
|
540
|
+
gaps,
|
|
541
|
+
iteration,
|
|
542
|
+
checkedAt: Date.now(),
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
plan.checks.push(check);
|
|
546
|
+
plan.latestCheck = check;
|
|
547
|
+
plan.updatedAt = Date.now();
|
|
548
|
+
this.save();
|
|
549
|
+
|
|
550
|
+
return check;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Get the latest check for a plan.
|
|
555
|
+
*/
|
|
556
|
+
getLatestCheck(planId: string): PlanCheck | null {
|
|
557
|
+
const plan = this.get(planId);
|
|
558
|
+
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
559
|
+
return plan.latestCheck ?? null;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Get all checks for a plan (history).
|
|
564
|
+
*/
|
|
565
|
+
getCheckHistory(planId: string): PlanCheck[] {
|
|
566
|
+
const plan = this.get(planId);
|
|
567
|
+
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
568
|
+
return [...plan.checks];
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Auto-grade: grade the plan and return whether it meets a target grade.
|
|
573
|
+
*/
|
|
574
|
+
meetsGrade(planId: string, targetGrade: PlanGrade): { meets: boolean; check: PlanCheck } {
|
|
575
|
+
const check = this.grade(planId);
|
|
576
|
+
const targetScore = this.gradeToMinScore(targetGrade);
|
|
577
|
+
return { meets: check.score >= targetScore, check };
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// ─── Grading Helpers ──────────────────────────────────────────────
|
|
581
|
+
|
|
582
|
+
private scoreToGrade(score: number): PlanGrade {
|
|
583
|
+
if (score >= 95) return 'A+';
|
|
584
|
+
if (score >= 90) return 'A';
|
|
585
|
+
if (score >= 80) return 'B';
|
|
586
|
+
if (score >= 70) return 'C';
|
|
587
|
+
if (score >= 60) return 'D';
|
|
588
|
+
return 'F';
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
private gradeToMinScore(grade: PlanGrade): number {
|
|
592
|
+
switch (grade) {
|
|
593
|
+
case 'A+': return 95;
|
|
594
|
+
case 'A': return 90;
|
|
595
|
+
case 'B': return 80;
|
|
596
|
+
case 'C': return 70;
|
|
597
|
+
case 'D': return 60;
|
|
598
|
+
case 'F': return 0;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
private hasCircularDependencies(plan: Plan): boolean {
|
|
603
|
+
const visited = new Set<string>();
|
|
604
|
+
const inStack = new Set<string>();
|
|
605
|
+
const taskMap = new Map(plan.tasks.map((t) => [t.id, t]));
|
|
606
|
+
|
|
607
|
+
const dfs = (taskId: string): boolean => {
|
|
608
|
+
if (inStack.has(taskId)) return true;
|
|
609
|
+
if (visited.has(taskId)) return false;
|
|
610
|
+
visited.add(taskId);
|
|
611
|
+
inStack.add(taskId);
|
|
612
|
+
const task = taskMap.get(taskId);
|
|
613
|
+
if (task?.dependsOn) {
|
|
614
|
+
for (const dep of task.dependsOn) {
|
|
615
|
+
if (dfs(dep)) return true;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
inStack.delete(taskId);
|
|
619
|
+
return false;
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
for (const task of plan.tasks) {
|
|
623
|
+
if (dfs(task.id)) return true;
|
|
624
|
+
}
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
151
627
|
}
|