@soleri/core 9.14.4 → 9.16.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/flows/deliver.flow.yaml +11 -0
- package/data/flows/design.flow.yaml +4 -14
- package/data/flows/enhance.flow.yaml +10 -0
- package/data/flows/explore.flow.yaml +16 -0
- package/data/flows/fix.flow.yaml +1 -1
- package/data/flows/review.flow.yaml +13 -4
- package/dist/brain/brain.d.ts +9 -0
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +11 -1
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +24 -0
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/brain/types.d.ts +1 -0
- package/dist/brain/types.d.ts.map +1 -1
- package/dist/capabilities/chain-mapping.d.ts.map +1 -1
- package/dist/capabilities/chain-mapping.js +5 -4
- package/dist/capabilities/chain-mapping.js.map +1 -1
- package/dist/capabilities/registry.d.ts +6 -0
- package/dist/capabilities/registry.d.ts.map +1 -1
- package/dist/capabilities/registry.js +3 -2
- package/dist/capabilities/registry.js.map +1 -1
- package/dist/chat/chat-session.d.ts +6 -0
- package/dist/chat/chat-session.d.ts.map +1 -1
- package/dist/chat/chat-session.js +68 -17
- package/dist/chat/chat-session.js.map +1 -1
- package/dist/context/context-engine.js +1 -1
- package/dist/context/context-engine.js.map +1 -1
- package/dist/curator/curator.d.ts +6 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +138 -0
- package/dist/curator/curator.js.map +1 -1
- package/dist/curator/types.d.ts +10 -0
- package/dist/curator/types.d.ts.map +1 -1
- package/dist/engine/bin/soleri-engine.js +0 -0
- package/dist/engine/core-ops.d.ts.map +1 -1
- package/dist/engine/core-ops.js +38 -1
- package/dist/engine/core-ops.js.map +1 -1
- package/dist/flows/epilogue.d.ts +5 -1
- package/dist/flows/epilogue.d.ts.map +1 -1
- package/dist/flows/epilogue.js +11 -3
- package/dist/flows/epilogue.js.map +1 -1
- package/dist/flows/executor.d.ts.map +1 -1
- package/dist/flows/executor.js +13 -5
- package/dist/flows/executor.js.map +1 -1
- package/dist/flows/index.d.ts +1 -2
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js +1 -0
- package/dist/flows/index.js.map +1 -1
- package/dist/flows/plan-builder.d.ts +17 -1
- package/dist/flows/plan-builder.d.ts.map +1 -1
- package/dist/flows/plan-builder.js +67 -6
- package/dist/flows/plan-builder.js.map +1 -1
- package/dist/flows/probes.d.ts +1 -1
- package/dist/flows/probes.d.ts.map +1 -1
- package/dist/flows/probes.js +15 -3
- package/dist/flows/probes.js.map +1 -1
- package/dist/flows/types.d.ts +47 -20
- package/dist/flows/types.d.ts.map +1 -1
- package/dist/flows/types.js +6 -1
- package/dist/flows/types.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/intake/content-classifier.d.ts +10 -4
- package/dist/intake/content-classifier.d.ts.map +1 -1
- package/dist/intake/content-classifier.js +19 -5
- package/dist/intake/content-classifier.js.map +1 -1
- package/dist/intake/text-ingester.d.ts +18 -0
- package/dist/intake/text-ingester.d.ts.map +1 -1
- package/dist/intake/text-ingester.js +37 -13
- package/dist/intake/text-ingester.js.map +1 -1
- package/dist/packs/pack-installer.d.ts.map +1 -1
- package/dist/packs/pack-installer.js +28 -2
- package/dist/packs/pack-installer.js.map +1 -1
- package/dist/planning/planner-types.d.ts +2 -0
- package/dist/planning/planner-types.d.ts.map +1 -1
- package/dist/planning/planner.d.ts +4 -0
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +50 -4
- package/dist/planning/planner.js.map +1 -1
- package/dist/playbooks/playbook-executor.d.ts +10 -1
- package/dist/playbooks/playbook-executor.d.ts.map +1 -1
- package/dist/playbooks/playbook-executor.js +8 -2
- package/dist/playbooks/playbook-executor.js.map +1 -1
- package/dist/playbooks/playbook-types.d.ts +8 -0
- package/dist/playbooks/playbook-types.d.ts.map +1 -1
- package/dist/plugins/types.d.ts +2 -2
- package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
- package/dist/runtime/admin-extra-ops.js +30 -0
- package/dist/runtime/admin-extra-ops.js.map +1 -1
- package/dist/runtime/admin-ops.d.ts.map +1 -1
- package/dist/runtime/admin-ops.js +60 -21
- package/dist/runtime/admin-ops.js.map +1 -1
- package/dist/runtime/admin-setup-ops.d.ts +11 -0
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
- package/dist/runtime/admin-setup-ops.js +146 -37
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/capture-ops.d.ts.map +1 -1
- package/dist/runtime/capture-ops.js +38 -12
- package/dist/runtime/capture-ops.js.map +1 -1
- package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
- package/dist/runtime/facades/brain-facade.js +16 -4
- package/dist/runtime/facades/brain-facade.js.map +1 -1
- package/dist/runtime/facades/context-facade.d.ts.map +1 -1
- package/dist/runtime/facades/context-facade.js +9 -3
- package/dist/runtime/facades/context-facade.js.map +1 -1
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
- package/dist/runtime/facades/memory-facade.js +20 -7
- package/dist/runtime/facades/memory-facade.js.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.js +40 -1
- package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
- package/dist/runtime/facades/plan-facade.js +113 -4
- package/dist/runtime/facades/plan-facade.js.map +1 -1
- package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
- package/dist/runtime/facades/vault-facade.js +24 -3
- package/dist/runtime/facades/vault-facade.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts +21 -0
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +132 -38
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +16 -0
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/schema-helpers.d.ts.map +1 -1
- package/dist/runtime/schema-helpers.js +4 -0
- package/dist/runtime/schema-helpers.js.map +1 -1
- package/dist/runtime/types.d.ts +19 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
- package/dist/runtime/vault-linking-ops.js +16 -3
- package/dist/runtime/vault-linking-ops.js.map +1 -1
- package/dist/scheduler/cron-validator.d.ts +15 -0
- package/dist/scheduler/cron-validator.d.ts.map +1 -0
- package/dist/scheduler/cron-validator.js +93 -0
- package/dist/scheduler/cron-validator.js.map +1 -0
- package/dist/scheduler/platform-linux.d.ts +14 -0
- package/dist/scheduler/platform-linux.d.ts.map +1 -0
- package/dist/scheduler/platform-linux.js +107 -0
- package/dist/scheduler/platform-linux.js.map +1 -0
- package/dist/scheduler/platform-macos.d.ts +15 -0
- package/dist/scheduler/platform-macos.d.ts.map +1 -0
- package/dist/scheduler/platform-macos.js +131 -0
- package/dist/scheduler/platform-macos.js.map +1 -0
- package/dist/scheduler/scheduler-ops.d.ts +14 -0
- package/dist/scheduler/scheduler-ops.d.ts.map +1 -0
- package/dist/scheduler/scheduler-ops.js +77 -0
- package/dist/scheduler/scheduler-ops.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +55 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -0
- package/dist/scheduler/scheduler.js +144 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/scheduler/types.d.ts +48 -0
- package/dist/scheduler/types.d.ts.map +1 -0
- package/dist/scheduler/types.js +6 -0
- package/dist/scheduler/types.js.map +1 -0
- package/dist/skills/sync-skills.d.ts +11 -0
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +132 -38
- package/dist/skills/sync-skills.js.map +1 -1
- package/dist/skills/validate-skills.d.ts +32 -0
- package/dist/skills/validate-skills.d.ts.map +1 -0
- package/dist/skills/validate-skills.js +396 -0
- package/dist/skills/validate-skills.js.map +1 -0
- package/dist/utils/worktree-reaper.d.ts +38 -0
- package/dist/utils/worktree-reaper.d.ts.map +1 -0
- package/dist/utils/worktree-reaper.js +85 -0
- package/dist/utils/worktree-reaper.js.map +1 -0
- package/dist/vault/default-canonical-tags.d.ts +15 -0
- package/dist/vault/default-canonical-tags.d.ts.map +1 -0
- package/dist/vault/default-canonical-tags.js +65 -0
- package/dist/vault/default-canonical-tags.js.map +1 -0
- package/dist/vault/scope-detector.d.ts.map +1 -1
- package/dist/vault/scope-detector.js +37 -4
- package/dist/vault/scope-detector.js.map +1 -1
- package/dist/vault/tag-normalizer.d.ts +42 -0
- package/dist/vault/tag-normalizer.d.ts.map +1 -0
- package/dist/vault/tag-normalizer.js +157 -0
- package/dist/vault/tag-normalizer.js.map +1 -0
- package/dist/vault/vault-entries.d.ts.map +1 -1
- package/dist/vault/vault-entries.js +3 -1
- package/dist/vault/vault-entries.js.map +1 -1
- package/package.json +5 -1
- package/src/__tests__/embeddings.test.ts +3 -3
- package/src/agency/agency-manager.test.ts +4 -4
- package/src/agency/default-rules.test.ts +0 -13
- package/src/brain/brain-intelligence.test.ts +0 -5
- package/src/brain/brain.ts +25 -1
- package/src/brain/intelligence.ts +25 -0
- package/src/brain/second-brain-features.test.ts +2 -14
- package/src/brain/types.ts +1 -0
- package/src/capabilities/chain-mapping.test.ts +1 -6
- package/src/capabilities/chain-mapping.ts +6 -4
- package/src/capabilities/registry.test.ts +1 -1
- package/src/capabilities/registry.ts +9 -2
- package/src/chat/agent-loop.test.ts +1 -1
- package/src/chat/chat-enhanced.test.ts +0 -8
- package/src/chat/chat-session.ts +75 -17
- package/src/chat/chat-transport.test.ts +31 -1
- package/src/claudemd/compose.test.ts +0 -5
- package/src/context/context-engine.test.ts +0 -1
- package/src/context/context-engine.ts +1 -1
- package/src/control/intent-router.test.ts +2 -2
- package/src/curator/curator.ts +180 -0
- package/src/curator/tag-manager.test.ts +0 -4
- package/src/curator/types.ts +10 -0
- package/src/domain-packs/types.test.ts +0 -5
- package/src/dream/dream.test.ts +0 -7
- package/src/enforcement/registry.test.ts +2 -2
- package/src/engine/core-ops.test.ts +4 -22
- package/src/engine/core-ops.ts +36 -1
- package/src/engine/module-manifest.test.ts +1 -31
- package/src/engine/register-engine.test.ts +3 -33
- package/src/errors/retry.test.ts +3 -1
- package/src/flows/chain-runner.test.ts +0 -6
- package/src/flows/context-router.test.ts +3 -3
- package/src/flows/epilogue.test.ts +40 -2
- package/src/flows/epilogue.ts +11 -2
- package/src/flows/executor.test.ts +48 -2
- package/src/flows/executor.ts +15 -5
- package/src/flows/index.ts +1 -3
- package/src/flows/plan-builder.test.ts +201 -0
- package/src/flows/plan-builder.ts +81 -5
- package/src/flows/probes.ts +17 -3
- package/src/flows/types.ts +31 -2
- package/src/health/health-registry.test.ts +3 -1
- package/src/index.ts +24 -0
- package/src/intake/content-classifier.ts +22 -4
- package/src/intake/dedup-gate.test.ts +2 -6
- package/src/intake/text-ingester.test.ts +3 -4
- package/src/intake/text-ingester.ts +61 -12
- package/src/llm/llm-client.test.ts +1 -1
- package/src/llm/utils.test.ts +1 -1
- package/src/migrations/migration-runner.test.ts +0 -1
- package/src/operator/operator-context-store.test.ts +0 -13
- package/src/operator/operator-profile.test.ts +2 -20
- package/src/packs/pack-installer.ts +28 -2
- package/src/packs/pack-system.test.ts +2 -2
- package/src/persona/defaults.test.ts +19 -19
- package/src/planning/gap-passes.test.ts +0 -46
- package/src/planning/gap-patterns.test.ts +0 -42
- package/src/planning/goal-ancestry.test.ts +3 -1
- package/src/planning/plan-lifecycle.test.ts +15 -7
- package/src/planning/planner-types.ts +2 -0
- package/src/planning/planner.test.ts +86 -90
- package/src/planning/planner.ts +56 -4
- package/src/planning/reconciliation-engine.test.ts +3 -10
- package/src/planning/task-complexity-assessor.test.ts +0 -5
- package/src/planning/task-verifier.test.ts +3 -1
- package/src/playbooks/generic/generic-playbooks.test.ts +0 -28
- package/src/playbooks/index.test.ts +0 -55
- package/src/playbooks/playbook-executor.test.ts +76 -0
- package/src/playbooks/playbook-executor.ts +24 -3
- package/src/playbooks/playbook-types.ts +8 -0
- package/src/plugins/plugin-registry.test.ts +6 -2
- package/src/project/project-registry.test.ts +2 -0
- package/src/queue/async-infrastructure.test.ts +6 -4
- package/src/queue/job-queue.test.ts +13 -7
- package/src/runtime/admin-extra-ops.test.ts +35 -30
- package/src/runtime/admin-extra-ops.ts +30 -0
- package/src/runtime/admin-ops.test.ts +0 -4
- package/src/runtime/admin-ops.ts +63 -21
- package/src/runtime/admin-setup-ops.test.ts +229 -13
- package/src/runtime/admin-setup-ops.ts +145 -36
- package/src/runtime/archive-ops.test.ts +0 -28
- package/src/runtime/branching-ops.test.ts +0 -17
- package/src/runtime/capture-ops.test.ts +41 -16
- package/src/runtime/capture-ops.ts +78 -46
- package/src/runtime/chain-ops.test.ts +0 -21
- package/src/runtime/facades/admin-facade.test.ts +0 -34
- package/src/runtime/facades/agency-facade.test.ts +0 -39
- package/src/runtime/facades/archive-facade.test.ts +0 -43
- package/src/runtime/facades/brain-facade.test.ts +8 -99
- package/src/runtime/facades/brain-facade.ts +29 -12
- package/src/runtime/facades/branching-facade.test.ts +30 -17
- package/src/runtime/facades/chat-facade.test.ts +0 -91
- package/src/runtime/facades/chat-service-ops.test.ts +0 -24
- package/src/runtime/facades/chat-session-ops.test.ts +0 -12
- package/src/runtime/facades/chat-transport-ops.test.ts +0 -23
- package/src/runtime/facades/context-facade.test.ts +0 -17
- package/src/runtime/facades/context-facade.ts +11 -4
- package/src/runtime/facades/control-facade.test.ts +0 -30
- package/src/runtime/facades/curator-facade.test.ts +0 -33
- package/src/runtime/facades/intake-facade.test.ts +0 -33
- package/src/runtime/facades/links-facade.test.ts +0 -37
- package/src/runtime/facades/loop-facade.test.ts +0 -26
- package/src/runtime/facades/memory-facade.test.ts +0 -18
- package/src/runtime/facades/memory-facade.ts +27 -11
- package/src/runtime/facades/operator-facade.test.ts +0 -31
- package/src/runtime/facades/orchestrate-facade.test.ts +0 -21
- package/src/runtime/facades/orchestrate-facade.ts +39 -1
- package/src/runtime/facades/plan-facade.test.ts +7 -32
- package/src/runtime/facades/plan-facade.ts +137 -4
- package/src/runtime/facades/review-facade.test.ts +1 -49
- package/src/runtime/facades/sync-facade.test.ts +24 -41
- package/src/runtime/facades/tier-facade.test.ts +30 -22
- package/src/runtime/facades/vault-facade.test.ts +0 -41
- package/src/runtime/facades/vault-facade.ts +26 -3
- package/src/runtime/grading-ops.test.ts +0 -27
- package/src/runtime/intake-ops.test.ts +0 -19
- package/src/runtime/loop-ops.test.ts +0 -48
- package/src/runtime/memory-cross-project-ops.test.ts +0 -14
- package/src/runtime/memory-extra-ops.test.ts +4 -8
- package/src/runtime/orchestrate-ops.test.ts +238 -19
- package/src/runtime/orchestrate-ops.ts +166 -41
- package/src/runtime/pack-ops.test.ts +0 -26
- package/src/runtime/planning-extra-ops.test.ts +2 -14
- package/src/runtime/playbook-ops-execution.test.ts +9 -20
- package/src/runtime/playbook-ops.test.ts +4 -67
- package/src/runtime/review-ops.test.ts +0 -15
- package/src/runtime/runtime.ts +18 -0
- package/src/runtime/schema-helpers.ts +4 -0
- package/src/runtime/sync-ops.test.ts +0 -18
- package/src/runtime/tier-ops.test.ts +0 -21
- package/src/runtime/types.ts +19 -0
- package/src/runtime/vault-extra-ops.test.ts +0 -12
- package/src/runtime/vault-linking-ops.test.ts +0 -4
- package/src/runtime/vault-linking-ops.ts +26 -8
- package/src/runtime/vault-sharing-ops.test.ts +0 -9
- package/src/scheduler/cron-validator.ts +101 -0
- package/src/scheduler/platform-linux.ts +122 -0
- package/src/scheduler/platform-macos.ts +150 -0
- package/src/scheduler/scheduler-ops.ts +77 -0
- package/src/scheduler/scheduler.test.ts +247 -0
- package/src/scheduler/scheduler.ts +174 -0
- package/src/scheduler/types.ts +52 -0
- package/src/skills/__tests__/sync-skills.test.ts +6 -17
- package/src/skills/global-claude-md.test.ts +113 -0
- package/src/skills/sync-skills.ts +143 -35
- package/src/skills/validate-skills.test.ts +206 -0
- package/src/skills/validate-skills.ts +470 -0
- package/src/telemetry/telemetry.test.ts +1 -0
- package/src/transport/http-server.test.ts +3 -0
- package/src/transport/session-manager.test.ts +3 -1
- package/src/transport/token-auth.test.ts +6 -9
- package/src/transport/ws-server.test.ts +10 -2
- package/src/utils/worktree-reaper.ts +113 -0
- package/src/vault/__tests__/vault-characterization.test.ts +0 -108
- package/src/vault/default-canonical-tags.ts +64 -0
- package/src/vault/linking.test.ts +0 -2
- package/src/vault/playbook.test.ts +4 -1
- package/src/vault/scope-detector.test.ts +3 -1
- package/src/vault/scope-detector.ts +42 -4
- package/src/vault/tag-normalizer.test.ts +214 -0
- package/src/vault/tag-normalizer.ts +188 -0
- package/src/vault/vault-connect.test.ts +1 -1
- package/src/vault/vault-entries.ts +3 -1
- package/src/vault/vault.test.ts +23 -8
- package/dist/embeddings/index.d.ts +0 -5
- package/dist/embeddings/index.d.ts.map +0 -1
- package/dist/embeddings/index.js +0 -3
- package/dist/embeddings/index.js.map +0 -1
|
@@ -51,14 +51,6 @@ describe('Vault Characterization Tests', () => {
|
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
describe('constructor', () => {
|
|
54
|
-
it('creates an in-memory vault', () => {
|
|
55
|
-
const v = new Vault(':memory:');
|
|
56
|
-
expect(v).toBeInstanceOf(Vault);
|
|
57
|
-
v.close();
|
|
58
|
-
});
|
|
59
|
-
it('stamps FORMAT_VERSION', () => {
|
|
60
|
-
expect(Vault.FORMAT_VERSION).toBe(1);
|
|
61
|
-
});
|
|
62
54
|
it('createWithSQLite factory', () => {
|
|
63
55
|
const v = Vault.createWithSQLite(':memory:');
|
|
64
56
|
expect(v).toBeInstanceOf(Vault);
|
|
@@ -83,26 +75,11 @@ describe('Vault Characterization Tests', () => {
|
|
|
83
75
|
});
|
|
84
76
|
|
|
85
77
|
describe('seed', () => {
|
|
86
|
-
it('inserts entries', () => {
|
|
87
|
-
expect(vault.seed([makeEntry({ id: 'seed-1' })])).toBe(1);
|
|
88
|
-
});
|
|
89
78
|
it('upserts on conflict', () => {
|
|
90
79
|
vault.seed([makeEntry({ id: 'u1', title: 'Old' })]);
|
|
91
80
|
vault.seed([makeEntry({ id: 'u1', title: 'New' })]);
|
|
92
81
|
expect(vault.get('u1')?.title).toBe('New');
|
|
93
82
|
});
|
|
94
|
-
it('handles multiple', () => {
|
|
95
|
-
expect(
|
|
96
|
-
vault.seed([makeEntry({ id: 'a1' }), makeEntry({ id: 'a2' }), makeEntry({ id: 'a3' })]),
|
|
97
|
-
).toBe(3);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe('add', () => {
|
|
102
|
-
it('delegates to seed', () => {
|
|
103
|
-
vault.add(makeEntry({ id: 'add-1' }));
|
|
104
|
-
expect(vault.get('add-1')).toBeTruthy();
|
|
105
|
-
});
|
|
106
83
|
});
|
|
107
84
|
|
|
108
85
|
describe('get', () => {
|
|
@@ -172,10 +149,6 @@ describe('Vault Characterization Tests', () => {
|
|
|
172
149
|
});
|
|
173
150
|
|
|
174
151
|
describe('list', () => {
|
|
175
|
-
it('returns entries', () => {
|
|
176
|
-
vault.seed([makeEntry({ id: 'l1' }), makeEntry({ id: 'l2' })]);
|
|
177
|
-
expect(vault.list().length).toBe(2);
|
|
178
|
-
});
|
|
179
152
|
it('filters by domain', () => {
|
|
180
153
|
vault.seed([
|
|
181
154
|
makeEntry({ id: 'ld1', domain: 'react' }),
|
|
@@ -245,13 +218,6 @@ describe('Vault Characterization Tests', () => {
|
|
|
245
218
|
]);
|
|
246
219
|
});
|
|
247
220
|
});
|
|
248
|
-
describe('getRecent', () => {
|
|
249
|
-
it('returns entries', () => {
|
|
250
|
-
vault.seed([makeEntry({ id: 'r1' })]);
|
|
251
|
-
vault.seed([makeEntry({ id: 'r2' })]);
|
|
252
|
-
expect(vault.getRecent(5).length).toBe(2);
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
221
|
|
|
256
222
|
describe('setTemporal', () => {
|
|
257
223
|
it('sets temporal fields', () => {
|
|
@@ -319,30 +285,7 @@ describe('Vault Characterization Tests', () => {
|
|
|
319
285
|
expect(vault.findByContentHash('x')).toBeNull();
|
|
320
286
|
});
|
|
321
287
|
});
|
|
322
|
-
describe('contentHashStats', () => {
|
|
323
|
-
it('zeros for empty', () => {
|
|
324
|
-
const s = vault.contentHashStats();
|
|
325
|
-
expect(s.total).toBe(0);
|
|
326
|
-
});
|
|
327
|
-
it('counts', () => {
|
|
328
|
-
vault.seed([
|
|
329
|
-
makeEntry({ id: 'c1', title: 'First' }),
|
|
330
|
-
makeEntry({ id: 'c2', title: 'Second' }),
|
|
331
|
-
]);
|
|
332
|
-
const s = vault.contentHashStats();
|
|
333
|
-
expect(s.total).toBe(2);
|
|
334
|
-
expect(s.uniqueHashes).toBe(2);
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
288
|
|
|
338
|
-
describe('exportAll', () => {
|
|
339
|
-
it('exports all', () => {
|
|
340
|
-
vault.seed([makeEntry({ id: 'e1' }), makeEntry({ id: 'e2' })]);
|
|
341
|
-
const r = vault.exportAll();
|
|
342
|
-
expect(r.count).toBe(2);
|
|
343
|
-
expect(typeof r.exportedAt).toBe('number');
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
289
|
describe('getAgeReport', () => {
|
|
347
290
|
it('empty report', () => {
|
|
348
291
|
const r = vault.getAgeReport();
|
|
@@ -389,12 +332,6 @@ describe('Vault Characterization Tests', () => {
|
|
|
389
332
|
});
|
|
390
333
|
});
|
|
391
334
|
|
|
392
|
-
describe('optimize', () => {
|
|
393
|
-
it('returns status', () => {
|
|
394
|
-
const r = vault.optimize();
|
|
395
|
-
expect(typeof r.vacuumed).toBe('boolean');
|
|
396
|
-
});
|
|
397
|
-
});
|
|
398
335
|
describe('rebuildFtsIndex', () => {
|
|
399
336
|
it('does not throw', () => {
|
|
400
337
|
expect(() => vault.rebuildFtsIndex()).not.toThrow();
|
|
@@ -402,10 +339,6 @@ describe('Vault Characterization Tests', () => {
|
|
|
402
339
|
});
|
|
403
340
|
|
|
404
341
|
describe('registerProject', () => {
|
|
405
|
-
it('registers new', () => {
|
|
406
|
-
const p = vault.registerProject('/t', 'test');
|
|
407
|
-
expect(p.sessionCount).toBe(1);
|
|
408
|
-
});
|
|
409
342
|
it('increments on re-register', () => {
|
|
410
343
|
vault.registerProject('/t');
|
|
411
344
|
expect(vault.registerProject('/t').sessionCount).toBe(2);
|
|
@@ -424,21 +357,7 @@ describe('Vault Characterization Tests', () => {
|
|
|
424
357
|
expect(vault.getProject('/t')!.name).toBe('T');
|
|
425
358
|
});
|
|
426
359
|
});
|
|
427
|
-
describe('listProjects', () => {
|
|
428
|
-
it('lists all', () => {
|
|
429
|
-
vault.registerProject('/a');
|
|
430
|
-
vault.registerProject('/b');
|
|
431
|
-
expect(vault.listProjects().length).toBe(2);
|
|
432
|
-
});
|
|
433
|
-
});
|
|
434
360
|
|
|
435
|
-
describe('captureMemory', () => {
|
|
436
|
-
it('creates with id', () => {
|
|
437
|
-
const m = vault.captureMemory(makeMemoryInput());
|
|
438
|
-
expect(m.id).toMatch(/^mem-/);
|
|
439
|
-
expect(m.archivedAt).toBeNull();
|
|
440
|
-
});
|
|
441
|
-
});
|
|
442
361
|
describe('getMemory', () => {
|
|
443
362
|
it('returns null', () => {
|
|
444
363
|
expect(vault.getMemory('x')).toBeNull();
|
|
@@ -489,9 +408,6 @@ describe('Vault Characterization Tests', () => {
|
|
|
489
408
|
});
|
|
490
409
|
|
|
491
410
|
describe('memoryStats', () => {
|
|
492
|
-
it('zeros for empty', () => {
|
|
493
|
-
expect(vault.memoryStats().total).toBe(0);
|
|
494
|
-
});
|
|
495
411
|
it('counts', () => {
|
|
496
412
|
vault.captureMemory(makeMemoryInput({ projectPath: '/a', type: 'session' }));
|
|
497
413
|
vault.captureMemory(makeMemoryInput({ projectPath: '/a', type: 'lesson' }));
|
|
@@ -502,20 +418,6 @@ describe('Vault Characterization Tests', () => {
|
|
|
502
418
|
});
|
|
503
419
|
});
|
|
504
420
|
|
|
505
|
-
describe('memoryStatsDetailed', () => {
|
|
506
|
-
it('includes extended fields', () => {
|
|
507
|
-
vault.captureMemory(makeMemoryInput());
|
|
508
|
-
const s = vault.memoryStatsDetailed();
|
|
509
|
-
expect(typeof s.oldest).toBe('number');
|
|
510
|
-
expect(s.archivedCount).toBe(0);
|
|
511
|
-
});
|
|
512
|
-
});
|
|
513
|
-
describe('exportMemories', () => {
|
|
514
|
-
it('exports', () => {
|
|
515
|
-
vault.captureMemory(makeMemoryInput());
|
|
516
|
-
expect(vault.exportMemories().length).toBe(1);
|
|
517
|
-
});
|
|
518
|
-
});
|
|
519
421
|
describe('importMemories', () => {
|
|
520
422
|
it('imports and deduplicates', () => {
|
|
521
423
|
vault.captureMemory(makeMemoryInput());
|
|
@@ -560,16 +462,6 @@ describe('Vault Characterization Tests', () => {
|
|
|
560
462
|
});
|
|
561
463
|
});
|
|
562
464
|
|
|
563
|
-
describe('getProvider', () => {
|
|
564
|
-
it('returns provider', () => {
|
|
565
|
-
expect(vault.getProvider().backend).toBe('sqlite');
|
|
566
|
-
});
|
|
567
|
-
});
|
|
568
|
-
describe('getDb', () => {
|
|
569
|
-
it('returns db', () => {
|
|
570
|
-
expect(vault.getDb()).toBeTruthy();
|
|
571
|
-
});
|
|
572
|
-
});
|
|
573
465
|
describe('close', () => {
|
|
574
466
|
it('does not throw', () => {
|
|
575
467
|
const v = new Vault(':memory:');
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default canonical tag taxonomy for Soleri agents.
|
|
3
|
+
*
|
|
4
|
+
* These tags represent the most common knowledge domains. When a vault is
|
|
5
|
+
* configured with tagConstraintMode 'suggest' or 'enforce', incoming tags
|
|
6
|
+
* are mapped to the nearest entry in this list via edit-distance matching.
|
|
7
|
+
*
|
|
8
|
+
* To use these defaults in your agent runtime config:
|
|
9
|
+
* import { DEFAULT_CANONICAL_TAGS } from '@soleri/core';
|
|
10
|
+
* // ...
|
|
11
|
+
* canonicalTags: DEFAULT_CANONICAL_TAGS,
|
|
12
|
+
* tagConstraintMode: 'suggest',
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_CANONICAL_TAGS: string[] = [
|
|
15
|
+
'architecture',
|
|
16
|
+
'typescript',
|
|
17
|
+
'react',
|
|
18
|
+
'testing',
|
|
19
|
+
'workflow',
|
|
20
|
+
'design-tokens',
|
|
21
|
+
'accessibility',
|
|
22
|
+
'performance',
|
|
23
|
+
'security',
|
|
24
|
+
'planning',
|
|
25
|
+
'soleri',
|
|
26
|
+
'vault',
|
|
27
|
+
'mcp',
|
|
28
|
+
'claude-code',
|
|
29
|
+
'ai',
|
|
30
|
+
'learning',
|
|
31
|
+
'gamification',
|
|
32
|
+
'education',
|
|
33
|
+
'adhd',
|
|
34
|
+
'routing',
|
|
35
|
+
'orchestration',
|
|
36
|
+
'skills',
|
|
37
|
+
'automation',
|
|
38
|
+
'git',
|
|
39
|
+
'database',
|
|
40
|
+
'api',
|
|
41
|
+
'authentication',
|
|
42
|
+
'subagent',
|
|
43
|
+
'design-system',
|
|
44
|
+
'component',
|
|
45
|
+
'frontend',
|
|
46
|
+
'backend',
|
|
47
|
+
'tooling',
|
|
48
|
+
'monorepo',
|
|
49
|
+
'refactoring',
|
|
50
|
+
'debugging',
|
|
51
|
+
'deployment',
|
|
52
|
+
'configuration',
|
|
53
|
+
'documentation',
|
|
54
|
+
'pattern',
|
|
55
|
+
'anti-pattern',
|
|
56
|
+
'principle',
|
|
57
|
+
'decision',
|
|
58
|
+
'migration',
|
|
59
|
+
'plugin',
|
|
60
|
+
'hook',
|
|
61
|
+
'schema',
|
|
62
|
+
'pipeline',
|
|
63
|
+
'ingestion',
|
|
64
|
+
];
|
|
@@ -355,7 +355,6 @@ describe('LinkManager', () => {
|
|
|
355
355
|
const result = mgr.backfillLinks();
|
|
356
356
|
expect(result.processed).toBe(0);
|
|
357
357
|
expect(result.linksCreated).toBe(0);
|
|
358
|
-
expect(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
359
358
|
});
|
|
360
359
|
|
|
361
360
|
it('dry run populates preview array', () => {
|
|
@@ -385,7 +384,6 @@ describe('LinkManager', () => {
|
|
|
385
384
|
if (result.processed > 0 && result.preview) {
|
|
386
385
|
expect(Array.isArray(result.preview)).toBe(true);
|
|
387
386
|
}
|
|
388
|
-
expect(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
389
387
|
});
|
|
390
388
|
|
|
391
389
|
it('calls onProgress callback', () => {
|
|
@@ -95,7 +95,10 @@ describe('validatePlaybook', () => {
|
|
|
95
95
|
const steps = [makeStep({ title: '', description: '' }, 1)];
|
|
96
96
|
const result = validatePlaybook(makePlaybook({ title: '', steps }));
|
|
97
97
|
expect(result.valid).toBe(false);
|
|
98
|
-
expect(result.errors
|
|
98
|
+
expect(result.errors).toHaveLength(3);
|
|
99
|
+
expect(result.errors).toContain('Playbook title must not be empty');
|
|
100
|
+
expect(result.errors).toContain('Step 1 title must not be empty');
|
|
101
|
+
expect(result.errors).toContain('Step 1 description must not be empty');
|
|
99
102
|
});
|
|
100
103
|
});
|
|
101
104
|
|
|
@@ -165,11 +165,13 @@ describe('detectScope', () => {
|
|
|
165
165
|
const result = detectScope(
|
|
166
166
|
makeInput({ description: 'Use focus ring for keyboard navigation' }),
|
|
167
167
|
);
|
|
168
|
+
expect(result.signals.length).toBeGreaterThan(0);
|
|
168
169
|
for (const signal of result.signals) {
|
|
169
170
|
expect(signal).toHaveProperty('tier');
|
|
170
171
|
expect(signal).toHaveProperty('source');
|
|
171
172
|
expect(signal).toHaveProperty('indicator');
|
|
172
173
|
expect(signal).toHaveProperty('weight');
|
|
174
|
+
// Weight must be in (0, 1] range
|
|
173
175
|
expect(signal.weight).toBeGreaterThan(0);
|
|
174
176
|
expect(signal.weight).toBeLessThanOrEqual(1);
|
|
175
177
|
}
|
|
@@ -179,7 +181,7 @@ describe('detectScope', () => {
|
|
|
179
181
|
const result = detectScope(
|
|
180
182
|
makeInput({ description: 'Accessibility best practice for a11y compliance' }),
|
|
181
183
|
);
|
|
182
|
-
expect(result.reason
|
|
184
|
+
expect(result.reason).toMatch(/accessibility|a11y/i);
|
|
183
185
|
expect(result.reason).not.toContain('defaulting');
|
|
184
186
|
});
|
|
185
187
|
});
|
|
@@ -49,7 +49,27 @@ const TEAM_CONTENT_PATTERNS: Array<{ pattern: RegExp; weight: number; desc: stri
|
|
|
49
49
|
{ pattern: /error\s+handling|error\s+boundary/i, weight: 0.6, desc: 'error handling' },
|
|
50
50
|
{ pattern: /touch\s+target|tap\s+target|fitts/i, weight: 0.8, desc: 'UX touch targets' },
|
|
51
51
|
{ pattern: /focus\s+(ring|state|indicator)/i, weight: 0.8, desc: 'focus states' },
|
|
52
|
-
|
|
52
|
+
// Reduced weight: "best practice" alone is too weak to push to team tier
|
|
53
|
+
{ pattern: /best\s+practice|anti.?pattern/i, weight: 0.4, desc: 'best practice' },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Project-specific negative signals — agent/tool names and framework-specific patterns
|
|
58
|
+
* that indicate the entry is project-scoped, not universally applicable.
|
|
59
|
+
*/
|
|
60
|
+
const PROJECT_SPECIFIC_PATTERNS: Array<{ pattern: RegExp; weight: number; desc: string }> = [
|
|
61
|
+
{ pattern: /\bsoleri\b/i, weight: 0.9, desc: 'Soleri project reference' },
|
|
62
|
+
{ pattern: /\bernesto\b/i, weight: 0.9, desc: 'Ernesto agent reference' },
|
|
63
|
+
{ pattern: /\bsalvador\b/i, weight: 0.9, desc: 'Salvador agent reference' },
|
|
64
|
+
{
|
|
65
|
+
pattern: /\bskill\s+(file|system|pack|registry|sync)/i,
|
|
66
|
+
weight: 0.7,
|
|
67
|
+
desc: 'skill system specific',
|
|
68
|
+
},
|
|
69
|
+
{ pattern: /vault\s+(entry|search|op|facade)/i, weight: 0.65, desc: 'vault system specific' },
|
|
70
|
+
{ pattern: /domain\s+pack|hook\s+pack/i, weight: 0.7, desc: 'Soleri pack system' },
|
|
71
|
+
{ pattern: /plan\s+(grade|facade|lifecycle)/i, weight: 0.65, desc: 'Soleri planning system' },
|
|
72
|
+
{ pattern: /soleri\s+(agent|cli|core|forge)/i, weight: 0.95, desc: 'Soleri package reference' },
|
|
53
73
|
];
|
|
54
74
|
|
|
55
75
|
const PROJECT_CONTENT_PATTERNS: Array<{ pattern: RegExp; weight: number; desc: string }> = [
|
|
@@ -128,6 +148,11 @@ function analyzeContent(text: string): ScopeSignal[] {
|
|
|
128
148
|
signals.push({ tier: 'project', source: 'content', indicator: desc, weight });
|
|
129
149
|
}
|
|
130
150
|
}
|
|
151
|
+
for (const { pattern, weight, desc } of PROJECT_SPECIFIC_PATTERNS) {
|
|
152
|
+
if (pattern.test(text)) {
|
|
153
|
+
signals.push({ tier: 'project', source: 'content', indicator: desc, weight });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
131
156
|
for (const { pattern, weight, desc } of AGENT_CONTENT_PATTERNS) {
|
|
132
157
|
if (pattern.test(text)) {
|
|
133
158
|
signals.push({ tier: 'agent', source: 'content', indicator: desc, weight });
|
|
@@ -165,7 +190,11 @@ function analyzeTags(tags: string[]): ScopeSignal[] {
|
|
|
165
190
|
return signals;
|
|
166
191
|
}
|
|
167
192
|
|
|
168
|
-
function computeConfidence(
|
|
193
|
+
function computeConfidence(
|
|
194
|
+
scores: Record<ScopeTier, number>,
|
|
195
|
+
winner: ScopeTier,
|
|
196
|
+
signals: ScopeSignal[],
|
|
197
|
+
): ConfidenceLevel {
|
|
169
198
|
const winScore = scores[winner];
|
|
170
199
|
const others = Object.entries(scores)
|
|
171
200
|
.filter(([t]) => t !== winner)
|
|
@@ -173,7 +202,16 @@ function computeConfidence(scores: Record<ScopeTier, number>, winner: ScopeTier)
|
|
|
173
202
|
const runnerUp = Math.max(...others, 0);
|
|
174
203
|
|
|
175
204
|
if (winScore === 0) return 'LOW';
|
|
176
|
-
if (runnerUp === 0 && winScore >= 0.5)
|
|
205
|
+
if (runnerUp === 0 && winScore >= 0.5) {
|
|
206
|
+
// Cap team tier at MEDIUM when only generic/methodology signals drove the win
|
|
207
|
+
if (winner === 'team') {
|
|
208
|
+
const genericOnly = signals
|
|
209
|
+
.filter((s) => s.tier === 'team')
|
|
210
|
+
.every((s) => s.indicator === 'best practice' || s.source === 'category');
|
|
211
|
+
if (genericOnly) return 'MEDIUM';
|
|
212
|
+
}
|
|
213
|
+
return 'HIGH';
|
|
214
|
+
}
|
|
177
215
|
const ratio = winScore / (winScore + runnerUp);
|
|
178
216
|
if (ratio >= 0.7 && winScore >= 1.0) return 'HIGH';
|
|
179
217
|
if (ratio >= 0.55) return 'MEDIUM';
|
|
@@ -205,7 +243,7 @@ export function detectScope(input: ScopeInput): ScopeDetectionResult {
|
|
|
205
243
|
}
|
|
206
244
|
}
|
|
207
245
|
|
|
208
|
-
const confidence = computeConfidence(scores, winner);
|
|
246
|
+
const confidence = computeConfidence(scores, winner, signals);
|
|
209
247
|
const topSignals = signals
|
|
210
248
|
.filter((s) => s.tier === winner)
|
|
211
249
|
.sort((a, b) => b.weight - a.weight)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
computeEditDistance,
|
|
4
|
+
normalizeTag,
|
|
5
|
+
normalizeTags,
|
|
6
|
+
isMetadataTag,
|
|
7
|
+
} from './tag-normalizer.js';
|
|
8
|
+
|
|
9
|
+
// ─── computeEditDistance ────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
describe('computeEditDistance', () => {
|
|
12
|
+
it('returns 0 for identical strings', () => {
|
|
13
|
+
expect(computeEditDistance('workflow', 'workflow')).toBe(0);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('returns length of b for empty a', () => {
|
|
17
|
+
expect(computeEditDistance('', 'abc')).toBe(3);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('returns length of a for empty b', () => {
|
|
21
|
+
expect(computeEditDistance('abc', '')).toBe(3);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('single insertion: workflow → workflows', () => {
|
|
25
|
+
expect(computeEditDistance('workflow', 'workflows')).toBe(1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('single deletion: testing → testin', () => {
|
|
29
|
+
expect(computeEditDistance('testing', 'testin')).toBe(1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('single substitution: arch → arcs', () => {
|
|
33
|
+
expect(computeEditDistance('arch', 'arcs')).toBe(1);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("distance 1: architcture → architecture (single insertion - missing 'e')", () => {
|
|
37
|
+
expect(computeEditDistance('architcture', 'architecture')).toBe(1);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('distance 2: archtecrure → architecture (two edits)', () => {
|
|
41
|
+
expect(computeEditDistance('archtecrure', 'architecture')).toBe(2);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('large distance: typescript → javascript', () => {
|
|
45
|
+
expect(computeEditDistance('typescript', 'javascript')).toBeGreaterThan(3);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('is symmetric', () => {
|
|
49
|
+
expect(computeEditDistance('foo', 'bar')).toBe(computeEditDistance('bar', 'foo'));
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ─── isMetadataTag ──────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
describe('isMetadataTag', () => {
|
|
56
|
+
it('returns true when tag matches a prefix', () => {
|
|
57
|
+
expect(isMetadataTag('source:article', ['source:'])).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('returns true for exact prefix match', () => {
|
|
61
|
+
expect(isMetadataTag('source:ingested', ['source:'])).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('returns false when no prefix matches', () => {
|
|
65
|
+
expect(isMetadataTag('typescript', ['source:'])).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('returns false with empty prefix list', () => {
|
|
69
|
+
expect(isMetadataTag('source:article', [])).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('supports multiple prefixes', () => {
|
|
73
|
+
expect(isMetadataTag('meta:foo', ['source:', 'meta:'])).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ─── normalizeTag ───────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
const CANONICAL = ['architecture', 'typescript', 'workflow', 'testing', 'performance'];
|
|
80
|
+
|
|
81
|
+
describe('normalizeTag — mode: off', () => {
|
|
82
|
+
it('returns tag as-is regardless of canonical list', () => {
|
|
83
|
+
expect(normalizeTag('workflows', CANONICAL, 'off')).toBe('workflows');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('passes through noise words in off mode', () => {
|
|
87
|
+
expect(normalizeTag('new', CANONICAL, 'off')).toBe('new');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('normalizeTag — noise stripping', () => {
|
|
92
|
+
it('drops version strings (v1.2)', () => {
|
|
93
|
+
expect(normalizeTag('v1.2', CANONICAL, 'suggest')).toBeNull();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('drops version strings (v10)', () => {
|
|
97
|
+
expect(normalizeTag('v10', CANONICAL, 'suggest')).toBeNull();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('drops generic noise word: new', () => {
|
|
101
|
+
expect(normalizeTag('new', CANONICAL, 'suggest')).toBeNull();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('drops generic noise word: via', () => {
|
|
105
|
+
expect(normalizeTag('via', CANONICAL, 'suggest')).toBeNull();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('drops generic noise word: raw', () => {
|
|
109
|
+
expect(normalizeTag('raw', CANONICAL, 'enforce')).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('normalizeTag — mode: suggest', () => {
|
|
114
|
+
it('returns canonical for exact match', () => {
|
|
115
|
+
expect(normalizeTag('typescript', CANONICAL, 'suggest')).toBe('typescript');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('maps within edit-distance 2 to canonical', () => {
|
|
119
|
+
// 'workflows' is distance 1 from 'workflow'
|
|
120
|
+
expect(normalizeTag('workflows', CANONICAL, 'suggest')).toBe('workflow');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('lowercases tag before matching', () => {
|
|
124
|
+
expect(normalizeTag('TypeScript', CANONICAL, 'suggest')).toBe('typescript');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('passes through unknown tag with no close canonical (suggest passthrough)', () => {
|
|
128
|
+
const result = normalizeTag('gamification', CANONICAL, 'suggest');
|
|
129
|
+
// No match within distance 2 — passthrough
|
|
130
|
+
expect(result).toBe('gamification');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('returns null for noise even in suggest mode', () => {
|
|
134
|
+
expect(normalizeTag('one', CANONICAL, 'suggest')).toBeNull();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('normalizeTag — mode: enforce', () => {
|
|
139
|
+
it('returns canonical for exact match', () => {
|
|
140
|
+
expect(normalizeTag('testing', CANONICAL, 'enforce')).toBe('testing');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('maps within edit-distance 3 to canonical', () => {
|
|
144
|
+
// 'archtecrure' is 2 away from 'architecture'
|
|
145
|
+
expect(normalizeTag('archtecrure', CANONICAL, 'enforce')).toBe('architecture');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('returns null for tag with no match within distance 3', () => {
|
|
149
|
+
// 'gamification' is far from all CANONICAL entries
|
|
150
|
+
expect(normalizeTag('gamification', CANONICAL, 'enforce')).toBeNull();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('returns null for noise words', () => {
|
|
154
|
+
expect(normalizeTag('full', CANONICAL, 'enforce')).toBeNull();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('normalizeTag — empty canonical list', () => {
|
|
159
|
+
it('suggest mode: passes through non-noise tags', () => {
|
|
160
|
+
expect(normalizeTag('react', [], 'suggest')).toBe('react');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('enforce mode: drops all tags (no canonical to match)', () => {
|
|
164
|
+
expect(normalizeTag('react', [], 'enforce')).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// ─── normalizeTags ──────────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
describe('normalizeTags', () => {
|
|
171
|
+
it('deduplicates tags that map to the same canonical', () => {
|
|
172
|
+
// Both 'workflows' and 'workflow' normalize to 'workflow'
|
|
173
|
+
const result = normalizeTags(['workflows', 'workflow'], CANONICAL, 'suggest');
|
|
174
|
+
expect(result).toEqual(['workflow']);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('preserves metadata tags unchanged', () => {
|
|
178
|
+
const result = normalizeTags(['source:article', 'typescript'], CANONICAL, 'enforce', [
|
|
179
|
+
'source:',
|
|
180
|
+
]);
|
|
181
|
+
expect(result).toContain('source:article');
|
|
182
|
+
expect(result).toContain('typescript');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('metadata tags bypass canonical normalization in enforce mode', () => {
|
|
186
|
+
// 'source:mytype' does not match any canonical — but it should be kept
|
|
187
|
+
const result = normalizeTags(['source:mytype'], CANONICAL, 'enforce', ['source:']);
|
|
188
|
+
expect(result).toEqual(['source:mytype']);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('returns empty array when all tags are noise', () => {
|
|
192
|
+
const result = normalizeTags(['new', 'via', 'raw', 'v1.2'], CANONICAL, 'suggest');
|
|
193
|
+
expect(result).toEqual([]);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('in off mode, returns tags unchanged', () => {
|
|
197
|
+
const input = ['new', 'workflows', 'v1.2'];
|
|
198
|
+
const result = normalizeTags(input, CANONICAL, 'off');
|
|
199
|
+
expect(result).toEqual(input);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('batch normalizes a mixed tag list', () => {
|
|
203
|
+
const result = normalizeTags(
|
|
204
|
+
['TypeScript', 'workflows', 'new', 'source:article'],
|
|
205
|
+
CANONICAL,
|
|
206
|
+
'suggest',
|
|
207
|
+
['source:'],
|
|
208
|
+
);
|
|
209
|
+
expect(result).toContain('typescript');
|
|
210
|
+
expect(result).toContain('workflow');
|
|
211
|
+
expect(result).toContain('source:article');
|
|
212
|
+
expect(result).not.toContain('new');
|
|
213
|
+
});
|
|
214
|
+
});
|