@soleri/core 9.2.0 → 9.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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.map +1 -1
- package/dist/engine/module-manifest.js +21 -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/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-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-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 +32 -10
- 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 +5 -4
- package/src/engine/module-manifest.ts +21 -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/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-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 +1 -3
- 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.ts +54 -27
- 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-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
|
@@ -73,9 +73,7 @@ describe('runAgentLoop', () => {
|
|
|
73
73
|
|
|
74
74
|
describe('basic flow', () => {
|
|
75
75
|
test('returns text on end_turn', async () => {
|
|
76
|
-
fetchMock.mockResolvedValueOnce(
|
|
77
|
-
anthropicResponse([{ type: 'text', text: 'Hello!' }]),
|
|
78
|
-
);
|
|
76
|
+
fetchMock.mockResolvedValueOnce(anthropicResponse([{ type: 'text', text: 'Hello!' }]));
|
|
79
77
|
|
|
80
78
|
const result = await runAgentLoop(makeMessages('hi'), makeConfig());
|
|
81
79
|
expect(result.text).toBe('Hello!');
|
|
@@ -87,9 +85,7 @@ describe('runAgentLoop', () => {
|
|
|
87
85
|
});
|
|
88
86
|
|
|
89
87
|
test('accumulates newMessages', async () => {
|
|
90
|
-
fetchMock.mockResolvedValueOnce(
|
|
91
|
-
anthropicResponse([{ type: 'text', text: 'reply' }]),
|
|
92
|
-
);
|
|
88
|
+
fetchMock.mockResolvedValueOnce(anthropicResponse([{ type: 'text', text: 'reply' }]));
|
|
93
89
|
|
|
94
90
|
const result = await runAgentLoop(makeMessages('q'), makeConfig());
|
|
95
91
|
expect(result.newMessages.length).toBeGreaterThanOrEqual(1);
|
|
@@ -111,20 +107,24 @@ describe('runAgentLoop', () => {
|
|
|
111
107
|
),
|
|
112
108
|
);
|
|
113
109
|
// Second call: LLM returns final text
|
|
114
|
-
fetchMock.mockResolvedValueOnce(
|
|
115
|
-
anthropicResponse([{ type: 'text', text: 'Found it!' }]),
|
|
116
|
-
);
|
|
110
|
+
fetchMock.mockResolvedValueOnce(anthropicResponse([{ type: 'text', text: 'Found it!' }]));
|
|
117
111
|
|
|
118
112
|
const executorResults: string[] = [];
|
|
119
|
-
const executor = async (
|
|
113
|
+
const executor = async (
|
|
114
|
+
name: string,
|
|
115
|
+
_input: Record<string, unknown>,
|
|
116
|
+
): Promise<ToolResult> => {
|
|
120
117
|
executorResults.push(name);
|
|
121
118
|
return { output: `result for ${name}`, isError: false };
|
|
122
119
|
};
|
|
123
120
|
|
|
124
|
-
const result = await runAgentLoop(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
const result = await runAgentLoop(
|
|
122
|
+
makeMessages('search'),
|
|
123
|
+
makeConfig({
|
|
124
|
+
tools: [{ name: 'search', description: 'Search', inputSchema: { type: 'object' } }],
|
|
125
|
+
executor,
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
128
|
|
|
129
129
|
expect(result.iterations).toBe(2);
|
|
130
130
|
expect(result.toolCalls).toBe(1);
|
|
@@ -139,16 +139,16 @@ describe('runAgentLoop', () => {
|
|
|
139
139
|
'tool_use',
|
|
140
140
|
),
|
|
141
141
|
);
|
|
142
|
-
fetchMock.mockResolvedValueOnce(
|
|
143
|
-
anthropicResponse([{ type: 'text', text: 'Done' }]),
|
|
144
|
-
);
|
|
142
|
+
fetchMock.mockResolvedValueOnce(anthropicResponse([{ type: 'text', text: 'Done' }]));
|
|
145
143
|
|
|
146
144
|
const errors: string[] = [];
|
|
147
145
|
const result = await runAgentLoop(
|
|
148
146
|
makeMessages('go'),
|
|
149
147
|
makeConfig({
|
|
150
148
|
tools: [{ name: 'broken', description: 'Broken', inputSchema: { type: 'object' } }],
|
|
151
|
-
executor: async () => {
|
|
149
|
+
executor: async () => {
|
|
150
|
+
throw new Error('tool failed');
|
|
151
|
+
},
|
|
152
152
|
}),
|
|
153
153
|
{
|
|
154
154
|
onError: (err, ctx) => errors.push(`${ctx}: ${err.message}`),
|
|
@@ -254,9 +254,7 @@ describe('runAgentLoop', () => {
|
|
|
254
254
|
|
|
255
255
|
describe('callbacks', () => {
|
|
256
256
|
test('fires onIteration for each loop', async () => {
|
|
257
|
-
fetchMock.mockResolvedValueOnce(
|
|
258
|
-
anthropicResponse([{ type: 'text', text: 'ok' }]),
|
|
259
|
-
);
|
|
257
|
+
fetchMock.mockResolvedValueOnce(anthropicResponse([{ type: 'text', text: 'ok' }]));
|
|
260
258
|
|
|
261
259
|
const iterations: number[] = [];
|
|
262
260
|
await runAgentLoop(makeMessages('hi'), makeConfig(), {
|
|
@@ -273,9 +271,7 @@ describe('runAgentLoop', () => {
|
|
|
273
271
|
'tool_use',
|
|
274
272
|
),
|
|
275
273
|
);
|
|
276
|
-
fetchMock.mockResolvedValueOnce(
|
|
277
|
-
anthropicResponse([{ type: 'text', text: 'done' }]),
|
|
278
|
-
);
|
|
274
|
+
fetchMock.mockResolvedValueOnce(anthropicResponse([{ type: 'text', text: 'done' }]));
|
|
279
275
|
|
|
280
276
|
const toolUses: string[] = [];
|
|
281
277
|
const toolResults: string[] = [];
|
|
@@ -304,9 +300,9 @@ describe('runAgentLoop', () => {
|
|
|
304
300
|
test('throws on non-retryable API error', async () => {
|
|
305
301
|
fetchMock.mockResolvedValueOnce(anthropicError(400, 'bad request'));
|
|
306
302
|
|
|
307
|
-
await expect(
|
|
308
|
-
|
|
309
|
-
)
|
|
303
|
+
await expect(runAgentLoop(makeMessages('hi'), makeConfig())).rejects.toThrow(
|
|
304
|
+
'Anthropic API error 400',
|
|
305
|
+
);
|
|
310
306
|
});
|
|
311
307
|
|
|
312
308
|
test('fires onError callback before throwing', async () => {
|
|
@@ -326,9 +322,7 @@ describe('runAgentLoop', () => {
|
|
|
326
322
|
|
|
327
323
|
describe('message sanitization', () => {
|
|
328
324
|
test('strips leading assistant messages', async () => {
|
|
329
|
-
fetchMock.mockResolvedValueOnce(
|
|
330
|
-
anthropicResponse([{ type: 'text', text: 'ok' }]),
|
|
331
|
-
);
|
|
325
|
+
fetchMock.mockResolvedValueOnce(anthropicResponse([{ type: 'text', text: 'ok' }]));
|
|
332
326
|
|
|
333
327
|
const messages: ChatMessage[] = [
|
|
334
328
|
{ role: 'assistant', content: 'orphan', timestamp: Date.now() },
|
|
@@ -344,9 +338,7 @@ describe('runAgentLoop', () => {
|
|
|
344
338
|
});
|
|
345
339
|
|
|
346
340
|
test('skips standalone tool messages', async () => {
|
|
347
|
-
fetchMock.mockResolvedValueOnce(
|
|
348
|
-
anthropicResponse([{ type: 'text', text: 'ok' }]),
|
|
349
|
-
);
|
|
341
|
+
fetchMock.mockResolvedValueOnce(anthropicResponse([{ type: 'text', text: 'ok' }]));
|
|
350
342
|
|
|
351
343
|
const messages: ChatMessage[] = [
|
|
352
344
|
{ role: 'user', content: 'hello', timestamp: Date.now() },
|
|
@@ -365,18 +357,16 @@ describe('runAgentLoop', () => {
|
|
|
365
357
|
describe('usage accumulation', () => {
|
|
366
358
|
test('sums usage across iterations', async () => {
|
|
367
359
|
fetchMock.mockResolvedValueOnce(
|
|
368
|
-
anthropicResponse(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
),
|
|
360
|
+
anthropicResponse([{ type: 'tool_use', id: 'tu_1', name: 'x', input: {} }], 'tool_use', {
|
|
361
|
+
input_tokens: 100,
|
|
362
|
+
output_tokens: 50,
|
|
363
|
+
}),
|
|
373
364
|
);
|
|
374
365
|
fetchMock.mockResolvedValueOnce(
|
|
375
|
-
anthropicResponse(
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
),
|
|
366
|
+
anthropicResponse([{ type: 'text', text: 'done' }], 'end_turn', {
|
|
367
|
+
input_tokens: 150,
|
|
368
|
+
output_tokens: 30,
|
|
369
|
+
}),
|
|
380
370
|
);
|
|
381
371
|
|
|
382
372
|
const result = await runAgentLoop(
|
|
@@ -108,7 +108,9 @@ describe('McpToolBridge', () => {
|
|
|
108
108
|
test('catches handler errors', async () => {
|
|
109
109
|
bridge.register({
|
|
110
110
|
...makeTool('broken'),
|
|
111
|
-
handler: async () => {
|
|
111
|
+
handler: async () => {
|
|
112
|
+
throw new Error('kaboom');
|
|
113
|
+
},
|
|
112
114
|
});
|
|
113
115
|
const result = await bridge.execute('broken', {});
|
|
114
116
|
expect(result.isError).toBe(true);
|
|
@@ -119,7 +121,10 @@ describe('McpToolBridge', () => {
|
|
|
119
121
|
let received: Record<string, unknown> = {};
|
|
120
122
|
bridge.register({
|
|
121
123
|
...makeTool('capture'),
|
|
122
|
-
handler: async (input) => {
|
|
124
|
+
handler: async (input) => {
|
|
125
|
+
received = input;
|
|
126
|
+
return 'ok';
|
|
127
|
+
},
|
|
123
128
|
});
|
|
124
129
|
await bridge.execute('capture', { foo: 'bar' });
|
|
125
130
|
expect(received).toEqual({ foo: 'bar' });
|
|
@@ -2,18 +2,8 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { mkdtempSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
removeCLAUDEmd,
|
|
8
|
-
hasCLAUDEmdBlock,
|
|
9
|
-
extractUserZone,
|
|
10
|
-
} from './inject.js';
|
|
11
|
-
import {
|
|
12
|
-
OPEN_MARKER,
|
|
13
|
-
CLOSE_MARKER,
|
|
14
|
-
USER_ZONE_OPEN,
|
|
15
|
-
USER_ZONE_CLOSE,
|
|
16
|
-
} from './compose.js';
|
|
5
|
+
import { injectCLAUDEmd, removeCLAUDEmd, hasCLAUDEmdBlock, extractUserZone } from './inject.js';
|
|
6
|
+
import { OPEN_MARKER, CLOSE_MARKER, USER_ZONE_OPEN, USER_ZONE_CLOSE } from './compose.js';
|
|
17
7
|
|
|
18
8
|
function block(content: string): string {
|
|
19
9
|
return `${OPEN_MARKER}\n${content}\n${CLOSE_MARKER}`;
|
|
@@ -31,7 +31,13 @@ function createMockIntelligence(overrides?: Partial<BrainIntelligence>): BrainIn
|
|
|
31
31
|
} as unknown as BrainIntelligence;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function makeVaultResult(
|
|
34
|
+
function makeVaultResult(
|
|
35
|
+
id: string,
|
|
36
|
+
title: string,
|
|
37
|
+
score: number,
|
|
38
|
+
domain: string,
|
|
39
|
+
tags: string[] = [],
|
|
40
|
+
) {
|
|
35
41
|
return {
|
|
36
42
|
entry: { id, title, domain, tags, type: 'pattern', severity: 'suggestion', description: '' },
|
|
37
43
|
score,
|
|
@@ -120,7 +126,9 @@ describe('ContextEngine', () => {
|
|
|
120
126
|
});
|
|
121
127
|
|
|
122
128
|
it('extracts kebab-case patterns but filters stop words', () => {
|
|
123
|
-
const result = engine.extractEntities(
|
|
129
|
+
const result = engine.extractEntities(
|
|
130
|
+
'Use the token-migration pattern, not built-in or real-time',
|
|
131
|
+
);
|
|
124
132
|
const patterns = result.byType.pattern ?? [];
|
|
125
133
|
expect(patterns.some((p) => p.value === 'token-migration')).toBe(true);
|
|
126
134
|
expect(patterns.some((p) => p.value === 'real-time')).toBe(false);
|
|
@@ -170,9 +178,11 @@ describe('ContextEngine', () => {
|
|
|
170
178
|
|
|
171
179
|
describe('retrieveKnowledge', () => {
|
|
172
180
|
it('returns vault results with enriched scores', async () => {
|
|
173
|
-
const mockSearch = vi
|
|
174
|
-
|
|
175
|
-
|
|
181
|
+
const mockSearch = vi
|
|
182
|
+
.fn()
|
|
183
|
+
.mockReturnValue([
|
|
184
|
+
makeVaultResult('v1', 'Button pattern', 10, 'design', ['button', 'component']),
|
|
185
|
+
]);
|
|
176
186
|
vault = createMockVault({ search: mockSearch } as unknown as Partial<Vault>);
|
|
177
187
|
engine = new ContextEngine(vault, brain, intelligence);
|
|
178
188
|
|
|
@@ -186,10 +196,12 @@ describe('ContextEngine', () => {
|
|
|
186
196
|
});
|
|
187
197
|
|
|
188
198
|
it('returns brain recommendations normalized to 0-1', async () => {
|
|
189
|
-
const mockRecommend = vi
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
intelligence = createMockIntelligence({
|
|
199
|
+
const mockRecommend = vi
|
|
200
|
+
.fn()
|
|
201
|
+
.mockReturnValue([makeBrainRecommendation('semantic-tokens', 'design', 80)]);
|
|
202
|
+
intelligence = createMockIntelligence({
|
|
203
|
+
recommend: mockRecommend,
|
|
204
|
+
} as unknown as Partial<BrainIntelligence>);
|
|
193
205
|
engine = new ContextEngine(vault, brain, intelligence);
|
|
194
206
|
|
|
195
207
|
const result = await engine.retrieveKnowledge('design tokens');
|
|
@@ -200,14 +212,16 @@ describe('ContextEngine', () => {
|
|
|
200
212
|
});
|
|
201
213
|
|
|
202
214
|
it('combines vault and brain results sorted by score descending', async () => {
|
|
203
|
-
const mockSearch = vi
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const mockRecommend = vi
|
|
207
|
-
|
|
208
|
-
|
|
215
|
+
const mockSearch = vi
|
|
216
|
+
.fn()
|
|
217
|
+
.mockReturnValue([makeVaultResult('v1', 'Low relevance', 2, 'general')]);
|
|
218
|
+
const mockRecommend = vi
|
|
219
|
+
.fn()
|
|
220
|
+
.mockReturnValue([makeBrainRecommendation('high-relevance', 'design', 95)]);
|
|
209
221
|
vault = createMockVault({ search: mockSearch } as unknown as Partial<Vault>);
|
|
210
|
-
intelligence = createMockIntelligence({
|
|
222
|
+
intelligence = createMockIntelligence({
|
|
223
|
+
recommend: mockRecommend,
|
|
224
|
+
} as unknown as Partial<BrainIntelligence>);
|
|
211
225
|
engine = new ContextEngine(vault, brain, intelligence);
|
|
212
226
|
|
|
213
227
|
const result = await engine.retrieveKnowledge('test query');
|
|
@@ -216,9 +230,9 @@ describe('ContextEngine', () => {
|
|
|
216
230
|
});
|
|
217
231
|
|
|
218
232
|
it('filters items below minScoreThreshold', async () => {
|
|
219
|
-
const mockSearch = vi
|
|
220
|
-
|
|
221
|
-
|
|
233
|
+
const mockSearch = vi
|
|
234
|
+
.fn()
|
|
235
|
+
.mockReturnValue([makeVaultResult('v1', 'Irrelevant entry', 1, 'misc')]);
|
|
222
236
|
vault = createMockVault({ search: mockSearch } as unknown as Partial<Vault>);
|
|
223
237
|
engine = new ContextEngine(vault, brain, intelligence, { minScoreThreshold: 0.99 });
|
|
224
238
|
|
|
@@ -241,12 +255,17 @@ describe('ContextEngine', () => {
|
|
|
241
255
|
engine = new ContextEngine(vault, brain, intelligence);
|
|
242
256
|
|
|
243
257
|
await engine.retrieveKnowledge('test', 'security');
|
|
244
|
-
expect(mockSearch).toHaveBeenCalledWith(
|
|
258
|
+
expect(mockSearch).toHaveBeenCalledWith(
|
|
259
|
+
'test',
|
|
260
|
+
expect.objectContaining({ domain: 'security' }),
|
|
261
|
+
);
|
|
245
262
|
});
|
|
246
263
|
|
|
247
264
|
it('passes domain to brain recommendations', async () => {
|
|
248
265
|
const mockRecommend = vi.fn().mockReturnValue([]);
|
|
249
|
-
intelligence = createMockIntelligence({
|
|
266
|
+
intelligence = createMockIntelligence({
|
|
267
|
+
recommend: mockRecommend,
|
|
268
|
+
} as unknown as Partial<BrainIntelligence>);
|
|
250
269
|
engine = new ContextEngine(vault, brain, intelligence);
|
|
251
270
|
|
|
252
271
|
await engine.retrieveKnowledge('test', 'design');
|
|
@@ -254,7 +273,9 @@ describe('ContextEngine', () => {
|
|
|
254
273
|
});
|
|
255
274
|
|
|
256
275
|
it('gracefully handles vault search throwing', async () => {
|
|
257
|
-
const mockSearch = vi.fn().mockImplementation(() => {
|
|
276
|
+
const mockSearch = vi.fn().mockImplementation(() => {
|
|
277
|
+
throw new Error('DB error');
|
|
278
|
+
});
|
|
258
279
|
vault = createMockVault({ search: mockSearch } as unknown as Partial<Vault>);
|
|
259
280
|
engine = new ContextEngine(vault, brain, intelligence);
|
|
260
281
|
|
|
@@ -264,8 +285,12 @@ describe('ContextEngine', () => {
|
|
|
264
285
|
});
|
|
265
286
|
|
|
266
287
|
it('gracefully handles brain recommend throwing', async () => {
|
|
267
|
-
const mockRecommend = vi.fn().mockImplementation(() => {
|
|
268
|
-
|
|
288
|
+
const mockRecommend = vi.fn().mockImplementation(() => {
|
|
289
|
+
throw new Error('Brain error');
|
|
290
|
+
});
|
|
291
|
+
intelligence = createMockIntelligence({
|
|
292
|
+
recommend: mockRecommend,
|
|
293
|
+
} as unknown as Partial<BrainIntelligence>);
|
|
269
294
|
engine = new ContextEngine(vault, brain, intelligence);
|
|
270
295
|
|
|
271
296
|
const result = await engine.retrieveKnowledge('test');
|
|
@@ -283,7 +308,9 @@ describe('ContextEngine', () => {
|
|
|
283
308
|
|
|
284
309
|
it('respects brainRecommendLimit config', async () => {
|
|
285
310
|
const mockRecommend = vi.fn().mockReturnValue([]);
|
|
286
|
-
intelligence = createMockIntelligence({
|
|
311
|
+
intelligence = createMockIntelligence({
|
|
312
|
+
recommend: mockRecommend,
|
|
313
|
+
} as unknown as Partial<BrainIntelligence>);
|
|
287
314
|
engine = new ContextEngine(vault, brain, intelligence, { brainRecommendLimit: 2 });
|
|
288
315
|
|
|
289
316
|
await engine.retrieveKnowledge('test');
|
|
@@ -312,7 +339,9 @@ describe('ContextEngine', () => {
|
|
|
312
339
|
});
|
|
313
340
|
|
|
314
341
|
it('confidence is between 0 and 1', async () => {
|
|
315
|
-
const result = await engine.analyze(
|
|
342
|
+
const result = await engine.analyze(
|
|
343
|
+
'Create React TypeScript component in src/app.tsx for security',
|
|
344
|
+
);
|
|
316
345
|
expect(result.confidence).toBeGreaterThanOrEqual(0);
|
|
317
346
|
expect(result.confidence).toBeLessThanOrEqual(1);
|
|
318
347
|
});
|
|
@@ -324,13 +353,17 @@ describe('ContextEngine', () => {
|
|
|
324
353
|
});
|
|
325
354
|
|
|
326
355
|
it('assigns higher confidence when entities and knowledge are present', async () => {
|
|
327
|
-
const mockSearch = vi
|
|
328
|
-
|
|
329
|
-
|
|
356
|
+
const mockSearch = vi
|
|
357
|
+
.fn()
|
|
358
|
+
.mockReturnValue([
|
|
359
|
+
makeVaultResult('v1', 'Unit test best practices', 10, 'testing', ['testing']),
|
|
360
|
+
]);
|
|
330
361
|
vault = createMockVault({ search: mockSearch } as unknown as Partial<Vault>);
|
|
331
362
|
engine = new ContextEngine(vault, brain, intelligence);
|
|
332
363
|
|
|
333
|
-
const result = await engine.analyze(
|
|
364
|
+
const result = await engine.analyze(
|
|
365
|
+
'Create unit tests for the search() function in src/vault.ts with TypeScript',
|
|
366
|
+
);
|
|
334
367
|
expect(result.confidence).toBeGreaterThan(0.4);
|
|
335
368
|
});
|
|
336
369
|
|
|
@@ -341,9 +374,9 @@ describe('ContextEngine', () => {
|
|
|
341
374
|
});
|
|
342
375
|
|
|
343
376
|
it('detects domains from knowledge items', async () => {
|
|
344
|
-
const mockSearch = vi
|
|
345
|
-
|
|
346
|
-
|
|
377
|
+
const mockSearch = vi
|
|
378
|
+
.fn()
|
|
379
|
+
.mockReturnValue([makeVaultResult('v1', 'Perf guide', 10, 'performance', ['performance'])]);
|
|
347
380
|
vault = createMockVault({ search: mockSearch } as unknown as Partial<Vault>);
|
|
348
381
|
engine = new ContextEngine(vault, brain, intelligence);
|
|
349
382
|
|
|
@@ -352,9 +385,9 @@ describe('ContextEngine', () => {
|
|
|
352
385
|
});
|
|
353
386
|
|
|
354
387
|
it('deduplicates domains from entities and knowledge', async () => {
|
|
355
|
-
const mockSearch = vi
|
|
356
|
-
|
|
357
|
-
|
|
388
|
+
const mockSearch = vi
|
|
389
|
+
.fn()
|
|
390
|
+
.mockReturnValue([makeVaultResult('v1', 'A11y rules', 10, 'accessibility', ['a11y'])]);
|
|
358
391
|
vault = createMockVault({ search: mockSearch } as unknown as Partial<Vault>);
|
|
359
392
|
engine = new ContextEngine(vault, brain, intelligence);
|
|
360
393
|
|
|
@@ -393,27 +426,35 @@ describe('ContextEngine', () => {
|
|
|
393
426
|
|
|
394
427
|
it('more entities increase confidence up to a cap', async () => {
|
|
395
428
|
const few = await engine.analyze('fix it');
|
|
396
|
-
const many = await engine.analyze(
|
|
429
|
+
const many = await engine.analyze(
|
|
430
|
+
'fix the React TypeScript component in src/app.tsx for accessibility and deploy',
|
|
431
|
+
);
|
|
397
432
|
expect(many.confidence).toBeGreaterThan(few.confidence);
|
|
398
433
|
});
|
|
399
434
|
|
|
400
435
|
it('source diversity bonus applies when multiple sources return results', async () => {
|
|
401
|
-
const mockSearch = vi
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const mockRecommend = vi
|
|
405
|
-
|
|
406
|
-
|
|
436
|
+
const mockSearch = vi
|
|
437
|
+
.fn()
|
|
438
|
+
.mockReturnValue([makeVaultResult('v1', 'Pattern one', 10, 'design', ['design'])]);
|
|
439
|
+
const mockRecommend = vi
|
|
440
|
+
.fn()
|
|
441
|
+
.mockReturnValue([makeBrainRecommendation('some-pattern', 'design', 70)]);
|
|
407
442
|
vault = createMockVault({ search: mockSearch } as unknown as Partial<Vault>);
|
|
408
|
-
intelligence = createMockIntelligence({
|
|
443
|
+
intelligence = createMockIntelligence({
|
|
444
|
+
recommend: mockRecommend,
|
|
445
|
+
} as unknown as Partial<BrainIntelligence>);
|
|
409
446
|
const multiEngine = new ContextEngine(vault, brain, intelligence);
|
|
410
447
|
|
|
411
|
-
const vaultOnlySearch = vi
|
|
412
|
-
|
|
413
|
-
|
|
448
|
+
const vaultOnlySearch = vi
|
|
449
|
+
.fn()
|
|
450
|
+
.mockReturnValue([makeVaultResult('v1', 'Pattern one', 10, 'design', ['design'])]);
|
|
414
451
|
const emptyRecommend = vi.fn().mockReturnValue([]);
|
|
415
|
-
const vaultOnlyVault = createMockVault({
|
|
416
|
-
|
|
452
|
+
const vaultOnlyVault = createMockVault({
|
|
453
|
+
search: vaultOnlySearch,
|
|
454
|
+
} as unknown as Partial<Vault>);
|
|
455
|
+
const emptyIntel = createMockIntelligence({
|
|
456
|
+
recommend: emptyRecommend,
|
|
457
|
+
} as unknown as Partial<BrainIntelligence>);
|
|
417
458
|
const singleEngine = new ContextEngine(vaultOnlyVault, brain, emptyIntel);
|
|
418
459
|
|
|
419
460
|
const multi = await multiEngine.analyze('design patterns');
|
|
@@ -428,7 +469,9 @@ describe('ContextEngine', () => {
|
|
|
428
469
|
expect(low.confidence).toBeLessThan(0.45);
|
|
429
470
|
|
|
430
471
|
// Construct a scenario with enough signals for medium (action + 6 entities = 0.2 + 0.4 cap = 0.6)
|
|
431
|
-
const medium = await engine.analyze(
|
|
472
|
+
const medium = await engine.analyze(
|
|
473
|
+
'create a React TypeScript component in src/app.tsx for security',
|
|
474
|
+
);
|
|
432
475
|
expect(medium.confidence).toBeGreaterThanOrEqual(0.45);
|
|
433
476
|
expect(['medium', 'high']).toContain(medium.confidenceLevel);
|
|
434
477
|
});
|
|
@@ -450,7 +493,9 @@ describe('ContextEngine', () => {
|
|
|
450
493
|
const mockSearch = vi.fn().mockReturnValue([]);
|
|
451
494
|
const mockRecommend = vi.fn().mockReturnValue([]);
|
|
452
495
|
vault = createMockVault({ search: mockSearch } as unknown as Partial<Vault>);
|
|
453
|
-
intelligence = createMockIntelligence({
|
|
496
|
+
intelligence = createMockIntelligence({
|
|
497
|
+
recommend: mockRecommend,
|
|
498
|
+
} as unknown as Partial<BrainIntelligence>);
|
|
454
499
|
engine = new ContextEngine(vault, brain, intelligence, { vaultSearchLimit: 5 });
|
|
455
500
|
|
|
456
501
|
await engine.retrieveKnowledge('test');
|
|
@@ -230,9 +230,9 @@ describe('IntentRouter', () => {
|
|
|
230
230
|
});
|
|
231
231
|
|
|
232
232
|
it('throws for unknown mode', () => {
|
|
233
|
-
expect(() =>
|
|
234
|
-
|
|
235
|
-
)
|
|
233
|
+
expect(() => router.updateModeRules('NOPE-MODE' as OperationalMode, ['x'])).toThrow(
|
|
234
|
+
/Unknown mode/,
|
|
235
|
+
);
|
|
236
236
|
});
|
|
237
237
|
});
|
|
238
238
|
|
|
@@ -32,12 +32,14 @@ describe('classifyEntry', () => {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
it('classifies entry with valid LLM response', async () => {
|
|
35
|
-
const llm = mockLLM(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
const llm = mockLLM(
|
|
36
|
+
JSON.stringify({
|
|
37
|
+
domain: 'security',
|
|
38
|
+
severity: 'critical',
|
|
39
|
+
tags: ['auth', 'jwt', 'security'],
|
|
40
|
+
confidence: 0.9,
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
41
43
|
|
|
42
44
|
const result = await classifyEntry(makeEntry({ title: 'Use JWT securely' }), llm);
|
|
43
45
|
expect(result.classified).toBe(true);
|
|
@@ -75,7 +77,9 @@ describe('classifyEntry', () => {
|
|
|
75
77
|
});
|
|
76
78
|
|
|
77
79
|
it('passes entry fields to LLM prompt', async () => {
|
|
78
|
-
const llm = mockLLM(
|
|
80
|
+
const llm = mockLLM(
|
|
81
|
+
JSON.stringify({ domain: 'test', severity: 'warning', tags: [], confidence: 0.5 }),
|
|
82
|
+
);
|
|
79
83
|
const entry = makeEntry({
|
|
80
84
|
title: 'Specific Title',
|
|
81
85
|
type: 'anti-pattern',
|
|
@@ -93,7 +97,9 @@ describe('classifyEntry', () => {
|
|
|
93
97
|
});
|
|
94
98
|
|
|
95
99
|
it('uses empty tags placeholder when entry has no tags', async () => {
|
|
96
|
-
const llm = mockLLM(
|
|
100
|
+
const llm = mockLLM(
|
|
101
|
+
JSON.stringify({ domain: 'test', severity: 'warning', tags: [], confidence: 0.5 }),
|
|
102
|
+
);
|
|
97
103
|
const entry = makeEntry({ tags: [] });
|
|
98
104
|
|
|
99
105
|
await classifyEntry(entry, llm);
|
|
@@ -111,8 +111,18 @@ describe('contradiction-detector', () => {
|
|
|
111
111
|
|
|
112
112
|
it('falls back to all patterns when searchFn returns empty', () => {
|
|
113
113
|
const entries = [
|
|
114
|
-
makeEntry({
|
|
115
|
-
|
|
114
|
+
makeEntry({
|
|
115
|
+
id: 'p1',
|
|
116
|
+
type: 'pattern',
|
|
117
|
+
title: 'Use inline styles',
|
|
118
|
+
description: 'Inline.',
|
|
119
|
+
}),
|
|
120
|
+
makeEntry({
|
|
121
|
+
id: 'ap1',
|
|
122
|
+
type: 'anti-pattern',
|
|
123
|
+
title: 'Avoid inline styles',
|
|
124
|
+
description: 'No inline.',
|
|
125
|
+
}),
|
|
116
126
|
];
|
|
117
127
|
|
|
118
128
|
const searchFn = vi.fn().mockReturnValue([]);
|
|
@@ -125,8 +135,18 @@ describe('contradiction-detector', () => {
|
|
|
125
135
|
|
|
126
136
|
it('falls back to all patterns when searchFn throws', () => {
|
|
127
137
|
const entries = [
|
|
128
|
-
makeEntry({
|
|
129
|
-
|
|
138
|
+
makeEntry({
|
|
139
|
+
id: 'p1',
|
|
140
|
+
type: 'pattern',
|
|
141
|
+
title: 'Use inline styles',
|
|
142
|
+
description: 'Inline.',
|
|
143
|
+
}),
|
|
144
|
+
makeEntry({
|
|
145
|
+
id: 'ap1',
|
|
146
|
+
type: 'anti-pattern',
|
|
147
|
+
title: 'Avoid inline styles',
|
|
148
|
+
description: 'No inline.',
|
|
149
|
+
}),
|
|
130
150
|
];
|
|
131
151
|
|
|
132
152
|
const searchFn = vi.fn().mockImplementation(() => {
|
|
@@ -170,7 +190,12 @@ describe('contradiction-detector', () => {
|
|
|
170
190
|
it('ignores entries that are neither pattern nor anti-pattern', () => {
|
|
171
191
|
const entries = [
|
|
172
192
|
makeEntry({ id: 'r1', type: 'rule', title: 'Use inline styles', description: 'Inline.' }),
|
|
173
|
-
makeEntry({
|
|
193
|
+
makeEntry({
|
|
194
|
+
id: 'ap1',
|
|
195
|
+
type: 'anti-pattern',
|
|
196
|
+
title: 'Avoid inline styles',
|
|
197
|
+
description: 'No inline.',
|
|
198
|
+
}),
|
|
174
199
|
];
|
|
175
200
|
// Rules should not be compared against anti-patterns
|
|
176
201
|
const results = findContradictions(entries, 0.1);
|