@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
|
@@ -22,7 +22,9 @@ function mockLLM(response: string): LLMClient {
|
|
|
22
22
|
|
|
23
23
|
function throwingLLM(error: Error): LLMClient {
|
|
24
24
|
return {
|
|
25
|
-
complete: async () => {
|
|
25
|
+
complete: async () => {
|
|
26
|
+
throw error;
|
|
27
|
+
},
|
|
26
28
|
isAvailable: () => ({ openai: true, anthropic: false }),
|
|
27
29
|
getRoutes: () => [],
|
|
28
30
|
} as unknown as LLMClient;
|
|
@@ -34,15 +36,17 @@ function throwingLLM(error: Error): LLMClient {
|
|
|
34
36
|
|
|
35
37
|
describe('classifyChunk', () => {
|
|
36
38
|
it('parses valid JSON array response into ClassifiedItems', async () => {
|
|
37
|
-
const llm = mockLLM(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
const llm = mockLLM(
|
|
40
|
+
JSON.stringify([
|
|
41
|
+
{
|
|
42
|
+
type: 'pattern',
|
|
43
|
+
title: 'Test Pattern',
|
|
44
|
+
description: 'A useful design pattern.',
|
|
45
|
+
tags: ['design', 'architecture'],
|
|
46
|
+
severity: 'suggestion',
|
|
47
|
+
},
|
|
48
|
+
]),
|
|
49
|
+
);
|
|
46
50
|
|
|
47
51
|
const result = await classifyChunk(llm, 'some text', 'page 1');
|
|
48
52
|
|
|
@@ -56,7 +60,9 @@ describe('classifyChunk', () => {
|
|
|
56
60
|
});
|
|
57
61
|
|
|
58
62
|
it('handles markdown fenced JSON responses', async () => {
|
|
59
|
-
const llm = mockLLM(
|
|
63
|
+
const llm = mockLLM(
|
|
64
|
+
'```json\n[{"type":"pattern","title":"Fenced","description":"Inside fences.","tags":["test"],"severity":"warning"}]\n```',
|
|
65
|
+
);
|
|
60
66
|
|
|
61
67
|
const result = await classifyChunk(llm, 'text', 'cite');
|
|
62
68
|
|
|
@@ -89,10 +95,24 @@ describe('classifyChunk', () => {
|
|
|
89
95
|
});
|
|
90
96
|
|
|
91
97
|
it('filters out items with invalid type', async () => {
|
|
92
|
-
const llm = mockLLM(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
const llm = mockLLM(
|
|
99
|
+
JSON.stringify([
|
|
100
|
+
{
|
|
101
|
+
type: 'invalid-type',
|
|
102
|
+
title: 'Bad',
|
|
103
|
+
description: 'Bad type',
|
|
104
|
+
tags: [],
|
|
105
|
+
severity: 'suggestion',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
type: 'pattern',
|
|
109
|
+
title: 'Good',
|
|
110
|
+
description: 'Good item.',
|
|
111
|
+
tags: [],
|
|
112
|
+
severity: 'suggestion',
|
|
113
|
+
},
|
|
114
|
+
]),
|
|
115
|
+
);
|
|
96
116
|
|
|
97
117
|
const result = await classifyChunk(llm, 'text', 'cite');
|
|
98
118
|
|
|
@@ -101,11 +121,19 @@ describe('classifyChunk', () => {
|
|
|
101
121
|
});
|
|
102
122
|
|
|
103
123
|
it('filters out items missing required fields (title, description)', async () => {
|
|
104
|
-
const llm = mockLLM(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
124
|
+
const llm = mockLLM(
|
|
125
|
+
JSON.stringify([
|
|
126
|
+
{ type: 'pattern', title: '', description: 'No title', tags: [], severity: 'suggestion' },
|
|
127
|
+
{ type: 'pattern', title: 'No desc', description: '', tags: [], severity: 'suggestion' },
|
|
128
|
+
{
|
|
129
|
+
type: 'pattern',
|
|
130
|
+
title: 'Valid',
|
|
131
|
+
description: 'Valid item.',
|
|
132
|
+
tags: [],
|
|
133
|
+
severity: 'suggestion',
|
|
134
|
+
},
|
|
135
|
+
]),
|
|
136
|
+
);
|
|
109
137
|
|
|
110
138
|
const result = await classifyChunk(llm, 'text', 'cite');
|
|
111
139
|
|
|
@@ -115,9 +143,17 @@ describe('classifyChunk', () => {
|
|
|
115
143
|
|
|
116
144
|
it('truncates title to 80 characters', async () => {
|
|
117
145
|
const longTitle = 'A'.repeat(100);
|
|
118
|
-
const llm = mockLLM(
|
|
119
|
-
|
|
120
|
-
|
|
146
|
+
const llm = mockLLM(
|
|
147
|
+
JSON.stringify([
|
|
148
|
+
{
|
|
149
|
+
type: 'pattern',
|
|
150
|
+
title: longTitle,
|
|
151
|
+
description: 'Desc.',
|
|
152
|
+
tags: [],
|
|
153
|
+
severity: 'suggestion',
|
|
154
|
+
},
|
|
155
|
+
]),
|
|
156
|
+
);
|
|
121
157
|
|
|
122
158
|
const result = await classifyChunk(llm, 'text', 'cite');
|
|
123
159
|
|
|
@@ -126,26 +162,36 @@ describe('classifyChunk', () => {
|
|
|
126
162
|
});
|
|
127
163
|
|
|
128
164
|
it('caps tags at 5 and lowercases them', async () => {
|
|
129
|
-
const llm = mockLLM(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
165
|
+
const llm = mockLLM(
|
|
166
|
+
JSON.stringify([
|
|
167
|
+
{
|
|
168
|
+
type: 'pattern',
|
|
169
|
+
title: 'Tagged',
|
|
170
|
+
description: 'Many tags.',
|
|
171
|
+
tags: ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven'],
|
|
172
|
+
severity: 'suggestion',
|
|
173
|
+
},
|
|
174
|
+
]),
|
|
175
|
+
);
|
|
138
176
|
|
|
139
177
|
const result = await classifyChunk(llm, 'text', 'cite');
|
|
140
178
|
|
|
141
179
|
expect(result[0].tags).toHaveLength(5);
|
|
142
|
-
expect(result[0].tags.every(t => t === t.toLowerCase())).toBe(true);
|
|
180
|
+
expect(result[0].tags.every((t) => t === t.toLowerCase())).toBe(true);
|
|
143
181
|
});
|
|
144
182
|
|
|
145
183
|
it('defaults severity to suggestion for invalid values', async () => {
|
|
146
|
-
const llm = mockLLM(
|
|
147
|
-
|
|
148
|
-
|
|
184
|
+
const llm = mockLLM(
|
|
185
|
+
JSON.stringify([
|
|
186
|
+
{
|
|
187
|
+
type: 'pattern',
|
|
188
|
+
title: 'Bad Sev',
|
|
189
|
+
description: 'Unknown sev.',
|
|
190
|
+
tags: [],
|
|
191
|
+
severity: 'unknown',
|
|
192
|
+
},
|
|
193
|
+
]),
|
|
194
|
+
);
|
|
149
195
|
|
|
150
196
|
const result = await classifyChunk(llm, 'text', 'cite');
|
|
151
197
|
|
|
@@ -153,24 +199,40 @@ describe('classifyChunk', () => {
|
|
|
153
199
|
});
|
|
154
200
|
|
|
155
201
|
it('handles all valid severity values', async () => {
|
|
156
|
-
const llm = mockLLM(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
202
|
+
const llm = mockLLM(
|
|
203
|
+
JSON.stringify([
|
|
204
|
+
{
|
|
205
|
+
type: 'pattern',
|
|
206
|
+
title: 'Critical',
|
|
207
|
+
description: 'Crit.',
|
|
208
|
+
tags: [],
|
|
209
|
+
severity: 'critical',
|
|
210
|
+
},
|
|
211
|
+
{ type: 'pattern', title: 'Warning', description: 'Warn.', tags: [], severity: 'warning' },
|
|
212
|
+
{
|
|
213
|
+
type: 'pattern',
|
|
214
|
+
title: 'Suggestion',
|
|
215
|
+
description: 'Sug.',
|
|
216
|
+
tags: [],
|
|
217
|
+
severity: 'suggestion',
|
|
218
|
+
},
|
|
219
|
+
]),
|
|
220
|
+
);
|
|
161
221
|
|
|
162
222
|
const result = await classifyChunk(llm, 'text', 'cite');
|
|
163
223
|
|
|
164
|
-
expect(result.map(r => r.severity)).toEqual(['critical', 'warning', 'suggestion']);
|
|
224
|
+
expect(result.map((r) => r.severity)).toEqual(['critical', 'warning', 'suggestion']);
|
|
165
225
|
});
|
|
166
226
|
|
|
167
227
|
it('filters out non-object items in the array', async () => {
|
|
168
|
-
const llm = mockLLM(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
228
|
+
const llm = mockLLM(
|
|
229
|
+
JSON.stringify([
|
|
230
|
+
null,
|
|
231
|
+
42,
|
|
232
|
+
'string',
|
|
233
|
+
{ type: 'pattern', title: 'Valid', description: 'OK.', tags: [], severity: 'suggestion' },
|
|
234
|
+
]),
|
|
235
|
+
);
|
|
174
236
|
|
|
175
237
|
const result = await classifyChunk(llm, 'text', 'cite');
|
|
176
238
|
|
|
@@ -179,9 +241,17 @@ describe('classifyChunk', () => {
|
|
|
179
241
|
});
|
|
180
242
|
|
|
181
243
|
it('filters non-string tags from the tags array', async () => {
|
|
182
|
-
const llm = mockLLM(
|
|
183
|
-
|
|
184
|
-
|
|
244
|
+
const llm = mockLLM(
|
|
245
|
+
JSON.stringify([
|
|
246
|
+
{
|
|
247
|
+
type: 'pattern',
|
|
248
|
+
title: 'Mixed Tags',
|
|
249
|
+
description: 'OK.',
|
|
250
|
+
tags: ['valid', 42, null, 'also-valid'],
|
|
251
|
+
severity: 'suggestion',
|
|
252
|
+
},
|
|
253
|
+
]),
|
|
254
|
+
);
|
|
185
255
|
|
|
186
256
|
const result = await classifyChunk(llm, 'text', 'cite');
|
|
187
257
|
|
|
@@ -10,7 +10,7 @@ import type { ClassifiedItem } from './types.js';
|
|
|
10
10
|
function mockVault(entries: Array<{ id: string; title: string; description: string }> = []): Vault {
|
|
11
11
|
return {
|
|
12
12
|
exportAll: () => ({
|
|
13
|
-
entries: entries.map(e => ({
|
|
13
|
+
entries: entries.map((e) => ({
|
|
14
14
|
id: e.id,
|
|
15
15
|
title: e.title,
|
|
16
16
|
description: e.description,
|
|
@@ -49,17 +49,19 @@ describe('dedupItems — colocated', () => {
|
|
|
49
49
|
const results = dedupItems(items, vault);
|
|
50
50
|
|
|
51
51
|
expect(results).toHaveLength(2);
|
|
52
|
-
expect(results.every(r => !r.isDuplicate)).toBe(true);
|
|
53
|
-
expect(results.every(r => r.similarity === 0)).toBe(true);
|
|
54
|
-
expect(results.every(r => r.bestMatchId === undefined)).toBe(true);
|
|
52
|
+
expect(results.every((r) => !r.isDuplicate)).toBe(true);
|
|
53
|
+
expect(results.every((r) => r.similarity === 0)).toBe(true);
|
|
54
|
+
expect(results.every((r) => r.bestMatchId === undefined)).toBe(true);
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
it('detects exact duplicate text as duplicate', () => {
|
|
58
|
-
const vault = mockVault([
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
const vault = mockVault([
|
|
59
|
+
{
|
|
60
|
+
id: 'existing-1',
|
|
61
|
+
title: 'Singleton Pattern',
|
|
62
|
+
description: 'The singleton pattern ensures a class has only one instance.',
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
63
65
|
|
|
64
66
|
const items = [
|
|
65
67
|
makeItem('Singleton Pattern', 'The singleton pattern ensures a class has only one instance.'),
|
|
@@ -74,14 +76,19 @@ describe('dedupItems — colocated', () => {
|
|
|
74
76
|
});
|
|
75
77
|
|
|
76
78
|
it('does not flag dissimilar items as duplicates', () => {
|
|
77
|
-
const vault = mockVault([
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
const vault = mockVault([
|
|
80
|
+
{
|
|
81
|
+
id: 'existing-2',
|
|
82
|
+
title: 'Observer Pattern',
|
|
83
|
+
description: 'Observer defines one-to-many dependency for event-driven communication.',
|
|
84
|
+
},
|
|
85
|
+
]);
|
|
82
86
|
|
|
83
87
|
const items = [
|
|
84
|
-
makeItem(
|
|
88
|
+
makeItem(
|
|
89
|
+
'Circuit Breaker',
|
|
90
|
+
'A resilience pattern that prevents cascading failures in distributed systems.',
|
|
91
|
+
),
|
|
85
92
|
];
|
|
86
93
|
|
|
87
94
|
const results = dedupItems(items, vault);
|
|
@@ -103,17 +110,26 @@ describe('dedupItems — colocated', () => {
|
|
|
103
110
|
});
|
|
104
111
|
|
|
105
112
|
it('handles multiple items — some duplicate, some not', () => {
|
|
106
|
-
const vault = mockVault([
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
const vault = mockVault([
|
|
114
|
+
{
|
|
115
|
+
id: 'v1',
|
|
116
|
+
title: 'Factory Method Pattern',
|
|
117
|
+
description:
|
|
118
|
+
'Factory method pattern provides interface for creating objects in a superclass.',
|
|
119
|
+
},
|
|
120
|
+
]);
|
|
111
121
|
|
|
112
122
|
const items = [
|
|
113
123
|
// Near-duplicate
|
|
114
|
-
makeItem(
|
|
124
|
+
makeItem(
|
|
125
|
+
'Factory Method Pattern',
|
|
126
|
+
'Factory method pattern provides interface for creating objects in a superclass.',
|
|
127
|
+
),
|
|
115
128
|
// Different
|
|
116
|
-
makeItem(
|
|
129
|
+
makeItem(
|
|
130
|
+
'Adapter Pattern',
|
|
131
|
+
'Adapter pattern allows incompatible interfaces to work together via a wrapper.',
|
|
132
|
+
),
|
|
117
133
|
];
|
|
118
134
|
|
|
119
135
|
const results = dedupItems(items, vault);
|
|
@@ -20,9 +20,11 @@ vi.mock('./content-classifier.js', () => ({
|
|
|
20
20
|
}));
|
|
21
21
|
|
|
22
22
|
vi.mock('./dedup-gate.js', () => ({
|
|
23
|
-
dedupItems: vi
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
dedupItems: vi
|
|
24
|
+
.fn()
|
|
25
|
+
.mockImplementation((items: unknown[]) =>
|
|
26
|
+
items.map((item) => ({ item, isDuplicate: false, similarity: 0 })),
|
|
27
|
+
),
|
|
26
28
|
}));
|
|
27
29
|
|
|
28
30
|
vi.mock('node:fs', () => ({
|
|
@@ -13,13 +13,15 @@ import type { ClassifiedItem } from './types.js';
|
|
|
13
13
|
function mockLLM(items: ClassifiedItem[]): LLMClient {
|
|
14
14
|
return {
|
|
15
15
|
complete: async (): Promise<LLMCallResult> => ({
|
|
16
|
-
text: JSON.stringify(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
text: JSON.stringify(
|
|
17
|
+
items.map((i) => ({
|
|
18
|
+
type: i.type,
|
|
19
|
+
title: i.title,
|
|
20
|
+
description: i.description,
|
|
21
|
+
tags: i.tags,
|
|
22
|
+
severity: i.severity,
|
|
23
|
+
})),
|
|
24
|
+
),
|
|
23
25
|
model: 'mock',
|
|
24
26
|
provider: 'openai' as const,
|
|
25
27
|
durationMs: 0,
|
|
@@ -29,11 +31,13 @@ function mockLLM(items: ClassifiedItem[]): LLMClient {
|
|
|
29
31
|
} as unknown as LLMClient;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
function mockVault(
|
|
34
|
+
function mockVault(
|
|
35
|
+
existingEntries: Array<{ id: string; title: string; description: string }> = [],
|
|
36
|
+
): Vault {
|
|
33
37
|
const seeded: unknown[] = [];
|
|
34
38
|
return {
|
|
35
39
|
exportAll: () => ({
|
|
36
|
-
entries: existingEntries.map(e => ({
|
|
40
|
+
entries: existingEntries.map((e) => ({
|
|
37
41
|
id: e.id,
|
|
38
42
|
title: e.title,
|
|
39
43
|
description: e.description,
|
|
@@ -43,7 +47,9 @@ function mockVault(existingEntries: Array<{ id: string; title: string; descripti
|
|
|
43
47
|
tags: [],
|
|
44
48
|
})),
|
|
45
49
|
}),
|
|
46
|
-
seed: (entries: unknown[]) => {
|
|
50
|
+
seed: (entries: unknown[]) => {
|
|
51
|
+
seeded.push(...entries);
|
|
52
|
+
},
|
|
47
53
|
add: vi.fn(),
|
|
48
54
|
_seeded: seeded,
|
|
49
55
|
} as unknown as Vault & { _seeded: unknown[] };
|
|
@@ -67,9 +73,7 @@ function makeItem(overrides: Partial<ClassifiedItem> = {}): ClassifiedItem {
|
|
|
67
73
|
|
|
68
74
|
describe('TextIngester — ingestText', () => {
|
|
69
75
|
it('classifies, deduplicates, and stores unique items in vault', async () => {
|
|
70
|
-
const items = [
|
|
71
|
-
makeItem({ title: 'Pattern A', description: 'Unique pattern about testing.' }),
|
|
72
|
-
];
|
|
76
|
+
const items = [makeItem({ title: 'Pattern A', description: 'Unique pattern about testing.' })];
|
|
73
77
|
const llm = mockLLM(items);
|
|
74
78
|
const vault = mockVault() as Vault & { _seeded: unknown[] };
|
|
75
79
|
const ingester = new TextIngester(vault, llm);
|
|
@@ -126,13 +130,15 @@ describe('TextIngester — ingestText', () => {
|
|
|
126
130
|
complete: async (): Promise<LLMCallResult> => {
|
|
127
131
|
callCount.n++;
|
|
128
132
|
return {
|
|
129
|
-
text: JSON.stringify([
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
text: JSON.stringify([
|
|
134
|
+
{
|
|
135
|
+
type: 'pattern',
|
|
136
|
+
title: `Item ${callCount.n}`,
|
|
137
|
+
description: 'A pattern.',
|
|
138
|
+
tags: ['test'],
|
|
139
|
+
severity: 'suggestion',
|
|
140
|
+
},
|
|
141
|
+
]),
|
|
136
142
|
model: 'mock',
|
|
137
143
|
provider: 'openai' as const,
|
|
138
144
|
durationMs: 0,
|
package/src/llm/key-pool.test.ts
CHANGED
|
@@ -222,9 +222,7 @@ describe('loadKeyPoolConfig', () => {
|
|
|
222
222
|
|
|
223
223
|
it('should use env vars for providers missing from keys.json', () => {
|
|
224
224
|
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
225
|
-
vi.mocked(fs.readFileSync).mockReturnValue(
|
|
226
|
-
JSON.stringify({ openai: ['sk-file'] }),
|
|
227
|
-
);
|
|
225
|
+
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ openai: ['sk-file'] }));
|
|
228
226
|
process.env.ANTHROPIC_API_KEY = 'sk-env-ant';
|
|
229
227
|
|
|
230
228
|
const config = loadKeyPoolConfig('test-agent');
|
|
@@ -7,10 +7,7 @@ import { LLMError } from './types.js';
|
|
|
7
7
|
// HELPERS
|
|
8
8
|
// =============================================================================
|
|
9
9
|
|
|
10
|
-
function makeClient(opts?: {
|
|
11
|
-
openaiKeys?: string[];
|
|
12
|
-
anthropicKeys?: string[];
|
|
13
|
-
}): LLMClient {
|
|
10
|
+
function makeClient(opts?: { openaiKeys?: string[]; anthropicKeys?: string[] }): LLMClient {
|
|
14
11
|
const openai = new KeyPool({ keys: opts?.openaiKeys ?? [] });
|
|
15
12
|
const anthropic = new KeyPool({ keys: opts?.anthropicKeys ?? [] });
|
|
16
13
|
return new LLMClient(openai, anthropic);
|
|
@@ -37,7 +37,9 @@ describe('discoverAnthropicToken', () => {
|
|
|
37
37
|
delete process.env.ANTHROPIC_API_KEY;
|
|
38
38
|
vi.mocked(existsSync).mockReturnValue(false);
|
|
39
39
|
vi.mocked(readFileSync).mockReturnValue('{}');
|
|
40
|
-
vi.mocked(execFileSync).mockImplementation(() => {
|
|
40
|
+
vi.mocked(execFileSync).mockImplementation(() => {
|
|
41
|
+
throw new Error('not found');
|
|
42
|
+
});
|
|
41
43
|
vi.mocked(platform).mockReturnValue('darwin');
|
|
42
44
|
});
|
|
43
45
|
|
|
@@ -69,18 +71,14 @@ describe('discoverAnthropicToken', () => {
|
|
|
69
71
|
|
|
70
72
|
it('should return token from credentials file with direct accessToken', async () => {
|
|
71
73
|
vi.mocked(existsSync).mockReturnValue(true);
|
|
72
|
-
vi.mocked(readFileSync).mockReturnValue(
|
|
73
|
-
JSON.stringify({ accessToken: 'direct-token' }),
|
|
74
|
-
);
|
|
74
|
+
vi.mocked(readFileSync).mockReturnValue(JSON.stringify({ accessToken: 'direct-token' }));
|
|
75
75
|
const discover = await freshDiscover();
|
|
76
76
|
expect(discover()).toBe('direct-token');
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
it('should return token from credentials file with apiKey field', async () => {
|
|
80
80
|
vi.mocked(existsSync).mockReturnValue(true);
|
|
81
|
-
vi.mocked(readFileSync).mockReturnValue(
|
|
82
|
-
JSON.stringify({ apiKey: 'api-key-field' }),
|
|
83
|
-
);
|
|
81
|
+
vi.mocked(readFileSync).mockReturnValue(JSON.stringify({ apiKey: 'api-key-field' }));
|
|
84
82
|
const discover = await freshDiscover();
|
|
85
83
|
expect(discover()).toBe('api-key-field');
|
|
86
84
|
});
|
|
@@ -101,9 +99,7 @@ describe('discoverAnthropicToken', () => {
|
|
|
101
99
|
|
|
102
100
|
it('should handle keychain returning non-JSON with regex fallback', async () => {
|
|
103
101
|
vi.mocked(platform).mockReturnValue('darwin');
|
|
104
|
-
vi.mocked(execFileSync).mockReturnValue(
|
|
105
|
-
'{ truncated "accessToken": "regex-token" garbage',
|
|
106
|
-
);
|
|
102
|
+
vi.mocked(execFileSync).mockReturnValue('{ truncated "accessToken": "regex-token" garbage');
|
|
107
103
|
const discover = await freshDiscover();
|
|
108
104
|
expect(discover()).toBe('regex-token');
|
|
109
105
|
});
|
|
@@ -124,7 +120,9 @@ describe('discoverAnthropicToken', () => {
|
|
|
124
120
|
|
|
125
121
|
it('should treat long raw string from Linux keyring as token', async () => {
|
|
126
122
|
vi.mocked(platform).mockReturnValue('linux');
|
|
127
|
-
vi.mocked(execFileSync).mockReturnValue(
|
|
123
|
+
vi.mocked(execFileSync).mockReturnValue(
|
|
124
|
+
'a-long-raw-token-string-that-is-definitely-over-twenty-chars',
|
|
125
|
+
);
|
|
128
126
|
const discover = await freshDiscover();
|
|
129
127
|
expect(discover()).toBe('a-long-raw-token-string-that-is-definitely-over-twenty-chars');
|
|
130
128
|
});
|
|
@@ -142,14 +140,18 @@ describe('discoverAnthropicToken', () => {
|
|
|
142
140
|
|
|
143
141
|
it('should handle credentials file read errors gracefully', async () => {
|
|
144
142
|
vi.mocked(existsSync).mockReturnValue(true);
|
|
145
|
-
vi.mocked(readFileSync).mockImplementation(() => {
|
|
143
|
+
vi.mocked(readFileSync).mockImplementation(() => {
|
|
144
|
+
throw new Error('permission denied');
|
|
145
|
+
});
|
|
146
146
|
const discover = await freshDiscover();
|
|
147
147
|
expect(discover()).toBeNull();
|
|
148
148
|
});
|
|
149
149
|
|
|
150
150
|
it('should handle keychain errors gracefully', async () => {
|
|
151
151
|
vi.mocked(platform).mockReturnValue('darwin');
|
|
152
|
-
vi.mocked(execFileSync).mockImplementation(() => {
|
|
152
|
+
vi.mocked(execFileSync).mockImplementation(() => {
|
|
153
|
+
throw new Error('no keychain entry');
|
|
154
|
+
});
|
|
153
155
|
const discover = await freshDiscover();
|
|
154
156
|
expect(discover()).toBeNull();
|
|
155
157
|
});
|
|
@@ -164,9 +166,7 @@ describe('discoverAnthropicToken', () => {
|
|
|
164
166
|
it('should prioritize env var over credentials file', async () => {
|
|
165
167
|
process.env.ANTHROPIC_API_KEY = 'env-key';
|
|
166
168
|
vi.mocked(existsSync).mockReturnValue(true);
|
|
167
|
-
vi.mocked(readFileSync).mockReturnValue(
|
|
168
|
-
JSON.stringify({ apiKey: 'file-key' }),
|
|
169
|
-
);
|
|
169
|
+
vi.mocked(readFileSync).mockReturnValue(JSON.stringify({ apiKey: 'file-key' }));
|
|
170
170
|
const discover = await freshDiscover();
|
|
171
171
|
expect(discover()).toBe('env-key');
|
|
172
172
|
});
|