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.
Files changed (240) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -0
  3. package/dist/adapters/embedding/onnx.d.ts +32 -0
  4. package/dist/adapters/embedding/onnx.js +51 -0
  5. package/dist/adapters/filesystem/cache.d.ts +16 -0
  6. package/dist/adapters/filesystem/cache.js +113 -0
  7. package/dist/adapters/filesystem/index.d.ts +2 -0
  8. package/dist/adapters/filesystem/index.js +2 -0
  9. package/dist/adapters/filesystem/source.d.ts +82 -0
  10. package/dist/adapters/filesystem/source.js +357 -0
  11. package/dist/adapters/postgres/index.d.ts +2 -0
  12. package/dist/adapters/postgres/index.js +2 -0
  13. package/dist/adapters/postgres/migrations.d.ts +14 -0
  14. package/dist/adapters/postgres/migrations.js +88 -0
  15. package/dist/adapters/postgres/source.d.ts +172 -0
  16. package/dist/adapters/postgres/source.js +569 -0
  17. package/dist/adapters/resolvers/github.d.ts +12 -0
  18. package/dist/adapters/resolvers/github.js +47 -0
  19. package/dist/adapters/resolvers/http.d.ts +22 -0
  20. package/dist/adapters/resolvers/http.js +145 -0
  21. package/dist/adapters/resolvers/index.d.ts +3 -0
  22. package/dist/adapters/resolvers/index.js +3 -0
  23. package/dist/adapters/resolvers/local.d.ts +24 -0
  24. package/dist/adapters/resolvers/local.js +132 -0
  25. package/dist/adapters/search/driver.d.ts +45 -0
  26. package/dist/adapters/search/driver.js +138 -0
  27. package/dist/adapters/search/migrations.d.ts +26 -0
  28. package/dist/adapters/search/migrations.js +58 -0
  29. package/dist/adapters/search/rrf.d.ts +23 -0
  30. package/dist/adapters/search/rrf.js +42 -0
  31. package/dist/adapters/search/sqlite-vec.d.ts +66 -0
  32. package/dist/adapters/search/sqlite-vec.js +294 -0
  33. package/dist/bootstrap.d.ts +45 -0
  34. package/dist/bootstrap.js +134 -0
  35. package/dist/extensions/agentskills/DOCS.md +18 -0
  36. package/dist/extensions/agentskills.d.ts +28 -0
  37. package/dist/extensions/agentskills.js +252 -0
  38. package/dist/extensions/agentsmd/DOCS.md +18 -0
  39. package/dist/extensions/agentsmd.d.ts +22 -0
  40. package/dist/extensions/agentsmd.js +170 -0
  41. package/dist/extensions/audit/kinds/audit-log.kind.yaml +148 -0
  42. package/dist/extensions/audit.d.ts +25 -0
  43. package/dist/extensions/audit.js +62 -0
  44. package/dist/extensions/automation/kinds/automation.kind.yaml +296 -0
  45. package/dist/extensions/automation/query.d.ts +49 -0
  46. package/dist/extensions/automation/query.js +77 -0
  47. package/dist/extensions/automation/write-guards.d.ts +59 -0
  48. package/dist/extensions/automation/write-guards.js +153 -0
  49. package/dist/extensions/automation.d.ts +41 -0
  50. package/dist/extensions/automation.js +54 -0
  51. package/dist/extensions/collab.d.ts +16 -0
  52. package/dist/extensions/collab.js +79 -0
  53. package/dist/extensions/doc/kinds/doc.kind.yaml +118 -0
  54. package/dist/extensions/doc.d.ts +25 -0
  55. package/dist/extensions/doc.js +10 -0
  56. package/dist/extensions/eval/kinds/eval-baseline.kind.yaml +65 -0
  57. package/dist/extensions/eval/kinds/eval-case.kind.yaml +140 -0
  58. package/dist/extensions/eval/kinds/eval-run.kind.yaml +134 -0
  59. package/dist/extensions/eval/kinds/eval-suite.kind.yaml +87 -0
  60. package/dist/extensions/eval.d.ts +24 -0
  61. package/dist/extensions/eval.js +31 -0
  62. package/dist/extensions/evidence/kinds/evidence.kind.yaml +92 -0
  63. package/dist/extensions/evidence.d.ts +17 -0
  64. package/dist/extensions/evidence.js +94 -0
  65. package/dist/extensions/federation.d.ts +22 -0
  66. package/dist/extensions/federation.js +173 -0
  67. package/dist/extensions/guardrails/DOCS.md +20 -0
  68. package/dist/extensions/guardrails.d.ts +31 -0
  69. package/dist/extensions/guardrails.js +228 -0
  70. package/dist/extensions/helix/DOCS-Actor.md +20 -0
  71. package/dist/extensions/helix/DOCS-Agent.md +20 -0
  72. package/dist/extensions/helix/DOCS-UseCase.md +20 -0
  73. package/dist/extensions/helix/layers.d.ts +12 -0
  74. package/dist/extensions/helix/layers.js +18 -0
  75. package/dist/extensions/helix/write-guards.d.ts +70 -0
  76. package/dist/extensions/helix/write-guards.js +172 -0
  77. package/dist/extensions/helix.d.ts +29 -0
  78. package/dist/extensions/helix.js +963 -0
  79. package/dist/extensions/helix_extras.d.ts +124 -0
  80. package/dist/extensions/helix_extras.js +328 -0
  81. package/dist/extensions/hooks.d.ts +31 -0
  82. package/dist/extensions/hooks.js +247 -0
  83. package/dist/extensions/kinddef/DOCS.md +24 -0
  84. package/dist/extensions/kinddef.d.ts +40 -0
  85. package/dist/extensions/kinddef.js +171 -0
  86. package/dist/extensions/lesson.d.ts +6 -0
  87. package/dist/extensions/lesson.js +194 -0
  88. package/dist/extensions/modelreg/kinds/model-profile.kind.yaml +155 -0
  89. package/dist/extensions/modelreg.d.ts +30 -0
  90. package/dist/extensions/modelreg.js +38 -0
  91. package/dist/extensions/recognizer.d.ts +34 -0
  92. package/dist/extensions/recognizer.js +272 -0
  93. package/dist/extensions/research.d.ts +32 -0
  94. package/dist/extensions/research.js +292 -0
  95. package/dist/extensions/safety.d.ts +33 -0
  96. package/dist/extensions/safety.js +230 -0
  97. package/dist/extensions/sdlc/kinds/adr.kind.yaml +136 -0
  98. package/dist/extensions/sdlc/kinds/archive-proposal.kind.yaml +107 -0
  99. package/dist/extensions/sdlc/kinds/changelog.kind.yaml +108 -0
  100. package/dist/extensions/sdlc/kinds/cognitive-policy.kind.yaml +470 -0
  101. package/dist/extensions/sdlc/kinds/forecast.kind.yaml +125 -0
  102. package/dist/extensions/sdlc/kinds/insight.kind.yaml +87 -0
  103. package/dist/extensions/sdlc/kinds/kaizen.kind.yaml +106 -0
  104. package/dist/extensions/sdlc/kinds/lesson-learned.kind.yaml +270 -0
  105. package/dist/extensions/sdlc/kinds/narrative.kind.yaml +189 -0
  106. package/dist/extensions/sdlc/kinds/pattern-insight.kind.yaml +204 -0
  107. package/dist/extensions/sdlc/kinds/postmortem.kind.yaml +168 -0
  108. package/dist/extensions/sdlc/kinds/pre-mortem.kind.yaml +145 -0
  109. package/dist/extensions/sdlc/kinds/prompt-template.kind.yaml +109 -0
  110. package/dist/extensions/sdlc/kinds/retrospective.kind.yaml +186 -0
  111. package/dist/extensions/sdlc/kinds/risk-register.kind.yaml +178 -0
  112. package/dist/extensions/sdlc/kinds/saved-view.kind.yaml +127 -0
  113. package/dist/extensions/sdlc/kinds/status-report.kind.yaml +129 -0
  114. package/dist/extensions/sdlc/kinds/synthesis-run.kind.yaml +279 -0
  115. package/dist/extensions/sdlc/kinds/synthesizer-state.kind.yaml +196 -0
  116. package/dist/extensions/sdlc/kinds/workflow-event.kind.yaml +195 -0
  117. package/dist/extensions/sdlc.d.ts +32 -0
  118. package/dist/extensions/sdlc.js +1318 -0
  119. package/dist/extensions/soulspec/DOCS.md +19 -0
  120. package/dist/extensions/soulspec.d.ts +22 -0
  121. package/dist/extensions/soulspec.js +287 -0
  122. package/dist/extensions/tenant.d.ts +6 -0
  123. package/dist/extensions/tenant.js +291 -0
  124. package/dist/extensions/testkit.d.ts +24 -0
  125. package/dist/extensions/testkit.js +211 -0
  126. package/dist/index.d.ts +80 -0
  127. package/dist/index.js +102 -0
  128. package/dist/kernel/_text.d.ts +28 -0
  129. package/dist/kernel/_text.js +42 -0
  130. package/dist/kernel/bundle-handle.d.ts +83 -0
  131. package/dist/kernel/bundle-handle.js +184 -0
  132. package/dist/kernel/capabilities.d.ts +157 -0
  133. package/dist/kernel/capabilities.js +109 -0
  134. package/dist/kernel/collaborator-ports.d.ts +91 -0
  135. package/dist/kernel/collaborator-ports.js +1 -0
  136. package/dist/kernel/composition-resolver.d.ts +215 -0
  137. package/dist/kernel/composition-resolver.js +558 -0
  138. package/dist/kernel/composition.d.ts +10 -0
  139. package/dist/kernel/composition.js +1 -0
  140. package/dist/kernel/descriptor-loader.d.ts +13 -0
  141. package/dist/kernel/descriptor-loader.js +48 -0
  142. package/dist/kernel/document.d.ts +43 -0
  143. package/dist/kernel/document.js +71 -0
  144. package/dist/kernel/embedding.d.ts +28 -0
  145. package/dist/kernel/embedding.js +78 -0
  146. package/dist/kernel/errors.d.ts +62 -0
  147. package/dist/kernel/errors.js +82 -0
  148. package/dist/kernel/events.d.ts +6 -0
  149. package/dist/kernel/events.js +21 -0
  150. package/dist/kernel/evidence-capture.d.ts +69 -0
  151. package/dist/kernel/evidence-capture.js +138 -0
  152. package/dist/kernel/fs.d.ts +20 -0
  153. package/dist/kernel/fs.js +128 -0
  154. package/dist/kernel/generic-rw.d.ts +33 -0
  155. package/dist/kernel/generic-rw.js +235 -0
  156. package/dist/kernel/hooks.d.ts +172 -0
  157. package/dist/kernel/hooks.js +256 -0
  158. package/dist/kernel/index.d.ts +624 -0
  159. package/dist/kernel/index.js +1638 -0
  160. package/dist/kernel/instance.d.ts +211 -0
  161. package/dist/kernel/instance.js +487 -0
  162. package/dist/kernel/kind-registry.d.ts +183 -0
  163. package/dist/kernel/kind-registry.js +710 -0
  164. package/dist/kernel/kind_base.d.ts +106 -0
  165. package/dist/kernel/kind_base.js +244 -0
  166. package/dist/kernel/layer-resolver.d.ts +48 -0
  167. package/dist/kernel/layer-resolver.js +284 -0
  168. package/dist/kernel/lock-manager.d.ts +21 -0
  169. package/dist/kernel/lock-manager.js +43 -0
  170. package/dist/kernel/lock.d.ts +38 -0
  171. package/dist/kernel/lock.js +115 -0
  172. package/dist/kernel/meta.d.ts +135 -0
  173. package/dist/kernel/meta.js +817 -0
  174. package/dist/kernel/models.d.ts +3089 -0
  175. package/dist/kernel/models.js +764 -0
  176. package/dist/kernel/navigator.d.ts +36 -0
  177. package/dist/kernel/navigator.js +132 -0
  178. package/dist/kernel/port-surface.d.ts +33 -0
  179. package/dist/kernel/port-surface.js +180 -0
  180. package/dist/kernel/preview.d.ts +60 -0
  181. package/dist/kernel/preview.js +54 -0
  182. package/dist/kernel/prompt-budget.d.ts +39 -0
  183. package/dist/kernel/prompt-budget.js +43 -0
  184. package/dist/kernel/prompt-builder.d.ts +25 -0
  185. package/dist/kernel/prompt-builder.js +237 -0
  186. package/dist/kernel/protocols.d.ts +751 -0
  187. package/dist/kernel/protocols.js +493 -0
  188. package/dist/kernel/reports.d.ts +26 -0
  189. package/dist/kernel/reports.js +215 -0
  190. package/dist/kernel/resolver.d.ts +146 -0
  191. package/dist/kernel/resolver.js +232 -0
  192. package/dist/kernel/resource.d.ts +57 -0
  193. package/dist/kernel/resource.js +97 -0
  194. package/dist/kernel/runtime.d.ts +16 -0
  195. package/dist/kernel/runtime.js +18 -0
  196. package/dist/kernel/safety-scanner.d.ts +38 -0
  197. package/dist/kernel/safety-scanner.js +228 -0
  198. package/dist/kernel/serialize-to-files.d.ts +29 -0
  199. package/dist/kernel/serialize-to-files.js +23 -0
  200. package/dist/kernel/spec-access.d.ts +23 -0
  201. package/dist/kernel/spec-access.js +40 -0
  202. package/dist/kernel/studio_ui.d.ts +88 -0
  203. package/dist/kernel/studio_ui.js +135 -0
  204. package/dist/kernel/templates.d.ts +77 -0
  205. package/dist/kernel/templates.js +116 -0
  206. package/dist/kernel/tool-registry.d.ts +52 -0
  207. package/dist/kernel/tool-registry.js +89 -0
  208. package/dist/kernel/write-pipeline.d.ts +65 -0
  209. package/dist/kernel/write-pipeline.js +271 -0
  210. package/dist/kernel/writer-helpers.d.ts +24 -0
  211. package/dist/kernel/writer-helpers.js +42 -0
  212. package/dist/memory/decay.d.ts +31 -0
  213. package/dist/memory/decay.js +97 -0
  214. package/dist/memory/ecphory.d.ts +25 -0
  215. package/dist/memory/ecphory.js +178 -0
  216. package/dist/memory/encodingContext.d.ts +21 -0
  217. package/dist/memory/encodingContext.js +62 -0
  218. package/dist/memory/index.d.ts +23 -0
  219. package/dist/memory/index.js +23 -0
  220. package/dist/memory/memoryType.d.ts +7 -0
  221. package/dist/memory/memoryType.js +24 -0
  222. package/dist/memory/policy.d.ts +35 -0
  223. package/dist/memory/policy.js +35 -0
  224. package/dist/memory/retrieval.d.ts +21 -0
  225. package/dist/memory/retrieval.js +145 -0
  226. package/dist/testing/index.d.ts +8 -0
  227. package/dist/testing/index.js +8 -0
  228. package/dist/testing/recordSearchConformance.d.ts +43 -0
  229. package/dist/testing/recordSearchConformance.js +147 -0
  230. package/dist/viz/ascii.d.ts +7 -0
  231. package/dist/viz/ascii.js +60 -0
  232. package/dist/viz/health.d.ts +8 -0
  233. package/dist/viz/health.js +161 -0
  234. package/dist/viz/index.d.ts +10 -0
  235. package/dist/viz/index.js +10 -0
  236. package/dist/viz/matrix.d.ts +8 -0
  237. package/dist/viz/matrix.js +84 -0
  238. package/dist/viz/mermaid.d.ts +38 -0
  239. package/dist/viz/mermaid.js +706 -0
  240. 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
+ }