intentdna 1.7.0 → 1.7.5

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 (48) 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/feedback.d.ts +2 -2
  5. package/dist/cli/commands/feedback.js +5 -5
  6. package/dist/cli/commands/sync.js +8 -5
  7. package/dist/cli/commands/verify.d.ts +32 -0
  8. package/dist/cli/commands/verify.js +173 -6
  9. package/dist/cli/index.js +21 -9
  10. package/dist/compiler/activate.js +11 -6
  11. package/dist/compiler/cascade.d.ts +3 -1
  12. package/dist/compiler/cascade.js +23 -1
  13. package/dist/compiler/compile.js +1 -0
  14. package/dist/compiler/index.d.ts +5 -1
  15. package/dist/compiler/index.js +7 -4
  16. package/dist/compiler/input-resolver.d.ts +8 -1
  17. package/dist/compiler/input-resolver.js +128 -22
  18. package/dist/compiler/provenance.d.ts +8 -0
  19. package/dist/compiler/provenance.js +127 -0
  20. package/dist/governance/index.d.ts +4 -3
  21. package/dist/governance/index.js +3 -4
  22. package/dist/governance/runtime-decision-event.d.ts +85 -0
  23. package/dist/governance/runtime-decision-event.js +231 -0
  24. package/dist/governance/types.d.ts +3 -3
  25. package/dist/governance/types.js +3 -3
  26. package/dist/hooks/cli.d.ts +1 -0
  27. package/dist/hooks/cli.js +181 -20
  28. package/dist/hooks/state.d.ts +3 -10
  29. package/dist/hooks/state.js +147 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +1 -0
  32. package/dist/mcp/tools-observability.js +3 -3
  33. package/dist/mcp/tools-state.d.ts +1 -1
  34. package/dist/mcp/tools-state.js +17 -7
  35. package/dist/report/kernel-report.d.ts +27 -0
  36. package/dist/report/kernel-report.js +60 -19
  37. package/dist/report/kernel-signals.d.ts +5 -2
  38. package/dist/report/kernel-signals.js +84 -2
  39. package/dist/report/report-package.d.ts +9 -1
  40. package/dist/report/report-package.js +32 -27
  41. package/dist/runtime/claude-sdk.d.ts +9 -4
  42. package/dist/runtime/claude-sdk.js +9 -0
  43. package/dist/runtime/plugin-adapter.d.ts +5 -1
  44. package/dist/runtime/plugin-adapter.js +2 -0
  45. package/dist/schema/types.d.ts +51 -0
  46. package/package.json +1 -1
  47. package/spec/README.md +1 -1
  48. package/spec/schema-spec.md +78 -1
@@ -9,7 +9,7 @@
9
9
  {
10
10
  "name": "intentdna",
11
11
  "description": "Declarative policy layer for AI agent behavior with plugin-managed hook runtime for Claude Code.",
12
- "version": "1.7.0",
12
+ "version": "1.7.5",
13
13
  "author": {
14
14
  "name": "Samuel"
15
15
  },
@@ -25,5 +25,5 @@
25
25
  ]
26
26
  }
27
27
  ],
28
- "version": "1.7.0"
28
+ "version": "1.7.5"
29
29
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intentdna",
3
- "version": "1.7.0",
3
+ "version": "1.7.5",
4
4
  "description": "Declarative policy layer for AI agent behavior",
5
5
  "author": {
6
6
  "name": "Samuel"
package/README.md CHANGED
@@ -1,26 +1,32 @@
1
1
  # Intent DNA
2
2
 
3
- **Declarative control plane for AI agent behavior.**
3
+ **Enterprise safety control plane for AI agent behavior.**
4
4
 
5
- Intent DNA turns intent, policy, and accumulated experience into enforceable agent constraints. Instead of relying on prompts alone, it compiles DNA into the native control surfaces of Claude Code and related harnesses. It governs how agents operate across existing tools and systems; it does not replace your business systems or act as a domain-specific automation runtime.
5
+ Intent DNA lets an organization declare safety policy once, cascade it through Species Enterprise Project Personal Role → Context → Task, and compile it into deterministic gates for every agent, project, and harness. It governs how agents operate across existing tools and systems; it does not replace your business systems or act as a domain-specific automation runtime.
6
6
 
7
7
  ---
8
8
 
9
9
  ## What It Is
10
10
 
11
- Intent DNA is a compiler and runtime toolkit for managing agent behavior across four layers:
11
+ Intent DNA is an enforcement-first control plane for organization-wide agent safety:
12
12
 
13
- - **Behavioral guidance** via generated prompt/context files
14
- - **Deterministic enforcement** via hooks and runtime checks
15
- - **Artifact handoff** via session-scoped manifests and resolver facts
16
- - **Feedback loops** via trace, audit, and epigenetic evolution
13
+ - **Enterprise / Species policy bundles** define shared red lines across projects and teams
14
+ - **Cascade governance** preserves provenance and prevents lower layers from relaxing organization thresholds
15
+ - **Constraint IR** carries policy provenance into hook, SDK-check, and CI strict gates
16
+ - **RuntimeDecisionEvent evidence** records who/what/why/from which policy for every allow/warn/block/escalate/validate decision
17
+ - **MCP, reports, state, trace, audit, and evolution** query or package evidence; they are not the policy source of truth
17
18
 
18
19
  It governs the agents operating on your systems rather than re-implementing the systems themselves.
19
20
 
20
21
  The core model is:
21
22
 
22
23
  ```text
23
- DNA config compiler pipeline → Constraint IR → harness-specific outputs
24
+ Enterprise / Species Policy Bundle
25
+ → Species→Enterprise→Project→Personal→Role→Context→Task Cascade
26
+ → Constraint IR + Provenance Manifest
27
+ → Hook / SDK-check / CI strict gate
28
+ → RuntimeDecisionEvent
29
+ → MCP / report / dashboard / evolution evidence layer
24
30
  ```
25
31
 
26
32
  For the architectural model and long-term design, see [DESIGN.md](DESIGN.md).
@@ -30,9 +36,10 @@ For the schema contract, see [spec/schema-spec.md](spec/schema-spec.md).
30
36
  ## Design Principles
31
37
 
32
38
  - Govern agents rather than re-implementing business systems.
33
- - Declare intent and boundaries first, then compile them into native control surfaces.
34
- - Combine guidance, enforcement, validation, and feedback into one governance loop.
35
- - Treat runtime state, validators, and signals as first-class governance assets.
39
+ - Declare enterprise safety red lines first, then compile them into native control surfaces.
40
+ - Treat Species and Enterprise thresholds as non-relaxable by Project, Personal, Role, Context, or Task layers.
41
+ - Keep enforcement points authoritative: hooks, SDK checks, and CI gates make decisions from compiled IR and provenance.
42
+ - Treat runtime state, validators, traces, reports, MCP queries, and signals as evidence assets, not policy truth.
36
43
  - Keep adapters and signal ingestion pluggable so the control plane outlives any single tool.
37
44
 
38
45
  ---
@@ -70,10 +77,10 @@ dna init --upgrade <template-name>
70
77
 
71
78
  ## How It Works
72
79
 
73
- Intent DNA compiles to your tool's native control surfaces:
80
+ Intent DNA compiles organization policy to your tool's native control surfaces:
74
81
 
75
82
  ```text
76
- DNA → Constraint IR → CLAUDE.md / settings / agents / skills / runtime state
83
+ DNA → Cascade provenance → Constraint IR → hooks / SDK checks / CI gates evidence
77
84
  ```
78
85
 
79
86
  Typical outputs include:
@@ -82,12 +89,14 @@ Typical outputs include:
82
89
  |--------|---------|
83
90
  | `CLAUDE.md` | Behavioral guidance |
84
91
  | `.dna/compiled/ir.json` | Runtime-readable compiled constraints |
92
+ | provenance manifest | Policy bundle, cascade layers, winners, and compiled IR hash |
93
+ | RuntimeDecisionEvent | Enterprise evidence for allow/warn/block/escalate/validate decisions |
85
94
  | `.dna/state/sessions/*/handoffs/*.json` | Local artifact manifests for workflow handoff facts |
86
95
  | `.claude/agents/*.md` | Role definitions |
87
96
  | `.claude/skills/*/SKILL.md` | Workflow entrypoints |
88
97
  | `.claude/settings.json` or plugin runtime | Hook wiring |
89
98
 
90
- **Key idea:** prompts can drift, but runtime constraints, resolver-backed handoffs, and feedback loops make behavior governable.
99
+ **Key idea:** prompts can drift, but non-relaxable enterprise thresholds, compiled gates, and provenance-backed runtime evidence make behavior governable.
91
100
 
92
101
  ---
93
102
 
@@ -95,11 +104,13 @@ Typical outputs include:
95
104
 
96
105
  Intent DNA currently focuses on:
97
106
 
107
+ - Enterprise Safety Slice: shared policy bundles, cascade provenance, and non-relaxable organization thresholds
98
108
  - Claude Code / Claude-style harnesses
99
109
  - Role-based agent workflows
100
110
  - Template-driven project bootstrap and upgrades
101
111
  - Deterministic guardrails and stateful enforcement
102
112
  - Resolver-backed artifact handoff between workflow steps
113
+ - Evidence-layer MCP/report/state/trace/audit surfaces
103
114
  - Dogfood-driven template iteration
104
115
 
105
116
  Adapter and ecosystem direction are tracked in [ROADMAP.md](ROADMAP.md), not duplicated here.
@@ -157,7 +168,7 @@ workflows:
157
168
  description: Review the implementation
158
169
  ```
159
170
 
160
- **Cascade:** DNA files inherit and override by priority. `threshold` codons (safety rules) can never be relaxed even by higher priority DNA.
171
+ **Cascade:** DNA files inherit and override by priority. `threshold` codons (safety rules) from Species or Enterprise are red lines: Project, Personal, Role, Context, and Task layers may add stricter rules but cannot relax them.
161
172
 
162
173
  **Genes:** Behavioral traits (attract/repel/threshold/weight/sense).
163
174
 
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * dna feedback [--days <N>] [--json] [--evolve]
3
3
  *
4
- * Build a local kernel observability report from traces and verifier results.
5
- * With --evolve: include a preview-only marker diff derived from local signals.
4
+ * Build a local kernel observability report from RuntimeDecisionEvent evidence.
5
+ * Legacy traces and verifier results are included as diagnostic context only.
6
6
  */
7
7
  export interface FeedbackOptions {
8
8
  days: number;
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * dna feedback [--days <N>] [--json] [--evolve]
3
3
  *
4
- * Build a local kernel observability report from traces and verifier results.
5
- * With --evolve: include a preview-only marker diff derived from local signals.
4
+ * Build a local kernel observability report from RuntimeDecisionEvent evidence.
5
+ * Legacy traces and verifier results are included as diagnostic context only.
6
6
  */
7
7
  import { buildKernelReport, formatKernelReport } from "../../report/kernel-report.js";
8
8
  import { buildReportPackage } from "../../report/report-package.js";
@@ -23,9 +23,9 @@ export async function runFeedback(opts) {
23
23
  includeMarkerPreview: opts.evolve,
24
24
  dnaId: opts.dnaId,
25
25
  });
26
- if (report.summary.traces === 0 && report.summary.verifier_results === 0) {
27
- process.stderr.write(`No trace or verifier data found for the last ${opts.days} day(s).\n`);
28
- process.stderr.write("Signals are generated by dna-hook during Claude Code sessions.\n");
26
+ if (report.summary.runtime_decisions === 0 && report.summary.legacy_diagnostics === 0) {
27
+ process.stderr.write(`No RuntimeDecisionEvent evidence or legacy diagnostics found for the last ${opts.days} day(s).\n`);
28
+ process.stderr.write("RuntimeDecisionEvent evidence is generated by dna-hook during Claude Code sessions.\n");
29
29
  return 0;
30
30
  }
31
31
  if (opts.json) {
@@ -14,7 +14,7 @@ import { resolve, dirname } from "node:path";
14
14
  import { readFile, writeFile as writeFileAsync, stat, mkdir, readdir, unlink, rm } from "node:fs/promises";
15
15
  import { createHash } from "node:crypto";
16
16
  import { createInterface } from "node:readline";
17
- import { assertNoNamespaceCollisions, compileFromFiles, detectDNAConfigs, expandDNAInputFiles, loadDNA, } from "../../compiler/index.js";
17
+ import { assertNoNamespaceCollisions, compileFromFiles, detectDNAConfigs, expandDNAInputs, loadDNA, } from "../../compiler/index.js";
18
18
  import { parseYAML } from "../../schema/yaml-parser.js";
19
19
  import { findTemplateCatalogEntry } from "../../templates/catalog.js";
20
20
  import { compileToMarkdown, injectIntoFile, removeFromFile } from "../../runtime/markdown.js";
@@ -318,7 +318,7 @@ function lockTrackedOutputPaths(trackedOutputs) {
318
318
  .filter((output) => getSyncOutputArtifactMetadata(output.artifact)?.trackedByLock)
319
319
  .map((output) => output.path);
320
320
  }
321
- async function generateLockFile(lockPath, dnaSources, outputFiles) {
321
+ async function generateLockFile(lockPath, dnaSources, outputFiles, provenance, compiledIrHash) {
322
322
  const outputs = {};
323
323
  for (const f of outputFiles) {
324
324
  outputs[f] = { sha256: await computeFileHash(f) };
@@ -327,6 +327,8 @@ async function generateLockFile(lockPath, dnaSources, outputFiles) {
327
327
  synced_at: new Date().toISOString(),
328
328
  dna_sources: dnaSources,
329
329
  outputs,
330
+ compiled_ir_hash: compiledIrHash,
331
+ provenance,
330
332
  };
331
333
  await mkdir(dirname(lockPath), { recursive: true });
332
334
  await writeFileAsync(lockPath, JSON.stringify(lock, null, 2) + "\n", "utf-8");
@@ -441,7 +443,8 @@ export async function runSync(opts) {
441
443
  // Step 0: Expand and pre-validate all DNA files before any work
442
444
  // loadDNA() calls validateDNA() internally — validation failures throw here,
443
445
  // before evolve/compile/inject waste any work.
444
- const expandedFiles = await expandDNAInputFiles(opts.files);
446
+ const expandedInputs = await expandDNAInputs(opts.files);
447
+ const expandedFiles = expandedInputs.files;
445
448
  await checkTemplateVersions(expandedFiles);
446
449
  const loadedDNAs = await Promise.all(expandedFiles.map(loadDNA));
447
450
  if (opts.dryRun) {
@@ -464,7 +467,7 @@ export async function runSync(opts) {
464
467
  }
465
468
  }
466
469
  // Step 2: Compile
467
- const ir = await compileFromFiles(expandedFiles, {
470
+ const ir = await compileFromFiles(opts.files, {
468
471
  context: opts.context,
469
472
  role: opts.role,
470
473
  epigenetic_markers: epigeneticMarkers,
@@ -721,7 +724,7 @@ export async function runSync(opts) {
721
724
  process.stderr.write(`Dry-run: would write lock file: ${lockPath}\n`);
722
725
  }
723
726
  else {
724
- await generateLockFile(lockPath, expandedFiles, lockTrackedOutputPaths(trackedOutputs));
727
+ await generateLockFile(lockPath, opts.files, lockTrackedOutputPaths(trackedOutputs), ir.provenance, ir.compiled_ir_hash);
725
728
  process.stderr.write(`Lock file: ${lockPath}\n`);
726
729
  }
727
730
  trackSyncOutput(trackedOutputs, "lock-file", lockPath);
@@ -9,10 +9,13 @@
9
9
  * 1 — drift detected (modified or missing files)
10
10
  * 2 — usage error (e.g. lock file not found)
11
11
  */
12
+ import type { CompiledProvenanceManifest } from "../../schema/types.js";
12
13
  export interface VerifyOptions {
13
14
  lockFile: string;
14
15
  stats?: boolean;
15
16
  health?: boolean;
17
+ strict?: boolean;
18
+ projectDir?: string;
16
19
  }
17
20
  export interface LockFileEntry {
18
21
  sha256: string;
@@ -21,6 +24,8 @@ export interface LockFile {
21
24
  synced_at: string;
22
25
  dna_sources: string[];
23
26
  outputs: Record<string, LockFileEntry>;
27
+ compiled_ir_hash?: string;
28
+ provenance?: CompiledProvenanceManifest;
24
29
  }
25
30
  export interface VerifyResult {
26
31
  file: string;
@@ -28,9 +33,36 @@ export interface VerifyResult {
28
33
  expected?: string;
29
34
  actual?: string;
30
35
  }
36
+ export interface ProvenanceVerifyResult {
37
+ source_id: string;
38
+ file: string;
39
+ status: "match" | "mismatch" | "missing";
40
+ expected?: string;
41
+ actual?: string;
42
+ }
43
+ export interface InheritanceGraphVerifyResult {
44
+ source_id: string;
45
+ file: string;
46
+ status: "match" | "mismatch" | "missing" | "extra";
47
+ expected?: string;
48
+ actual?: string;
49
+ }
31
50
  export declare function sha256(content: string): string;
32
51
  export declare function parseLockFile(raw: string): LockFile;
33
52
  export declare function verifyLock(lock: LockFile): Promise<VerifyResult[]>;
53
+ export declare function verifyInheritanceGraph(lock: LockFile): Promise<InheritanceGraphVerifyResult[]>;
54
+ export declare function verifyProvenance(provenance?: CompiledProvenanceManifest): Promise<ProvenanceVerifyResult[]>;
55
+ export interface StrictRuntimeEvidenceExpectation {
56
+ compiledIrHash?: string;
57
+ policyBundleId?: string;
58
+ policyBundleVersion?: string;
59
+ }
60
+ export declare function verifyStrictRuntimeEvidence(projectDir: string, expected?: StrictRuntimeEvidenceExpectation): Promise<{
61
+ ok: boolean;
62
+ total: number;
63
+ enterprise: number;
64
+ errors: string[];
65
+ }>;
34
66
  export declare function runVerify(opts: VerifyOptions): Promise<number>;
35
67
  export interface HealthCheckResult {
36
68
  name: string;
@@ -11,7 +11,11 @@
11
11
  */
12
12
  import { readFile } from "node:fs/promises";
13
13
  import { createHash } from "node:crypto";
14
- import { readTraces } from "../../hooks/state.js";
14
+ import { dirname, resolve } from "node:path";
15
+ import { readRuntimeDecisionEvents, readTraces } from "../../hooks/state.js";
16
+ import { expandDNAInputs, formatDiagnostics } from "../../compiler/index.js";
17
+ import { DNADiagnosticsError } from "../../compiler/diagnostics.js";
18
+ import { validateRuntimeDecisionEvent } from "../../governance/index.js";
15
19
  // ── Helpers ────────────────────────────────────────────────
16
20
  export function sha256(content) {
17
21
  return createHash("sha256").update(content, "utf-8").digest("hex");
@@ -52,6 +56,108 @@ export async function verifyLock(lock) {
52
56
  }
53
57
  return results;
54
58
  }
59
+ function enterpriseDirsFromLock(lock) {
60
+ const dirs = new Set();
61
+ for (const source of lock.provenance?.sources ?? []) {
62
+ if (source.source_ref.startsWith("enterprise:"))
63
+ dirs.add(dirname(source.resolved_path));
64
+ }
65
+ return [...dirs];
66
+ }
67
+ function sourceGraphKey(source) {
68
+ return JSON.stringify([source.source_id, source.source_ref, source.inherited_by ?? ""]);
69
+ }
70
+ function comparableSource(source) {
71
+ return JSON.stringify({
72
+ source_ref: source.source_ref,
73
+ resolved_path: source.resolved_path,
74
+ fingerprint: source.fingerprint,
75
+ inherited_by: source.inherited_by,
76
+ });
77
+ }
78
+ export async function verifyInheritanceGraph(lock) {
79
+ if (!lock.provenance)
80
+ return [];
81
+ const expanded = await expandDNAInputs(lock.dna_sources, { enterprisePolicyDirs: enterpriseDirsFromLock(lock) });
82
+ const current = new Map(expanded.provenance.map((source) => [sourceGraphKey(source), source]));
83
+ const locked = new Map(lock.provenance.sources.map((source) => [sourceGraphKey(source), source]));
84
+ const results = [];
85
+ for (const [key, expected] of locked) {
86
+ const actual = current.get(key);
87
+ if (!actual) {
88
+ results.push({ source_id: expected.source_id, file: expected.resolved_path, status: "missing", expected: comparableSource(expected) });
89
+ continue;
90
+ }
91
+ const expectedComparable = comparableSource(expected);
92
+ const actualComparable = comparableSource(actual);
93
+ results.push({
94
+ source_id: expected.source_id,
95
+ file: expected.resolved_path,
96
+ status: expectedComparable === actualComparable ? "match" : "mismatch",
97
+ expected: expectedComparable,
98
+ actual: actualComparable,
99
+ });
100
+ }
101
+ for (const [key, actual] of current) {
102
+ if (!locked.has(key)) {
103
+ results.push({ source_id: actual.source_id, file: actual.resolved_path, status: "extra", actual: comparableSource(actual) });
104
+ }
105
+ }
106
+ return results;
107
+ }
108
+ export async function verifyProvenance(provenance) {
109
+ if (!provenance)
110
+ return [];
111
+ const results = [];
112
+ for (const source of provenance.sources) {
113
+ try {
114
+ const content = await readFile(source.resolved_path, "utf-8");
115
+ const actual = `sha256:${sha256(content)}`;
116
+ results.push({
117
+ source_id: source.source_id,
118
+ file: source.resolved_path,
119
+ status: actual === source.fingerprint ? "match" : "mismatch",
120
+ expected: source.fingerprint,
121
+ actual,
122
+ });
123
+ }
124
+ catch {
125
+ results.push({
126
+ source_id: source.source_id,
127
+ file: source.resolved_path,
128
+ status: "missing",
129
+ expected: source.fingerprint,
130
+ });
131
+ }
132
+ }
133
+ return results;
134
+ }
135
+ export async function verifyStrictRuntimeEvidence(projectDir, expected = {}) {
136
+ const events = await readRuntimeDecisionEvents(projectDir, 7);
137
+ const errors = [];
138
+ let enterprise = 0;
139
+ for (const event of events) {
140
+ const result = validateRuntimeDecisionEvent(event);
141
+ if (!result.valid)
142
+ errors.push(...result.errors.map((error) => `${event.event_id ?? "unknown"}: ${error}`));
143
+ if (expected.compiledIrHash && event.compiled_ir_hash !== expected.compiledIrHash) {
144
+ errors.push(`${event.event_id ?? "unknown"}: compiled_ir_hash does not match current lock`);
145
+ }
146
+ if (expected.policyBundleId && event.policy_bundle_id !== expected.policyBundleId) {
147
+ errors.push(`${event.event_id ?? "unknown"}: policy_bundle_id does not match current lock`);
148
+ }
149
+ if (expected.policyBundleVersion && event.policy_bundle_version !== expected.policyBundleVersion) {
150
+ errors.push(`${event.event_id ?? "unknown"}: policy_bundle_version does not match current lock`);
151
+ }
152
+ if (result.classification === "enterprise_evidence")
153
+ enterprise++;
154
+ else
155
+ errors.push(`${event.event_id ?? "unknown"}: RuntimeDecisionEvent is diagnostic-only, not enterprise evidence`);
156
+ }
157
+ if (events.length === 0)
158
+ errors.push("no RuntimeDecisionEvent evidence found");
159
+ return { ok: errors.length === 0, total: events.length, enterprise, errors };
160
+ }
55
161
  // ── CLI entry ──────────────────────────────────────────────
56
162
  export async function runVerify(opts) {
57
163
  // Handle --health mode
@@ -79,8 +185,24 @@ export async function runVerify(opts) {
79
185
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
80
186
  return 2;
81
187
  }
188
+ let inheritanceResults = [];
189
+ if (lock.provenance) {
190
+ try {
191
+ inheritanceResults = await verifyInheritanceGraph(lock);
192
+ }
193
+ catch (err) {
194
+ if (err instanceof DNADiagnosticsError) {
195
+ process.stderr.write(`Error: DNA inheritance graph is invalid:\n${formatDiagnostics(err.diagnostics)}\n`);
196
+ }
197
+ else {
198
+ process.stderr.write(`Error: DNA inheritance graph is invalid: ${err instanceof Error ? err.message : String(err)}\n`);
199
+ }
200
+ return 1;
201
+ }
202
+ }
82
203
  // Verify
83
204
  const results = await verifyLock(lock);
205
+ const provenanceResults = await verifyProvenance(lock.provenance);
84
206
  // Report
85
207
  let hasFailure = false;
86
208
  for (const r of results) {
@@ -96,17 +218,62 @@ export async function runVerify(opts) {
96
218
  hasFailure = true;
97
219
  }
98
220
  }
99
- const total = results.length;
100
- const ok = results.filter((r) => r.status === "match").length;
221
+ for (const r of inheritanceResults) {
222
+ if (r.status === "match") {
223
+ process.stderr.write(` ok inheritance ${r.source_id}: ${r.file}\n`);
224
+ }
225
+ else if (r.status === "mismatch") {
226
+ process.stderr.write(` MISMATCH inheritance ${r.source_id}: ${r.file}\n`);
227
+ hasFailure = true;
228
+ }
229
+ else if (r.status === "missing") {
230
+ process.stderr.write(` MISSING inheritance ${r.source_id}: ${r.file}\n`);
231
+ hasFailure = true;
232
+ }
233
+ else {
234
+ process.stderr.write(` EXTRA inheritance ${r.source_id}: ${r.file}\n`);
235
+ hasFailure = true;
236
+ }
237
+ }
238
+ for (const r of provenanceResults) {
239
+ if (r.status === "match") {
240
+ process.stderr.write(` ok provenance ${r.source_id}: ${r.file}\n`);
241
+ }
242
+ else if (r.status === "mismatch") {
243
+ process.stderr.write(` MISMATCH provenance ${r.source_id}: ${r.file}\n`);
244
+ hasFailure = true;
245
+ }
246
+ else {
247
+ process.stderr.write(` MISSING provenance ${r.source_id}: ${r.file}\n`);
248
+ hasFailure = true;
249
+ }
250
+ }
251
+ if (lock.provenance?.compiled_ir_hash && lock.compiled_ir_hash && lock.provenance.compiled_ir_hash !== lock.compiled_ir_hash) {
252
+ process.stderr.write(" MISMATCH compiled_ir_hash differs between lock and provenance\n");
253
+ hasFailure = true;
254
+ }
255
+ const total = results.length + inheritanceResults.length + provenanceResults.length;
256
+ const ok = results.filter((r) => r.status === "match").length + inheritanceResults.filter((r) => r.status === "match").length + provenanceResults.filter((r) => r.status === "match").length;
101
257
  const failed = total - ok;
102
258
  if (hasFailure) {
103
259
  process.stderr.write(`\nDrift detected: ${failed}/${total} file(s) changed since last sync\n`);
104
260
  return 1;
105
261
  }
106
- else {
107
- process.stderr.write(`\nAll ${total} file(s) verified no drift\n`);
108
- return 0;
262
+ if (opts.strict) {
263
+ const strictProjectDir = opts.projectDir ?? dirname(dirname(resolve(opts.lockFile)));
264
+ const strict = await verifyStrictRuntimeEvidence(strictProjectDir, {
265
+ compiledIrHash: lock.compiled_ir_hash,
266
+ policyBundleId: lock.provenance?.policy_bundle_id,
267
+ policyBundleVersion: lock.provenance?.policy_bundle_version,
268
+ });
269
+ if (!strict.ok) {
270
+ process.stderr.write(`\nStrict runtime evidence failed: ${strict.errors.join("; ")}\n`);
271
+ return 1;
272
+ }
273
+ process.stderr.write(`\nStrict runtime evidence: ${strict.enterprise}/${strict.total} enterprise event(s) valid\n`);
109
274
  }
275
+ process.stderr.write(`\nAll ${total} file(s) verified — no drift\n`);
276
+ return 0;
110
277
  }
111
278
  // ── Stats Mode ────────────────────────────────────────────
112
279
  async function runStats() {
package/dist/cli/index.js CHANGED
@@ -12,7 +12,10 @@
12
12
  * help Show this help message
13
13
  */
14
14
  import { parseArgs } from "node:util";
15
- const HELP = `Intent DNA CLI v1.7.0
15
+ import { getPackageVersion } from "./util/version.js";
16
+ const fallbackVersion = "unknown";
17
+ function helpText(version) {
18
+ return `Intent DNA CLI v${version}
16
19
 
17
20
  Usage: dna <command> [options]
18
21
 
@@ -69,9 +72,10 @@ Examples:
69
72
  dna evolve my_dna_id Preview marker changes without persisting
70
73
  dna evolve my_dna_id --apply Unsupported in this kernel; use sync --evolve for explicit mutation
71
74
  `;
75
+ }
72
76
  const COMMAND_HELP = {
73
77
  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`,
78
+ verify: `Usage: dna verify [--lock <path>] [--stats] [--health] [--strict]\n\nOptions:\n --health Comprehensive installation health check\n --stats Show hook call statistics\n --strict Require valid RuntimeDecisionEvent enterprise evidence\n --lock <path> Lock file path (default .dna/lock)\n`,
75
79
  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
80
  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
81
  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`,
@@ -86,21 +90,27 @@ function commandHelp(command) {
86
90
  async function main() {
87
91
  const args = process.argv.slice(2);
88
92
  const command = args[0];
93
+ const version = await getPackageVersion() ?? fallbackVersion;
94
+ const help = helpText(version);
95
+ if (command === "--version" || command === "-v") {
96
+ process.stdout.write(`${version}\n`);
97
+ process.exit(0);
98
+ }
89
99
  if (!command || command === "--help" || command === "-h") {
90
- process.stderr.write(HELP);
100
+ process.stderr.write(help);
91
101
  process.exit(0);
92
102
  }
93
103
  if (command === "help") {
94
- const help = commandHelp(args[1]);
95
- process.stderr.write(help ?? HELP);
104
+ const commandSpecificHelp = commandHelp(args[1]);
105
+ process.stderr.write(commandSpecificHelp ?? help);
96
106
  process.exit(0);
97
107
  }
98
108
  // Parse remaining args (after command)
99
109
  const rest = args.slice(1);
100
110
  if (rest.includes("--help") || rest.includes("-h")) {
101
- const help = commandHelp(command);
102
- process.stderr.write(help ?? HELP);
103
- process.exit(help ? 0 : 2);
111
+ const commandSpecificHelp = commandHelp(command);
112
+ process.stderr.write(commandSpecificHelp ?? help);
113
+ process.exit(commandSpecificHelp ? 0 : 2);
104
114
  }
105
115
  switch (command) {
106
116
  case "sync": {
@@ -166,6 +176,7 @@ async function main() {
166
176
  lock: { type: "string", default: ".dna/lock" },
167
177
  stats: { type: "boolean", default: false },
168
178
  health: { type: "boolean", default: false },
179
+ strict: { type: "boolean", default: false },
169
180
  },
170
181
  allowPositionals: true,
171
182
  strict: false,
@@ -175,6 +186,7 @@ async function main() {
175
186
  lockFile: verifyValues.lock,
176
187
  stats: verifyValues.stats,
177
188
  health: verifyValues.health,
189
+ strict: verifyValues.strict,
178
190
  });
179
191
  process.exit(code);
180
192
  break;
@@ -512,7 +524,7 @@ async function main() {
512
524
  }
513
525
  default:
514
526
  process.stderr.write(`Unknown command: ${command}\n\n`);
515
- process.stderr.write(HELP);
527
+ process.stderr.write(help);
516
528
  process.exit(2);
517
529
  }
518
530
  }
@@ -10,7 +10,9 @@
10
10
  /**
11
11
  * Apply a modifier to a gene's expression level.
12
12
  */
13
- function applyModifier(level, mod) {
13
+ function applyModifier(level, mod, protectedThreshold = false) {
14
+ if (protectedThreshold && (mod.action === "deactivate" || mod.action === "suppress"))
15
+ return Math.max(level, 1.0);
14
16
  switch (mod.action) {
15
17
  case "amplify":
16
18
  return level * (mod.factor ?? 2.0);
@@ -28,7 +30,7 @@ function applyModifier(level, mod) {
28
30
  * Apply epigenetic markers to gene expression levels.
29
31
  * Markers decay over time based on their decay factor and age.
30
32
  */
31
- function applyEpigeneticMarkers(genes, markers) {
33
+ function applyEpigeneticMarkers(genes, markers, protectedThresholdGenes) {
32
34
  const now = Date.now();
33
35
  for (const marker of markers) {
34
36
  // Calculate decay based on time elapsed
@@ -40,6 +42,8 @@ function applyEpigeneticMarkers(genes, markers) {
40
42
  continue; // marker too old, skip
41
43
  for (const effect of marker.effects) {
42
44
  if (effect.gene && genes[effect.gene] && effect.action && effect.factor) {
45
+ if (protectedThresholdGenes.has(effect.gene) && effect.action === "suppress")
46
+ continue;
43
47
  const gene = genes[effect.gene];
44
48
  const effectiveFactor = 1 + (effect.factor - 1) * decayedStrength;
45
49
  if (effect.action === "amplify") {
@@ -77,18 +81,19 @@ export function activateDNA(cascaded, contextName = null, activeRole = null) {
77
81
  role: gene.role,
78
82
  };
79
83
  }
84
+ const protectedThresholdGenes = new Set(cascaded.protected_threshold_genes ?? []);
80
85
  // Apply context modifiers if a context is active
81
86
  if (contextName && cascaded.contexts[contextName]) {
82
87
  const ctx = cascaded.contexts[contextName];
83
88
  // Apply gene modifiers
84
89
  for (const mod of ctx.modifiers ?? []) {
85
90
  if (activated[mod.gene]) {
86
- activated[mod.gene].expression_level = applyModifier(activated[mod.gene].expression_level, mod);
91
+ activated[mod.gene].expression_level = applyModifier(activated[mod.gene].expression_level, mod, protectedThresholdGenes.has(mod.gene));
87
92
  }
88
93
  }
89
94
  // Deactivate genes
90
95
  for (const g of ctx.deactivate_genes ?? []) {
91
- if (activated[g]) {
96
+ if (activated[g] && !protectedThresholdGenes.has(g)) {
92
97
  activated[g].expression_level = 0;
93
98
  }
94
99
  }
@@ -125,13 +130,13 @@ export function activateDNA(cascaded, contextName = null, activeRole = null) {
125
130
  }
126
131
  // Suppress genes
127
132
  for (const geneName of role.suppresses_genes ?? []) {
128
- if (activated[geneName]) {
133
+ if (activated[geneName] && !protectedThresholdGenes.has(geneName)) {
129
134
  activated[geneName].expression_level *= 0.1;
130
135
  }
131
136
  }
132
137
  }
133
138
  // Apply epigenetic markers
134
- applyEpigeneticMarkers(activated, cascaded.epigenetic_markers);
139
+ applyEpigeneticMarkers(activated, cascaded.epigenetic_markers, protectedThresholdGenes);
135
140
  return {
136
141
  genes: activated,
137
142
  active_context: contextName,