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,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HttpResolver — ResolverPort for http:/https: URIs.
|
|
3
|
+
*
|
|
4
|
+
* 1:1 parity with Python dna.v3.adapters.resolvers.http.
|
|
5
|
+
*
|
|
6
|
+
* Fetches manifest documents from HTTP endpoints. Supports two modes:
|
|
7
|
+
*
|
|
8
|
+
* 1. **Index mode** (default): GET {uri}/index.json → list of {kind, name, path}.
|
|
9
|
+
* Each item is fetched individually: GET {uri}/{path} → raw dict.
|
|
10
|
+
*
|
|
11
|
+
* 2. **Bundle mode** (fallback): GET {uri} → list of raw dicts directly.
|
|
12
|
+
*/
|
|
13
|
+
import { mkdirSync, writeFileSync, mkdtempSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { ResolveNotFoundError, ResolveNetworkError, ResolveError, } from "../../kernel/protocols.js";
|
|
17
|
+
import { rejectLegacyShorthand } from "./local.js";
|
|
18
|
+
export class HttpResolver {
|
|
19
|
+
cacheKey(uri) {
|
|
20
|
+
const safe = uri.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
|
|
21
|
+
return `http-${safe}`.slice(0, 120);
|
|
22
|
+
}
|
|
23
|
+
async resolve(uri, dep) {
|
|
24
|
+
const baseUrl = uri.replace(/\/$/, "");
|
|
25
|
+
const requested = this._collectRequested(dep);
|
|
26
|
+
// Try index mode first
|
|
27
|
+
try {
|
|
28
|
+
const index = this._fetchJson(`${baseUrl}/index.json`);
|
|
29
|
+
if (Array.isArray(index)) {
|
|
30
|
+
return this._resolveFromIndex(baseUrl, index, requested);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
if (e instanceof ResolveError) {
|
|
35
|
+
// Fall through to bundle mode
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Fallback: bundle mode
|
|
42
|
+
try {
|
|
43
|
+
const bundle = this._fetchJson(baseUrl);
|
|
44
|
+
if (Array.isArray(bundle)) {
|
|
45
|
+
return this._resolveFromBundle(bundle, requested);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
if (e instanceof ResolveError) {
|
|
50
|
+
throw new ResolveError(`HTTP resolve failed for ${uri}: ${e.message}`);
|
|
51
|
+
}
|
|
52
|
+
throw e;
|
|
53
|
+
}
|
|
54
|
+
throw new ResolveError(`HTTP endpoint returned unexpected format: ${uri}`);
|
|
55
|
+
}
|
|
56
|
+
_fetchJson(url) {
|
|
57
|
+
const result = Bun.spawnSync(["curl", "-sf", "-H", "Accept: application/json", url]);
|
|
58
|
+
if (result.exitCode !== 0) {
|
|
59
|
+
const stderr = result.stderr.toString();
|
|
60
|
+
// curl exit code 22 = HTTP 4xx/5xx, 6 = DNS failure, 28 = timeout
|
|
61
|
+
if (stderr.includes("404") || result.stdout.toString().includes("404")) {
|
|
62
|
+
throw new ResolveNotFoundError(`Not found: ${url}`);
|
|
63
|
+
}
|
|
64
|
+
if (result.exitCode === 6 || result.exitCode === 7 || result.exitCode === 28) {
|
|
65
|
+
throw new ResolveNetworkError(`Network error fetching: ${url}`);
|
|
66
|
+
}
|
|
67
|
+
throw new ResolveError(`curl failed (exit ${result.exitCode}): ${url}`);
|
|
68
|
+
}
|
|
69
|
+
const body = result.stdout.toString();
|
|
70
|
+
if (!body.trim()) {
|
|
71
|
+
throw new ResolveNotFoundError(`Empty response from: ${url}`);
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(body);
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
throw new ResolveError(`Invalid JSON from ${url}: ${e}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
_resolveFromIndex(baseUrl, index, requested) {
|
|
81
|
+
const items = [];
|
|
82
|
+
for (const entry of index) {
|
|
83
|
+
const kind = entry.kind ?? "";
|
|
84
|
+
const name = entry.name ?? "";
|
|
85
|
+
const path = entry.path ?? "";
|
|
86
|
+
if (requested && !this._matchesRequest(kind, name, requested)) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
let raw;
|
|
90
|
+
try {
|
|
91
|
+
raw = this._fetchJson(`${baseUrl}/${path}`);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const tmp = mkdtempSync(join(tmpdir(), "dna-http-"));
|
|
97
|
+
const itemDir = join(tmp, name);
|
|
98
|
+
mkdirSync(itemDir, { recursive: true });
|
|
99
|
+
writeFileSync(join(itemDir, "manifest.yaml"), JSON.stringify(raw, null, 2));
|
|
100
|
+
items.push({ name, kind, sourcePath: itemDir });
|
|
101
|
+
}
|
|
102
|
+
return items;
|
|
103
|
+
}
|
|
104
|
+
_resolveFromBundle(bundle, requested) {
|
|
105
|
+
const items = [];
|
|
106
|
+
for (const raw of bundle) {
|
|
107
|
+
const kind = raw.kind ?? "";
|
|
108
|
+
const metadata = raw.metadata ?? {};
|
|
109
|
+
const name = metadata.name ?? "";
|
|
110
|
+
if (!name)
|
|
111
|
+
continue;
|
|
112
|
+
if (requested && !this._matchesRequest(kind, name, requested)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const tmp = mkdtempSync(join(tmpdir(), "dna-http-"));
|
|
116
|
+
const itemDir = join(tmp, name);
|
|
117
|
+
mkdirSync(itemDir, { recursive: true });
|
|
118
|
+
writeFileSync(join(itemDir, "manifest.yaml"), JSON.stringify(raw, null, 2));
|
|
119
|
+
items.push({ name, kind, sourcePath: itemDir });
|
|
120
|
+
}
|
|
121
|
+
return items;
|
|
122
|
+
}
|
|
123
|
+
_collectRequested(dep) {
|
|
124
|
+
// i-010 — same normalization contract as LocalResolver._collectRequested:
|
|
125
|
+
// the legacy pre-v3 shorthand throws ResolveError before any fetch.
|
|
126
|
+
rejectLegacyShorthand(dep);
|
|
127
|
+
const result = {};
|
|
128
|
+
const items = dep.items ?? [];
|
|
129
|
+
for (const item of items) {
|
|
130
|
+
const kind = item.kind ?? "";
|
|
131
|
+
if (kind) {
|
|
132
|
+
result[kind] = item.names ?? [];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
136
|
+
}
|
|
137
|
+
_matchesRequest(kind, name, requested) {
|
|
138
|
+
if (!(kind in requested))
|
|
139
|
+
return false;
|
|
140
|
+
const names = requested[kind];
|
|
141
|
+
if (!names || names.length === 0)
|
|
142
|
+
return true;
|
|
143
|
+
return names.includes(name);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalResolver — ResolverPort for local: URIs.
|
|
3
|
+
*
|
|
4
|
+
* 1:1 parity with Python dna.v3.adapters.resolvers.local.
|
|
5
|
+
*/
|
|
6
|
+
import type { ResolvedItem, ResolverPort } from "../../kernel/protocols.js";
|
|
7
|
+
export declare const LEGACY_SHORTHAND_KEYS: readonly ["skills", "souls", "agents", "actors", "guardrails"];
|
|
8
|
+
/**
|
|
9
|
+
* Throw ResolveError if the dep uses the dead pre-v3 category shorthand.
|
|
10
|
+
*
|
|
11
|
+
* Shared by the local, github and http resolvers so the legacy format
|
|
12
|
+
* fails loud (an entry in `mi.resolveErrors`) with a rewrite recipe
|
|
13
|
+
* instead of silently resolving at the wrong granularity.
|
|
14
|
+
*/
|
|
15
|
+
export declare function rejectLegacyShorthand(dep: Record<string, unknown>): void;
|
|
16
|
+
export declare class LocalResolver implements ResolverPort {
|
|
17
|
+
readonly baseDir: string | null;
|
|
18
|
+
constructor(baseDir?: string);
|
|
19
|
+
cacheKey(uri: string): string;
|
|
20
|
+
resolve(uri: string, dep: Record<string, unknown>): Promise<ResolvedItem[]>;
|
|
21
|
+
private _collectRequested;
|
|
22
|
+
private _resolveAll;
|
|
23
|
+
private _resolveByCategory;
|
|
24
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalResolver — ResolverPort for local: URIs.
|
|
3
|
+
*
|
|
4
|
+
* 1:1 parity with Python dna.v3.adapters.resolvers.local.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
7
|
+
import { resolve, join } from "node:path";
|
|
8
|
+
import { ResolveError } from "../../kernel/protocols.js";
|
|
9
|
+
// i-009 / i-010 — pre-v3 dependency shorthand: category-list keys at the dep
|
|
10
|
+
// top level (`skills: [...]`). DEAD format — the Genome contract is
|
|
11
|
+
// `items: [{kind, names}]` and no resolver ever read the shorthand, so it
|
|
12
|
+
// silently fell through to _resolveAll with the wrong granularity
|
|
13
|
+
// (bundle SUBDIRECTORIES instead of bundles). Rejected loudly instead.
|
|
14
|
+
// Twin of LEGACY_SHORTHAND_KEYS / reject_legacy_shorthand in
|
|
15
|
+
// dna/adapters/resolvers/local.py — same keys, same message.
|
|
16
|
+
export const LEGACY_SHORTHAND_KEYS = [
|
|
17
|
+
"skills",
|
|
18
|
+
"souls",
|
|
19
|
+
"agents",
|
|
20
|
+
"actors",
|
|
21
|
+
"guardrails",
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Throw ResolveError if the dep uses the dead pre-v3 category shorthand.
|
|
25
|
+
*
|
|
26
|
+
* Shared by the local, github and http resolvers so the legacy format
|
|
27
|
+
* fails loud (an entry in `mi.resolveErrors`) with a rewrite recipe
|
|
28
|
+
* instead of silently resolving at the wrong granularity.
|
|
29
|
+
*/
|
|
30
|
+
export function rejectLegacyShorthand(dep) {
|
|
31
|
+
const legacy = LEGACY_SHORTHAND_KEYS.filter((k) => Array.isArray(dep[k]));
|
|
32
|
+
if (legacy.length > 0) {
|
|
33
|
+
const source = typeof dep.source === "string" ? dep.source : "<unknown>";
|
|
34
|
+
const singular = legacy[0].replace(/s+$/, "");
|
|
35
|
+
const kind = singular.charAt(0).toUpperCase() + singular.slice(1);
|
|
36
|
+
throw new ResolveError(`Dependency '${source}' uses the legacy '${legacy[0]}:' shorthand, ` +
|
|
37
|
+
`which is no longer read. Rewrite it in the v3 items format:\n` +
|
|
38
|
+
` items:\n` +
|
|
39
|
+
` - kind: ${kind}\n` +
|
|
40
|
+
` names: [...]`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export class LocalResolver {
|
|
44
|
+
baseDir;
|
|
45
|
+
constructor(baseDir) {
|
|
46
|
+
this.baseDir = baseDir ? resolve(baseDir) : null;
|
|
47
|
+
}
|
|
48
|
+
cacheKey(uri) {
|
|
49
|
+
const path = uri.replace(/^local:/, "");
|
|
50
|
+
const safe = path.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
|
|
51
|
+
return `local-${safe}`;
|
|
52
|
+
}
|
|
53
|
+
async resolve(uri, dep) {
|
|
54
|
+
const pathStr = uri.replace(/^local:/, "");
|
|
55
|
+
let localPath = pathStr;
|
|
56
|
+
if (!localPath.startsWith("/") && this.baseDir) {
|
|
57
|
+
localPath = resolve(this.baseDir, pathStr);
|
|
58
|
+
}
|
|
59
|
+
if (!existsSync(localPath)) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
// Normalize dep format
|
|
63
|
+
const requested = this._collectRequested(dep);
|
|
64
|
+
if (requested) {
|
|
65
|
+
return this._resolveByCategory(localPath, requested);
|
|
66
|
+
}
|
|
67
|
+
return this._resolveAll(localPath);
|
|
68
|
+
}
|
|
69
|
+
_collectRequested(dep) {
|
|
70
|
+
// The legacy pre-v3 shorthand (`skills: [...]` at the dep top level)
|
|
71
|
+
// throws ResolveError (i-009/i-010) — see `rejectLegacyShorthand`.
|
|
72
|
+
rejectLegacyShorthand(dep);
|
|
73
|
+
const result = {};
|
|
74
|
+
const items = dep.items ?? [];
|
|
75
|
+
for (const item of items) {
|
|
76
|
+
const kind = item.kind ?? "";
|
|
77
|
+
if (kind) {
|
|
78
|
+
const category = kind.toLowerCase() + "s"; // Skill -> skills
|
|
79
|
+
result[category] = item.names ?? []; // [] = all
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
83
|
+
}
|
|
84
|
+
_resolveAll(source) {
|
|
85
|
+
const items = [];
|
|
86
|
+
const entries = readdirSync(source).sort();
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const full = join(source, entry);
|
|
89
|
+
if (!statSync(full).isDirectory() || entry.startsWith("."))
|
|
90
|
+
continue;
|
|
91
|
+
// Recurse into category dirs (skills/, souls/, agents/)
|
|
92
|
+
const subEntries = readdirSync(full).sort();
|
|
93
|
+
for (const sub of subEntries) {
|
|
94
|
+
const itemDir = join(full, sub);
|
|
95
|
+
if (statSync(itemDir).isDirectory()) {
|
|
96
|
+
items.push({ name: sub, kind: "", sourcePath: itemDir });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return items;
|
|
101
|
+
}
|
|
102
|
+
_resolveByCategory(source, requested) {
|
|
103
|
+
const items = [];
|
|
104
|
+
for (const [category, names] of Object.entries(requested)) {
|
|
105
|
+
const categoryDir = join(source, category);
|
|
106
|
+
if (!existsSync(categoryDir))
|
|
107
|
+
continue;
|
|
108
|
+
if (names.length > 0) {
|
|
109
|
+
// Import specific names
|
|
110
|
+
for (const name of names) {
|
|
111
|
+
for (const candidate of [join(categoryDir, name), join(source, name)]) {
|
|
112
|
+
if (existsSync(candidate) && statSync(candidate).isDirectory()) {
|
|
113
|
+
items.push({ name, kind: "", sourcePath: candidate });
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Import all from category
|
|
121
|
+
const entries = readdirSync(categoryDir).sort();
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
const subdir = join(categoryDir, entry);
|
|
124
|
+
if (statSync(subdir).isDirectory()) {
|
|
125
|
+
items.push({ name: entry, kind: "", sourcePath: subdir });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return items;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite driver shim for the sqlite-vec search provider — the ONE place that
|
|
3
|
+
* papers over the two runtimes DNA's TS SDK runs under, so the provider itself
|
|
4
|
+
* is runtime-agnostic:
|
|
5
|
+
*
|
|
6
|
+
* - Bun (the test runner) → `bun:sqlite`. Bun auto-loads SQLite on the first
|
|
7
|
+
* `Database` open and extension loading must be enabled BEFORE that, via
|
|
8
|
+
* `Database.setCustomSQLite(libPath)`. We probe for a libsqlite3 that allows
|
|
9
|
+
* extensions (env `DNA_SQLITE_LIB`, then common macOS/Linux paths) exactly
|
|
10
|
+
* once, before opening anything. Bun's own bundled SQLite already allows it
|
|
11
|
+
* on most Linux builds, so the probe is a best-effort fallback.
|
|
12
|
+
* - Node ≥22.5 → `node:sqlite` (`DatabaseSync`, `{ allowExtension: true }`),
|
|
13
|
+
* which bundles a SQLite compiled with extension loading — no external lib.
|
|
14
|
+
*
|
|
15
|
+
* Both expose the tiny synchronous surface the provider needs. sqlite-vec is
|
|
16
|
+
* loaded into every opened connection. If no runtime can load the extension,
|
|
17
|
+
* `openSearchDb` throws `SqliteVecUnavailableError` — the provider surfaces it
|
|
18
|
+
* and callers (CLI, conformance kit) degrade/skip with a clear reason rather
|
|
19
|
+
* than crash.
|
|
20
|
+
*/
|
|
21
|
+
/** The minimal synchronous SQLite surface the provider drives. */
|
|
22
|
+
export interface SearchDb {
|
|
23
|
+
exec(sql: string): void;
|
|
24
|
+
run(sql: string, params?: unknown[]): {
|
|
25
|
+
lastInsertRowid: number;
|
|
26
|
+
};
|
|
27
|
+
get<T = Record<string, unknown>>(sql: string, params?: unknown[]): T | undefined;
|
|
28
|
+
all<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[];
|
|
29
|
+
close(): void;
|
|
30
|
+
}
|
|
31
|
+
/** Thrown when no available runtime can load the sqlite-vec extension. */
|
|
32
|
+
export declare class SqliteVecUnavailableError extends Error {
|
|
33
|
+
constructor(message: string, options?: {
|
|
34
|
+
cause?: unknown;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Open a SQLite connection with sqlite-vec loaded, using whichever runtime
|
|
39
|
+
* driver is available. Throws {@link SqliteVecUnavailableError} when neither
|
|
40
|
+
* can load the extension.
|
|
41
|
+
*/
|
|
42
|
+
export declare function openSearchDb(path: string): Promise<SearchDb>;
|
|
43
|
+
/** Pack a float vector into sqlite-vec's float32 blob layout (parity with the
|
|
44
|
+
* Python `_serialize_f32`). */
|
|
45
|
+
export declare function serializeF32(vector: number[]): Uint8Array;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite driver shim for the sqlite-vec search provider — the ONE place that
|
|
3
|
+
* papers over the two runtimes DNA's TS SDK runs under, so the provider itself
|
|
4
|
+
* is runtime-agnostic:
|
|
5
|
+
*
|
|
6
|
+
* - Bun (the test runner) → `bun:sqlite`. Bun auto-loads SQLite on the first
|
|
7
|
+
* `Database` open and extension loading must be enabled BEFORE that, via
|
|
8
|
+
* `Database.setCustomSQLite(libPath)`. We probe for a libsqlite3 that allows
|
|
9
|
+
* extensions (env `DNA_SQLITE_LIB`, then common macOS/Linux paths) exactly
|
|
10
|
+
* once, before opening anything. Bun's own bundled SQLite already allows it
|
|
11
|
+
* on most Linux builds, so the probe is a best-effort fallback.
|
|
12
|
+
* - Node ≥22.5 → `node:sqlite` (`DatabaseSync`, `{ allowExtension: true }`),
|
|
13
|
+
* which bundles a SQLite compiled with extension loading — no external lib.
|
|
14
|
+
*
|
|
15
|
+
* Both expose the tiny synchronous surface the provider needs. sqlite-vec is
|
|
16
|
+
* loaded into every opened connection. If no runtime can load the extension,
|
|
17
|
+
* `openSearchDb` throws `SqliteVecUnavailableError` — the provider surfaces it
|
|
18
|
+
* and callers (CLI, conformance kit) degrade/skip with a clear reason rather
|
|
19
|
+
* than crash.
|
|
20
|
+
*/
|
|
21
|
+
/** Thrown when no available runtime can load the sqlite-vec extension. */
|
|
22
|
+
export class SqliteVecUnavailableError extends Error {
|
|
23
|
+
constructor(message, options) {
|
|
24
|
+
super(message, options);
|
|
25
|
+
this.name = "SqliteVecUnavailableError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const isBun = typeof globalThis.Bun !== "undefined";
|
|
29
|
+
// Candidate libsqlite3 paths that permit extension loading. An explicit
|
|
30
|
+
// `DNA_SQLITE_LIB` override wins on any platform. Otherwise we only probe on
|
|
31
|
+
// macOS: Apple's system libsqlite3 (which Bun uses there) has extension loading
|
|
32
|
+
// DISABLED, so we point Bun at Homebrew's build. On Linux, Bun's bundled SQLite
|
|
33
|
+
// already allows loadExtension, so we add NO candidate and trust it — forcing a
|
|
34
|
+
// distro lib could regress if that build omitted extension support.
|
|
35
|
+
function libCandidates() {
|
|
36
|
+
const out = [];
|
|
37
|
+
const proc = globalThis.process;
|
|
38
|
+
if (proc?.env?.DNA_SQLITE_LIB)
|
|
39
|
+
out.push(proc.env.DNA_SQLITE_LIB);
|
|
40
|
+
if (proc?.platform === "darwin") {
|
|
41
|
+
out.push("/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib", "/usr/local/opt/sqlite/lib/libsqlite3.dylib");
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
let bunCustomSqliteChosen = false;
|
|
46
|
+
async function chooseBunSqlite() {
|
|
47
|
+
if (bunCustomSqliteChosen)
|
|
48
|
+
return;
|
|
49
|
+
bunCustomSqliteChosen = true; // decide once, before the first open
|
|
50
|
+
const { Database } = (await import("bun:sqlite"));
|
|
51
|
+
// Only set a custom lib if one exists on disk; otherwise trust Bun's bundled
|
|
52
|
+
// SQLite (extension loading works on most Linux builds out of the box).
|
|
53
|
+
let fs;
|
|
54
|
+
try {
|
|
55
|
+
fs = (await import("node:fs"));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
for (const path of libCandidates()) {
|
|
61
|
+
if (fs.existsSync(path)) {
|
|
62
|
+
try {
|
|
63
|
+
Database.setCustomSQLite(path);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
/* already loaded or unsupported — fall through to bundled */
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function openBun(path) {
|
|
73
|
+
await chooseBunSqlite();
|
|
74
|
+
const { Database } = (await import("bun:sqlite"));
|
|
75
|
+
const sqliteVec = await import("sqlite-vec");
|
|
76
|
+
const db = new Database(path);
|
|
77
|
+
try {
|
|
78
|
+
sqliteVec.load(db);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
db.close();
|
|
82
|
+
throw new SqliteVecUnavailableError("bun:sqlite could not load the sqlite-vec extension — set DNA_SQLITE_LIB "
|
|
83
|
+
+ "to a libsqlite3 that permits extension loading (e.g. Homebrew's "
|
|
84
|
+
+ "/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib).", { cause: err });
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
exec: (sql) => db.exec(sql),
|
|
88
|
+
run: (sql, params = []) => {
|
|
89
|
+
const info = db.query(sql).run(...params);
|
|
90
|
+
return { lastInsertRowid: Number(info.lastInsertRowid) };
|
|
91
|
+
},
|
|
92
|
+
get: (sql, params = []) => db.query(sql).get(...params),
|
|
93
|
+
all: (sql, params = []) => db.query(sql).all(...params),
|
|
94
|
+
close: () => db.close(),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
async function openNode(path) {
|
|
98
|
+
let DatabaseSync;
|
|
99
|
+
try {
|
|
100
|
+
({ DatabaseSync } = (await import("node:sqlite")));
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
throw new SqliteVecUnavailableError("node:sqlite is unavailable (needs Node ≥22.5 with --experimental-sqlite "
|
|
104
|
+
+ "or ≥23.5). Run under Bun, or upgrade Node.", { cause: err });
|
|
105
|
+
}
|
|
106
|
+
const sqliteVec = await import("sqlite-vec");
|
|
107
|
+
const db = new DatabaseSync(path, { allowExtension: true });
|
|
108
|
+
try {
|
|
109
|
+
db.loadExtension(sqliteVec.getLoadablePath());
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
db.close();
|
|
113
|
+
throw new SqliteVecUnavailableError("node:sqlite could not load the sqlite-vec extension.", { cause: err });
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
exec: (sql) => db.exec(sql),
|
|
117
|
+
run: (sql, params = []) => {
|
|
118
|
+
const info = db.prepare(sql).run(...params);
|
|
119
|
+
return { lastInsertRowid: Number(info.lastInsertRowid) };
|
|
120
|
+
},
|
|
121
|
+
get: (sql, params = []) => db.prepare(sql).get(...params),
|
|
122
|
+
all: (sql, params = []) => db.prepare(sql).all(...params),
|
|
123
|
+
close: () => db.close(),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Open a SQLite connection with sqlite-vec loaded, using whichever runtime
|
|
128
|
+
* driver is available. Throws {@link SqliteVecUnavailableError} when neither
|
|
129
|
+
* can load the extension.
|
|
130
|
+
*/
|
|
131
|
+
export async function openSearchDb(path) {
|
|
132
|
+
return isBun ? openBun(path) : openNode(path);
|
|
133
|
+
}
|
|
134
|
+
/** Pack a float vector into sqlite-vec's float32 blob layout (parity with the
|
|
135
|
+
* Python `_serialize_f32`). */
|
|
136
|
+
export function serializeF32(vector) {
|
|
137
|
+
return new Uint8Array(new Float32Array(vector).buffer);
|
|
138
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forward-only numbered migrations for the sqlite-vec search store (TS twin of
|
|
3
|
+
* `dna/adapters/search/migrations.py`).
|
|
4
|
+
*
|
|
5
|
+
* The debt rsh-memory-similarity-evolution (f-embeddings-ddl-debt) calls out:
|
|
6
|
+
* the embeddings sidecar had no owning migration upstream. Here the store's
|
|
7
|
+
* schema is OWNED by a numbered, append-only, idempotent migration recorded in
|
|
8
|
+
* the store's own `schema_migrations` control table. The vec0 virtual table
|
|
9
|
+
* needs the embedding width baked in, so `buildMigrations(dims)` interpolates
|
|
10
|
+
* it — the migration still owns the DDL, it just parametrizes the vector width.
|
|
11
|
+
*
|
|
12
|
+
* The TS SDK has no shared migration runner (only the Postgres source has its
|
|
13
|
+
* own), so {@link runSearchMigrations} is the small local analog of the Python
|
|
14
|
+
* `run_migrations` contract: same forward-only/append-only/idempotent-boot
|
|
15
|
+
* semantics, over the synchronous {@link SearchDb} surface.
|
|
16
|
+
*/
|
|
17
|
+
import type { SearchDb } from "./driver.js";
|
|
18
|
+
/** version → one executescript payload (SQLite dialect). Parity with the Py
|
|
19
|
+
* `build_migrations(dims)`. */
|
|
20
|
+
export declare function buildMigrations(dims: number): Record<number, string>;
|
|
21
|
+
/**
|
|
22
|
+
* Apply every pending migration in ascending version order (forward-only,
|
|
23
|
+
* append-only, idempotent boot). Returns the versions applied by THIS call —
|
|
24
|
+
* `[]` when the store is already up to date (the idempotent re-boot case).
|
|
25
|
+
*/
|
|
26
|
+
export declare function runSearchMigrations(db: SearchDb, migrations: Record<number, string>): number[];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** version → one executescript payload (SQLite dialect). Parity with the Py
|
|
2
|
+
* `build_migrations(dims)`. */
|
|
3
|
+
export function buildMigrations(dims) {
|
|
4
|
+
if (dims < 1)
|
|
5
|
+
throw new Error(`embedding dims must be positive, got ${dims}`);
|
|
6
|
+
return {
|
|
7
|
+
1: `
|
|
8
|
+
CREATE TABLE IF NOT EXISTS search_docs (
|
|
9
|
+
rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
10
|
+
scope TEXT NOT NULL,
|
|
11
|
+
kind TEXT NOT NULL,
|
|
12
|
+
name TEXT NOT NULL,
|
|
13
|
+
tenant TEXT NOT NULL DEFAULT '',
|
|
14
|
+
text_hash TEXT NOT NULL,
|
|
15
|
+
title TEXT,
|
|
16
|
+
snippet TEXT,
|
|
17
|
+
text TEXT NOT NULL,
|
|
18
|
+
UNIQUE (scope, kind, name, tenant)
|
|
19
|
+
);
|
|
20
|
+
CREATE INDEX IF NOT EXISTS search_docs_lookup
|
|
21
|
+
ON search_docs (scope, kind, tenant);
|
|
22
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS search_vec USING vec0(
|
|
23
|
+
doc_rowid INTEGER PRIMARY KEY,
|
|
24
|
+
embedding float[${dims}]
|
|
25
|
+
);
|
|
26
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS search_fts USING fts5(text);
|
|
27
|
+
CREATE TABLE IF NOT EXISTS search_meta (
|
|
28
|
+
key TEXT PRIMARY KEY,
|
|
29
|
+
value TEXT NOT NULL
|
|
30
|
+
);
|
|
31
|
+
`,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Apply every pending migration in ascending version order (forward-only,
|
|
36
|
+
* append-only, idempotent boot). Returns the versions applied by THIS call —
|
|
37
|
+
* `[]` when the store is already up to date (the idempotent re-boot case).
|
|
38
|
+
*/
|
|
39
|
+
export function runSearchMigrations(db, migrations) {
|
|
40
|
+
db.exec("CREATE TABLE IF NOT EXISTS schema_migrations "
|
|
41
|
+
+ "(version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)");
|
|
42
|
+
const applied = new Set(db.all("SELECT version FROM schema_migrations").map((r) => r.version));
|
|
43
|
+
const appliedNow = [];
|
|
44
|
+
const versions = Object.keys(migrations)
|
|
45
|
+
.map(Number)
|
|
46
|
+
.sort((a, b) => a - b);
|
|
47
|
+
for (const version of versions) {
|
|
48
|
+
if (applied.has(version))
|
|
49
|
+
continue;
|
|
50
|
+
db.exec(migrations[version]);
|
|
51
|
+
db.run("INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)", [
|
|
52
|
+
version,
|
|
53
|
+
new Date().toISOString(),
|
|
54
|
+
]);
|
|
55
|
+
appliedNow.push(version);
|
|
56
|
+
}
|
|
57
|
+
return appliedNow;
|
|
58
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reciprocal Rank Fusion — the pure, deterministic hybrid-fusion core.
|
|
3
|
+
*
|
|
4
|
+
* TypeScript twin of `dna/adapters/search/rrf.py`. RRF (Cormack et al. 2009)
|
|
5
|
+
* fuses several independently-ranked lists using only each item's RANK within
|
|
6
|
+
* each list — never the raw scores, which are incomparable across a cosine
|
|
7
|
+
* dense list and a BM25 lexical list. Each item accrues `1 / (k + rank)` (rank
|
|
8
|
+
* 1-based) from every list it appears in; items are ordered by descending fused
|
|
9
|
+
* score, ties broken by id ascending — fully deterministic and bit-identical to
|
|
10
|
+
* the Python implementation.
|
|
11
|
+
*/
|
|
12
|
+
/** RRF smoothing constant (the paper's value; the de-facto default). */
|
|
13
|
+
export declare const DEFAULT_RRF_K = 60;
|
|
14
|
+
/**
|
|
15
|
+
* Fuse ranked id-lists into one ranking via Reciprocal Rank Fusion.
|
|
16
|
+
*
|
|
17
|
+
* @param rankedLists each inner array is item ids in rank order (best first).
|
|
18
|
+
* An id may appear across lists; duplicates WITHIN one list count at their
|
|
19
|
+
* first (best) rank only.
|
|
20
|
+
* @param k RRF smoothing constant (`DEFAULT_RRF_K`); must be > 0.
|
|
21
|
+
* @returns `[id, fusedScore]` pairs sorted by fused score desc, ties by id asc.
|
|
22
|
+
*/
|
|
23
|
+
export declare function reciprocalRankFusion(rankedLists: string[][], k?: number): Array<[string, number]>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reciprocal Rank Fusion — the pure, deterministic hybrid-fusion core.
|
|
3
|
+
*
|
|
4
|
+
* TypeScript twin of `dna/adapters/search/rrf.py`. RRF (Cormack et al. 2009)
|
|
5
|
+
* fuses several independently-ranked lists using only each item's RANK within
|
|
6
|
+
* each list — never the raw scores, which are incomparable across a cosine
|
|
7
|
+
* dense list and a BM25 lexical list. Each item accrues `1 / (k + rank)` (rank
|
|
8
|
+
* 1-based) from every list it appears in; items are ordered by descending fused
|
|
9
|
+
* score, ties broken by id ascending — fully deterministic and bit-identical to
|
|
10
|
+
* the Python implementation.
|
|
11
|
+
*/
|
|
12
|
+
/** RRF smoothing constant (the paper's value; the de-facto default). */
|
|
13
|
+
export const DEFAULT_RRF_K = 60;
|
|
14
|
+
/**
|
|
15
|
+
* Fuse ranked id-lists into one ranking via Reciprocal Rank Fusion.
|
|
16
|
+
*
|
|
17
|
+
* @param rankedLists each inner array is item ids in rank order (best first).
|
|
18
|
+
* An id may appear across lists; duplicates WITHIN one list count at their
|
|
19
|
+
* first (best) rank only.
|
|
20
|
+
* @param k RRF smoothing constant (`DEFAULT_RRF_K`); must be > 0.
|
|
21
|
+
* @returns `[id, fusedScore]` pairs sorted by fused score desc, ties by id asc.
|
|
22
|
+
*/
|
|
23
|
+
export function reciprocalRankFusion(rankedLists, k = DEFAULT_RRF_K) {
|
|
24
|
+
if (k <= 0)
|
|
25
|
+
throw new Error(`RRF k must be positive, got ${k}`);
|
|
26
|
+
const scores = new Map();
|
|
27
|
+
for (const ranked of rankedLists) {
|
|
28
|
+
const seen = new Set();
|
|
29
|
+
ranked.forEach((docId, i) => {
|
|
30
|
+
if (seen.has(docId))
|
|
31
|
+
return; // first (best) rank wins for a repeated id
|
|
32
|
+
seen.add(docId);
|
|
33
|
+
const rank = i + 1;
|
|
34
|
+
scores.set(docId, (scores.get(docId) ?? 0) + 1 / (k + rank));
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return [...scores.entries()].sort((a, b) => {
|
|
38
|
+
if (b[1] !== a[1])
|
|
39
|
+
return b[1] - a[1]; // score desc
|
|
40
|
+
return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0; // id asc
|
|
41
|
+
});
|
|
42
|
+
}
|