@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
package/src/curator/curator.ts
CHANGED
|
@@ -40,6 +40,10 @@ import {
|
|
|
40
40
|
import { initializeTables } from './schema.js';
|
|
41
41
|
import { computeHealthAudit, type HealthDataProvider } from './health-audit.js';
|
|
42
42
|
import { enrichEntryMetadata } from './metadata-enricher.js';
|
|
43
|
+
import {
|
|
44
|
+
computeEditDistance,
|
|
45
|
+
normalizeTags as normalizeTagsCanonical,
|
|
46
|
+
} from '../vault/tag-normalizer.js';
|
|
43
47
|
|
|
44
48
|
// ─── Constants ──────────────────────────────────────────────────────
|
|
45
49
|
|
|
@@ -359,15 +363,141 @@ export class Curator {
|
|
|
359
363
|
if (batch.length < DEFAULT_BATCH_SIZE) break;
|
|
360
364
|
offset += DEFAULT_BATCH_SIZE;
|
|
361
365
|
}
|
|
366
|
+
|
|
367
|
+
// Synonym merge: detect tag pairs with edit-distance ≤ 1 and merge lower-frequency into higher
|
|
368
|
+
const synonymMerges = this.mergeSynonymTags();
|
|
369
|
+
|
|
362
370
|
return {
|
|
363
371
|
totalEntries,
|
|
364
372
|
groomedCount: totalEntries,
|
|
365
373
|
tagsNormalized,
|
|
366
374
|
staleCount,
|
|
367
375
|
durationMs: Date.now() - start,
|
|
376
|
+
synonymMerges,
|
|
368
377
|
};
|
|
369
378
|
}
|
|
370
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Detect tag pairs where edit-distance ≤ 1 (e.g. 'workflow'/'workflows') and merge
|
|
382
|
+
* the lower-frequency tag into the higher-frequency one across all entries.
|
|
383
|
+
* Returns count of tags merged.
|
|
384
|
+
*/
|
|
385
|
+
private mergeSynonymTags(): number {
|
|
386
|
+
// Collect all unique tags and their usage counts
|
|
387
|
+
const rows = this.provider.all<{ tags: string }>(
|
|
388
|
+
'SELECT tags FROM entries WHERE tags IS NOT NULL',
|
|
389
|
+
);
|
|
390
|
+
const tagCounts = new Map<string, number>();
|
|
391
|
+
|
|
392
|
+
for (const row of rows) {
|
|
393
|
+
let tags: string[];
|
|
394
|
+
try {
|
|
395
|
+
tags = JSON.parse(row.tags) as string[];
|
|
396
|
+
} catch {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
for (const tag of tags) {
|
|
400
|
+
if (typeof tag === 'string' && tag.length > 0) {
|
|
401
|
+
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const allTags = Array.from(tagCounts.keys());
|
|
407
|
+
if (allTags.length < 2) return 0;
|
|
408
|
+
|
|
409
|
+
// Build synonym merge map: minorTag → majorTag
|
|
410
|
+
// Only merge if edit-distance ≤ 1 and major has higher or equal frequency
|
|
411
|
+
const mergeMap = new Map<string, string>(); // minor → major
|
|
412
|
+
const processed = new Set<string>();
|
|
413
|
+
|
|
414
|
+
// Bucket tags by length to reduce comparisons from O(n²) to O(n * avg_bucket_size)
|
|
415
|
+
const buckets = new Map<number, string[]>();
|
|
416
|
+
for (const tag of allTags) {
|
|
417
|
+
const len = tag.length;
|
|
418
|
+
const bucket = buckets.get(len);
|
|
419
|
+
if (bucket) {
|
|
420
|
+
bucket.push(tag);
|
|
421
|
+
} else {
|
|
422
|
+
buckets.set(len, [tag]);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
for (const a of allTags) {
|
|
427
|
+
if (processed.has(a)) continue;
|
|
428
|
+
// Only compare against tags of the same or adjacent length (edit distance ≤ 1)
|
|
429
|
+
const candidates: string[] = [
|
|
430
|
+
...(buckets.get(a.length) ?? []),
|
|
431
|
+
...(buckets.get(a.length - 1) ?? []),
|
|
432
|
+
...(buckets.get(a.length + 1) ?? []),
|
|
433
|
+
];
|
|
434
|
+
for (const b of candidates) {
|
|
435
|
+
if (b === a) continue;
|
|
436
|
+
if (processed.has(a) || processed.has(b)) continue;
|
|
437
|
+
if (computeEditDistance(a, b) <= 1) {
|
|
438
|
+
const countA = tagCounts.get(a) ?? 0;
|
|
439
|
+
const countB = tagCounts.get(b) ?? 0;
|
|
440
|
+
// Merge lower-frequency into higher-frequency
|
|
441
|
+
if (countA >= countB) {
|
|
442
|
+
mergeMap.set(b, a);
|
|
443
|
+
processed.add(b);
|
|
444
|
+
} else {
|
|
445
|
+
mergeMap.set(a, b);
|
|
446
|
+
processed.add(a);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (mergeMap.size === 0) return 0;
|
|
453
|
+
|
|
454
|
+
// Apply merges to all affected entries
|
|
455
|
+
let mergeCount = 0;
|
|
456
|
+
const allEntryRows = this.provider.all<{ id: string; tags: string }>(
|
|
457
|
+
'SELECT id, tags FROM entries WHERE tags IS NOT NULL',
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
for (const row of allEntryRows) {
|
|
461
|
+
let tags: string[];
|
|
462
|
+
try {
|
|
463
|
+
tags = JSON.parse(row.tags) as string[];
|
|
464
|
+
} catch {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
let changed = false;
|
|
469
|
+
const updated = [
|
|
470
|
+
...new Set(
|
|
471
|
+
tags.map((tag) => {
|
|
472
|
+
const replacement = mergeMap.get(tag);
|
|
473
|
+
if (replacement) {
|
|
474
|
+
changed = true;
|
|
475
|
+
return replacement;
|
|
476
|
+
}
|
|
477
|
+
return tag;
|
|
478
|
+
}),
|
|
479
|
+
),
|
|
480
|
+
];
|
|
481
|
+
|
|
482
|
+
if (changed) {
|
|
483
|
+
this.provider.run('UPDATE entries SET tags = ?, updated_at = unixepoch() WHERE id = ?', [
|
|
484
|
+
JSON.stringify(updated),
|
|
485
|
+
row.id,
|
|
486
|
+
]);
|
|
487
|
+
this.logChange(
|
|
488
|
+
'synonym_merge',
|
|
489
|
+
row.id,
|
|
490
|
+
JSON.stringify(tags),
|
|
491
|
+
JSON.stringify(updated),
|
|
492
|
+
'Synonym tag merge (edit-distance ≤ 1)',
|
|
493
|
+
);
|
|
494
|
+
mergeCount++;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return mergeCount;
|
|
499
|
+
}
|
|
500
|
+
|
|
371
501
|
// ─── Consolidation ───────────────────────────────────────────
|
|
372
502
|
|
|
373
503
|
consolidate(options?: ConsolidationOptions): ConsolidationResult {
|
|
@@ -419,6 +549,55 @@ export class Curator {
|
|
|
419
549
|
}
|
|
420
550
|
}
|
|
421
551
|
}
|
|
552
|
+
|
|
553
|
+
// Retag: run all entries through canonical normalization if requested
|
|
554
|
+
let retagged: number | undefined;
|
|
555
|
+
if (options?.retag && options.canonicalTags && options.canonicalTags.length > 0) {
|
|
556
|
+
const tagMode = options.tagConstraintMode ?? 'suggest';
|
|
557
|
+
const metaPrefixes = options.metadataTagPrefixes ?? ['source:'];
|
|
558
|
+
retagged = 0;
|
|
559
|
+
|
|
560
|
+
const entryRows = this.provider.all<{ id: string; tags: string }>(
|
|
561
|
+
'SELECT id, tags FROM entries WHERE tags IS NOT NULL',
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
for (const row of entryRows) {
|
|
565
|
+
let tags: string[];
|
|
566
|
+
try {
|
|
567
|
+
tags = JSON.parse(row.tags) as string[];
|
|
568
|
+
} catch {
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const normalized = normalizeTagsCanonical(
|
|
573
|
+
tags,
|
|
574
|
+
options.canonicalTags,
|
|
575
|
+
tagMode,
|
|
576
|
+
metaPrefixes,
|
|
577
|
+
);
|
|
578
|
+
const tagsChanged =
|
|
579
|
+
normalized.length !== tags.length || normalized.some((t, i) => t !== tags[i]);
|
|
580
|
+
|
|
581
|
+
if (tagsChanged) {
|
|
582
|
+
if (!dryRun) {
|
|
583
|
+
this.provider.run(
|
|
584
|
+
'UPDATE entries SET tags = ?, updated_at = unixepoch() WHERE id = ?',
|
|
585
|
+
[JSON.stringify(normalized), row.id],
|
|
586
|
+
);
|
|
587
|
+
this.logChange(
|
|
588
|
+
'retag',
|
|
589
|
+
row.id,
|
|
590
|
+
JSON.stringify(tags),
|
|
591
|
+
JSON.stringify(normalized),
|
|
592
|
+
'Canonical retag during consolidation',
|
|
593
|
+
);
|
|
594
|
+
mutations++;
|
|
595
|
+
}
|
|
596
|
+
retagged++;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
422
601
|
return {
|
|
423
602
|
dryRun,
|
|
424
603
|
duplicates,
|
|
@@ -426,6 +605,7 @@ export class Curator {
|
|
|
426
605
|
contradictions,
|
|
427
606
|
mutations,
|
|
428
607
|
durationMs: Date.now() - start,
|
|
608
|
+
retagged,
|
|
429
609
|
};
|
|
430
610
|
}
|
|
431
611
|
|
|
@@ -41,10 +41,6 @@ describe('tag-manager', () => {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
describe('DEFAULT_TAG_ALIASES', () => {
|
|
44
|
-
it('exports expected alias count', () => {
|
|
45
|
-
expect(DEFAULT_TAG_ALIASES.length).toBe(13);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
44
|
it('includes common aliases', () => {
|
|
49
45
|
const aliasMap = new Map(DEFAULT_TAG_ALIASES);
|
|
50
46
|
expect(aliasMap.get('a11y')).toBe('accessibility');
|
package/src/curator/types.ts
CHANGED
|
@@ -62,6 +62,7 @@ export interface GroomAllResult {
|
|
|
62
62
|
tagsNormalized: number;
|
|
63
63
|
staleCount: number;
|
|
64
64
|
durationMs: number;
|
|
65
|
+
synonymMerges: number;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
// ─── Consolidation ──────────────────────────────────────────────────
|
|
@@ -71,6 +72,14 @@ export interface ConsolidationOptions {
|
|
|
71
72
|
staleDaysThreshold?: number;
|
|
72
73
|
duplicateThreshold?: number;
|
|
73
74
|
contradictionThreshold?: number;
|
|
75
|
+
/** When true, run all entries through canonical tag normalization. Dry-run by default. */
|
|
76
|
+
retag?: boolean;
|
|
77
|
+
/** Canonical tag list for retag operation. Required when retag is true. */
|
|
78
|
+
canonicalTags?: string[];
|
|
79
|
+
/** Tag constraint mode for retag. Default: 'suggest'. */
|
|
80
|
+
tagConstraintMode?: 'enforce' | 'suggest' | 'off';
|
|
81
|
+
/** Metadata tag prefixes exempt from canonical normalization. Default: ['source:']. */
|
|
82
|
+
metadataTagPrefixes?: string[];
|
|
74
83
|
}
|
|
75
84
|
|
|
76
85
|
export interface ConsolidationResult {
|
|
@@ -80,6 +89,7 @@ export interface ConsolidationResult {
|
|
|
80
89
|
contradictions: Contradiction[];
|
|
81
90
|
mutations: number;
|
|
82
91
|
durationMs: number;
|
|
92
|
+
retagged?: number;
|
|
83
93
|
}
|
|
84
94
|
|
|
85
95
|
// ─── Changelog & Health ─────────────────────────────────────────────
|
|
@@ -118,11 +118,6 @@ describe('validateDomainPack', () => {
|
|
|
118
118
|
});
|
|
119
119
|
|
|
120
120
|
describe('SEMANTIC_FACADE_NAMES', () => {
|
|
121
|
-
it('is a readonly array (TypeScript enforced)', () => {
|
|
122
|
-
expect(Array.isArray(SEMANTIC_FACADE_NAMES)).toBe(true);
|
|
123
|
-
expect(SEMANTIC_FACADE_NAMES.length).toBeGreaterThan(0);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
121
|
it('contains all core engine facades', () => {
|
|
127
122
|
const expected = [
|
|
128
123
|
'vault',
|
package/src/dream/dream.test.ts
CHANGED
|
@@ -134,13 +134,6 @@ describe('dream ops', () => {
|
|
|
134
134
|
vault.close();
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
-
it('creates 3 ops with correct names', () => {
|
|
138
|
-
expect(ops).toHaveLength(3);
|
|
139
|
-
expect(ops.map((o) => o.name).sort()).toEqual(
|
|
140
|
-
['dream_check_gate', 'dream_run', 'dream_status'].sort(),
|
|
141
|
-
);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
137
|
it('dream_status returns status', async () => {
|
|
145
138
|
const result = (await findOp('dream_status').handler({})) as Record<string, unknown>;
|
|
146
139
|
expect(result).toHaveProperty('sessionsSinceLastDream');
|
|
@@ -136,7 +136,7 @@ describe('EnforcementRegistry', () => {
|
|
|
136
136
|
const result = registry.translate('claude-code');
|
|
137
137
|
expect(result.host).toBe('claude-code');
|
|
138
138
|
// Only r1 should be translated (r2 is disabled)
|
|
139
|
-
expect(result.files.length).
|
|
139
|
+
expect(result.files.length).toBe(1);
|
|
140
140
|
});
|
|
141
141
|
});
|
|
142
142
|
|
|
@@ -250,7 +250,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
250
250
|
makeRule({ id: 'bad', trigger: 'on-save' }),
|
|
251
251
|
],
|
|
252
252
|
});
|
|
253
|
-
expect(result.files.length).
|
|
253
|
+
expect(result.files.length).toBe(1);
|
|
254
254
|
expect(result.skipped).toHaveLength(1);
|
|
255
255
|
expect(result.skipped[0].ruleId).toBe('bad');
|
|
256
256
|
});
|
|
@@ -38,24 +38,6 @@ afterAll(() => {
|
|
|
38
38
|
runtime.close();
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
describe('createCoreOps', () => {
|
|
42
|
-
it('returns exactly 5 ops', () => {
|
|
43
|
-
const opDefs = createCoreOps(runtime, TEST_IDENTITY);
|
|
44
|
-
expect(opDefs).toHaveLength(5);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('returns ops with expected names', () => {
|
|
48
|
-
const names = [...ops.keys()];
|
|
49
|
-
expect(names).toEqual(['health', 'identity', 'activate', 'session_start', 'setup']);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('all ops have read or write auth level', () => {
|
|
53
|
-
for (const op of ops.values()) {
|
|
54
|
-
expect(['read', 'write']).toContain(op.auth);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
41
|
describe('health op', () => {
|
|
60
42
|
it('returns status ok with agent info', async () => {
|
|
61
43
|
const result = await executeOp(ops, 'health');
|
|
@@ -75,7 +57,7 @@ describe('health op', () => {
|
|
|
75
57
|
const data = result.data as Record<string, unknown>;
|
|
76
58
|
const vault = data.vault as Record<string, unknown>;
|
|
77
59
|
expect(typeof vault.entries).toBe('number');
|
|
78
|
-
expect(
|
|
60
|
+
expect(vault.domains).toEqual([]);
|
|
79
61
|
});
|
|
80
62
|
|
|
81
63
|
it('has read auth level', () => {
|
|
@@ -122,7 +104,7 @@ describe('activate op', () => {
|
|
|
122
104
|
const vault = data.vault as Record<string, unknown>;
|
|
123
105
|
expect(vault.connected).toBe(true);
|
|
124
106
|
expect(typeof vault.entries).toBe('number');
|
|
125
|
-
expect(
|
|
107
|
+
expect(vault.domains).toEqual([]);
|
|
126
108
|
});
|
|
127
109
|
|
|
128
110
|
it('returns deactivation response when deactivate=true', async () => {
|
|
@@ -202,8 +184,8 @@ describe('setup op', () => {
|
|
|
202
184
|
const data = result.data as Record<string, unknown>;
|
|
203
185
|
const vault = data.vault as Record<string, unknown>;
|
|
204
186
|
expect(typeof vault.entries).toBe('number');
|
|
205
|
-
expect(
|
|
206
|
-
expect(vault.byType).
|
|
187
|
+
expect(vault.domains).toEqual([]);
|
|
188
|
+
expect(vault.byType).toEqual({});
|
|
207
189
|
});
|
|
208
190
|
|
|
209
191
|
it('recommends action when vault is empty', async () => {
|
package/src/engine/core-ops.ts
CHANGED
|
@@ -8,10 +8,31 @@
|
|
|
8
8
|
* Now they're created dynamically by the engine at startup.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
11
14
|
import { z } from 'zod';
|
|
12
15
|
import type { OpDefinition } from '../facades/types.js';
|
|
13
16
|
import type { AgentRuntime } from '../runtime/types.js';
|
|
14
17
|
|
|
18
|
+
function getCoreVersion(): string {
|
|
19
|
+
try {
|
|
20
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
let dir = thisDir;
|
|
22
|
+
for (let i = 0; i < 5; i++) {
|
|
23
|
+
try {
|
|
24
|
+
const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
|
|
25
|
+
return pkg.version ?? 'unknown';
|
|
26
|
+
} catch {
|
|
27
|
+
dir = dirname(dir);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// import.meta.url unavailable in some test envs
|
|
32
|
+
}
|
|
33
|
+
return 'unknown';
|
|
34
|
+
}
|
|
35
|
+
|
|
15
36
|
export interface AgentIdentityConfig {
|
|
16
37
|
id: string;
|
|
17
38
|
name: string;
|
|
@@ -38,10 +59,24 @@ export function createCoreOps(
|
|
|
38
59
|
auth: 'read',
|
|
39
60
|
handler: async () => {
|
|
40
61
|
const s = runtime.vault.stats();
|
|
62
|
+
let vaultConnected = true;
|
|
63
|
+
try {
|
|
64
|
+
runtime.vault.stats();
|
|
65
|
+
} catch {
|
|
66
|
+
vaultConnected = false;
|
|
67
|
+
}
|
|
68
|
+
const runtimeAny = runtime as unknown as Record<string, unknown>;
|
|
69
|
+
const brainReady = typeof runtimeAny.brain === 'object' && runtimeAny.brain !== null;
|
|
41
70
|
return {
|
|
42
71
|
status: 'ok',
|
|
72
|
+
version: getCoreVersion(),
|
|
43
73
|
agent: { name: identity.name, role: identity.role, format: 'filetree' },
|
|
44
|
-
vault: {
|
|
74
|
+
vault: {
|
|
75
|
+
connected: vaultConnected,
|
|
76
|
+
entries: s.totalEntries,
|
|
77
|
+
domains: Object.keys(s.byDomain),
|
|
78
|
+
},
|
|
79
|
+
brain: { ready: brainReady },
|
|
45
80
|
};
|
|
46
81
|
},
|
|
47
82
|
},
|
|
@@ -6,12 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { describe, it, expect } from 'vitest';
|
|
9
|
-
import {
|
|
10
|
-
ENGINE_MODULE_MANIFEST,
|
|
11
|
-
CORE_KEY_OPS,
|
|
12
|
-
ENGINE_MAJOR_VERSION,
|
|
13
|
-
type ModuleManifestEntry,
|
|
14
|
-
} from './module-manifest.js';
|
|
9
|
+
import { ENGINE_MODULE_MANIFEST, CORE_KEY_OPS, ENGINE_MAJOR_VERSION } from './module-manifest.js';
|
|
15
10
|
|
|
16
11
|
describe('ENGINE_MODULE_MANIFEST', () => {
|
|
17
12
|
it('contains all expected engine modules', () => {
|
|
@@ -32,10 +27,6 @@ describe('ENGINE_MODULE_MANIFEST', () => {
|
|
|
32
27
|
expect(suffixes).toContain('intake');
|
|
33
28
|
});
|
|
34
29
|
|
|
35
|
-
it('has exactly 22 modules', () => {
|
|
36
|
-
expect(ENGINE_MODULE_MANIFEST).toHaveLength(22);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
30
|
it('has no duplicate suffixes', () => {
|
|
40
31
|
const suffixes = ENGINE_MODULE_MANIFEST.map((m) => m.suffix);
|
|
41
32
|
expect(new Set(suffixes).size).toBe(suffixes.length);
|
|
@@ -86,16 +77,6 @@ describe('ENGINE_MODULE_MANIFEST', () => {
|
|
|
86
77
|
}
|
|
87
78
|
});
|
|
88
79
|
|
|
89
|
-
it('satisfies ModuleManifestEntry interface shape', () => {
|
|
90
|
-
const testEntry: ModuleManifestEntry = {
|
|
91
|
-
suffix: 'test',
|
|
92
|
-
description: 'Test module',
|
|
93
|
-
keyOps: ['op1'],
|
|
94
|
-
};
|
|
95
|
-
expect(testEntry.suffix).toBe('test');
|
|
96
|
-
expect(testEntry.conditional).toBeUndefined();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
80
|
it('intentSignals is optional and a Record<string, string> when present', () => {
|
|
100
81
|
for (const entry of ENGINE_MODULE_MANIFEST) {
|
|
101
82
|
if (entry.intentSignals !== undefined) {
|
|
@@ -144,20 +125,9 @@ describe('CORE_KEY_OPS', () => {
|
|
|
144
125
|
it('contains the 4 core ops', () => {
|
|
145
126
|
expect(CORE_KEY_OPS).toEqual(['health', 'identity', 'session_start', 'activate']);
|
|
146
127
|
});
|
|
147
|
-
|
|
148
|
-
it('is a string array', () => {
|
|
149
|
-
for (const op of CORE_KEY_OPS) {
|
|
150
|
-
expect(typeof op).toBe('string');
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
128
|
});
|
|
154
129
|
|
|
155
130
|
describe('ENGINE_MAJOR_VERSION', () => {
|
|
156
|
-
it('is a positive integer', () => {
|
|
157
|
-
expect(Number.isInteger(ENGINE_MAJOR_VERSION)).toBe(true);
|
|
158
|
-
expect(ENGINE_MAJOR_VERSION).toBeGreaterThan(0);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
131
|
it('is currently version 9', () => {
|
|
162
132
|
expect(ENGINE_MAJOR_VERSION).toBe(9);
|
|
163
133
|
});
|
|
@@ -63,10 +63,6 @@ describe('registerEngine — module completeness', () => {
|
|
|
63
63
|
expect(moduleSuffixes).toEqual(manifestSuffixes);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
it('ENGINE_MODULES and manifest have same count', () => {
|
|
67
|
-
expect(ENGINE_MODULES.length).toBe(ENGINE_MODULE_MANIFEST.length);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
66
|
it('registers all unconditional modules', () => {
|
|
71
67
|
const server = makeServer();
|
|
72
68
|
const result = registerEngine(server, runtime, { agentId: 'check' });
|
|
@@ -203,10 +199,9 @@ describe('registerEngine — return value', () => {
|
|
|
203
199
|
it('returns tools array, totalOps count, and registerTool function', () => {
|
|
204
200
|
const server = makeServer();
|
|
205
201
|
const result = registerEngine(server, runtime, { agentId: 'ret' });
|
|
206
|
-
expect(
|
|
202
|
+
expect(result.tools.length).toBeGreaterThan(0);
|
|
207
203
|
expect(typeof result.totalOps).toBe('number');
|
|
208
204
|
expect(typeof result.registerTool).toBe('function');
|
|
209
|
-
expect(result.totalOps).toBeGreaterThan(0);
|
|
210
205
|
});
|
|
211
206
|
|
|
212
207
|
it('registerTool adds a new tool at runtime', () => {
|
|
@@ -247,8 +242,8 @@ describe('registerEngine — op visibility', () => {
|
|
|
247
242
|
expect(INTERNAL_OPS.has('create_plan')).toBe(false);
|
|
248
243
|
});
|
|
249
244
|
|
|
250
|
-
it('INTERNAL_OPS has
|
|
251
|
-
expect(INTERNAL_OPS.size).
|
|
245
|
+
it('INTERNAL_OPS has exactly 29 entries', () => {
|
|
246
|
+
expect(INTERNAL_OPS.size).toBe(29);
|
|
252
247
|
});
|
|
253
248
|
|
|
254
249
|
it('ops without visibility field default to user (backward compat)', () => {
|
|
@@ -266,31 +261,6 @@ describe('registerEngine — op visibility', () => {
|
|
|
266
261
|
expect(result.tools).toContain('vis_test');
|
|
267
262
|
});
|
|
268
263
|
|
|
269
|
-
it('ops with visibility: internal are excluded from MCP tool description but remain callable', () => {
|
|
270
|
-
const server = makeServer();
|
|
271
|
-
const visibleOp: OpDefinition = {
|
|
272
|
-
name: 'public_op',
|
|
273
|
-
description: 'Public op',
|
|
274
|
-
auth: 'read',
|
|
275
|
-
handler: async () => 'visible',
|
|
276
|
-
};
|
|
277
|
-
const internalOp: OpDefinition = {
|
|
278
|
-
name: 'secret_op',
|
|
279
|
-
description: 'Internal op',
|
|
280
|
-
auth: 'admin',
|
|
281
|
-
visibility: 'internal',
|
|
282
|
-
handler: async () => 'hidden',
|
|
283
|
-
};
|
|
284
|
-
// Register both ops under a pack facade
|
|
285
|
-
registerEngine(server, runtime, {
|
|
286
|
-
agentId: 'vt',
|
|
287
|
-
domainPacks: [{ name: 'test', facades: [{ name: 'check', ops: [visibleOp, internalOp] }] }],
|
|
288
|
-
});
|
|
289
|
-
// We can't easily inspect the MCP schema description string, but we verify
|
|
290
|
-
// that registration succeeds with mixed visibility ops
|
|
291
|
-
expect(true).toBe(true);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
264
|
it('every INTERNAL_OPS entry corresponds to a real op in some facade', () => {
|
|
295
265
|
// Collect all op names across all engine modules
|
|
296
266
|
const allOpNames = new Set<string>();
|
package/src/errors/retry.test.ts
CHANGED
|
@@ -33,9 +33,11 @@ describe('shouldRetry', () => {
|
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
describe('getRetryDelay', () => {
|
|
36
|
-
it('should return a
|
|
36
|
+
it('should return a non-negative number', () => {
|
|
37
37
|
const delay = getRetryDelay(0, 'fast');
|
|
38
|
+
expect(typeof delay).toBe('number');
|
|
38
39
|
expect(delay).toBeGreaterThanOrEqual(0);
|
|
40
|
+
expect(delay).toBeLessThanOrEqual(RETRY_PRESETS.fast.maxIntervalMs * 1.25);
|
|
39
41
|
});
|
|
40
42
|
|
|
41
43
|
it('should increase with attempt number', () => {
|
|
@@ -98,12 +98,6 @@ describe('ChainRunner', () => {
|
|
|
98
98
|
runner = new ChainRunner(provider);
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
it('initializes the chain_instances table on construction', () => {
|
|
102
|
-
expect(provider.execSql).toHaveBeenCalledWith(
|
|
103
|
-
expect.stringContaining('CREATE TABLE IF NOT EXISTS chain_instances'),
|
|
104
|
-
);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
101
|
describe('execute', () => {
|
|
108
102
|
it('runs all steps to completion', async () => {
|
|
109
103
|
const dispatch: DispatchFn = vi.fn(async () => ({ result: 'ok' }));
|
|
@@ -11,7 +11,7 @@ import { getFlowOverrides, detectContext } from './context-router.js';
|
|
|
11
11
|
describe('getFlowOverrides', () => {
|
|
12
12
|
it('returns overrides for BUILD-flow', () => {
|
|
13
13
|
const overrides = getFlowOverrides('BUILD-flow');
|
|
14
|
-
expect(overrides
|
|
14
|
+
expect(overrides).toHaveLength(4);
|
|
15
15
|
const contexts = overrides.map((o) => o.context);
|
|
16
16
|
expect(contexts).toContain('small-component');
|
|
17
17
|
expect(contexts).toContain('large-component');
|
|
@@ -19,7 +19,7 @@ describe('getFlowOverrides', () => {
|
|
|
19
19
|
|
|
20
20
|
it('returns overrides for FIX-flow', () => {
|
|
21
21
|
const overrides = getFlowOverrides('FIX-flow');
|
|
22
|
-
expect(overrides
|
|
22
|
+
expect(overrides).toHaveLength(2);
|
|
23
23
|
const contexts = overrides.map((o) => o.context);
|
|
24
24
|
expect(contexts).toContain('design-fix');
|
|
25
25
|
expect(contexts).toContain('a11y-fix');
|
|
@@ -27,7 +27,7 @@ describe('getFlowOverrides', () => {
|
|
|
27
27
|
|
|
28
28
|
it('returns overrides for REVIEW-flow', () => {
|
|
29
29
|
const overrides = getFlowOverrides('REVIEW-flow');
|
|
30
|
-
expect(overrides
|
|
30
|
+
expect(overrides).toHaveLength(2);
|
|
31
31
|
const contexts = overrides.map((o) => o.context);
|
|
32
32
|
expect(contexts).toContain('pr-review');
|
|
33
33
|
expect(contexts).toContain('architecture-review');
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
* Epilogue — colocated contract tests.
|
|
3
3
|
*
|
|
4
4
|
* Contract:
|
|
5
|
-
* - runEpilogue() calls capture_knowledge when vault is available
|
|
5
|
+
* - runEpilogue() calls capture_knowledge with intent-specific title when vault is available
|
|
6
6
|
* - runEpilogue() calls session_capture when sessionStore is available
|
|
7
7
|
* - Returns { captured: true, sessionId } on success
|
|
8
8
|
* - Silently ignores errors from dispatch (best-effort)
|
|
9
9
|
* - Returns { captured: false } when no probes are available
|
|
10
|
+
* - Title format: "{INTENT} execution — {objective}" (max 120 chars)
|
|
11
|
+
* - Tags include intent (lowercase) and domain (if provided)
|
|
10
12
|
*/
|
|
11
13
|
|
|
12
14
|
import { describe, it, expect, vi } from 'vitest';
|
|
@@ -33,7 +35,6 @@ describe('runEpilogue', () => {
|
|
|
33
35
|
expect(dispatch).toHaveBeenCalledWith(
|
|
34
36
|
'capture_knowledge',
|
|
35
37
|
expect.objectContaining({
|
|
36
|
-
title: 'Flow execution summary',
|
|
37
38
|
content: 'summary',
|
|
38
39
|
type: 'workflow',
|
|
39
40
|
projectPath: '/project',
|
|
@@ -42,6 +43,43 @@ describe('runEpilogue', () => {
|
|
|
42
43
|
expect(result.captured).toBe(true);
|
|
43
44
|
});
|
|
44
45
|
|
|
46
|
+
it('uses intent-specific title when planContext is provided', async () => {
|
|
47
|
+
const dispatch = vi.fn(async () => ({ tool: 'capture_knowledge', status: 'ok', data: {} }));
|
|
48
|
+
await runEpilogue(dispatch, probes({ vault: true }), '/project', 'summary', {
|
|
49
|
+
intent: 'BUILD',
|
|
50
|
+
objective: 'add authentication module',
|
|
51
|
+
domain: 'auth',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(dispatch).toHaveBeenCalledWith(
|
|
55
|
+
'capture_knowledge',
|
|
56
|
+
expect.objectContaining({
|
|
57
|
+
title: 'BUILD execution — add authentication module',
|
|
58
|
+
tags: expect.arrayContaining(['auto-captured', 'build', 'auth']),
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('falls back to FLOW intent when planContext is absent', async () => {
|
|
64
|
+
const dispatch = vi.fn(async () => ({ tool: 'capture_knowledge', status: 'ok', data: {} }));
|
|
65
|
+
await runEpilogue(dispatch, probes({ vault: true }), '/project', 'done summary');
|
|
66
|
+
|
|
67
|
+
const call = dispatch.mock.calls[0]?.[1] as Record<string, unknown>;
|
|
68
|
+
expect((call.title as string).startsWith('FLOW execution —')).toBe(true);
|
|
69
|
+
expect((call.tags as string[]).includes('flow')).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('omits domain tag when domain is not provided', async () => {
|
|
73
|
+
const dispatch = vi.fn(async () => ({ tool: 'capture_knowledge', status: 'ok', data: {} }));
|
|
74
|
+
await runEpilogue(dispatch, probes({ vault: true }), '/project', 'summary', {
|
|
75
|
+
intent: 'FIX',
|
|
76
|
+
objective: 'fix login bug',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const call = dispatch.mock.calls[0]?.[1] as Record<string, unknown>;
|
|
80
|
+
expect(call.tags).toEqual(['auto-captured', 'fix']);
|
|
81
|
+
});
|
|
82
|
+
|
|
45
83
|
it('captures session when sessionStore is available', async () => {
|
|
46
84
|
const dispatch = vi.fn(async () => ({
|
|
47
85
|
tool: 'session_capture',
|