@soleri/core 9.15.0 → 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/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/context/context-engine.js +1 -1
- package/dist/context/context-engine.js.map +1 -1
- 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 +31 -4
- 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 +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.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 +1 -0
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +7 -0
- 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/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 +87 -17
- 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 +12 -0
- 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/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/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/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/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/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 +1 -1
- 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/second-brain-features.test.ts +2 -14
- 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/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/tag-manager.test.ts +0 -4
- 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 +17 -0
- package/src/intake/dedup-gate.test.ts +2 -6
- package/src/intake/text-ingester.test.ts +3 -4
- 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.ts +8 -0
- 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 +185 -13
- package/src/runtime/admin-setup-ops.ts +86 -16
- 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 +12 -0
- 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/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/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 +12 -11
- 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/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/vault-connect.test.ts +1 -1
- package/src/vault/vault-entries.ts +3 -1
- package/src/vault/vault.test.ts +23 -8
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
* Epilogue — colocated contract tests.
|
|
3
3
|
*
|
|
4
4
|
* Contract:
|
|
5
|
-
* - runEpilogue() calls capture_knowledge when vault is available
|
|
5
|
+
* - runEpilogue() calls capture_knowledge with intent-specific title when vault is available
|
|
6
6
|
* - runEpilogue() calls session_capture when sessionStore is available
|
|
7
7
|
* - Returns { captured: true, sessionId } on success
|
|
8
8
|
* - Silently ignores errors from dispatch (best-effort)
|
|
9
9
|
* - Returns { captured: false } when no probes are available
|
|
10
|
+
* - Title format: "{INTENT} execution — {objective}" (max 120 chars)
|
|
11
|
+
* - Tags include intent (lowercase) and domain (if provided)
|
|
10
12
|
*/
|
|
11
13
|
|
|
12
14
|
import { describe, it, expect, vi } from 'vitest';
|
|
@@ -33,7 +35,6 @@ describe('runEpilogue', () => {
|
|
|
33
35
|
expect(dispatch).toHaveBeenCalledWith(
|
|
34
36
|
'capture_knowledge',
|
|
35
37
|
expect.objectContaining({
|
|
36
|
-
title: 'Flow execution summary',
|
|
37
38
|
content: 'summary',
|
|
38
39
|
type: 'workflow',
|
|
39
40
|
projectPath: '/project',
|
|
@@ -42,6 +43,43 @@ describe('runEpilogue', () => {
|
|
|
42
43
|
expect(result.captured).toBe(true);
|
|
43
44
|
});
|
|
44
45
|
|
|
46
|
+
it('uses intent-specific title when planContext is provided', async () => {
|
|
47
|
+
const dispatch = vi.fn(async () => ({ tool: 'capture_knowledge', status: 'ok', data: {} }));
|
|
48
|
+
await runEpilogue(dispatch, probes({ vault: true }), '/project', 'summary', {
|
|
49
|
+
intent: 'BUILD',
|
|
50
|
+
objective: 'add authentication module',
|
|
51
|
+
domain: 'auth',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(dispatch).toHaveBeenCalledWith(
|
|
55
|
+
'capture_knowledge',
|
|
56
|
+
expect.objectContaining({
|
|
57
|
+
title: 'BUILD execution — add authentication module',
|
|
58
|
+
tags: expect.arrayContaining(['auto-captured', 'build', 'auth']),
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('falls back to FLOW intent when planContext is absent', async () => {
|
|
64
|
+
const dispatch = vi.fn(async () => ({ tool: 'capture_knowledge', status: 'ok', data: {} }));
|
|
65
|
+
await runEpilogue(dispatch, probes({ vault: true }), '/project', 'done summary');
|
|
66
|
+
|
|
67
|
+
const call = dispatch.mock.calls[0]?.[1] as Record<string, unknown>;
|
|
68
|
+
expect((call.title as string).startsWith('FLOW execution —')).toBe(true);
|
|
69
|
+
expect((call.tags as string[]).includes('flow')).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('omits domain tag when domain is not provided', async () => {
|
|
73
|
+
const dispatch = vi.fn(async () => ({ tool: 'capture_knowledge', status: 'ok', data: {} }));
|
|
74
|
+
await runEpilogue(dispatch, probes({ vault: true }), '/project', 'summary', {
|
|
75
|
+
intent: 'FIX',
|
|
76
|
+
objective: 'fix login bug',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const call = dispatch.mock.calls[0]?.[1] as Record<string, unknown>;
|
|
80
|
+
expect(call.tags).toEqual(['auto-captured', 'fix']);
|
|
81
|
+
});
|
|
82
|
+
|
|
45
83
|
it('captures session when sessionStore is available', async () => {
|
|
46
84
|
const dispatch = vi.fn(async () => ({
|
|
47
85
|
tool: 'session_capture',
|
package/src/flows/epilogue.ts
CHANGED
|
@@ -22,18 +22,27 @@ export async function runEpilogue(
|
|
|
22
22
|
probes: ProbeResults,
|
|
23
23
|
projectPath: string,
|
|
24
24
|
summary: string,
|
|
25
|
+
planContext?: { intent?: string; objective?: string; domain?: string },
|
|
25
26
|
): Promise<{ captured: boolean; sessionId?: string }> {
|
|
26
27
|
let captured = false;
|
|
27
28
|
let sessionId: string | undefined;
|
|
28
29
|
|
|
29
30
|
// Capture knowledge to vault
|
|
30
31
|
if (probes.vault) {
|
|
32
|
+
const intent = planContext?.intent?.toUpperCase() ?? 'FLOW';
|
|
33
|
+
const objective = planContext?.objective ?? summary;
|
|
34
|
+
const title = `${intent} execution — ${objective}`.slice(0, 120);
|
|
35
|
+
const tags = [
|
|
36
|
+
'auto-captured',
|
|
37
|
+
intent.toLowerCase(),
|
|
38
|
+
...(planContext?.domain ? [planContext.domain] : []),
|
|
39
|
+
];
|
|
31
40
|
try {
|
|
32
41
|
await dispatch('capture_knowledge', {
|
|
33
|
-
title
|
|
42
|
+
title,
|
|
34
43
|
content: summary,
|
|
35
44
|
type: 'workflow',
|
|
36
|
-
tags
|
|
45
|
+
tags,
|
|
37
46
|
projectPath,
|
|
38
47
|
});
|
|
39
48
|
captured = true;
|
|
@@ -230,9 +230,9 @@ describe('FlowExecutor', () => {
|
|
|
230
230
|
expect(result.planId).toBe('test-plan');
|
|
231
231
|
expect(result.totalSteps).toBe(2);
|
|
232
232
|
expect(result.stepsCompleted).toBe(2);
|
|
233
|
-
expect(result.durationMs).
|
|
233
|
+
expect(typeof result.durationMs).toBe('number');
|
|
234
234
|
expect(result.stepResults).toHaveLength(2);
|
|
235
|
-
expect(result.stepResults[0].durationMs).
|
|
235
|
+
expect(typeof result.stepResults[0].durationMs).toBe('number');
|
|
236
236
|
});
|
|
237
237
|
|
|
238
238
|
it('returns failed status when a step has a STOP gate that fails', async () => {
|
|
@@ -260,4 +260,50 @@ describe('FlowExecutor', () => {
|
|
|
260
260
|
expect(result.stepResults[0].gateResult?.message).toBe('Blocked');
|
|
261
261
|
});
|
|
262
262
|
});
|
|
263
|
+
|
|
264
|
+
describe('step context (output → input flow)', () => {
|
|
265
|
+
it('passes prior step outputs as context to subsequent steps', async () => {
|
|
266
|
+
const received: Array<Record<string, unknown>> = [];
|
|
267
|
+
|
|
268
|
+
const dispatch = vi.fn(async (tool: string, params: Record<string, unknown>) => {
|
|
269
|
+
received.push({ tool, context: params.context });
|
|
270
|
+
return {
|
|
271
|
+
tool,
|
|
272
|
+
status: 'ok',
|
|
273
|
+
data: { 'vault-patterns': ['pattern-A', 'pattern-B'] },
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const executor = new FlowExecutor(dispatch);
|
|
278
|
+
const plan = makePlan([
|
|
279
|
+
step('search-vault', ['vault.search'], { output: ['vault-patterns'] }),
|
|
280
|
+
step('brainstorm', ['brain.recommend']),
|
|
281
|
+
]);
|
|
282
|
+
|
|
283
|
+
await executor.execute(plan);
|
|
284
|
+
|
|
285
|
+
// Step 1 receives empty context
|
|
286
|
+
expect(received[0].context).toEqual({});
|
|
287
|
+
|
|
288
|
+
// Step 2 receives vault-patterns from step 1
|
|
289
|
+
expect(received[1].context).toEqual({ 'vault-patterns': ['pattern-A', 'pattern-B'] });
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('steps with no output declaration do not pollute context', async () => {
|
|
293
|
+
const received: Array<Record<string, unknown>> = [];
|
|
294
|
+
|
|
295
|
+
const dispatch = vi.fn(async (tool: string, params: Record<string, unknown>) => {
|
|
296
|
+
received.push({ tool, context: params.context });
|
|
297
|
+
return { tool, status: 'ok', data: { 'some-key': 'value' } };
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const executor = new FlowExecutor(dispatch);
|
|
301
|
+
const plan = makePlan([step('s1', ['tool-a']), step('s2', ['tool-b'])]);
|
|
302
|
+
|
|
303
|
+
await executor.execute(plan);
|
|
304
|
+
|
|
305
|
+
// No output declared on s1 — context stays empty for s2
|
|
306
|
+
expect(received[1].context).toEqual({});
|
|
307
|
+
});
|
|
308
|
+
});
|
|
263
309
|
});
|
package/src/flows/executor.ts
CHANGED
|
@@ -104,6 +104,9 @@ export class FlowExecutor {
|
|
|
104
104
|
let branchIterations = 0;
|
|
105
105
|
let currentIndex = 0;
|
|
106
106
|
|
|
107
|
+
// Accumulated outputs from completed steps — passed as context to subsequent dispatches
|
|
108
|
+
const stepContext: Record<string, unknown> = {};
|
|
109
|
+
|
|
107
110
|
// Set up persistence if configured
|
|
108
111
|
let runDir: string | undefined;
|
|
109
112
|
let manifest: PlanRunManifest | undefined;
|
|
@@ -118,12 +121,13 @@ export class FlowExecutor {
|
|
|
118
121
|
step.status = 'running';
|
|
119
122
|
|
|
120
123
|
const toolResults: StepResult['toolResults'] = {};
|
|
124
|
+
const dispatchParams = { stepId: step.id, planId: plan.planId, context: { ...stepContext } };
|
|
121
125
|
|
|
122
126
|
try {
|
|
123
127
|
if (step.parallel && step.tools.length > 1) {
|
|
124
128
|
// Execute tools in parallel
|
|
125
129
|
const results = await Promise.allSettled(
|
|
126
|
-
step.tools.map((tool) => this.dispatch(tool,
|
|
130
|
+
step.tools.map((tool) => this.dispatch(tool, dispatchParams)),
|
|
127
131
|
);
|
|
128
132
|
for (let i = 0; i < step.tools.length; i++) {
|
|
129
133
|
const toolName = step.tools[i];
|
|
@@ -144,10 +148,7 @@ export class FlowExecutor {
|
|
|
144
148
|
// Execute tools sequentially
|
|
145
149
|
for (const toolName of step.tools) {
|
|
146
150
|
try {
|
|
147
|
-
toolResults[toolName] = await this.dispatch(toolName,
|
|
148
|
-
stepId: step.id,
|
|
149
|
-
planId: plan.planId,
|
|
150
|
-
});
|
|
151
|
+
toolResults[toolName] = await this.dispatch(toolName, dispatchParams);
|
|
151
152
|
} catch (_err) {
|
|
152
153
|
toolResults[toolName] = {
|
|
153
154
|
tool: toolName,
|
|
@@ -180,6 +181,15 @@ export class FlowExecutor {
|
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
183
|
|
|
184
|
+
// Accumulate declared step outputs into stepContext for subsequent steps
|
|
185
|
+
if (step.output) {
|
|
186
|
+
for (const outputKey of step.output) {
|
|
187
|
+
if (outputKey in flatData) {
|
|
188
|
+
stepContext[outputKey] = flatData[outputKey];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
183
193
|
const verdict = evaluateGate(step.gate, flatData);
|
|
184
194
|
|
|
185
195
|
const stepResult: StepResult = {
|
package/src/flows/index.ts
CHANGED
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
export type {
|
|
7
7
|
Flow,
|
|
8
8
|
FlowStep,
|
|
9
|
-
Gate,
|
|
10
|
-
GateAction,
|
|
11
9
|
ProbeName,
|
|
12
10
|
ProbeResults,
|
|
13
11
|
PlanStep,
|
|
@@ -38,7 +36,7 @@ export {
|
|
|
38
36
|
|
|
39
37
|
// Context router
|
|
40
38
|
export { detectContext, applyContextOverrides, getFlowOverrides } from './context-router.js';
|
|
41
|
-
|
|
39
|
+
// ContextOverride is intentionally unexported — internal use only
|
|
42
40
|
|
|
43
41
|
// Gate evaluator
|
|
44
42
|
export { evaluateGate, evaluateCondition, extractScore, resolvePath } from './gate-evaluator.js';
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plan-builder — colocated contract tests.
|
|
3
|
+
*
|
|
4
|
+
* Contract:
|
|
5
|
+
* - buildPlan() returns blocked:true with zero steps when a blocking capability's probe fails
|
|
6
|
+
* - buildPlan() skips (not blocks) steps whose optional probes are unavailable
|
|
7
|
+
* - buildPlan() builds a normal plan when all blocking capabilities are available
|
|
8
|
+
* - capabilityToProbe() maps known capability ID prefixes to probe names
|
|
9
|
+
* - capabilityToProbe() returns undefined for unmapped capabilities (no spurious blocking)
|
|
10
|
+
* - buildPlan() attaches vault constraints as recommendations (not gate steps)
|
|
11
|
+
* - buildPlan() marks mandatory entries and anti-patterns as mandatory:true in recommendations
|
|
12
|
+
* - buildPlan() includes recommendations in blocked plans
|
|
13
|
+
* - buildPlan() does not inject vault-gate-* steps
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
17
|
+
import { buildPlan, capabilityToProbe, type VaultConstraint } from './plan-builder.js';
|
|
18
|
+
import type { AgentRuntime } from '../runtime/types.js';
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Helpers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
function makeRuntime(vaultAvailable: boolean, brainAvailable = false): AgentRuntime {
|
|
25
|
+
return {
|
|
26
|
+
vault: {
|
|
27
|
+
stats: vi.fn(() =>
|
|
28
|
+
vaultAvailable
|
|
29
|
+
? { totalEntries: 10 }
|
|
30
|
+
: (() => {
|
|
31
|
+
throw new Error('vault down');
|
|
32
|
+
})(),
|
|
33
|
+
),
|
|
34
|
+
},
|
|
35
|
+
brain: {
|
|
36
|
+
getVocabularySize: vi.fn(() => (brainAvailable ? 5 : 0)),
|
|
37
|
+
},
|
|
38
|
+
projectRegistry: {
|
|
39
|
+
list: vi.fn(() => []),
|
|
40
|
+
},
|
|
41
|
+
} as unknown as AgentRuntime;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// capabilityToProbe unit tests
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
describe('capabilityToProbe', () => {
|
|
49
|
+
it('maps vault.* capabilities to the vault probe', () => {
|
|
50
|
+
expect(capabilityToProbe('vault.search')).toBe('vault');
|
|
51
|
+
expect(capabilityToProbe('vault.load')).toBe('vault');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('maps brain.* capabilities to the brain probe', () => {
|
|
55
|
+
expect(capabilityToProbe('brain.recommend')).toBe('brain');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('returns undefined for capabilities with no probe mapping — unknown cap does not block', () => {
|
|
59
|
+
// An unmapped capability must never trigger a blocking halt.
|
|
60
|
+
// If this returned a valid probe name, unrelated capabilities would silently block flows.
|
|
61
|
+
expect(capabilityToProbe('auth.validate')).toBeUndefined();
|
|
62
|
+
expect(capabilityToProbe('unknown.op')).toBeUndefined();
|
|
63
|
+
expect(capabilityToProbe('')).toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// buildPlan blocking behaviour
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
describe('buildPlan — blocking capability enforcement', () => {
|
|
72
|
+
it('returns blocked:true with zero steps when vault is down and vault.search is blocking', async () => {
|
|
73
|
+
// vault.search is in the blocking list of all 8 flows.
|
|
74
|
+
// When vault probe fails, the plan must halt — not silently skip steps.
|
|
75
|
+
const runtime = makeRuntime(false);
|
|
76
|
+
const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime);
|
|
77
|
+
|
|
78
|
+
expect(plan.blocked).toBe(true);
|
|
79
|
+
expect(plan.steps).toHaveLength(0);
|
|
80
|
+
expect(plan.warnings[0]).toMatch(/Blocked/);
|
|
81
|
+
expect(plan.warnings[0]).toMatch(/vault\.search/);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('builds a normal plan when vault is available', async () => {
|
|
85
|
+
// Blocking check must pass through when the probe is healthy.
|
|
86
|
+
// If blocking fired regardless of probe state, no plan would ever build.
|
|
87
|
+
const runtime = makeRuntime(true);
|
|
88
|
+
const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime);
|
|
89
|
+
|
|
90
|
+
expect(plan.blocked).toBeUndefined();
|
|
91
|
+
expect(plan.steps.length).toBeGreaterThanOrEqual(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('skips (not blocks) steps whose required probe is missing but not in blocking list', async () => {
|
|
95
|
+
// brain is not in the blocking list — its absence should skip brain-dependent
|
|
96
|
+
// steps with a warning, not halt the entire plan.
|
|
97
|
+
const runtime = makeRuntime(true, false); // vault up, brain down
|
|
98
|
+
const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime);
|
|
99
|
+
|
|
100
|
+
expect(plan.blocked).toBeUndefined();
|
|
101
|
+
// Plan continues; brain-dependent steps are skipped or warnings added
|
|
102
|
+
const hasBrainWarning =
|
|
103
|
+
plan.warnings.some((w) => /brain/i.test(w)) ||
|
|
104
|
+
plan.skipped.some((s) => /brain/i.test(s.reason));
|
|
105
|
+
// Either skipped or warned — what matters is the plan is not blocked
|
|
106
|
+
expect(plan.steps.length).toBeGreaterThanOrEqual(0);
|
|
107
|
+
expect(plan.blocked).toBeUndefined();
|
|
108
|
+
// suppress unused-var lint
|
|
109
|
+
void hasBrainWarning;
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// buildPlan vault recommendations
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
describe('buildPlan — vault recommendations', () => {
|
|
118
|
+
it('attaches mandatory constraint as recommendation with mandatory:true', async () => {
|
|
119
|
+
// Critical vault entries must be surfaced as mandatory recommendations so the
|
|
120
|
+
// executor can enforce them. They must NOT become gate steps (evaluateCondition
|
|
121
|
+
// cannot parse free-text narrative — gates would always fire STOP).
|
|
122
|
+
const runtime = makeRuntime(true);
|
|
123
|
+
const constraint: VaultConstraint = {
|
|
124
|
+
entryId: 'crit-1',
|
|
125
|
+
title: 'No skipping tests',
|
|
126
|
+
context: 'Tests must not be skipped under time pressure.',
|
|
127
|
+
mandatory: true,
|
|
128
|
+
entryType: 'pattern',
|
|
129
|
+
};
|
|
130
|
+
const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime, undefined, [constraint]);
|
|
131
|
+
|
|
132
|
+
const rec = plan.recommendations?.find((r) => r.entryId === 'crit-1');
|
|
133
|
+
expect(rec).toBeDefined();
|
|
134
|
+
expect(rec?.title).toBe('No skipping tests');
|
|
135
|
+
expect(rec?.context).toBe('Tests must not be skipped under time pressure.');
|
|
136
|
+
expect(rec?.mandatory).toBe(true);
|
|
137
|
+
expect(rec?.strength).toBe(100);
|
|
138
|
+
expect(rec?.source).toBe('vault');
|
|
139
|
+
// No gate step injected
|
|
140
|
+
expect(plan.steps.filter((s) => s.id.startsWith('vault-gate-'))).toHaveLength(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('marks anti-pattern entry as mandatory:true even when mandatory flag is false', async () => {
|
|
144
|
+
// anti-pattern entries are always treated as mandatory regardless of severity flag.
|
|
145
|
+
const runtime = makeRuntime(true);
|
|
146
|
+
const constraint: VaultConstraint = {
|
|
147
|
+
entryId: 'ap-1',
|
|
148
|
+
title: 'Avoid God Objects',
|
|
149
|
+
context: 'Classes must not exceed 500 lines.',
|
|
150
|
+
mandatory: false,
|
|
151
|
+
entryType: 'anti-pattern',
|
|
152
|
+
};
|
|
153
|
+
const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime, undefined, [constraint]);
|
|
154
|
+
|
|
155
|
+
const rec = plan.recommendations?.find((r) => r.entryId === 'ap-1');
|
|
156
|
+
expect(rec).toBeDefined();
|
|
157
|
+
expect(rec?.mandatory).toBe(true);
|
|
158
|
+
expect(plan.steps.filter((s) => s.id.startsWith('vault-gate-'))).toHaveLength(0);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('does not attach recommendations when no constraints are passed', async () => {
|
|
162
|
+
// Backward compatibility: callers that omit vaultConstraints get an unchanged plan.
|
|
163
|
+
const runtime = makeRuntime(true);
|
|
164
|
+
const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime);
|
|
165
|
+
expect(plan.recommendations).toBeUndefined();
|
|
166
|
+
expect(plan.steps.filter((s) => s.id.startsWith('vault-gate-'))).toHaveLength(0);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('attaches non-mandatory pattern as recommendation with mandatory:false', async () => {
|
|
170
|
+
// Warning and suggestion vault entries are surfaced as non-mandatory recommendations.
|
|
171
|
+
const runtime = makeRuntime(true);
|
|
172
|
+
const constraint: VaultConstraint = {
|
|
173
|
+
entryId: 'sug-1',
|
|
174
|
+
title: 'Consider using named exports',
|
|
175
|
+
mandatory: false,
|
|
176
|
+
entryType: 'pattern',
|
|
177
|
+
};
|
|
178
|
+
const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime, undefined, [constraint]);
|
|
179
|
+
const rec = plan.recommendations?.find((r) => r.entryId === 'sug-1');
|
|
180
|
+
expect(rec).toBeDefined();
|
|
181
|
+
expect(rec?.mandatory).toBe(false);
|
|
182
|
+
expect(rec?.strength).toBe(80);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('includes recommendations in blocked plans', async () => {
|
|
186
|
+
// Blocked plans must still carry vault constraints so callers can surface them.
|
|
187
|
+
const runtime = makeRuntime(false); // vault down → blocked
|
|
188
|
+
const constraint: VaultConstraint = {
|
|
189
|
+
entryId: 'crit-2',
|
|
190
|
+
title: 'No direct DB writes outside repositories',
|
|
191
|
+
mandatory: true,
|
|
192
|
+
entryType: 'anti-pattern',
|
|
193
|
+
};
|
|
194
|
+
const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime, undefined, [constraint]);
|
|
195
|
+
|
|
196
|
+
expect(plan.blocked).toBe(true);
|
|
197
|
+
const rec = plan.recommendations?.find((r) => r.entryId === 'crit-2');
|
|
198
|
+
expect(rec).toBeDefined();
|
|
199
|
+
expect(rec?.mandatory).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
OrchestrationPlan,
|
|
12
12
|
ProbeResults,
|
|
13
13
|
ProbeName,
|
|
14
|
+
VaultRecommendation,
|
|
14
15
|
} from './types.js';
|
|
15
16
|
import { loadFlowById } from './loader.js';
|
|
16
17
|
import { runProbes } from './probes.js';
|
|
@@ -56,6 +57,7 @@ export function chainToRequires(chain: string): ProbeName | undefined {
|
|
|
56
57
|
if (lower.startsWith('component') || lower.startsWith('token') || lower.startsWith('design'))
|
|
57
58
|
return 'designSystem';
|
|
58
59
|
if (lower.startsWith('session')) return 'sessionStore';
|
|
60
|
+
if (lower.startsWith('test')) return 'test';
|
|
59
61
|
// recommend-* and get-stack-* have no hard requirements
|
|
60
62
|
if (lower.startsWith('recommend') || lower.startsWith('get-stack')) return undefined;
|
|
61
63
|
return undefined;
|
|
@@ -119,6 +121,7 @@ export function flowStepsToPlanSteps(
|
|
|
119
121
|
tools,
|
|
120
122
|
parallel: step.parallel ?? false,
|
|
121
123
|
requires,
|
|
124
|
+
output: step.output,
|
|
122
125
|
status: 'pending',
|
|
123
126
|
};
|
|
124
127
|
|
|
@@ -155,6 +158,19 @@ export function flowStepsToPlanSteps(
|
|
|
155
158
|
});
|
|
156
159
|
}
|
|
157
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Map a capability ID (e.g. "vault.search") to the probe name that covers it.
|
|
163
|
+
* Returns undefined for capability IDs that have no corresponding probe.
|
|
164
|
+
*/
|
|
165
|
+
export function capabilityToProbe(capId: string): ProbeName | undefined {
|
|
166
|
+
if (capId.startsWith('vault.') || capId === 'vault') return 'vault';
|
|
167
|
+
if (capId.startsWith('brain.') || capId === 'brain') return 'brain';
|
|
168
|
+
if (capId.startsWith('design.') || capId.startsWith('component.') || capId.startsWith('token.'))
|
|
169
|
+
return 'designSystem';
|
|
170
|
+
if (capId.startsWith('session.')) return 'sessionStore';
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
158
174
|
/**
|
|
159
175
|
* Remove steps whose required capabilities are not available.
|
|
160
176
|
*/
|
|
@@ -181,6 +197,18 @@ export function pruneSteps(
|
|
|
181
197
|
return { kept, skipped };
|
|
182
198
|
}
|
|
183
199
|
|
|
200
|
+
/**
|
|
201
|
+
* A vault entry that should influence plan structure.
|
|
202
|
+
* critical severity OR anti-pattern type entries are surfaced as mandatory recommendations.
|
|
203
|
+
*/
|
|
204
|
+
export interface VaultConstraint {
|
|
205
|
+
entryId: string;
|
|
206
|
+
title: string;
|
|
207
|
+
context?: string;
|
|
208
|
+
mandatory: boolean;
|
|
209
|
+
entryType?: 'pattern' | 'anti-pattern' | 'rule' | 'playbook';
|
|
210
|
+
}
|
|
211
|
+
|
|
184
212
|
/**
|
|
185
213
|
* Build a full orchestration plan from intent, agent config, and runtime.
|
|
186
214
|
*/
|
|
@@ -190,6 +218,7 @@ export async function buildPlan(
|
|
|
190
218
|
projectPath: string,
|
|
191
219
|
runtime: AgentRuntime,
|
|
192
220
|
prompt?: string,
|
|
221
|
+
vaultConstraints: VaultConstraint[] = [],
|
|
193
222
|
): Promise<OrchestrationPlan> {
|
|
194
223
|
const normalizedIntent = intent.toUpperCase();
|
|
195
224
|
const flowId = INTENT_TO_FLOW[normalizedIntent] ?? 'BUILD-flow';
|
|
@@ -197,17 +226,63 @@ export async function buildPlan(
|
|
|
197
226
|
|
|
198
227
|
const probes = await runProbes(runtime, projectPath);
|
|
199
228
|
|
|
229
|
+
// Map vault constraints to recommendations — surfaced to executor as knowledge context.
|
|
230
|
+
// Anti-pattern entries are always mandatory regardless of the mandatory flag.
|
|
231
|
+
const recommendations: VaultRecommendation[] = vaultConstraints.map((c) => ({
|
|
232
|
+
entryId: c.entryId,
|
|
233
|
+
title: c.title,
|
|
234
|
+
...(c.context ? { context: c.context } : {}),
|
|
235
|
+
mandatory: c.mandatory || c.entryType === 'anti-pattern',
|
|
236
|
+
entryType: c.entryType,
|
|
237
|
+
source: 'vault' as const,
|
|
238
|
+
strength: c.mandatory ? 100 : 80,
|
|
239
|
+
}));
|
|
240
|
+
|
|
241
|
+
// Detect context entities from prompt before any early returns — blocked plans
|
|
242
|
+
// should still carry entity context so callers can surface useful information.
|
|
243
|
+
const entities = { components: [] as string[], actions: [] as string[] };
|
|
244
|
+
const contexts = prompt ? detectContext(prompt, entities) : [];
|
|
245
|
+
|
|
200
246
|
let steps: PlanStep[] = [];
|
|
201
247
|
let skipped: SkippedStep[] = [];
|
|
202
248
|
const warnings: string[] = [];
|
|
203
249
|
|
|
204
250
|
if (flow) {
|
|
251
|
+
// Check blocking capabilities before pruning optional steps.
|
|
252
|
+
// If any blocking capability maps to an unavailable probe, the plan cannot run.
|
|
253
|
+
const blockingCaps = flow['on-missing-capability']?.blocking ?? [];
|
|
254
|
+
const missingBlockers = blockingCaps.filter((capId) => {
|
|
255
|
+
const probe = capabilityToProbe(capId);
|
|
256
|
+
return probe !== undefined && !probes[probe];
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
if (missingBlockers.length > 0) {
|
|
260
|
+
return {
|
|
261
|
+
planId: randomUUID(),
|
|
262
|
+
intent: normalizedIntent,
|
|
263
|
+
flowId,
|
|
264
|
+
steps: [],
|
|
265
|
+
skipped: [],
|
|
266
|
+
epilogue: [],
|
|
267
|
+
warnings: [
|
|
268
|
+
`Blocked: required capabilities unavailable — ${missingBlockers.join(', ')}. Resolve these before running this flow.`,
|
|
269
|
+
],
|
|
270
|
+
summary: prompt ?? `${normalizedIntent} plan blocked`,
|
|
271
|
+
estimatedTools: 0,
|
|
272
|
+
blocked: true,
|
|
273
|
+
...(recommendations.length > 0 ? { recommendations } : {}),
|
|
274
|
+
context: {
|
|
275
|
+
intent: normalizedIntent,
|
|
276
|
+
probes,
|
|
277
|
+
entities,
|
|
278
|
+
projectPath,
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
205
283
|
let allSteps = flowStepsToPlanSteps(flow, agentId);
|
|
206
284
|
|
|
207
|
-
//
|
|
208
|
-
// and apply chain overrides (inject, skip, substitute) before pruning.
|
|
209
|
-
const entities = { components: [] as string[], actions: [] as string[] };
|
|
210
|
-
const contexts = prompt ? detectContext(prompt, entities) : [];
|
|
285
|
+
// Apply context-sensitive chain overrides (inject, skip, substitute) before pruning.
|
|
211
286
|
if (contexts.length > 0) {
|
|
212
287
|
allSteps = applyContextOverrides(allSteps, contexts, flowId, agentId);
|
|
213
288
|
}
|
|
@@ -240,10 +315,11 @@ export async function buildPlan(
|
|
|
240
315
|
warnings,
|
|
241
316
|
summary: prompt ?? `${normalizedIntent} plan with ${steps.length} step(s)`,
|
|
242
317
|
estimatedTools: steps.reduce((acc, s) => acc + s.tools.length, 0),
|
|
318
|
+
...(recommendations.length > 0 ? { recommendations } : {}),
|
|
243
319
|
context: {
|
|
244
320
|
intent: normalizedIntent,
|
|
245
321
|
probes,
|
|
246
|
-
entities
|
|
322
|
+
entities,
|
|
247
323
|
projectPath,
|
|
248
324
|
},
|
|
249
325
|
};
|
package/src/flows/probes.ts
CHANGED
|
@@ -9,19 +9,20 @@ import type { AgentRuntime } from '../runtime/types.js';
|
|
|
9
9
|
import type { ProbeResults } from './types.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Run all
|
|
12
|
+
* Run all capability probes in parallel and return results.
|
|
13
13
|
*/
|
|
14
14
|
export async function runProbes(runtime: AgentRuntime, projectPath: string): Promise<ProbeResults> {
|
|
15
|
-
const [vault, brain, designSystem, sessionStore, projectRules, active] = await Promise.all([
|
|
15
|
+
const [vault, brain, designSystem, sessionStore, projectRules, active, test] = await Promise.all([
|
|
16
16
|
probeVault(runtime),
|
|
17
17
|
probeBrain(runtime),
|
|
18
18
|
probeDesignSystem(runtime),
|
|
19
19
|
probeSessionStore(),
|
|
20
20
|
probeProjectRules(projectPath),
|
|
21
21
|
probeActive(),
|
|
22
|
+
probeTestRunner(projectPath),
|
|
22
23
|
]);
|
|
23
24
|
|
|
24
|
-
return { vault, brain, designSystem, sessionStore, projectRules, active };
|
|
25
|
+
return { vault, brain, designSystem, sessionStore, projectRules, active, test };
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
async function probeVault(runtime: AgentRuntime): Promise<boolean> {
|
|
@@ -68,3 +69,16 @@ async function probeActive(): Promise<boolean> {
|
|
|
68
69
|
// Always true when the engine is running
|
|
69
70
|
return true;
|
|
70
71
|
}
|
|
72
|
+
|
|
73
|
+
async function probeTestRunner(projectPath: string): Promise<boolean> {
|
|
74
|
+
try {
|
|
75
|
+
return (
|
|
76
|
+
existsSync(join(projectPath, 'vitest.config.ts')) ||
|
|
77
|
+
existsSync(join(projectPath, 'vitest.config.js')) ||
|
|
78
|
+
existsSync(join(projectPath, 'jest.config.ts')) ||
|
|
79
|
+
existsSync(join(projectPath, 'jest.config.js'))
|
|
80
|
+
);
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|