intentdna 1.6.5 → 1.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +26 -15
  4. package/dist/cli/commands/compile.js +2 -47
  5. package/dist/cli/commands/context.d.ts +8 -0
  6. package/dist/cli/commands/context.js +63 -0
  7. package/dist/cli/commands/feedback.d.ts +3 -2
  8. package/dist/cli/commands/feedback.js +16 -5
  9. package/dist/cli/commands/init.js +11 -63
  10. package/dist/cli/commands/run.js +5 -4
  11. package/dist/cli/commands/show.js +2 -38
  12. package/dist/cli/commands/sync.d.ts +9 -6
  13. package/dist/cli/commands/sync.js +191 -186
  14. package/dist/cli/commands/templates.d.ts +10 -1
  15. package/dist/cli/commands/templates.js +50 -1
  16. package/dist/cli/commands/validate.js +15 -9
  17. package/dist/cli/commands/verify.d.ts +32 -0
  18. package/dist/cli/commands/verify.js +270 -31
  19. package/dist/cli/index.js +78 -11
  20. package/dist/compiler/activate.js +11 -6
  21. package/dist/compiler/cascade.d.ts +5 -1
  22. package/dist/compiler/cascade.js +74 -1
  23. package/dist/compiler/compile.js +38 -0
  24. package/dist/compiler/diagnostics.d.ts +17 -0
  25. package/dist/compiler/diagnostics.js +30 -0
  26. package/dist/compiler/index.d.ts +7 -0
  27. package/dist/compiler/index.js +13 -13
  28. package/dist/compiler/input-resolver.d.ts +32 -0
  29. package/dist/compiler/input-resolver.js +281 -0
  30. package/dist/compiler/provenance.d.ts +8 -0
  31. package/dist/compiler/provenance.js +127 -0
  32. package/dist/governance/index.d.ts +4 -3
  33. package/dist/governance/index.js +3 -4
  34. package/dist/governance/runtime-decision-event.d.ts +85 -0
  35. package/dist/governance/runtime-decision-event.js +231 -0
  36. package/dist/governance/types.d.ts +3 -3
  37. package/dist/governance/types.js +3 -3
  38. package/dist/hooks/cli.d.ts +10 -1
  39. package/dist/hooks/cli.js +199 -35
  40. package/dist/hooks/state.d.ts +5 -10
  41. package/dist/hooks/state.js +170 -2
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.js +1 -0
  44. package/dist/mcp/index.js +2 -0
  45. package/dist/mcp/tools-compile.js +18 -49
  46. package/dist/mcp/tools-context.d.ts +2 -0
  47. package/dist/mcp/tools-context.js +85 -0
  48. package/dist/mcp/tools-enforce.d.ts +2 -2
  49. package/dist/mcp/tools-enforce.js +19 -49
  50. package/dist/mcp/tools-observability.js +26 -2
  51. package/dist/mcp/tools-state.d.ts +1 -1
  52. package/dist/mcp/tools-state.js +17 -7
  53. package/dist/report/kernel-report.d.ts +27 -0
  54. package/dist/report/kernel-report.js +60 -19
  55. package/dist/report/kernel-signals.d.ts +5 -2
  56. package/dist/report/kernel-signals.js +87 -2
  57. package/dist/report/report-package.d.ts +64 -0
  58. package/dist/report/report-package.js +90 -0
  59. package/dist/runtime/agent-md.d.ts +1 -0
  60. package/dist/runtime/agent-md.js +21 -3
  61. package/dist/runtime/claude-sdk.d.ts +9 -4
  62. package/dist/runtime/claude-sdk.js +9 -0
  63. package/dist/runtime/context-sources.d.ts +14 -0
  64. package/dist/runtime/context-sources.js +60 -0
  65. package/dist/runtime/plugin-adapter.d.ts +5 -1
  66. package/dist/runtime/plugin-adapter.js +2 -0
  67. package/dist/runtime/skill-adapter.d.ts +32 -4
  68. package/dist/runtime/skill-adapter.js +184 -9
  69. package/dist/runtime/workflow-runner.d.ts +1 -1
  70. package/dist/runtime/workflow-runner.js +1 -1
  71. package/dist/schema/types.d.ts +84 -0
  72. package/dist/schema/validate.js +156 -2
  73. package/dist/schema/validators/controllers.js +16 -0
  74. package/dist/signals/index.d.ts +10 -0
  75. package/dist/signals/index.js +90 -5
  76. package/dist/templates/catalog.d.ts +19 -0
  77. package/dist/templates/catalog.js +57 -0
  78. package/dist/templates/flutter-rewrite.dna.yaml +2 -2
  79. package/package.json +1 -1
  80. package/spec/README.md +1 -1
  81. package/spec/foundation-hardening.md +2 -1
  82. package/spec/schema-spec.md +78 -1
@@ -8,7 +8,7 @@
8
8
  * post_execution_validators → PostToolUse hooks
9
9
  * context_injections → UserPromptSubmit hook
10
10
  */
11
- import type { ConstraintIR } from "../schema/types.js";
11
+ import type { CompiledProvenanceManifest, ConstraintIR, ConstraintProvenance } from "../schema/types.js";
12
12
  export interface DNAHookOutput {
13
13
  hookSpecificOutput?: {
14
14
  hookEventName: string;
@@ -32,19 +32,24 @@ export interface ClaudeDNAConfig {
32
32
  source_dna_ids: string[];
33
33
  /** Compilation timestamp */
34
34
  compiled_at: string;
35
+ /** Stable compiled IR hash for runtime decision events */
36
+ compiled_ir_hash?: string;
37
+ /** Cascade/source provenance for SDK-side checks */
38
+ provenance?: CompiledProvenanceManifest;
35
39
  }
36
40
  export interface PreToolRule {
37
41
  condition: string;
38
42
  action: "block" | "escalate" | "warn";
39
43
  message: string;
40
44
  source_gene: string;
45
+ constraint_provenance?: ConstraintProvenance;
46
+ compiled_ir_hash?: string;
41
47
  }
42
48
  export interface PostToolRule {
43
49
  check: string;
44
50
  threshold?: number;
45
51
  source_gene: string;
52
+ constraint_provenance?: ConstraintProvenance;
53
+ compiled_ir_hash?: string;
46
54
  }
47
- /**
48
- * Compile Constraint IR to Claude Agent SDK configuration.
49
- */
50
55
  export declare function compileForClaude(ir: ConstraintIR): ClaudeDNAConfig;
@@ -12,6 +12,9 @@
12
12
  /**
13
13
  * Compile Constraint IR to Claude Agent SDK configuration.
14
14
  */
15
+ function provenanceForGene(ir, geneId) {
16
+ return ir.provenance?.constraint_winners.find((constraint) => constraint.gene_id === geneId);
17
+ }
15
18
  export function compileForClaude(ir) {
16
19
  // Build system prompt addition from directives
17
20
  const promptLines = [];
@@ -61,11 +64,15 @@ export function compileForClaude(ir) {
61
64
  action: g.action,
62
65
  message: g.message,
63
66
  source_gene: g.source_gene,
67
+ constraint_provenance: provenanceForGene(ir, g.source_gene),
68
+ compiled_ir_hash: ir.compiled_ir_hash,
64
69
  })),
65
70
  post_tool_rules: ir.post_execution_validators.map((v) => ({
66
71
  check: v.check,
67
72
  threshold: v.threshold,
68
73
  source_gene: v.source_gene,
74
+ constraint_provenance: provenanceForGene(ir, v.source_gene),
75
+ compiled_ir_hash: ir.compiled_ir_hash,
69
76
  })),
70
77
  context_additions: ir.context_injections.map((c) => c.text),
71
78
  deprioritized_tools: ir.tool_filters
@@ -73,5 +80,7 @@ export function compileForClaude(ir) {
73
80
  .map((f) => f.target),
74
81
  source_dna_ids: ir.source_dna_ids,
75
82
  compiled_at: ir.compiled_at,
83
+ compiled_ir_hash: ir.compiled_ir_hash,
84
+ provenance: ir.provenance,
76
85
  };
77
86
  }
@@ -0,0 +1,14 @@
1
+ import type { CompiledAdvisoryContextSource } from "../schema/types.js";
2
+ export interface ContextSourceDocument {
3
+ source: CompiledAdvisoryContextSource;
4
+ content?: string;
5
+ }
6
+ export interface ContextSourceIndex {
7
+ sources: CompiledAdvisoryContextSource[];
8
+ planning_instructions: string[];
9
+ }
10
+ export declare function loadContextSourceIndex(projectDir: string, files?: string[]): Promise<ContextSourceIndex>;
11
+ export declare function searchContextSources(index: ContextSourceIndex, query: string): CompiledAdvisoryContextSource[];
12
+ export declare function findContextSource(index: ContextSourceIndex, id: string): CompiledAdvisoryContextSource | undefined;
13
+ export declare function readContextSourceDocument(projectDir: string, source: CompiledAdvisoryContextSource): Promise<ContextSourceDocument>;
14
+ export declare function formatContextSource(source: CompiledAdvisoryContextSource): string;
@@ -0,0 +1,60 @@
1
+ import { readFile, realpath } from "node:fs/promises";
2
+ import { isAbsolute, relative, resolve } from "node:path";
3
+ import { compileFromFiles, detectDNAConfigs, expandDNAInputFiles } from "../compiler/index.js";
4
+ export async function loadContextSourceIndex(projectDir, files) {
5
+ const inputFiles = files && files.length > 0
6
+ ? files.map((file) => resolve(projectDir, file))
7
+ : await detectDNAConfigs(projectDir);
8
+ if (inputFiles.length === 0)
9
+ return { sources: [], planning_instructions: [] };
10
+ const expandedFiles = await expandDNAInputFiles(inputFiles);
11
+ const ir = await compileFromFiles(expandedFiles);
12
+ return {
13
+ sources: ir.context_sources ?? [],
14
+ planning_instructions: ir.planning_context?.instructions ?? [],
15
+ };
16
+ }
17
+ export function searchContextSources(index, query) {
18
+ const normalized = query.trim().toLowerCase();
19
+ if (!normalized)
20
+ return index.sources;
21
+ return index.sources.filter((source) => {
22
+ const haystack = [
23
+ source.id,
24
+ source.type,
25
+ source.path,
26
+ source.url,
27
+ source.title,
28
+ source.description,
29
+ ...(source.tags ?? []),
30
+ ].filter(Boolean).join("\n").toLowerCase();
31
+ return haystack.includes(normalized);
32
+ });
33
+ }
34
+ export function findContextSource(index, id) {
35
+ return index.sources.find((source) => source.id === id);
36
+ }
37
+ export async function readContextSourceDocument(projectDir, source) {
38
+ if (source.type !== "file" && source.type !== "doc") {
39
+ return { source };
40
+ }
41
+ if (!source.path)
42
+ return { source };
43
+ if (isAbsolute(source.path) || source.path.split(/[\\/]+/).includes("..")) {
44
+ throw new Error(`Context source '${source.id}' path must stay within the project`);
45
+ }
46
+ const projectRoot = await realpath(projectDir);
47
+ const filePath = await realpath(resolve(projectRoot, source.path));
48
+ const rel = relative(projectRoot, filePath);
49
+ if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
50
+ throw new Error(`Context source '${source.id}' path must stay within the project`);
51
+ }
52
+ const content = await readFile(filePath, "utf-8");
53
+ return { source, content };
54
+ }
55
+ export function formatContextSource(source) {
56
+ const locator = source.path ?? source.url ?? "(no locator)";
57
+ const title = source.title ? ` — ${source.title}` : "";
58
+ const tags = source.tags?.length ? ` [${source.tags.join(", ")}]` : "";
59
+ return `${source.id}: ${source.type} ${locator}${title}${tags}`;
60
+ }
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * This is the bridge between compile-time (dna sync) and runtime (hook execution).
8
8
  */
9
- import type { ConstraintIR, RoleDef } from "../schema/types.js";
9
+ import type { CompiledProvenanceManifest, ConstraintIR, RoleDef } from "../schema/types.js";
10
10
  /** Wrapper around ConstraintIR with versioning metadata */
11
11
  export interface CompiledIRFile {
12
12
  /** Schema version for forward compatibility */
@@ -19,6 +19,10 @@ export interface CompiledIRFile {
19
19
  ir: ConstraintIR;
20
20
  /** Cascaded role definitions (for output_schema enforcement) */
21
21
  roles?: Record<string, RoleDef>;
22
+ /** Stable hash of the compiled IR payload, excluding provenance/hash fields */
23
+ compiled_ir_hash?: string;
24
+ /** Cascade and source provenance used by enterprise evidence */
25
+ provenance?: CompiledProvenanceManifest;
22
26
  }
23
27
  /**
24
28
  * Create a CompiledIRFile wrapper around a ConstraintIR.
@@ -20,6 +20,8 @@ export function createCompiledIR(ir, roles) {
20
20
  source_dna_ids: ir.source_dna_ids,
21
21
  ir,
22
22
  roles: roles && Object.keys(roles).length > 0 ? roles : undefined,
23
+ compiled_ir_hash: ir.compiled_ir_hash,
24
+ provenance: ir.provenance,
23
25
  };
24
26
  }
25
27
  /**
@@ -7,17 +7,45 @@
7
7
  * Skills are compiled views of DNA's structured definitions — not stored content.
8
8
  */
9
9
  import type { WorkflowPlan, WorkflowDef, RoleDef, ConstraintIR, ControllerDef } from "../schema/types.js";
10
+ export interface SkillFile {
11
+ relativePath: string;
12
+ content: string;
13
+ }
14
+ export interface SkillMapEntry {
15
+ name: string;
16
+ dirName: string;
17
+ source: string;
18
+ kind: "workflow" | "controller";
19
+ bodyPath: string;
20
+ roles: string[];
21
+ consumes: Array<{
22
+ step_id: string;
23
+ type: string;
24
+ path?: string;
25
+ name?: string;
26
+ artifact_id?: string;
27
+ description: string;
28
+ }>;
29
+ produces: Array<{
30
+ step_id: string;
31
+ type: string;
32
+ path?: string;
33
+ name?: string;
34
+ artifact_id?: string;
35
+ description: string;
36
+ }>;
37
+ }
10
38
  export interface SkillResult {
11
39
  name: string;
12
40
  fileName: string;
13
41
  dirName: string;
14
42
  content: string;
43
+ launcherContent?: string;
44
+ bodyContent?: string;
45
+ bodyFileName?: string;
46
+ mapEntry?: SkillMapEntry;
15
47
  }
16
48
  export declare function compileControllerToSkill(controllerKey: string, controller: ControllerDef, variables?: Record<string, string>, workflows?: Record<string, WorkflowDef>): SkillResult;
17
- /**
18
- * Compile a WorkflowPlan + roles + IR into a SKILL.md file.
19
- * Variables from DNA config are substituted into prompts and descriptions.
20
- */
21
49
  export declare function compileWorkflowToSkill(plan: WorkflowPlan, roles: Record<string, RoleDef>, ir?: ConstraintIR, variables?: Record<string, string>): SkillResult;
22
50
  export declare function writeSkillFiles(results: SkillResult[], outputDir: string): Promise<string[]>;
23
51
  export declare function removeSkillFiles(outputDir: string): Promise<number>;
@@ -7,10 +7,18 @@
7
7
  * Skills are compiled views of DNA's structured definitions — not stored content.
8
8
  */
9
9
  import { mkdir, writeFile, readdir, readFile, rm } from "node:fs/promises";
10
- import { join } from "node:path";
10
+ import { join, resolve, sep } from "node:path";
11
11
  import { getControllerKindMetadata } from "../schema/controller-registry.js";
12
- import { toKebabCase } from "./agent-md.js";
12
+ import { assertSafeGeneratedName, toKebabCase } from "./agent-md.js";
13
13
  const SENTINEL = "<!-- intentdna:managed — do not edit manually -->";
14
+ function resolveContainedPath(baseDir, ...segments) {
15
+ const base = resolve(baseDir);
16
+ const target = resolve(baseDir, ...segments);
17
+ if (target !== base && !target.startsWith(`${base}${sep}`)) {
18
+ throw new Error(`generated path escapes output directory: ${target}`);
19
+ }
20
+ return target;
21
+ }
14
22
  // ── Compile Controller → Skill ─────────────────────────────
15
23
  export function compileControllerToSkill(controllerKey, controller, variables, workflows) {
16
24
  const kindMetadata = getControllerKindMetadata(controller.kind);
@@ -18,6 +26,7 @@ export function compileControllerToSkill(controllerKey, controller, variables, w
18
26
  throw new Error(`Unsupported controller kind: ${controller.kind}`);
19
27
  }
20
28
  const skillName = `dna-${toKebabCase(controllerKey)}`;
29
+ assertSafeGeneratedName(skillName, "skill name");
21
30
  const analyzerAgent = `dna-${toKebabCase(controller.roles.analyzer)}`;
22
31
  const analysisReviewerAgent = `dna-${toKebabCase(controller.roles.analysis_reviewer)}`;
23
32
  const surgeonAgent = `dna-${toKebabCase(controller.roles.surgeon)}`;
@@ -136,6 +145,7 @@ export function compileControllerToSkill(controllerKey, controller, variables, w
136
145
  lines.push("</Verdict_Mapping>");
137
146
  lines.push("");
138
147
  lines.push("<Stop_Report>");
148
+ lines.push("Every critical decision in the stop report must cite at least one verifier result_id, trace_id, or ArtifactManifest artifact id; prose-only claims are not sufficient.");
139
149
  for (const line of policy.stop_report) {
140
150
  lines.push(line);
141
151
  }
@@ -155,7 +165,28 @@ export function compileControllerToSkill(controllerKey, controller, variables, w
155
165
  }
156
166
  const dirName = skillName;
157
167
  const fileName = join(dirName, "SKILL.md");
158
- return { name: skillName, fileName, dirName, content };
168
+ return attachSkillBody({ name: skillName, fileName, dirName, content }, {
169
+ description: controller.description || controller.name,
170
+ triggers: [toKebabCase(controllerKey), `run ${toKebabCase(controllerKey)}`],
171
+ kind: "controller",
172
+ mapEntry: {
173
+ name: skillName,
174
+ dirName,
175
+ source: controllerKey,
176
+ kind: "controller",
177
+ bodyPath: join(dirName, "skill-bodies", "body.md"),
178
+ roles: Object.values(controller.roles),
179
+ consumes: [
180
+ { step_id: "controller", type: "file", path: controller.diagnosis_artifact_path, description: "Diagnosis artifact" },
181
+ { step_id: "controller", type: "file", path: controller.diagnosis_review_artifact_path, description: "Diagnosis review artifact" },
182
+ { step_id: "controller", type: "file", path: controller.review_artifact_path, description: "Fix review artifact" },
183
+ { step_id: "controller", type: "file", path: controller.progress_artifact_path, description: "Progress JSON artifact" },
184
+ ],
185
+ produces: [
186
+ { step_id: "controller", type: "file", path: controller.progress_artifact_path, description: "Progress JSON artifact" },
187
+ ],
188
+ },
189
+ });
159
190
  }
160
191
  function requireStepByRole(workflow, role, workflowName) {
161
192
  const step = workflow?.steps.find((candidate) => candidate.role === role);
@@ -164,6 +195,73 @@ function requireStepByRole(workflow, role, workflowName) {
164
195
  }
165
196
  return step;
166
197
  }
198
+ function createSkillLauncher(params) {
199
+ const lines = [];
200
+ lines.push("---");
201
+ lines.push(`name: ${params.skillName}`);
202
+ lines.push(`description: "Use when user says /${params.skillName}. ${escapeYaml(params.description)}"`);
203
+ lines.push("user-invocable: true");
204
+ lines.push("triggers:");
205
+ for (const trigger of params.triggers)
206
+ lines.push(` - "${escapeYaml(trigger)}"`);
207
+ lines.push("---");
208
+ lines.push("");
209
+ lines.push(SENTINEL);
210
+ lines.push(`<!-- Compiled: ${new Date().toISOString()} -->`);
211
+ lines.push("");
212
+ lines.push(`# /${params.skillName}`);
213
+ lines.push("");
214
+ lines.push("<Purpose>");
215
+ lines.push(params.description);
216
+ lines.push("</Purpose>");
217
+ lines.push("");
218
+ lines.push("<Skill_Body>");
219
+ lines.push(`Full ${params.kind} instructions live in \`skill-bodies/${params.bodyFileName}\`.`);
220
+ lines.push("Read that body before executing any step; the launcher is only an entrypoint.");
221
+ lines.push("</Skill_Body>");
222
+ lines.push("");
223
+ if (params.bodyContent.includes("<Required_Context>")) {
224
+ lines.push("<Required_Context>");
225
+ lines.push("The skill body contains required context files. Read them before any Edit, Write, or Bash action.");
226
+ lines.push("</Required_Context>");
227
+ lines.push("");
228
+ }
229
+ if (params.bodyContent.includes("<Advisory_Context>")) {
230
+ lines.push("<Advisory_Context>");
231
+ lines.push("The skill body contains planning-only advisory context sources. Quote and cite them when used; they never satisfy handoff or enforcement checks.");
232
+ lines.push("</Advisory_Context>");
233
+ lines.push("");
234
+ }
235
+ if (params.bodyContent.includes("<Handoff>")) {
236
+ lines.push("<Handoff>");
237
+ lines.push("Write human-readable handoff notes when requested, but do not treat handoff.md as a machine fact.");
238
+ lines.push("ArtifactManifest entries written by runtime hooks remain the machine source of truth for handoff satisfaction.");
239
+ lines.push("</Handoff>");
240
+ lines.push("");
241
+ }
242
+ lines.push("<Execution>");
243
+ lines.push(`Follow \`skill-bodies/${params.bodyFileName}\` exactly.`);
244
+ lines.push("</Execution>");
245
+ return lines.join("\n") + "\n";
246
+ }
247
+ function attachSkillBody(result, params) {
248
+ const bodyFileName = "body.md";
249
+ const launcherContent = createSkillLauncher({
250
+ skillName: result.name,
251
+ description: params.description,
252
+ triggers: params.triggers,
253
+ bodyFileName,
254
+ bodyContent: result.content,
255
+ kind: params.kind,
256
+ });
257
+ return {
258
+ ...result,
259
+ launcherContent,
260
+ bodyContent: result.content,
261
+ bodyFileName,
262
+ mapEntry: params.mapEntry,
263
+ };
264
+ }
167
265
  function normalizeControllerPolicy(controller) {
168
266
  const defaults = defaultControllerPolicy(controller);
169
267
  const policy = {
@@ -234,7 +332,7 @@ function defaultControllerPolicy(controller) {
234
332
  "- MAX_ROUNDS: stop and report max rounds reached",
235
333
  ],
236
334
  stop_report: [
237
- "On REQUEST_CHANGES, BLOCKED, or MAX_ROUNDS, explain the stop reason, the evidence, and the next action.",
335
+ "On REQUEST_CHANGES, BLOCKED, or MAX_ROUNDS, explain the stop reason, the evidence, the next action, and cite at least one verifier result_id, trace_id, or ArtifactManifest artifact id.",
238
336
  ],
239
337
  constraints: [
240
338
  `The controller must schedule only from ${controller.progress_artifact_path}.`,
@@ -247,9 +345,46 @@ function defaultControllerPolicy(controller) {
247
345
  * Compile a WorkflowPlan + roles + IR into a SKILL.md file.
248
346
  * Variables from DNA config are substituted into prompts and descriptions.
249
347
  */
348
+ function workflowSkillMapEntry(skillName, dirName, plan) {
349
+ const consumes = [];
350
+ const produces = [];
351
+ for (const step of plan.steps) {
352
+ for (const artifact of step.handoff?.consumes ?? []) {
353
+ consumes.push({
354
+ step_id: step.id,
355
+ type: artifact.type,
356
+ path: artifact.path,
357
+ name: artifact.name,
358
+ artifact_id: artifact.artifact_id,
359
+ description: artifact.description,
360
+ });
361
+ }
362
+ for (const artifact of step.handoff?.produces ?? []) {
363
+ produces.push({
364
+ step_id: step.id,
365
+ type: artifact.type,
366
+ path: artifact.path,
367
+ name: artifact.name,
368
+ artifact_id: artifact.artifact_id,
369
+ description: artifact.description,
370
+ });
371
+ }
372
+ }
373
+ return {
374
+ name: skillName,
375
+ dirName,
376
+ source: plan.workflow_key ?? plan.source_workflow,
377
+ kind: "workflow",
378
+ bodyPath: join(dirName, "skill-bodies", "body.md"),
379
+ roles: [...new Set(plan.steps.map((step) => step.role))],
380
+ consumes,
381
+ produces,
382
+ };
383
+ }
250
384
  export function compileWorkflowToSkill(plan, roles, ir, variables) {
251
385
  const lines = [];
252
386
  const skillName = `dna-${toKebabCase(plan.name)}`;
387
+ assertSafeGeneratedName(skillName, "skill name");
253
388
  // Frontmatter
254
389
  lines.push("---");
255
390
  lines.push(`name: ${skillName}`);
@@ -341,6 +476,22 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
341
476
  lines.push("</Required_Context>");
342
477
  lines.push("");
343
478
  }
479
+ if (ir?.context_sources?.length) {
480
+ lines.push("<Advisory_Context>");
481
+ lines.push("These sources are planning-only references. They do not satisfy ArtifactManifest, handoff consumes, completion checks, or enforcement facts.");
482
+ lines.push("If you use them, quote the relevant passage and cite the source id.");
483
+ lines.push("");
484
+ for (const source of ir.context_sources) {
485
+ const locator = source.path ?? source.url ?? "(no locator)";
486
+ const title = source.title ? ` — ${source.title}` : "";
487
+ lines.push(`- ${source.id}: ${source.type} ${locator}${title}`);
488
+ }
489
+ for (const instruction of ir.planning_context?.instructions ?? []) {
490
+ lines.push(`- Planning instruction: ${instruction}`);
491
+ }
492
+ lines.push("</Advisory_Context>");
493
+ lines.push("");
494
+ }
344
495
  // Steps — each step MUST be executed via Agent() tool call
345
496
  const usedRoles = new Set(plan.steps.map((s) => s.role));
346
497
  // Pre-compute handoff targets: steps that are pointed to by another step's handoff_to
@@ -489,6 +640,8 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
489
640
  const stepsWithHandoff = plan.steps.filter((s) => s.handoff && (s.handoff.consumes?.length || s.handoff.produces?.length));
490
641
  if (stepsWithHandoff.length > 0) {
491
642
  lines.push("<Handoff>");
643
+ lines.push("Human-readable handoff notes are useful for review, but they do not satisfy machine handoff gates.");
644
+ lines.push("ArtifactManifest records written by runtime hooks remain the machine source of truth for produced/consumed artifacts.");
492
645
  for (const step of stepsWithHandoff) {
493
646
  const h = step.handoff;
494
647
  if (h.produces && h.produces.length > 0) {
@@ -532,7 +685,12 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
532
685
  }
533
686
  const dirName = skillName;
534
687
  const fileName = join(dirName, "SKILL.md");
535
- return { name: skillName, fileName, dirName, content };
688
+ return attachSkillBody({ name: skillName, fileName, dirName, content }, {
689
+ description: plan.description || plan.name,
690
+ triggers: [plan.name, `run ${plan.name}`],
691
+ kind: "workflow",
692
+ mapEntry: workflowSkillMapEntry(skillName, dirName, plan),
693
+ });
536
694
  }
537
695
  /** Collect all {{var_name}} references from a workflow plan. */
538
696
  function collectVariableRefs(plan) {
@@ -555,12 +713,29 @@ function collectVariableRefs(plan) {
555
713
  // ── File Operations ────────────────────────────────────────
556
714
  export async function writeSkillFiles(results, outputDir) {
557
715
  const written = [];
716
+ const mapEntries = [];
558
717
  for (const result of results) {
559
- const dir = join(outputDir, result.dirName);
718
+ assertSafeGeneratedName(result.dirName, "skill directory");
719
+ const dir = resolveContainedPath(outputDir, result.dirName);
560
720
  await mkdir(dir, { recursive: true });
561
- const path = join(dir, "SKILL.md");
562
- await writeFile(path, result.content, "utf-8");
563
- written.push(path);
721
+ const skillPath = resolveContainedPath(dir, "SKILL.md");
722
+ await writeFile(skillPath, result.launcherContent ?? result.content, "utf-8");
723
+ written.push(skillPath);
724
+ if (result.bodyContent && result.bodyFileName) {
725
+ assertSafeGeneratedName(result.bodyFileName.replace(/\.md$/, ""), "skill body file");
726
+ const bodyDir = resolveContainedPath(dir, "skill-bodies");
727
+ await mkdir(bodyDir, { recursive: true });
728
+ const bodyPath = resolveContainedPath(bodyDir, result.bodyFileName);
729
+ await writeFile(bodyPath, result.bodyContent, "utf-8");
730
+ written.push(bodyPath);
731
+ }
732
+ if (result.mapEntry)
733
+ mapEntries.push(result.mapEntry);
734
+ }
735
+ if (mapEntries.length > 0) {
736
+ const mapPath = resolveContainedPath(outputDir, ".intentdna-skill-map.json");
737
+ await writeFile(mapPath, JSON.stringify({ generated_at: new Date().toISOString(), skills: mapEntries }, null, 2) + "\n", "utf-8");
738
+ written.push(mapPath);
564
739
  }
565
740
  return written;
566
741
  }
@@ -19,7 +19,7 @@ export interface WorkflowShellOptions {
19
19
  maxBudgetPerAgent?: number;
20
20
  /** Output format for agent results (default "json") */
21
21
  outputFormat?: "json" | "text";
22
- /** Permission mode (default "bypassPermissions") */
22
+ /** Permission mode (default "default") */
23
23
  permissionMode?: string;
24
24
  /** Log directory relative to project root (default ".") */
25
25
  logDir?: string;
@@ -466,7 +466,7 @@ export function compileWorkflowToShell(plan, options) {
466
466
  agentPrefix: options?.agentPrefix ?? "dna-",
467
467
  maxBudgetPerAgent: options?.maxBudgetPerAgent ?? 10,
468
468
  outputFormat: options?.outputFormat ?? "json",
469
- permissionMode: options?.permissionMode ?? "bypassPermissions",
469
+ permissionMode: options?.permissionMode ?? "default",
470
470
  logDir: options?.logDir ?? ".",
471
471
  pauseSupport: options?.pauseSupport ?? false,
472
472
  };
@@ -17,9 +17,16 @@ export interface RepelCodon {
17
17
  type: "repel";
18
18
  target: string;
19
19
  }
20
+ export interface ConstraintOrigin {
21
+ source_dna_id: string;
22
+ source_layer: DNAType;
23
+ cascade_priority: number;
24
+ protected_threshold?: boolean;
25
+ }
20
26
  export interface ThresholdCodon {
21
27
  type: "threshold";
22
28
  condition: string;
29
+ origin?: ConstraintOrigin;
23
30
  }
24
31
  export interface WeightCodon {
25
32
  type: "weight";
@@ -262,6 +269,47 @@ export interface CascadeConfig {
262
269
  inherits: string[];
263
270
  priority: number;
264
271
  }
272
+ export type ProvenanceCascadeLayerName = "species" | "enterprise" | "project" | "personal" | "role" | "context" | "task";
273
+ export type ProvenanceLayerStatus = "active" | "unknown" | "unsupported";
274
+ export type ProvenanceSourceTrust = "builtin" | "admin_shared" | "project_local" | "user_local" | "explicit_file" | "input";
275
+ export interface DNASourceProvenance {
276
+ source_id: string;
277
+ source_ref: string;
278
+ resolved_path: string;
279
+ fingerprint: string;
280
+ type: DNAType;
281
+ cascade_priority: number;
282
+ version: string;
283
+ trust: ProvenanceSourceTrust;
284
+ inherited_by?: string;
285
+ genes?: string[];
286
+ }
287
+ export interface CascadeLayerProvenance {
288
+ layer: ProvenanceCascadeLayerName;
289
+ source_id: string;
290
+ source_ref: string;
291
+ fingerprint: string;
292
+ status: ProvenanceLayerStatus;
293
+ sources?: string[];
294
+ }
295
+ export interface ConstraintProvenance {
296
+ constraint_id: string;
297
+ source_layer: ProvenanceCascadeLayerName | "unknown" | "unsupported";
298
+ source_dna_id: string;
299
+ gene_id: string;
300
+ codon_type: CodonType | "unknown";
301
+ action: "allow" | "warn" | "block" | "escalate" | "validate";
302
+ provenance_reason: string;
303
+ }
304
+ export interface CompiledProvenanceManifest {
305
+ schema_version: "intentdna.provenance.v1.7.2";
306
+ policy_bundle_id: string;
307
+ policy_bundle_version: string;
308
+ sources: DNASourceProvenance[];
309
+ cascade_layers: Record<ProvenanceCascadeLayerName, CascadeLayerProvenance>;
310
+ constraint_winners: ConstraintProvenance[];
311
+ compiled_ir_hash: string;
312
+ }
265
313
  export interface VariableDef {
266
314
  description: string;
267
315
  default: string;
@@ -295,6 +343,35 @@ export interface LegibilityAssetMap {
295
343
  per_role?: Record<string, LegibilityAsset[]>;
296
344
  per_workflow?: Record<string, LegibilityAsset[]>;
297
345
  }
346
+ export type AdvisoryContextSourceType = "file" | "url" | "doc" | "note";
347
+ export interface AdvisoryContextSource {
348
+ id: string;
349
+ type: AdvisoryContextSourceType;
350
+ path?: string;
351
+ url?: string;
352
+ title?: string;
353
+ description?: string;
354
+ tags?: string[];
355
+ }
356
+ export interface PlanningContext {
357
+ sources: string[];
358
+ instructions?: string[];
359
+ require_quotes?: boolean;
360
+ require_attribution?: boolean;
361
+ }
362
+ export interface CompiledAdvisoryContextSource extends AdvisoryContextSource {
363
+ usage: "planning_only";
364
+ non_enforcing: true;
365
+ requires_quotes: boolean;
366
+ requires_attribution: boolean;
367
+ }
368
+ export interface CompiledPlanningContext {
369
+ sources: string[];
370
+ instructions?: string[];
371
+ require_quotes: boolean;
372
+ require_attribution: boolean;
373
+ non_enforcing: true;
374
+ }
298
375
  export interface IntentDNA {
299
376
  $schema?: string;
300
377
  version: string;
@@ -311,6 +388,8 @@ export interface IntentDNA {
311
388
  variables?: Record<string, string | VariableDef>;
312
389
  mcp?: Record<string, MCPServerDef>;
313
390
  legibility_assets?: LegibilityAssetMap;
391
+ context_sources?: AdvisoryContextSource[];
392
+ planning_context?: PlanningContext;
314
393
  context_files?: {
315
394
  mandatory?: string[];
316
395
  per_role?: Record<string, string[]>;
@@ -341,6 +420,7 @@ export interface PreExecutionGate {
341
420
  action: "block" | "escalate" | "warn";
342
421
  message: string;
343
422
  source_gene: string;
423
+ origin?: ConstraintOrigin;
344
424
  }
345
425
  export interface PostExecutionValidator {
346
426
  check: string;
@@ -439,6 +519,8 @@ export interface ConstraintIR {
439
519
  post_execution_validators: PostExecutionValidator[];
440
520
  verifier_specs?: VerifierSpec[];
441
521
  context_injections: ContextInjection[];
522
+ context_sources?: CompiledAdvisoryContextSource[];
523
+ planning_context?: CompiledPlanningContext;
442
524
  source_dna_ids: string[];
443
525
  compiled_at: string;
444
526
  active_context?: string;
@@ -455,6 +537,8 @@ export interface ConstraintIR {
455
537
  per_workflow?: Record<string, string[]>;
456
538
  };
457
539
  verifier_policy?: VerifierCommandPolicy;
540
+ provenance?: CompiledProvenanceManifest;
541
+ compiled_ir_hash?: string;
458
542
  }
459
543
  /** A compiled workflow step with resolved metadata */
460
544
  export interface WorkflowStep {