@soleri/core 9.2.0 → 9.3.1
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/build.flow.yaml +8 -9
- package/data/flows/deliver.flow.yaml +9 -10
- package/data/flows/design.flow.yaml +3 -4
- package/data/flows/enhance.flow.yaml +5 -6
- package/data/flows/explore.flow.yaml +3 -4
- package/data/flows/fix.flow.yaml +5 -6
- package/data/flows/plan.flow.yaml +4 -5
- package/data/flows/review.flow.yaml +3 -4
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +98 -22
- package/dist/curator/curator.js.map +1 -1
- package/dist/engine/bin/soleri-engine.js.map +1 -1
- package/dist/engine/module-manifest.d.ts +2 -0
- package/dist/engine/module-manifest.d.ts.map +1 -1
- package/dist/engine/module-manifest.js +136 -1
- package/dist/engine/module-manifest.js.map +1 -1
- package/dist/engine/register-engine.d.ts.map +1 -1
- package/dist/engine/register-engine.js +25 -1
- package/dist/engine/register-engine.js.map +1 -1
- package/dist/flows/gate-evaluator.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/operator/operator-profile.d.ts.map +1 -1
- package/dist/operator/operator-profile.js +11 -5
- package/dist/operator/operator-profile.js.map +1 -1
- package/dist/operator/operator-signals.d.ts.map +1 -1
- package/dist/operator/operator-signals.js.map +1 -1
- package/dist/planning/evidence-collector.js.map +1 -1
- package/dist/planning/gap-passes.d.ts.map +1 -1
- package/dist/planning/gap-passes.js +23 -6
- package/dist/planning/gap-passes.js.map +1 -1
- package/dist/planning/gap-patterns.d.ts.map +1 -1
- package/dist/planning/gap-patterns.js +57 -11
- package/dist/planning/gap-patterns.js.map +1 -1
- package/dist/planning/github-projection.d.ts.map +1 -1
- package/dist/planning/github-projection.js +39 -20
- package/dist/planning/github-projection.js.map +1 -1
- package/dist/planning/impact-analyzer.d.ts.map +1 -1
- package/dist/planning/impact-analyzer.js +20 -18
- package/dist/planning/impact-analyzer.js.map +1 -1
- package/dist/planning/plan-lifecycle.d.ts.map +1 -1
- package/dist/planning/plan-lifecycle.js +22 -9
- package/dist/planning/plan-lifecycle.js.map +1 -1
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +60 -17
- package/dist/planning/planner.js.map +1 -1
- package/dist/planning/rationalization-detector.d.ts.map +1 -1
- package/dist/planning/rationalization-detector.js.map +1 -1
- package/dist/planning/reconciliation-engine.d.ts.map +1 -1
- package/dist/planning/reconciliation-engine.js.map +1 -1
- package/dist/planning/task-complexity-assessor.d.ts +42 -0
- package/dist/planning/task-complexity-assessor.d.ts.map +1 -0
- package/dist/planning/task-complexity-assessor.js +132 -0
- package/dist/planning/task-complexity-assessor.js.map +1 -0
- package/dist/planning/task-verifier.d.ts.map +1 -1
- package/dist/planning/task-verifier.js +14 -6
- package/dist/planning/task-verifier.js.map +1 -1
- package/dist/runtime/admin-ops.d.ts.map +1 -1
- package/dist/runtime/admin-ops.js +18 -0
- package/dist/runtime/admin-ops.js.map +1 -1
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
- package/dist/runtime/admin-setup-ops.js +2 -1
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/branching-ops.d.ts +12 -0
- package/dist/runtime/branching-ops.d.ts.map +1 -0
- package/dist/runtime/branching-ops.js +100 -0
- package/dist/runtime/branching-ops.js.map +1 -0
- package/dist/runtime/context-health.d.ts.map +1 -1
- package/dist/runtime/context-health.js.map +1 -1
- package/dist/runtime/facades/branching-facade.d.ts +7 -0
- package/dist/runtime/facades/branching-facade.d.ts.map +1 -0
- package/dist/runtime/facades/branching-facade.js +8 -0
- package/dist/runtime/facades/branching-facade.js.map +1 -0
- package/dist/runtime/facades/chat-service-ops.d.ts.map +1 -1
- package/dist/runtime/facades/chat-service-ops.js +3 -1
- package/dist/runtime/facades/chat-service-ops.js.map +1 -1
- package/dist/runtime/facades/chat-transport-ops.d.ts.map +1 -1
- package/dist/runtime/facades/chat-transport-ops.js.map +1 -1
- package/dist/runtime/facades/index.d.ts.map +1 -1
- package/dist/runtime/facades/index.js +42 -0
- package/dist/runtime/facades/index.js.map +1 -1
- package/dist/runtime/facades/intake-facade.d.ts +9 -0
- package/dist/runtime/facades/intake-facade.d.ts.map +1 -0
- package/dist/runtime/facades/intake-facade.js +11 -0
- package/dist/runtime/facades/intake-facade.js.map +1 -0
- package/dist/runtime/facades/links-facade.d.ts +9 -0
- package/dist/runtime/facades/links-facade.d.ts.map +1 -0
- package/dist/runtime/facades/links-facade.js +10 -0
- package/dist/runtime/facades/links-facade.js.map +1 -0
- package/dist/runtime/facades/operator-facade.d.ts.map +1 -1
- package/dist/runtime/facades/operator-facade.js.map +1 -1
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
- package/dist/runtime/facades/plan-facade.js +4 -1
- package/dist/runtime/facades/plan-facade.js.map +1 -1
- package/dist/runtime/facades/tier-facade.d.ts +7 -0
- package/dist/runtime/facades/tier-facade.d.ts.map +1 -0
- package/dist/runtime/facades/tier-facade.js +8 -0
- package/dist/runtime/facades/tier-facade.js.map +1 -0
- package/dist/runtime/facades/vault-facade.d.ts +9 -1
- package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
- package/dist/runtime/facades/vault-facade.js +44 -187
- package/dist/runtime/facades/vault-facade.js.map +1 -1
- package/dist/runtime/github-integration.d.ts.map +1 -1
- package/dist/runtime/github-integration.js +11 -4
- package/dist/runtime/github-integration.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +75 -42
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
- package/dist/runtime/planning-extra-ops.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +3 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/session-briefing.d.ts.map +1 -1
- package/dist/runtime/session-briefing.js +5 -1
- package/dist/runtime/session-briefing.js.map +1 -1
- package/dist/runtime/tier-ops.d.ts +13 -0
- package/dist/runtime/tier-ops.d.ts.map +1 -0
- package/dist/runtime/tier-ops.js +110 -0
- package/dist/runtime/tier-ops.js.map +1 -0
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +1 -1
- package/dist/skills/sync-skills.js.map +1 -1
- package/dist/vault/linking.d.ts.map +1 -1
- package/dist/vault/linking.js +41 -5
- package/dist/vault/linking.js.map +1 -1
- package/dist/vault/vault-entries.d.ts.map +1 -1
- package/dist/vault/vault-entries.js +68 -26
- package/dist/vault/vault-entries.js.map +1 -1
- package/dist/vault/vault-maintenance.d.ts.map +1 -1
- package/dist/vault/vault-maintenance.js +6 -2
- package/dist/vault/vault-maintenance.js.map +1 -1
- package/dist/vault/vault-markdown-sync.d.ts.map +1 -1
- package/dist/vault/vault-markdown-sync.js.map +1 -1
- package/dist/vault/vault-memories.d.ts.map +1 -1
- package/dist/vault/vault-memories.js +3 -1
- package/dist/vault/vault-memories.js.map +1 -1
- package/dist/vault/vault-schema.js +36 -10
- package/dist/vault/vault-schema.js.map +1 -1
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +5 -1
- package/dist/vault/vault.js.map +1 -1
- package/package.json +7 -7
- package/src/agency/agency-manager.test.ts +60 -40
- package/src/agency/default-rules.test.ts +17 -9
- package/src/capabilities/registry.test.ts +2 -12
- package/src/chat/agent-loop.test.ts +33 -43
- package/src/chat/mcp-bridge.test.ts +7 -2
- package/src/claudemd/inject.test.ts +2 -12
- package/src/context/context-engine.test.ts +96 -51
- package/src/control/intent-router.test.ts +3 -3
- package/src/curator/classifier.test.ts +14 -8
- package/src/curator/contradiction-detector.test.ts +30 -5
- package/src/curator/curator.ts +278 -56
- package/src/curator/duplicate-detector.test.ts +77 -15
- package/src/curator/quality-gate.test.ts +71 -31
- package/src/curator/tag-manager.test.ts +12 -4
- package/src/domain-packs/knowledge-installer.test.ts +2 -10
- package/src/domain-packs/token-resolver.test.ts +1 -3
- package/src/domain-packs/types.test.ts +16 -2
- package/src/enforcement/registry.test.ts +2 -8
- package/src/engine/bin/soleri-engine.ts +3 -1
- package/src/engine/module-manifest.test.ts +48 -4
- package/src/engine/module-manifest.ts +138 -1
- package/src/engine/register-engine.test.ts +6 -1
- package/src/engine/register-engine.ts +26 -3
- package/src/errors/classify.test.ts +6 -2
- package/src/errors/retry.test.ts +1 -4
- package/src/facades/facade-factory.test.ts +110 -64
- package/src/flows/epilogue.test.ts +16 -10
- package/src/flows/gate-evaluator.test.ts +12 -6
- package/src/flows/gate-evaluator.ts +1 -3
- package/src/governance/governance.test.ts +137 -21
- package/src/health/health-registry.test.ts +8 -1
- package/src/index.ts +8 -0
- package/src/intake/content-classifier.test.ts +121 -51
- package/src/intake/dedup-gate.test.ts +38 -22
- package/src/intake/intake-pipeline.test.ts +5 -3
- package/src/intake/text-ingester.test.ts +26 -20
- package/src/llm/key-pool.test.ts +1 -3
- package/src/llm/llm-client.test.ts +1 -4
- package/src/llm/oauth-discovery.test.ts +16 -16
- package/src/llm/utils.test.ts +62 -18
- package/src/logging/logger.test.ts +4 -1
- package/src/loop/loop-manager.test.ts +2 -6
- package/src/migrations/migration-runner.edge-cases.test.ts +2 -7
- package/src/operator/operator-profile-extended.test.ts +15 -5
- package/src/operator/operator-profile.test.ts +26 -8
- package/src/operator/operator-profile.ts +38 -22
- package/src/operator/operator-signals-extended.test.ts +35 -23
- package/src/operator/operator-signals.test.ts +6 -10
- package/src/operator/operator-signals.ts +2 -1
- package/src/operator/prompts/hook-precompact-operator-dispatch.md +10 -6
- package/src/operator/prompts/subagent-soft-signal-extractor.md +5 -0
- package/src/operator/prompts/subagent-synthesis-cognition.md +19 -10
- package/src/operator/prompts/subagent-synthesis-communication.md +13 -7
- package/src/operator/prompts/subagent-synthesis-technical.md +19 -9
- package/src/operator/prompts/subagent-synthesis-trust.md +27 -21
- package/src/persona/defaults.test.ts +1 -5
- package/src/planning/evidence-collector.test.ts +147 -38
- package/src/planning/evidence-collector.ts +1 -4
- package/src/planning/gap-analysis-alternatives.test.ts +41 -11
- package/src/planning/gap-passes.test.ts +215 -33
- package/src/planning/gap-passes.ts +115 -46
- package/src/planning/gap-patterns.test.ts +87 -13
- package/src/planning/gap-patterns.ts +114 -31
- package/src/planning/github-projection.test.ts +6 -1
- package/src/planning/github-projection.ts +41 -20
- package/src/planning/impact-analyzer.test.ts +10 -23
- package/src/planning/impact-analyzer.ts +33 -46
- package/src/planning/plan-lifecycle.test.ts +103 -36
- package/src/planning/plan-lifecycle.ts +49 -18
- package/src/planning/planner.test.ts +12 -2
- package/src/planning/planner.ts +198 -58
- package/src/planning/rationalization-detector.test.ts +5 -20
- package/src/planning/rationalization-detector.ts +14 -16
- package/src/planning/reconciliation-engine.test.ts +20 -3
- package/src/planning/reconciliation-engine.ts +1 -2
- package/src/planning/task-complexity-assessor.test.ts +298 -0
- package/src/planning/task-complexity-assessor.ts +183 -0
- package/src/planning/task-verifier.test.ts +59 -27
- package/src/planning/task-verifier.ts +15 -9
- package/src/playbooks/playbook-executor.test.ts +1 -3
- package/src/plugins/plugin-loader.test.ts +19 -14
- package/src/plugins/plugin-registry.test.ts +45 -33
- package/src/project/project-registry.test.ts +23 -12
- package/src/prompts/template-manager.test.ts +4 -1
- package/src/queue/job-queue.test.ts +10 -14
- package/src/runtime/admin-extra-ops.test.ts +5 -19
- package/src/runtime/admin-ops.test.ts +22 -1
- package/src/runtime/admin-ops.ts +19 -0
- package/src/runtime/admin-setup-ops.test.ts +3 -4
- package/src/runtime/admin-setup-ops.ts +9 -2
- package/src/runtime/archive-ops.test.ts +4 -1
- package/src/runtime/branching-ops.test.ts +144 -0
- package/src/runtime/branching-ops.ts +107 -0
- package/src/runtime/capture-ops.test.ts +7 -21
- package/src/runtime/chain-ops.test.ts +16 -6
- package/src/runtime/claude-md-helpers.test.ts +1 -3
- package/src/runtime/context-health.test.ts +1 -3
- package/src/runtime/context-health.ts +1 -3
- package/src/runtime/curator-extra-ops.test.ts +3 -1
- package/src/runtime/domain-ops.test.ts +46 -36
- package/src/runtime/facades/admin-facade.test.ts +1 -4
- package/src/runtime/facades/archive-facade.test.ts +21 -7
- package/src/runtime/facades/brain-facade.test.ts +176 -72
- package/src/runtime/facades/branching-facade.test.ts +43 -0
- package/src/runtime/facades/branching-facade.ts +11 -0
- package/src/runtime/facades/chat-facade.test.ts +81 -28
- package/src/runtime/facades/chat-service-ops.test.ts +178 -73
- package/src/runtime/facades/chat-service-ops.ts +3 -1
- package/src/runtime/facades/chat-session-ops.test.ts +25 -10
- package/src/runtime/facades/chat-transport-ops.test.ts +101 -34
- package/src/runtime/facades/chat-transport-ops.ts +0 -1
- package/src/runtime/facades/context-facade.test.ts +19 -4
- package/src/runtime/facades/control-facade.test.ts +3 -3
- package/src/runtime/facades/index.ts +42 -0
- package/src/runtime/facades/intake-facade.test.ts +215 -0
- package/src/runtime/facades/intake-facade.ts +14 -0
- package/src/runtime/facades/links-facade.test.ts +203 -0
- package/src/runtime/facades/links-facade.ts +13 -0
- package/src/runtime/facades/loop-facade.test.ts +22 -5
- package/src/runtime/facades/memory-facade.test.ts +19 -5
- package/src/runtime/facades/operator-facade.test.ts +17 -4
- package/src/runtime/facades/operator-facade.ts +11 -3
- package/src/runtime/facades/orchestrate-facade.test.ts +7 -1
- package/src/runtime/facades/plan-facade.test.ts +29 -12
- package/src/runtime/facades/plan-facade.ts +7 -2
- package/src/runtime/facades/tier-facade.test.ts +47 -0
- package/src/runtime/facades/tier-facade.ts +11 -0
- package/src/runtime/facades/vault-facade.test.ts +174 -242
- package/src/runtime/facades/vault-facade.ts +55 -199
- package/src/runtime/github-integration.ts +11 -8
- package/src/runtime/grading-ops.test.ts +39 -8
- package/src/runtime/intake-ops.test.ts +69 -16
- package/src/runtime/loop-ops.test.ts +16 -6
- package/src/runtime/memory-cross-project-ops.test.ts +25 -14
- package/src/runtime/orchestrate-ops.test.ts +204 -0
- package/src/runtime/orchestrate-ops.ts +103 -65
- package/src/runtime/pack-ops.test.ts +23 -6
- package/src/runtime/planning-extra-ops.test.ts +17 -7
- package/src/runtime/planning-extra-ops.ts +3 -1
- package/src/runtime/playbook-ops.test.ts +26 -3
- package/src/runtime/plugin-ops.test.ts +83 -25
- package/src/runtime/project-ops.test.ts +26 -6
- package/src/runtime/runtime.ts +3 -1
- package/src/runtime/session-briefing.test.ts +183 -54
- package/src/runtime/session-briefing.ts +8 -2
- package/src/runtime/sync-ops.test.ts +3 -12
- package/src/runtime/telemetry-ops.test.ts +31 -6
- package/src/runtime/tier-ops.test.ts +159 -0
- package/src/runtime/tier-ops.ts +119 -0
- package/src/runtime/vault-extra-ops.test.ts +32 -8
- package/src/runtime/vault-sharing-ops.test.ts +1 -4
- package/src/skills/sync-skills.ts +2 -12
- package/src/transport/ws-server.test.ts +7 -4
- package/src/vault/__tests__/vault-characterization.test.ts +492 -81
- package/src/vault/linking.test.ts +50 -17
- package/src/vault/linking.ts +48 -7
- package/src/vault/obsidian-sync.test.ts +6 -3
- package/src/vault/scope-detector.test.ts +1 -3
- package/src/vault/vault-branching.test.ts +9 -7
- package/src/vault/vault-entries.ts +209 -65
- package/src/vault/vault-maintenance.ts +7 -12
- package/src/vault/vault-manager.test.ts +10 -10
- package/src/vault/vault-markdown-sync.ts +4 -1
- package/src/vault/vault-memories.ts +7 -7
- package/src/vault/vault-scaling.test.ts +5 -5
- package/src/vault/vault-schema.ts +72 -15
- package/src/vault/vault.ts +55 -9
- package/src/brain/strength-scorer.ts +0 -404
- package/src/engine/index.ts +0 -21
- package/src/persona/index.ts +0 -9
- package/src/vault/vault-interfaces.ts +0 -56
package/src/planning/planner.ts
CHANGED
|
@@ -11,19 +11,40 @@ export * from './reconciliation-engine.js';
|
|
|
11
11
|
export * from './task-verifier.js';
|
|
12
12
|
export * from './planner-types.js';
|
|
13
13
|
import type {
|
|
14
|
-
TaskStatus,
|
|
14
|
+
TaskStatus,
|
|
15
|
+
PlanTask,
|
|
16
|
+
Plan,
|
|
17
|
+
PlanStore,
|
|
18
|
+
PlanCheck,
|
|
19
|
+
PlannerOptions,
|
|
15
20
|
} from './planner-types.js';
|
|
16
21
|
import {
|
|
17
|
-
applyTransition,
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
applyTransition,
|
|
23
|
+
scoreToGrade,
|
|
24
|
+
gradeToMinScore,
|
|
25
|
+
PlanGradeRejectionError,
|
|
26
|
+
hasCircularDependencies,
|
|
27
|
+
applyIteration,
|
|
28
|
+
applySplitTasks,
|
|
29
|
+
calculateScore,
|
|
30
|
+
applyTaskStatusUpdate,
|
|
31
|
+
createPlanObject,
|
|
20
32
|
} from './plan-lifecycle.js';
|
|
21
33
|
import type { PlanStatus, PlanGrade, IterateChanges } from './plan-lifecycle.js';
|
|
22
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
buildReconciliationReport,
|
|
36
|
+
buildAutoReconcileInput,
|
|
37
|
+
computeExecutionSummary,
|
|
38
|
+
} from './reconciliation-engine.js';
|
|
23
39
|
import type { ReconcileInput } from './reconciliation-engine.js';
|
|
24
40
|
import {
|
|
25
|
-
createEvidence,
|
|
26
|
-
|
|
41
|
+
createEvidence,
|
|
42
|
+
verifyTaskLogic,
|
|
43
|
+
verifyPlanLogic,
|
|
44
|
+
verifyDeliverablesLogic,
|
|
45
|
+
createDeliverable,
|
|
46
|
+
buildSpecReviewPrompt,
|
|
47
|
+
buildQualityReviewPrompt,
|
|
27
48
|
} from './task-verifier.js';
|
|
28
49
|
|
|
29
50
|
export class Planner {
|
|
@@ -51,7 +72,9 @@ export class Planner {
|
|
|
51
72
|
const store = JSON.parse(data) as PlanStore;
|
|
52
73
|
for (const plan of store.plans) plan.checks = plan.checks ?? [];
|
|
53
74
|
return store;
|
|
54
|
-
} catch {
|
|
75
|
+
} catch {
|
|
76
|
+
return { version: '1.0', plans: [] };
|
|
77
|
+
}
|
|
55
78
|
}
|
|
56
79
|
|
|
57
80
|
private save(): void {
|
|
@@ -88,7 +111,9 @@ export class Planner {
|
|
|
88
111
|
return this.store.plans.find((p) => p.id === planId) ?? null;
|
|
89
112
|
}
|
|
90
113
|
|
|
91
|
-
list(): Plan[] {
|
|
114
|
+
list(): Plan[] {
|
|
115
|
+
return [...this.store.plans];
|
|
116
|
+
}
|
|
92
117
|
|
|
93
118
|
remove(planId: string): boolean {
|
|
94
119
|
const idx = this.store.plans.findIndex((p) => p.id === planId);
|
|
@@ -109,7 +134,12 @@ export class Planner {
|
|
|
109
134
|
const plan = this.requirePlan(planId);
|
|
110
135
|
const check = plan.latestCheck;
|
|
111
136
|
if (check && check.score < gradeToMinScore(this.minGradeForApproval)) {
|
|
112
|
-
throw new PlanGradeRejectionError(
|
|
137
|
+
throw new PlanGradeRejectionError(
|
|
138
|
+
check.grade,
|
|
139
|
+
check.score,
|
|
140
|
+
this.minGradeForApproval,
|
|
141
|
+
check.gaps,
|
|
142
|
+
);
|
|
113
143
|
}
|
|
114
144
|
this.transition(plan, 'approved');
|
|
115
145
|
this.save();
|
|
@@ -140,7 +170,9 @@ export class Planner {
|
|
|
140
170
|
updateTask(planId: string, taskId: string, status: TaskStatus): Plan {
|
|
141
171
|
const plan = this.requirePlan(planId);
|
|
142
172
|
if (plan.status !== 'executing' && plan.status !== 'validating')
|
|
143
|
-
throw new Error(
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Cannot update tasks on plan in '${plan.status}' status — must be 'executing' or 'validating'`,
|
|
175
|
+
);
|
|
144
176
|
applyTaskStatusUpdate(this.requireTask(plan, taskId), status);
|
|
145
177
|
plan.updatedAt = Date.now();
|
|
146
178
|
this.save();
|
|
@@ -149,8 +181,14 @@ export class Planner {
|
|
|
149
181
|
|
|
150
182
|
reconcile(planId: string, report: ReconcileInput): Plan {
|
|
151
183
|
const plan = this.requirePlan(planId);
|
|
152
|
-
if (
|
|
153
|
-
|
|
184
|
+
if (
|
|
185
|
+
plan.status !== 'executing' &&
|
|
186
|
+
plan.status !== 'validating' &&
|
|
187
|
+
plan.status !== 'reconciling'
|
|
188
|
+
)
|
|
189
|
+
throw new Error(
|
|
190
|
+
`Cannot reconcile plan in '${plan.status}' status — must be 'executing', 'validating', or 'reconciling'`,
|
|
191
|
+
);
|
|
154
192
|
plan.reconciliation = buildReconciliationReport(planId, report);
|
|
155
193
|
plan.executionSummary = computeExecutionSummary(plan.tasks);
|
|
156
194
|
if (plan.status === 'executing' || plan.status === 'validating') plan.status = 'reconciling';
|
|
@@ -173,7 +211,9 @@ export class Planner {
|
|
|
173
211
|
autoReconcile(planId: string): Plan | null {
|
|
174
212
|
const plan = this.requirePlan(planId);
|
|
175
213
|
if (plan.status !== 'executing' && plan.status !== 'validating')
|
|
176
|
-
throw new Error(
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Cannot auto-reconcile plan in '${plan.status}' status — must be 'executing' or 'validating'`,
|
|
216
|
+
);
|
|
177
217
|
const result = buildAutoReconcileInput(plan.tasks);
|
|
178
218
|
if (!result.canAutoReconcile || !result.input) return null;
|
|
179
219
|
return this.reconcile(planId, result.input);
|
|
@@ -184,51 +224,81 @@ export class Planner {
|
|
|
184
224
|
}
|
|
185
225
|
|
|
186
226
|
getActive(): Plan[] {
|
|
187
|
-
return this.store.plans.filter(
|
|
188
|
-
p
|
|
189
|
-
|
|
227
|
+
return this.store.plans.filter(
|
|
228
|
+
(p) =>
|
|
229
|
+
p.status === 'brainstorming' ||
|
|
230
|
+
p.status === 'draft' ||
|
|
231
|
+
p.status === 'approved' ||
|
|
232
|
+
p.status === 'executing' ||
|
|
233
|
+
p.status === 'validating' ||
|
|
234
|
+
p.status === 'reconciling',
|
|
235
|
+
);
|
|
190
236
|
}
|
|
191
237
|
|
|
192
238
|
iterate(planId: string, changes: IterateChanges): Plan {
|
|
193
239
|
const plan = this.requirePlan(planId);
|
|
194
240
|
if (plan.status !== 'draft' && plan.status !== 'brainstorming')
|
|
195
|
-
throw new Error(
|
|
241
|
+
throw new Error(
|
|
242
|
+
`Cannot iterate plan in '${plan.status}' status — must be 'draft' or 'brainstorming'`,
|
|
243
|
+
);
|
|
196
244
|
applyIteration(plan, changes);
|
|
197
245
|
this.save();
|
|
198
246
|
return plan;
|
|
199
247
|
}
|
|
200
248
|
|
|
201
|
-
splitTasks(
|
|
202
|
-
|
|
203
|
-
|
|
249
|
+
splitTasks(
|
|
250
|
+
planId: string,
|
|
251
|
+
tasks: Array<{
|
|
252
|
+
title: string;
|
|
253
|
+
description: string;
|
|
254
|
+
dependsOn?: string[];
|
|
255
|
+
acceptanceCriteria?: string[];
|
|
256
|
+
}>,
|
|
257
|
+
): Plan {
|
|
204
258
|
const plan = this.requirePlan(planId);
|
|
205
259
|
if (plan.status !== 'brainstorming' && plan.status !== 'draft' && plan.status !== 'approved')
|
|
206
|
-
throw new Error(
|
|
260
|
+
throw new Error(
|
|
261
|
+
`Cannot split tasks on plan in '${plan.status}' status — must be 'brainstorming', 'draft', or 'approved'`,
|
|
262
|
+
);
|
|
207
263
|
applySplitTasks(plan, tasks);
|
|
208
264
|
this.save();
|
|
209
265
|
return plan;
|
|
210
266
|
}
|
|
211
267
|
|
|
212
|
-
addReview(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
268
|
+
addReview(
|
|
269
|
+
planId: string,
|
|
270
|
+
review: {
|
|
271
|
+
taskId?: string;
|
|
272
|
+
reviewer: string;
|
|
273
|
+
outcome: 'approved' | 'rejected' | 'needs_changes';
|
|
274
|
+
comments: string;
|
|
275
|
+
},
|
|
276
|
+
): Plan {
|
|
216
277
|
const plan = this.requirePlan(planId);
|
|
217
278
|
if (review.taskId) this.requireTask(plan, review.taskId);
|
|
218
279
|
if (!plan.reviews) plan.reviews = [];
|
|
219
280
|
plan.reviews.push({
|
|
220
|
-
planId,
|
|
221
|
-
|
|
281
|
+
planId,
|
|
282
|
+
taskId: review.taskId,
|
|
283
|
+
reviewer: review.reviewer,
|
|
284
|
+
outcome: review.outcome,
|
|
285
|
+
comments: review.comments,
|
|
286
|
+
reviewedAt: Date.now(),
|
|
222
287
|
});
|
|
223
288
|
plan.updatedAt = Date.now();
|
|
224
289
|
this.save();
|
|
225
290
|
return plan;
|
|
226
291
|
}
|
|
227
292
|
|
|
228
|
-
setGitHubProjection(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
293
|
+
setGitHubProjection(
|
|
294
|
+
planId: string,
|
|
295
|
+
projection: {
|
|
296
|
+
repo: string;
|
|
297
|
+
milestone?: number;
|
|
298
|
+
issues: Array<{ taskId: string; issueNumber: number }>;
|
|
299
|
+
projectedAt: number;
|
|
300
|
+
},
|
|
301
|
+
): Plan {
|
|
232
302
|
const plan = this.requirePlan(planId);
|
|
233
303
|
plan.githubProjection = projection;
|
|
234
304
|
plan.updatedAt = Date.now();
|
|
@@ -236,8 +306,13 @@ export class Planner {
|
|
|
236
306
|
return plan;
|
|
237
307
|
}
|
|
238
308
|
|
|
239
|
-
getDispatch(
|
|
240
|
-
|
|
309
|
+
getDispatch(
|
|
310
|
+
planId: string,
|
|
311
|
+
taskId: string,
|
|
312
|
+
): {
|
|
313
|
+
task: PlanTask;
|
|
314
|
+
unmetDependencies: PlanTask[];
|
|
315
|
+
ready: boolean;
|
|
241
316
|
deliverableStatus?: { count: number; staleCount: number };
|
|
242
317
|
} {
|
|
243
318
|
const plan = this.requirePlan(planId);
|
|
@@ -248,14 +323,21 @@ export class Planner {
|
|
|
248
323
|
if (dep && dep.status !== 'completed') unmetDeps.push(dep);
|
|
249
324
|
}
|
|
250
325
|
return {
|
|
251
|
-
task,
|
|
326
|
+
task,
|
|
327
|
+
unmetDependencies: unmetDeps,
|
|
328
|
+
ready: unmetDeps.length === 0,
|
|
252
329
|
...(task.deliverables?.length && {
|
|
253
|
-
deliverableStatus: {
|
|
330
|
+
deliverableStatus: {
|
|
331
|
+
count: task.deliverables.length,
|
|
332
|
+
staleCount: task.deliverables.filter((d) => d.stale).length,
|
|
333
|
+
},
|
|
254
334
|
}),
|
|
255
335
|
};
|
|
256
336
|
}
|
|
257
337
|
|
|
258
|
-
submitDeliverable(
|
|
338
|
+
submitDeliverable(
|
|
339
|
+
planId: string,
|
|
340
|
+
taskId: string,
|
|
259
341
|
deliverable: { type: 'file' | 'vault_entry' | 'url'; path: string; hash?: string },
|
|
260
342
|
): PlanTask {
|
|
261
343
|
const plan = this.requirePlan(planId);
|
|
@@ -268,9 +350,15 @@ export class Planner {
|
|
|
268
350
|
return task;
|
|
269
351
|
}
|
|
270
352
|
|
|
271
|
-
verifyDeliverables(
|
|
353
|
+
verifyDeliverables(
|
|
354
|
+
planId: string,
|
|
355
|
+
taskId: string,
|
|
272
356
|
vault?: { get(id: string): unknown | null },
|
|
273
|
-
): {
|
|
357
|
+
): {
|
|
358
|
+
verified: boolean;
|
|
359
|
+
deliverables: import('./planner-types.js').TaskDeliverable[];
|
|
360
|
+
staleCount: number;
|
|
361
|
+
} {
|
|
274
362
|
const plan = this.requirePlan(planId);
|
|
275
363
|
const task = this.requireTask(plan, taskId);
|
|
276
364
|
const result = verifyDeliverablesLogic(task.deliverables ?? [], vault);
|
|
@@ -280,8 +368,14 @@ export class Planner {
|
|
|
280
368
|
return result;
|
|
281
369
|
}
|
|
282
370
|
|
|
283
|
-
submitEvidence(
|
|
284
|
-
|
|
371
|
+
submitEvidence(
|
|
372
|
+
planId: string,
|
|
373
|
+
taskId: string,
|
|
374
|
+
evidence: {
|
|
375
|
+
criterion: string;
|
|
376
|
+
content: string;
|
|
377
|
+
type: import('./planner-types.js').TaskEvidence['type'];
|
|
378
|
+
},
|
|
285
379
|
): PlanTask {
|
|
286
380
|
const plan = this.requirePlan(planId);
|
|
287
381
|
const task = this.requireTask(plan, taskId);
|
|
@@ -292,8 +386,13 @@ export class Planner {
|
|
|
292
386
|
return task;
|
|
293
387
|
}
|
|
294
388
|
|
|
295
|
-
verifyTask(
|
|
296
|
-
|
|
389
|
+
verifyTask(
|
|
390
|
+
planId: string,
|
|
391
|
+
taskId: string,
|
|
392
|
+
): {
|
|
393
|
+
verified: boolean;
|
|
394
|
+
task: PlanTask;
|
|
395
|
+
missingCriteria: string[];
|
|
297
396
|
reviewStatus: 'approved' | 'rejected' | 'needs_changes' | 'no_reviews';
|
|
298
397
|
} {
|
|
299
398
|
const plan = this.requirePlan(planId);
|
|
@@ -312,35 +411,65 @@ export class Planner {
|
|
|
312
411
|
return verifyPlanLogic(planId, this.requirePlan(planId).tasks);
|
|
313
412
|
}
|
|
314
413
|
|
|
315
|
-
generateReviewSpec(
|
|
414
|
+
generateReviewSpec(
|
|
415
|
+
planId: string,
|
|
416
|
+
taskId: string,
|
|
417
|
+
): { prompt: string; task: PlanTask; plan: Plan } {
|
|
316
418
|
const plan = this.requirePlan(planId);
|
|
317
419
|
const task = this.requireTask(plan, taskId);
|
|
318
420
|
return { prompt: buildSpecReviewPrompt(task, plan.objective), task, plan };
|
|
319
421
|
}
|
|
320
422
|
|
|
321
|
-
generateReviewQuality(
|
|
423
|
+
generateReviewQuality(
|
|
424
|
+
planId: string,
|
|
425
|
+
taskId: string,
|
|
426
|
+
): { prompt: string; task: PlanTask; plan: Plan } {
|
|
322
427
|
const plan = this.requirePlan(planId);
|
|
323
428
|
const task = this.requireTask(plan, taskId);
|
|
324
429
|
return { prompt: buildQualityReviewPrompt(task), task, plan };
|
|
325
430
|
}
|
|
326
431
|
|
|
327
432
|
archive(olderThanDays?: number): Plan[] {
|
|
328
|
-
const cutoff =
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
433
|
+
const cutoff =
|
|
434
|
+
olderThanDays !== undefined
|
|
435
|
+
? Date.now() - olderThanDays * 24 * 60 * 60 * 1000
|
|
436
|
+
: Date.now() + 1;
|
|
437
|
+
const toArchive = this.store.plans.filter(
|
|
438
|
+
(p) => p.status === 'completed' && p.updatedAt < cutoff,
|
|
439
|
+
);
|
|
440
|
+
for (const plan of toArchive) {
|
|
441
|
+
plan.status = 'archived';
|
|
442
|
+
plan.updatedAt = Date.now();
|
|
443
|
+
}
|
|
332
444
|
if (toArchive.length > 0) this.save();
|
|
333
445
|
return toArchive;
|
|
334
446
|
}
|
|
335
447
|
|
|
336
448
|
stats(): {
|
|
337
|
-
total: number;
|
|
338
|
-
|
|
449
|
+
total: number;
|
|
450
|
+
byStatus: Record<PlanStatus, number>;
|
|
451
|
+
avgTasksPerPlan: number;
|
|
452
|
+
totalTasks: number;
|
|
453
|
+
tasksByStatus: Record<TaskStatus, number>;
|
|
339
454
|
} {
|
|
340
455
|
const plans = this.store.plans;
|
|
341
|
-
const byStatus = {
|
|
342
|
-
|
|
343
|
-
|
|
456
|
+
const byStatus = {
|
|
457
|
+
brainstorming: 0,
|
|
458
|
+
draft: 0,
|
|
459
|
+
approved: 0,
|
|
460
|
+
executing: 0,
|
|
461
|
+
validating: 0,
|
|
462
|
+
reconciling: 0,
|
|
463
|
+
completed: 0,
|
|
464
|
+
archived: 0,
|
|
465
|
+
} as Record<PlanStatus, number>;
|
|
466
|
+
const tasksByStatus = {
|
|
467
|
+
pending: 0,
|
|
468
|
+
in_progress: 0,
|
|
469
|
+
completed: 0,
|
|
470
|
+
skipped: 0,
|
|
471
|
+
failed: 0,
|
|
472
|
+
} as Record<TaskStatus, number>;
|
|
344
473
|
let totalTasks = 0;
|
|
345
474
|
for (const p of plans) {
|
|
346
475
|
byStatus[p.status]++;
|
|
@@ -348,7 +477,10 @@ export class Planner {
|
|
|
348
477
|
for (const t of p.tasks) tasksByStatus[t.status]++;
|
|
349
478
|
}
|
|
350
479
|
return {
|
|
351
|
-
total: plans.length,
|
|
480
|
+
total: plans.length,
|
|
481
|
+
byStatus,
|
|
482
|
+
totalTasks,
|
|
483
|
+
tasksByStatus,
|
|
352
484
|
avgTasksPerPlan: plans.length > 0 ? Math.round((totalTasks / plans.length) * 100) / 100 : 0,
|
|
353
485
|
};
|
|
354
486
|
}
|
|
@@ -358,17 +490,25 @@ export class Planner {
|
|
|
358
490
|
const gaps = runGapAnalysis(plan, this.gapOptions);
|
|
359
491
|
if (hasCircularDependencies(plan.tasks)) {
|
|
360
492
|
gaps.push({
|
|
361
|
-
id: `gap_${Date.now()}_circ`,
|
|
493
|
+
id: `gap_${Date.now()}_circ`,
|
|
494
|
+
severity: 'critical',
|
|
495
|
+
category: 'structure',
|
|
362
496
|
description: 'Circular dependencies detected among tasks.',
|
|
363
497
|
recommendation: 'Remove circular dependency chains so tasks can be executed in order.',
|
|
364
|
-
location: 'tasks',
|
|
498
|
+
location: 'tasks',
|
|
499
|
+
_trigger: 'circular_dependencies',
|
|
365
500
|
});
|
|
366
501
|
}
|
|
367
502
|
const iteration = plan.checks.length + 1;
|
|
368
503
|
const score = calculateScore(gaps, iteration);
|
|
369
504
|
const check: PlanCheck = {
|
|
370
505
|
checkId: `chk-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
371
|
-
planId,
|
|
506
|
+
planId,
|
|
507
|
+
grade: scoreToGrade(score),
|
|
508
|
+
score,
|
|
509
|
+
gaps,
|
|
510
|
+
iteration,
|
|
511
|
+
checkedAt: Date.now(),
|
|
372
512
|
};
|
|
373
513
|
plan.checks.push(check);
|
|
374
514
|
plan.latestCheck = check;
|
|
@@ -31,10 +31,7 @@ describe('detectRationalizations', () => {
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
it('detects "follow-up PR" pattern', () => {
|
|
34
|
-
const report = detectRationalizations(
|
|
35
|
-
CRITERIA,
|
|
36
|
-
'Test coverage bump deferred to follow-up PR.',
|
|
37
|
-
);
|
|
34
|
+
const report = detectRationalizations(CRITERIA, 'Test coverage bump deferred to follow-up PR.');
|
|
38
35
|
expect(report.detected).toBe(true);
|
|
39
36
|
expect(report.items[0].pattern).toBe('follow-up-ticket');
|
|
40
37
|
});
|
|
@@ -98,19 +95,13 @@ describe('detectRationalizations', () => {
|
|
|
98
95
|
// ─── Case-insensitive matching ──────────────────────────────
|
|
99
96
|
|
|
100
97
|
it('matches case-insensitively (uppercase)', () => {
|
|
101
|
-
const report = detectRationalizations(
|
|
102
|
-
CRITERIA,
|
|
103
|
-
'This is OUT OF SCOPE for the current work.',
|
|
104
|
-
);
|
|
98
|
+
const report = detectRationalizations(CRITERIA, 'This is OUT OF SCOPE for the current work.');
|
|
105
99
|
expect(report.detected).toBe(true);
|
|
106
100
|
expect(report.items[0].pattern).toBe('out-of-scope');
|
|
107
101
|
});
|
|
108
102
|
|
|
109
103
|
it('matches case-insensitively (mixed case)', () => {
|
|
110
|
-
const report = detectRationalizations(
|
|
111
|
-
CRITERIA,
|
|
112
|
-
'That is a Pre-Existing Issue we inherited.',
|
|
113
|
-
);
|
|
104
|
+
const report = detectRationalizations(CRITERIA, 'That is a Pre-Existing Issue we inherited.');
|
|
114
105
|
expect(report.detected).toBe(true);
|
|
115
106
|
expect(report.items[0].pattern).toBe('pre-existing-issue');
|
|
116
107
|
});
|
|
@@ -118,10 +109,7 @@ describe('detectRationalizations', () => {
|
|
|
118
109
|
// ─── Empty/skip cases ──────────────────────────────────────
|
|
119
110
|
|
|
120
111
|
it('skips detection when acceptance criteria are empty', () => {
|
|
121
|
-
const report = detectRationalizations(
|
|
122
|
-
[],
|
|
123
|
-
'This is out of scope and a pre-existing issue.',
|
|
124
|
-
);
|
|
112
|
+
const report = detectRationalizations([], 'This is out of scope and a pre-existing issue.');
|
|
125
113
|
expect(report.detected).toBe(false);
|
|
126
114
|
expect(report.items).toHaveLength(0);
|
|
127
115
|
});
|
|
@@ -158,10 +146,7 @@ describe('detectRationalizations', () => {
|
|
|
158
146
|
// ─── Suggestion is always present ──────────────────────────
|
|
159
147
|
|
|
160
148
|
it('provides actionable suggestions for each item', () => {
|
|
161
|
-
const report = detectRationalizations(
|
|
162
|
-
CRITERIA,
|
|
163
|
-
'This is out of scope and over-engineering.',
|
|
164
|
-
);
|
|
149
|
+
const report = detectRationalizations(CRITERIA, 'This is out of scope and over-engineering.');
|
|
165
150
|
expect(report.detected).toBe(true);
|
|
166
151
|
for (const item of report.items) {
|
|
167
152
|
expect(item.suggestion).toBeTruthy();
|
|
@@ -37,32 +37,38 @@ const PATTERNS: RationalizationPattern[] = [
|
|
|
37
37
|
{
|
|
38
38
|
name: 'out-of-scope',
|
|
39
39
|
regex: /out\s+of\s+scope/i,
|
|
40
|
-
suggestion:
|
|
40
|
+
suggestion:
|
|
41
|
+
'If it was in the acceptance criteria, it is in scope. Remove from criteria or complete it.',
|
|
41
42
|
},
|
|
42
43
|
{
|
|
43
44
|
name: 'follow-up-ticket',
|
|
44
45
|
regex: /follow[- ]?up\s+(ticket|issue|pr|task)/i,
|
|
45
|
-
suggestion:
|
|
46
|
+
suggestion:
|
|
47
|
+
'Deferring to a follow-up means the criterion is unmet. Complete it now or revise the plan.',
|
|
46
48
|
},
|
|
47
49
|
{
|
|
48
50
|
name: 'pre-existing-issue',
|
|
49
51
|
regex: /pre[- ]?existing\s+(issue|bug|problem)/i,
|
|
50
|
-
suggestion:
|
|
52
|
+
suggestion:
|
|
53
|
+
'Pre-existing or not, the criterion expects it resolved. Fix it or remove the criterion.',
|
|
51
54
|
},
|
|
52
55
|
{
|
|
53
56
|
name: 'over-engineering',
|
|
54
57
|
regex: /over[- ]?engineering/i,
|
|
55
|
-
suggestion:
|
|
58
|
+
suggestion:
|
|
59
|
+
'Meeting acceptance criteria is not over-engineering. Implement what was agreed upon.',
|
|
56
60
|
},
|
|
57
61
|
{
|
|
58
62
|
name: 'separate-pr',
|
|
59
63
|
regex: /separate\s+(pr|task|ticket|issue)/i,
|
|
60
|
-
suggestion:
|
|
64
|
+
suggestion:
|
|
65
|
+
'Splitting into a separate PR defers the work. Complete the criterion or update the plan.',
|
|
61
66
|
},
|
|
62
67
|
{
|
|
63
68
|
name: 'too-complex',
|
|
64
69
|
regex: /too\s+complex\s+for\s+this\s+(task|pr|ticket|issue|scope)/i,
|
|
65
|
-
suggestion:
|
|
70
|
+
suggestion:
|
|
71
|
+
'Complexity was known at planning time. Revisit the plan or complete the criterion.',
|
|
66
72
|
},
|
|
67
73
|
];
|
|
68
74
|
|
|
@@ -91,11 +97,7 @@ export function detectRationalizations(
|
|
|
91
97
|
if (!match) continue;
|
|
92
98
|
|
|
93
99
|
// Find which criterion is being rationalized (nearest mention)
|
|
94
|
-
const criterion = findRelatedCriterion(
|
|
95
|
-
acceptanceCriteria,
|
|
96
|
-
completionClaim,
|
|
97
|
-
match.index,
|
|
98
|
-
);
|
|
100
|
+
const criterion = findRelatedCriterion(acceptanceCriteria, completionClaim, match.index);
|
|
99
101
|
|
|
100
102
|
items.push({
|
|
101
103
|
criterion,
|
|
@@ -114,11 +116,7 @@ export function detectRationalizations(
|
|
|
114
116
|
* Find the acceptance criterion most related to the rationalization phrase
|
|
115
117
|
* by checking which criterion text appears closest to the match position.
|
|
116
118
|
*/
|
|
117
|
-
function findRelatedCriterion(
|
|
118
|
-
criteria: string[],
|
|
119
|
-
claim: string,
|
|
120
|
-
matchIndex: number,
|
|
121
|
-
): string {
|
|
119
|
+
function findRelatedCriterion(criteria: string[], claim: string, matchIndex: number): string {
|
|
122
120
|
const claimLower = claim.toLowerCase();
|
|
123
121
|
let bestCriterion = criteria[0];
|
|
124
122
|
let bestDistance = Infinity;
|
|
@@ -36,7 +36,10 @@ describe('reconciliation-engine', () => {
|
|
|
36
36
|
});
|
|
37
37
|
it('floors at 0', () => {
|
|
38
38
|
const items: DriftItem[] = Array.from({ length: 10 }, () => ({
|
|
39
|
-
type: 'skipped' as const,
|
|
39
|
+
type: 'skipped' as const,
|
|
40
|
+
description: 'x',
|
|
41
|
+
impact: 'high' as const,
|
|
42
|
+
rationale: 'r',
|
|
40
43
|
}));
|
|
41
44
|
expect(calculateDriftScore(items)).toBe(0);
|
|
42
45
|
});
|
|
@@ -63,8 +66,22 @@ describe('reconciliation-engine', () => {
|
|
|
63
66
|
});
|
|
64
67
|
it('computes average duration from tasks with metrics', () => {
|
|
65
68
|
const tasks: PlanTask[] = [
|
|
66
|
-
{
|
|
67
|
-
|
|
69
|
+
{
|
|
70
|
+
id: 't1',
|
|
71
|
+
title: '',
|
|
72
|
+
description: '',
|
|
73
|
+
status: 'completed',
|
|
74
|
+
updatedAt: 0,
|
|
75
|
+
metrics: { durationMs: 100 },
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 't2',
|
|
79
|
+
title: '',
|
|
80
|
+
description: '',
|
|
81
|
+
status: 'completed',
|
|
82
|
+
updatedAt: 0,
|
|
83
|
+
metrics: { durationMs: 200 },
|
|
84
|
+
},
|
|
68
85
|
{ id: 't3', title: '', description: '', status: 'completed', updatedAt: 0 }, // no metrics
|
|
69
86
|
];
|
|
70
87
|
const summary = computeExecutionSummary(tasks);
|
|
@@ -65,8 +65,7 @@ export function computeExecutionSummary(tasks: ReadonlyArray<PlanTask>): Executi
|
|
|
65
65
|
tasksCompleted,
|
|
66
66
|
tasksSkipped,
|
|
67
67
|
tasksFailed,
|
|
68
|
-
avgTaskDurationMs:
|
|
69
|
-
tasksWithDuration > 0 ? Math.round(totalDurationMs / tasksWithDuration) : 0,
|
|
68
|
+
avgTaskDurationMs: tasksWithDuration > 0 ? Math.round(totalDurationMs / tasksWithDuration) : 0,
|
|
70
69
|
};
|
|
71
70
|
}
|
|
72
71
|
|