dna-sdk 0.4.0 → 0.5.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.
@@ -13,6 +13,33 @@ import { readSpecString, readSpecStringArray } from "../kernel/spec-access.js";
13
13
  import { SettingKind, ThemeKind, UserProfileKind, CanvasKind } from "./helix_extras.js";
14
14
  import { registerWriteGuards } from "./helix/write-guards.js";
15
15
  const MOD_URL = import.meta.url;
16
+ // ── Named composition layouts (s-dx-named-layouts) ─────────────────────
17
+ //
18
+ // An author orders persona-vs-instruction by NAME (`layout:` in the Agent
19
+ // spec) instead of hand-writing raw Mustache with internal section names.
20
+ // Each preset resolves to one of these embedded templates via
21
+ // `AgentKind.layoutTemplate()`. 1:1 with Python `dna.extensions.helix`.
22
+ //
23
+ // The guardrails block is shared verbatim — guardrails are hard policy and
24
+ // always land LAST, after both the instruction and the soul, regardless of
25
+ // their relative order. (Aligns TS composition to Python, closing the
26
+ // pre-existing i-213/i-011 divergence where TS omitted the guardrails block.)
27
+ const GUARDRAILS_BLOCK = "{{#guardrails-guardrail}}" +
28
+ "## Guardrail: {{name}} ({{severity}})\n" +
29
+ "{{#description}}_{{description}}_\n\n{{/description}}" +
30
+ "{{#rules}}- {{{.}}}\n{{/rules}}\n" +
31
+ "{{/guardrails-guardrail}}";
32
+ // instruction-first (a.k.a. "default") — historic order: instruction, soul,
33
+ // guardrails. IS the kind default template.
34
+ const LAYOUT_INSTRUCTION_FIRST = "{{{agent.instruction}}}\n\n{{{soul_content}}}\n\n" + GUARDRAILS_BLOCK;
35
+ // persona-first — Soul leads, then instruction, then guardrails.
36
+ const LAYOUT_PERSONA_FIRST = "{{{soul_content}}}\n\n{{{agent.instruction}}}\n\n" + GUARDRAILS_BLOCK;
37
+ const AGENT_LAYOUTS = {
38
+ default: LAYOUT_INSTRUCTION_FIRST,
39
+ "instruction-first": LAYOUT_INSTRUCTION_FIRST,
40
+ "persona-first": LAYOUT_PERSONA_FIRST,
41
+ };
42
+ const AGENT_LAYOUT_NAMES = ["default", "instruction-first", "persona-first"];
16
43
  // GenomeKind — Phase 16 (scope segregation)
17
44
  //
18
45
  // Replaces ModuleKind as the scope-root identity Kind. Carries catalog
@@ -253,6 +280,13 @@ class AgentKind extends KindBase {
253
280
  },
254
281
  objective: { widget: "textarea", label: "Objective", order: 15 },
255
282
  model: { widget: "text", label: "Model", order: 20 },
283
+ layout: {
284
+ widget: "select",
285
+ label: "Layout",
286
+ options: ["default", "instruction-first", "persona-first"],
287
+ help: "Named composition order — 'persona-first' puts the Soul before the instruction. Leave empty for the default. A raw promptTemplate, if set, overrides this.",
288
+ order: 25,
289
+ },
256
290
  soul: { widget: "text", label: "Soul", help: "Name of the Soul doc to flatten into the prompt.", order: 30 },
257
291
  skills: { widget: "tags", label: "Skills", order: 40 },
258
292
  actors: { widget: "tags", label: "Actors this agent serves", order: 50 },
@@ -305,7 +339,17 @@ class AgentKind extends KindBase {
305
339
  return { skills: skills.length, soul: readSpecString(doc, "soul") ?? null };
306
340
  }
307
341
  promptTemplate() {
308
- return "{{{agent.instruction}}}\n\n{{{soul_content}}}";
342
+ // IS the `instruction-first` / `default` named layout — the kind default
343
+ // template and the `default` layout are one string, so an agent with no
344
+ // `layout:` composes identically. (Now includes the guardrails block,
345
+ // matching Python — see the layout-constants comment above.)
346
+ return LAYOUT_INSTRUCTION_FIRST;
347
+ }
348
+ layoutTemplate(name) {
349
+ return AGENT_LAYOUTS[name] ?? null;
350
+ }
351
+ layoutNames() {
352
+ return [...AGENT_LAYOUT_NAMES];
309
353
  }
310
354
  preview(doc) {
311
355
  const spec = (doc.spec ?? {});
@@ -27,6 +27,7 @@
27
27
  import { KindBase } from "../kernel/kind_base.js";
28
28
  import { SD, TenantScope } from "../kernel/protocols.js";
29
29
  import { loadDescriptors } from "../kernel/descriptor-loader.js";
30
+ import { HtmlArtifactSchema } from "../kernel/models.js";
30
31
  const API_VERSION = "github.com/ruinosus/dna/sdlc/v1";
31
32
  // v1.3: MILESTONE_STATUSES → EPIC_STATUSES (Jira/ADO alignment).
32
33
  const EPIC_STATUSES = ["planning", "in-progress", "done", "cancelled", "deprecated"];
@@ -1280,6 +1281,161 @@ const JOURNEY_METHODOLOGIES = [
1280
1281
  // the old class is frozen in sdk-py tests/test_kaizen_descriptor_equivalence.py
1281
1282
  // + tests/sdlc.test.ts.
1282
1283
  // ---------------------------------------------------------------------------
1284
+ // ---------------------------------------------------------------------------
1285
+ // HtmlArtifact — an HTML page as a first-class work-item output (record plane)
1286
+ //
1287
+ // s-dx-html-artifact-kind. 1:1 parity with Python HtmlArtifactKind. Bundle
1288
+ // Kind: ARTIFACT.html holds the raw HTML VERBATIM (byte-faithful round-trip —
1289
+ // no frontmatter injection), plus an optional artifact.json companion with
1290
+ // structured metadata (title, description, source, created_at) — the same
1291
+ // mechanic as a Soul (SOUL.md + soul.json). Custom reader/writer (not the
1292
+ // generic marker-frontmatter path) because the marker is arbitrary HTML.
1293
+ // ---------------------------------------------------------------------------
1294
+ class HtmlArtifactKind extends KindBase {
1295
+ apiVersion = API_VERSION;
1296
+ kind = "HtmlArtifact";
1297
+ // alias GENERATED = "<owner>-<kebab(kind)>" = "sdlc-html-artifact".
1298
+ // s-alias-generated-not-typed: new Kinds must NOT type an explicit alias
1299
+ // (EXPLICIT_ALIAS_ALLOWLIST ratchet is shrink-only). Empty here + aliasOwner
1300
+ // triggers generation at load time (Py twin sets alias=None + alias_owner).
1301
+ alias = "";
1302
+ aliasOwner = "sdlc";
1303
+ origin = "github.com/ruinosus/dna/sdlc";
1304
+ isPromptTarget = false;
1305
+ promptTargetPriority = 0;
1306
+ flattenInContext = false;
1307
+ plane = "record";
1308
+ storage = SD.bundle("html-artifacts", "ARTIFACT.html");
1309
+ graphStyle = { fill: "#EA580C", stroke: "#C2410C", textColor: "#fff" };
1310
+ asciiIcon = "📄";
1311
+ displayLabel = "HTML Artifacts";
1312
+ uiSchema = {
1313
+ html: {
1314
+ widget: "code",
1315
+ language: "html",
1316
+ label: "ARTIFACT.html",
1317
+ help: "The raw HTML page (stored byte-faithful).",
1318
+ height: 520,
1319
+ order: 10,
1320
+ },
1321
+ artifact_json: {
1322
+ widget: "code",
1323
+ language: "json",
1324
+ label: "artifact.json",
1325
+ help: "Structured metadata (title, description, source, created_at).",
1326
+ height: 220,
1327
+ order: 20,
1328
+ },
1329
+ };
1330
+ docs = "An HtmlArtifact stores an HTML page as a first-class, linkable output " +
1331
+ "of a work item (Story/Feature/Epic/Spike). Bundle: ARTIFACT.html holds " +
1332
+ "the raw HTML verbatim (byte-faithful round-trip) plus an optional " +
1333
+ "artifact.json companion with structured metadata (title, description, " +
1334
+ "source, created_at) — the same shape as a Soul's SOUL.md + soul.json. " +
1335
+ "Attach one with `dna sdlc produces add <WiKind>/<wi> HtmlArtifact/<name>`.";
1336
+ schema() {
1337
+ return {
1338
+ type: "object",
1339
+ additionalProperties: false,
1340
+ properties: {
1341
+ html: { type: "string", description: "The raw HTML document (byte-faithful)." },
1342
+ artifact_json: {
1343
+ type: "object",
1344
+ additionalProperties: true,
1345
+ description: "Structured metadata: title, description, source, created_at.",
1346
+ },
1347
+ },
1348
+ };
1349
+ }
1350
+ parse(raw) {
1351
+ return HtmlArtifactSchema.parse(raw);
1352
+ }
1353
+ summary(doc) {
1354
+ const spec = (doc.spec ?? {});
1355
+ const aj = spec.artifact_json ?? {};
1356
+ return {
1357
+ title: aj.title ?? null,
1358
+ source: aj.source ?? null,
1359
+ created_at: aj.created_at ?? null,
1360
+ html_bytes: (spec.html ?? "").length,
1361
+ };
1362
+ }
1363
+ preview(doc) {
1364
+ const spec = (doc.spec ?? {});
1365
+ const blocks = [];
1366
+ if (typeof spec.html === "string" && spec.html) {
1367
+ blocks.push({ kind: "code", title: "ARTIFACT.html", body: spec.html, language: "html" });
1368
+ }
1369
+ if (spec.artifact_json && typeof spec.artifact_json === "object") {
1370
+ blocks.push({
1371
+ kind: "code",
1372
+ title: "artifact.json",
1373
+ body: JSON.stringify(spec.artifact_json, null, 2),
1374
+ language: "json",
1375
+ });
1376
+ }
1377
+ if (blocks.length === 0) {
1378
+ return [{ kind: "empty", title: "HtmlArtifact (empty)" }];
1379
+ }
1380
+ return blocks;
1381
+ }
1382
+ }
1383
+ class HtmlArtifactReader {
1384
+ async detect(bundle) {
1385
+ return bundle.exists("ARTIFACT.html");
1386
+ }
1387
+ async read(bundle) {
1388
+ const spec = {};
1389
+ const metadata = {};
1390
+ // ARTIFACT.html — read verbatim (byte-faithful; no frontmatter parse).
1391
+ if (await bundle.exists("ARTIFACT.html")) {
1392
+ spec.html = await bundle.readText("ARTIFACT.html");
1393
+ }
1394
+ // artifact.json companion — structured metadata.
1395
+ if (await bundle.exists("artifact.json")) {
1396
+ const aj = JSON.parse(await bundle.readText("artifact.json"));
1397
+ if (aj && typeof aj === "object" && !Array.isArray(aj)) {
1398
+ spec.artifact_json = aj;
1399
+ const desc = aj.description;
1400
+ if (typeof desc === "string" && desc && !metadata.description) {
1401
+ metadata.description = desc;
1402
+ }
1403
+ }
1404
+ }
1405
+ if (metadata.name == null)
1406
+ metadata.name = bundle.name;
1407
+ return {
1408
+ apiVersion: API_VERSION,
1409
+ kind: "HtmlArtifact",
1410
+ metadata,
1411
+ spec,
1412
+ };
1413
+ }
1414
+ }
1415
+ class HtmlArtifactWriter {
1416
+ canWrite(raw) {
1417
+ return raw.kind === "HtmlArtifact";
1418
+ }
1419
+ serialize(raw) {
1420
+ const files = [];
1421
+ const spec = raw.spec ?? {};
1422
+ // ARTIFACT.html — verbatim HTML (byte-faithful).
1423
+ files.push({ relativePath: "ARTIFACT.html", content: spec.html ?? "" });
1424
+ // artifact.json — canonical JSON companion. metadata.description is a
1425
+ // DERIVED promotion of artifact_json.description on read, so it is NOT
1426
+ // re-emitted separately (no phantom frontmatter — F3 market-fidelity).
1427
+ const aj = spec.artifact_json;
1428
+ if (aj && typeof aj === "object") {
1429
+ files.push({ relativePath: "artifact.json", content: JSON.stringify(aj, null, 2) });
1430
+ }
1431
+ return files;
1432
+ }
1433
+ async write(bundle, raw) {
1434
+ for (const f of this.serialize(raw)) {
1435
+ await bundle.writeText(f.relativePath, f.content ?? "");
1436
+ }
1437
+ }
1438
+ }
1283
1439
  export class SdlcExtension {
1284
1440
  name = "sdlc";
1285
1441
  // v1.14.0 — s-consolidate-cognitive-policies: the 9 cognitive policy
@@ -1302,6 +1458,12 @@ export class SdlcExtension {
1302
1458
  // v1.10.0 — f-reference-citation-kind (ported from Python for
1303
1459
  // s-alias-generated-not-typed: Spike.depFilters → "sdlc-reference").
1304
1460
  kernel.kind(new ReferenceKind());
1461
+ // s-dx-html-artifact-kind — HTML page as a first-class work-item output.
1462
+ // Bundle Kind with a custom reader/writer for byte-faithful HTML,
1463
+ // mirroring the Soul (SOUL.md + soul.json) mechanic.
1464
+ kernel.kind(new HtmlArtifactKind());
1465
+ kernel.reader(new HtmlArtifactReader());
1466
+ kernel.writer(new HtmlArtifactWriter());
1305
1467
  // expr batch B: PromptTemplate is a descriptor now — registered via the
1306
1468
  // loadDescriptors loop below. s-consolidate-cognitive-policies: the
1307
1469
  // cognitive policy family is ONE descriptor (cognitive-policy.kind.yaml).
package/dist/index.d.ts CHANGED
@@ -22,7 +22,7 @@ export { ReportBuilder } from "./kernel/reports.js";
22
22
  export { serializeRawToFiles } from "./kernel/serialize-to-files.js";
23
23
  export * from "./viz/index.js";
24
24
  export { createKernelWithBuiltins, quickInstance, createRuntimeWithBuiltins, quickManifest, fromConfig } from "./bootstrap.js";
25
- export { AgentNotFound } from "./kernel/errors.js";
25
+ export { AgentNotFound, UnknownLayout } from "./kernel/errors.js";
26
26
  export { PromptLibrary, loadPrompts } from "./prompts.js";
27
27
  export { loadConfig, findConfig, CONFIG_FILENAME } from "./config.js";
28
28
  export type { DnaConfig, SearchMode, EmbeddingMode } from "./config.js";
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ export * from "./viz/index.js";
27
27
  export { createKernelWithBuiltins, quickInstance, createRuntimeWithBuiltins, quickManifest, fromConfig } from "./bootstrap.js";
28
28
  // DX consumer surface (s-dx-*): fail-loud prompt building + the collapse-the-
29
29
  // shim helper + declarative port wiring.
30
- export { AgentNotFound } from "./kernel/errors.js";
30
+ export { AgentNotFound, UnknownLayout } from "./kernel/errors.js";
31
31
  export { PromptLibrary, loadPrompts } from "./prompts.js";
32
32
  export { loadConfig, findConfig, CONFIG_FILENAME } from "./config.js";
33
33
  export { sourceFromUrl, resolveDefaultFsUrl, UnsupportedSourceScheme } from "./adapters/source-url.js";
@@ -31,6 +31,23 @@ export declare class AgentNotFound extends Error {
31
31
  readonly agent: string | null;
32
32
  constructor(agent: string | null);
33
33
  }
34
+ /**
35
+ * `buildPrompt` hit an Agent whose `layout:` names a preset the Kind does not
36
+ * offer (s-dx-named-layouts).
37
+ *
38
+ * Fail-loud DX: a typo'd layout (`persona_first` for `persona-first`) must not
39
+ * silently fall through to the Kind default and compose in the wrong order —
40
+ * it throws with the valid names listed.
41
+ *
42
+ * 1:1 parity with python `dna.UnknownLayout` (a `ValueError` subclass).
43
+ * Exported publicly from the package root.
44
+ */
45
+ export declare class UnknownLayout extends Error {
46
+ readonly layout: string;
47
+ readonly available: string[];
48
+ readonly agent: string | null;
49
+ constructor(layout: string, available?: string[], agent?: string | null);
50
+ }
34
51
  /** Base class for kernel registration validation failures. */
35
52
  export declare class KernelRegistrationError extends Error {
36
53
  constructor(message?: string);
@@ -36,6 +36,32 @@ export class AgentNotFound extends Error {
36
36
  Object.setPrototypeOf(this, new.target.prototype);
37
37
  }
38
38
  }
39
+ /**
40
+ * `buildPrompt` hit an Agent whose `layout:` names a preset the Kind does not
41
+ * offer (s-dx-named-layouts).
42
+ *
43
+ * Fail-loud DX: a typo'd layout (`persona_first` for `persona-first`) must not
44
+ * silently fall through to the Kind default and compose in the wrong order —
45
+ * it throws with the valid names listed.
46
+ *
47
+ * 1:1 parity with python `dna.UnknownLayout` (a `ValueError` subclass).
48
+ * Exported publicly from the package root.
49
+ */
50
+ export class UnknownLayout extends Error {
51
+ layout;
52
+ available;
53
+ agent;
54
+ constructor(layout, available = [], agent = null) {
55
+ const where = agent ? ` on agent '${agent}'` : "";
56
+ const hint = available.length ? ` — available: ${available.join(", ")}` : "";
57
+ super(`Unknown layout '${layout}'${where}${hint}`);
58
+ this.name = "UnknownLayout";
59
+ this.layout = layout;
60
+ this.available = available;
61
+ this.agent = agent;
62
+ Object.setPrototypeOf(this, new.target.prototype);
63
+ }
64
+ }
39
65
  /** Base class for kernel registration validation failures. */
40
66
  export class KernelRegistrationError extends Error {
41
67
  constructor(message) {
@@ -94,6 +94,19 @@ export declare abstract class KindBase implements Omit<KindPort, "apiVersion" |
94
94
  describe(_doc: Document): string | null;
95
95
  summary(doc: Document): Record<string, unknown> | null;
96
96
  promptTemplate(): string | null;
97
+ /**
98
+ * Resolve a NAMED composition layout to an embedded template
99
+ * (s-dx-named-layouts). Prompt-target Kinds override this to expose
100
+ * author-friendly presets (persona-first, instruction-first) so the common
101
+ * case never hand-writes Mustache. Returns null for an unknown name (the
102
+ * prompt builder fails loud). Twin of Python ``KindBase.layout_template``.
103
+ */
104
+ layoutTemplate(_name: string): string | null;
105
+ /**
106
+ * Public layout names this Kind offers (s-dx-named-layouts) — powers
107
+ * discovery + fail-loud error messages. Twin of Python ``layout_names``.
108
+ */
109
+ layoutNames(): string[];
97
110
  protected canonicalSpec(spec: Record<string, unknown>): Record<string, unknown>;
98
111
  /** Stable SHA-256 of the doc's authored identity — basis for source
99
112
  * diff/sync. Invariant to key order, formatting, volatile stamps, and
@@ -215,6 +215,23 @@ export class KindBase {
215
215
  promptTemplate() {
216
216
  return null;
217
217
  }
218
+ /**
219
+ * Resolve a NAMED composition layout to an embedded template
220
+ * (s-dx-named-layouts). Prompt-target Kinds override this to expose
221
+ * author-friendly presets (persona-first, instruction-first) so the common
222
+ * case never hand-writes Mustache. Returns null for an unknown name (the
223
+ * prompt builder fails loud). Twin of Python ``KindBase.layout_template``.
224
+ */
225
+ layoutTemplate(_name) {
226
+ return null;
227
+ }
228
+ /**
229
+ * Public layout names this Kind offers (s-dx-named-layouts) — powers
230
+ * discovery + fail-loud error messages. Twin of Python ``layout_names``.
231
+ */
232
+ layoutNames() {
233
+ return [];
234
+ }
218
235
  // ---- Source-sync digest (s-sync-s1; twin of Python canonical_digest) ----
219
236
  canonicalSpec(spec) {
220
237
  const out = {};
@@ -384,6 +384,7 @@ export declare const AgentSpecSchema: z.ZodObject<{
384
384
  tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
385
385
  guardrails: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
386
386
  promptTemplate: z.ZodOptional<z.ZodString>;
387
+ layout: z.ZodOptional<z.ZodString>;
387
388
  tool_groups: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
388
389
  mcp_servers: z.ZodDefault<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodObject<{
389
390
  ref: z.ZodString;
@@ -481,6 +482,7 @@ export declare const AgentSpecSchema: z.ZodObject<{
481
482
  reads: Record<string, Record<string, any>>;
482
483
  type?: string | undefined;
483
484
  promptTemplate?: string | undefined;
485
+ layout?: string | undefined;
484
486
  model?: string | undefined;
485
487
  instruction_file?: string | undefined;
486
488
  soul?: string | undefined;
@@ -517,6 +519,7 @@ export declare const AgentSpecSchema: z.ZodObject<{
517
519
  guardrails?: string[] | undefined;
518
520
  instruction?: string | undefined;
519
521
  promptTemplate?: string | undefined;
522
+ layout?: string | undefined;
520
523
  model?: string | undefined;
521
524
  instruction_file?: string | undefined;
522
525
  tags?: string[] | undefined;
@@ -601,6 +604,7 @@ export declare const AgentSchema: z.ZodObject<{
601
604
  tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
602
605
  guardrails: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
603
606
  promptTemplate: z.ZodOptional<z.ZodString>;
607
+ layout: z.ZodOptional<z.ZodString>;
604
608
  tool_groups: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
605
609
  mcp_servers: z.ZodDefault<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodObject<{
606
610
  ref: z.ZodString;
@@ -698,6 +702,7 @@ export declare const AgentSchema: z.ZodObject<{
698
702
  reads: Record<string, Record<string, any>>;
699
703
  type?: string | undefined;
700
704
  promptTemplate?: string | undefined;
705
+ layout?: string | undefined;
701
706
  model?: string | undefined;
702
707
  instruction_file?: string | undefined;
703
708
  soul?: string | undefined;
@@ -734,6 +739,7 @@ export declare const AgentSchema: z.ZodObject<{
734
739
  guardrails?: string[] | undefined;
735
740
  instruction?: string | undefined;
736
741
  promptTemplate?: string | undefined;
742
+ layout?: string | undefined;
737
743
  model?: string | undefined;
738
744
  instruction_file?: string | undefined;
739
745
  tags?: string[] | undefined;
@@ -810,6 +816,7 @@ export declare const AgentSchema: z.ZodObject<{
810
816
  reads: Record<string, Record<string, any>>;
811
817
  type?: string | undefined;
812
818
  promptTemplate?: string | undefined;
819
+ layout?: string | undefined;
813
820
  model?: string | undefined;
814
821
  instruction_file?: string | undefined;
815
822
  soul?: string | undefined;
@@ -860,6 +867,7 @@ export declare const AgentSchema: z.ZodObject<{
860
867
  guardrails?: string[] | undefined;
861
868
  instruction?: string | undefined;
862
869
  promptTemplate?: string | undefined;
870
+ layout?: string | undefined;
863
871
  model?: string | undefined;
864
872
  instruction_file?: string | undefined;
865
873
  tags?: string[] | undefined;
@@ -1551,6 +1559,83 @@ export declare const SoulSchema: z.ZodObject<{
1551
1559
  } | undefined;
1552
1560
  }>;
1553
1561
  export type TypedSoul = z.output<typeof SoulSchema>;
1562
+ export declare const HtmlArtifactSpecSchema: z.ZodObject<{
1563
+ html: z.ZodDefault<z.ZodOptional<z.ZodString>>;
1564
+ artifact_json: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1565
+ }, "strip", z.ZodTypeAny, {
1566
+ html: string;
1567
+ artifact_json?: Record<string, unknown> | undefined;
1568
+ }, {
1569
+ html?: string | undefined;
1570
+ artifact_json?: Record<string, unknown> | undefined;
1571
+ }>;
1572
+ export declare const HtmlArtifactSchema: z.ZodObject<{
1573
+ apiVersion: z.ZodLiteral<"github.com/ruinosus/dna/sdlc/v1">;
1574
+ kind: z.ZodLiteral<"HtmlArtifact">;
1575
+ metadata: z.ZodObject<{
1576
+ name: z.ZodString;
1577
+ description: z.ZodDefault<z.ZodOptional<z.ZodString>>;
1578
+ version: z.ZodDefault<z.ZodOptional<z.ZodString>>;
1579
+ icon: z.ZodDefault<z.ZodOptional<z.ZodString>>;
1580
+ group: z.ZodDefault<z.ZodOptional<z.ZodString>>;
1581
+ labels: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
1582
+ }, "strip", z.ZodTypeAny, {
1583
+ name: string;
1584
+ description: string;
1585
+ version: string;
1586
+ icon: string;
1587
+ group: string;
1588
+ labels: Record<string, string>;
1589
+ }, {
1590
+ name: string;
1591
+ description?: string | undefined;
1592
+ version?: string | undefined;
1593
+ icon?: string | undefined;
1594
+ group?: string | undefined;
1595
+ labels?: Record<string, string> | undefined;
1596
+ }>;
1597
+ spec: z.ZodDefault<z.ZodObject<{
1598
+ html: z.ZodDefault<z.ZodOptional<z.ZodString>>;
1599
+ artifact_json: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1600
+ }, "strip", z.ZodTypeAny, {
1601
+ html: string;
1602
+ artifact_json?: Record<string, unknown> | undefined;
1603
+ }, {
1604
+ html?: string | undefined;
1605
+ artifact_json?: Record<string, unknown> | undefined;
1606
+ }>>;
1607
+ }, "strip", z.ZodTypeAny, {
1608
+ metadata: {
1609
+ name: string;
1610
+ description: string;
1611
+ version: string;
1612
+ icon: string;
1613
+ group: string;
1614
+ labels: Record<string, string>;
1615
+ };
1616
+ spec: {
1617
+ html: string;
1618
+ artifact_json?: Record<string, unknown> | undefined;
1619
+ };
1620
+ apiVersion: "github.com/ruinosus/dna/sdlc/v1";
1621
+ kind: "HtmlArtifact";
1622
+ }, {
1623
+ metadata: {
1624
+ name: string;
1625
+ description?: string | undefined;
1626
+ version?: string | undefined;
1627
+ icon?: string | undefined;
1628
+ group?: string | undefined;
1629
+ labels?: Record<string, string> | undefined;
1630
+ };
1631
+ apiVersion: "github.com/ruinosus/dna/sdlc/v1";
1632
+ kind: "HtmlArtifact";
1633
+ spec?: {
1634
+ html?: string | undefined;
1635
+ artifact_json?: Record<string, unknown> | undefined;
1636
+ } | undefined;
1637
+ }>;
1638
+ export type TypedHtmlArtifact = z.output<typeof HtmlArtifactSchema>;
1554
1639
  export declare const AgentDefinitionSpecSchema: z.ZodObject<{
1555
1640
  content: z.ZodDefault<z.ZodOptional<z.ZodString>>;
1556
1641
  }, "strip", z.ZodTypeAny, {
@@ -128,6 +128,12 @@ export const AgentSpecSchema = z.object({
128
128
  tags: z.array(z.string()).default([]),
129
129
  guardrails: z.array(z.string()).default([]),
130
130
  promptTemplate: z.string().optional(),
131
+ // s-dx-named-layouts — pick the composition ORDER by name instead of
132
+ // hand-writing raw Mustache. "persona-first" puts the Soul before the
133
+ // instruction; "instruction-first" (a.k.a. "default") keeps the historic
134
+ // order. Resolved by the Kind's layoutTemplate() into an embedded preset.
135
+ // A raw promptTemplate still wins over layout when both are set.
136
+ layout: z.string().optional(),
131
137
  // Phase 14x — tool-group specialization (TS parity with Python).
132
138
  tool_groups: z.array(z.string()).default([]),
133
139
  // s-mcp-servers-on-agent (2026-07-07, spec
@@ -393,6 +399,19 @@ export const SoulSchema = z.object({
393
399
  spec: SoulSpecSchema.default({}),
394
400
  });
395
401
  // ---------------------------------------------------------------------------
402
+ // HtmlArtifact (github.com/ruinosus/dna/sdlc/v1)
403
+ // ---------------------------------------------------------------------------
404
+ export const HtmlArtifactSpecSchema = z.object({
405
+ html: z.string().optional().default(""),
406
+ artifact_json: z.record(z.unknown()).optional(),
407
+ });
408
+ export const HtmlArtifactSchema = z.object({
409
+ apiVersion: z.literal("github.com/ruinosus/dna/sdlc/v1"),
410
+ kind: z.literal("HtmlArtifact"),
411
+ metadata: MetadataSchema,
412
+ spec: HtmlArtifactSpecSchema.default({}),
413
+ });
414
+ // ---------------------------------------------------------------------------
396
415
  // AgentDefinition (agents.md/v1)
397
416
  // ---------------------------------------------------------------------------
398
417
  export const AgentDefinitionSpecSchema = z.object({
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import Mustache from "mustache";
12
12
  import { stripPromptBlock } from "./_text.js";
13
- import { AgentNotFound } from "./errors.js";
13
+ import { AgentNotFound, UnknownLayout } from "./errors.js";
14
14
  // ---------------------------------------------------------------------------
15
15
  // PromptBuilder
16
16
  // ---------------------------------------------------------------------------
@@ -206,22 +206,33 @@ export class PromptBuilder {
206
206
  async _renderPrompt(ctx, agentDoc) {
207
207
  const agentSpec = agentDoc.spec;
208
208
  const kinds = this.host._kinds;
209
- // 1. Agent-level template override
209
+ // 1. Agent-level raw template override (poweruser escape hatch).
210
210
  const agentTemplate = agentSpec.promptTemplate ??
211
211
  agentSpec.prompt_template ??
212
212
  null;
213
213
  if (agentTemplate) {
214
214
  return await this._mustacheRender(agentTemplate, ctx);
215
215
  }
216
- // 2. Kind default template
217
216
  const agentKp = kinds.get(`${agentDoc.apiVersion}\0${agentDoc.kind}`);
217
+ // 2. Named layout preset (s-dx-named-layouts) — author picks the
218
+ // composition order by NAME; the kernel resolves it to an embedded
219
+ // template so the common case never hand-writes Mustache.
220
+ const layout = agentSpec.layout;
221
+ if (layout && agentKp) {
222
+ const layoutTmpl = agentKp.layoutTemplate(layout);
223
+ if (layoutTmpl == null) {
224
+ throw new UnknownLayout(layout, agentKp.layoutNames(), agentDoc.name);
225
+ }
226
+ return await this._mustacheRender(layoutTmpl, ctx);
227
+ }
228
+ // 3. Kind default template
218
229
  if (agentKp) {
219
230
  const kindTemplate = agentKp.promptTemplate();
220
231
  if (kindTemplate) {
221
232
  return await this._mustacheRender(kindTemplate, ctx);
222
233
  }
223
234
  }
224
- // 3. Fallback: agent instruction as plain text
235
+ // 4. Fallback: agent instruction as plain text
225
236
  const agent = ctx.agent;
226
237
  return agent?.instruction ?? "";
227
238
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dna-sdk",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "DNA — Domain Notation of Anything (TypeScript SDK)",
5
5
  "type": "module",
6
6
  "license": "MIT",