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,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FSLike — filesystem abstraction for readers/writers.
|
|
3
|
+
*
|
|
4
|
+
* Allows the SDK to run in Node.js (nodeFS), Tauri/webview (VFS adapter),
|
|
5
|
+
* or in-memory (createMemoryFS for tests).
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync, mkdirSync, } from "node:fs";
|
|
8
|
+
import { dirname } from "node:path";
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Node.js adapter (default)
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
export const nodeFS = {
|
|
13
|
+
exists: (p) => existsSync(p),
|
|
14
|
+
readFile: (p) => readFileSync(p, "utf-8"),
|
|
15
|
+
readDir: (p) => readdirSync(p).sort(),
|
|
16
|
+
isDirectory: (p) => {
|
|
17
|
+
try {
|
|
18
|
+
return statSync(p).isDirectory();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
isFile: (p) => {
|
|
25
|
+
try {
|
|
26
|
+
return statSync(p).isFile();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
writeFile: (p, c) => {
|
|
33
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
34
|
+
writeFileSync(p, c);
|
|
35
|
+
},
|
|
36
|
+
mkdir: (p) => mkdirSync(p, { recursive: true }),
|
|
37
|
+
};
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// In-memory adapter (tests)
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
export function createMemoryFS(initial = {}) {
|
|
42
|
+
const files = new Map(Object.entries(initial));
|
|
43
|
+
function dirs() {
|
|
44
|
+
const result = new Set();
|
|
45
|
+
for (const key of files.keys()) {
|
|
46
|
+
const parts = key.split("/");
|
|
47
|
+
for (let i = 1; i < parts.length; i++) {
|
|
48
|
+
result.add(parts.slice(0, i).join("/"));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
exists: (p) => files.has(p) || dirs().has(p),
|
|
55
|
+
readFile: (p) => {
|
|
56
|
+
const c = files.get(p);
|
|
57
|
+
if (c === undefined)
|
|
58
|
+
throw new Error(`ENOENT: ${p}`);
|
|
59
|
+
return c;
|
|
60
|
+
},
|
|
61
|
+
readDir: (p) => {
|
|
62
|
+
const prefix = p.endsWith("/") ? p : p + "/";
|
|
63
|
+
const entries = new Set();
|
|
64
|
+
for (const key of files.keys()) {
|
|
65
|
+
if (key.startsWith(prefix)) {
|
|
66
|
+
const rest = key.slice(prefix.length);
|
|
67
|
+
const first = rest.split("/")[0];
|
|
68
|
+
if (first)
|
|
69
|
+
entries.add(first);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
for (const d of dirs()) {
|
|
73
|
+
if (d.startsWith(prefix)) {
|
|
74
|
+
const rest = d.slice(prefix.length);
|
|
75
|
+
const first = rest.split("/")[0];
|
|
76
|
+
if (first)
|
|
77
|
+
entries.add(first);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return [...entries].sort();
|
|
81
|
+
},
|
|
82
|
+
isDirectory: (p) => dirs().has(p),
|
|
83
|
+
isFile: (p) => files.has(p),
|
|
84
|
+
writeFile: (p, c) => { files.set(p, c); },
|
|
85
|
+
mkdir: () => { },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Shared helpers (used by readers in extensions)
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
export function relativePath(root, full) {
|
|
92
|
+
return full.startsWith(root + "/") ? full.slice(root.length + 1) : full;
|
|
93
|
+
}
|
|
94
|
+
const BINARY_EXTENSIONS = new Set([
|
|
95
|
+
".tar", ".gz", ".zip", ".png", ".jpg", ".jpeg", ".gif",
|
|
96
|
+
".pdf", ".wasm", ".bin", ".exe", ".so", ".dylib", ".ico",
|
|
97
|
+
]);
|
|
98
|
+
export function readTextSafe(fs, path) {
|
|
99
|
+
for (const ext of BINARY_EXTENSIONS) {
|
|
100
|
+
if (path.endsWith(ext))
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
return fs.readFile(path);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export function collectDir(fs, directory, root) {
|
|
111
|
+
const files = {};
|
|
112
|
+
_collectRecursive(fs, root, directory, files);
|
|
113
|
+
return files;
|
|
114
|
+
}
|
|
115
|
+
function _collectRecursive(fs, root, directory, files) {
|
|
116
|
+
for (const entry of fs.readDir(directory)) {
|
|
117
|
+
const full = `${directory}/${entry}`;
|
|
118
|
+
if (fs.isDirectory(full)) {
|
|
119
|
+
_collectRecursive(fs, root, full, files);
|
|
120
|
+
}
|
|
121
|
+
else if (fs.isFile(full)) {
|
|
122
|
+
const text = readTextSafe(fs, full);
|
|
123
|
+
if (text !== null) {
|
|
124
|
+
files[relativePath(root, full)] = text;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenericBundleReader / GenericBundleWriter — auto-generated from StorageDescriptor.
|
|
3
|
+
*
|
|
4
|
+
* Custom kinds that follow the standard BUNDLE layout can use these instead of
|
|
5
|
+
* hand-writing dedicated Reader/Writer classes.
|
|
6
|
+
*
|
|
7
|
+
* 1:1 parity with Python dna.kernel.generic_rw.
|
|
8
|
+
*/
|
|
9
|
+
import type { FSLike } from "./fs.js";
|
|
10
|
+
import type { BundleHandle, ReaderPort, WriterPort, SerializedFile } from "./protocols.js";
|
|
11
|
+
import type { StorageDescriptor } from "./protocols.js";
|
|
12
|
+
export declare class GenericBundleReader implements ReaderPort {
|
|
13
|
+
private sd;
|
|
14
|
+
private apiVersion;
|
|
15
|
+
private kindName;
|
|
16
|
+
private fs;
|
|
17
|
+
/** Exposed for deferred registration detection. */
|
|
18
|
+
readonly _marker: string;
|
|
19
|
+
constructor(sd: StorageDescriptor, apiVersion: string, kindName: string, fs?: FSLike);
|
|
20
|
+
detect(bundle: BundleHandle): boolean;
|
|
21
|
+
read(bundle: BundleHandle): Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
export declare class GenericBundleWriter implements WriterPort {
|
|
24
|
+
private fs;
|
|
25
|
+
/** Exposed for deferred registration detection. */
|
|
26
|
+
readonly _kind: string;
|
|
27
|
+
/** Exposed for serialize() and deferred registration. */
|
|
28
|
+
readonly _sd: StorageDescriptor;
|
|
29
|
+
constructor(sd: StorageDescriptor, kind: string, fs?: FSLike);
|
|
30
|
+
canWrite(raw: Record<string, unknown>): boolean;
|
|
31
|
+
write(bundle: BundleHandle, raw: Record<string, unknown>): void;
|
|
32
|
+
serialize(raw: Record<string, unknown>): SerializedFile[];
|
|
33
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenericBundleReader / GenericBundleWriter — auto-generated from StorageDescriptor.
|
|
3
|
+
*
|
|
4
|
+
* Custom kinds that follow the standard BUNDLE layout can use these instead of
|
|
5
|
+
* hand-writing dedicated Reader/Writer classes.
|
|
6
|
+
*
|
|
7
|
+
* 1:1 parity with Python dna.kernel.generic_rw.
|
|
8
|
+
*/
|
|
9
|
+
import yaml from "js-yaml";
|
|
10
|
+
import { nodeFS } from "./fs.js";
|
|
11
|
+
const META_FIELDS = new Set(["name", "description", "labels"]);
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Helpers
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
function parseFrontmatter(text, source) {
|
|
16
|
+
const match = text.match(/^---\n([\s\S]*?)---\n?/);
|
|
17
|
+
if (!match)
|
|
18
|
+
return [{}, text];
|
|
19
|
+
let fm = {};
|
|
20
|
+
try {
|
|
21
|
+
const parsed = yaml.load(match[1]);
|
|
22
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
23
|
+
fm = parsed;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
// Surface the failure loudly instead of silently dropping spec fields.
|
|
28
|
+
// Matches the Python `FrontmatterParseWarning` hardening in generic_rw.py.
|
|
29
|
+
const where = source ? ` in ${source}` : "";
|
|
30
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
31
|
+
const mark = e?.mark;
|
|
32
|
+
const location = mark && typeof mark.line === "number"
|
|
33
|
+
? ` (line ${mark.line + 1}, column ${(mark.column ?? 0) + 1})`
|
|
34
|
+
: "";
|
|
35
|
+
console.warn(`[dna-sdk] Invalid YAML frontmatter${where}${location}: ${message}. ` +
|
|
36
|
+
`Falling back to empty frontmatter — all spec fields from this file ` +
|
|
37
|
+
`will be missing. Fix the frontmatter and reload the manifest.`);
|
|
38
|
+
}
|
|
39
|
+
const body = text.slice(match[0].length);
|
|
40
|
+
return [fm, body];
|
|
41
|
+
}
|
|
42
|
+
function parseBody(body, bodyAs) {
|
|
43
|
+
if (bodyAs === "text") {
|
|
44
|
+
return body.trim();
|
|
45
|
+
}
|
|
46
|
+
if (bodyAs === "list") {
|
|
47
|
+
return body
|
|
48
|
+
.split("\n")
|
|
49
|
+
.filter((l) => l.trim().startsWith("- "))
|
|
50
|
+
.map((l) => l.trim().slice(2));
|
|
51
|
+
}
|
|
52
|
+
if (bodyAs === "sections") {
|
|
53
|
+
const sections = {};
|
|
54
|
+
let currentHeading = null;
|
|
55
|
+
const lines = [];
|
|
56
|
+
for (const line of body.split("\n")) {
|
|
57
|
+
const headingMatch = line.match(/^## (.+)$/);
|
|
58
|
+
if (headingMatch && !line.startsWith("### ")) {
|
|
59
|
+
const block = lines.join("\n").trim();
|
|
60
|
+
if (currentHeading === null) {
|
|
61
|
+
if (block)
|
|
62
|
+
sections["_preamble"] = block;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
sections[currentHeading] = block;
|
|
66
|
+
}
|
|
67
|
+
currentHeading = headingMatch[1].trim();
|
|
68
|
+
lines.length = 0;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
lines.push(line);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const block = lines.join("\n").trim();
|
|
75
|
+
if (currentHeading === null) {
|
|
76
|
+
if (block)
|
|
77
|
+
sections["_preamble"] = block;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
sections[currentHeading] = block;
|
|
81
|
+
}
|
|
82
|
+
return sections;
|
|
83
|
+
}
|
|
84
|
+
return body.trim();
|
|
85
|
+
}
|
|
86
|
+
function buildBody(value, bodyAs) {
|
|
87
|
+
if (bodyAs === "text") {
|
|
88
|
+
return value != null ? String(value) : "";
|
|
89
|
+
}
|
|
90
|
+
if (bodyAs === "list") {
|
|
91
|
+
if (!Array.isArray(value))
|
|
92
|
+
return "";
|
|
93
|
+
return value.map((item) => `- ${item}`).join("\n");
|
|
94
|
+
}
|
|
95
|
+
if (bodyAs === "sections") {
|
|
96
|
+
if (typeof value !== "object" || value === null)
|
|
97
|
+
return "";
|
|
98
|
+
const parts = [];
|
|
99
|
+
const dict = value;
|
|
100
|
+
if ("_preamble" in dict) {
|
|
101
|
+
parts.push(dict["_preamble"].trim());
|
|
102
|
+
}
|
|
103
|
+
for (const [heading, content] of Object.entries(dict)) {
|
|
104
|
+
if (heading === "_preamble")
|
|
105
|
+
continue;
|
|
106
|
+
parts.push(`## ${heading}\n\n${typeof content === "string" ? content.trim() : content}`);
|
|
107
|
+
}
|
|
108
|
+
return parts.join("\n\n");
|
|
109
|
+
}
|
|
110
|
+
return value != null ? String(value) : "";
|
|
111
|
+
}
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// GenericBundleReader
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
export class GenericBundleReader {
|
|
116
|
+
sd;
|
|
117
|
+
apiVersion;
|
|
118
|
+
kindName;
|
|
119
|
+
fs;
|
|
120
|
+
/** Exposed for deferred registration detection. */
|
|
121
|
+
_marker;
|
|
122
|
+
constructor(sd, apiVersion, kindName, fs = nodeFS) {
|
|
123
|
+
this.sd = sd;
|
|
124
|
+
this.apiVersion = apiVersion;
|
|
125
|
+
this.kindName = kindName;
|
|
126
|
+
this.fs = fs;
|
|
127
|
+
this._marker = sd.marker;
|
|
128
|
+
}
|
|
129
|
+
detect(bundle) {
|
|
130
|
+
const path = bundle.path ?? "";
|
|
131
|
+
return this.fs.exists(`${path}/${this.sd.marker}`);
|
|
132
|
+
}
|
|
133
|
+
read(bundle) {
|
|
134
|
+
const path = bundle.path ?? "";
|
|
135
|
+
const markerPath = `${path}/${this.sd.marker}`;
|
|
136
|
+
const text = this.fs.readFile(markerPath);
|
|
137
|
+
const [fm, body] = parseFrontmatter(text, markerPath);
|
|
138
|
+
const metadata = { name: path.split("/").pop() };
|
|
139
|
+
const spec = {};
|
|
140
|
+
for (const [k, v] of Object.entries(fm)) {
|
|
141
|
+
if (META_FIELDS.has(k))
|
|
142
|
+
metadata[k] = v;
|
|
143
|
+
else
|
|
144
|
+
spec[k] = v;
|
|
145
|
+
}
|
|
146
|
+
const bodyField = this.sd.bodyField ?? "content";
|
|
147
|
+
const bodyAs = this.sd.bodyAs ?? "text";
|
|
148
|
+
if (this.sd.bodyParser) {
|
|
149
|
+
Object.assign(spec, this.sd.bodyParser(body));
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
spec[bodyField] = parseBody(body, bodyAs);
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
apiVersion: this.apiVersion,
|
|
156
|
+
kind: this.kindName,
|
|
157
|
+
metadata,
|
|
158
|
+
spec,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// GenericBundleWriter
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
export class GenericBundleWriter {
|
|
166
|
+
fs;
|
|
167
|
+
/** Exposed for deferred registration detection. */
|
|
168
|
+
_kind;
|
|
169
|
+
/** Exposed for serialize() and deferred registration. */
|
|
170
|
+
_sd;
|
|
171
|
+
constructor(sd, kind, fs = nodeFS) {
|
|
172
|
+
this.fs = fs;
|
|
173
|
+
this._sd = sd;
|
|
174
|
+
this._kind = kind;
|
|
175
|
+
}
|
|
176
|
+
canWrite(raw) {
|
|
177
|
+
return raw.kind === this._kind;
|
|
178
|
+
}
|
|
179
|
+
write(bundle, raw) {
|
|
180
|
+
const path = bundle.path ?? "";
|
|
181
|
+
this.fs.mkdir(path);
|
|
182
|
+
const meta = (raw.metadata ?? {});
|
|
183
|
+
const spec = (raw.spec ?? {});
|
|
184
|
+
const bodyField = this._sd.bodyField ?? "content";
|
|
185
|
+
const bodyAs = this._sd.bodyAs ?? "text";
|
|
186
|
+
// Build frontmatter: metadata fields first, then spec fields (excluding body_field)
|
|
187
|
+
const fm = {};
|
|
188
|
+
for (const [k, v] of Object.entries(meta)) {
|
|
189
|
+
if (v != null)
|
|
190
|
+
fm[k] = v;
|
|
191
|
+
}
|
|
192
|
+
for (const [k, v] of Object.entries(spec)) {
|
|
193
|
+
if (k !== bodyField && v != null)
|
|
194
|
+
fm[k] = v;
|
|
195
|
+
}
|
|
196
|
+
const frontmatter = yaml.dump(fm, { flowLevel: -1, sortKeys: false });
|
|
197
|
+
const bodyValue = spec[bodyField];
|
|
198
|
+
const body = buildBody(bodyValue, bodyAs);
|
|
199
|
+
this.fs.writeFile(`${path}/${this._sd.marker}`, `---\n${frontmatter}---\n\n${body}`);
|
|
200
|
+
}
|
|
201
|
+
serialize(raw) {
|
|
202
|
+
const meta = (raw.metadata ?? {});
|
|
203
|
+
const spec = (raw.spec ?? {});
|
|
204
|
+
const fm = {};
|
|
205
|
+
for (const [k, v] of Object.entries(meta)) {
|
|
206
|
+
if (v != null)
|
|
207
|
+
fm[k] = v;
|
|
208
|
+
}
|
|
209
|
+
const bodyField = this._sd.bodyField;
|
|
210
|
+
for (const [k, v] of Object.entries(spec)) {
|
|
211
|
+
if (k !== bodyField && v != null)
|
|
212
|
+
fm[k] = v;
|
|
213
|
+
}
|
|
214
|
+
const frontmatter = yaml.dump(fm, { flowLevel: -1, sortKeys: false });
|
|
215
|
+
const bodyData = spec[bodyField];
|
|
216
|
+
let body;
|
|
217
|
+
if (this._sd.bodyAs === "list" && Array.isArray(bodyData)) {
|
|
218
|
+
body = bodyData.map((item) => `- ${item}`).join("\n");
|
|
219
|
+
}
|
|
220
|
+
else if (this._sd.bodyAs === "sections" && typeof bodyData === "object" && bodyData) {
|
|
221
|
+
const parts = [];
|
|
222
|
+
for (const [heading, content] of Object.entries(bodyData)) {
|
|
223
|
+
if (heading === "_preamble")
|
|
224
|
+
parts.unshift(content);
|
|
225
|
+
else
|
|
226
|
+
parts.push(`## ${heading}\n\n${content}`);
|
|
227
|
+
}
|
|
228
|
+
body = parts.join("\n\n");
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
body = String(bodyData ?? "");
|
|
232
|
+
}
|
|
233
|
+
return [{ relativePath: this._sd.marker, content: `---\n${frontmatter}---\n\n${body}` }];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HookRegistry — middleware (use) + events (on / onAsync) for kernel lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* 1:1 parity with Python dna.v3.kernel.hooks.
|
|
5
|
+
*
|
|
6
|
+
* Dual-channel events:
|
|
7
|
+
* - Sync listeners (legacy + in-process metrics/logging):
|
|
8
|
+
* k.on("post_build_prompt", fn) // fn: (ctx) => void
|
|
9
|
+
* k.hooks.emit("post_build_prompt", ctx)
|
|
10
|
+
* - Async listeners (I/O-heavy: event bus publishers, HTTP, DB writes):
|
|
11
|
+
* k.onAsync("post_save", fn) // fn: (ctx) => Promise<void>
|
|
12
|
+
* await k.hooks.emitAsync("post_save", ctx)
|
|
13
|
+
*
|
|
14
|
+
* Sync `on()` autodetects based on whether the handler returns a Promise.
|
|
15
|
+
* Callers that invoke `emit` (sync) skip async listeners — callers that
|
|
16
|
+
* invoke `emitAsync` run both channels. The skip is LOUD
|
|
17
|
+
* (s-kernel-fail-soft-audit, Py twin semantics): `emit` counts every skip
|
|
18
|
+
* in `skippedAsyncEmits` and console.warns ONCE per (hook, listener);
|
|
19
|
+
* `emit(hook, ctx, { strict: true })` throws instead — for call sites
|
|
20
|
+
* where skipping a listener would be a bug, not lost telemetry. The
|
|
21
|
+
* kernel's own sync emits (parse_error / kinddef_conflict /
|
|
22
|
+
* extension_error / sync-path post_build_prompt) are observability
|
|
23
|
+
* funnels and stay non-strict.
|
|
24
|
+
*
|
|
25
|
+
* Veto hooks (s-write-path-despecialize): ordered listeners that can BLOCK
|
|
26
|
+
* the operation by throwing. Unlike `emit`/`emitAsync` (fire-and-forget,
|
|
27
|
+
* errors swallowed), `emitVeto` runs listeners in ascending `priority`
|
|
28
|
+
* order and lets exceptions PROPAGATE — a throw IS the veto. Listeners may
|
|
29
|
+
* also mutate the typed context in place (e.g. fields on `ctx.raw`) — the
|
|
30
|
+
* write proceeds with the mutated payload.
|
|
31
|
+
*
|
|
32
|
+
* k.hooks.onVeto("pre_save", fn, { priority: 20, key: "helix.rule" });
|
|
33
|
+
* await k.hooks.emitVeto("pre_save", ctx);
|
|
34
|
+
*
|
|
35
|
+
* `key` makes registration idempotent: re-registering the same key REPLACES
|
|
36
|
+
* the previous listener (extensions may be re-loaded onto a shared registry).
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* The full vocabulary of hook names the SDK itself emits/consumes
|
|
40
|
+
* (s-dna-typed-hook-names). Py twin: `HookName` / `KNOWN_HOOK_NAMES` in
|
|
41
|
+
* dna/kernel/hooks.py. Both sides are locked to the shared fixture
|
|
42
|
+
* `tests/parity-fixtures/port-surface-parity.json` (section `hook_names`)
|
|
43
|
+
* by tests/hook-names.test.ts + tests/test_hook_names.py — vocabulary
|
|
44
|
+
* drift turns both suites red. Add a name HERE + fixture + Py, never ad hoc.
|
|
45
|
+
*/
|
|
46
|
+
export declare const KNOWN_HOOK_NAMES: readonly ["pre_build_prompt", "post_build_prompt", "pre_save", "post_save", "post_delete", "kinddef_conflict", "parse_error", "extension_error"];
|
|
47
|
+
export type HookName = (typeof KNOWN_HOOK_NAMES)[number];
|
|
48
|
+
/**
|
|
49
|
+
* Parameter type for hook-name arguments: the known vocabulary (with editor
|
|
50
|
+
* autocomplete) plus arbitrary strings for back-compat (custom hooks stay
|
|
51
|
+
* legal — they warn at runtime, never break). A misspelled builtin name
|
|
52
|
+
* like `on("pre_saev", fn)` used to compile, run, and never fire, silently;
|
|
53
|
+
* the registry now console.warns once per (registry, name).
|
|
54
|
+
*/
|
|
55
|
+
export type HookNameArg = HookName | (string & {});
|
|
56
|
+
export interface HookContext {
|
|
57
|
+
scope: string;
|
|
58
|
+
agent?: string;
|
|
59
|
+
kind?: string;
|
|
60
|
+
name?: string;
|
|
61
|
+
data: Record<string, unknown>;
|
|
62
|
+
prompt?: string;
|
|
63
|
+
/** Optional layer tuple when the write targeted a layer overlay.
|
|
64
|
+
* `undefined` means the write went to the scope's base. Set by
|
|
65
|
+
* Kernel.writeDocument when a layer kwarg is passed (Phase 2a.0). */
|
|
66
|
+
layer?: [string, string];
|
|
67
|
+
}
|
|
68
|
+
export type Middleware = (ctx: HookContext) => HookContext;
|
|
69
|
+
export type EventHandler = (ctx: HookContext) => void;
|
|
70
|
+
export type AsyncEventHandler = (ctx: HookContext) => Promise<void>;
|
|
71
|
+
/** Typed context for the `pre_save` veto hook (s-write-path-despecialize).
|
|
72
|
+
* Handed to every veto listener BEFORE the document reaches the source
|
|
73
|
+
* adapter. `raw` is the live payload — listeners may mutate it in place.
|
|
74
|
+
* `kernel` is the kernel the write flows through (tenant-bound copy when
|
|
75
|
+
* applicable) so guards can read (getDocument, ...). Throwing vetoes the
|
|
76
|
+
* write. Py twin: dna.kernel.hooks.PreSaveContext. */
|
|
77
|
+
export interface PreSaveContext {
|
|
78
|
+
scope: string;
|
|
79
|
+
kind: string;
|
|
80
|
+
name: string;
|
|
81
|
+
raw: Record<string, unknown>;
|
|
82
|
+
tenant: string | null;
|
|
83
|
+
layer?: [string, string];
|
|
84
|
+
kernel: unknown;
|
|
85
|
+
}
|
|
86
|
+
/** Veto listener: sync or async; throwing vetoes the operation. */
|
|
87
|
+
export type VetoHandler = (ctx: PreSaveContext) => void | Promise<void>;
|
|
88
|
+
export declare class HookRegistry {
|
|
89
|
+
private _middleware;
|
|
90
|
+
private _events;
|
|
91
|
+
private _asyncEvents;
|
|
92
|
+
/** Veto channel — hook → list of {priority, seq, key, fn}. `seq` keeps
|
|
93
|
+
* registration order stable among equal priorities. */
|
|
94
|
+
private _veto;
|
|
95
|
+
private _vetoSeq;
|
|
96
|
+
/** s-kernel-fail-soft-audit — sync-emit async-listener skip tracking:
|
|
97
|
+
* warn ONCE per (hook, listener) + count every skip per hook so a
|
|
98
|
+
* mis-wired listener is visible instead of an invisible no-op. */
|
|
99
|
+
private _asyncSkipWarned;
|
|
100
|
+
readonly skippedAsyncEmits: Map<string, number>;
|
|
101
|
+
/** s-dna-typed-hook-names — unknown-name warn-once tracking (per
|
|
102
|
+
* registry, per name; same posture as _asyncSkipWarned). */
|
|
103
|
+
private _unknownHookWarned;
|
|
104
|
+
/** Warn (once per registry+name) on a name outside `HookName`.
|
|
105
|
+
* Fail-loud, NOT fail-closed: custom hook names keep working
|
|
106
|
+
* (back-compat / third-party channels), but a typo of a builtin name —
|
|
107
|
+
* the listener that never fires — is now visible. Py twin semantics:
|
|
108
|
+
* `UnknownHookNameWarning`. */
|
|
109
|
+
private _checkHookName;
|
|
110
|
+
/** Register middleware. Called in order, each receives output of previous. */
|
|
111
|
+
use(hook: HookNameArg, fn: Middleware): void;
|
|
112
|
+
/**
|
|
113
|
+
* Register event subscriber. Fire-and-forget, errors logged but not raised.
|
|
114
|
+
*
|
|
115
|
+
* Autodetects sync vs async via `fn.constructor.name === "AsyncFunction"`.
|
|
116
|
+
* Async functions are routed to the async channel — callers using `emit`
|
|
117
|
+
* (sync) skip them; `emitAsync` runs both.
|
|
118
|
+
*/
|
|
119
|
+
on(hook: HookNameArg, fn: EventHandler | AsyncEventHandler): void;
|
|
120
|
+
/** Explicit async-only registration. Prefer `on()` — this is here for
|
|
121
|
+
* symmetry and for non-`async function` callables (arrow with Promise
|
|
122
|
+
* body, partials) where AsyncFunction detection returns false. */
|
|
123
|
+
onAsync(hook: HookNameArg, fn: AsyncEventHandler): void;
|
|
124
|
+
/** Run middleware chain. Returns modified context. */
|
|
125
|
+
runMiddleware(hook: HookNameArg, ctx: HookContext): HookContext;
|
|
126
|
+
/**
|
|
127
|
+
* Sync emit. Runs only sync listeners. If async listeners exist for this
|
|
128
|
+
* hook they CANNOT run here — the caller must use `emitAsync` to reach
|
|
129
|
+
* them. The skip is loud (s-kernel-fail-soft-audit):
|
|
130
|
+
*
|
|
131
|
+
* - default: each skipped listener is counted in `skippedAsyncEmits` and
|
|
132
|
+
* a console.warn fires once per (hook, listener) — a mis-wired async
|
|
133
|
+
* listener is visible, not an invisible no-op.
|
|
134
|
+
* - `strict: true`: throw instead — for emit sites where skipping a
|
|
135
|
+
* listener would be a bug, not lost telemetry.
|
|
136
|
+
*/
|
|
137
|
+
emit(hook: HookNameArg, ctx: HookContext, opts?: {
|
|
138
|
+
strict?: boolean;
|
|
139
|
+
}): void;
|
|
140
|
+
/**
|
|
141
|
+
* Async emit. Runs sync listeners inline, then awaits each async listener.
|
|
142
|
+
* Use from async call sites (kernel.writeDocument, kernel.deleteDocument).
|
|
143
|
+
* Errors in any listener are logged and do NOT prevent other listeners.
|
|
144
|
+
*/
|
|
145
|
+
emitAsync(hook: HookNameArg, ctx: HookContext): Promise<void>;
|
|
146
|
+
/**
|
|
147
|
+
* Register a veto listener on `hook`. Listeners run in ascending
|
|
148
|
+
* `priority` (ties keep registration order). Throwing from a listener
|
|
149
|
+
* PROPAGATES to the emitter — that is the veto. Sync and async callables
|
|
150
|
+
* both work.
|
|
151
|
+
*
|
|
152
|
+
* `key` (recommended: `"<extension>.<rule>"`) makes registration
|
|
153
|
+
* idempotent — a second `onVeto` with the same key REPLACES the earlier
|
|
154
|
+
* listener instead of stacking a duplicate.
|
|
155
|
+
*/
|
|
156
|
+
onVeto(hook: HookNameArg, fn: VetoHandler, opts?: {
|
|
157
|
+
priority?: number;
|
|
158
|
+
key?: string;
|
|
159
|
+
}): void;
|
|
160
|
+
/**
|
|
161
|
+
* Run veto listeners for `hook` in priority order. Unlike
|
|
162
|
+
* `emit`/`emitAsync`, exceptions are NOT swallowed — the first throw
|
|
163
|
+
* aborts the chain and propagates to the caller (the operation is
|
|
164
|
+
* vetoed). Listeners may mutate `ctx` in place.
|
|
165
|
+
*/
|
|
166
|
+
emitVeto(hook: HookNameArg, ctx: PreSaveContext): Promise<void>;
|
|
167
|
+
/** Check if any veto listeners are registered for `hook`. */
|
|
168
|
+
hasVeto(hook: string): boolean;
|
|
169
|
+
/** Check if any middleware, events (sync or async) or veto listeners are
|
|
170
|
+
* registered. */
|
|
171
|
+
has(hook: string): boolean;
|
|
172
|
+
}
|