intentdna 1.6.5 → 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 (58) 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/init.js +11 -63
  9. package/dist/cli/commands/run.js +5 -4
  10. package/dist/cli/commands/show.js +2 -38
  11. package/dist/cli/commands/sync.d.ts +9 -6
  12. package/dist/cli/commands/sync.js +186 -184
  13. package/dist/cli/commands/templates.d.ts +10 -1
  14. package/dist/cli/commands/templates.js +50 -1
  15. package/dist/cli/commands/validate.js +15 -9
  16. package/dist/cli/commands/verify.js +97 -25
  17. package/dist/cli/index.js +76 -11
  18. package/dist/compiler/cascade.d.ts +3 -1
  19. package/dist/compiler/cascade.js +51 -0
  20. package/dist/compiler/compile.js +37 -0
  21. package/dist/compiler/diagnostics.d.ts +17 -0
  22. package/dist/compiler/diagnostics.js +30 -0
  23. package/dist/compiler/index.d.ts +3 -0
  24. package/dist/compiler/index.js +8 -11
  25. package/dist/compiler/input-resolver.d.ts +25 -0
  26. package/dist/compiler/input-resolver.js +175 -0
  27. package/dist/hooks/cli.d.ts +10 -1
  28. package/dist/hooks/cli.js +37 -22
  29. package/dist/hooks/state.d.ts +2 -0
  30. package/dist/hooks/state.js +23 -2
  31. package/dist/mcp/index.js +2 -0
  32. package/dist/mcp/tools-compile.js +18 -49
  33. package/dist/mcp/tools-context.d.ts +2 -0
  34. package/dist/mcp/tools-context.js +85 -0
  35. package/dist/mcp/tools-enforce.d.ts +2 -2
  36. package/dist/mcp/tools-enforce.js +19 -49
  37. package/dist/mcp/tools-observability.js +24 -0
  38. package/dist/report/kernel-signals.js +3 -0
  39. package/dist/report/report-package.d.ts +56 -0
  40. package/dist/report/report-package.js +85 -0
  41. package/dist/runtime/agent-md.d.ts +1 -0
  42. package/dist/runtime/agent-md.js +21 -3
  43. package/dist/runtime/context-sources.d.ts +14 -0
  44. package/dist/runtime/context-sources.js +60 -0
  45. package/dist/runtime/skill-adapter.d.ts +32 -4
  46. package/dist/runtime/skill-adapter.js +184 -9
  47. package/dist/runtime/workflow-runner.d.ts +1 -1
  48. package/dist/runtime/workflow-runner.js +1 -1
  49. package/dist/schema/types.d.ts +33 -0
  50. package/dist/schema/validate.js +156 -2
  51. package/dist/schema/validators/controllers.js +16 -0
  52. package/dist/signals/index.d.ts +10 -0
  53. package/dist/signals/index.js +90 -5
  54. package/dist/templates/catalog.d.ts +19 -0
  55. package/dist/templates/catalog.js +57 -0
  56. package/dist/templates/flutter-rewrite.dna.yaml +2 -2
  57. package/package.json +1 -1
  58. package/spec/foundation-hardening.md +2 -1
@@ -0,0 +1,30 @@
1
+ export class DNADiagnosticsError extends Error {
2
+ diagnostics;
3
+ constructor(message, diagnostics) {
4
+ super(message);
5
+ this.name = "DNADiagnosticsError";
6
+ this.diagnostics = diagnostics;
7
+ }
8
+ }
9
+ export function hasDiagnosticErrors(diagnostics) {
10
+ return diagnostics.some((diagnostic) => diagnostic.severity === "error");
11
+ }
12
+ export function formatDiagnostic(diagnostic) {
13
+ const location = [diagnostic.file, diagnostic.path].filter(Boolean).join(":");
14
+ const prefix = location ? `${location}: ` : "";
15
+ const hint = diagnostic.hint ? ` (${diagnostic.hint})` : "";
16
+ return `${diagnostic.severity.toUpperCase()} ${diagnostic.code}: ${prefix}${diagnostic.message}${hint}`;
17
+ }
18
+ export function formatDiagnostics(diagnostics) {
19
+ return diagnostics.map(formatDiagnostic).join("\n");
20
+ }
21
+ export function diagnosticsFromError(error, file) {
22
+ if (error instanceof DNADiagnosticsError)
23
+ return error.diagnostics;
24
+ return [{
25
+ severity: "error",
26
+ code: "unexpected_error",
27
+ message: error instanceof Error ? error.message : String(error),
28
+ file,
29
+ }];
30
+ }
@@ -10,6 +10,9 @@ export { activateDNA } from "./activate.js";
10
10
  export { compileDNA } from "./compile.js";
11
11
  export { compileWorkflow } from "./workflow.js";
12
12
  export type { CompileWorkflowOptions, CompileWorkflowResult, CompileWorkflowError, } from "./workflow.js";
13
+ export type { Diagnostic, DiagnosticSeverity } from "./diagnostics.js";
14
+ export { DNADiagnosticsError, formatDiagnostic, formatDiagnostics } from "./diagnostics.js";
15
+ export { assertNoNamespaceCollisions, checkNamespaceCollisions, detectDNAConfigs, expandDNAInputFiles, loadDNAWithDiagnostics, resolveDNAInputs, resolveSpeciesReference, } from "./input-resolver.js";
13
16
  /**
14
17
  * Load and parse a DNA file from disk.
15
18
  */
@@ -4,29 +4,26 @@
4
4
  * Full compilation pipeline:
5
5
  * DNA files → parse → cascade → activate (+ epigenetic) → compile → IR
6
6
  */
7
- import { readFile } from "node:fs/promises";
8
- import { validateDNA } from "../schema/validate.js";
9
- import { parseYAML } from "../schema/yaml-parser.js";
10
7
  import { cascadeDNA } from "./cascade.js";
11
8
  import { activateDNA } from "./activate.js";
12
9
  import { compileDNA } from "./compile.js";
10
+ import { DNADiagnosticsError, formatDiagnostics } from "./diagnostics.js";
11
+ import { loadDNAWithDiagnostics } from "./input-resolver.js";
13
12
  export { cascadeDNA } from "./cascade.js";
14
13
  export { activateDNA } from "./activate.js";
15
14
  export { compileDNA } from "./compile.js";
16
15
  export { compileWorkflow } from "./workflow.js";
16
+ export { DNADiagnosticsError, formatDiagnostic, formatDiagnostics } from "./diagnostics.js";
17
+ export { assertNoNamespaceCollisions, checkNamespaceCollisions, detectDNAConfigs, expandDNAInputFiles, loadDNAWithDiagnostics, resolveDNAInputs, resolveSpeciesReference, } from "./input-resolver.js";
17
18
  /**
18
19
  * Load and parse a DNA file from disk.
19
20
  */
20
21
  export async function loadDNA(filePath) {
21
- const raw = await readFile(filePath, "utf-8");
22
- const isYAML = filePath.endsWith(".yaml") || filePath.endsWith(".yml");
23
- const dna = (isYAML ? parseYAML(raw) : JSON.parse(raw));
24
- const result = validateDNA(dna);
25
- if (!result.valid) {
26
- const messages = result.errors.map((e) => ` ${e.path}: ${e.message}`).join("\n");
27
- throw new Error(`Invalid DNA file ${filePath}:\n${messages}`);
22
+ const result = await loadDNAWithDiagnostics(filePath);
23
+ if (!result.dna) {
24
+ throw new DNADiagnosticsError(`Invalid DNA file ${filePath}:\n${formatDiagnostics(result.diagnostics)}`, result.diagnostics);
28
25
  }
29
- return dna;
26
+ return result.dna;
30
27
  }
31
28
  /**
32
29
  * Full compilation pipeline: load DNA files, cascade, activate context, compile to IR.
@@ -0,0 +1,25 @@
1
+ import type { IntentDNA } from "../schema/types.js";
2
+ import type { Diagnostic } from "./diagnostics.js";
3
+ export interface ResolveDNAInputOptions {
4
+ cwd?: string;
5
+ includeDiagnostics?: boolean;
6
+ }
7
+ export interface ResolvedDNAInputs {
8
+ files: string[];
9
+ dnas: IntentDNA[];
10
+ diagnostics: Diagnostic[];
11
+ }
12
+ export declare function parseDNAFile(filePath: string): Promise<IntentDNA>;
13
+ export declare function resolveSpeciesReference(ref: string): string | null;
14
+ export declare function detectDNAConfigs(projectDir?: string): Promise<string[]>;
15
+ export interface ExpandDNAInputOptions {
16
+ cwd?: string;
17
+ }
18
+ export declare function expandDNAInputFiles(files: string[], options?: ExpandDNAInputOptions): Promise<string[]>;
19
+ export declare function checkNamespaceCollisions(configPaths: string[]): Promise<Diagnostic[]>;
20
+ export declare function assertNoNamespaceCollisions(configPaths: string[]): Promise<void>;
21
+ export declare function loadDNAWithDiagnostics(filePath: string): Promise<{
22
+ dna?: IntentDNA;
23
+ diagnostics: Diagnostic[];
24
+ }>;
25
+ export declare function resolveDNAInputs(files: string[], options?: ResolveDNAInputOptions): Promise<ResolvedDNAInputs>;
@@ -0,0 +1,175 @@
1
+ import { readdir, readFile, stat } from "node:fs/promises";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { validateDNA } from "../schema/validate.js";
5
+ import { parseYAML } from "../schema/yaml-parser.js";
6
+ import { DNADiagnosticsError, hasDiagnosticErrors } from "./diagnostics.js";
7
+ const DNA_CONFIG_CANDIDATES = [
8
+ ".dna/config.yaml",
9
+ ".dna/config.yml",
10
+ ".dna/config.json",
11
+ ".dna.yaml",
12
+ ".dna.yml",
13
+ ".dna.json",
14
+ ];
15
+ async function fileExists(path) {
16
+ try {
17
+ await stat(path);
18
+ return true;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ function parseDNAContent(filePath, raw) {
25
+ const isYAML = filePath.endsWith(".yaml") || filePath.endsWith(".yml");
26
+ return (isYAML ? parseYAML(raw) : JSON.parse(raw));
27
+ }
28
+ export async function parseDNAFile(filePath) {
29
+ const raw = await readFile(filePath, "utf-8");
30
+ return parseDNAContent(filePath, raw);
31
+ }
32
+ export function resolveSpeciesReference(ref) {
33
+ if (!ref.startsWith("species:"))
34
+ return null;
35
+ const name = ref.slice("species:".length);
36
+ const thisDir = dirname(fileURLToPath(import.meta.url));
37
+ return resolve(thisDir, "..", "species", `${name}.dna.json`);
38
+ }
39
+ export async function detectDNAConfigs(projectDir = process.cwd()) {
40
+ const configsDir = resolve(projectDir, ".dna", "configs");
41
+ try {
42
+ const files = await readdir(configsDir);
43
+ const configs = files
44
+ .filter((file) => file.endsWith(".yaml") || file.endsWith(".yml"))
45
+ .sort()
46
+ .map((file) => resolve(configsDir, file));
47
+ if (configs.length > 0)
48
+ return configs;
49
+ }
50
+ catch {
51
+ // Directory does not exist.
52
+ }
53
+ for (const candidate of DNA_CONFIG_CANDIDATES) {
54
+ const path = resolve(projectDir, candidate);
55
+ if (await fileExists(path))
56
+ return [path];
57
+ }
58
+ return [];
59
+ }
60
+ export async function expandDNAInputFiles(files, options) {
61
+ const cwd = options?.cwd ?? process.cwd();
62
+ const expanded = [];
63
+ const seen = new Set();
64
+ const add = (path, position) => {
65
+ if (seen.has(path))
66
+ return;
67
+ seen.add(path);
68
+ if (position === "front")
69
+ expanded.unshift(path);
70
+ else
71
+ expanded.push(path);
72
+ };
73
+ for (const file of files) {
74
+ const abs = resolve(cwd, file);
75
+ add(abs, "back");
76
+ try {
77
+ const dna = await parseDNAFile(abs);
78
+ for (const ref of dna.cascade?.inherits ?? []) {
79
+ const speciesPath = resolveSpeciesReference(ref);
80
+ if (speciesPath)
81
+ add(speciesPath, "front");
82
+ }
83
+ }
84
+ catch {
85
+ // Validation/loading reports the concrete parse error later.
86
+ }
87
+ }
88
+ return expanded;
89
+ }
90
+ export async function checkNamespaceCollisions(configPaths) {
91
+ const diagnostics = [];
92
+ const namespaces = new Map();
93
+ for (const configPath of configPaths) {
94
+ try {
95
+ const data = await parseDNAFile(configPath);
96
+ const namespace = typeof data.namespace === "string" ? data.namespace : undefined;
97
+ if (!namespace)
98
+ continue;
99
+ const previous = namespaces.get(namespace);
100
+ if (previous) {
101
+ diagnostics.push({
102
+ severity: "error",
103
+ code: "namespace_conflict",
104
+ message: `Namespace "${namespace}" conflict: ${previous} and ${configPath}. Each template must have a unique namespace.`,
105
+ file: configPath,
106
+ });
107
+ }
108
+ else {
109
+ namespaces.set(namespace, configPath);
110
+ }
111
+ }
112
+ catch {
113
+ // Parse/validation errors are reported by the load step.
114
+ }
115
+ }
116
+ return diagnostics;
117
+ }
118
+ export async function assertNoNamespaceCollisions(configPaths) {
119
+ const diagnostics = await checkNamespaceCollisions(configPaths);
120
+ if (diagnostics.length > 0) {
121
+ throw new DNADiagnosticsError(diagnostics[0].message, diagnostics);
122
+ }
123
+ }
124
+ export async function loadDNAWithDiagnostics(filePath) {
125
+ const diagnostics = [];
126
+ try {
127
+ const dna = await parseDNAFile(filePath);
128
+ const result = validateDNA(dna);
129
+ for (const warning of result.warnings ?? []) {
130
+ diagnostics.push({
131
+ severity: "warning",
132
+ code: "schema_warning",
133
+ message: warning.message,
134
+ file: filePath,
135
+ path: warning.path,
136
+ });
137
+ }
138
+ for (const error of result.errors) {
139
+ diagnostics.push({
140
+ severity: "error",
141
+ code: "schema_error",
142
+ message: error.message,
143
+ file: filePath,
144
+ path: error.path,
145
+ });
146
+ }
147
+ return { dna: result.valid ? dna : undefined, diagnostics };
148
+ }
149
+ catch (error) {
150
+ diagnostics.push({
151
+ severity: "error",
152
+ code: "parse_error",
153
+ message: error instanceof Error ? error.message : String(error),
154
+ file: filePath,
155
+ });
156
+ return { diagnostics };
157
+ }
158
+ }
159
+ export async function resolveDNAInputs(files, options) {
160
+ const cwd = options?.cwd ?? process.cwd();
161
+ const inputFiles = files.length > 0 ? files : await detectDNAConfigs(cwd);
162
+ const expandedFiles = await expandDNAInputFiles(inputFiles, { cwd });
163
+ const diagnostics = await checkNamespaceCollisions(expandedFiles);
164
+ const dnas = [];
165
+ for (const file of expandedFiles) {
166
+ const result = await loadDNAWithDiagnostics(file);
167
+ diagnostics.push(...result.diagnostics);
168
+ if (result.dna)
169
+ dnas.push(result.dna);
170
+ }
171
+ if (hasDiagnosticErrors(diagnostics)) {
172
+ throw new DNADiagnosticsError("DNA input resolution failed", diagnostics);
173
+ }
174
+ return { files: expandedFiles, dnas, diagnostics };
175
+ }
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * Fail-open: all errors → { continue: true, suppressOutput: true }
16
16
  */
17
- import type { ArtifactFact, ConstraintIR, VerifierSpec } from "../schema/types.js";
17
+ import type { ArtifactFact, ConstraintIR, RoleDef, VerifierSpec } from "../schema/types.js";
18
18
  import type { HookEvent, HookOutput } from "./protocol.js";
19
19
  import { blockOutput } from "./protocol.js";
20
20
  import { readWorkflowState } from "./state.js";
@@ -39,6 +39,15 @@ export declare function computeSummary(traces: Array<{
39
39
  reason?: string;
40
40
  }>): SessionSummary;
41
41
  export declare function formatSummary(s: SessionSummary): string | null;
42
+ export interface RunHookEventOptions {
43
+ event: HookEvent;
44
+ ir: ConstraintIR;
45
+ rawInput: Record<string, unknown>;
46
+ projectDir: string;
47
+ sessionId?: string;
48
+ roles?: Record<string, RoleDef>;
49
+ }
50
+ export declare function runHookEvent(options: RunHookEventOptions): Promise<HookOutput>;
42
51
  declare function recordProducedArtifacts(projectDir: string, ir: ConstraintIR, wfState: {
43
52
  workflow: string;
44
53
  current_step: string;
package/dist/hooks/cli.js CHANGED
@@ -15,9 +15,11 @@
15
15
  * Fail-open: all errors → { continue: true, suppressOutput: true }
16
16
  */
17
17
  import { mkdir, readFile, realpath, stat, unlink, writeFile } from "node:fs/promises";
18
+ import { realpathSync } from "node:fs";
18
19
  import { spawn } from "node:child_process";
19
20
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
20
21
  import { randomUUID } from "node:crypto";
22
+ import { fileURLToPath } from "node:url";
21
23
  import { readStdin, writeOutput, silentOutput, allowOutput, blockOutput, stopOutput } from "./protocol.js";
22
24
  import { validateHookInput } from "./schema.js";
23
25
  import { enforcePreToolUse, enforcePostToolUse, enforceUserPromptSubmit, enforceSubagentStop, enforcePreCompact, enforceNotification, enforceSessionStart, enforceStop, extractBashWritePaths, checkReflectionLimit, checkContextReadiness, checkWorkflowBoundary, } from "./enforce.js";
@@ -101,14 +103,18 @@ async function main() {
101
103
  writeOutput(silentOutput());
102
104
  return;
103
105
  }
106
+ const output = await runHookEvent({ event, ir, rawInput, projectDir, sessionId });
107
+ writeOutput(output);
108
+ }
109
+ export async function runHookEvent(options) {
110
+ const { event, ir, rawInput, projectDir, sessionId, roles } = options;
104
111
  const state = {};
105
112
  let wfStateRaw = null;
106
113
  // Load workflow state for events that need it (handoff context + PreCompact preservation)
107
114
  if (event === "PreToolUse" || event === "PostToolUse" || event === "PreCompact") {
108
115
  const workflowState = await readWorkflowStateForHook(projectDir, event, sessionId);
109
116
  if ("output" in workflowState) {
110
- writeOutput(workflowState.output);
111
- return;
117
+ return workflowState.output;
112
118
  }
113
119
  wfStateRaw = workflowState.state;
114
120
  if (wfStateRaw && wfStateRaw.active) {
@@ -121,9 +127,8 @@ async function main() {
121
127
  };
122
128
  const artifactFacts = await resolveWorkflowArtifactFactsForHook(projectDir, ir, wfStateRaw, event, sessionId);
123
129
  if ("output" in artifactFacts) {
124
- writeOutput(artifactFacts.output);
125
130
  appendArtifactResolverTrace(projectDir, event, wfStateRaw, artifactFacts.output, sessionId);
126
- return;
131
+ return artifactFacts.output;
127
132
  }
128
133
  state.artifactFacts = artifactFacts.facts;
129
134
  }
@@ -139,7 +144,6 @@ async function main() {
139
144
  if (event === "PreToolUse") {
140
145
  const gateResult = await handlePreToolGates(ir, rawInput, wfStateRaw, projectDir, sessionId);
141
146
  if (gateResult) {
142
- writeOutput(gateResult.output);
143
147
  appendTrace(projectDir, {
144
148
  trace_id: randomUUID(),
145
149
  event,
@@ -152,24 +156,22 @@ async function main() {
152
156
  duration_ms: 0,
153
157
  timestamp: new Date().toISOString(),
154
158
  }, sessionId).catch(() => { });
155
- return;
159
+ return gateResult.output;
156
160
  }
157
161
  }
158
162
  // Special handling for Stop — needs async workflow state read + session summary
159
163
  if (event === "Stop") {
160
164
  const workflowState = await readWorkflowStateForHook(projectDir, event, sessionId);
161
165
  if ("output" in workflowState) {
162
- writeOutput(workflowState.output);
163
- return;
166
+ return workflowState.output;
164
167
  }
165
168
  const wfState = workflowState.state;
166
169
  let stopArtifactFacts = [];
167
170
  if (wfState?.active) {
168
171
  const artifactFacts = await finalizeAndResolveArtifactsForHook(projectDir, ir, wfState, event, sessionId);
169
172
  if ("output" in artifactFacts) {
170
- writeOutput(artifactFacts.output);
171
173
  appendArtifactResolverTrace(projectDir, event, wfState, artifactFacts.output, sessionId);
172
- return;
174
+ return artifactFacts.output;
173
175
  }
174
176
  stopArtifactFacts = artifactFacts.facts;
175
177
  }
@@ -217,7 +219,6 @@ async function main() {
217
219
  }
218
220
  }
219
221
  catch { /* fail-open */ }
220
- writeOutput(stopOutput);
221
222
  // Trace for Stop
222
223
  appendTrace(projectDir, {
223
224
  trace_id: randomUUID(),
@@ -228,11 +229,11 @@ async function main() {
228
229
  duration_ms: 0,
229
230
  timestamp: new Date().toISOString(),
230
231
  }, sessionId).catch(() => { });
231
- return;
232
+ return stopOutput;
232
233
  }
233
234
  // Dispatch to enforcement engine with timing
234
235
  const start = Date.now();
235
- let result = dispatch(event, ir, rawInput, state);
236
+ let result = dispatch(event, ir, rawInput, state, roles);
236
237
  let output = result?.output ?? silentOutput();
237
238
  const durationMs = Date.now() - start;
238
239
  // PostToolUse side effects: session read tracking + surgeon reflection gate.
@@ -304,7 +305,6 @@ async function main() {
304
305
  }
305
306
  catch { /* fail-open: pattern detection never blocks */ }
306
307
  }
307
- writeOutput(output);
308
308
  // Trace logging (async, fail-open)
309
309
  const decision = output.continue === false ? "block"
310
310
  : output.hookSpecificOutput?.additionalContext?.startsWith("WARN") ? "warn"
@@ -339,9 +339,10 @@ async function main() {
339
339
  session_id: sessionId,
340
340
  }).catch(() => { }); // Fail-open
341
341
  }
342
+ return output;
342
343
  }
343
344
  // ── Dispatch ───────────────────────────────────────────────
344
- function dispatch(event, ir, input, state) {
345
+ function dispatch(event, ir, input, state, roles) {
345
346
  switch (event) {
346
347
  case "PreToolUse":
347
348
  return enforcePreToolUse(ir, {
@@ -351,7 +352,7 @@ function dispatch(event, ir, input, state) {
351
352
  cwd: typeof input.cwd === "string" ? input.cwd : undefined,
352
353
  sessionId: typeof input.session_id === "string" ? input.session_id
353
354
  : typeof input.sessionId === "string" ? input.sessionId : undefined,
354
- }, state);
355
+ }, state, roles);
355
356
  case "PostToolUse":
356
357
  return enforcePostToolUse(ir, {
357
358
  tool_name: String(input.tool_name ?? ""),
@@ -574,7 +575,7 @@ async function finalizeAndResolveArtifacts(projectDir, ir, wfState, sessionId) {
574
575
  }
575
576
  function artifactResolverErrorOutput(event, error) {
576
577
  const detail = error instanceof Error ? error.message : String(error);
577
- return blockOutput(`[Intent DNA] ${event} artifact resolver failed: ${detail}`);
578
+ return blockOutput(`[Intent DNA] ${event} artifact resolver failed: ${detail}. Fix the workflow/session identifiers in DNA state, then rerun the hook or \`dna sync\`.`);
578
579
  }
579
580
  async function readWorkflowStateForHook(projectDir, event, sessionId) {
580
581
  try {
@@ -689,7 +690,8 @@ export function appendStopVerifierWarnings(output, verifierResults, currentStep)
689
690
  if (warningVerifierFailures.length === 0)
690
691
  return output;
691
692
  const warningText = `[Intent DNA] Verifier warnings at step '${currentStep ?? "unknown"}':\n` +
692
- warningVerifierFailures.map((result) => ` - ${result.message ?? result.verifier_id}`).join("\n");
693
+ warningVerifierFailures.map((result) => ` - ${result.message ?? result.verifier_id}`).join("\n") +
694
+ "\nFix the warning above before relying on this step, or rerun the verifier after repair.";
693
695
  return appendOutputText(output, warningText, "Stop");
694
696
  }
695
697
  function trimEvidence(raw) {
@@ -1521,7 +1523,20 @@ function toKebabCase(s) {
1521
1523
  .toLowerCase();
1522
1524
  }
1523
1525
  // ── Entry Point ────────────────────────────────────────────
1524
- main().catch(() => {
1525
- // Fail-open: never block Claude Code on unexpected errors
1526
- writeOutput(silentOutput());
1527
- });
1526
+ function isDirectEntryPoint() {
1527
+ const entry = process.argv[1];
1528
+ if (!entry)
1529
+ return false;
1530
+ try {
1531
+ return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));
1532
+ }
1533
+ catch {
1534
+ return resolve(entry) === resolve(fileURLToPath(import.meta.url));
1535
+ }
1536
+ }
1537
+ if (isDirectEntryPoint()) {
1538
+ main().catch(() => {
1539
+ // Fail-open: never block Claude Code on unexpected errors
1540
+ writeOutput(silentOutput());
1541
+ });
1542
+ }
@@ -136,6 +136,7 @@ export declare function writeSessionReads(projectDir: string, state: SessionRead
136
136
  */
137
137
  export declare function appendSessionRead(projectDir: string, filePath: string, sessionId?: string): Promise<void>;
138
138
  export interface VerifierResultEntry {
139
+ result_id?: string;
139
140
  verifier_id: string;
140
141
  when: VerifierWhen;
141
142
  severity: VerifierSeverity;
@@ -151,6 +152,7 @@ export interface VerifierResultEntry {
151
152
  message?: string;
152
153
  timestamp: string;
153
154
  }
155
+ export declare function verifierResultId(result: Omit<VerifierResultEntry, "result_id"> | VerifierResultEntry): string;
154
156
  export declare function readVerifierResults(projectDir: string, sessionId?: string): Promise<VerifierResultEntry[]>;
155
157
  export declare function writeVerifierResults(projectDir: string, results: VerifierResultEntry[], sessionId?: string): Promise<void>;
156
158
  export declare function appendVerifierResult(projectDir: string, result: VerifierResultEntry, sessionId?: string): Promise<void>;
@@ -330,13 +330,34 @@ export async function appendSessionRead(projectDir, filePath, sessionId) {
330
330
  }
331
331
  }
332
332
  const VERIFIER_RESULTS_FILE = "workflow/verifier-results.json";
333
+ export function verifierResultId(result) {
334
+ return "vr_" + createHash("sha256")
335
+ .update(JSON.stringify({
336
+ verifier_id: result.verifier_id,
337
+ when: result.when,
338
+ severity: result.severity,
339
+ kind: result.kind,
340
+ workflow: result.workflow ?? "",
341
+ step_id: result.step_id ?? "",
342
+ status: result.status,
343
+ target: result.target ?? "",
344
+ evidence: result.evidence ?? "",
345
+ artifact: result.artifact ?? "",
346
+ timestamp: result.timestamp,
347
+ }))
348
+ .digest("hex")
349
+ .slice(0, 16);
350
+ }
351
+ function normalizeVerifierResult(result) {
352
+ return result.result_id ? result : { ...result, result_id: verifierResultId(result) };
353
+ }
333
354
  async function readVerifierResultsFile(projectDir, sessionId) {
334
355
  const stateDir = resolveStateDir(projectDir, sessionId);
335
356
  const filePath = join(stateDir, VERIFIER_RESULTS_FILE);
336
357
  try {
337
358
  const raw = await readFile(filePath, "utf-8");
338
359
  const parsed = JSON.parse(raw);
339
- return Array.isArray(parsed) ? parsed : [];
360
+ return Array.isArray(parsed) ? parsed.map(normalizeVerifierResult) : [];
340
361
  }
341
362
  catch {
342
363
  return [];
@@ -381,7 +402,7 @@ export async function writeVerifierResults(projectDir, results, sessionId) {
381
402
  }
382
403
  export async function appendVerifierResult(projectDir, result, sessionId) {
383
404
  const results = await readVerifierResultsFile(projectDir, sessionId);
384
- results.push(result);
405
+ results.push(normalizeVerifierResult(result));
385
406
  await writeVerifierResults(projectDir, results, sessionId);
386
407
  }
387
408
  const TRACE_DIR = "trace";
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
  ];