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,292 @@
1
+ /**
2
+ * ResearchExtension — curated research syntheses with evidence ratings (TS twin).
3
+ *
4
+ * 1:1 parity with python/dna/extensions/research/__init__.py.
5
+ *
6
+ * Storage layout per doc:
7
+ *
8
+ * .dna/<scope>/research/<name>/
9
+ * RESEARCH.md ← marker (frontmatter = spec)
10
+ *
11
+ * A Research synthesizes N Reference docs with objective, methodology,
12
+ * evidence-rated findings, and priority recommendations — agent-facing
13
+ * knowledge WITH provenance (the declarative counter to LLM-generated
14
+ * repo-wiki prose).
15
+ *
16
+ * Tenancy: PERMISSIVE — no `scope` attribute declared, so the write
17
+ * pipeline treats it as permissive (base writes with or without a
18
+ * tenant). Research is repo-authored knowledge, not per-client data.
19
+ *
20
+ * The Reference companion Kind is provided by the sdlc extension
21
+ * (alias sdlc-reference); it is NOT registered here.
22
+ */
23
+ import yaml from "js-yaml";
24
+ import { KindBase } from "../kernel/kind_base.js";
25
+ import { SD } from "../kernel/protocols.js";
26
+ import { writeEntriesToHandle } from "../kernel/writer-helpers.js";
27
+ const API_VERSION = "github.com/ruinosus/dna/research/v1";
28
+ export const METHODOLOGIES = [
29
+ "web-search-curated",
30
+ "literature-review",
31
+ "interview",
32
+ "field-study",
33
+ "experiment",
34
+ "synthesis",
35
+ "other",
36
+ ];
37
+ export const EVIDENCE_RATINGS = ["evidence-based", "opinion-practice", "anecdotal"];
38
+ export const STATUSES = ["brief", "ready", "draft", "published", "superseded", "retracted"];
39
+ export const VISIBILITY = ["scope-private", "shared"];
40
+ function parseFrontmatter(text) {
41
+ const m = text.match(/^---\n([\s\S]*?)---\n?([\s\S]*)$/);
42
+ if (!m)
43
+ return { fm: {}, body: text };
44
+ try {
45
+ const parsed = yaml.load(m[1]);
46
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
47
+ return { fm: parsed, body: (m[2] ?? "").replace(/^\n+/, "") };
48
+ }
49
+ }
50
+ catch {
51
+ // fall through
52
+ }
53
+ return { fm: {}, body: text };
54
+ }
55
+ // ---------------------------------------------------------------------------
56
+ // Research Kind
57
+ // ---------------------------------------------------------------------------
58
+ class ResearchKind extends KindBase {
59
+ apiVersion = API_VERSION;
60
+ kind = "Research";
61
+ // alias generated as <owner>-<kebab(kind)> = "research-research" (owner =
62
+ // extension name). New Kinds must not TYPE an explicit alias (guard:
63
+ // tests/alias-generation.test.ts, EXPLICIT_ALIAS_ALLOWLIST). Empty here
64
+ // keeps the KindPort shape while triggering generation at register time.
65
+ alias = "";
66
+ aliasOwner = "research";
67
+ origin = "github.com/ruinosus/dna/research";
68
+ // PERMISSIVE tenancy — NO `scope` declared (base writes with or without tenant).
69
+ isPromptTarget = false;
70
+ promptTargetPriority = 0;
71
+ flattenInContext = false;
72
+ storage = SD.bundle("research", "RESEARCH.md");
73
+ graphStyle = { fill: "#7C3AED", stroke: "#5B21B6", textColor: "#fff" };
74
+ asciiIcon = "🔬";
75
+ displayLabel = "Research";
76
+ docs = "A Research is a curated synthesis of N external sources " +
77
+ "(Reference docs) with objective, methodology, evidence-rated " +
78
+ "findings, and priority recommendations. Designed for " +
79
+ "auditability + agent consumption.";
80
+ dependencies() { return null; }
81
+ schema() {
82
+ return {
83
+ type: "object",
84
+ required: ["title", "objective", "methodology", "status"],
85
+ additionalProperties: true,
86
+ properties: {
87
+ title: { type: "string" },
88
+ objective: { type: "string" },
89
+ executive_summary: { type: "string" },
90
+ key_takeaways: { type: "array", items: { type: "string" }, default: [] },
91
+ overall_confidence: { type: "string", enum: ["high", "moderate", "low", "very-low"] },
92
+ last_reviewed_at: { type: "string", format: "date-time" },
93
+ next_review_due: { type: "string", format: "date-time" },
94
+ methodology: { type: "string", enum: [...METHODOLOGIES], default: "web-search-curated" },
95
+ conducted_by: { type: "string" },
96
+ conducted_at: { type: "string", format: "date-time" },
97
+ scope_ref: { type: "string" },
98
+ visibility: { type: "string", enum: [...VISIBILITY], default: "scope-private" },
99
+ sources: {
100
+ type: "array",
101
+ items: { type: "string" },
102
+ default: [],
103
+ },
104
+ findings: {
105
+ type: "array",
106
+ items: {
107
+ type: "object",
108
+ required: ["id", "title", "evidence_rating"],
109
+ additionalProperties: true,
110
+ properties: {
111
+ id: { type: "string", pattern: "^f-[a-z0-9-]+$" },
112
+ title: { type: "string" },
113
+ summary: { type: "string" },
114
+ evidence_rating: { type: "string", enum: [...EVIDENCE_RATINGS] },
115
+ source_refs: { type: "array", items: { type: "string" }, default: [] },
116
+ tags: { type: "array", items: { type: "string" }, default: [] },
117
+ },
118
+ },
119
+ default: [],
120
+ },
121
+ recommendations: {
122
+ type: "array",
123
+ items: {
124
+ type: "object",
125
+ required: ["id", "priority", "summary"],
126
+ additionalProperties: true,
127
+ properties: {
128
+ id: { type: "string", pattern: "^rec-[a-z0-9-]+$" },
129
+ priority: { type: "string", enum: ["high", "medium", "low"] },
130
+ summary: { type: "string" },
131
+ effort_hours: { type: "number" },
132
+ clinical_decision: { type: "boolean", default: false },
133
+ depends_on: { type: "array", items: { type: "string" }, default: [] },
134
+ backed_by_findings: { type: "array", items: { type: "string" }, default: [] },
135
+ status: {
136
+ type: "string",
137
+ enum: ["proposed", "accepted", "rejected", "implemented", "blocked"],
138
+ default: "proposed",
139
+ },
140
+ },
141
+ },
142
+ default: [],
143
+ },
144
+ status: { type: "string", enum: [...STATUSES], default: "draft" },
145
+ superseded_by: { type: "string" },
146
+ retracted_reason: { type: "string" },
147
+ audience_context: { type: "string" },
148
+ research_blocks: {
149
+ type: "array",
150
+ items: {
151
+ type: "object",
152
+ additionalProperties: true,
153
+ properties: {
154
+ title: { type: "string" },
155
+ questions: { type: "array", items: { type: "string" } },
156
+ },
157
+ },
158
+ default: [],
159
+ },
160
+ output_constraints: { type: "array", items: { type: "string" }, default: [] },
161
+ reference_baselines: { type: "array", items: { type: "string" }, default: [] },
162
+ brief_notes: { type: "string" },
163
+ tags: { type: "array", items: { type: "string" }, default: [] },
164
+ owner: { type: "string" },
165
+ created_at: { type: "string", format: "date-time" },
166
+ updated_at: { type: "string", format: "date-time" },
167
+ },
168
+ };
169
+ }
170
+ parse(raw) {
171
+ return raw;
172
+ }
173
+ describe(doc) {
174
+ const spec = (doc.spec ?? {});
175
+ const title = spec["title"] ?? "?";
176
+ const status = spec["status"] ?? "draft";
177
+ return `${title} [${status}]`;
178
+ }
179
+ summary(doc) {
180
+ const spec = (doc.spec ?? {});
181
+ const findings = spec["findings"] ?? [];
182
+ const recs = spec["recommendations"] ?? [];
183
+ const evFindings = findings.filter((f) => f.evidence_rating === "evidence-based").length;
184
+ return {
185
+ title: spec["title"] ?? "",
186
+ methodology: spec["methodology"] ?? "",
187
+ status: spec["status"] ?? "draft",
188
+ sources_count: (spec["sources"] ?? []).length,
189
+ findings_count: findings.length,
190
+ evidence_based_count: evFindings,
191
+ recommendations_count: recs.length,
192
+ };
193
+ }
194
+ promptTemplate() { return null; }
195
+ }
196
+ // ---------------------------------------------------------------------------
197
+ // Reader / Writer
198
+ // ---------------------------------------------------------------------------
199
+ class ResearchReader {
200
+ async detect(bundle) {
201
+ return bundle.exists("RESEARCH.md");
202
+ }
203
+ async read(bundle) {
204
+ const text = await bundle.readText("RESEARCH.md");
205
+ const { fm } = parseFrontmatter(text);
206
+ if (fm &&
207
+ typeof fm === "object" &&
208
+ "spec" in fm &&
209
+ fm["spec"] &&
210
+ typeof fm["spec"] === "object") {
211
+ const metadata = fm["metadata"] ?? {};
212
+ if (!metadata["name"])
213
+ metadata["name"] = bundle.name;
214
+ return {
215
+ apiVersion: fm["apiVersion"] ?? API_VERSION,
216
+ kind: fm["kind"] ?? "Research",
217
+ metadata,
218
+ spec: fm["spec"],
219
+ };
220
+ }
221
+ return {
222
+ apiVersion: API_VERSION,
223
+ kind: "Research",
224
+ metadata: { name: bundle.name },
225
+ spec: fm,
226
+ };
227
+ }
228
+ }
229
+ class ResearchWriter {
230
+ canWrite(raw) {
231
+ return raw["kind"] === "Research";
232
+ }
233
+ serialize(raw) {
234
+ const spec = raw["spec"] ?? {};
235
+ const meta = raw["metadata"] ?? {};
236
+ const cleanSpec = {};
237
+ for (const [k, v] of Object.entries(spec)) {
238
+ if (v === null || v === undefined || v === "")
239
+ continue;
240
+ if (Array.isArray(v) && v.length === 0)
241
+ continue;
242
+ if (typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0)
243
+ continue;
244
+ cleanSpec[k] = v;
245
+ }
246
+ const cleanMeta = {};
247
+ for (const [k, v] of Object.entries(meta)) {
248
+ if (v !== null && v !== undefined)
249
+ cleanMeta[k] = v;
250
+ }
251
+ const envelope = {
252
+ apiVersion: raw["apiVersion"] ?? API_VERSION,
253
+ kind: raw["kind"] ?? "Research",
254
+ metadata: cleanMeta,
255
+ spec: cleanSpec,
256
+ };
257
+ const fmYaml = yaml.dump(envelope, {
258
+ sortKeys: false,
259
+ lineWidth: 100,
260
+ noRefs: true,
261
+ noCompatMode: true,
262
+ }).replace(/\n+$/, "");
263
+ const title = cleanSpec["title"] ?? "?";
264
+ const method = cleanSpec["methodology"] ?? "synthesis";
265
+ const nFindings = (cleanSpec["findings"] ?? []).length;
266
+ const nSources = (cleanSpec["sources"] ?? []).length;
267
+ const body = `# Research — ${title}\n\n` +
268
+ `Methodology: ${method} · ${nSources} sources · ${nFindings} findings.\n\n` +
269
+ `This file's spec (frontmatter above) is the authoritative ` +
270
+ `data. The prose below is for human reading and is regenerated ` +
271
+ `on each write.\n`;
272
+ return [
273
+ { relativePath: "RESEARCH.md", content: `---\n${fmYaml}\n---\n\n${body}` },
274
+ ];
275
+ }
276
+ async write(bundle, raw) {
277
+ await writeEntriesToHandle(bundle, this.serialize(raw));
278
+ }
279
+ }
280
+ // ---------------------------------------------------------------------------
281
+ // Extension
282
+ // ---------------------------------------------------------------------------
283
+ export class ResearchExtension {
284
+ name = "research";
285
+ version = "1.2.0";
286
+ register(kernel) {
287
+ kernel.kind(new ResearchKind());
288
+ // Reference companion is provided by the sdlc extension — not here.
289
+ kernel.reader(new ResearchReader());
290
+ kernel.writer(new ResearchWriter());
291
+ }
292
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * SafetyPolicyExtension — SafetyPolicy kind + SafetyPolicyReader + SafetyPolicyWriter.
3
+ *
4
+ * Declarative safety enforcement rules. Tier 1 (regex) is built-in;
5
+ * heavier tiers (ml, api, llm_judge) are opt-in.
6
+ *
7
+ * 1:1 parity with Python dna.v3.extensions.safety.
8
+ */
9
+ import type { BundleHandle } from "../kernel/bundle-handle.js";
10
+ import type { FSLike } from "../kernel/fs.js";
11
+ import type { ExtensionHost, Extension, ReaderPort, SerializedFile, WriterPort } from "../kernel/protocols.js";
12
+ export declare class SafetyPolicyReader implements ReaderPort {
13
+ private fs;
14
+ readonly _marker = "SAFETYPOLICY.md";
15
+ constructor(fs?: FSLike);
16
+ detect(bundle: BundleHandle): boolean;
17
+ read(bundle: BundleHandle): Record<string, unknown>;
18
+ }
19
+ export declare class SafetyPolicyWriter implements WriterPort {
20
+ private fs;
21
+ readonly _kind = "SafetyPolicy";
22
+ constructor(fs?: FSLike);
23
+ canWrite(raw: Record<string, unknown>): boolean;
24
+ write(bundle: BundleHandle, raw: Record<string, unknown>): void;
25
+ serialize(raw: Record<string, unknown>): SerializedFile[];
26
+ }
27
+ export declare class SafetyPolicyExtension implements Extension {
28
+ private fs;
29
+ readonly name = "safety";
30
+ readonly version = "1.0.0";
31
+ constructor(fs?: FSLike);
32
+ register(kernel: ExtensionHost): void;
33
+ }
@@ -0,0 +1,230 @@
1
+ /**
2
+ * SafetyPolicyExtension — SafetyPolicy kind + SafetyPolicyReader + SafetyPolicyWriter.
3
+ *
4
+ * Declarative safety enforcement rules. Tier 1 (regex) is built-in;
5
+ * heavier tiers (ml, api, llm_judge) are opt-in.
6
+ *
7
+ * 1:1 parity with Python dna.v3.extensions.safety.
8
+ */
9
+ import yaml from "js-yaml";
10
+ import { nodeFS } from "../kernel/fs.js";
11
+ import { KindBase } from "../kernel/kind_base.js";
12
+ import { SafetyPolicySchema, SafetyPolicySpecSchema, zodSpecToJsonSchema } from "../kernel/models.js";
13
+ import { SD } from "../kernel/protocols.js";
14
+ // ---------------------------------------------------------------------------
15
+ // SafetyPolicyKind
16
+ // ---------------------------------------------------------------------------
17
+ class SafetyPolicyKind extends KindBase {
18
+ apiVersion = "github.com/ruinosus/dna/v1";
19
+ kind = "SafetyPolicy";
20
+ alias = "helix-safety-policy";
21
+ isSchemaAffecting = true;
22
+ origin = "github.com/ruinosus/dna";
23
+ isPromptTarget = false;
24
+ promptTargetPriority = 0;
25
+ flattenInContext = false;
26
+ descriptionFallbackField = "instruction";
27
+ storage = SD.bundle("safety", "SAFETYPOLICY.md", "list", "rules");
28
+ graphStyle = { fill: "#DC2626", stroke: "#B91C1C", textColor: "#fff" };
29
+ asciiIcon = "🔒";
30
+ displayLabel = "Safety Policies";
31
+ _sourceUrl = import.meta.url;
32
+ docs = "A SafetyPolicy declares runtime enforcement rules for PII masking, " +
33
+ "content safety, topic restriction, prompt injection detection, and " +
34
+ "custom regex patterns. Rules are tiered: regex (built-in), ml, api, " +
35
+ "llm_judge. Stored as a bundle rooted on SAFETYPOLICY.md.";
36
+ uiSchema = {
37
+ scope: {
38
+ widget: "select",
39
+ label: "Scope",
40
+ help: "input guards the user prompt; output guards the agent response; both runs on each side.",
41
+ options: ["input", "output", "both"],
42
+ order: 1,
43
+ },
44
+ action: {
45
+ widget: "select",
46
+ label: "Action",
47
+ help: "mask: redact PII inline; block: reject the message; log: pass through with violation metadata.",
48
+ options: ["mask", "block", "log"],
49
+ order: 2,
50
+ },
51
+ severity: {
52
+ widget: "select",
53
+ label: "Severity",
54
+ help: "error fails the turn; warn lets the turn continue.",
55
+ options: ["error", "warn"],
56
+ order: 3,
57
+ },
58
+ rules: {
59
+ widget: "code",
60
+ language: "yaml",
61
+ label: "Rules",
62
+ help: "YAML list of safety rules (type, tier, entities, patterns, etc.)",
63
+ height: 300,
64
+ order: 4,
65
+ },
66
+ };
67
+ depFilters() { return { recognizers: "presidio-recognizer" }; }
68
+ schema() { return zodSpecToJsonSchema(SafetyPolicySpecSchema); }
69
+ parse(raw) {
70
+ return SafetyPolicySchema.parse(raw);
71
+ }
72
+ describe(doc) {
73
+ const spec = (doc.spec ?? {});
74
+ return `SafetyPolicy: ${doc.name} (scope=${spec.scope}, action=${spec.action})`;
75
+ }
76
+ summary(doc) {
77
+ const spec = (doc.spec ?? {});
78
+ return {
79
+ scope: typeof spec.scope === "string" ? spec.scope : "both",
80
+ action: typeof spec.action === "string" ? spec.action : "mask",
81
+ severity: typeof spec.severity === "string" ? spec.severity : "error",
82
+ rules: Array.isArray(spec.rules) ? spec.rules.length : 0,
83
+ };
84
+ }
85
+ preview(doc) {
86
+ const spec = (doc.spec ?? {});
87
+ const blocks = [];
88
+ // Policy fields
89
+ const meta = [];
90
+ if (typeof spec.scope === "string")
91
+ meta.push({ label: "Scope", value: spec.scope });
92
+ if (typeof spec.action === "string")
93
+ meta.push({ label: "Action", value: spec.action });
94
+ if (typeof spec.severity === "string")
95
+ meta.push({ label: "Severity", value: spec.severity });
96
+ if (meta.length > 0) {
97
+ blocks.push({ kind: "fields", title: "Policy", fields: meta });
98
+ }
99
+ // Rules as YAML code block
100
+ const rules = spec.rules;
101
+ if (Array.isArray(rules) && rules.length > 0) {
102
+ blocks.push({
103
+ kind: "code",
104
+ title: "Rules",
105
+ body: yaml.dump(rules, { flowLevel: 3 }).trim(),
106
+ language: "yaml",
107
+ });
108
+ }
109
+ if (blocks.length === 0) {
110
+ return [{ kind: "empty", title: "SafetyPolicy (empty)" }];
111
+ }
112
+ return blocks;
113
+ }
114
+ }
115
+ // ---------------------------------------------------------------------------
116
+ // SafetyPolicyReader
117
+ // ---------------------------------------------------------------------------
118
+ function parseFrontmatter(text) {
119
+ const match = text.match(/^---\n([\s\S]*?)---\n?/);
120
+ if (!match)
121
+ return [{}, text];
122
+ let fm = {};
123
+ try {
124
+ const parsed = yaml.load(match[1]);
125
+ if (typeof parsed === "object" && parsed !== null) {
126
+ fm = parsed;
127
+ }
128
+ }
129
+ catch { /* ignore */ }
130
+ const body = text.slice(match[0].length);
131
+ return [fm, body];
132
+ }
133
+ export class SafetyPolicyReader {
134
+ fs;
135
+ _marker = "SAFETYPOLICY.md";
136
+ constructor(fs = nodeFS) {
137
+ this.fs = fs;
138
+ }
139
+ detect(bundle) {
140
+ const path = bundle.path ?? "";
141
+ return this.fs.exists(`${path}/SAFETYPOLICY.md`);
142
+ }
143
+ read(bundle) {
144
+ const path = bundle.path ?? "";
145
+ const text = this.fs.readFile(`${path}/SAFETYPOLICY.md`);
146
+ const [fm, body] = parseFrontmatter(text);
147
+ const name = String(fm.name || "") || path.split("/").pop() || "";
148
+ const description = String(fm.description || "");
149
+ const scope = String(fm.scope || "both");
150
+ const action = String(fm.action || "mask");
151
+ const severity = String(fm.severity || "error");
152
+ // Parse body as YAML list of rules
153
+ let rules = [];
154
+ const trimmedBody = body.trim();
155
+ if (trimmedBody) {
156
+ try {
157
+ const parsed = yaml.load(trimmedBody);
158
+ if (Array.isArray(parsed)) {
159
+ rules = parsed;
160
+ }
161
+ }
162
+ catch { /* ignore invalid YAML */ }
163
+ }
164
+ return {
165
+ apiVersion: "github.com/ruinosus/dna/v1",
166
+ kind: "SafetyPolicy",
167
+ metadata: { name, description },
168
+ spec: { scope, action, severity, rules },
169
+ };
170
+ }
171
+ }
172
+ // ---------------------------------------------------------------------------
173
+ // SafetyPolicyWriter
174
+ // ---------------------------------------------------------------------------
175
+ export class SafetyPolicyWriter {
176
+ fs;
177
+ _kind = "SafetyPolicy";
178
+ constructor(fs = nodeFS) {
179
+ this.fs = fs;
180
+ }
181
+ canWrite(raw) {
182
+ return raw.kind === "SafetyPolicy";
183
+ }
184
+ write(bundle, raw) {
185
+ const path = bundle.path ?? "";
186
+ this.fs.mkdir(path);
187
+ for (const f of this.serialize(raw)) {
188
+ this.fs.writeFile(`${path}/${f.relativePath}`, f.content ?? "");
189
+ }
190
+ }
191
+ serialize(raw) {
192
+ const meta = (raw.metadata ?? {});
193
+ const spec = (raw.spec ?? {});
194
+ const fmParts = [];
195
+ if (meta.name)
196
+ fmParts.push(`name: ${meta.name}`);
197
+ if (meta.description)
198
+ fmParts.push(`description: ${meta.description}`);
199
+ fmParts.push(`scope: ${spec.scope ?? "both"}`);
200
+ fmParts.push(`action: ${spec.action ?? "mask"}`);
201
+ fmParts.push(`severity: ${spec.severity ?? "error"}`);
202
+ const frontmatter = fmParts.join("\n");
203
+ // Serialize rules as YAML list
204
+ const rules = spec.rules ?? [];
205
+ let body = "";
206
+ if (rules.length > 0) {
207
+ body = yaml.dump(rules, { flowLevel: 3 }).trim();
208
+ }
209
+ const content = body
210
+ ? `---\n${frontmatter}\n---\n\n${body}\n`
211
+ : `---\n${frontmatter}\n---\n`;
212
+ return [{ relativePath: "SAFETYPOLICY.md", content }];
213
+ }
214
+ }
215
+ // ---------------------------------------------------------------------------
216
+ // Extension
217
+ // ---------------------------------------------------------------------------
218
+ export class SafetyPolicyExtension {
219
+ fs;
220
+ name = "safety";
221
+ version = "1.0.0";
222
+ constructor(fs = nodeFS) {
223
+ this.fs = fs;
224
+ }
225
+ register(kernel) {
226
+ kernel.kind(new SafetyPolicyKind());
227
+ kernel.reader(new SafetyPolicyReader(this.fs));
228
+ kernel.writer(new SafetyPolicyWriter(this.fs));
229
+ }
230
+ }
@@ -0,0 +1,136 @@
1
+ # ADR — Architecture Decision Record (Nygard 2011 / MADR), record plane.
2
+ # One ratified architectural decision per doc: context/decision/consequences.
3
+ # Split out from Narrative.decisions[] (Phase 2.2 vocabulary sanitization).
4
+ #
5
+ # F3 lote-2 (spec 2026-06-10-kinds-descriptor-f3): synthesized from this
6
+ # descriptor; the hand-written class twins were deleted. PARITY-CRITICAL
7
+ # package data — byte-identical mirror under packages/sdk-{py,ts};
8
+ # tests/test_descriptor_hash_parity.py enforces. Equivalence vs the extinct
9
+ # class frozen in tests/test_lote2_descriptor_equivalence.py (golden:
10
+ # tests/goldens/lote2/ADR.golden.json). Deltas intencionais (semântica
11
+ # canônica = port): summary curado substitui o eco whole-spec do KindBase
12
+ # default (list endpoints não usam kp.summary()); parse valida contra o
13
+ # schema (classe era pass-through); to_card morto não migrado.
14
+ apiVersion: github.com/ruinosus/dna/core/v1
15
+ kind: KindDefinition
16
+ metadata:
17
+ name: adr
18
+ spec:
19
+ target_api_version: github.com/ruinosus/dna/sdlc/v1
20
+ target_kind: ADR
21
+ alias: sdlc-adr
22
+ origin: github.com/ruinosus/dna/sdlc
23
+ tenant_scope: global
24
+ plane: record
25
+ prompt_target: false
26
+ flatten_in_context: false
27
+ prompt_target_priority: 0
28
+ storage:
29
+ type: bundle
30
+ container: adrs
31
+ marker: ADR.md
32
+ body_field: body
33
+ dep_filters:
34
+ superseded_by: sdlc-adr
35
+ supersedes: sdlc-adr
36
+ covers_features: sdlc-feature
37
+ schema:
38
+ type: object
39
+ required:
40
+ - title
41
+ - status
42
+ - context
43
+ - decision
44
+ properties:
45
+ title:
46
+ type: string
47
+ description: Decision headline — start with imperative verb.
48
+ status:
49
+ type: string
50
+ enum:
51
+ - proposed
52
+ - accepted
53
+ - deprecated
54
+ - superseded
55
+ description: 'Lifecycle: proposed → accepted → deprecated|superseded. Use
56
+ `superseded` (not deprecated) when a newer ADR replaces this one — link
57
+ via `superseded_by`.'
58
+ context:
59
+ type: string
60
+ description: WHY we needed to decide. What forces are in play? What constraints
61
+ (technical, business, team) shape the choice? 1-3 paragraphs.
62
+ decision:
63
+ type: string
64
+ description: 'WHAT we decided. Active voice: ''We will X'' or ''We chose X
65
+ over Y''. 1-2 paragraphs.'
66
+ consequences:
67
+ type: string
68
+ description: What follows from this decision — positive AND negative. Future
69
+ readers need to see the trade-offs, not just the wins.
70
+ alternatives_considered:
71
+ type: array
72
+ items:
73
+ type: string
74
+ description: Other options weighed and rejected, with brief why-not.
75
+ deciders:
76
+ type: array
77
+ items:
78
+ type: string
79
+ description: Actor names who participated in the decision.
80
+ date:
81
+ type: string
82
+ format: date
83
+ description: Date the decision was accepted (ISO-8601).
84
+ superseded_by:
85
+ type: string
86
+ description: ADR slug that supersedes this one (when status=superseded).
87
+ supersedes:
88
+ type: array
89
+ items:
90
+ type: string
91
+ description: ADR slugs this one replaces.
92
+ covers_features:
93
+ type: array
94
+ items:
95
+ type: string
96
+ description: Feature names this decision affects.
97
+ narrative_origin:
98
+ type: string
99
+ description: When extracted from a Narrative.decisions[] entry during Phase
100
+ 2.2 migration, this points to the source Narrative slug for provenance.
101
+ tags:
102
+ type: array
103
+ items:
104
+ type: string
105
+ description: Free-form tags (e.g. 'persistence', 'auth', 'ui').
106
+ body:
107
+ type: string
108
+ description: Optional full markdown body (ADR.md). When present, takes precedence
109
+ over the structured fields above for rendering. Useful when the ADR predates
110
+ this schema.
111
+ created_at:
112
+ type: string
113
+ format: date-time
114
+ updated_at:
115
+ type: string
116
+ format: date-time
117
+ summary:
118
+ title: ''
119
+ status: ''
120
+ date: ''
121
+ superseded_by: ''
122
+ embed:
123
+ - title
124
+ - context
125
+ - decision
126
+ graph_style:
127
+ fill: '#10B981'
128
+ stroke: '#047857'
129
+ text_color: '#fff'
130
+ ascii_icon: 📐
131
+ display_label: ADRs
132
+ docs: 'An ADR captures ONE architectural decision with its context, rationale, and
133
+ consequences. Convention: one ADR per file, immutable once accepted (subsequent
134
+ decisions supersede). Follows Nygard / MADR template — Adopt on ThoughtWorks Tech
135
+ Radar. Studio renders these as the decision log of the project; PMs/architects
136
+ can scan rationale without reading code.'