dna-sdk 0.3.1 → 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.
- package/dist/adapters/source-url.d.ts +40 -0
- package/dist/adapters/source-url.js +55 -0
- package/dist/bootstrap.d.ts +15 -0
- package/dist/bootstrap.js +83 -0
- package/dist/config.d.ts +34 -0
- package/dist/config.js +102 -0
- package/dist/extensions/helix.js +45 -1
- package/dist/extensions/sdlc.js +162 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +7 -1
- package/dist/kernel/errors.d.ts +35 -0
- package/dist/kernel/errors.js +49 -0
- package/dist/kernel/kind_base.d.ts +13 -0
- package/dist/kernel/kind_base.js +17 -0
- package/dist/kernel/models.d.ts +85 -0
- package/dist/kernel/models.js +19 -0
- package/dist/kernel/prompt-builder.js +23 -5
- package/dist/prompts.d.ts +27 -0
- package/dist/prompts.js +76 -0
- package/package.json +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sourceFromUrl` — the URL → SourcePort factory, as a PUBLIC SDK surface.
|
|
3
|
+
*
|
|
4
|
+
* 1:1 parity with python `dna/adapters/source_url.py`. Promotes URL→adapter
|
|
5
|
+
* resolution out of ad-hoc host code so `fromConfig()` and any host share ONE
|
|
6
|
+
* factory.
|
|
7
|
+
*
|
|
8
|
+
* Scheme map (TS runtime):
|
|
9
|
+
*
|
|
10
|
+
* file:// <path> → FilesystemSource (read/write on disk)
|
|
11
|
+
* fs:// <path> → alias of file://
|
|
12
|
+
* <plain path> → treated as file://<path>
|
|
13
|
+
* postgresql:// … → PostgresSource (node-postgres)
|
|
14
|
+
* postgres:// … → alias of postgresql://
|
|
15
|
+
* sqlite:// <path> → NOT SUPPORTED in the TS runtime (Python-only; the
|
|
16
|
+
* SqlAlchemy adapter is a Python thing) — fails loud.
|
|
17
|
+
*
|
|
18
|
+
* An unknown scheme fails loud with the supported set.
|
|
19
|
+
*/
|
|
20
|
+
import type { SourcePort } from "../kernel/protocols.js";
|
|
21
|
+
export declare class UnsupportedSourceScheme extends Error {
|
|
22
|
+
constructor(message: string);
|
|
23
|
+
}
|
|
24
|
+
interface SourceFromUrlOptions {
|
|
25
|
+
/** Postgres schema namespace (postgres sources only). */
|
|
26
|
+
schema?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build a source from a scheme URL (see module docstring).
|
|
30
|
+
*
|
|
31
|
+
* Postgres sources run their migrations on first use; this awaits `init()` so
|
|
32
|
+
* the returned source is ready.
|
|
33
|
+
*/
|
|
34
|
+
export declare function sourceFromUrl(url: string, opts?: SourceFromUrlOptions): Promise<SourcePort>;
|
|
35
|
+
/**
|
|
36
|
+
* The `file://` URL the SDK falls back to with no explicit config.
|
|
37
|
+
* Priority: explicit override > `DNA_BASE_DIR` env > `./.dna`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveDefaultFsUrl(baseDirOverride?: string): string;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export class UnsupportedSourceScheme extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "UnsupportedSourceScheme";
|
|
5
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function urlToFsPath(url) {
|
|
9
|
+
// Mirror the Python rule (join netloc + path so `fs://./x` stays RELATIVE).
|
|
10
|
+
const m = /^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/(.*)$/.exec(url);
|
|
11
|
+
if (!m)
|
|
12
|
+
return url; // plain path, no scheme
|
|
13
|
+
return m[2]; // everything after scheme:// (host+path joined)
|
|
14
|
+
}
|
|
15
|
+
function schemeOf(url) {
|
|
16
|
+
const m = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(url);
|
|
17
|
+
return (m ? m[1] : "file").toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build a source from a scheme URL (see module docstring).
|
|
21
|
+
*
|
|
22
|
+
* Postgres sources run their migrations on first use; this awaits `init()` so
|
|
23
|
+
* the returned source is ready.
|
|
24
|
+
*/
|
|
25
|
+
export async function sourceFromUrl(url, opts = {}) {
|
|
26
|
+
const scheme = schemeOf(url);
|
|
27
|
+
const base = scheme.split("+", 1)[0];
|
|
28
|
+
if (scheme === "file" || scheme === "fs" || scheme === "") {
|
|
29
|
+
const { FilesystemSource } = await import("./filesystem/source.js");
|
|
30
|
+
return new FilesystemSource(urlToFsPath(url) || url);
|
|
31
|
+
}
|
|
32
|
+
if (base === "postgresql" || base === "postgres") {
|
|
33
|
+
const { PostgresSource } = await import("./postgres/source.js");
|
|
34
|
+
const src = new PostgresSource({ connectionString: url, schema: opts.schema });
|
|
35
|
+
await src.init();
|
|
36
|
+
return src;
|
|
37
|
+
}
|
|
38
|
+
if (base === "sqlite") {
|
|
39
|
+
throw new UnsupportedSourceScheme(`sqlite:// sources are Python-only — they ride the Python SqlAlchemy ` +
|
|
40
|
+
`adapter, which the TypeScript runtime does not ship. Use file:// ` +
|
|
41
|
+
`(filesystem) or postgresql:// here. Got: ${url}`);
|
|
42
|
+
}
|
|
43
|
+
throw new UnsupportedSourceScheme(`unsupported source URL scheme '${scheme}://' — the TS runtime ships ` +
|
|
44
|
+
`file:// (filesystem) and postgresql:// adapters. Got: ${url}`);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The `file://` URL the SDK falls back to with no explicit config.
|
|
48
|
+
* Priority: explicit override > `DNA_BASE_DIR` env > `./.dna`.
|
|
49
|
+
*/
|
|
50
|
+
export function resolveDefaultFsUrl(baseDirOverride) {
|
|
51
|
+
const base = baseDirOverride ?? process.env.DNA_BASE_DIR;
|
|
52
|
+
if (base)
|
|
53
|
+
return `file://${base}`;
|
|
54
|
+
return "file://.dna";
|
|
55
|
+
}
|
package/dist/bootstrap.d.ts
CHANGED
|
@@ -43,3 +43,18 @@ export declare function createRuntimeWithBuiltins(): Runtime;
|
|
|
43
43
|
* `ManifestInstance` for the given scope.
|
|
44
44
|
*/
|
|
45
45
|
export declare function quickManifest(scope: string, baseDir?: string): Promise<ManifestInstance>;
|
|
46
|
+
/**
|
|
47
|
+
* Boot a fully-wired Kernel from a `dna.config.yaml` (declarative port wiring —
|
|
48
|
+
* s-dx-kernel-from-config). TS twin of python `Kernel.from_config`.
|
|
49
|
+
*
|
|
50
|
+
* The config selects the `source` (`file://` / `postgresql://`) and,
|
|
51
|
+
* optionally, the `search` + `embedding` providers; every port is resolved to
|
|
52
|
+
* its adapter and wired. With NO config present (and no `path`) the behavior is
|
|
53
|
+
* unchanged — a filesystem `.dna` source, exactly like the bare default.
|
|
54
|
+
*
|
|
55
|
+
* Returns a wired Kernel; call `.instance(scope)` for the ManifestInstance.
|
|
56
|
+
* (Python exposes this as the `Kernel.from_config` static; the TS bootstrap
|
|
57
|
+
* keeps it a free function, mirroring `quickInstance` — same documented
|
|
58
|
+
* asymmetry the SDK already lives with.)
|
|
59
|
+
*/
|
|
60
|
+
export declare function fromConfig(path?: string): Promise<Kernel>;
|
package/dist/bootstrap.js
CHANGED
|
@@ -40,6 +40,8 @@ import { TestkitExtension } from "./extensions/testkit.js";
|
|
|
40
40
|
import { ModelRegExtension } from "./extensions/modelreg.js";
|
|
41
41
|
import { AutomationExtension } from "./extensions/automation.js";
|
|
42
42
|
import { EvalExtension } from "./extensions/eval.js";
|
|
43
|
+
import { loadConfig } from "./config.js";
|
|
44
|
+
import { resolveDefaultFsUrl, sourceFromUrl } from "./adapters/source-url.js";
|
|
43
45
|
/**
|
|
44
46
|
* The canonical built-in extension set. Loaded into BOTH a Kernel and a
|
|
45
47
|
* Runtime through this SINGLE list so the two bootstraps can never drift
|
|
@@ -132,3 +134,84 @@ export async function quickManifest(scope, baseDir = ".dna") {
|
|
|
132
134
|
rt.resolver("github", new GitHubResolver());
|
|
133
135
|
return rt.manifest(scope);
|
|
134
136
|
}
|
|
137
|
+
/** Minimal CachePort for non-filesystem sources (all docs self-contained).
|
|
138
|
+
* TS twin of python `kernel_bootstrap._NoopCache`. */
|
|
139
|
+
class NoopCache {
|
|
140
|
+
async loadAll() {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
async loadKey() {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
async store(_scope, _key, _items) {
|
|
147
|
+
// no-op
|
|
148
|
+
}
|
|
149
|
+
async has() {
|
|
150
|
+
return true; // pretend always hit → skip resolver calls
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Boot a fully-wired Kernel from a `dna.config.yaml` (declarative port wiring —
|
|
155
|
+
* s-dx-kernel-from-config). TS twin of python `Kernel.from_config`.
|
|
156
|
+
*
|
|
157
|
+
* The config selects the `source` (`file://` / `postgresql://`) and,
|
|
158
|
+
* optionally, the `search` + `embedding` providers; every port is resolved to
|
|
159
|
+
* its adapter and wired. With NO config present (and no `path`) the behavior is
|
|
160
|
+
* unchanged — a filesystem `.dna` source, exactly like the bare default.
|
|
161
|
+
*
|
|
162
|
+
* Returns a wired Kernel; call `.instance(scope)` for the ManifestInstance.
|
|
163
|
+
* (Python exposes this as the `Kernel.from_config` static; the TS bootstrap
|
|
164
|
+
* keeps it a free function, mirroring `quickInstance` — same documented
|
|
165
|
+
* asymmetry the SDK already lives with.)
|
|
166
|
+
*/
|
|
167
|
+
export async function fromConfig(path) {
|
|
168
|
+
const cfg = loadConfig(path);
|
|
169
|
+
const sourceUrl = cfg ? cfg.source : resolveDefaultFsUrl();
|
|
170
|
+
const source = await sourceFromUrl(sourceUrl);
|
|
171
|
+
const k = createKernelWithBuiltins();
|
|
172
|
+
k.source(source);
|
|
173
|
+
if (source.supportsReaders) {
|
|
174
|
+
const baseDir = source.baseDir;
|
|
175
|
+
k.cache(new FilesystemCache(baseDir));
|
|
176
|
+
k.resolver("local", new LocalResolver(baseDir));
|
|
177
|
+
k.resolver("http", new HttpResolver());
|
|
178
|
+
k.resolver("https", new HttpResolver());
|
|
179
|
+
k.resolver("github", new GitHubResolver());
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
k.cache(new NoopCache());
|
|
183
|
+
}
|
|
184
|
+
// Uniform writer/reader wiring for any KernelAttachable source.
|
|
185
|
+
const attach = source.attachKernel;
|
|
186
|
+
if (typeof attach === "function")
|
|
187
|
+
attach.call(source, k);
|
|
188
|
+
if (cfg) {
|
|
189
|
+
await wireSearch(k, cfg);
|
|
190
|
+
await wireEmbedding(k, cfg);
|
|
191
|
+
}
|
|
192
|
+
return k;
|
|
193
|
+
}
|
|
194
|
+
async function wireEmbedding(kernel, cfg) {
|
|
195
|
+
if (cfg.embedding === "off" || cfg.embedding === "fake")
|
|
196
|
+
return;
|
|
197
|
+
if (cfg.embedding === "onnx") {
|
|
198
|
+
// Dynamic import: the ONNX adapter (transformers.js) must never enter the
|
|
199
|
+
// default bundle (guard: tests/embedding-import-isolation.test.ts).
|
|
200
|
+
const { OnnxEmbeddingProvider } = await import("./adapters/embedding/onnx.js");
|
|
201
|
+
kernel.embeddingProvider(new OnnxEmbeddingProvider());
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function wireSearch(kernel, cfg) {
|
|
205
|
+
if (cfg.search === "off")
|
|
206
|
+
return;
|
|
207
|
+
if (cfg.search === "sqlite-vec") {
|
|
208
|
+
// Dynamic import: sqlite-vec must never enter the default bundle
|
|
209
|
+
// (guard: tests/search-import-isolation.test.ts).
|
|
210
|
+
const { SqliteVecRecordSearchProvider } = await import("./adapters/search/sqlite-vec.js");
|
|
211
|
+
kernel.recordSearchProvider(new SqliteVecRecordSearchProvider(kernel, {}));
|
|
212
|
+
}
|
|
213
|
+
else if (cfg.search === "pgvector") {
|
|
214
|
+
throw new Error("search: pgvector is Python-only in this build — the TS runtime ships " +
|
|
215
|
+
"the sqlite-vec record-search provider. Use `search: sqlite-vec` here.");
|
|
216
|
+
}
|
|
217
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare const CONFIG_FILENAME = "dna.config.yaml";
|
|
2
|
+
declare const VALID_SEARCH: readonly ["off", "pgvector", "sqlite-vec"];
|
|
3
|
+
declare const VALID_EMBEDDING: readonly ["off", "fake", "onnx"];
|
|
4
|
+
export type SearchMode = (typeof VALID_SEARCH)[number];
|
|
5
|
+
export type EmbeddingMode = (typeof VALID_EMBEDDING)[number];
|
|
6
|
+
/** Parsed + validated `dna.config.yaml`. */
|
|
7
|
+
export interface DnaConfig {
|
|
8
|
+
/** Scheme URL: `file://` | `sqlite://` | `postgresql://`. */
|
|
9
|
+
source: string;
|
|
10
|
+
/** Record-search provider selector (default `off`). */
|
|
11
|
+
search: SearchMode;
|
|
12
|
+
/** Embedding provider selector (default `off`). */
|
|
13
|
+
embedding: EmbeddingMode;
|
|
14
|
+
/** Where it was loaded from (`null` for a synthesized default). */
|
|
15
|
+
path: string | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Path to `dna.config.yaml` in `start` (default: cwd), or `null` if absent.
|
|
19
|
+
* Deliberately NOT a walk-up — a config's meaning is tied to the boot dir.
|
|
20
|
+
*/
|
|
21
|
+
export declare function findConfig(start?: string): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Load + validate `dna.config.yaml`.
|
|
24
|
+
*
|
|
25
|
+
* - `path` given → it MUST exist (a typo'd path is an error, not a silent
|
|
26
|
+
* fallback); parsed and validated.
|
|
27
|
+
* - `path` omitted → look for `dna.config.yaml` in cwd. Found → parsed. Absent
|
|
28
|
+
* → `null` (the caller keeps its default: a filesystem `.dna` source).
|
|
29
|
+
*
|
|
30
|
+
* Throws on: not-a-mapping, missing `source`, unknown keys, or an out-of-enum
|
|
31
|
+
* `search` / `embedding` value.
|
|
32
|
+
*/
|
|
33
|
+
export declare function loadConfig(path?: string): DnaConfig | null;
|
|
34
|
+
export {};
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dna.config.yaml` — declarative port wiring (s-dx-kernel-from-config).
|
|
3
|
+
*
|
|
4
|
+
* 1:1 parity with python `dna/config.py`. The SAME file drives both SDKs — the
|
|
5
|
+
* schema is language-agnostic:
|
|
6
|
+
*
|
|
7
|
+
* ```yaml
|
|
8
|
+
* # dna.config.yaml
|
|
9
|
+
* source: postgresql://user:pass@host/db # or sqlite:///./dev.db, file://.dna
|
|
10
|
+
* search: pgvector # pgvector | sqlite-vec | off (default: off)
|
|
11
|
+
* embedding: onnx # onnx | fake | off (default: off / fake floor)
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Only `source` is required. This module parses + VALIDATES the file (unknown
|
|
15
|
+
* keys and bad enum values fail loud); the wiring lives in `fromConfig()`.
|
|
16
|
+
*
|
|
17
|
+
* Runtime asymmetry (documented, not silent): the TS runtime ships the
|
|
18
|
+
* filesystem + postgres source adapters; `sqlite://` is Python-only (it rides
|
|
19
|
+
* the Python-only SqlAlchemy adapter). A `sqlite://` source — or a `pgvector`
|
|
20
|
+
* search — in a TS host fails loud with that explanation at wire time.
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import yaml from "js-yaml";
|
|
25
|
+
export const CONFIG_FILENAME = "dna.config.yaml";
|
|
26
|
+
const VALID_SEARCH = ["off", "pgvector", "sqlite-vec"];
|
|
27
|
+
const VALID_EMBEDDING = ["off", "fake", "onnx"];
|
|
28
|
+
const KNOWN_KEYS = new Set(["source", "search", "embedding"]);
|
|
29
|
+
/**
|
|
30
|
+
* Path to `dna.config.yaml` in `start` (default: cwd), or `null` if absent.
|
|
31
|
+
* Deliberately NOT a walk-up — a config's meaning is tied to the boot dir.
|
|
32
|
+
*/
|
|
33
|
+
export function findConfig(start) {
|
|
34
|
+
const candidate = join(start ?? process.cwd(), CONFIG_FILENAME);
|
|
35
|
+
return existsSync(candidate) ? candidate : null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Load + validate `dna.config.yaml`.
|
|
39
|
+
*
|
|
40
|
+
* - `path` given → it MUST exist (a typo'd path is an error, not a silent
|
|
41
|
+
* fallback); parsed and validated.
|
|
42
|
+
* - `path` omitted → look for `dna.config.yaml` in cwd. Found → parsed. Absent
|
|
43
|
+
* → `null` (the caller keeps its default: a filesystem `.dna` source).
|
|
44
|
+
*
|
|
45
|
+
* Throws on: not-a-mapping, missing `source`, unknown keys, or an out-of-enum
|
|
46
|
+
* `search` / `embedding` value.
|
|
47
|
+
*/
|
|
48
|
+
export function loadConfig(path) {
|
|
49
|
+
let resolved;
|
|
50
|
+
if (path !== undefined) {
|
|
51
|
+
if (!existsSync(path)) {
|
|
52
|
+
throw new Error(`dna config not found at ${path} — pass a path to an existing ` +
|
|
53
|
+
`${CONFIG_FILENAME}, or omit it to auto-discover one in the ` +
|
|
54
|
+
`current directory.`);
|
|
55
|
+
}
|
|
56
|
+
resolved = path;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const found = findConfig();
|
|
60
|
+
if (found === null)
|
|
61
|
+
return null;
|
|
62
|
+
resolved = found;
|
|
63
|
+
}
|
|
64
|
+
const raw = yaml.load(readFileSync(resolved, "utf-8"));
|
|
65
|
+
return parse(raw, resolved);
|
|
66
|
+
}
|
|
67
|
+
function parse(raw, path) {
|
|
68
|
+
if (raw === null || raw === undefined) {
|
|
69
|
+
throw new Error(`${path} is empty — it must at least declare a \`source:\` URL ` +
|
|
70
|
+
`(e.g. \`source: file://.dna\`).`);
|
|
71
|
+
}
|
|
72
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
73
|
+
throw new Error(`${path} must be a YAML mapping (key: value).`);
|
|
74
|
+
}
|
|
75
|
+
const obj = raw;
|
|
76
|
+
const unknown = Object.keys(obj).filter((k) => !KNOWN_KEYS.has(k)).sort();
|
|
77
|
+
if (unknown.length > 0) {
|
|
78
|
+
throw new Error(`${path}: unknown key(s) ${JSON.stringify(unknown)} — supported keys ` +
|
|
79
|
+
`are ${JSON.stringify([...KNOWN_KEYS].sort())}.`);
|
|
80
|
+
}
|
|
81
|
+
const source = obj.source;
|
|
82
|
+
if (typeof source !== "string" || source.length === 0) {
|
|
83
|
+
throw new Error(`${path}: \`source:\` is required and must be a URL string ` +
|
|
84
|
+
`(file:// | sqlite:// | postgresql://).`);
|
|
85
|
+
}
|
|
86
|
+
const search = String(obj.search ?? "off").trim() || "off";
|
|
87
|
+
if (!VALID_SEARCH.includes(search)) {
|
|
88
|
+
throw new Error(`${path}: \`search: ${search}\` is not valid — choose one of ` +
|
|
89
|
+
`${JSON.stringify(VALID_SEARCH)}.`);
|
|
90
|
+
}
|
|
91
|
+
const embedding = String(obj.embedding ?? "off").trim() || "off";
|
|
92
|
+
if (!VALID_EMBEDDING.includes(embedding)) {
|
|
93
|
+
throw new Error(`${path}: \`embedding: ${embedding}\` is not valid — choose one of ` +
|
|
94
|
+
`${JSON.stringify(VALID_EMBEDDING)}.`);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
source,
|
|
98
|
+
search: search,
|
|
99
|
+
embedding: embedding,
|
|
100
|
+
path,
|
|
101
|
+
};
|
|
102
|
+
}
|
package/dist/extensions/helix.js
CHANGED
|
@@ -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
|
-
|
|
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 ?? {});
|
package/dist/extensions/sdlc.js
CHANGED
|
@@ -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
|
@@ -21,7 +21,12 @@ export { LockManager } from "./kernel/lock-manager.js";
|
|
|
21
21
|
export { ReportBuilder } from "./kernel/reports.js";
|
|
22
22
|
export { serializeRawToFiles } from "./kernel/serialize-to-files.js";
|
|
23
23
|
export * from "./viz/index.js";
|
|
24
|
-
export { createKernelWithBuiltins, quickInstance, createRuntimeWithBuiltins, quickManifest } from "./bootstrap.js";
|
|
24
|
+
export { createKernelWithBuiltins, quickInstance, createRuntimeWithBuiltins, quickManifest, fromConfig } from "./bootstrap.js";
|
|
25
|
+
export { AgentNotFound, UnknownLayout } from "./kernel/errors.js";
|
|
26
|
+
export { PromptLibrary, loadPrompts } from "./prompts.js";
|
|
27
|
+
export { loadConfig, findConfig, CONFIG_FILENAME } from "./config.js";
|
|
28
|
+
export type { DnaConfig, SearchMode, EmbeddingMode } from "./config.js";
|
|
29
|
+
export { sourceFromUrl, resolveDefaultFsUrl, UnsupportedSourceScheme } from "./adapters/source-url.js";
|
|
25
30
|
export { HookRegistry, KNOWN_HOOK_NAMES } from "./kernel/hooks.js";
|
|
26
31
|
export type { HookContext, HookName, HookNameArg, Middleware, EventHandler, PreSaveContext, VetoHandler, } from "./kernel/hooks.js";
|
|
27
32
|
export type { SourcePort, CachePort, ResolverPort, ReaderPort, WriterPort, KindPort, KindPresentation, Extension, ExtensionHost, CompositionResult, CacheItem, ResolvedItem, StorageDescriptor, StoragePattern, BodyMode, SerializedFile, SerializedDocument, WritableSourcePort, QueryFilter, QueryOrder, CountResult, SourceQueryOpts, SourceCountOpts, RecordStorePort, RecordSearchProvider, EmbeddingPort, ToolPort, } from "./kernel/protocols.js";
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,13 @@ export { serializeRawToFiles } from "./kernel/serialize-to-files.js";
|
|
|
24
24
|
// Viz — diagram generators, health, matrix, ASCII tree
|
|
25
25
|
export * from "./viz/index.js";
|
|
26
26
|
// Opinionated bootstrap — extension wiring lives here, not in kernel/
|
|
27
|
-
export { createKernelWithBuiltins, quickInstance, createRuntimeWithBuiltins, quickManifest } from "./bootstrap.js";
|
|
27
|
+
export { createKernelWithBuiltins, quickInstance, createRuntimeWithBuiltins, quickManifest, fromConfig } from "./bootstrap.js";
|
|
28
|
+
// DX consumer surface (s-dx-*): fail-loud prompt building + the collapse-the-
|
|
29
|
+
// shim helper + declarative port wiring.
|
|
30
|
+
export { AgentNotFound, UnknownLayout } from "./kernel/errors.js";
|
|
31
|
+
export { PromptLibrary, loadPrompts } from "./prompts.js";
|
|
32
|
+
export { loadConfig, findConfig, CONFIG_FILENAME } from "./config.js";
|
|
33
|
+
export { sourceFromUrl, resolveDefaultFsUrl, UnsupportedSourceScheme } from "./adapters/source-url.js";
|
|
28
34
|
export { HookRegistry, KNOWN_HOOK_NAMES } from "./kernel/hooks.js";
|
|
29
35
|
// i-005 — types REFERENCED by already-exported public API, previously
|
|
30
36
|
// broken xrefs in TypeDoc. Each is public surface a consumer needs to name:
|
package/dist/kernel/errors.d.ts
CHANGED
|
@@ -13,6 +13,41 @@
|
|
|
13
13
|
* These errors surface those problems at boot time instead of as silent
|
|
14
14
|
* runtime drift.
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* `buildPrompt({ agent: X })` was asked for an agent that no prompt-target
|
|
18
|
+
* document in the loaded manifest declares (missing, renamed, or unparseable).
|
|
19
|
+
*
|
|
20
|
+
* Fail-loud contract (s-dx-build-prompt-fail-loud): the builder used to RETURN
|
|
21
|
+
* the string `"Agent 'X' not found"` instead of throwing — which sailed
|
|
22
|
+
* straight through a consumer's `if (!text)` check and became the LITERAL
|
|
23
|
+
* agent instruction. Every consumer therefore wrote the same defensive guard
|
|
24
|
+
* (`mi.findAgent(x) === null`) before every call. Throwing a typed error
|
|
25
|
+
* deletes that guard: the miss is now impossible to ignore.
|
|
26
|
+
*
|
|
27
|
+
* 1:1 parity with python `dna.AgentNotFound` (a `LookupError` subclass).
|
|
28
|
+
* Exported publicly from the package root.
|
|
29
|
+
*/
|
|
30
|
+
export declare class AgentNotFound extends Error {
|
|
31
|
+
readonly agent: string | null;
|
|
32
|
+
constructor(agent: string | null);
|
|
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
|
+
}
|
|
16
51
|
/** Base class for kernel registration validation failures. */
|
|
17
52
|
export declare class KernelRegistrationError extends Error {
|
|
18
53
|
constructor(message?: string);
|
package/dist/kernel/errors.js
CHANGED
|
@@ -13,6 +13,55 @@
|
|
|
13
13
|
* These errors surface those problems at boot time instead of as silent
|
|
14
14
|
* runtime drift.
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* `buildPrompt({ agent: X })` was asked for an agent that no prompt-target
|
|
18
|
+
* document in the loaded manifest declares (missing, renamed, or unparseable).
|
|
19
|
+
*
|
|
20
|
+
* Fail-loud contract (s-dx-build-prompt-fail-loud): the builder used to RETURN
|
|
21
|
+
* the string `"Agent 'X' not found"` instead of throwing — which sailed
|
|
22
|
+
* straight through a consumer's `if (!text)` check and became the LITERAL
|
|
23
|
+
* agent instruction. Every consumer therefore wrote the same defensive guard
|
|
24
|
+
* (`mi.findAgent(x) === null`) before every call. Throwing a typed error
|
|
25
|
+
* deletes that guard: the miss is now impossible to ignore.
|
|
26
|
+
*
|
|
27
|
+
* 1:1 parity with python `dna.AgentNotFound` (a `LookupError` subclass).
|
|
28
|
+
* Exported publicly from the package root.
|
|
29
|
+
*/
|
|
30
|
+
export class AgentNotFound extends Error {
|
|
31
|
+
agent;
|
|
32
|
+
constructor(agent) {
|
|
33
|
+
super(`Agent '${agent}' not found`);
|
|
34
|
+
this.name = "AgentNotFound";
|
|
35
|
+
this.agent = agent;
|
|
36
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
37
|
+
}
|
|
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
|
+
}
|
|
16
65
|
/** Base class for kernel registration validation failures. */
|
|
17
66
|
export class KernelRegistrationError extends Error {
|
|
18
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
|
package/dist/kernel/kind_base.js
CHANGED
|
@@ -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 = {};
|
package/dist/kernel/models.d.ts
CHANGED
|
@@ -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, {
|
package/dist/kernel/models.js
CHANGED
|
@@ -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,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import Mustache from "mustache";
|
|
12
12
|
import { stripPromptBlock } from "./_text.js";
|
|
13
|
+
import { AgentNotFound, UnknownLayout } from "./errors.js";
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
// PromptBuilder
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
@@ -38,7 +39,10 @@ export class PromptBuilder {
|
|
|
38
39
|
}
|
|
39
40
|
const agentDoc = agentName ? this._findAgent(agentName) : null;
|
|
40
41
|
if (!agentDoc) {
|
|
41
|
-
|
|
42
|
+
// Fail loud (s-dx-build-prompt-fail-loud): throw a typed error instead
|
|
43
|
+
// of returning a placeholder string that would become the literal
|
|
44
|
+
// instruction.
|
|
45
|
+
throw new AgentNotFound(agentName);
|
|
42
46
|
}
|
|
43
47
|
// Build context — merge backwards-compat params into enabledSlots
|
|
44
48
|
const enabledSlots = { ...(opts?.enabledSlots ?? {}) };
|
|
@@ -69,7 +73,10 @@ export class PromptBuilder {
|
|
|
69
73
|
data: {},
|
|
70
74
|
});
|
|
71
75
|
}
|
|
72
|
-
|
|
76
|
+
// Clean output (s-dx-clean-composition-output): template sections can pad
|
|
77
|
+
// the composed prompt with trailing newlines; consumers used to strip them
|
|
78
|
+
// themselves. Return it already clean.
|
|
79
|
+
return prompt.replace(/\n+$/, "");
|
|
73
80
|
}
|
|
74
81
|
// -------------------------------------------------------------------------
|
|
75
82
|
// Private helpers
|
|
@@ -199,22 +206,33 @@ export class PromptBuilder {
|
|
|
199
206
|
async _renderPrompt(ctx, agentDoc) {
|
|
200
207
|
const agentSpec = agentDoc.spec;
|
|
201
208
|
const kinds = this.host._kinds;
|
|
202
|
-
// 1. Agent-level template override
|
|
209
|
+
// 1. Agent-level raw template override (poweruser escape hatch).
|
|
203
210
|
const agentTemplate = agentSpec.promptTemplate ??
|
|
204
211
|
agentSpec.prompt_template ??
|
|
205
212
|
null;
|
|
206
213
|
if (agentTemplate) {
|
|
207
214
|
return await this._mustacheRender(agentTemplate, ctx);
|
|
208
215
|
}
|
|
209
|
-
// 2. Kind default template
|
|
210
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
|
|
211
229
|
if (agentKp) {
|
|
212
230
|
const kindTemplate = agentKp.promptTemplate();
|
|
213
231
|
if (kindTemplate) {
|
|
214
232
|
return await this._mustacheRender(kindTemplate, ctx);
|
|
215
233
|
}
|
|
216
234
|
}
|
|
217
|
-
//
|
|
235
|
+
// 4. Fallback: agent instruction as plain text
|
|
218
236
|
const agent = ctx.agent;
|
|
219
237
|
return agent?.instruction ?? "";
|
|
220
238
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ManifestInstance } from "./kernel/instance.js";
|
|
2
|
+
/** Lazy, cached view `agent name -> composed prompt` over one scope. */
|
|
3
|
+
export declare class PromptLibrary {
|
|
4
|
+
/** The underlying ManifestInstance (drop down for the full surface). */
|
|
5
|
+
readonly mi: ManifestInstance;
|
|
6
|
+
private readonly cache;
|
|
7
|
+
constructor(
|
|
8
|
+
/** The underlying ManifestInstance (drop down for the full surface). */
|
|
9
|
+
mi: ManifestInstance);
|
|
10
|
+
/**
|
|
11
|
+
* Compose `name`'s prompt (cached). Throws {@link AgentNotFound} on a miss;
|
|
12
|
+
* the returned text is clean (no trailing newlines).
|
|
13
|
+
*/
|
|
14
|
+
get(name: string): Promise<string>;
|
|
15
|
+
/** True when `name` is a prompt-target document in the scope (no compose). */
|
|
16
|
+
has(name: string): boolean;
|
|
17
|
+
/** Names of every prompt-target document in the scope, sorted. */
|
|
18
|
+
names(): string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Compose the prompts of `scope` behind a {@link PromptLibrary}.
|
|
22
|
+
*
|
|
23
|
+
* `baseDir` follows the {@link quickInstance} convention — the directory that
|
|
24
|
+
* holds `<scope>/` (the `.dna` scopes root). Omitted → the `DNA_BASE_DIR` env
|
|
25
|
+
* var, then `.dna` in the cwd.
|
|
26
|
+
*/
|
|
27
|
+
export declare function loadPrompts(scope: string, baseDir?: string): Promise<PromptLibrary>;
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `loadPrompts` — the one call that collapses the prompt shim (TS twin of
|
|
3
|
+
* python `dna.load_prompts`).
|
|
4
|
+
*
|
|
5
|
+
* With the fail-loud builder (s-dx-build-prompt-fail-loud) and clean output
|
|
6
|
+
* (s-dx-clean-composition-output) in place, a hand-rolled prompt module
|
|
7
|
+
* becomes:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { loadPrompts } from "@ruinosus/dna";
|
|
11
|
+
*
|
|
12
|
+
* const prompts = await loadPrompts("helpdesk");
|
|
13
|
+
* export const TRIAGE = await prompts.get("triage"); // composed, clean, or throws
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* `loadPrompts` boots a filesystem kernel for `scope` and returns a
|
|
17
|
+
* {@link PromptLibrary} — a lazy, cached, read-only view from agent name to
|
|
18
|
+
* composed system prompt. A missing agent throws {@link AgentNotFound} (never a
|
|
19
|
+
* placeholder string); the returned text is already stripped of trailing
|
|
20
|
+
* newlines.
|
|
21
|
+
*
|
|
22
|
+
* TS/Py asymmetry: composition is async in TS, so `get(name)` returns a
|
|
23
|
+
* `Promise<string>` (python's `lib[name]` is sync — same reason `buildPrompt`
|
|
24
|
+
* is `await`ed in TS but not in Python).
|
|
25
|
+
*/
|
|
26
|
+
import { quickInstance } from "./bootstrap.js";
|
|
27
|
+
/** Lazy, cached view `agent name -> composed prompt` over one scope. */
|
|
28
|
+
export class PromptLibrary {
|
|
29
|
+
mi;
|
|
30
|
+
cache = new Map();
|
|
31
|
+
constructor(
|
|
32
|
+
/** The underlying ManifestInstance (drop down for the full surface). */
|
|
33
|
+
mi) {
|
|
34
|
+
this.mi = mi;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Compose `name`'s prompt (cached). Throws {@link AgentNotFound} on a miss;
|
|
38
|
+
* the returned text is clean (no trailing newlines).
|
|
39
|
+
*/
|
|
40
|
+
async get(name) {
|
|
41
|
+
const cached = this.cache.get(name);
|
|
42
|
+
if (cached !== undefined)
|
|
43
|
+
return cached;
|
|
44
|
+
// buildPrompt throws AgentNotFound on a miss and returns clean text.
|
|
45
|
+
const text = await this.mi.buildPrompt({ agent: name });
|
|
46
|
+
this.cache.set(name, text);
|
|
47
|
+
return text;
|
|
48
|
+
}
|
|
49
|
+
/** True when `name` is a prompt-target document in the scope (no compose). */
|
|
50
|
+
has(name) {
|
|
51
|
+
return this.names().includes(name);
|
|
52
|
+
}
|
|
53
|
+
/** Names of every prompt-target document in the scope, sorted. */
|
|
54
|
+
names() {
|
|
55
|
+
const kinds = this.mi._kinds;
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
for (const doc of this.mi.documents) {
|
|
58
|
+
const kp = kinds.get(`${doc.apiVersion}\0${doc.kind}`);
|
|
59
|
+
if (kp?.isPromptTarget)
|
|
60
|
+
seen.add(doc.name);
|
|
61
|
+
}
|
|
62
|
+
return [...seen].sort();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Compose the prompts of `scope` behind a {@link PromptLibrary}.
|
|
67
|
+
*
|
|
68
|
+
* `baseDir` follows the {@link quickInstance} convention — the directory that
|
|
69
|
+
* holds `<scope>/` (the `.dna` scopes root). Omitted → the `DNA_BASE_DIR` env
|
|
70
|
+
* var, then `.dna` in the cwd.
|
|
71
|
+
*/
|
|
72
|
+
export async function loadPrompts(scope, baseDir) {
|
|
73
|
+
const resolved = baseDir ?? process.env.DNA_BASE_DIR ?? ".dna";
|
|
74
|
+
const mi = await quickInstance(scope, resolved);
|
|
75
|
+
return new PromptLibrary(mi);
|
|
76
|
+
}
|