@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
|
@@ -8,9 +8,7 @@ function makeMockRuntime(overrides: Record<string, unknown> = {}) {
|
|
|
8
8
|
vault: {
|
|
9
9
|
get: vi.fn().mockReturnValue({ id: 'e1', tags: ['pattern'] }),
|
|
10
10
|
update: vi.fn(),
|
|
11
|
-
searchMemories: vi.fn().mockReturnValue([
|
|
12
|
-
{ id: 'm1', content: 'memory 1' },
|
|
13
|
-
]),
|
|
11
|
+
searchMemories: vi.fn().mockReturnValue([{ id: 'm1', content: 'memory 1' }]),
|
|
14
12
|
search: vi.fn().mockReturnValue([
|
|
15
13
|
{ entry: { id: 'g1', tags: ['_global'] }, score: 0.9 },
|
|
16
14
|
{ entry: { id: 'g2', tags: ['other'] }, score: 0.8 },
|
|
@@ -25,9 +23,9 @@ function makeMockRuntime(overrides: Record<string, unknown> = {}) {
|
|
|
25
23
|
metadata: { memoryConfig: { crossProjectEnabled: true, extraPaths: ['/extra'] } },
|
|
26
24
|
}),
|
|
27
25
|
register: vi.fn(),
|
|
28
|
-
getLinkedProjects: vi
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
getLinkedProjects: vi
|
|
27
|
+
.fn()
|
|
28
|
+
.mockReturnValue([{ project: { path: '/linked', name: 'linked-project' } }]),
|
|
31
29
|
},
|
|
32
30
|
} as unknown as AgentRuntime;
|
|
33
31
|
}
|
|
@@ -60,7 +58,9 @@ describe('createMemoryCrossProjectOps', () => {
|
|
|
60
58
|
it('promotes entry by adding _global tag', async () => {
|
|
61
59
|
const runtime = makeMockRuntime();
|
|
62
60
|
ops = createMemoryCrossProjectOps(runtime);
|
|
63
|
-
const result = (await findOp('memory_promote_to_global').handler({
|
|
61
|
+
const result = (await findOp('memory_promote_to_global').handler({
|
|
62
|
+
entryId: 'e1',
|
|
63
|
+
})) as Record<string, unknown>;
|
|
64
64
|
expect(result.promoted).toBe(true);
|
|
65
65
|
expect(result.tags).toContain('_global');
|
|
66
66
|
expect(runtime.vault.update).toHaveBeenCalledWith('e1', { tags: ['pattern', '_global'] });
|
|
@@ -69,7 +69,9 @@ describe('createMemoryCrossProjectOps', () => {
|
|
|
69
69
|
it('returns error when entry not found', async () => {
|
|
70
70
|
const runtime = makeMockRuntime({ get: vi.fn().mockReturnValue(null) });
|
|
71
71
|
ops = createMemoryCrossProjectOps(runtime);
|
|
72
|
-
const result = (await findOp('memory_promote_to_global').handler({
|
|
72
|
+
const result = (await findOp('memory_promote_to_global').handler({
|
|
73
|
+
entryId: 'missing',
|
|
74
|
+
})) as Record<string, unknown>;
|
|
73
75
|
expect(result.promoted).toBe(false);
|
|
74
76
|
expect(result.error).toContain('Entry not found');
|
|
75
77
|
});
|
|
@@ -79,7 +81,9 @@ describe('createMemoryCrossProjectOps', () => {
|
|
|
79
81
|
get: vi.fn().mockReturnValue({ id: 'e1', tags: ['_global'] }),
|
|
80
82
|
});
|
|
81
83
|
ops = createMemoryCrossProjectOps(runtime);
|
|
82
|
-
const result = (await findOp('memory_promote_to_global').handler({
|
|
84
|
+
const result = (await findOp('memory_promote_to_global').handler({
|
|
85
|
+
entryId: 'e1',
|
|
86
|
+
})) as Record<string, unknown>;
|
|
83
87
|
expect(result.promoted).toBe(false);
|
|
84
88
|
expect(result.message).toContain('already promoted');
|
|
85
89
|
});
|
|
@@ -132,12 +136,15 @@ describe('createMemoryCrossProjectOps', () => {
|
|
|
132
136
|
const runtime = makeMockRuntime();
|
|
133
137
|
ops = createMemoryCrossProjectOps(runtime);
|
|
134
138
|
await findOp('memory_cross_project_search').handler({
|
|
135
|
-
query: 'test',
|
|
139
|
+
query: 'test',
|
|
140
|
+
projectPath: '/test',
|
|
136
141
|
});
|
|
137
142
|
|
|
138
143
|
// Should search current project, linked project, and extra path
|
|
139
144
|
const searchCalls = (runtime.vault.searchMemories as ReturnType<typeof vi.fn>).mock.calls;
|
|
140
|
-
const searchedPaths = searchCalls.map(
|
|
145
|
+
const searchedPaths = searchCalls.map(
|
|
146
|
+
(c: unknown[]) => (c[1] as Record<string, unknown>).projectPath,
|
|
147
|
+
);
|
|
141
148
|
expect(searchedPaths).toContain('/test');
|
|
142
149
|
expect(searchedPaths).toContain('/linked');
|
|
143
150
|
expect(searchedPaths).toContain('/extra');
|
|
@@ -151,7 +158,8 @@ describe('createMemoryCrossProjectOps', () => {
|
|
|
151
158
|
]);
|
|
152
159
|
ops = createMemoryCrossProjectOps(runtime);
|
|
153
160
|
const result = (await findOp('memory_cross_project_search').handler({
|
|
154
|
-
query: 'test',
|
|
161
|
+
query: 'test',
|
|
162
|
+
projectPath: '/test',
|
|
155
163
|
})) as Record<string, unknown>;
|
|
156
164
|
|
|
157
165
|
// linkedMemories should be empty because m1 was already in current results
|
|
@@ -162,12 +170,15 @@ describe('createMemoryCrossProjectOps', () => {
|
|
|
162
170
|
it('skips linked search when cross-project disabled', async () => {
|
|
163
171
|
const runtime = makeMockRuntime();
|
|
164
172
|
(runtime.projectRegistry.getByPath as ReturnType<typeof vi.fn>).mockReturnValue({
|
|
165
|
-
id: 'proj-1',
|
|
173
|
+
id: 'proj-1',
|
|
174
|
+
name: 'test',
|
|
175
|
+
path: '/test',
|
|
166
176
|
metadata: { memoryConfig: { crossProjectEnabled: false } },
|
|
167
177
|
});
|
|
168
178
|
ops = createMemoryCrossProjectOps(runtime);
|
|
169
179
|
const result = (await findOp('memory_cross_project_search').handler({
|
|
170
|
-
query: 'test',
|
|
180
|
+
query: 'test',
|
|
181
|
+
projectPath: '/test',
|
|
171
182
|
})) as Record<string, unknown>;
|
|
172
183
|
|
|
173
184
|
expect(runtime.projectRegistry.getLinkedProjects).not.toHaveBeenCalled();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { createOrchestrateOps } from './orchestrate-ops.js';
|
|
3
|
+
import { assessTaskComplexity } from '../planning/task-complexity-assessor.js';
|
|
3
4
|
import type { AgentRuntime } from './types.js';
|
|
4
5
|
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
@@ -241,6 +242,73 @@ describe('createOrchestrateOps', () => {
|
|
|
241
242
|
await op.handler({ planId: 'plan-1', sessionId: 'session-1' });
|
|
242
243
|
expect(rt.brainIntelligence.extractKnowledge).toHaveBeenCalledWith('session-1');
|
|
243
244
|
});
|
|
245
|
+
|
|
246
|
+
it('works without a preceding plan', async () => {
|
|
247
|
+
const op = findOp(ops, 'orchestrate_complete');
|
|
248
|
+
const result = (await op.handler({
|
|
249
|
+
sessionId: 'session-1',
|
|
250
|
+
outcome: 'completed',
|
|
251
|
+
summary: 'Fixed a typo in the README',
|
|
252
|
+
})) as Record<string, unknown>;
|
|
253
|
+
|
|
254
|
+
// Should not call planner.complete
|
|
255
|
+
expect(rt.planner.complete).not.toHaveBeenCalled();
|
|
256
|
+
|
|
257
|
+
// Should return a lightweight completion record
|
|
258
|
+
const plan = result.plan as Record<string, unknown>;
|
|
259
|
+
expect(plan.status).toBe('completed');
|
|
260
|
+
expect(plan.objective).toBe('Fixed a typo in the README');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('captures knowledge even without plan', async () => {
|
|
264
|
+
const op = findOp(ops, 'orchestrate_complete');
|
|
265
|
+
await op.handler({
|
|
266
|
+
sessionId: 'session-1',
|
|
267
|
+
summary: 'Refactored utility function',
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Brain session end and knowledge extraction still run
|
|
271
|
+
expect(rt.brainIntelligence.lifecycle).toHaveBeenCalledWith(
|
|
272
|
+
expect.objectContaining({ action: 'end', sessionId: 'session-1' }),
|
|
273
|
+
);
|
|
274
|
+
expect(rt.brainIntelligence.extractKnowledge).toHaveBeenCalledWith('session-1');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('skips anti-rationalization gate when no criteria', async () => {
|
|
278
|
+
const { detectRationalizations } = await import('../planning/rationalization-detector.js');
|
|
279
|
+
const op = findOp(ops, 'orchestrate_complete');
|
|
280
|
+
|
|
281
|
+
await op.handler({
|
|
282
|
+
sessionId: 'session-1',
|
|
283
|
+
outcome: 'completed',
|
|
284
|
+
summary: 'This was basically done already',
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// detectRationalizations should never be called since there are no criteria
|
|
288
|
+
expect(detectRationalizations).not.toHaveBeenCalled();
|
|
289
|
+
// Should still complete successfully
|
|
290
|
+
expect(rt.brainIntelligence.lifecycle).toHaveBeenCalled();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('still runs brain session end without plan', async () => {
|
|
294
|
+
const op = findOp(ops, 'orchestrate_complete');
|
|
295
|
+
const result = (await op.handler({
|
|
296
|
+
sessionId: 'session-1',
|
|
297
|
+
outcome: 'completed',
|
|
298
|
+
toolsUsed: ['grep', 'edit'],
|
|
299
|
+
filesModified: [],
|
|
300
|
+
})) as Record<string, unknown>;
|
|
301
|
+
|
|
302
|
+
expect(rt.brainIntelligence.lifecycle).toHaveBeenCalledWith(
|
|
303
|
+
expect.objectContaining({
|
|
304
|
+
action: 'end',
|
|
305
|
+
sessionId: 'session-1',
|
|
306
|
+
planOutcome: 'completed',
|
|
307
|
+
toolsUsed: ['grep', 'edit'],
|
|
308
|
+
}),
|
|
309
|
+
);
|
|
310
|
+
expect(result.session).toBeDefined();
|
|
311
|
+
});
|
|
244
312
|
});
|
|
245
313
|
|
|
246
314
|
// ─── orchestrate_status ───────────────────────────────────────
|
|
@@ -299,4 +367,140 @@ describe('createOrchestrateOps', () => {
|
|
|
299
367
|
await expect(op.handler({ planId: 'missing' })).rejects.toThrow('not found');
|
|
300
368
|
});
|
|
301
369
|
});
|
|
370
|
+
|
|
371
|
+
// ─── task auto-assessment routing ────────────────────────────
|
|
372
|
+
//
|
|
373
|
+
// Integration-style tests that verify the full assess → route → complete flow:
|
|
374
|
+
// 1. Use TaskComplexityAssessor to classify the task
|
|
375
|
+
// 2. Route to direct execution (simple) or planning (complex)
|
|
376
|
+
// 3. Complete via orchestrate_complete in both paths
|
|
377
|
+
|
|
378
|
+
describe('task auto-assessment routing', () => {
|
|
379
|
+
it('simple task routes to direct execution + complete', async () => {
|
|
380
|
+
// Step 1: Assess — "fix typo in README" should be simple
|
|
381
|
+
const assessment = assessTaskComplexity({ prompt: 'fix typo in README' });
|
|
382
|
+
expect(assessment.classification).toBe('simple');
|
|
383
|
+
|
|
384
|
+
// Step 2: Skip planning, go straight to complete without a planId
|
|
385
|
+
const completeOp = findOp(ops, 'orchestrate_complete');
|
|
386
|
+
const result = (await completeOp.handler({
|
|
387
|
+
sessionId: 'session-simple',
|
|
388
|
+
outcome: 'completed',
|
|
389
|
+
summary: 'Fixed typo in README',
|
|
390
|
+
})) as Record<string, unknown>;
|
|
391
|
+
|
|
392
|
+
// Should not touch the planner at all
|
|
393
|
+
expect(rt.planner.complete).not.toHaveBeenCalled();
|
|
394
|
+
|
|
395
|
+
// Should still produce a valid completion record
|
|
396
|
+
const plan = result.plan as Record<string, unknown>;
|
|
397
|
+
expect(plan.status).toBe('completed');
|
|
398
|
+
expect(plan.objective).toBe('Fixed typo in README');
|
|
399
|
+
|
|
400
|
+
// Knowledge should still be captured
|
|
401
|
+
expect(rt.brainIntelligence.extractKnowledge).toHaveBeenCalledWith('session-simple');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('complex task routes through planning + complete', async () => {
|
|
405
|
+
// Step 1: Assess — cross-cutting auth task should be complex
|
|
406
|
+
const assessment = assessTaskComplexity({
|
|
407
|
+
prompt: 'add authentication across all API routes',
|
|
408
|
+
filesEstimated: 8,
|
|
409
|
+
});
|
|
410
|
+
expect(assessment.classification).toBe('complex');
|
|
411
|
+
|
|
412
|
+
// Step 2: Create a plan via orchestrate_plan
|
|
413
|
+
const planOp = findOp(ops, 'orchestrate_plan');
|
|
414
|
+
const planResult = (await planOp.handler({
|
|
415
|
+
prompt: 'add authentication across all API routes',
|
|
416
|
+
})) as Record<string, unknown>;
|
|
417
|
+
expect(planResult).toHaveProperty('plan');
|
|
418
|
+
expect(planResult).toHaveProperty('flow');
|
|
419
|
+
|
|
420
|
+
// Step 3: Complete with the planId
|
|
421
|
+
const completeOp = findOp(ops, 'orchestrate_complete');
|
|
422
|
+
const result = (await completeOp.handler({
|
|
423
|
+
planId: 'plan-1',
|
|
424
|
+
sessionId: 'session-complex',
|
|
425
|
+
outcome: 'completed',
|
|
426
|
+
summary: 'Added authentication middleware to all API routes',
|
|
427
|
+
})) as Record<string, unknown>;
|
|
428
|
+
|
|
429
|
+
// Should complete via the planner lifecycle
|
|
430
|
+
expect(rt.planner.complete).toHaveBeenCalledWith('plan-1');
|
|
431
|
+
|
|
432
|
+
// Knowledge should be captured
|
|
433
|
+
expect(rt.brainIntelligence.lifecycle).toHaveBeenCalledWith(
|
|
434
|
+
expect.objectContaining({ action: 'end', sessionId: 'session-complex' }),
|
|
435
|
+
);
|
|
436
|
+
expect(rt.brainIntelligence.extractKnowledge).toHaveBeenCalledWith('session-complex');
|
|
437
|
+
|
|
438
|
+
// Plan should be marked completed
|
|
439
|
+
const completedPlan = result.plan as Record<string, unknown>;
|
|
440
|
+
expect(completedPlan.status).toBe('completed');
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('orchestrate_complete captures knowledge in both paths', async () => {
|
|
444
|
+
const completeOp = findOp(ops, 'orchestrate_complete');
|
|
445
|
+
|
|
446
|
+
// ── Simple path (no planId) ──
|
|
447
|
+
vi.clearAllMocks();
|
|
448
|
+
rt = mockRuntime();
|
|
449
|
+
ops = createOrchestrateOps(rt);
|
|
450
|
+
|
|
451
|
+
await findOp(ops, 'orchestrate_complete').handler({
|
|
452
|
+
sessionId: 'session-simple',
|
|
453
|
+
outcome: 'completed',
|
|
454
|
+
summary: 'Renamed a variable',
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// Brain session end called
|
|
458
|
+
expect(rt.brainIntelligence.lifecycle).toHaveBeenCalledWith(
|
|
459
|
+
expect.objectContaining({ action: 'end', sessionId: 'session-simple' }),
|
|
460
|
+
);
|
|
461
|
+
// Knowledge extraction called
|
|
462
|
+
expect(rt.brainIntelligence.extractKnowledge).toHaveBeenCalledWith('session-simple');
|
|
463
|
+
// Planner.complete NOT called (no plan)
|
|
464
|
+
expect(rt.planner.complete).not.toHaveBeenCalled();
|
|
465
|
+
|
|
466
|
+
// ── Complex path (with planId) ──
|
|
467
|
+
vi.clearAllMocks();
|
|
468
|
+
rt = mockRuntime();
|
|
469
|
+
ops = createOrchestrateOps(rt);
|
|
470
|
+
|
|
471
|
+
await findOp(ops, 'orchestrate_complete').handler({
|
|
472
|
+
planId: 'plan-1',
|
|
473
|
+
sessionId: 'session-complex',
|
|
474
|
+
outcome: 'completed',
|
|
475
|
+
summary: 'Implemented full auth layer',
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// Brain session end called
|
|
479
|
+
expect(rt.brainIntelligence.lifecycle).toHaveBeenCalledWith(
|
|
480
|
+
expect.objectContaining({ action: 'end', sessionId: 'session-complex' }),
|
|
481
|
+
);
|
|
482
|
+
// Knowledge extraction called
|
|
483
|
+
expect(rt.brainIntelligence.extractKnowledge).toHaveBeenCalledWith('session-complex');
|
|
484
|
+
// Planner.complete IS called (has plan)
|
|
485
|
+
expect(rt.planner.complete).toHaveBeenCalledWith('plan-1');
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('assessment result includes non-empty reasoning for simple tasks', () => {
|
|
489
|
+
const result = assessTaskComplexity({ prompt: 'fix typo in README' });
|
|
490
|
+
expect(result.classification).toBe('simple');
|
|
491
|
+
expect(typeof result.reasoning).toBe('string');
|
|
492
|
+
expect(result.reasoning.length).toBeGreaterThan(0);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('assessment result includes non-empty reasoning for complex tasks', () => {
|
|
496
|
+
const result = assessTaskComplexity({
|
|
497
|
+
prompt: 'add authentication across all API routes',
|
|
498
|
+
filesEstimated: 8,
|
|
499
|
+
domains: ['auth', 'api', 'middleware'],
|
|
500
|
+
});
|
|
501
|
+
expect(result.classification).toBe('complex');
|
|
502
|
+
expect(typeof result.reasoning).toBe('string');
|
|
503
|
+
expect(result.reasoning.length).toBeGreaterThan(0);
|
|
504
|
+
});
|
|
505
|
+
});
|
|
302
506
|
});
|
|
@@ -182,10 +182,7 @@ function buildHealthWarning(
|
|
|
182
182
|
* Collect all acceptance criteria from a plan's tasks.
|
|
183
183
|
* Returns empty array if plan not found or has no criteria (graceful skip).
|
|
184
184
|
*/
|
|
185
|
-
function collectAcceptanceCriteria(
|
|
186
|
-
plannerRef: AgentRuntime['planner'],
|
|
187
|
-
planId: string,
|
|
188
|
-
): string[] {
|
|
185
|
+
function collectAcceptanceCriteria(plannerRef: AgentRuntime['planner'], planId: string): string[] {
|
|
189
186
|
const plan = plannerRef.get(planId);
|
|
190
187
|
if (!plan) return [];
|
|
191
188
|
const criteria: string[] = [];
|
|
@@ -210,7 +207,8 @@ function captureRationalizationAntiPattern(
|
|
|
210
207
|
vaultRef.add({
|
|
211
208
|
id: `antipattern-rationalization-${Date.now()}`,
|
|
212
209
|
title: 'Rationalization detected in completion claim',
|
|
213
|
-
description:
|
|
210
|
+
description:
|
|
211
|
+
`Detected rationalization patterns: ${patterns}. ` +
|
|
214
212
|
`Items: ${report.items.map((i) => `"${i.phrase}" (${i.pattern})`).join('; ')}.`,
|
|
215
213
|
type: 'anti-pattern',
|
|
216
214
|
domain: 'planning',
|
|
@@ -247,7 +245,10 @@ export function createOrchestrateOps(
|
|
|
247
245
|
'a pruned orchestration plan with gate-guarded steps.',
|
|
248
246
|
auth: 'write',
|
|
249
247
|
schema: z.object({
|
|
250
|
-
prompt: z
|
|
248
|
+
prompt: z
|
|
249
|
+
.string()
|
|
250
|
+
.optional()
|
|
251
|
+
.describe('Natural language description of what to do (or use objective)'),
|
|
251
252
|
projectPath: z.string().optional().default('.').describe('Project root path'),
|
|
252
253
|
// Legacy params — still accepted for backward compat
|
|
253
254
|
objective: z.string().optional().describe('(Legacy) Plan objective — use prompt instead'),
|
|
@@ -277,9 +278,10 @@ export function createOrchestrateOps(
|
|
|
277
278
|
recommendations = raw.map((r) => {
|
|
278
279
|
// Look up vault entry ID by title for feedback tracking
|
|
279
280
|
const entries = vault.search(r.pattern, { limit: 1 });
|
|
280
|
-
const entryId =
|
|
281
|
-
|
|
282
|
-
|
|
281
|
+
const entryId =
|
|
282
|
+
entries.length > 0 && entries[0].entry.title === r.pattern
|
|
283
|
+
? entries[0].entry.id
|
|
284
|
+
: undefined;
|
|
283
285
|
return { pattern: r.pattern, strength: r.strength, entryId };
|
|
284
286
|
});
|
|
285
287
|
} catch {
|
|
@@ -307,12 +309,10 @@ export function createOrchestrateOps(
|
|
|
307
309
|
planStore.set(plan.planId, { plan, createdAt: Date.now() });
|
|
308
310
|
|
|
309
311
|
// 5. Also create a planner plan for lifecycle tracking (backward compat)
|
|
310
|
-
const decisions = recommendations.map(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
},
|
|
315
|
-
);
|
|
312
|
+
const decisions = recommendations.map((r) => {
|
|
313
|
+
const base = `Brain pattern: ${r.pattern} (strength: ${r.strength.toFixed(1)})`;
|
|
314
|
+
return r.entryId ? `${base} [entryId:${r.entryId}]` : base;
|
|
315
|
+
});
|
|
316
316
|
const tasks = (params.tasks as Array<{ title: string; description: string }>) ?? [];
|
|
317
317
|
|
|
318
318
|
// 5b. Extract GitHub issue context if prompt references #NNN
|
|
@@ -333,7 +333,8 @@ export function createOrchestrateOps(
|
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
-
const planObjective =
|
|
336
|
+
const planObjective =
|
|
337
|
+
((params as Record<string, unknown>)._enrichedObjective as string | undefined) ?? prompt;
|
|
337
338
|
|
|
338
339
|
let legacyPlan;
|
|
339
340
|
try {
|
|
@@ -471,22 +472,32 @@ export function createOrchestrateOps(
|
|
|
471
472
|
'end brain session, and clean up.',
|
|
472
473
|
auth: 'write',
|
|
473
474
|
schema: z.object({
|
|
474
|
-
planId: z.string().describe('ID of the executing plan to complete'),
|
|
475
|
+
planId: z.string().optional().describe('ID of the executing plan to complete (optional for direct tasks)'),
|
|
475
476
|
sessionId: z.string().describe('ID of the brain session to end'),
|
|
476
477
|
outcome: z
|
|
477
478
|
.enum(['completed', 'abandoned', 'partial'])
|
|
478
479
|
.optional()
|
|
479
480
|
.default('completed')
|
|
480
481
|
.describe('Plan outcome'),
|
|
481
|
-
summary: z
|
|
482
|
+
summary: z
|
|
483
|
+
.string()
|
|
484
|
+
.optional()
|
|
485
|
+
.describe('Completion summary — checked for rationalization language'),
|
|
482
486
|
toolsUsed: z.array(z.string()).optional().describe('Tools used during execution'),
|
|
483
487
|
filesModified: z.array(z.string()).optional().describe('Files modified during execution'),
|
|
484
|
-
projectPath: z
|
|
485
|
-
|
|
488
|
+
projectPath: z
|
|
489
|
+
.string()
|
|
490
|
+
.optional()
|
|
491
|
+
.default('.')
|
|
492
|
+
.describe('Project root path for impact analysis'),
|
|
493
|
+
overrideRationalization: z
|
|
494
|
+
.boolean()
|
|
495
|
+
.optional()
|
|
496
|
+
.default(false)
|
|
486
497
|
.describe('Set true to bypass rationalization gate and impact warnings after review'),
|
|
487
498
|
}),
|
|
488
499
|
handler: async (params) => {
|
|
489
|
-
const planId = params.planId as string;
|
|
500
|
+
const planId = params.planId as string | undefined;
|
|
490
501
|
const sessionId = params.sessionId as string;
|
|
491
502
|
const outcome = (params.outcome as string) ?? 'completed';
|
|
492
503
|
const completionSummary = (params.summary as string) ?? '';
|
|
@@ -494,20 +505,21 @@ export function createOrchestrateOps(
|
|
|
494
505
|
const filesModified = (params.filesModified as string[]) ?? [];
|
|
495
506
|
const overrideRationalization = (params.overrideRationalization as boolean) ?? false;
|
|
496
507
|
|
|
497
|
-
//
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
508
|
+
// Look up plan — optional for direct tasks that skipped planning
|
|
509
|
+
const planObj = planId ? planner.get(planId) : null;
|
|
510
|
+
|
|
511
|
+
// Anti-rationalization gate: only if we have acceptance criteria from a plan
|
|
512
|
+
const criteria = planObj && planId ? collectAcceptanceCriteria(planner, planId) : [];
|
|
513
|
+
if (outcome === 'completed' && criteria.length > 0 && completionSummary && !overrideRationalization) {
|
|
514
|
+
const report = detectRationalizations(criteria, completionSummary);
|
|
515
|
+
if (report.detected) {
|
|
516
|
+
captureRationalizationAntiPattern(vault, report);
|
|
517
|
+
return {
|
|
518
|
+
blocked: true,
|
|
519
|
+
reason: 'Rationalization language detected in completion summary',
|
|
520
|
+
rationalization: report,
|
|
521
|
+
hint: 'Address the unmet criteria, or set overrideRationalization: true to bypass this gate.',
|
|
522
|
+
};
|
|
511
523
|
}
|
|
512
524
|
}
|
|
513
525
|
|
|
@@ -516,10 +528,7 @@ export function createOrchestrateOps(
|
|
|
516
528
|
if (filesModified.length > 0) {
|
|
517
529
|
try {
|
|
518
530
|
const analyzer = new ImpactAnalyzer();
|
|
519
|
-
const
|
|
520
|
-
const scopeHints = planObj?.scope
|
|
521
|
-
? [planObj.scope]
|
|
522
|
-
: undefined;
|
|
531
|
+
const scopeHints = planObj?.scope ? [planObj.scope] : undefined;
|
|
523
532
|
impactReport = analyzer.analyzeImpact(
|
|
524
533
|
filesModified,
|
|
525
534
|
(params.projectPath as string) ?? '.',
|
|
@@ -540,10 +549,19 @@ export function createOrchestrateOps(
|
|
|
540
549
|
}
|
|
541
550
|
}
|
|
542
551
|
|
|
543
|
-
// Complete the planner plan (legacy lifecycle)
|
|
544
|
-
|
|
552
|
+
// Complete the planner plan (legacy lifecycle) — only if plan exists
|
|
553
|
+
let completedPlan;
|
|
554
|
+
if (planObj && planId) {
|
|
555
|
+
completedPlan = planner.complete(planId);
|
|
556
|
+
} else {
|
|
557
|
+
completedPlan = {
|
|
558
|
+
id: planId ?? `direct-${Date.now()}`,
|
|
559
|
+
status: 'completed',
|
|
560
|
+
objective: completionSummary || 'Direct execution',
|
|
561
|
+
};
|
|
562
|
+
}
|
|
545
563
|
|
|
546
|
-
// End brain session
|
|
564
|
+
// End brain session — runs regardless of plan existence
|
|
547
565
|
const session = brainIntelligence.lifecycle({
|
|
548
566
|
action: 'end',
|
|
549
567
|
sessionId,
|
|
@@ -553,7 +571,7 @@ export function createOrchestrateOps(
|
|
|
553
571
|
filesModified,
|
|
554
572
|
});
|
|
555
573
|
|
|
556
|
-
// Extract knowledge
|
|
574
|
+
// Extract knowledge — runs regardless of plan existence
|
|
557
575
|
let extraction = null;
|
|
558
576
|
try {
|
|
559
577
|
extraction = brainIntelligence.extractKnowledge(sessionId);
|
|
@@ -563,27 +581,29 @@ export function createOrchestrateOps(
|
|
|
563
581
|
|
|
564
582
|
// Run flow-engine epilogue if we have a flow plan
|
|
565
583
|
let epilogueResult = null;
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
584
|
+
if (planId) {
|
|
585
|
+
const entry = planStore.get(planId);
|
|
586
|
+
if (entry) {
|
|
587
|
+
try {
|
|
588
|
+
const dispatch = buildDispatch(agentId, runtime, facades);
|
|
589
|
+
const summary = `${outcome}: ${entry.plan.summary}. Tools: ${toolsUsed.join(', ') || 'none'}. Files: ${filesModified.join(', ') || 'none'}.`;
|
|
590
|
+
epilogueResult = await runEpilogue(
|
|
591
|
+
dispatch,
|
|
592
|
+
entry.plan.context.probes,
|
|
593
|
+
entry.plan.context.projectPath,
|
|
594
|
+
summary,
|
|
595
|
+
);
|
|
596
|
+
} catch {
|
|
597
|
+
// Epilogue is best-effort
|
|
598
|
+
}
|
|
580
599
|
|
|
581
|
-
|
|
582
|
-
|
|
600
|
+
// Clean up plan store
|
|
601
|
+
planStore.delete(planId);
|
|
602
|
+
}
|
|
583
603
|
}
|
|
584
604
|
|
|
585
605
|
return {
|
|
586
|
-
plan,
|
|
606
|
+
plan: completedPlan,
|
|
587
607
|
session,
|
|
588
608
|
extraction,
|
|
589
609
|
epilogue: epilogueResult,
|
|
@@ -710,11 +730,22 @@ export function createOrchestrateOps(
|
|
|
710
730
|
auth: 'write',
|
|
711
731
|
schema: z.object({
|
|
712
732
|
planId: z.string().describe('ID of the plan to project to GitHub'),
|
|
713
|
-
projectPath: z
|
|
733
|
+
projectPath: z
|
|
734
|
+
.string()
|
|
735
|
+
.optional()
|
|
736
|
+
.default('.')
|
|
737
|
+
.describe('Project root path for git detection'),
|
|
714
738
|
milestone: z.number().optional().describe('GitHub milestone number to assign issues to'),
|
|
715
739
|
labels: z.array(z.string()).optional().describe('Labels to apply to created issues'),
|
|
716
|
-
linkToIssue: z
|
|
717
|
-
|
|
740
|
+
linkToIssue: z
|
|
741
|
+
.number()
|
|
742
|
+
.optional()
|
|
743
|
+
.describe('Existing issue number to link plan to instead of creating new issues'),
|
|
744
|
+
dryRun: z
|
|
745
|
+
.boolean()
|
|
746
|
+
.optional()
|
|
747
|
+
.default(false)
|
|
748
|
+
.describe('Preview what would be created without actually creating issues'),
|
|
718
749
|
}),
|
|
719
750
|
handler: async (params) => {
|
|
720
751
|
const planId = params.planId as string;
|
|
@@ -729,7 +760,9 @@ export function createOrchestrateOps(
|
|
|
729
760
|
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
730
761
|
|
|
731
762
|
if (plan.tasks.length === 0) {
|
|
732
|
-
throw new Error(
|
|
763
|
+
throw new Error(
|
|
764
|
+
'Plan has no tasks — run plan_split first to define tasks before projecting to GitHub',
|
|
765
|
+
);
|
|
733
766
|
}
|
|
734
767
|
|
|
735
768
|
// 2. Detect GitHub context
|
|
@@ -807,7 +840,12 @@ export function createOrchestrateOps(
|
|
|
807
840
|
|
|
808
841
|
// 6. Create issues per task (with duplicate detection)
|
|
809
842
|
const created: Array<{ taskId: string; issueNumber: number; title: string }> = [];
|
|
810
|
-
const skipped: Array<{
|
|
843
|
+
const skipped: Array<{
|
|
844
|
+
taskId: string;
|
|
845
|
+
title: string;
|
|
846
|
+
existingIssue: number;
|
|
847
|
+
reason: string;
|
|
848
|
+
}> = [];
|
|
811
849
|
const failed: Array<{ taskId: string; title: string; reason: string }> = [];
|
|
812
850
|
|
|
813
851
|
for (const task of plan.tasks) {
|