intentdna 1.6.5 → 1.7.4

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 (82) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +26 -15
  4. package/dist/cli/commands/compile.js +2 -47
  5. package/dist/cli/commands/context.d.ts +8 -0
  6. package/dist/cli/commands/context.js +63 -0
  7. package/dist/cli/commands/feedback.d.ts +3 -2
  8. package/dist/cli/commands/feedback.js +16 -5
  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 +191 -186
  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.d.ts +32 -0
  18. package/dist/cli/commands/verify.js +270 -31
  19. package/dist/cli/index.js +78 -11
  20. package/dist/compiler/activate.js +11 -6
  21. package/dist/compiler/cascade.d.ts +5 -1
  22. package/dist/compiler/cascade.js +74 -1
  23. package/dist/compiler/compile.js +38 -0
  24. package/dist/compiler/diagnostics.d.ts +17 -0
  25. package/dist/compiler/diagnostics.js +30 -0
  26. package/dist/compiler/index.d.ts +7 -0
  27. package/dist/compiler/index.js +13 -13
  28. package/dist/compiler/input-resolver.d.ts +32 -0
  29. package/dist/compiler/input-resolver.js +281 -0
  30. package/dist/compiler/provenance.d.ts +8 -0
  31. package/dist/compiler/provenance.js +127 -0
  32. package/dist/governance/index.d.ts +4 -3
  33. package/dist/governance/index.js +3 -4
  34. package/dist/governance/runtime-decision-event.d.ts +85 -0
  35. package/dist/governance/runtime-decision-event.js +231 -0
  36. package/dist/governance/types.d.ts +3 -3
  37. package/dist/governance/types.js +3 -3
  38. package/dist/hooks/cli.d.ts +10 -1
  39. package/dist/hooks/cli.js +199 -35
  40. package/dist/hooks/state.d.ts +5 -10
  41. package/dist/hooks/state.js +170 -2
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.js +1 -0
  44. package/dist/mcp/index.js +2 -0
  45. package/dist/mcp/tools-compile.js +18 -49
  46. package/dist/mcp/tools-context.d.ts +2 -0
  47. package/dist/mcp/tools-context.js +85 -0
  48. package/dist/mcp/tools-enforce.d.ts +2 -2
  49. package/dist/mcp/tools-enforce.js +19 -49
  50. package/dist/mcp/tools-observability.js +26 -2
  51. package/dist/mcp/tools-state.d.ts +1 -1
  52. package/dist/mcp/tools-state.js +17 -7
  53. package/dist/report/kernel-report.d.ts +27 -0
  54. package/dist/report/kernel-report.js +60 -19
  55. package/dist/report/kernel-signals.d.ts +5 -2
  56. package/dist/report/kernel-signals.js +87 -2
  57. package/dist/report/report-package.d.ts +64 -0
  58. package/dist/report/report-package.js +90 -0
  59. package/dist/runtime/agent-md.d.ts +1 -0
  60. package/dist/runtime/agent-md.js +21 -3
  61. package/dist/runtime/claude-sdk.d.ts +9 -4
  62. package/dist/runtime/claude-sdk.js +9 -0
  63. package/dist/runtime/context-sources.d.ts +14 -0
  64. package/dist/runtime/context-sources.js +60 -0
  65. package/dist/runtime/plugin-adapter.d.ts +5 -1
  66. package/dist/runtime/plugin-adapter.js +2 -0
  67. package/dist/runtime/skill-adapter.d.ts +32 -4
  68. package/dist/runtime/skill-adapter.js +184 -9
  69. package/dist/runtime/workflow-runner.d.ts +1 -1
  70. package/dist/runtime/workflow-runner.js +1 -1
  71. package/dist/schema/types.d.ts +84 -0
  72. package/dist/schema/validate.js +156 -2
  73. package/dist/schema/validators/controllers.js +16 -0
  74. package/dist/signals/index.d.ts +10 -0
  75. package/dist/signals/index.js +90 -5
  76. package/dist/templates/catalog.d.ts +19 -0
  77. package/dist/templates/catalog.js +57 -0
  78. package/dist/templates/flutter-rewrite.dna.yaml +2 -2
  79. package/package.json +1 -1
  80. package/spec/README.md +1 -1
  81. package/spec/foundation-hardening.md +2 -1
  82. package/spec/schema-spec.md +78 -1
@@ -10,6 +10,7 @@
10
10
  * All write operations use atomic temp+rename pattern.
11
11
  */
12
12
  import type { ArtifactFact, ArtifactKey, ArtifactManifest, CompletedArtifactEntry, HandoffArtifact, HandoffType, VerifierKind, VerifierSeverity, VerifierWhen } from "../schema/types.js";
13
+ import { type RuntimeDecisionEvent } from "../governance/index.js";
13
14
  /** Workflow state written by runtime and read by hooks */
14
15
  export interface DNAWorkflowState {
15
16
  active: boolean;
@@ -136,6 +137,7 @@ export declare function writeSessionReads(projectDir: string, state: SessionRead
136
137
  */
137
138
  export declare function appendSessionRead(projectDir: string, filePath: string, sessionId?: string): Promise<void>;
138
139
  export interface VerifierResultEntry {
140
+ result_id?: string;
139
141
  verifier_id: string;
140
142
  when: VerifierWhen;
141
143
  severity: VerifierSeverity;
@@ -151,6 +153,7 @@ export interface VerifierResultEntry {
151
153
  message?: string;
152
154
  timestamp: string;
153
155
  }
156
+ export declare function verifierResultId(result: Omit<VerifierResultEntry, "result_id"> | VerifierResultEntry): string;
154
157
  export declare function readVerifierResults(projectDir: string, sessionId?: string): Promise<VerifierResultEntry[]>;
155
158
  export declare function writeVerifierResults(projectDir: string, results: VerifierResultEntry[], sessionId?: string): Promise<void>;
156
159
  export declare function appendVerifierResult(projectDir: string, result: VerifierResultEntry, sessionId?: string): Promise<void>;
@@ -178,16 +181,8 @@ export interface TraceEntry {
178
181
  * Fail-open: never throws.
179
182
  */
180
183
  export declare function appendTrace(projectDir: string, entry: TraceEntry, sessionId?: string): Promise<void>;
181
- /**
182
- * Read trace entries.
183
- *
184
- * G1 session isolation:
185
- * - With sessionId: reads from `.dna/state/sessions/{id}/trace.jsonl`
186
- * (falls back to legacy global files with sessionId in name for backward compat)
187
- * - Without sessionId: reads from `.dna/state/trace/trace-{date}.jsonl` (global merged view)
188
- *
189
- * Returns parsed entries sorted by timestamp.
190
- */
184
+ export declare function appendRuntimeDecisionEvent(projectDir: string, event: RuntimeDecisionEvent, sessionId?: string): Promise<void>;
185
+ export declare function readRuntimeDecisionEvents(projectDir: string, days?: number, sessionId?: string): Promise<RuntimeDecisionEvent[]>;
191
186
  export declare function readTraces(projectDir: string, days?: number, sessionId?: string): Promise<TraceEntry[]>;
192
187
  /**
193
188
  * List active session directories under `.dna/state/sessions/`.
@@ -12,6 +12,7 @@
12
12
  import { readFile, writeFile, rename, mkdir, appendFile, unlink, readdir, stat } from "node:fs/promises";
13
13
  import { createHash } from "node:crypto";
14
14
  import { join, dirname } from "node:path";
15
+ import { CASCADE_LAYER_NAMES, RUNTIME_DECISION_EVENT_SCHEMA_VERSION } from "../governance/index.js";
15
16
  // ── Constants ──────────────────────────────────────────────
16
17
  const WORKFLOW_FILE = "workflow.json";
17
18
  const DEFAULT_STALENESS_MS = 2 * 60 * 60 * 1000; // 2 hours
@@ -330,13 +331,34 @@ export async function appendSessionRead(projectDir, filePath, sessionId) {
330
331
  }
331
332
  }
332
333
  const VERIFIER_RESULTS_FILE = "workflow/verifier-results.json";
334
+ export function verifierResultId(result) {
335
+ return "vr_" + createHash("sha256")
336
+ .update(JSON.stringify({
337
+ verifier_id: result.verifier_id,
338
+ when: result.when,
339
+ severity: result.severity,
340
+ kind: result.kind,
341
+ workflow: result.workflow ?? "",
342
+ step_id: result.step_id ?? "",
343
+ status: result.status,
344
+ target: result.target ?? "",
345
+ evidence: result.evidence ?? "",
346
+ artifact: result.artifact ?? "",
347
+ timestamp: result.timestamp,
348
+ }))
349
+ .digest("hex")
350
+ .slice(0, 16);
351
+ }
352
+ function normalizeVerifierResult(result) {
353
+ return result.result_id ? result : { ...result, result_id: verifierResultId(result) };
354
+ }
333
355
  async function readVerifierResultsFile(projectDir, sessionId) {
334
356
  const stateDir = resolveStateDir(projectDir, sessionId);
335
357
  const filePath = join(stateDir, VERIFIER_RESULTS_FILE);
336
358
  try {
337
359
  const raw = await readFile(filePath, "utf-8");
338
360
  const parsed = JSON.parse(raw);
339
- return Array.isArray(parsed) ? parsed : [];
361
+ return Array.isArray(parsed) ? parsed.map(normalizeVerifierResult) : [];
340
362
  }
341
363
  catch {
342
364
  return [];
@@ -381,10 +403,11 @@ export async function writeVerifierResults(projectDir, results, sessionId) {
381
403
  }
382
404
  export async function appendVerifierResult(projectDir, result, sessionId) {
383
405
  const results = await readVerifierResultsFile(projectDir, sessionId);
384
- results.push(result);
406
+ results.push(normalizeVerifierResult(result));
385
407
  await writeVerifierResults(projectDir, results, sessionId);
386
408
  }
387
409
  const TRACE_DIR = "trace";
410
+ const RUNTIME_DECISION_DIR = "runtime-decisions";
388
411
  const MAX_TRACE_SIZE = 10 * 1024 * 1024; // 10MB
389
412
  const TRACE_RETENTION_DAYS = 7;
390
413
  /**
@@ -446,6 +469,151 @@ export async function appendTrace(projectDir, entry, sessionId) {
446
469
  *
447
470
  * Returns parsed entries sorted by timestamp.
448
471
  */
472
+ function datedJsonlFileName(prefix, date) {
473
+ return `${prefix}-${date}.jsonl`;
474
+ }
475
+ function decisionFileName(date) {
476
+ return datedJsonlFileName("runtime-decision", date);
477
+ }
478
+ export async function appendRuntimeDecisionEvent(projectDir, event, sessionId) {
479
+ try {
480
+ const line = JSON.stringify(event) + "\n";
481
+ const date = new Date().toISOString().slice(0, 10);
482
+ const globalDir = join(projectDir, ".dna", "state", RUNTIME_DECISION_DIR);
483
+ await mkdir(globalDir, { recursive: true });
484
+ await appendFile(join(globalDir, decisionFileName(date)), line, "utf-8");
485
+ if (sessionId) {
486
+ const sessionDir = resolveStateDir(projectDir, sessionId);
487
+ await mkdir(sessionDir, { recursive: true });
488
+ await appendFile(join(sessionDir, "runtime-decisions.jsonl"), line, "utf-8");
489
+ }
490
+ }
491
+ catch {
492
+ // Fail-open: evidence write failure never affects hook execution
493
+ }
494
+ }
495
+ function normalizeRuntimeDecisionEvent(value) {
496
+ if (typeof value !== "object" || value === null || Array.isArray(value))
497
+ return null;
498
+ const record = value;
499
+ const hasDecision = record.decision === "allow" || record.decision === "warn" || record.decision === "block" || record.decision === "escalate" || record.decision === "validate";
500
+ const decision = hasDecision ? record.decision : "unknown";
501
+ const schemaVersion = record.schema_version === RUNTIME_DECISION_EVENT_SCHEMA_VERSION ? RUNTIME_DECISION_EVENT_SCHEMA_VERSION : "unknown";
502
+ const hasTimestamp = typeof record.timestamp === "string" && record.timestamp.trim().length > 0;
503
+ const cascadeLayers = Object.fromEntries(CASCADE_LAYER_NAMES.map((layer) => {
504
+ const entry = typeof record.cascade_layers === "object" && record.cascade_layers !== null && !Array.isArray(record.cascade_layers)
505
+ ? record.cascade_layers[layer]
506
+ : undefined;
507
+ const layerRecord = typeof entry === "object" && entry !== null && !Array.isArray(entry) ? entry : {};
508
+ return [layer, {
509
+ layer,
510
+ source_id: typeof layerRecord.source_id === "string" ? layerRecord.source_id : "unknown",
511
+ source_ref: typeof layerRecord.source_ref === "string" ? layerRecord.source_ref : "unknown",
512
+ fingerprint: typeof layerRecord.fingerprint === "string" ? layerRecord.fingerprint : "unknown",
513
+ status: layerRecord.status === "active" || layerRecord.status === "unsupported" ? layerRecord.status : "unknown",
514
+ }];
515
+ }));
516
+ const winningConstraint = typeof record.winning_constraint === "object" && record.winning_constraint !== null && !Array.isArray(record.winning_constraint)
517
+ ? record.winning_constraint
518
+ : {};
519
+ const harnessRuntimeContext = typeof record.harness_runtime_context === "object" && record.harness_runtime_context !== null && !Array.isArray(record.harness_runtime_context)
520
+ ? record.harness_runtime_context
521
+ : {};
522
+ const evidenceRefs = Array.isArray(record.evidence_refs)
523
+ ? record.evidence_refs.filter((ref) => typeof ref === "object" && ref !== null && !Array.isArray(ref) && typeof ref.type === "string" && typeof ref.ref === "string")
524
+ : [];
525
+ return {
526
+ schema_version: schemaVersion,
527
+ event_id: typeof record.event_id === "string" && record.event_id ? record.event_id : "unknown",
528
+ timestamp: hasTimestamp ? record.timestamp : "unknown",
529
+ org_id: typeof record.org_id === "string" ? record.org_id : "unknown",
530
+ team_id: typeof record.team_id === "string" ? record.team_id : "unknown",
531
+ user_id: typeof record.user_id === "string" ? record.user_id : "unknown",
532
+ project_id: typeof record.project_id === "string" ? record.project_id : "unknown",
533
+ agent_id: typeof record.agent_id === "string" ? record.agent_id : "unknown",
534
+ agent_role: typeof record.agent_role === "string" ? record.agent_role : "unknown",
535
+ agent_type: typeof record.agent_type === "string" ? record.agent_type : "unknown",
536
+ session_agent: typeof record.session_agent === "string" ? record.session_agent : "unknown",
537
+ harness_adapter: typeof record.harness_adapter === "string" ? record.harness_adapter : "unknown",
538
+ runtime: typeof record.runtime === "string" ? record.runtime : "unknown",
539
+ policy_bundle_id: typeof record.policy_bundle_id === "string" ? record.policy_bundle_id : "unknown",
540
+ policy_bundle_version: typeof record.policy_bundle_version === "string" ? record.policy_bundle_version : "unknown",
541
+ cascade_layers: cascadeLayers,
542
+ winning_constraint: {
543
+ constraint_id: typeof winningConstraint.constraint_id === "string" ? winningConstraint.constraint_id : "unknown",
544
+ source_layer: typeof winningConstraint.source_layer === "string" && CASCADE_LAYER_NAMES.some((layer) => layer === winningConstraint.source_layer) ? winningConstraint.source_layer : "unknown",
545
+ source_dna_id: typeof winningConstraint.source_dna_id === "string" ? winningConstraint.source_dna_id : "unknown",
546
+ gene_id: typeof winningConstraint.gene_id === "string" ? winningConstraint.gene_id : "unknown",
547
+ codon_type: typeof winningConstraint.codon_type === "string" ? winningConstraint.codon_type : "unknown",
548
+ action: decision,
549
+ provenance_reason: typeof winningConstraint.provenance_reason === "string" ? winningConstraint.provenance_reason : "unknown",
550
+ },
551
+ compiled_ir_hash: typeof record.compiled_ir_hash === "string" ? record.compiled_ir_hash : "unknown",
552
+ decision: decision,
553
+ decision_reason: typeof record.decision_reason === "string" ? record.decision_reason : "unknown",
554
+ enforcement_point: typeof record.enforcement_point === "string" ? record.enforcement_point : "unknown",
555
+ harness_runtime_context: {
556
+ agent_id: typeof harnessRuntimeContext.agent_id === "string" ? harnessRuntimeContext.agent_id : typeof record.agent_id === "string" ? record.agent_id : "unknown",
557
+ agent_role: typeof harnessRuntimeContext.agent_role === "string" ? harnessRuntimeContext.agent_role : typeof record.agent_role === "string" ? record.agent_role : "unknown",
558
+ agent_type: typeof harnessRuntimeContext.agent_type === "string" ? harnessRuntimeContext.agent_type : typeof record.agent_type === "string" ? record.agent_type : "unknown",
559
+ session_agent: typeof harnessRuntimeContext.session_agent === "string" ? harnessRuntimeContext.session_agent : typeof record.session_agent === "string" ? record.session_agent : "unknown",
560
+ harness_adapter: typeof harnessRuntimeContext.harness_adapter === "string" ? harnessRuntimeContext.harness_adapter : typeof record.harness_adapter === "string" ? record.harness_adapter : "unknown",
561
+ runtime: typeof harnessRuntimeContext.runtime === "string" ? harnessRuntimeContext.runtime : typeof record.runtime === "string" ? record.runtime : "unknown",
562
+ },
563
+ session_id: typeof record.session_id === "string" ? record.session_id : "unknown",
564
+ run_id: typeof record.run_id === "string" ? record.run_id : "unknown",
565
+ step_id: typeof record.step_id === "string" ? record.step_id : "unknown",
566
+ tool_name: typeof record.tool_name === "string" ? record.tool_name : "unknown",
567
+ action_kind: typeof record.action_kind === "string" ? record.action_kind : "unknown",
568
+ resource_ref: typeof record.resource_ref === "string" ? record.resource_ref : "unknown",
569
+ evidence_refs: evidenceRefs.length > 0 ? evidenceRefs : [{ type: "unknown", ref: "unknown" }],
570
+ };
571
+ }
572
+ export async function readRuntimeDecisionEvents(projectDir, days = 1, sessionId) {
573
+ const entries = [];
574
+ if (sessionId) {
575
+ entries.push(...await readRuntimeDecisionFile(join(resolveStateDir(projectDir, sessionId), "runtime-decisions.jsonl")));
576
+ }
577
+ else {
578
+ entries.push(...await readGlobalRuntimeDecisionEvents(projectDir, days));
579
+ }
580
+ return entries.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
581
+ }
582
+ async function readRuntimeDecisionFile(filePath) {
583
+ const entries = [];
584
+ try {
585
+ const content = await readFile(filePath, "utf-8");
586
+ for (const line of content.trim().split("\n")) {
587
+ if (!line)
588
+ continue;
589
+ try {
590
+ const event = normalizeRuntimeDecisionEvent(JSON.parse(line));
591
+ if (event)
592
+ entries.push(event);
593
+ }
594
+ catch { /* skip malformed JSON */ }
595
+ }
596
+ }
597
+ catch { /* file doesn't exist */ }
598
+ return entries;
599
+ }
600
+ async function readGlobalRuntimeDecisionEvents(projectDir, days) {
601
+ const dir = join(projectDir, ".dna", "state", RUNTIME_DECISION_DIR);
602
+ const entries = [];
603
+ const cutoff = new Date();
604
+ cutoff.setDate(cutoff.getDate() - days);
605
+ const cutoffDate = cutoff.toISOString().slice(0, 10);
606
+ try {
607
+ const files = (await readdir(dir))
608
+ .filter(f => f.startsWith("runtime-decision-") && f.endsWith(".jsonl"))
609
+ .filter(f => f.slice("runtime-decision-".length, "runtime-decision-".length + 10) >= cutoffDate)
610
+ .sort();
611
+ for (const file of files)
612
+ entries.push(...await readRuntimeDecisionFile(join(dir, file)));
613
+ }
614
+ catch { /* no decisions yet */ }
615
+ return entries;
616
+ }
449
617
  export async function readTraces(projectDir, days = 1, sessionId) {
450
618
  const entries = [];
451
619
  if (sessionId) {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./schema/index.js";
2
2
  export * from "./compiler/index.js";
3
3
  export * from "./runtime/index.js";
4
+ export * from "./governance/index.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./schema/index.js";
2
2
  export * from "./compiler/index.js";
3
3
  export * from "./runtime/index.js";
4
+ export * from "./governance/index.js";
package/dist/mcp/index.js CHANGED
@@ -15,6 +15,7 @@ import { createStateTools } from "./tools-state.js";
15
15
  import { createCompileTools } from "./tools-compile.js";
16
16
  import { createEnforceTools } from "./tools-enforce.js";
17
17
  import { createObservabilityTools } from "./tools-observability.js";
18
+ import { createContextTools } from "./tools-context.js";
18
19
  // Parse args
19
20
  const args = process.argv.slice(2);
20
21
  let projectDir = process.cwd();
@@ -36,6 +37,7 @@ catch { /* use default */ }
36
37
  const tools = [
37
38
  ...createStateTools(projectDir),
38
39
  ...createCompileTools(projectDir),
40
+ ...createContextTools(projectDir),
39
41
  ...createEnforceTools(projectDir),
40
42
  ...createObservabilityTools(projectDir),
41
43
  ];
@@ -7,45 +7,8 @@
7
7
  * - dna_sync: Run full sync pipeline (compile + inject + hooks)
8
8
  */
9
9
  import { resolve } from "node:path";
10
- import { stat } from "node:fs/promises";
11
- import { loadDNA, compileFromFiles } from "../compiler/index.js";
10
+ import { compileFromFiles, detectDNAConfigs, expandDNAInputFiles, formatDiagnostic, loadDNAWithDiagnostics } from "../compiler/index.js";
12
11
  import { textResult, errorResult } from "./server.js";
13
- async function fileExists(path) {
14
- try {
15
- await stat(path);
16
- return true;
17
- }
18
- catch {
19
- return false;
20
- }
21
- }
22
- /** Auto-detect DNA config files in the project directory. */
23
- async function detectConfigs(projectDir) {
24
- const { readdir } = await import("node:fs/promises");
25
- // Multi-config: .dna/configs/*.yaml
26
- const configsDir = resolve(projectDir, ".dna", "configs");
27
- try {
28
- const files = await readdir(configsDir);
29
- const yamls = files
30
- .filter(f => f.endsWith(".yaml") || f.endsWith(".yml"))
31
- .sort()
32
- .map(f => resolve(configsDir, f));
33
- if (yamls.length > 0)
34
- return yamls;
35
- }
36
- catch { /* dir doesn't exist */ }
37
- // Legacy single-config
38
- const candidates = [
39
- ".dna/config.yaml", ".dna/config.yml", ".dna/config.json",
40
- ".dna.yaml", ".dna.yml", ".dna.json",
41
- ];
42
- for (const name of candidates) {
43
- const path = resolve(projectDir, name);
44
- if (await fileExists(path))
45
- return [path];
46
- }
47
- return [];
48
- }
49
12
  export function createCompileTools(projectDir) {
50
13
  return [
51
14
  // ── dna_validate ─────────────────────────────────────
@@ -62,20 +25,23 @@ export function createCompileTools(projectDir) {
62
25
  const configPath = typeof args.config_path === "string"
63
26
  ? resolve(projectDir, args.config_path)
64
27
  : undefined;
65
- const files = configPath ? [configPath] : await detectConfigs(projectDir);
28
+ const files = configPath ? [configPath] : await detectDNAConfigs(projectDir);
66
29
  if (files.length === 0)
67
30
  return errorResult("No DNA config files found.");
68
31
  const results = [];
69
32
  let allValid = true;
70
33
  for (const file of files) {
71
- try {
72
- // loadDNA calls validateDNA internally throws on invalid
73
- await loadDNA(file);
34
+ const result = await loadDNAWithDiagnostics(file);
35
+ const errors = result.diagnostics.filter((diagnostic) => diagnostic.severity === "error");
36
+ if (errors.length > 0 || !result.dna) {
37
+ allValid = false;
38
+ results.push(`${file}: INVALID`);
39
+ }
40
+ else {
74
41
  results.push(`${file}: valid`);
75
42
  }
76
- catch (err) {
77
- allValid = false;
78
- results.push(`${file}: INVALID — ${err instanceof Error ? err.message : String(err)}`);
43
+ for (const diagnostic of result.diagnostics) {
44
+ results.push(` ${formatDiagnostic(diagnostic)}`);
79
45
  }
80
46
  }
81
47
  results.unshift(allValid ? "All configs valid." : "Validation errors found.");
@@ -98,11 +64,12 @@ export function createCompileTools(projectDir) {
98
64
  const configPath = typeof args.config_path === "string"
99
65
  ? resolve(projectDir, args.config_path)
100
66
  : undefined;
101
- const files = configPath ? [configPath] : await detectConfigs(projectDir);
67
+ const files = configPath ? [configPath] : await detectDNAConfigs(projectDir);
102
68
  if (files.length === 0)
103
69
  return errorResult("No DNA config files found.");
104
70
  try {
105
- const ir = await compileFromFiles(files, {
71
+ const expandedFiles = await expandDNAInputFiles(files);
72
+ const ir = await compileFromFiles(expandedFiles, {
106
73
  context: typeof args.context === "string" ? args.context : undefined,
107
74
  role: typeof args.role === "string" ? args.role : undefined,
108
75
  });
@@ -142,12 +109,13 @@ export function createCompileTools(projectDir) {
142
109
  evolve: { type: "boolean", description: "Run evolution engine before compiling (default false)" },
143
110
  context: { type: "string", description: "Active context name" },
144
111
  role: { type: "string", description: "Active role name" },
112
+ mode: { type: "string", enum: ["auto", "plugin", "bin"], description: "Sync mode (default auto)" },
145
113
  },
146
114
  },
147
115
  handler: async (args) => {
148
116
  // Import sync command dynamically to avoid circular deps
149
- const { runSync, autoDetectConfigs: detectAll } = await import("../cli/commands/sync.js");
150
- const files = await detectAll();
117
+ const { runSync } = await import("../cli/commands/sync.js");
118
+ const files = await detectDNAConfigs(projectDir);
151
119
  if (files.length === 0)
152
120
  return errorResult("No DNA config files found.");
153
121
  const exitCode = await runSync({
@@ -160,6 +128,7 @@ export function createCompileTools(projectDir) {
160
128
  skillsDir: resolve(projectDir, ".claude", "skills"),
161
129
  settingsPath: resolve(projectDir, ".claude", "settings.json"),
162
130
  remove: false,
131
+ mode: typeof args.mode === "string" && ["auto", "plugin", "bin"].includes(args.mode) ? args.mode : "auto",
163
132
  });
164
133
  if (exitCode === 0) {
165
134
  return textResult("Sync completed successfully.");
@@ -0,0 +1,2 @@
1
+ import type { ToolDef } from "./server.js";
2
+ export declare function createContextTools(projectDir: string): ToolDef[];
@@ -0,0 +1,85 @@
1
+ import { resolve } from "node:path";
2
+ import { findContextSource, formatContextSource, loadContextSourceIndex, readContextSourceDocument, searchContextSources, } from "../runtime/context-sources.js";
3
+ import { errorResult, textResult } from "./server.js";
4
+ function filesArg(args, projectDir) {
5
+ if (typeof args.config_path === "string")
6
+ return [resolve(projectDir, args.config_path)];
7
+ return undefined;
8
+ }
9
+ export function createContextTools(projectDir) {
10
+ return [
11
+ {
12
+ name: "dna_context_sources",
13
+ description: "List explicitly declared planning-only advisory context sources",
14
+ inputSchema: {
15
+ type: "object",
16
+ properties: {
17
+ config_path: { type: "string", description: "Path to DNA config (auto-detected if omitted)" },
18
+ },
19
+ },
20
+ handler: async (args) => {
21
+ const index = await loadContextSourceIndex(projectDir, filesArg(args, projectDir));
22
+ if (index.sources.length === 0)
23
+ return textResult("No advisory context sources declared.");
24
+ const lines = index.sources.map(formatContextSource);
25
+ if (index.planning_instructions.length > 0) {
26
+ lines.push("", "Planning instructions:");
27
+ for (const instruction of index.planning_instructions)
28
+ lines.push(`- ${instruction}`);
29
+ }
30
+ return textResult(lines.join("\n"));
31
+ },
32
+ },
33
+ {
34
+ name: "dna_context_search",
35
+ description: "Search advisory context source metadata. This does not search repository files.",
36
+ inputSchema: {
37
+ type: "object",
38
+ properties: {
39
+ query: { type: "string", description: "Metadata query" },
40
+ config_path: { type: "string", description: "Path to DNA config (auto-detected if omitted)" },
41
+ },
42
+ required: ["query"],
43
+ },
44
+ handler: async (args) => {
45
+ const query = typeof args.query === "string" ? args.query : "";
46
+ const index = await loadContextSourceIndex(projectDir, filesArg(args, projectDir));
47
+ const matches = searchContextSources(index, query);
48
+ if (matches.length === 0)
49
+ return errorResult(`No advisory context sources matched '${query}'.`);
50
+ return textResult(matches.map(formatContextSource).join("\n"));
51
+ },
52
+ },
53
+ {
54
+ name: "dna_get_context",
55
+ description: "Read a declared local advisory context document by source id",
56
+ inputSchema: {
57
+ type: "object",
58
+ properties: {
59
+ id: { type: "string", description: "Context source id" },
60
+ config_path: { type: "string", description: "Path to DNA config (auto-detected if omitted)" },
61
+ },
62
+ required: ["id"],
63
+ },
64
+ handler: async (args) => {
65
+ const id = typeof args.id === "string" ? args.id : "";
66
+ const index = await loadContextSourceIndex(projectDir, filesArg(args, projectDir));
67
+ const source = findContextSource(index, id);
68
+ if (!source)
69
+ return errorResult(`Unknown advisory context source '${id}'.`);
70
+ const doc = await readContextSourceDocument(projectDir, source);
71
+ const lines = [
72
+ formatContextSource(source),
73
+ "Planning-only advisory context; does not satisfy enforcement, handoff, or ArtifactManifest requirements.",
74
+ ];
75
+ if (doc.content !== undefined) {
76
+ lines.push("", "---", doc.content);
77
+ }
78
+ else {
79
+ lines.push("No local document body is available for this source type.");
80
+ }
81
+ return textResult(lines.join("\n"));
82
+ },
83
+ },
84
+ ];
85
+ }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Intent DNA — MCP Enforce Tools (U2)
3
3
  *
4
- * Provides enforcement via MCP server with in-memory IR cache.
5
- * Zero cold-start: IR is loaded once and cached with mtime-based invalidation.
4
+ * Provides enforcement via a persistent MCP server with in-memory IR cache.
5
+ * MCP calls reuse the full hook runtime without reloading IR on every request.
6
6
  *
7
7
  * Tools:
8
8
  * - dna_enforce: Run enforcement for a hook event (PreToolUse, PostToolUse, etc.)
@@ -1,16 +1,18 @@
1
1
  /**
2
2
  * Intent DNA — MCP Enforce Tools (U2)
3
3
  *
4
- * Provides enforcement via MCP server with in-memory IR cache.
5
- * Zero cold-start: IR is loaded once and cached with mtime-based invalidation.
4
+ * Provides enforcement via a persistent MCP server with in-memory IR cache.
5
+ * MCP calls reuse the full hook runtime without reloading IR on every request.
6
6
  *
7
7
  * Tools:
8
8
  * - dna_enforce: Run enforcement for a hook event (PreToolUse, PostToolUse, etc.)
9
9
  */
10
10
  import { resolve } from "node:path";
11
11
  import { stat, readFile } from "node:fs/promises";
12
- import { enforcePreToolUse, enforcePostToolUse, enforceUserPromptSubmit, enforceSubagentStop, enforcePreCompact, enforceNotification, enforceSessionStart, } from "../hooks/enforce.js";
12
+ import { silentOutput } from "../hooks/protocol.js";
13
13
  import { hookEventsForSurface } from "../hooks/event-registry.js";
14
+ import { validateHookInput } from "../hooks/schema.js";
15
+ import { runHookEvent } from "../hooks/cli.js";
14
16
  import { textResult, errorResult } from "./server.js";
15
17
  // ── IR Cache ────────────────────────────────────────────────
16
18
  /** In-memory IR cache with mtime-based invalidation. */
@@ -63,49 +65,17 @@ export class IRCache {
63
65
  }
64
66
  }
65
67
  const VALID_EVENTS = new Set(hookEventsForSurface("mcp"));
66
- function dispatchEnforce(event, ir, input, roles) {
67
- const result = dispatchEnforceInner(event, ir, input, roles);
68
- return result?.output ?? { continue: true, suppressOutput: true };
69
- }
70
- function dispatchEnforceInner(event, ir, input, roles) {
71
- switch (event) {
72
- case "PreToolUse":
73
- return enforcePreToolUse(ir, {
74
- tool_name: String(input.tool_name ?? ""),
75
- tool_input: (input.tool_input ?? {}),
76
- agent_type: typeof input.agent_type === "string" ? input.agent_type : undefined,
77
- cwd: typeof input.cwd === "string" ? input.cwd : undefined,
78
- }, undefined, roles);
79
- case "PostToolUse":
80
- return enforcePostToolUse(ir, {
81
- tool_name: String(input.tool_name ?? ""),
82
- tool_input: (input.tool_input ?? {}),
83
- tool_output: typeof input.tool_output === "string" ? input.tool_output : undefined,
84
- agent_type: typeof input.agent_type === "string" ? input.agent_type : undefined,
85
- });
86
- case "UserPromptSubmit":
87
- return enforceUserPromptSubmit(ir, {
88
- prompt: typeof input.prompt === "string" ? input.prompt : undefined,
89
- });
90
- case "SubagentStop":
91
- return enforceSubagentStop(ir, {
92
- agent_name: typeof input.agent_name === "string" ? input.agent_name : undefined,
93
- agent_type: typeof input.agent_type === "string" ? input.agent_type : undefined,
94
- });
95
- case "PreCompact":
96
- return enforcePreCompact(ir);
97
- case "Notification":
98
- return enforceNotification(ir, {
99
- title: typeof input.title === "string" ? input.title : undefined,
100
- message: typeof input.message === "string" ? input.message : undefined,
101
- });
102
- case "SessionStart":
103
- return enforceSessionStart(ir, {
104
- cwd: typeof input.cwd === "string" ? input.cwd : undefined,
105
- sessionId: typeof input.session_id === "string" ? input.session_id : undefined,
106
- });
107
- default:
108
- return null;
68
+ async function dispatchEnforce(event, ir, input, projectDir, roles) {
69
+ try {
70
+ const validation = validateHookInput(event, input);
71
+ if (!validation.valid)
72
+ return silentOutput();
73
+ const rawInput = validation.normalized;
74
+ const sessionId = typeof rawInput.sessionId === "string" ? rawInput.sessionId : undefined;
75
+ return await runHookEvent({ event, ir, rawInput, projectDir, sessionId, roles });
76
+ }
77
+ catch {
78
+ return silentOutput();
109
79
  }
110
80
  }
111
81
  // ── Tool Registration ───────────────────────────────────────
@@ -114,8 +84,8 @@ export function createEnforceTools(projectDir) {
114
84
  return [
115
85
  {
116
86
  name: "dna_enforce",
117
- description: "Run DNA enforcement for a hook event. Uses in-memory cached IR for zero cold-start. " +
118
- "Returns the same result as the dna-hook binary but without spawning a new process.",
87
+ description: "Run DNA enforcement for a hook event inside the persistent MCP server. " +
88
+ "Uses cached IR and the same runtime path as the dna-hook binary.",
119
89
  inputSchema: {
120
90
  type: "object",
121
91
  properties: {
@@ -141,7 +111,7 @@ export function createEnforceTools(projectDir) {
141
111
  return errorResult("No compiled IR found. Run `dna sync` to compile your DNA config.");
142
112
  }
143
113
  const input = (args.input ?? {});
144
- const result = dispatchEnforce(event, cached.ir, input, cached.roles);
114
+ const result = await dispatchEnforce(event, cached.ir, input, projectDir, cached.roles);
145
115
  // Format as human-readable text
146
116
  const lines = [];
147
117
  lines.push(`Decision: ${result.continue ? "allow" : "BLOCK"}`);
@@ -1,10 +1,11 @@
1
1
  import { textResult } from "./server.js";
2
2
  import { buildKernelReport, formatKernelReport } from "../report/kernel-report.js";
3
+ import { buildReportPackage } from "../report/report-package.js";
3
4
  export function createObservabilityTools(projectDir) {
4
5
  return [
5
6
  {
6
7
  name: "dna_kernel_report",
7
- description: "Observability-only local kernel report. Reads traces/verifier results and optional preview-only marker changes; does not execute verifiers or mutate state.",
8
+ description: "Observability-only local kernel report. Reads RuntimeDecisionEvent as primary enterprise evidence; legacy traces/verifier results are diagnostic-only. Does not execute verifiers or mutate state.",
8
9
  inputSchema: {
9
10
  type: "object",
10
11
  properties: {
@@ -27,7 +28,30 @@ export function createObservabilityTools(projectDir) {
27
28
  return textResult(JSON.stringify(report, null, 2));
28
29
  }
29
30
  return textResult(formatKernelReport(report) +
30
- "\nMCP observability only: this tool does not execute verifiers, does not enforce hook parity, and does not mutate state or markers.\n");
31
+ "\nMCP observability only: RuntimeDecisionEvent is primary enterprise evidence; legacy traces/verifiers are diagnostic-only. This tool does not execute verifiers, does not enforce hook parity, and does not mutate state or markers.\n");
32
+ },
33
+ },
34
+ {
35
+ name: "dna_report_package",
36
+ description: "Read-only local evidence package with RuntimeDecisionEvent primary evidence, diagnostic legacy traces/verifier results/handoffs, deferrals, and fact-backed critical decisions.",
37
+ inputSchema: {
38
+ type: "object",
39
+ properties: {
40
+ days: { type: "number", description: "Days to look back (default 7)" },
41
+ session_id: { type: "string", description: "Session ID filter" },
42
+ dna_id: { type: "string", description: "DNA ID for marker preview; derived from compiled IR if omitted and unambiguous" },
43
+ include_marker_preview: { type: "boolean", description: "Include preview-only marker changes (default false)" },
44
+ },
45
+ },
46
+ handler: async (args) => {
47
+ const reportPackage = await buildReportPackage({
48
+ projectDir,
49
+ days: typeof args.days === "number" ? args.days : 7,
50
+ sessionId: typeof args.session_id === "string" ? args.session_id : undefined,
51
+ dnaId: typeof args.dna_id === "string" ? args.dna_id : undefined,
52
+ includeMarkerPreview: typeof args.include_marker_preview === "boolean" ? args.include_marker_preview : false,
53
+ });
54
+ return textResult(JSON.stringify(reportPackage, null, 2));
31
55
  },
32
56
  },
33
57
  ];