intentdna 1.5.16 → 1.5.18

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 (36) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +72 -92
  4. package/dist/audit/index.d.ts +12 -3
  5. package/dist/cli/commands/feedback.d.ts +3 -0
  6. package/dist/cli/commands/feedback.js +33 -11
  7. package/dist/cli/commands/run.js +1 -1
  8. package/dist/cli/commands/sync.js +5 -5
  9. package/dist/compiler/cascade.d.ts +3 -1
  10. package/dist/compiler/cascade.js +86 -2
  11. package/dist/compiler/compile.js +93 -3
  12. package/dist/compiler/workflow.d.ts +1 -0
  13. package/dist/compiler/workflow.js +1 -0
  14. package/dist/evolution/trace-bridge.d.ts +4 -5
  15. package/dist/evolution/trace-bridge.js +31 -55
  16. package/dist/hooks/cli.d.ts +18 -1
  17. package/dist/hooks/cli.js +624 -24
  18. package/dist/hooks/enforce.js +3 -2
  19. package/dist/hooks/state.d.ts +20 -1
  20. package/dist/hooks/state.js +55 -0
  21. package/dist/runtime/markdown.d.ts +1 -1
  22. package/dist/runtime/markdown.js +149 -0
  23. package/dist/runtime/settings-adapter.d.ts +3 -2
  24. package/dist/runtime/settings-adapter.js +26 -4
  25. package/dist/runtime/skill-adapter.js +49 -23
  26. package/dist/schema/types.d.ts +39 -0
  27. package/dist/schema/validate.js +143 -0
  28. package/dist/signals/index.d.ts +45 -0
  29. package/dist/signals/index.js +117 -0
  30. package/dist/templates/code-review-pipeline.dna.yaml +4 -0
  31. package/dist/templates/flutter-rewrite.dna.yaml +139 -212
  32. package/dist/templates/full-pipeline.dna.yaml +4 -0
  33. package/dist/templates/mobile-dev.dna.yaml +5 -0
  34. package/hooks/hooks.json +1 -1
  35. package/package.json +1 -1
  36. package/spec/control-plane-convergence-handoff-2026-04-24.md +432 -0
@@ -713,8 +713,9 @@ export function checkReflectionLimit(failCount, maxAttempts, handoffCount, maxHa
713
713
  * Pure function — no I/O.
714
714
  */
715
715
  export function checkContextReadiness(sessionReads, required) {
716
- const readSet = new Set(sessionReads);
717
- const missing = required.filter(f => !readSet.has(f));
716
+ const normalizePath = (value) => normalize(value);
717
+ const readSet = new Set(sessionReads.map(normalizePath));
718
+ const missing = required.filter((f) => !readSet.has(normalizePath(f)));
718
719
  return { ready: missing.length === 0, missing };
719
720
  }
720
721
  // ── Workflow Boundary Gate ────────────────────────────────────
@@ -9,7 +9,7 @@
9
9
  * All read operations are fail-safe (return null on error, never throw).
10
10
  * All write operations use atomic temp+rename pattern.
11
11
  */
12
- import type { CompletedArtifactEntry } from "../schema/types.js";
12
+ import type { CompletedArtifactEntry, VerifierKind, VerifierSeverity, VerifierWhen } from "../schema/types.js";
13
13
  /** Workflow state written by runtime and read by hooks */
14
14
  export interface DNAWorkflowState {
15
15
  active: boolean;
@@ -115,6 +115,25 @@ export declare function writeSessionReads(projectDir: string, state: SessionRead
115
115
  * @deprecated Prefer `DNAStateManager.appendSessionRead()`.
116
116
  */
117
117
  export declare function appendSessionRead(projectDir: string, filePath: string, sessionId?: string): Promise<void>;
118
+ export interface VerifierResultEntry {
119
+ verifier_id: string;
120
+ when: VerifierWhen;
121
+ severity: VerifierSeverity;
122
+ kind: VerifierKind;
123
+ workflow?: string;
124
+ step_id?: string;
125
+ source_genes?: string[];
126
+ status: "pass" | "fail";
127
+ target?: string;
128
+ evidence?: string;
129
+ exit_code?: number;
130
+ artifact?: string;
131
+ message?: string;
132
+ timestamp: string;
133
+ }
134
+ export declare function readVerifierResults(projectDir: string, sessionId?: string): Promise<VerifierResultEntry[]>;
135
+ export declare function writeVerifierResults(projectDir: string, results: VerifierResultEntry[], sessionId?: string): Promise<void>;
136
+ export declare function appendVerifierResult(projectDir: string, result: VerifierResultEntry, sessionId?: string): Promise<void>;
118
137
  /** Trace entry for hook call observability */
119
138
  export interface TraceEntry {
120
139
  trace_id: string;
@@ -214,6 +214,61 @@ export async function appendSessionRead(projectDir, filePath, sessionId) {
214
214
  await writeSessionReads(projectDir, state, sessionId);
215
215
  }
216
216
  }
217
+ const VERIFIER_RESULTS_FILE = "workflow/verifier-results.json";
218
+ async function readVerifierResultsFile(projectDir, sessionId) {
219
+ const stateDir = resolveStateDir(projectDir, sessionId);
220
+ const filePath = join(stateDir, VERIFIER_RESULTS_FILE);
221
+ try {
222
+ const raw = await readFile(filePath, "utf-8");
223
+ const parsed = JSON.parse(raw);
224
+ return Array.isArray(parsed) ? parsed : [];
225
+ }
226
+ catch {
227
+ return [];
228
+ }
229
+ }
230
+ function verifierResultKey(result) {
231
+ return [
232
+ result.verifier_id,
233
+ result.when,
234
+ result.severity,
235
+ result.kind,
236
+ result.workflow ?? "",
237
+ result.step_id ?? "",
238
+ result.status,
239
+ result.target ?? "",
240
+ result.timestamp,
241
+ ].join("|");
242
+ }
243
+ export async function readVerifierResults(projectDir, sessionId) {
244
+ if (sessionId) {
245
+ return readVerifierResultsFile(projectDir, sessionId);
246
+ }
247
+ const sessions = await listSessions(projectDir);
248
+ const all = await readVerifierResultsFile(projectDir);
249
+ for (const id of sessions) {
250
+ const results = await readVerifierResultsFile(projectDir, id);
251
+ all.push(...results);
252
+ }
253
+ const seen = new Set();
254
+ return all.filter((result) => {
255
+ const key = verifierResultKey(result);
256
+ if (seen.has(key))
257
+ return false;
258
+ seen.add(key);
259
+ return true;
260
+ }).sort((a, b) => a.timestamp.localeCompare(b.timestamp));
261
+ }
262
+ export async function writeVerifierResults(projectDir, results, sessionId) {
263
+ const stateDir = resolveStateDir(projectDir, sessionId);
264
+ const filePath = join(stateDir, VERIFIER_RESULTS_FILE);
265
+ await atomicWrite(filePath, JSON.stringify(results, null, 2));
266
+ }
267
+ export async function appendVerifierResult(projectDir, result, sessionId) {
268
+ const results = await readVerifierResultsFile(projectDir, sessionId);
269
+ results.push(result);
270
+ await writeVerifierResults(projectDir, results, sessionId);
271
+ }
217
272
  const TRACE_DIR = "trace";
218
273
  const MAX_TRACE_SIZE = 10 * 1024 * 1024; // 10MB
219
274
  const TRACE_RETENTION_DAYS = 7;
@@ -13,7 +13,7 @@
13
13
  * <!-- intentdna:end -->
14
14
  */
15
15
  import type { ConstraintIR } from "../schema/types.js";
16
- export type MarkdownTarget = "claude-md" | "soul-md" | "cursorrules" | "system-prompt";
16
+ export type MarkdownTarget = "claude-md" | "soul-md" | "cursorrules" | "system-prompt" | "agents-md";
17
17
  /**
18
18
  * Compile Constraint IR to a markdown block suitable for the target format.
19
19
  */
@@ -28,6 +28,8 @@ export function compileToMarkdown(ir, target) {
28
28
  return renderCursorrules(ir);
29
29
  case "system-prompt":
30
30
  return renderSystemPrompt(ir);
31
+ case "agents-md":
32
+ return renderAgentsMd(ir);
31
33
  }
32
34
  }
33
35
  /**
@@ -191,6 +193,153 @@ function renderCursorrules(ir) {
191
193
  }
192
194
  return lines.join("\n");
193
195
  }
196
+ function renderAgentsMd(ir) {
197
+ const lines = [];
198
+ const high = ir.prompt_directives.filter(d => d.priority === "high");
199
+ const med = ir.prompt_directives.filter(d => d.priority === "medium");
200
+ const low = ir.prompt_directives.filter(d => d.priority === "low");
201
+ lines.push("# Intent DNA — Codex Instructions");
202
+ lines.push("");
203
+ lines.push("Codex loads AGENTS.md at session start. After running `dna sync`, restart Codex so these instructions are reloaded.");
204
+ lines.push("These instructions are behavioral guidance; use Codex sandbox/config for deterministic enforcement.");
205
+ lines.push("");
206
+ if (ir.active_context) {
207
+ lines.push(`Active context: ${ir.active_context}`);
208
+ lines.push("");
209
+ }
210
+ if (high.length > 0) {
211
+ lines.push("## Critical Directives");
212
+ for (const d of high)
213
+ lines.push(`- ${d.text}`);
214
+ lines.push("");
215
+ }
216
+ if (med.length > 0) {
217
+ lines.push("## Standard Directives");
218
+ for (const d of med)
219
+ lines.push(`- ${d.text}`);
220
+ lines.push("");
221
+ }
222
+ if (low.length > 0) {
223
+ lines.push("## Preferences");
224
+ for (const d of low)
225
+ lines.push(`- ${d.text}`);
226
+ lines.push("");
227
+ }
228
+ if (ir.context_files || ir.legibility_assets) {
229
+ lines.push("## Required Context");
230
+ const files = collectContextFiles(ir);
231
+ if (files.length > 0) {
232
+ lines.push("Read these files before work that depends on project context:");
233
+ for (const file of files)
234
+ lines.push(`- ${file}`);
235
+ }
236
+ else {
237
+ lines.push("Follow any required-read assets declared by the active DNA.");
238
+ }
239
+ lines.push("");
240
+ }
241
+ if (ir.roles_scope_map && ir.roles_scope_map.length > 0) {
242
+ lines.push("## Role Boundaries");
243
+ for (const role of ir.roles_scope_map) {
244
+ lines.push(`- ${role.role_name}: read ${formatList(role.scope.read)}; write ${formatList(role.scope.write)}${formatToolPermissions(role.tool_permissions)}`);
245
+ }
246
+ lines.push("");
247
+ }
248
+ else if (ir.role_scope || ir.role_tool_permissions) {
249
+ lines.push("## Active Role Boundary");
250
+ if (ir.role_scope)
251
+ lines.push(`- Read: ${formatList(ir.role_scope.read)}`);
252
+ if (ir.role_scope)
253
+ lines.push(`- Write: ${formatList(ir.role_scope.write)}`);
254
+ if (ir.role_tool_permissions)
255
+ lines.push(`- Tools:${formatToolPermissions(ir.role_tool_permissions).replace(/^;/, "")}`);
256
+ lines.push("");
257
+ }
258
+ if (ir.pre_execution_gates.length > 0) {
259
+ lines.push("## Runtime Constraints");
260
+ for (const g of ir.pre_execution_gates) {
261
+ const action = g.action === "block" ? "must not" : g.action === "escalate" ? "ask first" : "warn";
262
+ lines.push(`- [${action}] ${g.message}`);
263
+ }
264
+ lines.push("");
265
+ }
266
+ if (ir.tool_filters.length > 0) {
267
+ lines.push("## Tool and Sandbox Guidance");
268
+ for (const f of ir.tool_filters)
269
+ lines.push(`- ${f.action}: ${f.target ?? f.reason} (${f.reason})`);
270
+ lines.push("- Prefer Codex `workspace-write` with `on-request` approvals for editable work; use `read-only` for planning/review.");
271
+ lines.push("");
272
+ }
273
+ if ((ir.workflows_ir && ir.workflows_ir.length > 0) || (ir.verifier_specs && ir.verifier_specs.length > 0)) {
274
+ lines.push("## Workflow and Verification Expectations");
275
+ for (const wf of ir.workflows_ir ?? []) {
276
+ lines.push(`- Workflow ${wf.workflow_name}: roles ${formatList(wf.active_roles)}.`);
277
+ for (const handoff of wf.handoff_chain ?? []) {
278
+ const produced = handoff.produces?.map(a => a.path ?? a.type).join(", ");
279
+ if (produced)
280
+ lines.push(` - Step ${handoff.step_id} should produce: ${produced}`);
281
+ }
282
+ }
283
+ for (const spec of ir.verifier_specs ?? []) {
284
+ const target = spec.checkpoint?.assert ?? spec.completion?.file_exists ?? spec.completion?.file_not_empty ?? spec.completion?.command_success ?? spec.id;
285
+ lines.push(`- Verify ${spec.when}/${spec.severity}: ${target}`);
286
+ }
287
+ lines.push("");
288
+ }
289
+ if (ir.post_execution_validators.length > 0) {
290
+ lines.push("## Post-Work Checks");
291
+ for (const v of ir.post_execution_validators)
292
+ lines.push(`- ${v.check}`);
293
+ lines.push("");
294
+ }
295
+ lines.push(`_Compiled: ${ir.compiled_at} | Sources: ${ir.source_dna_ids.join(", ")}_`);
296
+ return lines.join("\n");
297
+ }
298
+ function collectContextFiles(ir) {
299
+ const files = [];
300
+ const add = (file) => {
301
+ if (file && !files.includes(file))
302
+ files.push(file);
303
+ };
304
+ for (const file of ir.context_files?.mandatory ?? [])
305
+ add(file);
306
+ for (const roleFiles of Object.values(ir.context_files?.per_role ?? {})) {
307
+ for (const file of roleFiles)
308
+ add(file);
309
+ }
310
+ for (const workflowFiles of Object.values(ir.context_files?.per_workflow ?? {})) {
311
+ for (const file of workflowFiles)
312
+ add(file);
313
+ }
314
+ for (const asset of ir.legibility_assets?.mandatory ?? []) {
315
+ if (asset.type === "required_read")
316
+ add(asset.path);
317
+ }
318
+ for (const roleAssets of Object.values(ir.legibility_assets?.per_role ?? {})) {
319
+ for (const asset of roleAssets)
320
+ if (asset.type === "required_read")
321
+ add(asset.path);
322
+ }
323
+ for (const workflowAssets of Object.values(ir.legibility_assets?.per_workflow ?? {})) {
324
+ for (const asset of workflowAssets)
325
+ if (asset.type === "required_read")
326
+ add(asset.path);
327
+ }
328
+ return files;
329
+ }
330
+ function formatList(values) {
331
+ return values && values.length > 0 ? values.join(", ") : "none";
332
+ }
333
+ function formatToolPermissions(permissions) {
334
+ if (!permissions)
335
+ return "";
336
+ const parts = [];
337
+ if (permissions.allow?.length)
338
+ parts.push(`allow ${permissions.allow.join(", ")}`);
339
+ if (permissions.deny?.length)
340
+ parts.push(`deny ${permissions.deny.join(", ")}`);
341
+ return parts.length > 0 ? `; tools ${parts.join("; ")}` : "";
342
+ }
194
343
  function renderSystemPrompt(ir) {
195
344
  const lines = [];
196
345
  lines.push("# Behavioral Guidelines");
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * See docs/references/cc-source-analysis.md §2 for the full hook event catalog.
8
8
  */
9
- export type HookEventKey = "preToolUse" | "postToolUse" | "userPromptSubmit" | "subagentStop" | "preCompact" | "notification";
9
+ export type HookEventKey = "preToolUse" | "postToolUse" | "userPromptSubmit" | "subagentStop" | "preCompact" | "notification" | "stop";
10
10
  export interface SettingsHookEntry {
11
11
  matcher: string;
12
12
  hooks: Array<{
@@ -21,7 +21,8 @@ export interface DNASettings {
21
21
  * Compile settings.json hook configuration for dna-hook binary.
22
22
  * Uses `dna-hook <event>` commands.
23
23
  */
24
- export declare function compilePluginSettings(enabledEvents: HookEventKey[], timeout?: number): DNASettings;
24
+ export declare function compilePluginSettings(enabledEvents: HookEventKey[], timeout?: number, timeouts?: Partial<Record<HookEventKey, number>>): DNASettings;
25
+ export declare function recommendHookTimeouts(ir: import("../schema/types.js").ConstraintIR, baseTimeout?: number): Partial<Record<HookEventKey, number>>;
25
26
  /**
26
27
  * Determine which hook events are needed based on IR content.
27
28
  */
@@ -16,17 +16,19 @@ const HOOK_EVENT_MAP = {
16
16
  subagentStop: "SubagentStop",
17
17
  preCompact: "PreCompact",
18
18
  notification: "Notification",
19
+ stop: "Stop",
19
20
  };
20
21
  /**
21
22
  * Compile settings.json hook configuration for dna-hook binary.
22
23
  * Uses `dna-hook <event>` commands.
23
24
  */
24
- export function compilePluginSettings(enabledEvents, timeout = 10) {
25
+ export function compilePluginSettings(enabledEvents, timeout = 10, timeouts) {
25
26
  const settings = { hooks: {} };
26
27
  for (const key of enabledEvents) {
27
28
  const eventName = HOOK_EVENT_MAP[key];
28
29
  if (!eventName)
29
30
  continue;
31
+ const eventTimeout = timeouts?.[key] ?? timeout;
30
32
  settings.hooks[eventName] = [
31
33
  {
32
34
  matcher: "",
@@ -34,7 +36,7 @@ export function compilePluginSettings(enabledEvents, timeout = 10) {
34
36
  {
35
37
  type: "command",
36
38
  command: `dna-hook ${eventName}`,
37
- ...(timeout > 0 ? { timeout } : {}),
39
+ ...(eventTimeout > 0 ? { timeout: eventTimeout } : {}),
38
40
  },
39
41
  ],
40
42
  },
@@ -42,6 +44,20 @@ export function compilePluginSettings(enabledEvents, timeout = 10) {
42
44
  }
43
45
  return settings;
44
46
  }
47
+ export function recommendHookTimeouts(ir, baseTimeout = 10) {
48
+ const timeouts = {};
49
+ const hasStopCommandVerifiers = ir.verifier_specs?.some((spec) => (spec.when === "stop" || spec.when === "pre_handoff" || spec.when === "post_step") &&
50
+ ((spec.kind === "checkpoint" && !!spec.checkpoint?.command) ||
51
+ (spec.kind === "completion" && !!spec.completion?.command_success) ||
52
+ spec.checkpoint?.assert === "build_passing" ||
53
+ spec.checkpoint?.assert === "lint_passing" ||
54
+ spec.checkpoint?.assert === "clean_working_tree" ||
55
+ spec.checkpoint?.assert === "no_test_regression")) ?? false;
56
+ if (hasStopCommandVerifiers) {
57
+ timeouts.stop = Math.max(baseTimeout, 45);
58
+ }
59
+ return timeouts;
60
+ }
45
61
  /**
46
62
  * Determine which hook events are needed based on IR content.
47
63
  */
@@ -51,10 +67,13 @@ export function detectEnabledEvents(ir) {
51
67
  const hasFilters = ir.tool_filters.length > 0;
52
68
  const hasRoleScope = ir.roles_scope_map && ir.roles_scope_map.length > 0;
53
69
  const hasCheckpoints = ir.step_checkpoints && ir.step_checkpoints.length > 0;
70
+ const hasStopVerifiers = ir.verifier_specs?.some((spec) => spec.when === "stop" || spec.when === "pre_handoff" || spec.when === "post_step") ?? false;
71
+ const hasPostToolVerifiers = ir.verifier_specs?.some((spec) => spec.when === "post_tool_use") ?? false;
72
+ const hasHandoffs = ir.workflows_ir?.some((wf) => wf.handoff_chain.length > 0) ?? false;
54
73
  if (hasGates || hasFilters || hasRoleScope || hasCheckpoints) {
55
74
  events.push("preToolUse");
56
75
  }
57
- if (ir.post_execution_validators.length > 0) {
76
+ if (ir.post_execution_validators.length > 0 || hasPostToolVerifiers) {
58
77
  events.push("postToolUse");
59
78
  }
60
79
  if (ir.prompt_directives.some(d => d.priority === "high")) {
@@ -69,7 +88,10 @@ export function detectEnabledEvents(ir) {
69
88
  if (hasGates || hasFilters) {
70
89
  events.push("notification");
71
90
  }
72
- return events;
91
+ if (hasStopVerifiers || hasCheckpoints || hasHandoffs) {
92
+ events.push("stop");
93
+ }
94
+ return [...new Set(events)];
73
95
  }
74
96
  // ── File Operations ────────────────────────────────────────
75
97
  /**
@@ -43,39 +43,65 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
43
43
  lines.push("");
44
44
  }
45
45
  // Required Context — context files that must be read before any work
46
- if (ir?.context_files) {
47
- const contextFiles = [];
46
+ const workflowKeys = [plan.workflow_key, plan.source_workflow, plan.name].filter((key, index, keys) => Boolean(key) && keys.indexOf(key) === index);
47
+ const requiredReadAssets = [];
48
+ if (ir?.legibility_assets) {
49
+ const assets = ir.legibility_assets;
50
+ for (const asset of assets.mandatory ?? []) {
51
+ if (asset.type === "required_read" && !requiredReadAssets.includes(asset.path))
52
+ requiredReadAssets.push(asset.path);
53
+ }
54
+ for (const workflowKey of workflowKeys) {
55
+ for (const asset of assets.per_workflow?.[workflowKey] ?? []) {
56
+ if (asset.type === "required_read" && !requiredReadAssets.includes(asset.path))
57
+ requiredReadAssets.push(asset.path);
58
+ }
59
+ }
60
+ for (const step of plan.steps) {
61
+ for (const asset of assets.per_role?.[step.role] ?? []) {
62
+ if (asset.type === "required_read" && !requiredReadAssets.includes(asset.path))
63
+ requiredReadAssets.push(asset.path);
64
+ }
65
+ }
66
+ }
67
+ const contextFiles = requiredReadAssets.length > 0 ? requiredReadAssets : (() => {
68
+ if (!ir?.context_files)
69
+ return [];
70
+ const files = [];
48
71
  if (ir.context_files.mandatory)
49
- contextFiles.push(...ir.context_files.mandatory);
50
- // per_workflow for this workflow
51
- if (ir.context_files.per_workflow?.[plan.source_workflow]) {
52
- contextFiles.push(...ir.context_files.per_workflow[plan.source_workflow]);
72
+ files.push(...ir.context_files.mandatory);
73
+ for (const workflowKey of workflowKeys) {
74
+ const workflowFiles = ir.context_files.per_workflow?.[workflowKey] ?? [];
75
+ for (const file of workflowFiles) {
76
+ if (!files.includes(file))
77
+ files.push(file);
78
+ }
53
79
  }
54
- // per_role for all roles used in this workflow
55
80
  if (ir.context_files.per_role) {
56
81
  for (const step of plan.steps) {
57
82
  const roleFiles = ir.context_files.per_role[step.role];
58
83
  if (roleFiles) {
59
84
  for (const f of roleFiles) {
60
- if (!contextFiles.includes(f))
61
- contextFiles.push(f);
85
+ if (!files.includes(f))
86
+ files.push(f);
62
87
  }
63
88
  }
64
89
  }
65
90
  }
66
- if (contextFiles.length > 0) {
67
- lines.push("<Required_Context>");
68
- lines.push("Your context is EMPTY at startup. You MUST read these files first:");
69
- lines.push("");
70
- for (let i = 0; i < contextFiles.length; i++) {
71
- lines.push(`${i + 1}. ${contextFiles[i]}`);
72
- }
73
- lines.push("");
74
- lines.push("Without reading these, you cannot perform your role.");
75
- lines.push("Hook will warn on first Edit/Bash/Write until these are read.");
76
- lines.push("</Required_Context>");
77
- lines.push("");
91
+ return files;
92
+ })();
93
+ if (contextFiles.length > 0) {
94
+ lines.push("<Required_Context>");
95
+ lines.push("Your context is EMPTY at startup. You MUST read these files first:");
96
+ lines.push("");
97
+ for (let i = 0; i < contextFiles.length; i++) {
98
+ lines.push(`${i + 1}. ${contextFiles[i]}`);
78
99
  }
100
+ lines.push("");
101
+ lines.push("Without reading these, you cannot perform your role.");
102
+ lines.push("Hook will warn on first Edit/Bash/Write until these are read.");
103
+ lines.push("</Required_Context>");
104
+ lines.push("");
79
105
  }
80
106
  // Steps — each step MUST be executed via Agent() tool call
81
107
  const usedRoles = new Set(plan.steps.map((s) => s.role));
@@ -153,9 +179,9 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
153
179
  // Execution Policy — force Agent dispatch
154
180
  lines.push("<Execution_Policy>");
155
181
  lines.push("- **CRITICAL**: Each step MUST be executed by spawning an Agent using the Agent tool with the specified subagent_type. DO NOT perform any step's work directly in the main session.");
156
- lines.push("- Use TodoWrite to track each step as pending/in_progress/completed.");
182
+ lines.push("- Use the task tracker to track each step only as pending/in_progress/completed; use deleted only for skipped dependent tasks.");
157
183
  lines.push("- After completing each step, immediately proceed to the next — do not stop, summarize, or wait for confirmation.");
158
- lines.push("- If a step fails, mark it as failed in TodoWrite, log the error, and continue to the next non-dependent step.");
184
+ lines.push("- If a step cannot complete, do not use a failed status. Keep the current task in_progress or record the blocker in the task description/output, delete skipped dependent tasks, and continue only to non-dependent steps.");
159
185
  lines.push("- Do not ask the user for permission between steps — the workflow is pre-approved.");
160
186
  lines.push("- Wait for each Agent to complete and read its output before proceeding to the next step.");
161
187
  lines.push("</Execution_Policy>");
@@ -83,6 +83,11 @@ export interface RoleDef {
83
83
  success_criteria?: string[];
84
84
  failure_modes?: string[];
85
85
  }
86
+ export interface VerifierCommandPolicy {
87
+ allow_commands?: string[];
88
+ allow_command_prefixes?: string[];
89
+ allow_builtin_asserts?: string[];
90
+ }
86
91
  /** Handoff artifact type — what kind of artifact is passed between steps */
87
92
  export type HandoffType = "file" | "directory" | "test_result" | "git_commit" | "summary" | "state";
88
93
  /** A single artifact consumed or produced by a workflow step */
@@ -218,6 +223,18 @@ export interface MCPServerDef {
218
223
  timeout?: number;
219
224
  optional?: boolean;
220
225
  }
226
+ export type LegibilityAssetType = "required_read" | "repo_map" | "manifest" | "artifact_index";
227
+ export interface LegibilityAsset {
228
+ type: LegibilityAssetType;
229
+ path: string;
230
+ description?: string;
231
+ tags?: string[];
232
+ }
233
+ export interface LegibilityAssetMap {
234
+ mandatory?: LegibilityAsset[];
235
+ per_role?: Record<string, LegibilityAsset[]>;
236
+ per_workflow?: Record<string, LegibilityAsset[]>;
237
+ }
221
238
  export interface IntentDNA {
222
239
  $schema?: string;
223
240
  version: string;
@@ -232,11 +249,13 @@ export interface IntentDNA {
232
249
  workflows?: Record<string, WorkflowDef>;
233
250
  variables?: Record<string, string | VariableDef>;
234
251
  mcp?: Record<string, MCPServerDef>;
252
+ legibility_assets?: LegibilityAssetMap;
235
253
  context_files?: {
236
254
  mandatory?: string[];
237
255
  per_role?: Record<string, string[]>;
238
256
  per_workflow?: Record<string, string[]>;
239
257
  };
258
+ verifier_policy?: VerifierCommandPolicy;
240
259
  epigenetic: {
241
260
  markers: EpigeneticMarker[];
242
261
  };
@@ -267,6 +286,22 @@ export interface PostExecutionValidator {
267
286
  threshold?: number;
268
287
  source_gene: string;
269
288
  }
289
+ export type VerifierWhen = "stop" | "post_tool_use" | "pre_handoff" | "post_step";
290
+ export type VerifierSeverity = "block" | "warn";
291
+ export type VerifierKind = "checkpoint" | "completion";
292
+ export interface VerifierSpec {
293
+ id: string;
294
+ when: VerifierWhen;
295
+ severity: VerifierSeverity;
296
+ kind: VerifierKind;
297
+ workflow_name?: string;
298
+ step_id?: string;
299
+ step_role?: string;
300
+ source_genes?: string[];
301
+ checkpoint?: StepCheckpoint;
302
+ completion?: CompletionCheck;
303
+ source?: "workflow_checkpoint" | "workflow_completion" | "role_post_check";
304
+ }
270
305
  export interface ContextInjection {
271
306
  type: "experience" | "preference" | "warning";
272
307
  text: string;
@@ -333,6 +368,7 @@ export interface ConstraintIR {
333
368
  tool_filters: ToolFilter[];
334
369
  pre_execution_gates: PreExecutionGate[];
335
370
  post_execution_validators: PostExecutionValidator[];
371
+ verifier_specs?: VerifierSpec[];
336
372
  context_injections: ContextInjection[];
337
373
  source_dna_ids: string[];
338
374
  compiled_at: string;
@@ -343,11 +379,13 @@ export interface ConstraintIR {
343
379
  roles_scope_map?: RoleScopeEntry[];
344
380
  step_checkpoints?: StepCheckpointIR[];
345
381
  workflows_ir?: WorkflowIR[];
382
+ legibility_assets?: LegibilityAssetMap;
346
383
  context_files?: {
347
384
  mandatory?: string[];
348
385
  per_role?: Record<string, string[]>;
349
386
  per_workflow?: Record<string, string[]>;
350
387
  };
388
+ verifier_policy?: VerifierCommandPolicy;
351
389
  }
352
390
  /** A compiled workflow step with resolved metadata */
353
391
  export interface WorkflowStep {
@@ -416,4 +454,5 @@ export interface WorkflowPlan {
416
454
  /** Compilation metadata */
417
455
  compiled_at: string;
418
456
  source_workflow: string;
457
+ workflow_key?: string;
419
458
  }