@soleri/core 9.14.4 → 9.16.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/flows/deliver.flow.yaml +11 -0
- package/data/flows/design.flow.yaml +4 -14
- package/data/flows/enhance.flow.yaml +10 -0
- package/data/flows/explore.flow.yaml +16 -0
- package/data/flows/fix.flow.yaml +1 -1
- package/data/flows/review.flow.yaml +13 -4
- package/dist/brain/brain.d.ts +9 -0
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +11 -1
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +24 -0
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/brain/types.d.ts +1 -0
- package/dist/brain/types.d.ts.map +1 -1
- package/dist/capabilities/chain-mapping.d.ts.map +1 -1
- package/dist/capabilities/chain-mapping.js +5 -4
- package/dist/capabilities/chain-mapping.js.map +1 -1
- package/dist/capabilities/registry.d.ts +6 -0
- package/dist/capabilities/registry.d.ts.map +1 -1
- package/dist/capabilities/registry.js +3 -2
- package/dist/capabilities/registry.js.map +1 -1
- package/dist/chat/chat-session.d.ts +6 -0
- package/dist/chat/chat-session.d.ts.map +1 -1
- package/dist/chat/chat-session.js +68 -17
- package/dist/chat/chat-session.js.map +1 -1
- package/dist/context/context-engine.js +1 -1
- package/dist/context/context-engine.js.map +1 -1
- package/dist/curator/curator.d.ts +6 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +138 -0
- package/dist/curator/curator.js.map +1 -1
- package/dist/curator/types.d.ts +10 -0
- package/dist/curator/types.d.ts.map +1 -1
- package/dist/engine/bin/soleri-engine.js +0 -0
- package/dist/engine/core-ops.d.ts.map +1 -1
- package/dist/engine/core-ops.js +38 -1
- package/dist/engine/core-ops.js.map +1 -1
- package/dist/flows/epilogue.d.ts +5 -1
- package/dist/flows/epilogue.d.ts.map +1 -1
- package/dist/flows/epilogue.js +11 -3
- package/dist/flows/epilogue.js.map +1 -1
- package/dist/flows/executor.d.ts.map +1 -1
- package/dist/flows/executor.js +13 -5
- package/dist/flows/executor.js.map +1 -1
- package/dist/flows/index.d.ts +1 -2
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js +1 -0
- package/dist/flows/index.js.map +1 -1
- package/dist/flows/plan-builder.d.ts +17 -1
- package/dist/flows/plan-builder.d.ts.map +1 -1
- package/dist/flows/plan-builder.js +67 -6
- package/dist/flows/plan-builder.js.map +1 -1
- package/dist/flows/probes.d.ts +1 -1
- package/dist/flows/probes.d.ts.map +1 -1
- package/dist/flows/probes.js +15 -3
- package/dist/flows/probes.js.map +1 -1
- package/dist/flows/types.d.ts +47 -20
- package/dist/flows/types.d.ts.map +1 -1
- package/dist/flows/types.js +6 -1
- package/dist/flows/types.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/intake/content-classifier.d.ts +10 -4
- package/dist/intake/content-classifier.d.ts.map +1 -1
- package/dist/intake/content-classifier.js +19 -5
- package/dist/intake/content-classifier.js.map +1 -1
- package/dist/intake/text-ingester.d.ts +18 -0
- package/dist/intake/text-ingester.d.ts.map +1 -1
- package/dist/intake/text-ingester.js +37 -13
- package/dist/intake/text-ingester.js.map +1 -1
- package/dist/packs/pack-installer.d.ts.map +1 -1
- package/dist/packs/pack-installer.js +28 -2
- package/dist/packs/pack-installer.js.map +1 -1
- package/dist/planning/planner-types.d.ts +2 -0
- package/dist/planning/planner-types.d.ts.map +1 -1
- package/dist/planning/planner.d.ts +4 -0
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +50 -4
- package/dist/planning/planner.js.map +1 -1
- package/dist/playbooks/playbook-executor.d.ts +10 -1
- package/dist/playbooks/playbook-executor.d.ts.map +1 -1
- package/dist/playbooks/playbook-executor.js +8 -2
- package/dist/playbooks/playbook-executor.js.map +1 -1
- package/dist/playbooks/playbook-types.d.ts +8 -0
- package/dist/playbooks/playbook-types.d.ts.map +1 -1
- package/dist/plugins/types.d.ts +2 -2
- package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
- package/dist/runtime/admin-extra-ops.js +30 -0
- package/dist/runtime/admin-extra-ops.js.map +1 -1
- package/dist/runtime/admin-ops.d.ts.map +1 -1
- package/dist/runtime/admin-ops.js +60 -21
- package/dist/runtime/admin-ops.js.map +1 -1
- package/dist/runtime/admin-setup-ops.d.ts +11 -0
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
- package/dist/runtime/admin-setup-ops.js +146 -37
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/capture-ops.d.ts.map +1 -1
- package/dist/runtime/capture-ops.js +38 -12
- package/dist/runtime/capture-ops.js.map +1 -1
- package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
- package/dist/runtime/facades/brain-facade.js +16 -4
- package/dist/runtime/facades/brain-facade.js.map +1 -1
- package/dist/runtime/facades/context-facade.d.ts.map +1 -1
- package/dist/runtime/facades/context-facade.js +9 -3
- package/dist/runtime/facades/context-facade.js.map +1 -1
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
- package/dist/runtime/facades/memory-facade.js +20 -7
- package/dist/runtime/facades/memory-facade.js.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.js +40 -1
- package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
- package/dist/runtime/facades/plan-facade.js +113 -4
- package/dist/runtime/facades/plan-facade.js.map +1 -1
- package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
- package/dist/runtime/facades/vault-facade.js +24 -3
- package/dist/runtime/facades/vault-facade.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts +21 -0
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +132 -38
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +16 -0
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/schema-helpers.d.ts.map +1 -1
- package/dist/runtime/schema-helpers.js +4 -0
- package/dist/runtime/schema-helpers.js.map +1 -1
- package/dist/runtime/types.d.ts +19 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
- package/dist/runtime/vault-linking-ops.js +16 -3
- package/dist/runtime/vault-linking-ops.js.map +1 -1
- package/dist/scheduler/cron-validator.d.ts +15 -0
- package/dist/scheduler/cron-validator.d.ts.map +1 -0
- package/dist/scheduler/cron-validator.js +93 -0
- package/dist/scheduler/cron-validator.js.map +1 -0
- package/dist/scheduler/platform-linux.d.ts +14 -0
- package/dist/scheduler/platform-linux.d.ts.map +1 -0
- package/dist/scheduler/platform-linux.js +107 -0
- package/dist/scheduler/platform-linux.js.map +1 -0
- package/dist/scheduler/platform-macos.d.ts +15 -0
- package/dist/scheduler/platform-macos.d.ts.map +1 -0
- package/dist/scheduler/platform-macos.js +131 -0
- package/dist/scheduler/platform-macos.js.map +1 -0
- package/dist/scheduler/scheduler-ops.d.ts +14 -0
- package/dist/scheduler/scheduler-ops.d.ts.map +1 -0
- package/dist/scheduler/scheduler-ops.js +77 -0
- package/dist/scheduler/scheduler-ops.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +55 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -0
- package/dist/scheduler/scheduler.js +144 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/scheduler/types.d.ts +48 -0
- package/dist/scheduler/types.d.ts.map +1 -0
- package/dist/scheduler/types.js +6 -0
- package/dist/scheduler/types.js.map +1 -0
- package/dist/skills/sync-skills.d.ts +11 -0
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +132 -38
- package/dist/skills/sync-skills.js.map +1 -1
- package/dist/skills/validate-skills.d.ts +32 -0
- package/dist/skills/validate-skills.d.ts.map +1 -0
- package/dist/skills/validate-skills.js +396 -0
- package/dist/skills/validate-skills.js.map +1 -0
- package/dist/utils/worktree-reaper.d.ts +38 -0
- package/dist/utils/worktree-reaper.d.ts.map +1 -0
- package/dist/utils/worktree-reaper.js +85 -0
- package/dist/utils/worktree-reaper.js.map +1 -0
- package/dist/vault/default-canonical-tags.d.ts +15 -0
- package/dist/vault/default-canonical-tags.d.ts.map +1 -0
- package/dist/vault/default-canonical-tags.js +65 -0
- package/dist/vault/default-canonical-tags.js.map +1 -0
- package/dist/vault/scope-detector.d.ts.map +1 -1
- package/dist/vault/scope-detector.js +37 -4
- package/dist/vault/scope-detector.js.map +1 -1
- package/dist/vault/tag-normalizer.d.ts +42 -0
- package/dist/vault/tag-normalizer.d.ts.map +1 -0
- package/dist/vault/tag-normalizer.js +157 -0
- package/dist/vault/tag-normalizer.js.map +1 -0
- package/dist/vault/vault-entries.d.ts.map +1 -1
- package/dist/vault/vault-entries.js +3 -1
- package/dist/vault/vault-entries.js.map +1 -1
- package/package.json +5 -1
- package/src/__tests__/embeddings.test.ts +3 -3
- package/src/agency/agency-manager.test.ts +4 -4
- package/src/agency/default-rules.test.ts +0 -13
- package/src/brain/brain-intelligence.test.ts +0 -5
- package/src/brain/brain.ts +25 -1
- package/src/brain/intelligence.ts +25 -0
- package/src/brain/second-brain-features.test.ts +2 -14
- package/src/brain/types.ts +1 -0
- package/src/capabilities/chain-mapping.test.ts +1 -6
- package/src/capabilities/chain-mapping.ts +6 -4
- package/src/capabilities/registry.test.ts +1 -1
- package/src/capabilities/registry.ts +9 -2
- package/src/chat/agent-loop.test.ts +1 -1
- package/src/chat/chat-enhanced.test.ts +0 -8
- package/src/chat/chat-session.ts +75 -17
- package/src/chat/chat-transport.test.ts +31 -1
- package/src/claudemd/compose.test.ts +0 -5
- package/src/context/context-engine.test.ts +0 -1
- package/src/context/context-engine.ts +1 -1
- package/src/control/intent-router.test.ts +2 -2
- package/src/curator/curator.ts +180 -0
- package/src/curator/tag-manager.test.ts +0 -4
- package/src/curator/types.ts +10 -0
- package/src/domain-packs/types.test.ts +0 -5
- package/src/dream/dream.test.ts +0 -7
- package/src/enforcement/registry.test.ts +2 -2
- package/src/engine/core-ops.test.ts +4 -22
- package/src/engine/core-ops.ts +36 -1
- package/src/engine/module-manifest.test.ts +1 -31
- package/src/engine/register-engine.test.ts +3 -33
- package/src/errors/retry.test.ts +3 -1
- package/src/flows/chain-runner.test.ts +0 -6
- package/src/flows/context-router.test.ts +3 -3
- package/src/flows/epilogue.test.ts +40 -2
- package/src/flows/epilogue.ts +11 -2
- package/src/flows/executor.test.ts +48 -2
- package/src/flows/executor.ts +15 -5
- package/src/flows/index.ts +1 -3
- package/src/flows/plan-builder.test.ts +201 -0
- package/src/flows/plan-builder.ts +81 -5
- package/src/flows/probes.ts +17 -3
- package/src/flows/types.ts +31 -2
- package/src/health/health-registry.test.ts +3 -1
- package/src/index.ts +24 -0
- package/src/intake/content-classifier.ts +22 -4
- package/src/intake/dedup-gate.test.ts +2 -6
- package/src/intake/text-ingester.test.ts +3 -4
- package/src/intake/text-ingester.ts +61 -12
- package/src/llm/llm-client.test.ts +1 -1
- package/src/llm/utils.test.ts +1 -1
- package/src/migrations/migration-runner.test.ts +0 -1
- package/src/operator/operator-context-store.test.ts +0 -13
- package/src/operator/operator-profile.test.ts +2 -20
- package/src/packs/pack-installer.ts +28 -2
- package/src/packs/pack-system.test.ts +2 -2
- package/src/persona/defaults.test.ts +19 -19
- package/src/planning/gap-passes.test.ts +0 -46
- package/src/planning/gap-patterns.test.ts +0 -42
- package/src/planning/goal-ancestry.test.ts +3 -1
- package/src/planning/plan-lifecycle.test.ts +15 -7
- package/src/planning/planner-types.ts +2 -0
- package/src/planning/planner.test.ts +86 -90
- package/src/planning/planner.ts +56 -4
- package/src/planning/reconciliation-engine.test.ts +3 -10
- package/src/planning/task-complexity-assessor.test.ts +0 -5
- package/src/planning/task-verifier.test.ts +3 -1
- package/src/playbooks/generic/generic-playbooks.test.ts +0 -28
- package/src/playbooks/index.test.ts +0 -55
- package/src/playbooks/playbook-executor.test.ts +76 -0
- package/src/playbooks/playbook-executor.ts +24 -3
- package/src/playbooks/playbook-types.ts +8 -0
- package/src/plugins/plugin-registry.test.ts +6 -2
- package/src/project/project-registry.test.ts +2 -0
- package/src/queue/async-infrastructure.test.ts +6 -4
- package/src/queue/job-queue.test.ts +13 -7
- package/src/runtime/admin-extra-ops.test.ts +35 -30
- package/src/runtime/admin-extra-ops.ts +30 -0
- package/src/runtime/admin-ops.test.ts +0 -4
- package/src/runtime/admin-ops.ts +63 -21
- package/src/runtime/admin-setup-ops.test.ts +229 -13
- package/src/runtime/admin-setup-ops.ts +145 -36
- package/src/runtime/archive-ops.test.ts +0 -28
- package/src/runtime/branching-ops.test.ts +0 -17
- package/src/runtime/capture-ops.test.ts +41 -16
- package/src/runtime/capture-ops.ts +78 -46
- package/src/runtime/chain-ops.test.ts +0 -21
- package/src/runtime/facades/admin-facade.test.ts +0 -34
- package/src/runtime/facades/agency-facade.test.ts +0 -39
- package/src/runtime/facades/archive-facade.test.ts +0 -43
- package/src/runtime/facades/brain-facade.test.ts +8 -99
- package/src/runtime/facades/brain-facade.ts +29 -12
- package/src/runtime/facades/branching-facade.test.ts +30 -17
- package/src/runtime/facades/chat-facade.test.ts +0 -91
- package/src/runtime/facades/chat-service-ops.test.ts +0 -24
- package/src/runtime/facades/chat-session-ops.test.ts +0 -12
- package/src/runtime/facades/chat-transport-ops.test.ts +0 -23
- package/src/runtime/facades/context-facade.test.ts +0 -17
- package/src/runtime/facades/context-facade.ts +11 -4
- package/src/runtime/facades/control-facade.test.ts +0 -30
- package/src/runtime/facades/curator-facade.test.ts +0 -33
- package/src/runtime/facades/intake-facade.test.ts +0 -33
- package/src/runtime/facades/links-facade.test.ts +0 -37
- package/src/runtime/facades/loop-facade.test.ts +0 -26
- package/src/runtime/facades/memory-facade.test.ts +0 -18
- package/src/runtime/facades/memory-facade.ts +27 -11
- package/src/runtime/facades/operator-facade.test.ts +0 -31
- package/src/runtime/facades/orchestrate-facade.test.ts +0 -21
- package/src/runtime/facades/orchestrate-facade.ts +39 -1
- package/src/runtime/facades/plan-facade.test.ts +7 -32
- package/src/runtime/facades/plan-facade.ts +137 -4
- package/src/runtime/facades/review-facade.test.ts +1 -49
- package/src/runtime/facades/sync-facade.test.ts +24 -41
- package/src/runtime/facades/tier-facade.test.ts +30 -22
- package/src/runtime/facades/vault-facade.test.ts +0 -41
- package/src/runtime/facades/vault-facade.ts +26 -3
- package/src/runtime/grading-ops.test.ts +0 -27
- package/src/runtime/intake-ops.test.ts +0 -19
- package/src/runtime/loop-ops.test.ts +0 -48
- package/src/runtime/memory-cross-project-ops.test.ts +0 -14
- package/src/runtime/memory-extra-ops.test.ts +4 -8
- package/src/runtime/orchestrate-ops.test.ts +238 -19
- package/src/runtime/orchestrate-ops.ts +166 -41
- package/src/runtime/pack-ops.test.ts +0 -26
- package/src/runtime/planning-extra-ops.test.ts +2 -14
- package/src/runtime/playbook-ops-execution.test.ts +9 -20
- package/src/runtime/playbook-ops.test.ts +4 -67
- package/src/runtime/review-ops.test.ts +0 -15
- package/src/runtime/runtime.ts +18 -0
- package/src/runtime/schema-helpers.ts +4 -0
- package/src/runtime/sync-ops.test.ts +0 -18
- package/src/runtime/tier-ops.test.ts +0 -21
- package/src/runtime/types.ts +19 -0
- package/src/runtime/vault-extra-ops.test.ts +0 -12
- package/src/runtime/vault-linking-ops.test.ts +0 -4
- package/src/runtime/vault-linking-ops.ts +26 -8
- package/src/runtime/vault-sharing-ops.test.ts +0 -9
- package/src/scheduler/cron-validator.ts +101 -0
- package/src/scheduler/platform-linux.ts +122 -0
- package/src/scheduler/platform-macos.ts +150 -0
- package/src/scheduler/scheduler-ops.ts +77 -0
- package/src/scheduler/scheduler.test.ts +247 -0
- package/src/scheduler/scheduler.ts +174 -0
- package/src/scheduler/types.ts +52 -0
- package/src/skills/__tests__/sync-skills.test.ts +6 -17
- package/src/skills/global-claude-md.test.ts +113 -0
- package/src/skills/sync-skills.ts +143 -35
- package/src/skills/validate-skills.test.ts +206 -0
- package/src/skills/validate-skills.ts +470 -0
- package/src/telemetry/telemetry.test.ts +1 -0
- package/src/transport/http-server.test.ts +3 -0
- package/src/transport/session-manager.test.ts +3 -1
- package/src/transport/token-auth.test.ts +6 -9
- package/src/transport/ws-server.test.ts +10 -2
- package/src/utils/worktree-reaper.ts +113 -0
- package/src/vault/__tests__/vault-characterization.test.ts +0 -108
- package/src/vault/default-canonical-tags.ts +64 -0
- package/src/vault/linking.test.ts +0 -2
- package/src/vault/playbook.test.ts +4 -1
- package/src/vault/scope-detector.test.ts +3 -1
- package/src/vault/scope-detector.ts +42 -4
- package/src/vault/tag-normalizer.test.ts +214 -0
- package/src/vault/tag-normalizer.ts +188 -0
- package/src/vault/vault-connect.test.ts +1 -1
- package/src/vault/vault-entries.ts +3 -1
- package/src/vault/vault.test.ts +23 -8
- package/dist/embeddings/index.d.ts +0 -5
- package/dist/embeddings/index.d.ts.map +0 -1
- package/dist/embeddings/index.js +0 -3
- package/dist/embeddings/index.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
4
|
-
import { createOrchestrateOps } from './orchestrate-ops.js';
|
|
4
|
+
import { createOrchestrateOps, mapVaultResults } from './orchestrate-ops.js';
|
|
5
5
|
import { assessTaskComplexity } from '../planning/task-complexity-assessor.js';
|
|
6
6
|
import type { AgentRuntime } from './types.js';
|
|
7
7
|
|
|
@@ -104,6 +104,7 @@ function mockRuntime(): AgentRuntime {
|
|
|
104
104
|
},
|
|
105
105
|
brain: {
|
|
106
106
|
recordFeedback: vi.fn(),
|
|
107
|
+
intelligentSearch: vi.fn().mockResolvedValue([]),
|
|
107
108
|
},
|
|
108
109
|
brainIntelligence: {
|
|
109
110
|
recommend: vi.fn().mockReturnValue([]),
|
|
@@ -164,16 +165,6 @@ describe('createOrchestrateOps', () => {
|
|
|
164
165
|
ops = createOrchestrateOps(rt);
|
|
165
166
|
});
|
|
166
167
|
|
|
167
|
-
it('returns all orchestrate ops', () => {
|
|
168
|
-
expect(ops.length).toBeGreaterThanOrEqual(5);
|
|
169
|
-
const names = ops.map((o) => o.name);
|
|
170
|
-
expect(names).toContain('orchestrate_plan');
|
|
171
|
-
expect(names).toContain('orchestrate_execute');
|
|
172
|
-
expect(names).toContain('orchestrate_complete');
|
|
173
|
-
expect(names).toContain('orchestrate_status');
|
|
174
|
-
expect(names).toContain('orchestrate_quick_capture');
|
|
175
|
-
});
|
|
176
|
-
|
|
177
168
|
// ─── orchestrate_plan ─────────────────────────────────────────
|
|
178
169
|
|
|
179
170
|
describe('orchestrate_plan', () => {
|
|
@@ -190,18 +181,159 @@ describe('createOrchestrateOps', () => {
|
|
|
190
181
|
expect(flow.intent).toBe('BUILD');
|
|
191
182
|
});
|
|
192
183
|
|
|
193
|
-
it('
|
|
184
|
+
it('vault results appear when brain recommend has no data', async () => {
|
|
185
|
+
// intelligentSearch succeeds; vault results appear even when brain.recommend throws.
|
|
194
186
|
const op = findOp(ops, 'orchestrate_plan');
|
|
195
187
|
vi.mocked(rt.brainIntelligence.recommend).mockImplementation(() => {
|
|
196
188
|
throw new Error('no data');
|
|
197
189
|
});
|
|
198
|
-
vi.mocked(rt.
|
|
190
|
+
vi.mocked(rt.brain.intelligentSearch).mockResolvedValue([
|
|
199
191
|
{ entry: { id: 'e1', title: 'Pattern A' }, score: 0.8 },
|
|
200
192
|
] as never);
|
|
201
193
|
const result = (await op.handler({ prompt: 'fix a bug' })) as Record<string, unknown>;
|
|
202
194
|
const recs = result.recommendations as Array<Record<string, unknown>>;
|
|
203
195
|
expect(recs.length).toBe(1);
|
|
204
196
|
expect(recs[0].pattern).toBe('Pattern A');
|
|
197
|
+
expect(recs[0].source).toBe('vault');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('intelligentSearch is called for vault retrieval — semantic search is primary', async () => {
|
|
201
|
+
// The primary vault retrieval path must use semantic search, not keyword search.
|
|
202
|
+
// If intelligentSearch were not called, ranking would fall back to TF-IDF keyword
|
|
203
|
+
// frequency, missing semantically related entries.
|
|
204
|
+
const op = findOp(ops, 'orchestrate_plan');
|
|
205
|
+
vi.mocked(rt.brainIntelligence.recommend).mockReturnValue([
|
|
206
|
+
{ pattern: 'Brain Pattern', strength: 70 },
|
|
207
|
+
] as never);
|
|
208
|
+
vi.mocked(rt.brain.intelligentSearch).mockResolvedValue([
|
|
209
|
+
{ entry: { id: 'v1', title: 'Vault Pattern' }, score: 0.9 },
|
|
210
|
+
] as never);
|
|
211
|
+
await op.handler({ prompt: 'build a feature' });
|
|
212
|
+
expect(rt.brain.intelligentSearch).toHaveBeenCalled();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('vault results precede brain results and brain does not duplicate vault entries', async () => {
|
|
216
|
+
// Vault patterns come first; brain adds only novel patterns.
|
|
217
|
+
const op = findOp(ops, 'orchestrate_plan');
|
|
218
|
+
vi.mocked(rt.brain.intelligentSearch).mockResolvedValue([
|
|
219
|
+
{ entry: { id: 'v1', title: 'Vault Pattern' }, score: 0.9 },
|
|
220
|
+
] as never);
|
|
221
|
+
vi.mocked(rt.brainIntelligence.recommend).mockReturnValue([
|
|
222
|
+
{ pattern: 'Vault Pattern', strength: 70 }, // duplicate — should be dropped
|
|
223
|
+
{ pattern: 'Brain Pattern', strength: 60 }, // novel — should be appended
|
|
224
|
+
] as never);
|
|
225
|
+
const result = (await op.handler({ prompt: 'build something' })) as Record<string, unknown>;
|
|
226
|
+
const recs = result.recommendations as Array<Record<string, unknown>>;
|
|
227
|
+
expect(recs).toHaveLength(2);
|
|
228
|
+
expect(recs[0].source).toBe('vault');
|
|
229
|
+
expect(recs[0].pattern).toBe('Vault Pattern');
|
|
230
|
+
expect(recs[1].source).toBe('brain');
|
|
231
|
+
expect(recs[1].pattern).toBe('Brain Pattern');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('falls back to vault.search when intelligentSearch throws', async () => {
|
|
235
|
+
// If the semantic layer is unavailable, keyword search covers it.
|
|
236
|
+
// Without this fallback, any intelligentSearch failure would silently drop all vault results.
|
|
237
|
+
const op = findOp(ops, 'orchestrate_plan');
|
|
238
|
+
vi.mocked(rt.brain.intelligentSearch).mockRejectedValue(new Error('embedding unavailable'));
|
|
239
|
+
vi.mocked(rt.vault.search).mockReturnValue([
|
|
240
|
+
{ entry: { id: 'k1', title: 'Keyword Pattern' }, score: 0.6 },
|
|
241
|
+
] as never);
|
|
242
|
+
const result = (await op.handler({ prompt: 'fix bug' })) as Record<string, unknown>;
|
|
243
|
+
const recs = result.recommendations as Array<Record<string, unknown>>;
|
|
244
|
+
expect(recs).toHaveLength(1);
|
|
245
|
+
expect(recs[0].pattern).toBe('Keyword Pattern');
|
|
246
|
+
expect(recs[0].source).toBe('vault');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('brain results used alone when both vault search paths are unavailable', async () => {
|
|
250
|
+
// If intelligentSearch and vault.search both fail, brain results cover the gap.
|
|
251
|
+
const op = findOp(ops, 'orchestrate_plan');
|
|
252
|
+
vi.mocked(rt.brain.intelligentSearch).mockRejectedValue(new Error('down'));
|
|
253
|
+
vi.mocked(rt.vault.search).mockImplementation(() => {
|
|
254
|
+
throw new Error('vault down');
|
|
255
|
+
});
|
|
256
|
+
vi.mocked(rt.brainIntelligence.recommend).mockReturnValue([
|
|
257
|
+
{ pattern: 'Brain Only Pattern', strength: 65 },
|
|
258
|
+
] as never);
|
|
259
|
+
const result = (await op.handler({ prompt: 'fix bug' })) as Record<string, unknown>;
|
|
260
|
+
const recs = result.recommendations as Array<Record<string, unknown>>;
|
|
261
|
+
expect(recs).toHaveLength(1);
|
|
262
|
+
expect(recs[0].source).toBe('brain');
|
|
263
|
+
expect(recs[0].pattern).toBe('Brain Only Pattern');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('includes context and example from vault entry body in recommendations', async () => {
|
|
267
|
+
// RankedResult.entry contains the full IntelligenceEntry — context and example
|
|
268
|
+
// must be forwarded into the recommendations payload, not dropped.
|
|
269
|
+
const op = findOp(ops, 'orchestrate_plan');
|
|
270
|
+
vi.mocked(rt.brain.intelligentSearch).mockResolvedValue([
|
|
271
|
+
{
|
|
272
|
+
entry: {
|
|
273
|
+
id: 'e-full',
|
|
274
|
+
title: 'Anti-Pattern: Skip Tests',
|
|
275
|
+
context: 'Never skip tests when under time pressure.',
|
|
276
|
+
example: 'Adding --passWithNoTests to CI.',
|
|
277
|
+
},
|
|
278
|
+
score: 0.9,
|
|
279
|
+
},
|
|
280
|
+
] as never);
|
|
281
|
+
const result = (await op.handler({ prompt: 'write tests quickly' })) as Record<
|
|
282
|
+
string,
|
|
283
|
+
unknown
|
|
284
|
+
>;
|
|
285
|
+
const recs = result.recommendations as Array<Record<string, unknown>>;
|
|
286
|
+
expect(recs[0].context).toBe('Never skip tests when under time pressure.');
|
|
287
|
+
expect(recs[0].example).toBe('Adding --passWithNoTests to CI.');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('omits context key when vault entry has no body', async () => {
|
|
291
|
+
// Title-only entries must not surface context: null or context: "" — the key should
|
|
292
|
+
// be absent so consumers can reliably check `if (rec.context)`.
|
|
293
|
+
const op = findOp(ops, 'orchestrate_plan');
|
|
294
|
+
vi.mocked(rt.brain.intelligentSearch).mockResolvedValue([
|
|
295
|
+
{ entry: { id: 'e-bare', title: 'Pattern B' }, score: 0.7 },
|
|
296
|
+
] as never);
|
|
297
|
+
const result = (await op.handler({ prompt: 'build something' })) as Record<string, unknown>;
|
|
298
|
+
const recs = result.recommendations as Array<Record<string, unknown>>;
|
|
299
|
+
expect('context' in recs[0]).toBe(false);
|
|
300
|
+
expect('example' in recs[0]).toBe(false);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('sets mandatory:true for critical vault entries', async () => {
|
|
304
|
+
// A critical entry must surface as mandatory so gate injection can promote
|
|
305
|
+
// it to a hard stop. If severity is ignored, critical rules are treated
|
|
306
|
+
// identically to suggestions — the whole enforcement chain breaks.
|
|
307
|
+
const op = findOp(ops, 'orchestrate_plan');
|
|
308
|
+
vi.mocked(rt.brain.intelligentSearch).mockResolvedValue([
|
|
309
|
+
{ entry: { id: 'c1', title: 'Critical Rule', severity: 'critical' }, score: 0.95 },
|
|
310
|
+
{ entry: { id: 'w1', title: 'Warning Rule', severity: 'warning' }, score: 0.75 },
|
|
311
|
+
{ entry: { id: 's1', title: 'Suggestion', severity: 'suggestion' }, score: 0.5 },
|
|
312
|
+
] as never);
|
|
313
|
+
const result = (await op.handler({ prompt: 'plan something' })) as Record<string, unknown>;
|
|
314
|
+
const recs = result.recommendations as Array<Record<string, unknown>>;
|
|
315
|
+
const critical = recs.find((r) => r.pattern === 'Critical Rule');
|
|
316
|
+
const warning = recs.find((r) => r.pattern === 'Warning Rule');
|
|
317
|
+
const suggestion = recs.find((r) => r.pattern === 'Suggestion');
|
|
318
|
+
expect(critical?.mandatory).toBe(true);
|
|
319
|
+
expect(critical?.strength).toBe(100);
|
|
320
|
+
expect(warning?.mandatory).toBe(false);
|
|
321
|
+
expect(suggestion?.mandatory).toBe(false);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('sets mandatory:false for all brain-sourced recommendations', async () => {
|
|
325
|
+
// Brain learns from usage frequency, not from curated rules — it cannot
|
|
326
|
+
// declare a rule mandatory. If brain recs were mandatory, spurious patterns
|
|
327
|
+
// from frequent usage would block plans with no policy basis.
|
|
328
|
+
const op = findOp(ops, 'orchestrate_plan');
|
|
329
|
+
vi.mocked(rt.brain.intelligentSearch).mockResolvedValue([] as never);
|
|
330
|
+
vi.mocked(rt.brainIntelligence.recommend).mockReturnValue([
|
|
331
|
+
{ pattern: 'Brain Pattern', strength: 75 },
|
|
332
|
+
] as never);
|
|
333
|
+
const result = (await op.handler({ prompt: 'build feature' })) as Record<string, unknown>;
|
|
334
|
+
const recs = result.recommendations as Array<Record<string, unknown>>;
|
|
335
|
+
expect(recs[0].source).toBe('brain');
|
|
336
|
+
expect(recs[0].mandatory).toBe(false);
|
|
205
337
|
});
|
|
206
338
|
|
|
207
339
|
it('creates a planner plan for lifecycle tracking', async () => {
|
|
@@ -209,6 +341,93 @@ describe('createOrchestrateOps', () => {
|
|
|
209
341
|
await op.handler({ prompt: 'Build something' });
|
|
210
342
|
expect(rt.planner.create).toHaveBeenCalled();
|
|
211
343
|
});
|
|
344
|
+
|
|
345
|
+
it('builds plan with no recommendations when all three retrieval paths fail', async () => {
|
|
346
|
+
// Triple-failure: intelligentSearch down, vault.search down, brain.recommend down.
|
|
347
|
+
// The plan must still build — missing recommendations are non-fatal.
|
|
348
|
+
const op = findOp(ops, 'orchestrate_plan');
|
|
349
|
+
vi.mocked(rt.brain.intelligentSearch).mockRejectedValue(new Error('down'));
|
|
350
|
+
vi.mocked(rt.vault.search).mockImplementation(() => {
|
|
351
|
+
throw new Error('vault down');
|
|
352
|
+
});
|
|
353
|
+
vi.mocked(rt.brainIntelligence.recommend).mockImplementation(() => {
|
|
354
|
+
throw new Error('brain down');
|
|
355
|
+
});
|
|
356
|
+
const result = (await op.handler({ prompt: 'fix bug' })) as Record<string, unknown>;
|
|
357
|
+
const recs = result.recommendations as Array<Record<string, unknown>>;
|
|
358
|
+
expect(recs).toHaveLength(0);
|
|
359
|
+
expect(result.flow).toBeDefined();
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// ─── mapVaultResults isolation ─────────────────────────────────
|
|
364
|
+
|
|
365
|
+
describe('mapVaultResults', () => {
|
|
366
|
+
it('maps critical severity to mandatory:true and strength:100', () => {
|
|
367
|
+
const results = [
|
|
368
|
+
{
|
|
369
|
+
entry: {
|
|
370
|
+
id: 'e1',
|
|
371
|
+
title: 'No skipping tests',
|
|
372
|
+
severity: 'critical',
|
|
373
|
+
type: 'anti-pattern',
|
|
374
|
+
},
|
|
375
|
+
score: 0.9,
|
|
376
|
+
breakdown: {} as never,
|
|
377
|
+
},
|
|
378
|
+
];
|
|
379
|
+
const recs = mapVaultResults(results);
|
|
380
|
+
expect(recs[0].mandatory).toBe(true);
|
|
381
|
+
expect(recs[0].strength).toBe(100);
|
|
382
|
+
expect(recs[0].source).toBe('vault');
|
|
383
|
+
expect(recs[0].entryType).toBe('anti-pattern');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('maps warning severity to mandatory:false and strength:80', () => {
|
|
387
|
+
const results = [
|
|
388
|
+
{
|
|
389
|
+
entry: { id: 'e2', title: 'Warning Rule', severity: 'warning', type: 'pattern' },
|
|
390
|
+
score: 0.7,
|
|
391
|
+
breakdown: {} as never,
|
|
392
|
+
},
|
|
393
|
+
];
|
|
394
|
+
const recs = mapVaultResults(results);
|
|
395
|
+
expect(recs[0].mandatory).toBe(false);
|
|
396
|
+
expect(recs[0].strength).toBe(80);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('forwards context and example when present', () => {
|
|
400
|
+
const results = [
|
|
401
|
+
{
|
|
402
|
+
entry: {
|
|
403
|
+
id: 'e3',
|
|
404
|
+
title: 'Rule',
|
|
405
|
+
severity: 'warning',
|
|
406
|
+
type: 'pattern',
|
|
407
|
+
context: 'ctx',
|
|
408
|
+
example: 'ex',
|
|
409
|
+
},
|
|
410
|
+
score: 0.5,
|
|
411
|
+
breakdown: {} as never,
|
|
412
|
+
},
|
|
413
|
+
];
|
|
414
|
+
const recs = mapVaultResults(results);
|
|
415
|
+
expect(recs[0].context).toBe('ctx');
|
|
416
|
+
expect(recs[0].example).toBe('ex');
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('does not set context or example keys when absent', () => {
|
|
420
|
+
const results = [
|
|
421
|
+
{
|
|
422
|
+
entry: { id: 'e4', title: 'Bare Rule', severity: 'suggestion', type: 'pattern' },
|
|
423
|
+
score: 0.4,
|
|
424
|
+
breakdown: {} as never,
|
|
425
|
+
},
|
|
426
|
+
];
|
|
427
|
+
const recs = mapVaultResults(results);
|
|
428
|
+
expect('context' in recs[0]).toBe(false);
|
|
429
|
+
expect('example' in recs[0]).toBe(false);
|
|
430
|
+
});
|
|
212
431
|
});
|
|
213
432
|
|
|
214
433
|
// ─── orchestrate_execute ──────────────────────────────────────
|
|
@@ -749,11 +968,10 @@ describe('createOrchestrateOps', () => {
|
|
|
749
968
|
expect(rt.planner.complete).toHaveBeenCalledWith('plan-1');
|
|
750
969
|
});
|
|
751
970
|
|
|
752
|
-
it('assessment result includes
|
|
971
|
+
it('assessment result includes default reasoning when no signals triggered', () => {
|
|
753
972
|
const result = assessTaskComplexity({ prompt: 'fix typo in README' });
|
|
754
973
|
expect(result.classification).toBe('simple');
|
|
755
|
-
expect(
|
|
756
|
-
expect(result.reasoning.length).toBeGreaterThan(0);
|
|
974
|
+
expect(result.reasoning).toBe('No complexity signals detected — treating as simple task');
|
|
757
975
|
});
|
|
758
976
|
|
|
759
977
|
it('orchestrate_complete compounds operator signals when provided', async () => {
|
|
@@ -949,15 +1167,16 @@ describe('createOrchestrateOps', () => {
|
|
|
949
1167
|
expect(fs.writeFileSync).not.toHaveBeenCalled();
|
|
950
1168
|
});
|
|
951
1169
|
|
|
952
|
-
it('assessment result
|
|
1170
|
+
it('assessment result lists triggered signals in reasoning for complex tasks', () => {
|
|
953
1171
|
const result = assessTaskComplexity({
|
|
954
1172
|
prompt: 'add authentication across all API routes',
|
|
955
1173
|
filesEstimated: 8,
|
|
956
1174
|
domains: ['auth', 'api', 'middleware'],
|
|
957
1175
|
});
|
|
958
1176
|
expect(result.classification).toBe('complex');
|
|
959
|
-
expect(
|
|
960
|
-
|
|
1177
|
+
expect(result.reasoning).toBe(
|
|
1178
|
+
'Complex: file-count, cross-cutting-keywords, multi-domain (score 50)',
|
|
1179
|
+
);
|
|
961
1180
|
});
|
|
962
1181
|
});
|
|
963
1182
|
});
|
|
@@ -14,7 +14,48 @@ import path from 'node:path';
|
|
|
14
14
|
import { z } from 'zod';
|
|
15
15
|
import type { OpDefinition, FacadeConfig } from '../facades/types.js';
|
|
16
16
|
import type { AgentRuntime } from './types.js';
|
|
17
|
-
import { buildPlan } from '../flows/plan-builder.js';
|
|
17
|
+
import { buildPlan, type VaultConstraint } from '../flows/plan-builder.js';
|
|
18
|
+
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Recommendation types + helpers (module-level for testability)
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
export interface PlanRecommendation {
|
|
25
|
+
pattern: string;
|
|
26
|
+
strength: number;
|
|
27
|
+
entryId?: string;
|
|
28
|
+
source: 'vault' | 'brain';
|
|
29
|
+
context?: string;
|
|
30
|
+
example?: string;
|
|
31
|
+
mandatory: boolean;
|
|
32
|
+
entryType?: 'pattern' | 'anti-pattern' | 'rule' | 'playbook';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Map vault search results to PlanRecommendation[].
|
|
37
|
+
* Accepts both RankedResult[] (semantic search) and SearchResult[] (keyword fallback)
|
|
38
|
+
* since both share the same entry: IntelligenceEntry shape.
|
|
39
|
+
* Critical entries get strength:100 and mandatory:true; all others get 80/false.
|
|
40
|
+
*/
|
|
41
|
+
export function mapVaultResults(
|
|
42
|
+
results: Array<{ entry: IntelligenceEntry; score: number }>,
|
|
43
|
+
): PlanRecommendation[] {
|
|
44
|
+
return results.map((r) => {
|
|
45
|
+
const isCritical = r.entry.severity === 'critical';
|
|
46
|
+
const rec: PlanRecommendation = {
|
|
47
|
+
pattern: r.entry.title,
|
|
48
|
+
strength: isCritical ? 100 : 80,
|
|
49
|
+
entryId: r.entry.id,
|
|
50
|
+
source: 'vault',
|
|
51
|
+
mandatory: isCritical,
|
|
52
|
+
entryType: r.entry.type,
|
|
53
|
+
};
|
|
54
|
+
if (r.entry.context) rec.context = r.entry.context;
|
|
55
|
+
if (r.entry.example) rec.example = r.entry.example;
|
|
56
|
+
return rec;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
18
59
|
import { FlowExecutor, getPlanRunDir, loadManifest, saveManifest } from '../flows/executor.js';
|
|
19
60
|
import { createDispatcher } from '../flows/dispatch-registry.js';
|
|
20
61
|
import { runEpilogue } from '../flows/epilogue.js';
|
|
@@ -376,43 +417,67 @@ export function createOrchestrateOps(
|
|
|
376
417
|
// 1. Detect intent from prompt
|
|
377
418
|
const intent = detectIntent(prompt);
|
|
378
419
|
|
|
379
|
-
// 2.
|
|
380
|
-
let recommendations:
|
|
420
|
+
// 2. Build recommendations — vault first (authoritative), brain enriches (additive)
|
|
421
|
+
let recommendations: PlanRecommendation[] = [];
|
|
422
|
+
|
|
423
|
+
// Vault always runs first — curated explicit knowledge takes precedence.
|
|
424
|
+
// Prefer semantic search (vector-scored); fall back to keyword search.
|
|
425
|
+
|
|
381
426
|
try {
|
|
382
|
-
const
|
|
427
|
+
const vaultResults = await brain.intelligentSearch(prompt, {
|
|
383
428
|
domain,
|
|
384
|
-
task: prompt,
|
|
385
429
|
limit: 5,
|
|
386
430
|
});
|
|
387
|
-
recommendations =
|
|
388
|
-
// Look up vault entry ID by title for feedback tracking
|
|
389
|
-
const entries = vault.search(r.pattern, { limit: 1 });
|
|
390
|
-
const entryId =
|
|
391
|
-
entries.length > 0 && entries[0].entry.title === r.pattern
|
|
392
|
-
? entries[0].entry.id
|
|
393
|
-
: undefined;
|
|
394
|
-
return { pattern: r.pattern, strength: r.strength, entryId };
|
|
395
|
-
});
|
|
431
|
+
recommendations = mapVaultResults(vaultResults);
|
|
396
432
|
} catch {
|
|
397
|
-
//
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Fallback to vault if brain empty
|
|
401
|
-
if (recommendations.length === 0) {
|
|
433
|
+
// Semantic search unavailable — fall back to keyword search
|
|
402
434
|
try {
|
|
403
435
|
const vaultResults = vault.search(prompt, { domain, limit: 5 });
|
|
404
|
-
recommendations = vaultResults
|
|
405
|
-
pattern: r.entry.title,
|
|
406
|
-
strength: 50,
|
|
407
|
-
entryId: r.entry.id,
|
|
408
|
-
}));
|
|
436
|
+
recommendations = mapVaultResults(vaultResults);
|
|
409
437
|
} catch {
|
|
410
|
-
// Vault
|
|
438
|
+
// Vault unavailable — brain will cover below
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Brain enriches with learned usage patterns — additive, never replaces vault
|
|
443
|
+
try {
|
|
444
|
+
const brainResults = brainIntelligence.recommend({
|
|
445
|
+
domain,
|
|
446
|
+
task: prompt,
|
|
447
|
+
limit: 5,
|
|
448
|
+
});
|
|
449
|
+
for (const r of brainResults) {
|
|
450
|
+
if (!recommendations.find((rec) => rec.pattern === r.pattern)) {
|
|
451
|
+
recommendations.push({
|
|
452
|
+
pattern: r.pattern,
|
|
453
|
+
strength: r.strength,
|
|
454
|
+
source: 'brain',
|
|
455
|
+
mandatory: false,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
411
458
|
}
|
|
459
|
+
} catch {
|
|
460
|
+
// Brain has no data yet
|
|
412
461
|
}
|
|
413
462
|
|
|
414
|
-
// 3. Build flow-engine plan
|
|
415
|
-
const
|
|
463
|
+
// 3. Build flow-engine plan — pass vault constraints for gate injection
|
|
464
|
+
const vaultConstraints: VaultConstraint[] = recommendations
|
|
465
|
+
.filter((r) => r.source === 'vault' && r.entryId)
|
|
466
|
+
.map((r) => ({
|
|
467
|
+
entryId: r.entryId!,
|
|
468
|
+
title: r.pattern,
|
|
469
|
+
context: r.context,
|
|
470
|
+
mandatory: r.mandatory,
|
|
471
|
+
entryType: r.entryType,
|
|
472
|
+
}));
|
|
473
|
+
const plan = await buildPlan(
|
|
474
|
+
intent,
|
|
475
|
+
agentId,
|
|
476
|
+
projectPath,
|
|
477
|
+
runtime,
|
|
478
|
+
prompt,
|
|
479
|
+
vaultConstraints,
|
|
480
|
+
);
|
|
416
481
|
|
|
417
482
|
// 3b. Merge workflow overrides (gates + tools) if agent has a matching workflow
|
|
418
483
|
let workflowApplied: string | undefined;
|
|
@@ -436,7 +501,8 @@ export function createOrchestrateOps(
|
|
|
436
501
|
|
|
437
502
|
// 5. Also create a planner plan for lifecycle tracking (backward compat)
|
|
438
503
|
const decisions = recommendations.map((r) => {
|
|
439
|
-
const
|
|
504
|
+
const label = r.source === 'vault' ? 'Vault pattern' : 'Brain pattern';
|
|
505
|
+
const base = `${label}: ${r.pattern} (strength: ${r.strength.toFixed(1)})`;
|
|
440
506
|
return r.entryId ? `${base} [entryId:${r.entryId}]` : base;
|
|
441
507
|
});
|
|
442
508
|
const tasks = (params.tasks as Array<{ title: string; description: string }>) ?? [];
|
|
@@ -492,6 +558,7 @@ export function createOrchestrateOps(
|
|
|
492
558
|
skippedCount: plan.skipped.length,
|
|
493
559
|
warnings: plan.warnings,
|
|
494
560
|
estimatedTools: plan.estimatedTools,
|
|
561
|
+
...(plan.recommendations ? { vaultConstraints: plan.recommendations } : {}),
|
|
495
562
|
...(workflowApplied ? { workflowOverride: workflowApplied } : {}),
|
|
496
563
|
},
|
|
497
564
|
};
|
|
@@ -607,6 +674,27 @@ export function createOrchestrateOps(
|
|
|
607
674
|
const healthStatus = contextHealth.check();
|
|
608
675
|
const healthWarning = buildHealthWarning(healthStatus, vault);
|
|
609
676
|
|
|
677
|
+
// Check for subagent review stage requirements from matched playbook
|
|
678
|
+
const legacyPlanForReview = planner.get(planId);
|
|
679
|
+
let reviewStagesRequired: string[] | undefined;
|
|
680
|
+
if (legacyPlanForReview?.playbookSessionId && runtime.playbookExecutor) {
|
|
681
|
+
const session_ = runtime.playbookExecutor.getSession(
|
|
682
|
+
legacyPlanForReview.playbookSessionId,
|
|
683
|
+
);
|
|
684
|
+
if (session_) {
|
|
685
|
+
const postTaskGates = session_.gates.filter((g) => g.phase === 'post-task');
|
|
686
|
+
if (postTaskGates.length > 0) {
|
|
687
|
+
reviewStagesRequired = postTaskGates.map((g) => g.checkType);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
} else if (
|
|
691
|
+
legacyPlanForReview?.playbookMatch?.genericId === 'generic-subagent-execution' ||
|
|
692
|
+
legacyPlanForReview?.playbookMatch?.label?.toLowerCase().includes('subagent')
|
|
693
|
+
) {
|
|
694
|
+
// Playbook matched but no live session — surface known review stages
|
|
695
|
+
reviewStagesRequired = ['spec-review', 'quality-review'];
|
|
696
|
+
}
|
|
697
|
+
|
|
610
698
|
return {
|
|
611
699
|
plan: { id: planId, status: 'executing' },
|
|
612
700
|
session,
|
|
@@ -618,6 +706,14 @@ export function createOrchestrateOps(
|
|
|
618
706
|
durationMs: aggregated.durationMs,
|
|
619
707
|
totalUsage: aggregated.totalUsage,
|
|
620
708
|
},
|
|
709
|
+
...(reviewStagesRequired
|
|
710
|
+
? {
|
|
711
|
+
reviewStagesRequired,
|
|
712
|
+
reviewNote:
|
|
713
|
+
'Subagent Execution playbook matched. Each completed task requires review evidence before status can be set to completed: ' +
|
|
714
|
+
reviewStagesRequired.join(' → '),
|
|
715
|
+
}
|
|
716
|
+
: {}),
|
|
621
717
|
...(reapedOrphans.length > 0 ? { reapedOrphans } : {}),
|
|
622
718
|
...(healthWarning ? { contextHealth: healthWarning } : {}),
|
|
623
719
|
};
|
|
@@ -775,7 +871,10 @@ export function createOrchestrateOps(
|
|
|
775
871
|
.string()
|
|
776
872
|
.optional()
|
|
777
873
|
.describe('ID of the executing plan to complete (optional for direct tasks)'),
|
|
778
|
-
sessionId: z
|
|
874
|
+
sessionId: z
|
|
875
|
+
.string()
|
|
876
|
+
.optional()
|
|
877
|
+
.describe('ID of the brain session to end (auto-resolved from planId if omitted)'),
|
|
779
878
|
outcome: z
|
|
780
879
|
.enum(['completed', 'abandoned', 'partial'])
|
|
781
880
|
.optional()
|
|
@@ -842,7 +941,10 @@ export function createOrchestrateOps(
|
|
|
842
941
|
}),
|
|
843
942
|
handler: async (params) => {
|
|
844
943
|
const planId = params.planId as string | undefined;
|
|
845
|
-
const sessionId =
|
|
944
|
+
const sessionId =
|
|
945
|
+
(params.sessionId as string | undefined) ??
|
|
946
|
+
(planId ? brainIntelligence.getSessionByPlanId(planId)?.id : undefined) ??
|
|
947
|
+
'';
|
|
846
948
|
const outcome = (params.outcome as string) ?? 'completed';
|
|
847
949
|
const completionSummary = (params.summary as string) ?? '';
|
|
848
950
|
const toolsUsed = (params.toolsUsed as string[]) ?? [];
|
|
@@ -945,17 +1047,19 @@ export function createOrchestrateOps(
|
|
|
945
1047
|
};
|
|
946
1048
|
}
|
|
947
1049
|
|
|
948
|
-
// End brain session —
|
|
1050
|
+
// End brain session — only if we have a valid sessionId
|
|
949
1051
|
const fixTrail = evidenceReport ? buildFixTrailSummary(evidenceReport) : undefined;
|
|
950
|
-
const session =
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1052
|
+
const session = sessionId
|
|
1053
|
+
? brainIntelligence.lifecycle({
|
|
1054
|
+
action: 'end',
|
|
1055
|
+
sessionId,
|
|
1056
|
+
planId,
|
|
1057
|
+
planOutcome: outcome,
|
|
1058
|
+
toolsUsed,
|
|
1059
|
+
filesModified,
|
|
1060
|
+
...(fixTrail ? { context: `Fix trail: ${fixTrail}` } : {}),
|
|
1061
|
+
})
|
|
1062
|
+
: null;
|
|
959
1063
|
|
|
960
1064
|
// Record brain feedback for vault entries referenced in plan decisions
|
|
961
1065
|
if (planObj && planObj.decisions) {
|
|
@@ -1018,6 +1122,11 @@ export function createOrchestrateOps(
|
|
|
1018
1122
|
entry.plan.context.probes,
|
|
1019
1123
|
entry.plan.context.projectPath,
|
|
1020
1124
|
summary,
|
|
1125
|
+
{
|
|
1126
|
+
intent: entry.plan.intent,
|
|
1127
|
+
objective: completionSummary || entry.plan.summary,
|
|
1128
|
+
domain: entry.plan.context.entities?.technologies?.[0],
|
|
1129
|
+
},
|
|
1021
1130
|
);
|
|
1022
1131
|
} catch {
|
|
1023
1132
|
// Epilogue is best-effort
|
|
@@ -1043,11 +1152,27 @@ export function createOrchestrateOps(
|
|
|
1043
1152
|
}
|
|
1044
1153
|
}
|
|
1045
1154
|
|
|
1155
|
+
// Best-effort worktree cleanup after plan completion
|
|
1156
|
+
try {
|
|
1157
|
+
const { worktreeReap } = await import('../utils/worktree-reaper.js');
|
|
1158
|
+
Promise.resolve()
|
|
1159
|
+
.then(() => worktreeReap((params.projectPath as string) ?? '.'))
|
|
1160
|
+
.catch(() => {
|
|
1161
|
+
/* best-effort */
|
|
1162
|
+
});
|
|
1163
|
+
} catch {
|
|
1164
|
+
/* skip silently */
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1046
1167
|
return {
|
|
1047
1168
|
plan: completedPlan,
|
|
1048
1169
|
session,
|
|
1049
1170
|
extraction,
|
|
1050
|
-
epilogue: epilogueResult
|
|
1171
|
+
epilogue: epilogueResult ?? {
|
|
1172
|
+
completed: true,
|
|
1173
|
+
captured: false,
|
|
1174
|
+
note: 'no flow plan in store',
|
|
1175
|
+
},
|
|
1051
1176
|
...(impactReport ? { impactAnalysis: impactReport } : {}),
|
|
1052
1177
|
evidenceReport,
|
|
1053
1178
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
@@ -44,32 +44,6 @@ describe('createPackOps', () => {
|
|
|
44
44
|
setHotRegister(null as never);
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
it('returns 4 ops', () => {
|
|
48
|
-
runtime = makeMockRuntime();
|
|
49
|
-
ops = createPackOps(runtime);
|
|
50
|
-
expect(ops).toHaveLength(4);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('has correct op names', () => {
|
|
54
|
-
runtime = makeMockRuntime();
|
|
55
|
-
ops = createPackOps(runtime);
|
|
56
|
-
expect(ops.map((o) => o.name)).toEqual([
|
|
57
|
-
'pack_validate',
|
|
58
|
-
'pack_install',
|
|
59
|
-
'pack_list',
|
|
60
|
-
'pack_uninstall',
|
|
61
|
-
]);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('assigns correct auth levels', () => {
|
|
65
|
-
runtime = makeMockRuntime();
|
|
66
|
-
ops = createPackOps(runtime);
|
|
67
|
-
expect(findOp('pack_validate').auth).toBe('read');
|
|
68
|
-
expect(findOp('pack_install').auth).toBe('admin');
|
|
69
|
-
expect(findOp('pack_list').auth).toBe('read');
|
|
70
|
-
expect(findOp('pack_uninstall').auth).toBe('admin');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
47
|
describe('pack_validate', () => {
|
|
74
48
|
it('delegates to packInstaller.validate', async () => {
|
|
75
49
|
runtime = makeMockRuntime();
|