intentdna 1.6.4 → 1.7.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 (76) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/cli/commands/compile.js +2 -47
  4. package/dist/cli/commands/context.d.ts +8 -0
  5. package/dist/cli/commands/context.js +63 -0
  6. package/dist/cli/commands/feedback.d.ts +1 -0
  7. package/dist/cli/commands/feedback.js +11 -0
  8. package/dist/cli/commands/generate.js +5 -33
  9. package/dist/cli/commands/init.js +11 -63
  10. package/dist/cli/commands/run.js +5 -4
  11. package/dist/cli/commands/show.js +2 -38
  12. package/dist/cli/commands/sync.d.ts +9 -6
  13. package/dist/cli/commands/sync.js +209 -190
  14. package/dist/cli/commands/templates.d.ts +10 -1
  15. package/dist/cli/commands/templates.js +50 -1
  16. package/dist/cli/commands/validate.js +15 -9
  17. package/dist/cli/commands/verify.js +97 -25
  18. package/dist/cli/index.js +76 -11
  19. package/dist/compiler/cascade.d.ts +3 -1
  20. package/dist/compiler/cascade.js +51 -0
  21. package/dist/compiler/compile.js +37 -0
  22. package/dist/compiler/diagnostics.d.ts +17 -0
  23. package/dist/compiler/diagnostics.js +30 -0
  24. package/dist/compiler/index.d.ts +3 -0
  25. package/dist/compiler/index.js +8 -11
  26. package/dist/compiler/input-resolver.d.ts +25 -0
  27. package/dist/compiler/input-resolver.js +175 -0
  28. package/dist/hooks/cli.d.ts +10 -1
  29. package/dist/hooks/cli.js +39 -26
  30. package/dist/hooks/event-registry.d.ts +17 -0
  31. package/dist/hooks/event-registry.js +89 -0
  32. package/dist/hooks/protocol.d.ts +1 -1
  33. package/dist/hooks/schema.js +5 -1
  34. package/dist/hooks/state.d.ts +2 -0
  35. package/dist/hooks/state.js +23 -2
  36. package/dist/mcp/index.js +2 -0
  37. package/dist/mcp/tools-compile.js +18 -49
  38. package/dist/mcp/tools-context.d.ts +2 -0
  39. package/dist/mcp/tools-context.js +85 -0
  40. package/dist/mcp/tools-enforce.d.ts +2 -2
  41. package/dist/mcp/tools-enforce.js +21 -53
  42. package/dist/mcp/tools-observability.js +24 -0
  43. package/dist/report/kernel-signals.js +3 -0
  44. package/dist/report/report-package.d.ts +56 -0
  45. package/dist/report/report-package.js +85 -0
  46. package/dist/runtime/agent-md.d.ts +1 -0
  47. package/dist/runtime/agent-md.js +21 -3
  48. package/dist/runtime/context-sources.d.ts +14 -0
  49. package/dist/runtime/context-sources.js +60 -0
  50. package/dist/runtime/markdown-target-registry.d.ts +17 -0
  51. package/dist/runtime/markdown-target-registry.js +37 -0
  52. package/dist/runtime/markdown.d.ts +2 -1
  53. package/dist/runtime/markdown.js +9 -12
  54. package/dist/runtime/output-artifact-registry.d.ts +11 -0
  55. package/dist/runtime/output-artifact-registry.js +30 -0
  56. package/dist/runtime/settings-adapter.d.ts +2 -1
  57. package/dist/runtime/settings-adapter.js +4 -12
  58. package/dist/runtime/skill-adapter.d.ts +32 -4
  59. package/dist/runtime/skill-adapter.js +187 -10
  60. package/dist/runtime/workflow-runner.d.ts +1 -1
  61. package/dist/runtime/workflow-runner.js +1 -1
  62. package/dist/schema/controller-registry.d.ts +29 -0
  63. package/dist/schema/controller-registry.js +35 -0
  64. package/dist/schema/types.d.ts +33 -0
  65. package/dist/schema/validate.js +157 -138
  66. package/dist/schema/validators/controllers.d.ts +3 -0
  67. package/dist/schema/validators/controllers.js +156 -0
  68. package/dist/signals/index.d.ts +10 -0
  69. package/dist/signals/index.js +90 -5
  70. package/dist/templates/catalog.d.ts +19 -0
  71. package/dist/templates/catalog.js +57 -0
  72. package/dist/templates/flutter-rewrite.dna.yaml +2 -2
  73. package/dist/templates/metadata.d.ts +6 -0
  74. package/dist/templates/metadata.js +32 -0
  75. package/package.json +1 -1
  76. package/spec/foundation-hardening.md +2 -1
@@ -1,9 +1,11 @@
1
1
  /**
2
- * dna templates deploy copy local src/templates/*.dna.yaml to global intentdna install
2
+ * dna templates — template catalog and deployment commands.
3
3
  */
4
4
  import { readdirSync, copyFileSync, existsSync, readFileSync, mkdirSync } from "node:fs";
5
5
  import { resolve, join } from "node:path";
6
6
  import { execSync } from "node:child_process";
7
+ import { readFile } from "node:fs/promises";
8
+ import { findTemplateCatalogEntry, listTemplateCatalog } from "../../templates/catalog.js";
7
9
  /**
8
10
  * Resolve the global intentdna templates directory.
9
11
  * Strategy: npm root -g + /intentdna/dist/templates
@@ -45,6 +47,53 @@ function verifyProjectRoot(cwd) {
45
47
  }
46
48
  return cwd;
47
49
  }
50
+ export async function runTemplatesList(opts = {}) {
51
+ const templates = await listTemplateCatalog({ projectDir: opts.projectDir });
52
+ if (templates.length === 0) {
53
+ process.stderr.write("No templates found.\n");
54
+ return 1;
55
+ }
56
+ process.stderr.write(`Available templates (${templates.length}):\n`);
57
+ for (const template of templates) {
58
+ const ns = template.namespace ? ` ns=${template.namespace}` : "";
59
+ const version = template.version ? ` v${template.version}` : "";
60
+ process.stderr.write(` - ${template.name} [${template.source}]${ns}${version}`);
61
+ if (template.displayName)
62
+ process.stderr.write(` — ${template.displayName}`);
63
+ process.stderr.write("\n");
64
+ }
65
+ return 0;
66
+ }
67
+ export async function runTemplatesShow(opts) {
68
+ if (!opts.name) {
69
+ process.stderr.write("Usage: dna templates show <name>\n");
70
+ return 2;
71
+ }
72
+ const template = await findTemplateCatalogEntry(opts.name, { projectDir: opts.projectDir });
73
+ if (!template) {
74
+ process.stderr.write(`Unknown template: ${opts.name}\n`);
75
+ return 2;
76
+ }
77
+ const content = await readFile(template.path, "utf-8");
78
+ process.stderr.write(`Template: ${template.name}\n`);
79
+ process.stderr.write(`Source: ${template.source}\n`);
80
+ process.stderr.write(`Path: ${template.path}\n`);
81
+ if (template.displayName)
82
+ process.stderr.write(`Name: ${template.displayName}\n`);
83
+ if (template.namespace)
84
+ process.stderr.write(`Namespace: ${template.namespace}\n`);
85
+ if (template.version)
86
+ process.stderr.write(`Version: ${template.version}\n`);
87
+ process.stderr.write(`Content hash: ${template.contentHash}\n`);
88
+ if (template.keywords.length > 0) {
89
+ process.stderr.write(`Match keywords: ${template.keywords.join(", ")}\n`);
90
+ }
91
+ process.stderr.write("\n---\n");
92
+ process.stdout.write(content);
93
+ if (!content.endsWith("\n"))
94
+ process.stdout.write("\n");
95
+ return 0;
96
+ }
48
97
  export async function runTemplatesDeploy(opts = {}) {
49
98
  try {
50
99
  const cwd = process.cwd();
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Validate DNA files for structural correctness.
5
5
  */
6
- import { loadDNA } from "../../compiler/index.js";
6
+ import { formatDiagnostic, loadDNAWithDiagnostics } from "../../compiler/index.js";
7
7
  export async function runValidate(opts) {
8
8
  if (opts.files.length === 0) {
9
9
  process.stderr.write("Error: no files specified\n");
@@ -11,17 +11,23 @@ export async function runValidate(opts) {
11
11
  }
12
12
  let allValid = true;
13
13
  for (const file of opts.files) {
14
- try {
15
- await loadDNA(file);
14
+ const result = await loadDNAWithDiagnostics(file);
15
+ const errors = result.diagnostics.filter((diagnostic) => diagnostic.severity === "error");
16
+ const warnings = result.diagnostics.filter((diagnostic) => diagnostic.severity === "warning");
17
+ if (errors.length > 0 || !result.dna) {
18
+ allValid = false;
19
+ process.stderr.write(`invalid: ${file}\n`);
16
20
  if (!opts.quiet) {
17
- process.stderr.write(`valid: ${file}\n`);
21
+ for (const diagnostic of result.diagnostics) {
22
+ process.stderr.write(`${formatDiagnostic(diagnostic)}\n`);
23
+ }
18
24
  }
25
+ continue;
19
26
  }
20
- catch (err) {
21
- allValid = false;
22
- process.stderr.write(`invalid: ${file}\n`);
23
- if (!opts.quiet && err instanceof Error) {
24
- process.stderr.write(`${err.message}\n`);
27
+ if (!opts.quiet) {
28
+ process.stderr.write(`valid: ${file}\n`);
29
+ for (const warning of warnings) {
30
+ process.stderr.write(`${formatDiagnostic(warning)}\n`);
25
31
  }
26
32
  }
27
33
  }
@@ -218,6 +218,18 @@ function truncate(s, max) {
218
218
  return s;
219
219
  return s.slice(0, max - 3) + "...";
220
220
  }
221
+ async function checkTemplateVersionsQuiet(configs) {
222
+ const stderr = process.stderr;
223
+ const originalWrite = stderr.write;
224
+ stderr.write = (() => true);
225
+ try {
226
+ const { checkTemplateVersions } = await import("./sync.js");
227
+ return await checkTemplateVersions(configs);
228
+ }
229
+ finally {
230
+ stderr.write = originalWrite;
231
+ }
232
+ }
221
233
  /**
222
234
  * U3: Comprehensive health report for DNA installation.
223
235
  * Checks IR, configs, templates, hooks, and trace system.
@@ -225,7 +237,7 @@ function truncate(s, max) {
225
237
  export async function runHealth() {
226
238
  const cwd = process.cwd();
227
239
  const checks = [];
228
- const { resolve } = await import("node:path");
240
+ const { resolve, basename } = await import("node:path");
229
241
  const { stat, readFile: rf, readdir } = await import("node:fs/promises");
230
242
  // 1. Check compiled IR
231
243
  const irPath = resolve(cwd, ".dna", "compiled", "ir.json");
@@ -245,15 +257,15 @@ export async function runHealth() {
245
257
  checks.push({ name: "Compiled IR", status: "warn", detail: "Legacy format — run `dna sync` to upgrade" });
246
258
  }
247
259
  else {
248
- checks.push({ name: "Compiled IR", status: "fail", detail: "Unrecognized format" });
260
+ checks.push({ name: "Compiled IR", status: "fail", detail: "Unrecognized format — run `dna sync` to regenerate" });
249
261
  }
250
262
  }
251
263
  catch {
252
264
  checks.push({ name: "Compiled IR", status: "fail", detail: "Not found — run `dna sync`" });
253
265
  }
254
266
  // 2. Check DNA config files
255
- const { autoDetectConfigs } = await import("./sync.js");
256
- const configs = await autoDetectConfigs();
267
+ const { detectDNAConfigs } = await import("../../compiler/index.js");
268
+ const configs = await detectDNAConfigs(cwd);
257
269
  if (configs.length > 0) {
258
270
  checks.push({ name: "DNA configs", status: "pass", detail: `${configs.length} config(s) found` });
259
271
  }
@@ -262,51 +274,72 @@ export async function runHealth() {
262
274
  }
263
275
  // 3. Check config validity
264
276
  if (configs.length > 0) {
265
- const { loadDNA } = await import("../../compiler/index.js");
277
+ const { loadDNAWithDiagnostics, formatDiagnostic } = await import("../../compiler/index.js");
266
278
  let valid = 0;
267
279
  let invalid = 0;
280
+ let warningCount = 0;
268
281
  const errors = [];
269
282
  for (const configPath of configs) {
270
- try {
271
- await loadDNA(configPath);
283
+ const result = await loadDNAWithDiagnostics(configPath);
284
+ const errorDiagnostics = result.diagnostics.filter((diagnostic) => diagnostic.severity === "error");
285
+ const warningDiagnostics = result.diagnostics.filter((diagnostic) => diagnostic.severity === "warning");
286
+ warningCount += warningDiagnostics.length;
287
+ if (errorDiagnostics.length === 0 && result.dna) {
272
288
  valid++;
273
289
  }
274
- catch (err) {
290
+ else {
275
291
  invalid++;
276
- const { basename } = await import("node:path");
277
- errors.push(`${basename(configPath)}: ${err instanceof Error ? err.message : String(err)}`);
292
+ errors.push(`${basename(configPath)}: ${errorDiagnostics.map(formatDiagnostic).join(", ")}`);
278
293
  }
279
294
  }
280
295
  if (invalid === 0) {
281
- checks.push({ name: "Config validity", status: "pass", detail: `${valid} config(s) valid` });
296
+ const detail = warningCount > 0 ? `${valid} config(s) valid, ${warningCount} warning(s)` : `${valid} config(s) valid`;
297
+ checks.push({ name: "Config validity", status: warningCount > 0 ? "warn" : "pass", detail });
282
298
  }
283
299
  else {
284
- checks.push({ name: "Config validity", status: "fail", detail: errors.join("; ") });
300
+ checks.push({ name: "Config validity", status: "fail", detail: `${errors.join("; ")} — fix listed config errors, then run \`dna validate\`` });
285
301
  }
286
302
  }
287
303
  // 4. Check template versions
288
304
  if (configs.length > 0) {
289
- const { checkTemplateVersions: checkVersions } = await import("./sync.js");
290
- // Suppress stderr output from checkTemplateVersions — we'll report ourselves
291
- const origWrite = process.stderr.write;
292
- process.stderr.write = (() => true);
293
305
  try {
294
- const upgrades = await checkVersions(configs);
306
+ const upgrades = await checkTemplateVersionsQuiet(configs);
295
307
  if (upgrades.length === 0) {
296
308
  checks.push({ name: "Template versions", status: "pass", detail: "All up to date" });
297
309
  }
298
310
  else {
299
- const names = upgrades.map(u => `${u.templateName} (${u.currentVersion} → ${u.availableVersion})`);
300
- checks.push({ name: "Template versions", status: "warn", detail: `Upgrade available: ${names.join(", ")}` });
311
+ const versionUpdates = upgrades.filter(u => u.reason === "version-update");
312
+ const missingHash = upgrades.filter(u => u.reason === "missing-hash");
313
+ const contentDrift = upgrades.filter(u => u.reason === "content-drift");
314
+ const parts = [];
315
+ if (versionUpdates.length > 0) {
316
+ const names = versionUpdates.map(u => `${u.templateName} (${u.currentVersion} → ${u.availableVersion})`);
317
+ parts.push(`Upgrade available: ${names.join(", ")}`);
318
+ }
319
+ if (missingHash.length > 0) {
320
+ const names = missingHash.map(u => `${u.templateName} (${u.currentVersion})`);
321
+ parts.push(`Template metadata incomplete: ${names.join(", ")} — run dna init --upgrade <template>`);
322
+ }
323
+ if (contentDrift.length > 0) {
324
+ const names = contentDrift.map(u => `${u.templateName} (${u.currentVersion})`);
325
+ parts.push(`Template content drift: ${names.join(", ")} — run dna init --upgrade <template>`);
326
+ }
327
+ checks.push({ name: "Template versions", status: "warn", detail: parts.join("; ") });
301
328
  }
302
329
  }
303
330
  catch {
304
- checks.push({ name: "Template versions", status: "warn", detail: "Could not check versions" });
305
- }
306
- finally {
307
- process.stderr.write = origWrite;
331
+ checks.push({ name: "Template versions", status: "warn", detail: "Could not check versions — run `dna templates list` and retry `dna verify --health`" });
308
332
  }
309
333
  }
334
+ // 4.5 Check sync mode detection
335
+ try {
336
+ const { detectMode } = await import("./sync.js");
337
+ const mode = await detectMode();
338
+ checks.push({ name: "Sync mode", status: "pass", detail: `auto resolves to ${mode}` });
339
+ }
340
+ catch {
341
+ checks.push({ name: "Sync mode", status: "warn", detail: "Could not auto-detect plugin/bin mode — use `dna sync --mode bin` if plugin mode is unavailable" });
342
+ }
310
343
  // 5. Check .claude settings or plugin registration
311
344
  const settingsPath = resolve(cwd, ".claude", "settings.json");
312
345
  try {
@@ -319,7 +352,7 @@ export async function runHealth() {
319
352
  checks.push({ name: "Hook registration", status: "pass", detail: `${eventCount} event(s) in settings.json` });
320
353
  }
321
354
  else {
322
- checks.push({ name: "Hook registration", status: "warn", detail: "No hooks in settings.json — using plugin mode?" });
355
+ checks.push({ name: "Hook registration", status: "warn", detail: "settings.json has no hooks run `dna sync --mode bin` or use plugin-managed hooks" });
323
356
  }
324
357
  }
325
358
  catch {
@@ -332,13 +365,52 @@ export async function runHealth() {
332
365
  checks.push({ name: "MCP server", status: "pass", detail: "dna-mcp registered in .mcp.json" });
333
366
  }
334
367
  else {
335
- checks.push({ name: "Hook registration", status: "warn", detail: "No hooks or MCP server found" });
368
+ checks.push({ name: "Hook registration", status: "warn", detail: "No hooks or MCP server found — run `dna sync`" });
336
369
  }
337
370
  }
338
371
  catch {
339
372
  checks.push({ name: "Hook registration", status: "warn", detail: "No settings.json or .mcp.json — run `dna setup` or `dna sync`" });
340
373
  }
341
374
  }
375
+ // 5.5 Check generated skill map/body references
376
+ const skillsDir = resolve(cwd, ".claude", "skills");
377
+ const skillMapPath = resolve(skillsDir, ".intentdna-skill-map.json");
378
+ try {
379
+ const raw = await rf(skillMapPath, "utf-8");
380
+ const map = JSON.parse(raw);
381
+ const skills = Array.isArray(map.skills) ? map.skills : [];
382
+ const missingBodies = [];
383
+ for (const skill of skills) {
384
+ if (!skill.bodyPath)
385
+ continue;
386
+ try {
387
+ await stat(resolve(skillsDir, skill.bodyPath));
388
+ }
389
+ catch {
390
+ missingBodies.push(`${skill.name ?? "unknown"}: ${skill.bodyPath}`);
391
+ }
392
+ }
393
+ if (missingBodies.length === 0) {
394
+ checks.push({ name: "Generated skills", status: "pass", detail: `${skills.length} skill map entr${skills.length === 1 ? "y" : "ies"} with valid bodies` });
395
+ }
396
+ else {
397
+ checks.push({ name: "Generated skills", status: "fail", detail: `Missing body files: ${missingBodies.join(", ")} — run \`dna sync --skills .claude/skills\`` });
398
+ }
399
+ }
400
+ catch {
401
+ try {
402
+ const entries = await readdir(skillsDir);
403
+ const hasSkillDirs = entries.some((entry) => entry.startsWith("dna-"));
404
+ checks.push({
405
+ name: "Generated skills",
406
+ status: hasSkillDirs ? "warn" : "pass",
407
+ detail: hasSkillDirs ? "Skill dirs exist without .intentdna-skill-map.json — run `dna sync`" : "No generated skill dirs found",
408
+ });
409
+ }
410
+ catch {
411
+ checks.push({ name: "Generated skills", status: "pass", detail: "No skills directory" });
412
+ }
413
+ }
342
414
  // 6. Check trace system
343
415
  const traceDir = resolve(cwd, ".dna", "state", "trace");
344
416
  try {
package/dist/cli/index.js CHANGED
@@ -12,7 +12,7 @@
12
12
  * help Show this help message
13
13
  */
14
14
  import { parseArgs } from "node:util";
15
- const HELP = `Intent DNA CLI v0.3.0
15
+ const HELP = `Intent DNA CLI v1.7.0
16
16
 
17
17
  Usage: dna <command> [options]
18
18
 
@@ -29,7 +29,8 @@ Commands:
29
29
  show Show gene expression state
30
30
  evolve Preview epigenetic marker changes from persisted outcomes
31
31
  epigenetic Record outcomes, view markers and summaries
32
- feedback Build local kernel report (--days <N>, --json, --evolve preview, --dna-id <id>)
32
+ feedback Build local kernel report (--days <N>, --json, --package, --evolve preview, --dna-id <id>)
33
+ context Inspect planning-only advisory context sources
33
34
  templates Template management (deploy to global install)
34
35
 
35
36
  Options:
@@ -68,15 +69,39 @@ Examples:
68
69
  dna evolve my_dna_id Preview marker changes without persisting
69
70
  dna evolve my_dna_id --apply Unsupported in this kernel; use sync --evolve for explicit mutation
70
71
  `;
72
+ const COMMAND_HELP = {
73
+ sync: `Usage: dna sync [files...] [options]\n\nOptions:\n --mode auto|plugin|bin Sync mode (default: auto)\n --dry-run Print planned outputs without writing files\n --post-summary Print registry-backed output summary\n --target <target> Markdown target for injection\n --inject <path> File to inject markdown into\n --agents <dir> Generate agent files\n --skills <dir> Generate skill files\n --workflow <dir> Generate workflow shell scripts\n --settings <path> Write hook settings in bin mode\n --lock <path> Write lock file (default .dna/lock)\n`,
74
+ verify: `Usage: dna verify [--lock <path>] [--stats] [--health]\n\nOptions:\n --health Comprehensive installation health check\n --stats Show hook call statistics\n --lock <path> Lock file path (default .dna/lock)\n`,
75
+ templates: `Usage: dna templates <list|show|deploy>\n\nSubcommands:\n list List built-in and project templates\n show <name> Show template metadata and YAML\n deploy Copy local templates to global install\n`,
76
+ context: `Usage: dna context <index|search|doc> [query|id] [files...]\n\nSubcommands:\n index List explicitly declared planning-only advisory context sources\n search <query> Search source metadata\n doc <id> Read a local file/doc source body\n`,
77
+ run: `Usage: dna run --dna <file> --workflow <name> --task <id> [options]\n\nOptions:\n --dry-run Print execution plan only\n --permission-mode <mode> Claude permission mode (default: default)\n`,
78
+ compile: `Usage: dna compile <files...> [--target claude-sdk] [--context <name>] [--output <path>] [--ir]\n`,
79
+ validate: `Usage: dna validate <files...> [--quiet]\n`,
80
+ show: `Usage: dna show <files...> [--context <name>] [--json]\n`,
81
+ init: `Usage: dna init [--template <name>|--template list|--register <path>|--upgrade <name>]\n`,
82
+ };
83
+ function commandHelp(command) {
84
+ return command ? COMMAND_HELP[command] : undefined;
85
+ }
71
86
  async function main() {
72
87
  const args = process.argv.slice(2);
73
88
  const command = args[0];
74
- if (!command || command === "help" || command === "--help" || command === "-h") {
89
+ if (!command || command === "--help" || command === "-h") {
75
90
  process.stderr.write(HELP);
76
91
  process.exit(0);
77
92
  }
93
+ if (command === "help") {
94
+ const help = commandHelp(args[1]);
95
+ process.stderr.write(help ?? HELP);
96
+ process.exit(0);
97
+ }
78
98
  // Parse remaining args (after command)
79
99
  const rest = args.slice(1);
100
+ if (rest.includes("--help") || rest.includes("-h")) {
101
+ const help = commandHelp(command);
102
+ process.stderr.write(help ?? HELP);
103
+ process.exit(help ? 0 : 2);
104
+ }
80
105
  switch (command) {
81
106
  case "sync": {
82
107
  const { values, positionals } = parseArgs({
@@ -96,11 +121,19 @@ async function main() {
96
121
  settings: { type: "string" },
97
122
  lock: { type: "string", default: ".dna/lock" },
98
123
  remove: { type: "boolean", default: false },
99
- plugin: { type: "boolean", default: false },
124
+ mode: { type: "string", default: "auto" },
125
+ plugin: { type: "boolean" },
126
+ "dry-run": { type: "boolean", default: false },
127
+ "post-summary": { type: "boolean", default: false },
100
128
  },
101
129
  allowPositionals: true,
102
130
  strict: false,
103
131
  });
132
+ const mode = values.mode;
133
+ if (!["auto", "plugin", "bin"].includes(mode)) {
134
+ process.stderr.write(`Error: --mode must be one of: auto, plugin, bin\n`);
135
+ process.exit(2);
136
+ }
104
137
  const { runSync } = await import("./commands/sync.js");
105
138
  const code = await runSync({
106
139
  files: positionals,
@@ -118,7 +151,10 @@ async function main() {
118
151
  settingsPath: values.settings,
119
152
  lockFile: values.lock,
120
153
  remove: values.remove,
154
+ mode: mode,
121
155
  plugin: values.plugin,
156
+ dryRun: values["dry-run"],
157
+ postSummary: values["post-summary"],
122
158
  });
123
159
  process.exit(code);
124
160
  break;
@@ -413,6 +449,25 @@ async function main() {
413
449
  process.exit(code);
414
450
  break;
415
451
  }
452
+ case "context": {
453
+ const subcommand = rest[0] ?? "index";
454
+ const contextRest = rest.slice(1);
455
+ const { positionals } = parseArgs({
456
+ args: contextRest,
457
+ options: {},
458
+ allowPositionals: true,
459
+ strict: false,
460
+ });
461
+ const { runContext } = await import("./commands/context.js");
462
+ const code = await runContext({
463
+ subcommand,
464
+ query: subcommand === "search" ? positionals[0] : undefined,
465
+ id: subcommand === "doc" ? positionals[0] : undefined,
466
+ files: subcommand === "index" ? positionals : positionals.slice(1),
467
+ });
468
+ process.exit(code);
469
+ break;
470
+ }
416
471
  case "feedback": {
417
472
  const { values: fbValues } = parseArgs({
418
473
  args: rest,
@@ -420,6 +475,7 @@ async function main() {
420
475
  days: { type: "string", short: "d", default: "7" },
421
476
  json: { type: "boolean", default: false },
422
477
  evolve: { type: "boolean", default: false },
478
+ package: { type: "boolean", default: false },
423
479
  "dna-id": { type: "string" },
424
480
  },
425
481
  strict: false,
@@ -429,20 +485,29 @@ async function main() {
429
485
  days: parseInt(fbValues.days, 10) || 7,
430
486
  json: fbValues.json,
431
487
  evolve: fbValues.evolve,
488
+ packageReport: fbValues.package,
432
489
  dnaId: fbValues["dna-id"],
433
490
  });
434
491
  process.exit(code);
435
492
  break;
436
493
  }
437
494
  case "templates": {
438
- const subcommand = rest[0] ?? "";
439
- if (subcommand !== "deploy") {
440
- process.stderr.write(`Usage: dna templates deploy\n\nCopy local src/templates/*.dna.yaml to global intentdna install.\n`);
441
- process.exit(subcommand ? 2 : 0);
495
+ const subcommand = rest[0] ?? "list";
496
+ const { runTemplatesDeploy, runTemplatesList, runTemplatesShow } = await import("./commands/templates.js");
497
+ if (subcommand === "list") {
498
+ const code = await runTemplatesList();
499
+ process.exit(code);
442
500
  }
443
- const { runTemplatesDeploy } = await import("./commands/templates.js");
444
- const code = await runTemplatesDeploy();
445
- process.exit(code);
501
+ if (subcommand === "show") {
502
+ const code = await runTemplatesShow({ name: rest[1] ?? "" });
503
+ process.exit(code);
504
+ }
505
+ if (subcommand === "deploy") {
506
+ const code = await runTemplatesDeploy();
507
+ process.exit(code);
508
+ }
509
+ process.stderr.write(COMMAND_HELP.templates);
510
+ process.exit(2);
446
511
  break;
447
512
  }
448
513
  default:
@@ -10,7 +10,7 @@
10
10
  * - attract/repel: same target accumulates, different targets independent
11
11
  * - amplify/suppress: factors multiply (1.5x × 2.0x = 3.0x)
12
12
  */
13
- import type { IntentDNA, Gene, ContextRegion, EpigeneticMarker, RoleDef, WorkflowDef, LegibilityAssetMap, VerifierCommandPolicy, ControllerDef } from "../schema/types.js";
13
+ import type { IntentDNA, Gene, ContextRegion, EpigeneticMarker, RoleDef, WorkflowDef, LegibilityAssetMap, VerifierCommandPolicy, ControllerDef, AdvisoryContextSource, PlanningContext } from "../schema/types.js";
14
14
  export interface CascadedDNA {
15
15
  genes: Record<string, Gene>;
16
16
  contexts: Record<string, ContextRegion>;
@@ -27,6 +27,8 @@ export interface CascadedDNA {
27
27
  per_role?: Record<string, string[]>;
28
28
  per_workflow?: Record<string, string[]>;
29
29
  };
30
+ context_sources?: AdvisoryContextSource[];
31
+ planning_context?: PlanningContext;
30
32
  verifier_policy?: VerifierCommandPolicy;
31
33
  }
32
34
  /**
@@ -156,6 +156,47 @@ function mergeContextFiles(base, incoming) {
156
156
  per_workflow: mergePrefixedRecords(base.per_workflow, incoming.per_workflow),
157
157
  };
158
158
  }
159
+ function prefixContextSources(sources, ns) {
160
+ if (!sources)
161
+ return undefined;
162
+ if (!ns)
163
+ return sources;
164
+ return sources.map((source) => ({ ...source, id: `${ns}_${source.id}` }));
165
+ }
166
+ function prefixPlanningContext(planning, ns) {
167
+ if (!planning)
168
+ return undefined;
169
+ if (!ns)
170
+ return planning;
171
+ return {
172
+ ...planning,
173
+ sources: planning.sources.map((sourceId) => `${ns}_${sourceId}`),
174
+ };
175
+ }
176
+ function mergeContextSources(base, incoming) {
177
+ if (!base)
178
+ return incoming;
179
+ if (!incoming)
180
+ return base;
181
+ const merged = new Map();
182
+ for (const source of base)
183
+ merged.set(source.id, source);
184
+ for (const source of incoming)
185
+ merged.set(source.id, source);
186
+ return [...merged.values()];
187
+ }
188
+ function mergePlanningContext(base, incoming) {
189
+ if (!base)
190
+ return incoming;
191
+ if (!incoming)
192
+ return base;
193
+ return {
194
+ sources: incoming.sources,
195
+ instructions: incoming.instructions ?? base.instructions,
196
+ require_quotes: incoming.require_quotes ?? base.require_quotes,
197
+ require_attribution: incoming.require_attribution ?? base.require_attribution,
198
+ };
199
+ }
159
200
  function mergeVerifierPolicy(base, incoming) {
160
201
  if (!base)
161
202
  return incoming;
@@ -257,6 +298,8 @@ export function cascadeDNA(layers) {
257
298
  // legibility_assets/context_files: highest-priority DNA wins (last in sorted layers)
258
299
  let legibilityAssets;
259
300
  let contextFiles;
301
+ let contextSources;
302
+ let planningContext;
260
303
  let verifierPolicy;
261
304
  for (const dna of sorted) {
262
305
  if (dna.legibility_assets) {
@@ -265,6 +308,12 @@ export function cascadeDNA(layers) {
265
308
  if (dna.context_files) {
266
309
  contextFiles = mergeContextFiles(contextFiles, prefixContextFiles(dna.context_files, dna.namespace));
267
310
  }
311
+ if (dna.context_sources) {
312
+ contextSources = mergeContextSources(contextSources, prefixContextSources(dna.context_sources, dna.namespace));
313
+ }
314
+ if (dna.planning_context) {
315
+ planningContext = mergePlanningContext(planningContext, prefixPlanningContext(dna.planning_context, dna.namespace));
316
+ }
268
317
  if (dna.verifier_policy) {
269
318
  verifierPolicy = mergeVerifierPolicy(verifierPolicy, dna.verifier_policy);
270
319
  }
@@ -280,6 +329,8 @@ export function cascadeDNA(layers) {
280
329
  warnings: warnings.length > 0 ? warnings : undefined,
281
330
  legibility_assets: legibilityAssets,
282
331
  context_files: contextFiles,
332
+ context_sources: contextSources,
333
+ planning_context: planningContext,
283
334
  verifier_policy: verifierPolicy,
284
335
  };
285
336
  }
@@ -172,6 +172,39 @@ function legibilityAssetsToContextFiles(assets) {
172
172
  return undefined;
173
173
  return { mandatory, per_role, per_workflow };
174
174
  }
175
+ function compileAdvisoryContext(cascaded) {
176
+ const declaredSources = cascaded?.context_sources ?? [];
177
+ const planning = cascaded?.planning_context;
178
+ const plannedSourceIds = planning?.sources ?? declaredSources.map((source) => source.id);
179
+ const sourceById = new Map(declaredSources.map((source) => [source.id, source]));
180
+ const sources = plannedSourceIds
181
+ .map((sourceId) => sourceById.get(sourceId))
182
+ .filter((source) => Boolean(source))
183
+ .map((source) => ({
184
+ ...source,
185
+ usage: "planning_only",
186
+ non_enforcing: true,
187
+ requires_quotes: planning?.require_quotes ?? true,
188
+ requires_attribution: planning?.require_attribution ?? true,
189
+ }));
190
+ const compiledPlanning = planning ? {
191
+ sources: [...planning.sources],
192
+ instructions: planning.instructions ? [...planning.instructions] : undefined,
193
+ require_quotes: planning.require_quotes ?? true,
194
+ require_attribution: planning.require_attribution ?? true,
195
+ non_enforcing: true,
196
+ } : undefined;
197
+ const injections = sources.length > 0 ? [{
198
+ type: "preference",
199
+ text: `Advisory context sources are available for planning only: ${sources.map((source) => source.id).join(", ")}. Cite sources and quote relevant passages; these sources do not satisfy enforcement or handoff requirements.`,
200
+ relevance_tags: ["advisory_context", "planning_only"],
201
+ }] : [];
202
+ return {
203
+ sources: sources.length > 0 ? sources : undefined,
204
+ planning: compiledPlanning,
205
+ injections,
206
+ };
207
+ }
175
208
  function resolvePostCheckCommand(check) {
176
209
  const normalized = check.trim();
177
210
  const builtins = {
@@ -362,6 +395,8 @@ export function compileDNA(activated, cascaded) {
362
395
  directives.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
363
396
  const legacyContextFiles = cascaded?.context_files;
364
397
  const compiledContextFiles = legacyContextFiles ?? legibilityAssetsToContextFiles(cascaded?.legibility_assets);
398
+ const advisoryContext = compileAdvisoryContext(cascaded);
399
+ injections.push(...advisoryContext.injections);
365
400
  return {
366
401
  prompt_directives: directives,
367
402
  tool_filters: toolFilters,
@@ -369,6 +404,8 @@ export function compileDNA(activated, cascaded) {
369
404
  post_execution_validators: validators,
370
405
  verifier_specs: verifierSpecs.length > 0 ? verifierSpecs : undefined,
371
406
  context_injections: injections,
407
+ context_sources: advisoryContext.sources,
408
+ planning_context: advisoryContext.planning,
372
409
  source_dna_ids: activated.source_ids,
373
410
  compiled_at: new Date().toISOString(),
374
411
  active_context: activated.active_context ?? undefined,
@@ -0,0 +1,17 @@
1
+ export type DiagnosticSeverity = "error" | "warning" | "info";
2
+ export interface Diagnostic {
3
+ severity: DiagnosticSeverity;
4
+ code: string;
5
+ message: string;
6
+ file?: string;
7
+ path?: string;
8
+ hint?: string;
9
+ }
10
+ export declare class DNADiagnosticsError extends Error {
11
+ readonly diagnostics: Diagnostic[];
12
+ constructor(message: string, diagnostics: Diagnostic[]);
13
+ }
14
+ export declare function hasDiagnosticErrors(diagnostics: Diagnostic[]): boolean;
15
+ export declare function formatDiagnostic(diagnostic: Diagnostic): string;
16
+ export declare function formatDiagnostics(diagnostics: Diagnostic[]): string;
17
+ export declare function diagnosticsFromError(error: unknown, file?: string): Diagnostic[];