intentdna 1.5.16 → 1.5.17

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.
@@ -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.5.16",
12
+ "version": "1.5.17",
13
13
  "author": {
14
14
  "name": "Samuel"
15
15
  },
@@ -25,5 +25,5 @@
25
25
  ]
26
26
  }
27
27
  ],
28
- "version": "1.5.16"
28
+ "version": "1.5.17"
29
29
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intentdna",
3
- "version": "1.5.16",
3
+ "version": "1.5.17",
4
4
  "description": "Declarative policy layer for AI agent behavior",
5
5
  "author": {
6
6
  "name": "Samuel"
package/README.md CHANGED
@@ -1,133 +1,105 @@
1
1
  # Intent DNA
2
2
 
3
- **AI Agent Guardrails in 30 seconds.**
3
+ **Declarative control plane for AI agent behavior.**
4
4
 
5
- ```bash
6
- npm install -g intentdna
7
- cd your-project
8
- dna guard
9
- ```
10
-
11
- That's it. Your AI agent (Claude Code, Cursor, Cline) now has safety boundaries — no more accidental `rm -rf`, force-push to main, or production deletes.
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.
12
6
 
13
7
  ---
14
8
 
15
- ## Why?
9
+ ## What It Is
10
+
11
+ Intent DNA is a compiler and runtime toolkit for managing agent behavior across three layers:
12
+
13
+ - **Behavioral guidance** via generated prompt/context files
14
+ - **Deterministic enforcement** via hooks and runtime checks
15
+ - **Feedback loops** via trace, audit, and epigenetic evolution
16
16
 
17
- AI agents are powerful, but **one wrong move can be expensive**:
17
+ It governs the agents operating on your systems rather than re-implementing the systems themselves.
18
18
 
19
- - Deletes production database
20
- - Force-pushes over main branch
21
- - Exposes secrets in logs
22
- - Runs up $500 API bills overnight
19
+ The core model is:
20
+
21
+ ```text
22
+ DNA config compiler pipeline Constraint IR → harness-specific outputs
23
+ ```
23
24
 
24
- **Intent DNA** gives agents clear rules about what they can and cannot do — enforced at the harness level, not just in prompts.
25
+ For the architectural model and long-term design, see [DESIGN.md](DESIGN.md).
26
+ For the current stage and upcoming work, see [ROADMAP.md](ROADMAP.md).
27
+ For the schema contract, see [spec/schema-spec.md](spec/schema-spec.md).
28
+
29
+ ## Design Principles
30
+
31
+ - Govern agents rather than re-implementing business systems.
32
+ - Declare intent and boundaries first, then compile them into native control surfaces.
33
+ - Combine guidance, enforcement, validation, and feedback into one governance loop.
34
+ - Treat runtime state, validators, and signals as first-class governance assets.
35
+ - Keep adapters and signal ingestion pluggable so the control plane outlives any single tool.
25
36
 
26
37
  ---
27
38
 
28
39
  ## Quick Start
29
40
 
30
- ### Option 1: Zero-Config Guardrails (Recommended)
41
+ ### Option 1: Zero-config guardrails
31
42
 
32
43
  ```bash
33
44
  npm install -g intentdna
34
45
  cd your-project
35
- dna guard # Auto-detects Claude Code/Cursor/Cline
36
- ```
37
-
38
- **What it does:**
39
- - Detects your AI coding environment
40
- - Injects safety rules (no production deletes, no force-push, etc.)
41
- - Generates enforcement hooks (hard blocks, not suggestions)
42
- - Creates audit logs at `~/.intentdna/audit/`
43
-
44
- **Levels:**
45
- ```bash
46
- dna guard --level standard # Safety baseline (default)
47
- dna guard --level yolo # Max autonomy + safety net
48
- dna guard --level strict # Enterprise-grade
49
- ```
50
-
51
- **Audit:**
52
- ```bash
53
- dna guard --audit # Show last 7 days of violations
54
- dna guard --remove # Remove all guardrails
46
+ dna guard
55
47
  ```
56
48
 
57
- ---
58
-
59
- ### Option 2: Custom DNA (Advanced)
60
-
61
- Start from a template:
49
+ ### Option 2: Template-based DNA
62
50
 
63
51
  ```bash
64
52
  dna init --template secure-dev
65
- dna sync .dna.yaml --inject CLAUDE.md --hooks .claude/hooks
53
+ dna sync
66
54
  ```
67
55
 
68
- Or import your existing config:
56
+ List templates:
69
57
 
70
58
  ```bash
71
- dna import . # Reads CLAUDE.md, hooks → generates .dna.yaml
72
- # Edit .dna.yaml to customize
73
- dna sync .dna.yaml --inject CLAUDE.md --hooks .claude/hooks
59
+ dna init --template list
74
60
  ```
75
61
 
76
- ---
62
+ Upgrade an existing template-backed config:
77
63
 
78
- ## 20 Built-in Templates
79
-
80
- **Workflows (Superpowers-inspired):**
81
- - `brainstorming-first` — Socratic design before coding
82
- - `subagent-parallel` — Multi-agent with two-phase review
83
- - `verification-loop` — Confirm fix before marking done
84
- - `branch-finishing` — Merge/PR/cleanup decisions
85
- - `pr-submitter` — Pre-review checklist
86
-
87
- **Quality & Safety:**
88
- - `secure-dev` — Security + quality baseline
89
- - `tdd-strict` — Strict test-driven development
90
- - `safe-refactoring` — Step-by-step refactoring
91
- - `systematic-debugging` — Structured debugging
92
- - `code-review-pipeline` — Review workflow with retry
93
-
94
- **Domain-Specific:**
95
- - `frontend-quality` — React/accessibility/performance
96
- - `api-backend` — API security + database safety
97
- - `mobile-dev` — Flutter/React Native standards
98
- - `devops-cicd` — CI/CD agent guardrails
99
- - `monorepo-governance` — Multi-package isolation
100
-
101
- **Collaboration:**
102
- - `multi-perspective-review` — Bull/Bear debate review
103
- - `documentation-writer` — Technical docs
104
- - `enterprise-baseline` — Organization security
105
- - `yolo-with-guardrails` — Full autonomy + limits
106
- - `full-pipeline` — Complete plan→implement→test→review
107
-
108
- List all:
109
64
  ```bash
110
- dna init --template list
65
+ dna init --upgrade <template-name>
111
66
  ```
112
67
 
113
68
  ---
114
69
 
115
70
  ## How It Works
116
71
 
117
- DNA compiles to your AI tool's native format:
72
+ Intent DNA compiles to your tool's native control surfaces:
118
73
 
119
- ```
120
- .dna.yamldna sync → CLAUDE.md + hooks + agents + skills + settings
74
+ ```text
75
+ DNAConstraint IR → CLAUDE.md / settings / agents / skills / runtime state
121
76
  ```
122
77
 
78
+ Typical outputs include:
79
+
123
80
  | Output | Purpose |
124
81
  |--------|---------|
125
- | `CLAUDE.md` | Behavioral guidelines (soft) |
126
- | `.claude/hooks/*.sh` | Hard constraints (exit 2 = block) |
82
+ | `CLAUDE.md` | Behavioral guidance |
83
+ | `.dna/compiled/ir.json` | Runtime-readable compiled constraints |
127
84
  | `.claude/agents/*.md` | Role definitions |
128
- | `.cursorrules` | Cursor integration |
85
+ | `.claude/skills/*/SKILL.md` | Workflow entrypoints |
86
+ | `.claude/settings.json` or plugin runtime | Hook wiring |
129
87
 
130
- **Key insight:** Prompts can be ignored. Hooks cannot. DNA generates both.
88
+ **Key idea:** prompts can drift, but runtime constraints and feedback loops make behavior governable.
89
+
90
+ ---
91
+
92
+ ## Supported Workflows
93
+
94
+ Intent DNA currently focuses on:
95
+
96
+ - Claude Code / Claude-style harnesses
97
+ - Role-based agent workflows
98
+ - Template-driven project bootstrap and upgrades
99
+ - Deterministic guardrails and stateful enforcement
100
+ - Dogfood-driven template iteration
101
+
102
+ Adapter and ecosystem direction are tracked in [ROADMAP.md](ROADMAP.md), not duplicated here.
131
103
 
132
104
  ---
133
105
 
@@ -197,10 +169,8 @@ workflows:
197
169
  ## Supported Environments
198
170
 
199
171
  - ✅ **Claude Code** (CLAUDE.md + hooks + agents + skills + settings)
200
- - **Cursor** (.cursorrules)
201
- - ✅ **Cline** (.clinerules)
172
+ - 🚧 **Prompt-style adapters and markdown outputs** (.cursorrules, .clinerules, GEMINI.md)
202
173
  - 🚧 **AgentScope** (coming soon)
203
- - 🚧 **Gemini CLI** (GEMINI.md)
204
174
 
205
175
  ---
206
176
 
@@ -223,13 +193,23 @@ dna evolve <dna-id> # Generate epigenetic markers from outcomes
223
193
 
224
194
  - **Genes** encode behavioral traits
225
195
  - **Codons** are the atomic instructions
226
- - **Cascade** mimics genetic inheritance
227
- - **Epigenetics** learns from experience without changing genes
196
+ - **Cascade** mimics inheritance and override rules
197
+ - **Epigenetics** learns from experience without mutating source intent
228
198
 
229
199
  Read more: [DESIGN.md](DESIGN.md)
230
200
 
231
201
  ---
232
202
 
203
+ ## Documentation Map
204
+
205
+ - [DESIGN.md](DESIGN.md) — long-term architecture and design
206
+ - [ROADMAP.md](ROADMAP.md) — current stage, next plan, deferred work
207
+ - [spec/schema-spec.md](spec/schema-spec.md) — schema contract
208
+ - [docs/references/](docs/references/) — distilled research and implementation references
209
+ - [docs/insights/](docs/insights/) — strategic and product-thinking documents
210
+
211
+ ---
212
+
233
213
  ## License
234
214
 
235
215
  Apache-2.0
@@ -248,4 +228,4 @@ Issues and PRs welcome. See [ROADMAP.md](ROADMAP.md) for planned features.
248
228
  - [Cline KanBan](https://github.com/cline/cline-kanban) — Multi-agent task orchestration
249
229
  - [AgentScope](https://github.com/modelscope/agentscope) — Multi-agent framework from Alibaba
250
230
 
251
- Intent DNA compiles to these frameworks' native formats.
231
+ Intent DNA is designed to compile into the native control surfaces those frameworks expose, with current maturity varying by adapter.
@@ -37,9 +37,17 @@ export interface PolicyViolationEvent extends AuditEvent {
37
37
  /** Completion check event */
38
38
  export interface CompletionCheckEvent extends AuditEvent {
39
39
  event_type: "completion_check";
40
- check_type: "file_exists" | "file_not_empty" | "file_contains" | "command_success";
40
+ check_type: "file_exists" | "file_not_empty" | "file_contains" | "command_success" | "checkpoint_assert";
41
41
  target: string;
42
42
  result: "pass" | "fail";
43
+ verifier_id?: string;
44
+ verifier_when?: string;
45
+ verifier_severity?: string;
46
+ verifier_kind?: string;
47
+ evidence?: string;
48
+ exit_code?: number;
49
+ artifact?: string;
50
+ message?: string;
43
51
  }
44
52
  /** Session event */
45
53
  export interface SessionEvent extends AuditEvent {
@@ -56,6 +64,7 @@ export interface ConfigDriftEvent extends AuditEvent {
56
64
  expected_checksum: string;
57
65
  actual_checksum: string;
58
66
  }
67
+ export type AnyAuditEvent = AuditEvent | ToolCallEvent | PolicyViolationEvent | CompletionCheckEvent | SessionEvent | ConfigDriftEvent;
59
68
  export interface AuditConfig {
60
69
  enabled: boolean;
61
70
  storage: string;
@@ -67,7 +76,7 @@ export type AuditTrackOption = "tool_calls" | "policy_violations" | "completion_
67
76
  * Write an audit event to the log.
68
77
  * Events are stored as JSONL (one JSON per line) in daily files.
69
78
  */
70
- export declare function writeAuditEvent(event: AuditEvent, config: AuditConfig): Promise<void>;
79
+ export declare function writeAuditEvent(event: AnyAuditEvent, config: AuditConfig): Promise<void>;
71
80
  /**
72
81
  * Read audit events for a date range.
73
82
  */
@@ -75,7 +84,7 @@ export declare function readAuditEvents(logDir: string, options?: {
75
84
  from?: string;
76
85
  to?: string;
77
86
  event_type?: AuditEventType;
78
- }): Promise<AuditEvent[]>;
87
+ }): Promise<AnyAuditEvent[]>;
79
88
  export interface AuditSummary {
80
89
  period: {
81
90
  from: string;
@@ -18,6 +18,9 @@ export interface FeedbackReport {
18
18
  blocks: number;
19
19
  warns: number;
20
20
  allows: number;
21
+ verifier_results: number;
22
+ verifier_passes: number;
23
+ verifier_failures: number;
21
24
  top_blocked_tools: Array<{
22
25
  tool: string;
23
26
  count: number;
@@ -7,8 +7,8 @@
7
7
  * Reads: .dna/state/trace/trace-*.jsonl
8
8
  * Outputs: human-readable report (or JSON with --json)
9
9
  */
10
- import { readTraces } from "../../hooks/state.js";
11
- import { traceToOutcomes, summarizeConversion } from "../../evolution/trace-bridge.js";
10
+ import { readTraces, readVerifierResults } from "../../hooks/state.js";
11
+ import { mergeOutcomes, summarizeConversion } from "../../evolution/trace-bridge.js";
12
12
  import { describeMarkerChanges } from "../../evolution/marker-gen.js";
13
13
  import { EvolutionStore } from "../../evolution/store.js";
14
14
  function aggregate(traces) {
@@ -50,10 +50,15 @@ function aggregate(traces) {
50
50
  top_block_reasons: sorted(reasonCounts, 5, "reason"),
51
51
  };
52
52
  }
53
- function generateSuggestions(report) {
53
+ function generateSuggestions(report, verifierResults = 0) {
54
54
  const suggestions = [];
55
55
  if (report.blocks === 0 && report.warns === 0) {
56
- suggestions.push("No blocks or warnings — DNA constraints are not firing. Consider tightening rules or verifying hooks are active.");
56
+ if (verifierResults > 0) {
57
+ suggestions.push("No trace blocks or warnings in this window. Verifier activity is present, so review verifier pass/fail results alongside trace data.");
58
+ }
59
+ else {
60
+ suggestions.push("No blocks or warnings — DNA constraints are not firing. Consider tightening rules or verifying hooks are active.");
61
+ }
57
62
  return suggestions;
58
63
  }
59
64
  const blockRate = report.total_events > 0 ? report.blocks / report.total_events : 0;
@@ -93,6 +98,12 @@ function formatReport(report) {
93
98
  lines.push(` Blocked: ${report.blocks}`);
94
99
  lines.push(` Warned: ${report.warns}`);
95
100
  lines.push("");
101
+ if (report.verifier_results > 0) {
102
+ lines.push(`Verifier results: ${report.verifier_results}`);
103
+ lines.push(` Passed: ${report.verifier_passes}`);
104
+ lines.push(` Failed: ${report.verifier_failures}`);
105
+ lines.push("");
106
+ }
96
107
  if (report.top_blocked_tools.length > 0) {
97
108
  lines.push("Top blocked tools:");
98
109
  for (const t of report.top_blocked_tools) {
@@ -126,23 +137,34 @@ function formatReport(report) {
126
137
  export async function runFeedback(opts) {
127
138
  const projectDir = process.cwd();
128
139
  const traces = await readTraces(projectDir, opts.days);
129
- if (traces.length === 0) {
130
- process.stderr.write(`No trace data found for the last ${opts.days} day(s).\n`);
131
- process.stderr.write("Traces are generated by dna-hook during Claude Code sessions.\n");
140
+ const cutoff = new Date();
141
+ cutoff.setDate(cutoff.getDate() - opts.days);
142
+ const verifierResults = (await readVerifierResults(projectDir)).filter((result) => {
143
+ const timestamp = Date.parse(result.timestamp);
144
+ return Number.isNaN(timestamp) || timestamp >= cutoff.getTime();
145
+ });
146
+ if (traces.length === 0 && verifierResults.length === 0) {
147
+ process.stderr.write(`No trace or verifier data found for the last ${opts.days} day(s).\n`);
148
+ process.stderr.write("Signals are generated by dna-hook during Claude Code sessions.\n");
132
149
  return 0;
133
150
  }
134
151
  const stats = aggregate(traces);
135
- const suggestions = generateSuggestions(stats);
136
- const report = { ...stats, period_days: opts.days, suggestions };
152
+ const verifierStats = {
153
+ verifier_results: verifierResults.length,
154
+ verifier_passes: verifierResults.filter((result) => result.status === "pass").length,
155
+ verifier_failures: verifierResults.filter((result) => result.status === "fail").length,
156
+ };
157
+ const suggestions = generateSuggestions(stats, verifierStats.verifier_results);
158
+ const report = { ...stats, ...verifierStats, period_days: opts.days, suggestions };
137
159
  if (opts.json && !opts.evolve) {
138
160
  process.stdout.write(JSON.stringify(report, null, 2) + "\n");
139
161
  }
140
162
  else if (!opts.evolve) {
141
163
  process.stderr.write(formatReport(report));
142
164
  }
143
- // --evolve: convert traces → epigenetic markers
165
+ // --evolve: convert normalized local signals → epigenetic markers
144
166
  if (opts.evolve) {
145
- const outcomes = traceToOutcomes(traces);
167
+ const outcomes = mergeOutcomes(traces, verifierResults);
146
168
  if (outcomes.length === 0) {
147
169
  process.stderr.write(formatReport(report));
148
170
  process.stderr.write("\nNo actionable gene feedback found in traces — nothing to evolve.\n");
@@ -270,7 +270,7 @@ export async function runRun(opts) {
270
270
  process.stderr.write(`Error: Workflow '${opts.workflowName}' not found. Available: ${Object.keys(cascaded.workflows).join(", ") || "(none)"}\n`);
271
271
  return 1;
272
272
  }
273
- const wfResult = compileWorkflow(wfDef);
273
+ const wfResult = compileWorkflow(wfDef, { workflow_key: opts.workflowName });
274
274
  if (!wfResult.ok) {
275
275
  process.stderr.write(`Error: Workflow compilation failed:\n`);
276
276
  for (const err of wfResult.errors) {
@@ -21,7 +21,7 @@ import { compileToMarkdown, injectIntoFile, removeFromFile } from "../../runtime
21
21
  import { compileAllRolesToAgentMD, writeAgentMDFiles, removeAgentMDFiles } from "../../runtime/agent-md.js";
22
22
  import { removeWorkflowScripts } from "../../runtime/workflow-runner.js";
23
23
  import { compileWorkflowToSkill, writeSkillFiles } from "../../runtime/skill-adapter.js";
24
- import { compilePluginSettings, detectEnabledEvents, mergeSettingsFile } from "../../runtime/settings-adapter.js";
24
+ import { compilePluginSettings, detectEnabledEvents, mergeSettingsFile, recommendHookTimeouts } from "../../runtime/settings-adapter.js";
25
25
  import { createCompiledIR, writeCompiledIR } from "../../runtime/plugin-adapter.js";
26
26
  import { cascadeDNA } from "../../compiler/cascade.js";
27
27
  import { EvolutionEngine } from "../../evolution/index.js";
@@ -569,7 +569,7 @@ export async function runSync(opts) {
569
569
  if (!opts.plugin && opts.settingsPath) {
570
570
  const enabledEvents = detectEnabledEvents(ir);
571
571
  if (enabledEvents.length > 0) {
572
- const pluginSettings = compilePluginSettings(enabledEvents);
572
+ const pluginSettings = compilePluginSettings(enabledEvents, 10, recommendHookTimeouts(ir));
573
573
  await mergeSettingsFile(pluginSettings, opts.settingsPath);
574
574
  process.stderr.write(`Bin mode: registered ${enabledEvents.length} hook event(s) in ${opts.settingsPath}\n`);
575
575
  trackedOutputs.push(resolve(opts.settingsPath));
@@ -627,7 +627,7 @@ export async function runSync(opts) {
627
627
  process.stderr.write(`Workflow '${name}' not found\n`);
628
628
  continue;
629
629
  }
630
- const result = compileWorkflow(wfDef);
630
+ const result = compileWorkflow(wfDef, { workflow_key: name });
631
631
  if (!result.ok) {
632
632
  process.stderr.write(`Workflow '${name}' compilation failed:\n`);
633
633
  for (const err of result.errors) {
@@ -682,8 +682,8 @@ export async function runSync(opts) {
682
682
  else {
683
683
  const { compileWorkflow } = await import("../../compiler/workflow.js");
684
684
  const skillResults = [];
685
- for (const [, wfDef] of Object.entries(workflows)) {
686
- const result = compileWorkflow(wfDef);
685
+ for (const [name, wfDef] of Object.entries(workflows)) {
686
+ const result = compileWorkflow(wfDef, { workflow_key: name });
687
687
  if (result.ok) {
688
688
  skillResults.push(compileWorkflowToSkill(result.plan, roles, ir, variables));
689
689
  }
@@ -10,7 +10,7 @@
10
10
  * - attract/repel: same target accumulates, different targets independent
11
11
  * - amplify/suppress: factors multiply (1.5x × 2.0x = 3.0x)
12
12
  */
13
- import type { IntentDNA, Gene, ContextRegion, EpigeneticMarker, RoleDef, WorkflowDef } from "../schema/types.js";
13
+ import type { IntentDNA, Gene, ContextRegion, EpigeneticMarker, RoleDef, WorkflowDef, LegibilityAssetMap, VerifierCommandPolicy } from "../schema/types.js";
14
14
  export interface CascadedDNA {
15
15
  genes: Record<string, Gene>;
16
16
  contexts: Record<string, ContextRegion>;
@@ -20,11 +20,13 @@ export interface CascadedDNA {
20
20
  source_ids: string[];
21
21
  /** Warnings from cascade process (e.g., cross-namespace gene collisions) */
22
22
  warnings?: string[];
23
+ legibility_assets?: LegibilityAssetMap;
23
24
  context_files?: {
24
25
  mandatory?: string[];
25
26
  per_role?: Record<string, string[]>;
26
27
  per_workflow?: Record<string, string[]>;
27
28
  };
29
+ verifier_policy?: VerifierCommandPolicy;
28
30
  }
29
31
  /**
30
32
  * Cascade multiple DNA layers into a single merged DNA.
@@ -84,6 +84,80 @@ function prefixWorkflowRoles(wf, ns) {
84
84
  })),
85
85
  };
86
86
  }
87
+ function prefixRecordKeys(record, ns) {
88
+ if (!record)
89
+ return undefined;
90
+ if (!ns)
91
+ return record;
92
+ return Object.fromEntries(Object.entries(record).map(([key, value]) => [`${ns}_${key}`, value]));
93
+ }
94
+ function prefixLegibilityAssets(map, ns) {
95
+ if (!map)
96
+ return undefined;
97
+ if (!ns)
98
+ return map;
99
+ return {
100
+ mandatory: map.mandatory,
101
+ per_role: prefixRecordKeys(map.per_role, ns),
102
+ per_workflow: prefixRecordKeys(map.per_workflow, ns),
103
+ };
104
+ }
105
+ function mergePrefixedRecords(base, incoming) {
106
+ if (!base)
107
+ return incoming;
108
+ if (!incoming)
109
+ return base;
110
+ return { ...base, ...incoming };
111
+ }
112
+ function mergeLegibilityAssets(base, incoming) {
113
+ if (!base)
114
+ return incoming;
115
+ if (!incoming)
116
+ return base;
117
+ return {
118
+ mandatory: incoming.mandatory ?? base.mandatory,
119
+ per_role: mergePrefixedRecords(base.per_role, incoming.per_role),
120
+ per_workflow: mergePrefixedRecords(base.per_workflow, incoming.per_workflow),
121
+ };
122
+ }
123
+ function prefixContextFiles(contextFiles, ns) {
124
+ if (!contextFiles)
125
+ return undefined;
126
+ if (!ns)
127
+ return contextFiles;
128
+ return {
129
+ mandatory: contextFiles.mandatory,
130
+ per_role: prefixRecordKeys(contextFiles.per_role, ns),
131
+ per_workflow: prefixRecordKeys(contextFiles.per_workflow, ns),
132
+ };
133
+ }
134
+ function mergeContextFiles(base, incoming) {
135
+ if (!base)
136
+ return incoming;
137
+ if (!incoming)
138
+ return base;
139
+ return {
140
+ mandatory: incoming.mandatory ?? base.mandatory,
141
+ per_role: mergePrefixedRecords(base.per_role, incoming.per_role),
142
+ per_workflow: mergePrefixedRecords(base.per_workflow, incoming.per_workflow),
143
+ };
144
+ }
145
+ function mergeVerifierPolicy(base, incoming) {
146
+ if (!base)
147
+ return incoming;
148
+ if (!incoming)
149
+ return base;
150
+ const mergeList = (a, b) => {
151
+ if (!a && !b)
152
+ return undefined;
153
+ return [...new Set([...(a ?? []), ...(b ?? [])])];
154
+ };
155
+ return {
156
+ allow_commands: mergeList(base.allow_commands, incoming.allow_commands),
157
+ allow_command_prefixes: mergeList(base.allow_command_prefixes, incoming.allow_command_prefixes),
158
+ allow_builtin_asserts: mergeList(base.allow_builtin_asserts, incoming.allow_builtin_asserts),
159
+ };
160
+ }
87
161
  /**
88
162
  * Cascade multiple DNA layers into a single merged DNA.
89
163
  * Input must be sorted by priority (lowest first).
@@ -156,11 +230,19 @@ export function cascadeDNA(layers) {
156
230
  allMarkers.push(...dna.epigenetic.markers);
157
231
  }
158
232
  }
159
- // context_files: highest-priority DNA wins (last in sorted layers)
233
+ // legibility_assets/context_files: highest-priority DNA wins (last in sorted layers)
234
+ let legibilityAssets;
160
235
  let contextFiles;
236
+ let verifierPolicy;
161
237
  for (const dna of sorted) {
238
+ if (dna.legibility_assets) {
239
+ legibilityAssets = mergeLegibilityAssets(legibilityAssets, prefixLegibilityAssets(dna.legibility_assets, dna.namespace));
240
+ }
162
241
  if (dna.context_files) {
163
- contextFiles = dna.context_files;
242
+ contextFiles = mergeContextFiles(contextFiles, prefixContextFiles(dna.context_files, dna.namespace));
243
+ }
244
+ if (dna.verifier_policy) {
245
+ verifierPolicy = mergeVerifierPolicy(verifierPolicy, dna.verifier_policy);
164
246
  }
165
247
  }
166
248
  return {
@@ -171,6 +253,8 @@ export function cascadeDNA(layers) {
171
253
  epigenetic_markers: allMarkers,
172
254
  source_ids: sourceIds,
173
255
  warnings: warnings.length > 0 ? warnings : undefined,
256
+ legibility_assets: legibilityAssets,
174
257
  context_files: contextFiles,
258
+ verifier_policy: verifierPolicy,
175
259
  };
176
260
  }