dna-sdk 0.1.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/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/adapters/embedding/onnx.d.ts +32 -0
- package/dist/adapters/embedding/onnx.js +51 -0
- package/dist/adapters/filesystem/cache.d.ts +16 -0
- package/dist/adapters/filesystem/cache.js +113 -0
- package/dist/adapters/filesystem/index.d.ts +2 -0
- package/dist/adapters/filesystem/index.js +2 -0
- package/dist/adapters/filesystem/source.d.ts +82 -0
- package/dist/adapters/filesystem/source.js +357 -0
- package/dist/adapters/postgres/index.d.ts +2 -0
- package/dist/adapters/postgres/index.js +2 -0
- package/dist/adapters/postgres/migrations.d.ts +14 -0
- package/dist/adapters/postgres/migrations.js +88 -0
- package/dist/adapters/postgres/source.d.ts +172 -0
- package/dist/adapters/postgres/source.js +569 -0
- package/dist/adapters/resolvers/github.d.ts +12 -0
- package/dist/adapters/resolvers/github.js +47 -0
- package/dist/adapters/resolvers/http.d.ts +22 -0
- package/dist/adapters/resolvers/http.js +145 -0
- package/dist/adapters/resolvers/index.d.ts +3 -0
- package/dist/adapters/resolvers/index.js +3 -0
- package/dist/adapters/resolvers/local.d.ts +24 -0
- package/dist/adapters/resolvers/local.js +132 -0
- package/dist/adapters/search/driver.d.ts +45 -0
- package/dist/adapters/search/driver.js +138 -0
- package/dist/adapters/search/migrations.d.ts +26 -0
- package/dist/adapters/search/migrations.js +58 -0
- package/dist/adapters/search/rrf.d.ts +23 -0
- package/dist/adapters/search/rrf.js +42 -0
- package/dist/adapters/search/sqlite-vec.d.ts +66 -0
- package/dist/adapters/search/sqlite-vec.js +294 -0
- package/dist/bootstrap.d.ts +45 -0
- package/dist/bootstrap.js +134 -0
- package/dist/extensions/agentskills/DOCS.md +18 -0
- package/dist/extensions/agentskills.d.ts +28 -0
- package/dist/extensions/agentskills.js +252 -0
- package/dist/extensions/agentsmd/DOCS.md +18 -0
- package/dist/extensions/agentsmd.d.ts +22 -0
- package/dist/extensions/agentsmd.js +170 -0
- package/dist/extensions/audit/kinds/audit-log.kind.yaml +148 -0
- package/dist/extensions/audit.d.ts +25 -0
- package/dist/extensions/audit.js +62 -0
- package/dist/extensions/automation/kinds/automation.kind.yaml +296 -0
- package/dist/extensions/automation/query.d.ts +49 -0
- package/dist/extensions/automation/query.js +77 -0
- package/dist/extensions/automation/write-guards.d.ts +59 -0
- package/dist/extensions/automation/write-guards.js +153 -0
- package/dist/extensions/automation.d.ts +41 -0
- package/dist/extensions/automation.js +54 -0
- package/dist/extensions/collab.d.ts +16 -0
- package/dist/extensions/collab.js +79 -0
- package/dist/extensions/doc/kinds/doc.kind.yaml +118 -0
- package/dist/extensions/doc.d.ts +25 -0
- package/dist/extensions/doc.js +10 -0
- package/dist/extensions/eval/kinds/eval-baseline.kind.yaml +65 -0
- package/dist/extensions/eval/kinds/eval-case.kind.yaml +140 -0
- package/dist/extensions/eval/kinds/eval-run.kind.yaml +134 -0
- package/dist/extensions/eval/kinds/eval-suite.kind.yaml +87 -0
- package/dist/extensions/eval.d.ts +24 -0
- package/dist/extensions/eval.js +31 -0
- package/dist/extensions/evidence/kinds/evidence.kind.yaml +92 -0
- package/dist/extensions/evidence.d.ts +17 -0
- package/dist/extensions/evidence.js +94 -0
- package/dist/extensions/federation.d.ts +22 -0
- package/dist/extensions/federation.js +173 -0
- package/dist/extensions/guardrails/DOCS.md +20 -0
- package/dist/extensions/guardrails.d.ts +31 -0
- package/dist/extensions/guardrails.js +228 -0
- package/dist/extensions/helix/DOCS-Actor.md +20 -0
- package/dist/extensions/helix/DOCS-Agent.md +20 -0
- package/dist/extensions/helix/DOCS-UseCase.md +20 -0
- package/dist/extensions/helix/layers.d.ts +12 -0
- package/dist/extensions/helix/layers.js +18 -0
- package/dist/extensions/helix/write-guards.d.ts +70 -0
- package/dist/extensions/helix/write-guards.js +172 -0
- package/dist/extensions/helix.d.ts +29 -0
- package/dist/extensions/helix.js +963 -0
- package/dist/extensions/helix_extras.d.ts +124 -0
- package/dist/extensions/helix_extras.js +328 -0
- package/dist/extensions/hooks.d.ts +31 -0
- package/dist/extensions/hooks.js +247 -0
- package/dist/extensions/kinddef/DOCS.md +24 -0
- package/dist/extensions/kinddef.d.ts +40 -0
- package/dist/extensions/kinddef.js +171 -0
- package/dist/extensions/lesson.d.ts +6 -0
- package/dist/extensions/lesson.js +194 -0
- package/dist/extensions/modelreg/kinds/model-profile.kind.yaml +155 -0
- package/dist/extensions/modelreg.d.ts +30 -0
- package/dist/extensions/modelreg.js +38 -0
- package/dist/extensions/recognizer.d.ts +34 -0
- package/dist/extensions/recognizer.js +272 -0
- package/dist/extensions/research.d.ts +32 -0
- package/dist/extensions/research.js +292 -0
- package/dist/extensions/safety.d.ts +33 -0
- package/dist/extensions/safety.js +230 -0
- package/dist/extensions/sdlc/kinds/adr.kind.yaml +136 -0
- package/dist/extensions/sdlc/kinds/archive-proposal.kind.yaml +107 -0
- package/dist/extensions/sdlc/kinds/changelog.kind.yaml +108 -0
- package/dist/extensions/sdlc/kinds/cognitive-policy.kind.yaml +470 -0
- package/dist/extensions/sdlc/kinds/forecast.kind.yaml +125 -0
- package/dist/extensions/sdlc/kinds/insight.kind.yaml +87 -0
- package/dist/extensions/sdlc/kinds/kaizen.kind.yaml +106 -0
- package/dist/extensions/sdlc/kinds/lesson-learned.kind.yaml +270 -0
- package/dist/extensions/sdlc/kinds/narrative.kind.yaml +189 -0
- package/dist/extensions/sdlc/kinds/pattern-insight.kind.yaml +204 -0
- package/dist/extensions/sdlc/kinds/postmortem.kind.yaml +168 -0
- package/dist/extensions/sdlc/kinds/pre-mortem.kind.yaml +145 -0
- package/dist/extensions/sdlc/kinds/prompt-template.kind.yaml +109 -0
- package/dist/extensions/sdlc/kinds/retrospective.kind.yaml +186 -0
- package/dist/extensions/sdlc/kinds/risk-register.kind.yaml +178 -0
- package/dist/extensions/sdlc/kinds/saved-view.kind.yaml +127 -0
- package/dist/extensions/sdlc/kinds/status-report.kind.yaml +129 -0
- package/dist/extensions/sdlc/kinds/synthesis-run.kind.yaml +279 -0
- package/dist/extensions/sdlc/kinds/synthesizer-state.kind.yaml +196 -0
- package/dist/extensions/sdlc/kinds/workflow-event.kind.yaml +195 -0
- package/dist/extensions/sdlc.d.ts +32 -0
- package/dist/extensions/sdlc.js +1318 -0
- package/dist/extensions/soulspec/DOCS.md +19 -0
- package/dist/extensions/soulspec.d.ts +22 -0
- package/dist/extensions/soulspec.js +287 -0
- package/dist/extensions/tenant.d.ts +6 -0
- package/dist/extensions/tenant.js +291 -0
- package/dist/extensions/testkit.d.ts +24 -0
- package/dist/extensions/testkit.js +211 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +102 -0
- package/dist/kernel/_text.d.ts +28 -0
- package/dist/kernel/_text.js +42 -0
- package/dist/kernel/bundle-handle.d.ts +83 -0
- package/dist/kernel/bundle-handle.js +184 -0
- package/dist/kernel/capabilities.d.ts +157 -0
- package/dist/kernel/capabilities.js +109 -0
- package/dist/kernel/collaborator-ports.d.ts +91 -0
- package/dist/kernel/collaborator-ports.js +1 -0
- package/dist/kernel/composition-resolver.d.ts +215 -0
- package/dist/kernel/composition-resolver.js +558 -0
- package/dist/kernel/composition.d.ts +10 -0
- package/dist/kernel/composition.js +1 -0
- package/dist/kernel/descriptor-loader.d.ts +13 -0
- package/dist/kernel/descriptor-loader.js +48 -0
- package/dist/kernel/document.d.ts +43 -0
- package/dist/kernel/document.js +71 -0
- package/dist/kernel/embedding.d.ts +28 -0
- package/dist/kernel/embedding.js +78 -0
- package/dist/kernel/errors.d.ts +62 -0
- package/dist/kernel/errors.js +82 -0
- package/dist/kernel/events.d.ts +6 -0
- package/dist/kernel/events.js +21 -0
- package/dist/kernel/evidence-capture.d.ts +69 -0
- package/dist/kernel/evidence-capture.js +138 -0
- package/dist/kernel/fs.d.ts +20 -0
- package/dist/kernel/fs.js +128 -0
- package/dist/kernel/generic-rw.d.ts +33 -0
- package/dist/kernel/generic-rw.js +235 -0
- package/dist/kernel/hooks.d.ts +172 -0
- package/dist/kernel/hooks.js +256 -0
- package/dist/kernel/index.d.ts +624 -0
- package/dist/kernel/index.js +1638 -0
- package/dist/kernel/instance.d.ts +211 -0
- package/dist/kernel/instance.js +487 -0
- package/dist/kernel/kind-registry.d.ts +183 -0
- package/dist/kernel/kind-registry.js +710 -0
- package/dist/kernel/kind_base.d.ts +106 -0
- package/dist/kernel/kind_base.js +244 -0
- package/dist/kernel/layer-resolver.d.ts +48 -0
- package/dist/kernel/layer-resolver.js +284 -0
- package/dist/kernel/lock-manager.d.ts +21 -0
- package/dist/kernel/lock-manager.js +43 -0
- package/dist/kernel/lock.d.ts +38 -0
- package/dist/kernel/lock.js +115 -0
- package/dist/kernel/meta.d.ts +135 -0
- package/dist/kernel/meta.js +817 -0
- package/dist/kernel/models.d.ts +3089 -0
- package/dist/kernel/models.js +764 -0
- package/dist/kernel/navigator.d.ts +36 -0
- package/dist/kernel/navigator.js +132 -0
- package/dist/kernel/port-surface.d.ts +33 -0
- package/dist/kernel/port-surface.js +180 -0
- package/dist/kernel/preview.d.ts +60 -0
- package/dist/kernel/preview.js +54 -0
- package/dist/kernel/prompt-budget.d.ts +39 -0
- package/dist/kernel/prompt-budget.js +43 -0
- package/dist/kernel/prompt-builder.d.ts +25 -0
- package/dist/kernel/prompt-builder.js +237 -0
- package/dist/kernel/protocols.d.ts +751 -0
- package/dist/kernel/protocols.js +493 -0
- package/dist/kernel/reports.d.ts +26 -0
- package/dist/kernel/reports.js +215 -0
- package/dist/kernel/resolver.d.ts +146 -0
- package/dist/kernel/resolver.js +232 -0
- package/dist/kernel/resource.d.ts +57 -0
- package/dist/kernel/resource.js +97 -0
- package/dist/kernel/runtime.d.ts +16 -0
- package/dist/kernel/runtime.js +18 -0
- package/dist/kernel/safety-scanner.d.ts +38 -0
- package/dist/kernel/safety-scanner.js +228 -0
- package/dist/kernel/serialize-to-files.d.ts +29 -0
- package/dist/kernel/serialize-to-files.js +23 -0
- package/dist/kernel/spec-access.d.ts +23 -0
- package/dist/kernel/spec-access.js +40 -0
- package/dist/kernel/studio_ui.d.ts +88 -0
- package/dist/kernel/studio_ui.js +135 -0
- package/dist/kernel/templates.d.ts +77 -0
- package/dist/kernel/templates.js +116 -0
- package/dist/kernel/tool-registry.d.ts +52 -0
- package/dist/kernel/tool-registry.js +89 -0
- package/dist/kernel/write-pipeline.d.ts +65 -0
- package/dist/kernel/write-pipeline.js +271 -0
- package/dist/kernel/writer-helpers.d.ts +24 -0
- package/dist/kernel/writer-helpers.js +42 -0
- package/dist/memory/decay.d.ts +31 -0
- package/dist/memory/decay.js +97 -0
- package/dist/memory/ecphory.d.ts +25 -0
- package/dist/memory/ecphory.js +178 -0
- package/dist/memory/encodingContext.d.ts +21 -0
- package/dist/memory/encodingContext.js +62 -0
- package/dist/memory/index.d.ts +23 -0
- package/dist/memory/index.js +23 -0
- package/dist/memory/memoryType.d.ts +7 -0
- package/dist/memory/memoryType.js +24 -0
- package/dist/memory/policy.d.ts +35 -0
- package/dist/memory/policy.js +35 -0
- package/dist/memory/retrieval.d.ts +21 -0
- package/dist/memory/retrieval.js +145 -0
- package/dist/testing/index.d.ts +8 -0
- package/dist/testing/index.js +8 -0
- package/dist/testing/recordSearchConformance.d.ts +43 -0
- package/dist/testing/recordSearchConformance.js +147 -0
- package/dist/viz/ascii.d.ts +7 -0
- package/dist/viz/ascii.js +60 -0
- package/dist/viz/health.d.ts +8 -0
- package/dist/viz/health.js +161 -0
- package/dist/viz/index.d.ts +10 -0
- package/dist/viz/index.js +10 -0
- package/dist/viz/matrix.d.ts +8 -0
- package/dist/viz/matrix.js +84 -0
- package/dist/viz/mermaid.d.ts +38 -0
- package/dist/viz/mermaid.js +706 -0
- package/package.json +82 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic encoding_context stamping — the engraphy conditions snapshot
|
|
3
|
+
* (TS twin of `dna/memory/encoding_context.py`). Encoding-specificity (Semon
|
|
4
|
+
* 1904): conditions at engraphy must be partially reinstated at ecphory.
|
|
5
|
+
*
|
|
6
|
+
* s-memory-verbs (2026-07-09).
|
|
7
|
+
*/
|
|
8
|
+
type Spec = Record<string, unknown>;
|
|
9
|
+
/** Map an hour to {morning, afternoon, evening, night}. */
|
|
10
|
+
export declare function timeOfDay(hour: number): string;
|
|
11
|
+
/** Build a deterministic encoding_context from the spec. Fresh object. */
|
|
12
|
+
export declare function deriveEncodingContext(spec: Spec, opts?: {
|
|
13
|
+
ambient?: Record<string, unknown>;
|
|
14
|
+
derivedMarker?: string;
|
|
15
|
+
}): Record<string, unknown>;
|
|
16
|
+
/** Mutate `spec` in place: add encoding_context if missing/empty. Idempotent. */
|
|
17
|
+
export declare function stampEncodingContextIfAbsent(spec: Spec, opts?: {
|
|
18
|
+
ambient?: Record<string, unknown>;
|
|
19
|
+
derivedMarker?: string;
|
|
20
|
+
}): Spec;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic encoding_context stamping — the engraphy conditions snapshot
|
|
3
|
+
* (TS twin of `dna/memory/encoding_context.py`). Encoding-specificity (Semon
|
|
4
|
+
* 1904): conditions at engraphy must be partially reinstated at ecphory.
|
|
5
|
+
*
|
|
6
|
+
* s-memory-verbs (2026-07-09).
|
|
7
|
+
*/
|
|
8
|
+
/** Map an hour to {morning, afternoon, evening, night}. */
|
|
9
|
+
export function timeOfDay(hour) {
|
|
10
|
+
if (hour >= 5 && hour < 12)
|
|
11
|
+
return "morning";
|
|
12
|
+
if (hour >= 12 && hour < 18)
|
|
13
|
+
return "afternoon";
|
|
14
|
+
if (hour >= 18 && hour < 22)
|
|
15
|
+
return "evening";
|
|
16
|
+
return "night";
|
|
17
|
+
}
|
|
18
|
+
function hourOfIso(value) {
|
|
19
|
+
if (typeof value !== "string" || !value)
|
|
20
|
+
return null;
|
|
21
|
+
const ms = Date.parse(value.replace("Z", "+00:00"));
|
|
22
|
+
if (Number.isNaN(ms))
|
|
23
|
+
return null;
|
|
24
|
+
return new Date(ms).getUTCHours();
|
|
25
|
+
}
|
|
26
|
+
/** Build a deterministic encoding_context from the spec. Fresh object. */
|
|
27
|
+
export function deriveEncodingContext(spec, opts = {}) {
|
|
28
|
+
const ambient = opts.ambient ?? {};
|
|
29
|
+
const derivedMarker = opts.derivedMarker ?? "verb-autostamp";
|
|
30
|
+
const hour = hourOfIso(spec.created_at) ?? new Date().getUTCHours();
|
|
31
|
+
const tod = ambient.time_of_day || timeOfDay(hour);
|
|
32
|
+
const specTags = (spec.tags ?? [])
|
|
33
|
+
.filter((t) => typeof t === "string" || typeof t === "number")
|
|
34
|
+
.map((t) => String(t));
|
|
35
|
+
const ambientTopics = (ambient.recent_turn_topics ?? [])
|
|
36
|
+
.filter((t) => typeof t === "string" || typeof t === "number")
|
|
37
|
+
.map((t) => String(t));
|
|
38
|
+
const seen = [];
|
|
39
|
+
for (const t of [...specTags, ...ambientTopics]) {
|
|
40
|
+
if (t && !seen.includes(t))
|
|
41
|
+
seen.push(t);
|
|
42
|
+
if (seen.length >= 5)
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
area: spec.area ?? "",
|
|
47
|
+
affect: ambient.affect ?? spec.affect ?? "neutral",
|
|
48
|
+
time_of_day: tod,
|
|
49
|
+
co_topics: seen,
|
|
50
|
+
source_refs: [...(spec.source_refs ?? [])],
|
|
51
|
+
_derived: derivedMarker,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Mutate `spec` in place: add encoding_context if missing/empty. Idempotent. */
|
|
55
|
+
export function stampEncodingContextIfAbsent(spec, opts = {}) {
|
|
56
|
+
const existing = spec.encoding_context;
|
|
57
|
+
if (existing && typeof existing === "object" && Object.keys(existing).length > 0) {
|
|
58
|
+
return spec;
|
|
59
|
+
}
|
|
60
|
+
spec.encoding_context = deriveEncodingContext(spec, opts);
|
|
61
|
+
return spec;
|
|
62
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dna/memory` — the deterministic memory-scoring core (TS twin).
|
|
3
|
+
*
|
|
4
|
+
* Memory in DNA is the Kinds the SDK already has (LessonLearned, Research,
|
|
5
|
+
* Evidence) recalled by the same RecordSearchProvider that powers search. This
|
|
6
|
+
* package holds the DETERMINISTIC pure scoring core ported from the upstream
|
|
7
|
+
* cognitive layer — ecphory (Semon's Law of Ecphory), BM25 retrieval,
|
|
8
|
+
* Ebbinghaus decay, encoding-context stamping, CoALA classification, and the
|
|
9
|
+
* recall/decay policy defaults. Parity with the Python twin
|
|
10
|
+
* (`dna/memory/*.py`) is pinned by `tests/fixtures/memory-scoring-parity.json`.
|
|
11
|
+
*
|
|
12
|
+
* The four VERBS (remember/recall/forget/consolidate) are kernel-bound and live
|
|
13
|
+
* on the Python side (the `dna` CLI); this twin ports the pure math the verbs
|
|
14
|
+
* compose, keeping Py↔TS parity of the scoring surface.
|
|
15
|
+
*
|
|
16
|
+
* s-memory-verbs (2026-07-09).
|
|
17
|
+
*/
|
|
18
|
+
export * from "./policy.js";
|
|
19
|
+
export * from "./decay.js";
|
|
20
|
+
export * from "./memoryType.js";
|
|
21
|
+
export * from "./encodingContext.js";
|
|
22
|
+
export * from "./retrieval.js";
|
|
23
|
+
export * from "./ecphory.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dna/memory` — the deterministic memory-scoring core (TS twin).
|
|
3
|
+
*
|
|
4
|
+
* Memory in DNA is the Kinds the SDK already has (LessonLearned, Research,
|
|
5
|
+
* Evidence) recalled by the same RecordSearchProvider that powers search. This
|
|
6
|
+
* package holds the DETERMINISTIC pure scoring core ported from the upstream
|
|
7
|
+
* cognitive layer — ecphory (Semon's Law of Ecphory), BM25 retrieval,
|
|
8
|
+
* Ebbinghaus decay, encoding-context stamping, CoALA classification, and the
|
|
9
|
+
* recall/decay policy defaults. Parity with the Python twin
|
|
10
|
+
* (`dna/memory/*.py`) is pinned by `tests/fixtures/memory-scoring-parity.json`.
|
|
11
|
+
*
|
|
12
|
+
* The four VERBS (remember/recall/forget/consolidate) are kernel-bound and live
|
|
13
|
+
* on the Python side (the `dna` CLI); this twin ports the pure math the verbs
|
|
14
|
+
* compose, keeping Py↔TS parity of the scoring surface.
|
|
15
|
+
*
|
|
16
|
+
* s-memory-verbs (2026-07-09).
|
|
17
|
+
*/
|
|
18
|
+
export * from "./policy.js";
|
|
19
|
+
export * from "./decay.js";
|
|
20
|
+
export * from "./memoryType.js";
|
|
21
|
+
export * from "./encodingContext.js";
|
|
22
|
+
export * from "./retrieval.js";
|
|
23
|
+
export * from "./ecphory.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CoALA memory_type inference — heuristic, pure, conservative (TS twin of
|
|
3
|
+
* `dna/memory/memory_type.py`). Never overwrites an explicit `memory_type`.
|
|
4
|
+
*
|
|
5
|
+
* s-memory-verbs (2026-07-09).
|
|
6
|
+
*/
|
|
7
|
+
export declare function classifyMemoryType(spec: Record<string, unknown>): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CoALA memory_type inference — heuristic, pure, conservative (TS twin of
|
|
3
|
+
* `dna/memory/memory_type.py`). Never overwrites an explicit `memory_type`.
|
|
4
|
+
*
|
|
5
|
+
* s-memory-verbs (2026-07-09).
|
|
6
|
+
*/
|
|
7
|
+
const RULE_WORDS = [
|
|
8
|
+
"always", "never", "must", "should", "don't", "do not", "ensure",
|
|
9
|
+
"sempre", "nunca", "deve", "não ", "garanta", "evite", "prefira",
|
|
10
|
+
];
|
|
11
|
+
const EPISODIC_AREAS = ["feature/", "epic/", "story/", "issue/", "roadmap/"];
|
|
12
|
+
export function classifyMemoryType(spec) {
|
|
13
|
+
const existing = spec.memory_type;
|
|
14
|
+
if (existing === "episodic" || existing === "semantic" || existing === "procedural") {
|
|
15
|
+
return existing;
|
|
16
|
+
}
|
|
17
|
+
const text = `${spec.summary ?? ""} ${spec.body ?? ""}`.toLowerCase();
|
|
18
|
+
if (RULE_WORDS.some((w) => text.includes(w)))
|
|
19
|
+
return "procedural";
|
|
20
|
+
const area = String(spec.area ?? "").toLowerCase();
|
|
21
|
+
if (EPISODIC_AREAS.some((p) => area.startsWith(p)))
|
|
22
|
+
return "episodic";
|
|
23
|
+
return "semantic";
|
|
24
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall/decay tuning knobs — pure declarative defaults (TS twin of
|
|
3
|
+
* `dna/memory/policy.py`). The kernel-reading resolvers are deliberately left
|
|
4
|
+
* behind (service concern); these defaults ARE the calibrated values and the
|
|
5
|
+
* pure scoring functions fall back to them.
|
|
6
|
+
*
|
|
7
|
+
* s-memory-verbs (2026-07-09). Parity-critical numeric constants.
|
|
8
|
+
*/
|
|
9
|
+
export interface RecallPolicy {
|
|
10
|
+
directThreshold: number;
|
|
11
|
+
cosineWeight: number;
|
|
12
|
+
contentWeight: number;
|
|
13
|
+
summaryPartialWeight: number;
|
|
14
|
+
coTopicsWeight: number;
|
|
15
|
+
sourceRefsWeight: number;
|
|
16
|
+
affectWeight: number;
|
|
17
|
+
timeWeight: number;
|
|
18
|
+
noveltyBoost: number;
|
|
19
|
+
recencyBoost: number;
|
|
20
|
+
saturationDecay: number;
|
|
21
|
+
saturationThreshold: number;
|
|
22
|
+
limitDirect: number;
|
|
23
|
+
limitHomophonic: number;
|
|
24
|
+
}
|
|
25
|
+
export declare const DEFAULT_RECALL_POLICY: RecallPolicy;
|
|
26
|
+
export interface DecayPolicy {
|
|
27
|
+
tierFaint: number;
|
|
28
|
+
tierFirm: number;
|
|
29
|
+
tierBurning: number;
|
|
30
|
+
defaultStabilityDays: number;
|
|
31
|
+
maxStabilityDays: number;
|
|
32
|
+
relevanceDecaySeed: number;
|
|
33
|
+
}
|
|
34
|
+
export declare const DEFAULT_DECAY_POLICY: DecayPolicy;
|
|
35
|
+
export declare function decayTiers(policy?: DecayPolicy): Record<string, number>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall/decay tuning knobs — pure declarative defaults (TS twin of
|
|
3
|
+
* `dna/memory/policy.py`). The kernel-reading resolvers are deliberately left
|
|
4
|
+
* behind (service concern); these defaults ARE the calibrated values and the
|
|
5
|
+
* pure scoring functions fall back to them.
|
|
6
|
+
*
|
|
7
|
+
* s-memory-verbs (2026-07-09). Parity-critical numeric constants.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_RECALL_POLICY = {
|
|
10
|
+
directThreshold: 0.3,
|
|
11
|
+
cosineWeight: 0.61,
|
|
12
|
+
contentWeight: 0.55,
|
|
13
|
+
summaryPartialWeight: 0.28,
|
|
14
|
+
coTopicsWeight: 0.2,
|
|
15
|
+
sourceRefsWeight: 0.15,
|
|
16
|
+
affectWeight: 0.05,
|
|
17
|
+
timeWeight: 0.05,
|
|
18
|
+
noveltyBoost: 0.05,
|
|
19
|
+
recencyBoost: 0.1,
|
|
20
|
+
saturationDecay: 0.6,
|
|
21
|
+
saturationThreshold: 3,
|
|
22
|
+
limitDirect: 8,
|
|
23
|
+
limitHomophonic: 6,
|
|
24
|
+
};
|
|
25
|
+
export const DEFAULT_DECAY_POLICY = {
|
|
26
|
+
tierFaint: 5.0,
|
|
27
|
+
tierFirm: 15.0,
|
|
28
|
+
tierBurning: 45.0,
|
|
29
|
+
defaultStabilityDays: 15.0,
|
|
30
|
+
maxStabilityDays: 60.0,
|
|
31
|
+
relevanceDecaySeed: 0.95,
|
|
32
|
+
};
|
|
33
|
+
export function decayTiers(policy = DEFAULT_DECAY_POLICY) {
|
|
34
|
+
return { faint: policy.tierFaint, firm: policy.tierFirm, burning: policy.tierBurning };
|
|
35
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface Memory {
|
|
2
|
+
name: string;
|
|
3
|
+
spec: Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
export interface RankedMemory {
|
|
6
|
+
name: string;
|
|
7
|
+
score: number;
|
|
8
|
+
factors: Record<string, number | boolean>;
|
|
9
|
+
}
|
|
10
|
+
export declare function tokenize(text: string): string[];
|
|
11
|
+
export declare function bm25Score(queryTokens: string[], docTokens: string[], docLengths: number[], idf: Map<string, number>): number;
|
|
12
|
+
export declare function buildIdf(corpusTokens: string[][]): Map<string, number>;
|
|
13
|
+
export declare function recencyFactor(lastSurfaced: unknown, now: number): number;
|
|
14
|
+
export declare function affectFactor(affect: unknown): number;
|
|
15
|
+
export declare function surfaceDamp(count: unknown): number;
|
|
16
|
+
/** Rank memories by `bm25 × recency × affect × surface_damp × confidence`. Pure. */
|
|
17
|
+
export declare function rankMemories(memories: Memory[], query: string, opts?: {
|
|
18
|
+
now?: number;
|
|
19
|
+
limit?: number;
|
|
20
|
+
partial?: boolean;
|
|
21
|
+
}): RankedMemory[];
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BM25 memory ranking — the pure lexical scoring core (TS twin of
|
|
3
|
+
* `dna/memory/retrieval.py`). Bit-identical arithmetic to Python:
|
|
4
|
+
*
|
|
5
|
+
* final = base_bm25 × recency_decay × affect_weight × surface_damp × confidence
|
|
6
|
+
*
|
|
7
|
+
* s-memory-verbs (2026-07-09).
|
|
8
|
+
*/
|
|
9
|
+
import { confidenceScoreNumeric } from "./decay.js";
|
|
10
|
+
const AFFECT_WEIGHTS = {
|
|
11
|
+
triumph: 1.2,
|
|
12
|
+
regret: 1.3,
|
|
13
|
+
surprise: 1.5,
|
|
14
|
+
wistful: 1.0,
|
|
15
|
+
ominous: 1.4,
|
|
16
|
+
};
|
|
17
|
+
const BM25_K1 = 1.5;
|
|
18
|
+
const BM25_B = 0.75;
|
|
19
|
+
const RECENCY_HALFLIFE_DAYS = 30.0;
|
|
20
|
+
const SURFACE_DAMP_K = 0.1;
|
|
21
|
+
const PARTIAL_PREFIX_FLOOR = 0.05;
|
|
22
|
+
const PARTIAL_MIN_PREFIX_LEN = 3;
|
|
23
|
+
export function tokenize(text) {
|
|
24
|
+
return text.toLowerCase().match(/[a-z0-9]+/g) ?? [];
|
|
25
|
+
}
|
|
26
|
+
function docText(spec) {
|
|
27
|
+
return `${spec.area ?? ""} ${spec.summary ?? ""}`;
|
|
28
|
+
}
|
|
29
|
+
export function bm25Score(queryTokens, docTokens, docLengths, idf) {
|
|
30
|
+
if (docTokens.length === 0)
|
|
31
|
+
return 0.0;
|
|
32
|
+
const avgDl = docLengths.length ? docLengths.reduce((a, b) => a + b, 0) / docLengths.length : 0.0;
|
|
33
|
+
if (avgDl === 0.0)
|
|
34
|
+
return 0.0;
|
|
35
|
+
const dl = docTokens.length;
|
|
36
|
+
const tf = new Map();
|
|
37
|
+
for (const t of docTokens)
|
|
38
|
+
tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
39
|
+
let score = 0.0;
|
|
40
|
+
for (const term of queryTokens) {
|
|
41
|
+
const termIdf = idf.get(term);
|
|
42
|
+
if (termIdf === undefined)
|
|
43
|
+
continue;
|
|
44
|
+
const f = tf.get(term) ?? 0;
|
|
45
|
+
if (f === 0)
|
|
46
|
+
continue;
|
|
47
|
+
const num = f * (BM25_K1 + 1);
|
|
48
|
+
const den = f + BM25_K1 * (1 - BM25_B + (BM25_B * dl) / avgDl);
|
|
49
|
+
score += termIdf * (num / den);
|
|
50
|
+
}
|
|
51
|
+
return score;
|
|
52
|
+
}
|
|
53
|
+
export function buildIdf(corpusTokens) {
|
|
54
|
+
const n = corpusTokens.length;
|
|
55
|
+
const df = new Map();
|
|
56
|
+
for (const tokens of corpusTokens) {
|
|
57
|
+
for (const term of new Set(tokens))
|
|
58
|
+
df.set(term, (df.get(term) ?? 0) + 1);
|
|
59
|
+
}
|
|
60
|
+
const idf = new Map();
|
|
61
|
+
for (const [term, count] of df) {
|
|
62
|
+
idf.set(term, Math.log((n - count + 0.5) / (count + 0.5) + 1));
|
|
63
|
+
}
|
|
64
|
+
return idf;
|
|
65
|
+
}
|
|
66
|
+
export function recencyFactor(lastSurfaced, now) {
|
|
67
|
+
if (!lastSurfaced || typeof lastSurfaced !== "string")
|
|
68
|
+
return 1.0;
|
|
69
|
+
const ms = Date.parse(lastSurfaced.replace("Z", "+00:00"));
|
|
70
|
+
if (Number.isNaN(ms))
|
|
71
|
+
return 1.0;
|
|
72
|
+
const deltaDays = (now - ms) / 86_400_000;
|
|
73
|
+
if (deltaDays <= 0)
|
|
74
|
+
return 1.0;
|
|
75
|
+
return Math.exp(-deltaDays / RECENCY_HALFLIFE_DAYS);
|
|
76
|
+
}
|
|
77
|
+
export function affectFactor(affect) {
|
|
78
|
+
return AFFECT_WEIGHTS[String(affect ?? "")] ?? 1.0;
|
|
79
|
+
}
|
|
80
|
+
export function surfaceDamp(count) {
|
|
81
|
+
const n = typeof count === "number" ? count : 0;
|
|
82
|
+
return 1.0 / (1.0 + n * SURFACE_DAMP_K);
|
|
83
|
+
}
|
|
84
|
+
function hasPrefixOverlap(queryTokens, docTokens) {
|
|
85
|
+
const q = queryTokens.filter((t) => t.length >= PARTIAL_MIN_PREFIX_LEN);
|
|
86
|
+
const d = docTokens.filter((t) => t.length >= PARTIAL_MIN_PREFIX_LEN);
|
|
87
|
+
for (const qt of q) {
|
|
88
|
+
for (const dt of d) {
|
|
89
|
+
if (qt === dt)
|
|
90
|
+
continue;
|
|
91
|
+
const n = Math.min(qt.length, dt.length);
|
|
92
|
+
let i = 0;
|
|
93
|
+
while (i < n && qt[i] === dt[i])
|
|
94
|
+
i += 1;
|
|
95
|
+
if (i >= PARTIAL_MIN_PREFIX_LEN)
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
/** Rank memories by `bm25 × recency × affect × surface_damp × confidence`. Pure. */
|
|
102
|
+
export function rankMemories(memories, query, opts = {}) {
|
|
103
|
+
const { now = Date.now(), limit = 5, partial = false } = opts;
|
|
104
|
+
const queryTokens = tokenize(query);
|
|
105
|
+
const corpusTokens = memories.map((m) => tokenize(docText(m.spec)));
|
|
106
|
+
const ranked = [];
|
|
107
|
+
if (queryTokens.length && memories.length) {
|
|
108
|
+
const docLengths = corpusTokens.map((t) => t.length);
|
|
109
|
+
const idf = buildIdf(corpusTokens);
|
|
110
|
+
memories.forEach((mem, i) => {
|
|
111
|
+
const tokens = corpusTokens[i];
|
|
112
|
+
let base = bm25Score(queryTokens, tokens, docLengths, idf);
|
|
113
|
+
let partialMatch = false;
|
|
114
|
+
if (base <= 0.0) {
|
|
115
|
+
if (!partial || !hasPrefixOverlap(queryTokens, tokens))
|
|
116
|
+
return;
|
|
117
|
+
base = PARTIAL_PREFIX_FLOOR;
|
|
118
|
+
partialMatch = true;
|
|
119
|
+
}
|
|
120
|
+
const spec = mem.spec ?? {};
|
|
121
|
+
const rec = recencyFactor(spec.last_surfaced, now);
|
|
122
|
+
const aff = affectFactor(spec.affect);
|
|
123
|
+
const damp = surfaceDamp(spec.surface_count);
|
|
124
|
+
const conf = confidenceScoreNumeric(spec);
|
|
125
|
+
ranked.push({
|
|
126
|
+
name: mem.name,
|
|
127
|
+
score: base * rec * aff * damp * conf,
|
|
128
|
+
factors: {
|
|
129
|
+
base,
|
|
130
|
+
recency: rec,
|
|
131
|
+
affect: aff,
|
|
132
|
+
surface_damp: damp,
|
|
133
|
+
confidence_score: conf,
|
|
134
|
+
partial_match: partialMatch,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
ranked.sort((a, b) => {
|
|
140
|
+
if (b.score !== a.score)
|
|
141
|
+
return b.score - a.score;
|
|
142
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
143
|
+
});
|
|
144
|
+
return ranked.slice(0, limit);
|
|
145
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public testing kit for TS SDK port adapters (twin of Python `dna.testing`).
|
|
3
|
+
*
|
|
4
|
+
* Ship-with-the-SDK conformance suites: an adapter author hands us a factory
|
|
5
|
+
* for their adapter and gets back the battery of cases every conforming
|
|
6
|
+
* implementation must pass. Currently the RecordSearchProvider search-plane kit.
|
|
7
|
+
*/
|
|
8
|
+
export { FIXTURE_SCOPE, fixtureRecords, recordSearchConformanceSuite, SearchCaseNotApplicable, type ConformanceProvider, type ProviderFactory, type RecordSearchCase, } from "./recordSearchConformance.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public testing kit for TS SDK port adapters (twin of Python `dna.testing`).
|
|
3
|
+
*
|
|
4
|
+
* Ship-with-the-SDK conformance suites: an adapter author hands us a factory
|
|
5
|
+
* for their adapter and gets back the battery of cases every conforming
|
|
6
|
+
* implementation must pass. Currently the RecordSearchProvider search-plane kit.
|
|
7
|
+
*/
|
|
8
|
+
export { FIXTURE_SCOPE, fixtureRecords, recordSearchConformanceSuite, SearchCaseNotApplicable, } from "./recordSearchConformance.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RecordSearchProvider conformance kit (TS twin of
|
|
3
|
+
* `dna/testing/record_search_conformance.py`).
|
|
4
|
+
*
|
|
5
|
+
* The behavioral contract every `RecordSearchProvider` implementation must
|
|
6
|
+
* satisfy — the search-plane conformance kit. The sqlite-vec provider runs it
|
|
7
|
+
* today; a future adapter runs the SAME kit. Cases assert only on PUBLIC
|
|
8
|
+
* behavior (relative ranking, filtering, overlay shadowing, k-limit,
|
|
9
|
+
* idempotence) under the deterministic fake embedder, so they hold for a real
|
|
10
|
+
* embedder too and run fully offline.
|
|
11
|
+
*
|
|
12
|
+
* A `factory` is an async zero-arg callable returning `{ provider, cleanup }`.
|
|
13
|
+
* The provider must expose async `index` / `search` / `delete`. Each case runs
|
|
14
|
+
* against a FRESH provider (isolation), then always awaits cleanup.
|
|
15
|
+
*/
|
|
16
|
+
export declare const FIXTURE_SCOPE = "search-conformance-kit";
|
|
17
|
+
/** A record-search provider, structurally. */
|
|
18
|
+
export interface ConformanceProvider {
|
|
19
|
+
index(records: Array<Record<string, unknown>>): Promise<number>;
|
|
20
|
+
search(opts: {
|
|
21
|
+
scope: string;
|
|
22
|
+
queryText: string;
|
|
23
|
+
kind?: string | null;
|
|
24
|
+
k?: number;
|
|
25
|
+
tenant?: string;
|
|
26
|
+
}): Promise<Array<Record<string, unknown>>>;
|
|
27
|
+
delete?(ids: Array<Record<string, unknown>>): Promise<number>;
|
|
28
|
+
close?(): void | Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export type ProviderFactory = () => Promise<{
|
|
31
|
+
provider: ConformanceProvider;
|
|
32
|
+
cleanup?: () => void | Promise<void>;
|
|
33
|
+
}>;
|
|
34
|
+
export declare function fixtureRecords(): Array<Record<string, unknown>>;
|
|
35
|
+
declare class SkipCase extends Error {
|
|
36
|
+
}
|
|
37
|
+
export interface RecordSearchCase {
|
|
38
|
+
name: string;
|
|
39
|
+
requires: string;
|
|
40
|
+
run(): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
export declare function recordSearchConformanceSuite(factory: ProviderFactory): RecordSearchCase[];
|
|
43
|
+
export { SkipCase as SearchCaseNotApplicable };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RecordSearchProvider conformance kit (TS twin of
|
|
3
|
+
* `dna/testing/record_search_conformance.py`).
|
|
4
|
+
*
|
|
5
|
+
* The behavioral contract every `RecordSearchProvider` implementation must
|
|
6
|
+
* satisfy — the search-plane conformance kit. The sqlite-vec provider runs it
|
|
7
|
+
* today; a future adapter runs the SAME kit. Cases assert only on PUBLIC
|
|
8
|
+
* behavior (relative ranking, filtering, overlay shadowing, k-limit,
|
|
9
|
+
* idempotence) under the deterministic fake embedder, so they hold for a real
|
|
10
|
+
* embedder too and run fully offline.
|
|
11
|
+
*
|
|
12
|
+
* A `factory` is an async zero-arg callable returning `{ provider, cleanup }`.
|
|
13
|
+
* The provider must expose async `index` / `search` / `delete`. Each case runs
|
|
14
|
+
* against a FRESH provider (isolation), then always awaits cleanup.
|
|
15
|
+
*/
|
|
16
|
+
export const FIXTURE_SCOPE = "search-conformance-kit";
|
|
17
|
+
export function fixtureRecords() {
|
|
18
|
+
return [
|
|
19
|
+
{ scope: FIXTURE_SCOPE, kind: "Story", name: "s-memory", title: "Memory recall",
|
|
20
|
+
text: "memory similarity vector embedding recall cognitive ecphory" },
|
|
21
|
+
{ scope: FIXTURE_SCOPE, kind: "Story", name: "s-banana", title: "Banana smoothie",
|
|
22
|
+
text: "banana tropical yellow fruit smoothie breakfast" },
|
|
23
|
+
{ scope: FIXTURE_SCOPE, kind: "Story", name: "s-fusion", title: "Hybrid fusion",
|
|
24
|
+
text: "hybrid search fusion reciprocal rank bm25 dense lexical" },
|
|
25
|
+
{ scope: FIXTURE_SCOPE, kind: "Genome", name: "g-root", title: "Root genome",
|
|
26
|
+
text: "root genome package catalog identity" },
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
class SkipCase extends Error {
|
|
30
|
+
}
|
|
31
|
+
async function indexFixture(p) {
|
|
32
|
+
await p.index(fixtureRecords().map((r) => ({ ...r })));
|
|
33
|
+
}
|
|
34
|
+
function names(hits) {
|
|
35
|
+
return hits.map((h) => h.name);
|
|
36
|
+
}
|
|
37
|
+
function assert(cond, msg) {
|
|
38
|
+
if (!cond)
|
|
39
|
+
throw new Error(msg);
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// cases
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
async function indexSearchRoundTrip(p) {
|
|
45
|
+
await indexFixture(p);
|
|
46
|
+
const hits = await p.search({ scope: FIXTURE_SCOPE, queryText: "memory recall cognitive", k: 10 });
|
|
47
|
+
assert(hits.length > 0, "search returned nothing after indexing");
|
|
48
|
+
assert(hits[0].name === "s-memory", `expected s-memory first, got ${names(hits).join(",")}`);
|
|
49
|
+
for (const h of hits) {
|
|
50
|
+
assert(["scope", "kind", "name", "score"].every((key) => key in h), `hit missing guaranteed keys: ${JSON.stringify(h)}`);
|
|
51
|
+
assert(h.scope === FIXTURE_SCOPE, "wrong scope on hit");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function rrfOrdersByRelevance(p) {
|
|
55
|
+
await indexFixture(p);
|
|
56
|
+
const hits = await p.search({ scope: FIXTURE_SCOPE, queryText: "banana fruit smoothie", k: 10 });
|
|
57
|
+
assert(hits.length > 0 && hits[0].name === "s-banana", `RRF must rank s-banana first, got ${names(hits).join(",")}`);
|
|
58
|
+
const rank = new Map(names(hits).map((n, i) => [n, i]));
|
|
59
|
+
if (rank.has("s-memory")) {
|
|
60
|
+
assert(rank.get("s-banana") < rank.get("s-memory"), "relevant doc must outrank an unrelated one");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function kindFilter(p) {
|
|
64
|
+
await indexFixture(p);
|
|
65
|
+
const hits = await p.search({ scope: FIXTURE_SCOPE, queryText: "root genome catalog", kind: "Genome", k: 10 });
|
|
66
|
+
assert(hits.length > 0, "kind-filtered search found nothing");
|
|
67
|
+
assert(hits.every((h) => h.kind === "Genome"), `kind filter leaked other kinds: ${JSON.stringify(hits.map((h) => [h.kind, h.name]))}`);
|
|
68
|
+
assert(names(hits).includes("g-root"), "expected g-root");
|
|
69
|
+
}
|
|
70
|
+
async function kLimit(p) {
|
|
71
|
+
await indexFixture(p);
|
|
72
|
+
const hits = await p.search({ scope: FIXTURE_SCOPE, queryText: "memory banana fusion genome", k: 2 });
|
|
73
|
+
assert(hits.length <= 2, `k=2 must cap results, got ${hits.length}`);
|
|
74
|
+
}
|
|
75
|
+
async function emptyQuery(p) {
|
|
76
|
+
await indexFixture(p);
|
|
77
|
+
for (const q of ["", " "]) {
|
|
78
|
+
const hits = await p.search({ scope: FIXTURE_SCOPE, queryText: q, k: 10 });
|
|
79
|
+
assert(hits.length === 0, `empty query must return no hits, got ${names(hits).join(",")}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function idempotentIndex(p) {
|
|
83
|
+
await indexFixture(p);
|
|
84
|
+
await indexFixture(p);
|
|
85
|
+
const hits = await p.search({ scope: FIXTURE_SCOPE, queryText: "memory recall", k: 10 });
|
|
86
|
+
const memory = hits.filter((h) => h.name === "s-memory");
|
|
87
|
+
assert(memory.length === 1, `re-index duplicated the doc: ${memory.length} copies of s-memory`);
|
|
88
|
+
}
|
|
89
|
+
async function deleteRemoves(p) {
|
|
90
|
+
if (typeof p.delete !== "function")
|
|
91
|
+
throw new SkipCase("no delete()");
|
|
92
|
+
await indexFixture(p);
|
|
93
|
+
await p.delete([{ scope: FIXTURE_SCOPE, kind: "Story", name: "s-banana" }]);
|
|
94
|
+
const hits = await p.search({ scope: FIXTURE_SCOPE, queryText: "banana fruit smoothie", k: 10 });
|
|
95
|
+
assert(!names(hits).includes("s-banana"), `deleted doc still returned: ${names(hits).join(",")}`);
|
|
96
|
+
}
|
|
97
|
+
async function tenantOverlayShadowsBase(p) {
|
|
98
|
+
await indexFixture(p);
|
|
99
|
+
await p.index([{
|
|
100
|
+
scope: FIXTURE_SCOPE, kind: "Story", name: "s-memory", tenant: "acme",
|
|
101
|
+
title: "Memory recall (acme)",
|
|
102
|
+
text: "memory similarity recall acme overlay variant tenant-specific",
|
|
103
|
+
}]);
|
|
104
|
+
const base = await p.search({ scope: FIXTURE_SCOPE, queryText: "memory recall", kind: "Story", k: 10, tenant: "" });
|
|
105
|
+
const over = await p.search({ scope: FIXTURE_SCOPE, queryText: "memory recall", kind: "Story", k: 10, tenant: "acme" });
|
|
106
|
+
assert(names(base).includes("s-memory"), "base s-memory missing");
|
|
107
|
+
const baseHit = base.find((h) => h.name === "s-memory");
|
|
108
|
+
assert(!String(baseHit.title ?? "").toLowerCase().includes("acme"), "tenant overlay leaked into a base-tenant search");
|
|
109
|
+
const overMemory = over.filter((h) => h.name === "s-memory");
|
|
110
|
+
assert(overMemory.length === 1, `overlay must shadow base (one s-memory), got ${overMemory.length}`);
|
|
111
|
+
assert(String(overMemory[0].title ?? "").toLowerCase().includes("acme"), "tenant search must return the overlay variant, not the base");
|
|
112
|
+
}
|
|
113
|
+
const CASES = [
|
|
114
|
+
{ name: "index_search_round_trip", requires: "always", fn: indexSearchRoundTrip },
|
|
115
|
+
{ name: "rrf_orders_by_relevance", requires: "always", fn: rrfOrdersByRelevance },
|
|
116
|
+
{ name: "kind_filter", requires: "always", fn: kindFilter },
|
|
117
|
+
{ name: "k_limit", requires: "always", fn: kLimit },
|
|
118
|
+
{ name: "empty_query_returns_empty", requires: "always", fn: emptyQuery },
|
|
119
|
+
{ name: "idempotent_index", requires: "always", fn: idempotentIndex },
|
|
120
|
+
{ name: "delete_removes", requires: "delete()", fn: deleteRemoves },
|
|
121
|
+
{ name: "tenant_overlay_shadows_base", requires: "index() tenant", fn: tenantOverlayShadowsBase },
|
|
122
|
+
];
|
|
123
|
+
export function recordSearchConformanceSuite(factory) {
|
|
124
|
+
return CASES.map(({ name, requires, fn }) => ({
|
|
125
|
+
name,
|
|
126
|
+
requires,
|
|
127
|
+
async run() {
|
|
128
|
+
const { provider, cleanup } = await factory();
|
|
129
|
+
try {
|
|
130
|
+
await fn(provider);
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
if (cleanup)
|
|
134
|
+
await cleanup();
|
|
135
|
+
if (typeof provider.close === "function") {
|
|
136
|
+
try {
|
|
137
|
+
await provider.close();
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
/* ignore */
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
export { SkipCase as SearchCaseNotApplicable };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ASCII tree rendering — standalone function operating on ManifestInstance.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from ManifestInstance to keep the kernel class focused.
|
|
5
|
+
*/
|
|
6
|
+
import type { ManifestInstance } from "../kernel/instance.js";
|
|
7
|
+
export declare function asciiTree(mi: ManifestInstance): string;
|