intentdna 1.8.7 → 1.9.0-rc.2

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 (69) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/cli/commands/candidate-promotion.d.ts +15 -0
  4. package/dist/cli/commands/candidate-promotion.js +197 -0
  5. package/dist/cli/commands/run-lifecycle.js +1 -0
  6. package/dist/cli/commands/run.js +76 -42
  7. package/dist/cli/commands/setup.d.ts +10 -0
  8. package/dist/cli/commands/setup.js +129 -36
  9. package/dist/cli/commands/source-assets.d.ts +32 -0
  10. package/dist/cli/commands/source-assets.js +154 -0
  11. package/dist/cli/commands/sync.js +52 -12
  12. package/dist/cli/commands/templates.js +59 -37
  13. package/dist/cli/index.js +161 -5
  14. package/dist/compiler/cascade.js +1 -1
  15. package/dist/hooks/cli.d.ts +6 -0
  16. package/dist/hooks/cli.js +118 -10
  17. package/dist/hooks/enforcement-boundary.d.ts +1 -1
  18. package/dist/hooks/protocol.d.ts +1 -1
  19. package/dist/hooks/state.d.ts +5 -0
  20. package/dist/hooks/state.js +21 -6
  21. package/dist/mcp/tools-state.js +251 -15
  22. package/dist/runtime/claude-sync-target.d.ts +2 -0
  23. package/dist/runtime/claude-sync-target.js +1 -1
  24. package/dist/runtime/evaluator.d.ts +13 -0
  25. package/dist/runtime/evaluator.js +46 -0
  26. package/dist/runtime/index.d.ts +8 -4
  27. package/dist/runtime/index.js +4 -2
  28. package/dist/runtime/plugin-adapter.d.ts +14 -1
  29. package/dist/runtime/plugin-adapter.js +23 -9
  30. package/dist/runtime/run-contracts.d.ts +5 -3
  31. package/dist/runtime/run-contracts.js +11 -1
  32. package/dist/runtime/run-controller.d.ts +15 -1
  33. package/dist/runtime/run-controller.js +235 -94
  34. package/dist/runtime/skill-adapter.js +24 -53
  35. package/dist/runtime/verifier.js +101 -2
  36. package/dist/runtime/workflow-plan-adapter.d.ts +2 -1
  37. package/dist/runtime/workflow-plan-adapter.js +78 -11
  38. package/dist/runtime/workflow-runtime-manifest.d.ts +48 -0
  39. package/dist/runtime/workflow-runtime-manifest.js +196 -0
  40. package/dist/runtime/workflow-variable-scopes.d.ts +12 -0
  41. package/dist/runtime/workflow-variable-scopes.js +55 -0
  42. package/dist/schema/types.d.ts +9 -0
  43. package/dist/schema/validate.js +29 -1
  44. package/dist/sources/authoring-packet.d.ts +8 -0
  45. package/dist/sources/authoring-packet.js +85 -0
  46. package/dist/sources/discovery.d.ts +7 -0
  47. package/dist/sources/discovery.js +150 -0
  48. package/dist/sources/license.d.ts +5 -0
  49. package/dist/sources/license.js +94 -0
  50. package/dist/sources/manifest.d.ts +11 -0
  51. package/dist/sources/manifest.js +84 -0
  52. package/dist/sources/skill-markdown.d.ts +6 -0
  53. package/dist/sources/skill-markdown.js +174 -0
  54. package/dist/sources/types.d.ts +125 -0
  55. package/dist/sources/types.js +1 -0
  56. package/dist/templates/asset-refinery.dna.yaml +192 -0
  57. package/dist/templates/flutter-behavior-lock.dna.yaml +17 -0
  58. package/dist/templates/flutter-refactoring-rescue.dna.yaml +11 -0
  59. package/dist/templates/flutter-rewrite.dna.yaml +154 -28
  60. package/dist/templates/metadata.js +2 -0
  61. package/dist/templates/multi-agent-handoff-coordination.dna.yaml +102 -37
  62. package/dist/templates/persistent-executor.dna.yaml +61 -3
  63. package/dist/templates/research-improvement.dna.yaml +295 -0
  64. package/dist/templates/subagent-parallel.dna.yaml +90 -17
  65. package/dist/templates/workflow-evidence-review.dna.yaml +8 -0
  66. package/dist/workflow/candidate-projection.d.ts +33 -0
  67. package/dist/workflow/candidate-projection.js +70 -0
  68. package/package.json +3 -1
  69. package/scripts/sync-release-plugin.cjs +87 -0
package/dist/hooks/cli.js CHANGED
@@ -24,7 +24,7 @@ import { readStdinResult, writeOutput, silentOutput, allowOutput, blockOutput, s
24
24
  import { validateHookInput } from "./schema.js";
25
25
  import { hookFailureOutput } from "./enforcement-boundary.js";
26
26
  import { enforcePreToolUse, enforcePostToolUse, enforceUserPromptSubmit, enforceSubagentStop, enforcePreCompact, enforceNotification, enforceSessionStart, enforceStop, extractBashWritePaths, checkReflectionLimit, checkContextReadiness, checkWorkflowBoundary, } from "./enforce.js";
27
- import { appendAudit, readWorkflowState, appendTrace, appendRuntimeDecisionEvent, appendEvidenceCaptureEvent, rotateTraces, readTraces, cleanStaleState, readSurgeonAttempts, updateSurgeonAttempts, appendSessionRead, readSessionReads, appendVerifierResult, readVerifierResults, appendCompletedArtifact, artifactIdentity, buildArtifactKey, readArtifactManifest, resolveArtifactTemplate, safePathComponent, writeArtifactManifest, EVIDENCE_CAPTURE_SCHEMA_VERSION } from "./state.js";
27
+ import { appendAudit, readWorkflowState, appendTrace, appendRuntimeDecisionEvent, appendEvidenceCaptureEvent, rotateTraces, readTraces, cleanStaleState, readSurgeonAttempts, updateSurgeonAttempts, appendSessionRead, readSessionReads, appendVerifierResult, readVerifierResults, appendCompletedArtifact, artifactIdentity, buildArtifactKey, readArtifactManifest, resolveArtifactTemplate, safePathComponent, workflowTemplateVariables, writeArtifactManifest, EVIDENCE_CAPTURE_SCHEMA_VERSION } from "./state.js";
28
28
  import { hookEventsForSurface } from "./event-registry.js";
29
29
  import { writeAuditEvent } from "../audit/index.js";
30
30
  import { RUNTIME_DECISION_EVENT_SCHEMA_VERSION, trustedEvidenceCaptureAttribution } from "../governance/index.js";
@@ -33,6 +33,90 @@ import { runCheckpointVerifier as runSharedCheckpointVerifier, runCompletionVeri
33
33
  // ── Constants ──────────────────────────────────────────────
34
34
  const DEFAULT_IR_PATH = ".dna/compiled/ir.json";
35
35
  const VALID_EVENTS = new Set(hookEventsForSurface("cli"));
36
+ function workflowRuntimeRevisionMismatch(ir, state) {
37
+ if (!state?.active || state.resolved_variables_source !== "compiled_manifest")
38
+ return null;
39
+ const pinnedHash = state.workflow_runtime_compiled_ir_hash;
40
+ if (!pinnedHash)
41
+ return "Active workflow is missing its pinned compiled IR hash; restart it after running dna sync.";
42
+ if (!ir.compiled_ir_hash)
43
+ return "Current compiled IR is missing its hash; run dna sync and restart the active workflow.";
44
+ if (pinnedHash !== ir.compiled_ir_hash) {
45
+ return "Compiled IR changed during the active workflow; restart it before executing more tools.";
46
+ }
47
+ return null;
48
+ }
49
+ function isTerminalWorkflowDeactivation(event, rawInput, state) {
50
+ if (event !== "PreToolUse" || !state?.active)
51
+ return false;
52
+ const toolName = typeof rawInput.tool_name === "string" ? rawInput.tool_name : "";
53
+ const toolInput = rawInput.tool_input;
54
+ if (!toolName.endsWith("dna_workflow_write") || !toolInput || typeof toolInput !== "object")
55
+ return false;
56
+ const args = toolInput;
57
+ return args.active === false && args.workflow === state.workflow;
58
+ }
59
+ async function hookProjectBoundaryExists(projectDir) {
60
+ for (const marker of [".dna", ".git"]) {
61
+ try {
62
+ await stat(resolve(projectDir, marker));
63
+ return true;
64
+ }
65
+ catch (error) {
66
+ const code = error instanceof Error && "code" in error
67
+ ? error.code
68
+ : undefined;
69
+ if (code !== "ENOENT" && code !== "ENOTDIR")
70
+ return true;
71
+ }
72
+ }
73
+ return false;
74
+ }
75
+ async function resolveHookRuntimePaths(cwd, irPath = DEFAULT_IR_PATH, explicitIRPath = false) {
76
+ const initialDir = resolve(cwd);
77
+ if (explicitIRPath) {
78
+ return {
79
+ projectDir: initialDir,
80
+ irPath: resolve(initialDir, irPath),
81
+ };
82
+ }
83
+ let candidateDir = initialDir;
84
+ while (true) {
85
+ const candidateIRPath = resolve(candidateDir, DEFAULT_IR_PATH);
86
+ try {
87
+ await stat(candidateIRPath);
88
+ return {
89
+ projectDir: candidateDir,
90
+ irPath: candidateIRPath,
91
+ };
92
+ }
93
+ catch (error) {
94
+ const code = error instanceof Error && "code" in error
95
+ ? error.code
96
+ : undefined;
97
+ if (code !== "ENOENT" && code !== "ENOTDIR") {
98
+ return {
99
+ projectDir: candidateDir,
100
+ irPath: candidateIRPath,
101
+ };
102
+ }
103
+ }
104
+ if (await hookProjectBoundaryExists(candidateDir)) {
105
+ return {
106
+ projectDir: candidateDir,
107
+ irPath: candidateIRPath,
108
+ };
109
+ }
110
+ const parentDir = dirname(candidateDir);
111
+ if (parentDir === candidateDir) {
112
+ return {
113
+ projectDir: initialDir,
114
+ irPath: resolve(initialDir, DEFAULT_IR_PATH),
115
+ };
116
+ }
117
+ candidateDir = parentDir;
118
+ }
119
+ }
36
120
  // ── Runtime Decision Evidence ────────────────────────────
37
121
  function normalizeDecision(output) {
38
122
  if (output.continue === false)
@@ -150,7 +234,12 @@ function buildRuntimeDecisionEvent(params) {
150
234
  tool_name: typeof params.rawInput.tool_name === "string" ? params.rawInput.tool_name : "unknown",
151
235
  action_kind: actionKind(params.rawInput),
152
236
  resource_ref: resourceRef(params.rawInput),
153
- evidence_refs: [{ type: "trace", ref: params.traceId }],
237
+ evidence_refs: [
238
+ { type: "trace", ref: params.traceId },
239
+ ...(params.wfState?.workflow_runtime_manifest_hash
240
+ ? [{ type: "artifact", ref: params.wfState.workflow_runtime_manifest_hash }]
241
+ : []),
242
+ ],
154
243
  };
155
244
  }
156
245
  function captureResultForHook(rawInput, decision) {
@@ -277,9 +366,11 @@ async function main() {
277
366
  return;
278
367
  }
279
368
  let irPath = DEFAULT_IR_PATH;
369
+ let explicitIRPath = false;
280
370
  const irFlagIdx = args.indexOf("--ir");
281
371
  if (irFlagIdx !== -1 && args[irFlagIdx + 1]) {
282
372
  irPath = args[irFlagIdx + 1];
373
+ explicitIRPath = true;
283
374
  }
284
375
  // Read stdin
285
376
  const stdin = await readStdinResult(5000);
@@ -297,10 +388,12 @@ async function main() {
297
388
  }
298
389
  const rawInput = validation.normalized;
299
390
  // Resolve project directory from input or cwd
300
- const projectDir = typeof rawInput.cwd === "string" ? rawInput.cwd : process.cwd();
391
+ const cwd = typeof rawInput.cwd === "string" ? rawInput.cwd : process.cwd();
392
+ const runtimePaths = await resolveHookRuntimePaths(cwd, irPath, explicitIRPath);
393
+ const { projectDir } = runtimePaths;
301
394
  const sessionId = typeof rawInput.sessionId === "string" ? rawInput.sessionId : undefined;
302
395
  // Load compiled IR
303
- const resolvedIRPath = resolve(projectDir, irPath);
396
+ const resolvedIRPath = runtimePaths.irPath;
304
397
  const ir = await loadIR(resolvedIRPath);
305
398
  if (!ir) {
306
399
  writeOutput(hookFailureOutput(event, "missing_ir", `Compiled IR is missing, unreadable, invalid, or version-incompatible at ${resolvedIRPath}.`));
@@ -361,6 +454,12 @@ export async function runHookEvent(options) {
361
454
  return finish(workflowState.output);
362
455
  }
363
456
  wfStateRaw = workflowState.state;
457
+ const revisionMismatch = workflowRuntimeRevisionMismatch(ir, wfStateRaw);
458
+ if (revisionMismatch && !isTerminalWorkflowDeactivation(event, rawInput, wfStateRaw)) {
459
+ const mismatch = hookFailureOutput(event, "runtime_revision_mismatch", revisionMismatch);
460
+ await writeRuntimeDecision(mismatch, { output: mismatch, trace: { matched_rule: "workflow_runtime_revision", step_id: wfStateRaw?.current_step } }, wfStateRaw);
461
+ return finish(mismatch);
462
+ }
364
463
  if (wfStateRaw?.active
365
464
  && typeof rawInput.agent_type === "string"
366
465
  && !subagentMatchesWorkflowRole(rawInput, wfStateRaw)) {
@@ -463,6 +562,12 @@ export async function runHookEvent(options) {
463
562
  return finish(workflowState.output);
464
563
  }
465
564
  const wfState = workflowState.state;
565
+ const revisionMismatch = workflowRuntimeRevisionMismatch(ir, wfState);
566
+ if (revisionMismatch) {
567
+ const mismatch = hookFailureOutput(event, "runtime_revision_mismatch", revisionMismatch);
568
+ await writeRuntimeDecision(mismatch, { output: mismatch, trace: { matched_rule: "workflow_runtime_revision", step_id: wfState?.current_step } }, wfState);
569
+ return finish(mismatch);
570
+ }
466
571
  let stopArtifactFacts = [];
467
572
  if (wfState?.active) {
468
573
  const artifactFacts = await finalizeAndResolveArtifactsForHook(projectDir, ir, wfState, event, sessionId);
@@ -759,11 +864,13 @@ async function runCodexHookCli(args) {
759
864
  return;
760
865
  }
761
866
  const rawInput = stdin.data;
762
- const projectDir = typeof rawInput.cwd === "string" ? rawInput.cwd : process.cwd();
763
- const irPath = argValue(args, "--ir") ?? DEFAULT_IR_PATH;
764
- const ir = await loadIR(resolve(projectDir, irPath));
867
+ const cwd = typeof rawInput.cwd === "string" ? rawInput.cwd : process.cwd();
868
+ const explicitIRPath = argValue(args, "--ir");
869
+ const runtimePaths = await resolveHookRuntimePaths(cwd, explicitIRPath ?? DEFAULT_IR_PATH, explicitIRPath !== undefined);
870
+ const { projectDir, irPath } = runtimePaths;
871
+ const ir = await loadIR(irPath);
765
872
  if (!ir) {
766
- writeOutput(hookFailureOutput(policyEvent, "missing_ir", `Compiled IR is missing, unreadable, invalid, or version-incompatible at ${resolve(projectDir, irPath)}.`));
873
+ writeOutput(hookFailureOutput(policyEvent, "missing_ir", `Compiled IR is missing, unreadable, invalid, or version-incompatible at ${irPath}.`));
767
874
  return;
768
875
  }
769
876
  const result = await runCodexHookWithLoadedIr(rawInput, projectDir, args, ir);
@@ -1014,6 +1121,7 @@ export const recordProducedArtifactsForTest = recordProducedArtifacts;
1014
1121
  export const resolveWorkflowArtifactFactsForTest = resolveWorkflowArtifactFacts;
1015
1122
  export const readWorkflowStateForHookTest = readWorkflowStateForHook;
1016
1123
  export const runCodexHookWithLoadedIrForTest = runCodexHookWithLoadedIr;
1124
+ export const resolveHookRuntimePathsForTest = resolveHookRuntimePaths;
1017
1125
  export const inferWorkflowAssetFromIrForTest = inferWorkflowAssetFromIr;
1018
1126
  export const resolveWorkflowArtifactFactsForHookTest = resolveWorkflowArtifactFactsForHook;
1019
1127
  export const finalizeAndResolveArtifactsForHookTest = finalizeAndResolveArtifactsForHook;
@@ -1137,7 +1245,7 @@ async function runVerifierSpec(projectDir, ir, spec, workflowState, sessionId, o
1137
1245
  if (spec.kind === "checkpoint" && spec.checkpoint) {
1138
1246
  const checkpointResult = await runSharedCheckpointVerifier(spec.checkpoint, ir, {
1139
1247
  projectDir,
1140
- variables: { ...(workflowState.resolved_variables ?? {}), ...(workflowState.inputs ?? {}) },
1248
+ variables: workflowTemplateVariables(workflowState),
1141
1249
  commandTimeoutMs: options?.commandTimeoutMs,
1142
1250
  });
1143
1251
  passed = checkpointResult.passed;
@@ -1157,7 +1265,7 @@ async function runVerifierSpec(projectDir, ir, spec, workflowState, sessionId, o
1157
1265
  else {
1158
1266
  const completionResult = await runCompletionVerifier(completion, ir, {
1159
1267
  projectDir,
1160
- variables: { ...(workflowState.resolved_variables ?? {}), ...(workflowState.inputs ?? {}) },
1268
+ variables: workflowTemplateVariables(workflowState),
1161
1269
  commandTimeoutMs: options?.commandTimeoutMs,
1162
1270
  });
1163
1271
  passed = completionResult.passed;
@@ -1,6 +1,6 @@
1
1
  import type { HookEvent } from "./event-registry.js";
2
2
  import { type HookOutput } from "./protocol.js";
3
- export type HookFailureKind = "malformed_input" | "missing_ir" | "role_mismatch" | "gate_evaluation" | "bash_write_scope";
3
+ export type HookFailureKind = "malformed_input" | "missing_ir" | "runtime_revision_mismatch" | "role_mismatch" | "gate_evaluation" | "bash_write_scope";
4
4
  export type HookFailureDisposition = "advisory" | "blocking";
5
5
  /**
6
6
  * Foundation Hook failure policy.
@@ -59,7 +59,7 @@ export interface HookOutput {
59
59
  permissionDecision?: "ask" | "allow" | "deny";
60
60
  };
61
61
  }
62
- export type MatchedRule = "scope" | "filter" | "gate" | "schema" | "validator" | "handoff" | "step_rule" | "output_schema" | "context_gate" | "reflection_gate" | "workflow_boundary";
62
+ export type MatchedRule = "scope" | "filter" | "gate" | "schema" | "validator" | "handoff" | "step_rule" | "output_schema" | "context_gate" | "reflection_gate" | "workflow_runtime_revision" | "workflow_boundary";
63
63
  export interface TraceMetadata {
64
64
  matched_rule: MatchedRule;
65
65
  gene_id?: string;
@@ -30,6 +30,10 @@ export interface DNAWorkflowState {
30
30
  started_at: string;
31
31
  inputs?: Record<string, string>;
32
32
  resolved_variables?: Record<string, string>;
33
+ resolved_variables_source?: "compiled_manifest" | "legacy_skill";
34
+ workflow_runtime_manifest_hash?: string;
35
+ workflow_runtime_compiled_ir_hash?: string;
36
+ workflow_inputs_pinned?: boolean;
33
37
  completed_artifacts?: CompletedArtifactEntry[];
34
38
  }
35
39
  /** Audit log entry (one per line in JSON Lines format) */
@@ -117,6 +121,7 @@ export declare function buildArtifactKey(params: {
117
121
  }): ArtifactKey;
118
122
  export declare function artifactManifestPath(projectDir: string, key: ArtifactKey): string;
119
123
  export declare function resolveArtifactTemplate(template: string, state?: Pick<DNAWorkflowState, "inputs" | "resolved_variables">): string | null;
124
+ export declare function workflowTemplateVariables(state?: Pick<DNAWorkflowState, "inputs" | "resolved_variables">): Record<string, string>;
120
125
  export declare function writeArtifactManifest(projectDir: string, manifest: Omit<ArtifactManifest, "id" | "recorded_at" | "resolver_version">): Promise<ArtifactFact>;
121
126
  export declare function readArtifactManifest(projectDir: string, key: ArtifactKey): Promise<ArtifactFact | null>;
122
127
  /**
@@ -78,16 +78,19 @@ export function resolveArtifactTemplate(template, state) {
78
78
  const resolvedVariables = state?.resolved_variables ?? {};
79
79
  resolved = resolved.replace(/\{\{(\w+)\}\}/g, (_match, name) => {
80
80
  const inputValue = inputs[name];
81
- if (inputValue !== undefined) {
81
+ const resolvedValue = resolvedVariables[name];
82
+ if (USER_ARTIFACT_PATH_ARGUMENTS.has(name) && inputValue !== undefined) {
82
83
  return safeUserArtifactPathArgument(inputValue, name) ?? `{{${name}}}`;
83
84
  }
84
- const resolvedValue = resolvedVariables[name];
85
- if (resolvedValue === undefined)
86
- return `{{${name}}}`;
87
- if (USER_ARTIFACT_PATH_ARGUMENTS.has(name)) {
85
+ if (resolvedValue !== undefined) {
86
+ if (!USER_ARTIFACT_PATH_ARGUMENTS.has(name))
87
+ return resolvedValue;
88
88
  return safeUserArtifactPathArgument(resolvedValue, name) ?? `{{${name}}}`;
89
89
  }
90
- return resolvedValue;
90
+ if (inputValue !== undefined) {
91
+ return safeUserArtifactPathArgument(inputValue, name) ?? `{{${name}}}`;
92
+ }
93
+ return `{{${name}}}`;
91
94
  });
92
95
  const args = inputs.ARGUMENTS ?? inputs.arguments ?? inputs.task_id ?? resolvedVariables.ARGUMENTS ?? resolvedVariables.arguments ?? resolvedVariables.task_id;
93
96
  if (resolved.includes("$ARGUMENTS")) {
@@ -102,6 +105,18 @@ export function resolveArtifactTemplate(template, state) {
102
105
  return null;
103
106
  return resolved;
104
107
  }
108
+ export function workflowTemplateVariables(state) {
109
+ const variables = { ...(state?.inputs ?? {}) };
110
+ const argumentsValue = variables.ARGUMENTS ?? variables.arguments ?? variables.task_id;
111
+ for (const [name, value] of Object.entries(state?.resolved_variables ?? {})) {
112
+ if (!USER_ARTIFACT_PATH_ARGUMENTS.has(name) || variables[name] === undefined) {
113
+ variables[name] = argumentsValue === undefined
114
+ ? value
115
+ : value.replace(/(^|[^\\])\$ARGUMENTS/g, (_match, prefix) => `${prefix}${argumentsValue}`);
116
+ }
117
+ }
118
+ return variables;
119
+ }
105
120
  export async function writeArtifactManifest(projectDir, manifest) {
106
121
  const id = artifactHash(manifest.key);
107
122
  const full = {
@@ -10,12 +10,111 @@
10
10
  import { resolve } from "node:path";
11
11
  import { readWorkflowState, writeWorkflowState, readRuntimeDecisionEvents, readTraces, listSessions } from "../hooks/state.js";
12
12
  import { validateRuntimeDecisionEvent } from "../governance/index.js";
13
- import { loadCompiledIR } from "../runtime/plugin-adapter.js";
14
- import { textResult } from "./server.js";
13
+ import { loadCompiledIR, loadCompiledIRResult } from "../runtime/plugin-adapter.js";
14
+ import { validateWorkflowRuntimeManifest } from "../runtime/workflow-runtime-manifest.js";
15
+ import { errorResult, textResult } from "./server.js";
15
16
  function isStringRecord(value) {
16
- return typeof value === "object" && value !== null &&
17
+ return typeof value === "object" && value !== null && !Array.isArray(value) &&
17
18
  Object.values(value).every(v => typeof v === "string");
18
19
  }
20
+ function sameStringRecord(a, b) {
21
+ const aKeys = Object.keys(a).sort();
22
+ const bKeys = Object.keys(b).sort();
23
+ return aKeys.length === bKeys.length && aKeys.every((key, index) => key === bKeys[index] && a[key] === b[key]);
24
+ }
25
+ const RUNTIME_ARGUMENT_NAMES = ["ARGUMENTS", "arguments", "task_id"];
26
+ function compatibleManifestInputCandidate(binding, candidate) {
27
+ if (!candidate)
28
+ return { ignored: [] };
29
+ const allowed = new Set([...binding.runtime_input_refs, ...RUNTIME_ARGUMENT_NAMES]);
30
+ const entries = Object.entries(candidate);
31
+ return {
32
+ candidate: Object.fromEntries(entries.filter(([name]) => allowed.has(name))),
33
+ ignored: entries.filter(([name]) => !allowed.has(name)).map(([name]) => name).sort(),
34
+ };
35
+ }
36
+ async function lookupRuntimeBinding(projectDir, workflow) {
37
+ const result = await loadCompiledIRResult(resolve(projectDir, ".dna", "compiled", "ir.json"));
38
+ if (result.status === "missing") {
39
+ return { status: "error", kind: "invalid_ir", message: "No compiled IR found. Run `dna sync` before starting or continuing this workflow." };
40
+ }
41
+ if (result.status === "invalid") {
42
+ return { status: "error", kind: "invalid_ir", message: `Invalid compiled IR: ${result.reason}. Run \`dna sync\` before starting or continuing this workflow.` };
43
+ }
44
+ const compiled = result.compiled;
45
+ if (!Object.prototype.hasOwnProperty.call(compiled, "workflow_runtime")) {
46
+ return { status: "legacy", compiledIrHash: compiled.compiled_ir_hash };
47
+ }
48
+ if (typeof compiled.compiled_ir_hash !== "string" ||
49
+ typeof compiled.ir.compiled_ir_hash !== "string" ||
50
+ compiled.compiled_ir_hash !== compiled.ir.compiled_ir_hash) {
51
+ return {
52
+ status: "error",
53
+ kind: "invalid_ir",
54
+ message: "Invalid compiled IR: workflow runtime bindings require matching wrapper and IR hashes. Run `dna sync` before starting or continuing this workflow.",
55
+ };
56
+ }
57
+ const validation = validateWorkflowRuntimeManifest(compiled.workflow_runtime, compiled.compiled_ir_hash);
58
+ if (!validation.valid) {
59
+ return {
60
+ status: "error",
61
+ kind: "invalid_manifest",
62
+ compiledIrHash: compiled.compiled_ir_hash,
63
+ message: `Invalid compiled workflow runtime manifest: ${validation.reason}. Run \`dna sync\` before starting or continuing this workflow.`,
64
+ };
65
+ }
66
+ if (!Object.prototype.hasOwnProperty.call(validation.manifest.workflows, workflow)) {
67
+ return {
68
+ status: "error",
69
+ kind: "workflow_missing",
70
+ compiledIrHash: validation.manifest.compiled_ir_hash,
71
+ message: `Workflow ${workflow} is not present in the compiled workflow runtime manifest. Run \`dna sync\` to refresh generated runtime artifacts.`,
72
+ };
73
+ }
74
+ const binding = validation.manifest.workflows[workflow];
75
+ return {
76
+ status: "found",
77
+ binding,
78
+ manifestHash: validation.manifest.manifest_hash,
79
+ compiledIrHash: validation.manifest.compiled_ir_hash,
80
+ };
81
+ }
82
+ function normalizeManifestInputs(binding, candidate) {
83
+ const rawInputs = { ...(candidate ?? {}) };
84
+ const allowed = new Set([...binding.runtime_input_refs, ...RUNTIME_ARGUMENT_NAMES]);
85
+ const unexpected = Object.keys(rawInputs).filter((name) => !allowed.has(name));
86
+ if (unexpected.length > 0) {
87
+ return { valid: false, message: `Workflow inputs are not declared by the compiled runtime binding: ${unexpected.join(", ")}` };
88
+ }
89
+ const argumentValues = RUNTIME_ARGUMENT_NAMES
90
+ .map((name) => rawInputs[name])
91
+ .filter((value) => value !== undefined);
92
+ const distinctArgumentValues = [...new Set(argumentValues)];
93
+ if (distinctArgumentValues.length > 1) {
94
+ return { valid: false, message: "ARGUMENTS, arguments, and task_id must identify the same runtime input." };
95
+ }
96
+ const argumentValue = distinctArgumentValues[0];
97
+ if (!binding.requires_arguments && argumentValue !== undefined) {
98
+ return { valid: true, note: "Ignored legacy argument input for an argument-free workflow." };
99
+ }
100
+ if (binding.requires_arguments && (!argumentValue || argumentValue.trim() === "")) {
101
+ return { valid: false, message: "This workflow requires a non-empty ARGUMENTS input." };
102
+ }
103
+ const inputs = {};
104
+ for (const name of binding.runtime_input_refs) {
105
+ if (RUNTIME_ARGUMENT_NAMES.includes(name)) {
106
+ if (argumentValue !== undefined)
107
+ inputs[name] = argumentValue;
108
+ }
109
+ else if (rawInputs[name] !== undefined) {
110
+ inputs[name] = rawInputs[name];
111
+ }
112
+ else {
113
+ return { valid: false, message: `This workflow requires runtime input ${name}.` };
114
+ }
115
+ }
116
+ return { valid: true, inputs: Object.keys(inputs).length > 0 ? inputs : undefined };
117
+ }
19
118
  export function createStateTools(projectDir) {
20
119
  return [
21
120
  // ── dna_status ────────────────────────────────────────
@@ -39,6 +138,15 @@ export function createStateTools(projectDir) {
39
138
  if (ir.workflows_ir?.length) {
40
139
  lines.push(` Workflows: ${ir.workflows_ir.map(w => w.workflow_name).join(", ")}`);
41
140
  }
141
+ if (!Object.prototype.hasOwnProperty.call(compiled, "workflow_runtime")) {
142
+ lines.push(" Workflow runtime: legacy; workflow starts are blocked until `dna sync` regenerates deterministic bindings");
143
+ }
144
+ else {
145
+ const runtimeValidation = validateWorkflowRuntimeManifest(compiled.workflow_runtime, compiled.compiled_ir_hash);
146
+ lines.push(runtimeValidation.valid
147
+ ? ` Workflow runtime: ${runtimeValidation.manifest.schema_version}`
148
+ : ` Workflow runtime: invalid (${runtimeValidation.reason}); run \`dna sync\``);
149
+ }
42
150
  }
43
151
  else {
44
152
  lines.push("IR: not found (run `dna sync` to compile)");
@@ -99,40 +207,168 @@ export function createStateTools(projectDir) {
99
207
  // ── dna_workflow_write ────────────────────────────────
100
208
  {
101
209
  name: "dna_workflow_write",
102
- description: "Update workflow state (set current step, role, iteration, or deactivate)",
210
+ description: "Update workflow state. Template variables and workflow asset identity are initialized from the compiled runtime manifest.",
103
211
  inputSchema: {
104
212
  type: "object",
105
213
  properties: {
106
214
  workflow: { type: "string", description: "Workflow name" },
107
- workflow_asset: { type: "string", description: "Workflow asset id/name for local evidence attribution" },
215
+ workflow_asset: { type: "string", description: "Deprecated Skill input; ignored because the compiled runtime manifest owns workflow attribution" },
108
216
  current_step: { type: "string", description: "Current step ID" },
109
217
  current_role: { type: "string", description: "Current role name" },
110
218
  iteration: { type: "number", description: "Current iteration number" },
111
219
  active: { type: "boolean", description: "Whether workflow is active (false to deactivate)" },
112
220
  session_id: { type: "string", description: "Session ID for isolation" },
113
221
  inputs: { type: "object", description: "Workflow runtime inputs such as ARGUMENTS" },
114
- resolved_variables: { type: "object", description: "Resolved template variables for artifact paths" },
222
+ resolved_variables: { type: "object", description: "Deprecated Skill input; ignored when a manifest exists and never accepted as compiled configuration" },
115
223
  },
116
224
  required: ["workflow", "current_step", "current_role"],
117
225
  },
118
226
  handler: async (args) => {
119
227
  const sessionId = typeof args.session_id === "string" ? args.session_id : undefined;
120
228
  const existing = await readWorkflowState(projectDir, sessionId, 0);
229
+ const workflow = String(args.workflow);
230
+ if (existing?.active && existing.workflow !== workflow) {
231
+ return errorResult(`Workflow ${existing.workflow} is already active. Deactivate it before starting or writing state for ${workflow}.`);
232
+ }
233
+ const sameActiveWorkflow = existing?.active === true && existing.workflow === workflow;
234
+ const closingWorkflow = args.active === false;
235
+ if (sameActiveWorkflow && closingWorkflow) {
236
+ const state = {
237
+ ...existing,
238
+ active: false,
239
+ current_step: String(args.current_step),
240
+ current_role: String(args.current_role),
241
+ iteration: typeof args.iteration === "number" ? args.iteration : existing.iteration,
242
+ };
243
+ await writeWorkflowState(projectDir, state, sessionId);
244
+ return textResult(`Workflow state updated: ${state.workflow} step=${state.current_step} role=${state.current_role} iteration=${state.iteration}\n`
245
+ + "Terminal deactivation preserved the run's pinned state and ignored replacement runtime bindings.");
246
+ }
247
+ const runtimeBinding = await lookupRuntimeBinding(projectDir, workflow);
248
+ const hasPinnedRuntimeBinding = sameActiveWorkflow &&
249
+ existing?.resolved_variables_source === "compiled_manifest" &&
250
+ typeof existing.workflow_runtime_manifest_hash === "string" &&
251
+ typeof existing.workflow_runtime_compiled_ir_hash === "string";
252
+ if (runtimeBinding.status === "error" && !hasPinnedRuntimeBinding) {
253
+ return errorResult(runtimeBinding.message);
254
+ }
255
+ const currentCompiledIrHash = runtimeBinding.compiledIrHash;
256
+ if (hasPinnedRuntimeBinding &&
257
+ currentCompiledIrHash !== existing.workflow_runtime_compiled_ir_hash &&
258
+ !closingWorkflow) {
259
+ return errorResult("The compiled IR changed during this active workflow. Restart the workflow so one run cannot execute against multiple policy revisions.");
260
+ }
261
+ if (args.inputs !== undefined && !isStringRecord(args.inputs)) {
262
+ return errorResult("Workflow inputs must be an object whose values are strings.");
263
+ }
264
+ const providedInputs = isStringRecord(args.inputs) ? args.inputs : undefined;
265
+ let inputs;
266
+ let workflowInputsPinned = sameActiveWorkflow ? existing?.workflow_inputs_pinned : undefined;
267
+ let workflowAsset;
268
+ let resolvedVariables;
269
+ let resolvedVariablesSource;
270
+ let workflowRuntimeManifestHash;
271
+ let workflowRuntimeCompiledIrHash;
272
+ const notes = [];
273
+ if (hasPinnedRuntimeBinding) {
274
+ workflowAsset = existing.workflow_asset;
275
+ resolvedVariables = existing.resolved_variables;
276
+ resolvedVariablesSource = "compiled_manifest";
277
+ workflowRuntimeManifestHash = existing.workflow_runtime_manifest_hash;
278
+ workflowRuntimeCompiledIrHash = existing.workflow_runtime_compiled_ir_hash;
279
+ workflowInputsPinned = true;
280
+ if (providedInputs) {
281
+ const normalized = runtimeBinding.status === "found"
282
+ ? normalizeManifestInputs(runtimeBinding.binding, providedInputs)
283
+ : { valid: true, inputs: providedInputs };
284
+ if (!normalized.valid)
285
+ return errorResult(normalized.message);
286
+ if (!sameStringRecord(existing.inputs ?? {}, normalized.inputs ?? {})) {
287
+ return errorResult("Workflow inputs are pinned for an active run and cannot be changed. Finish or restart the workflow to use different inputs.");
288
+ }
289
+ }
290
+ inputs = existing.inputs;
291
+ if (args.resolved_variables !== undefined) {
292
+ notes.push("Deprecated resolved_variables input ignored; active workflow bindings are pinned.");
293
+ }
294
+ if (runtimeBinding.status === "found" && runtimeBinding.manifestHash !== workflowRuntimeManifestHash) {
295
+ notes.push("A newer compiled workflow runtime manifest exists; this active workflow remains pinned to its start-time bindings.");
296
+ }
297
+ else if (runtimeBinding.status === "error") {
298
+ notes.push("The compiled workflow manifest is invalid or unavailable; this run remains pinned to the same compiled IR revision. Run `dna sync` before the next workflow.");
299
+ }
300
+ else if (runtimeBinding.status === "legacy") {
301
+ notes.push("The compiled runtime manifest is unavailable; this active workflow remains pinned, but `dna sync` is required before the next workflow.");
302
+ }
303
+ }
304
+ else if (runtimeBinding.status === "found") {
305
+ let candidateInputs = providedInputs ?? (sameActiveWorkflow ? existing?.inputs : undefined);
306
+ if (sameActiveWorkflow && existing?.resolved_variables_source !== "compiled_manifest") {
307
+ const existingCandidate = compatibleManifestInputCandidate(runtimeBinding.binding, existing.inputs);
308
+ const providedCandidate = compatibleManifestInputCandidate(runtimeBinding.binding, providedInputs);
309
+ const normalizedExisting = normalizeManifestInputs(runtimeBinding.binding, existingCandidate.candidate);
310
+ const normalizedProvided = providedInputs
311
+ ? normalizeManifestInputs(runtimeBinding.binding, providedCandidate.candidate)
312
+ : null;
313
+ if (normalizedExisting.valid && normalizedExisting.inputs) {
314
+ if (normalizedProvided?.valid && normalizedProvided.inputs &&
315
+ !sameStringRecord(normalizedExisting.inputs, normalizedProvided.inputs)) {
316
+ return errorResult("Workflow inputs are pinned for an active legacy run and cannot be changed during migration.");
317
+ }
318
+ candidateInputs = existingCandidate.candidate;
319
+ }
320
+ else {
321
+ candidateInputs = { ...existingCandidate.candidate, ...providedCandidate.candidate };
322
+ }
323
+ const ignored = [...new Set([...existingCandidate.ignored, ...providedCandidate.ignored])];
324
+ if (ignored.length > 0) {
325
+ notes.push(`Ignored legacy workflow input metadata during migration: ${ignored.join(", ")}.`);
326
+ }
327
+ }
328
+ const normalizedInputs = normalizeManifestInputs(runtimeBinding.binding, candidateInputs);
329
+ if (!normalizedInputs.valid)
330
+ return errorResult(normalizedInputs.message);
331
+ inputs = normalizedInputs.inputs;
332
+ workflowInputsPinned = true;
333
+ workflowAsset = runtimeBinding.binding.workflow_asset;
334
+ resolvedVariables = runtimeBinding.binding.resolved_variables;
335
+ resolvedVariablesSource = "compiled_manifest";
336
+ workflowRuntimeManifestHash = runtimeBinding.manifestHash;
337
+ workflowRuntimeCompiledIrHash = runtimeBinding.compiledIrHash;
338
+ if (normalizedInputs.note)
339
+ notes.push(normalizedInputs.note);
340
+ if (args.resolved_variables !== undefined) {
341
+ notes.push("Deprecated resolved_variables input ignored; compiled workflow runtime manifest is authoritative.");
342
+ }
343
+ if (sameActiveWorkflow && existing?.resolved_variables_source !== "compiled_manifest") {
344
+ notes.push("Legacy workflow state upgraded to compiled workflow runtime bindings.");
345
+ }
346
+ }
347
+ else {
348
+ return errorResult("This project has a legacy compiled IR without deterministic workflow runtime bindings. Run `dna sync` with the upgraded IntentDNA CLI, then retry the workflow. Model-supplied workflow_asset and resolved_variables are not trusted.");
349
+ }
121
350
  const state = {
122
351
  active: typeof args.active === "boolean" ? args.active : true,
123
- workflow_asset: typeof args.workflow_asset === "string" ? args.workflow_asset : existing?.workflow_asset,
124
- workflow: String(args.workflow),
352
+ workflow_asset: workflowAsset,
353
+ workflow,
125
354
  current_step: String(args.current_step),
126
355
  current_role: String(args.current_role),
127
- iteration: typeof args.iteration === "number" ? args.iteration : (existing?.iteration ?? 1),
128
- session_id: sessionId ?? existing?.session_id ?? "",
129
- started_at: existing?.started_at ?? new Date().toISOString(),
130
- inputs: isStringRecord(args.inputs) ? args.inputs : existing?.inputs,
131
- resolved_variables: isStringRecord(args.resolved_variables) ? args.resolved_variables : existing?.resolved_variables,
132
- completed_artifacts: existing?.completed_artifacts,
356
+ iteration: typeof args.iteration === "number" ? args.iteration : (sameActiveWorkflow ? existing?.iteration ?? 1 : 1),
357
+ session_id: sessionId ?? (sameActiveWorkflow ? existing?.session_id : undefined) ?? "",
358
+ started_at: sameActiveWorkflow ? existing.started_at : new Date().toISOString(),
359
+ inputs,
360
+ resolved_variables: resolvedVariables,
361
+ resolved_variables_source: resolvedVariablesSource,
362
+ workflow_runtime_manifest_hash: workflowRuntimeManifestHash,
363
+ workflow_runtime_compiled_ir_hash: workflowRuntimeCompiledIrHash,
364
+ workflow_inputs_pinned: workflowInputsPinned,
365
+ completed_artifacts: sameActiveWorkflow ? existing?.completed_artifacts : undefined,
133
366
  };
134
367
  await writeWorkflowState(projectDir, state, sessionId);
135
- return textResult(`Workflow state updated: ${state.workflow} step=${state.current_step} role=${state.current_role} iteration=${state.iteration}`);
368
+ return textResult([
369
+ `Workflow state updated: ${state.workflow} step=${state.current_step} role=${state.current_role} iteration=${state.iteration}`,
370
+ ...notes,
371
+ ].join("\n"));
136
372
  },
137
373
  },
138
374
  // ── dna_trace_query ──────────────────────────────────
@@ -1,6 +1,7 @@
1
1
  import type { ConstraintIR, RoleDef } from "../schema/types.js";
2
2
  import type { SyncOutputArtifactId } from "./output-artifact-registry.js";
3
3
  import { type CompiledIRFile } from "./plugin-adapter.js";
4
+ import type { WorkflowRuntimeManifest } from "./workflow-runtime-manifest.js";
4
5
  import { type DNASettings, type HookEventKey, type IntentDNASettingsHookCleanupResult } from "./settings-adapter.js";
5
6
  import type { AdapterCapabilityReport, CapabilityReportEntry, PlannedArtifact, SyncTargetPlan } from "./sync-target-plan.js";
6
7
  export type ClaudeSyncMode = "plugin" | "bin";
@@ -21,6 +22,7 @@ export interface ClaudeCoreSyncTargetPlanOptions {
21
22
  projectDir: string;
22
23
  ir: ConstraintIR;
23
24
  roles?: Record<string, RoleDef>;
25
+ workflowRuntime?: WorkflowRuntimeManifest;
24
26
  mode: ClaudeSyncMode;
25
27
  settingsPath?: string;
26
28
  }
@@ -43,7 +43,7 @@ function createSettingsHookRegistration(ir, mode, settingsPath) {
43
43
  };
44
44
  }
45
45
  export function createClaudeCoreSyncTargetPlan(options) {
46
- const compiledIR = createCompiledIR(options.ir, options.roles);
46
+ const compiledIR = createCompiledIR(options.ir, options.roles, options.workflowRuntime);
47
47
  const compiledIRPath = resolve(options.projectDir, ".dna", "compiled", "ir.json");
48
48
  const legacyHooksDir = resolve(options.projectDir, ".claude", "hooks");
49
49
  const mcpConfigPath = resolve(options.projectDir, ".mcp.json");
@@ -0,0 +1,13 @@
1
+ export declare const MAX_EVALUATOR_RESULT_BYTES = 65536;
2
+ export interface EvaluatorResult {
3
+ readonly pass: boolean;
4
+ readonly score?: number;
5
+ }
6
+ export interface ParseEvaluatorResultOptions {
7
+ readonly require_score?: boolean;
8
+ readonly max_bytes?: number;
9
+ }
10
+ export declare class EvaluatorResultError extends Error {
11
+ constructor(message: string);
12
+ }
13
+ export declare function parseEvaluatorResult(raw: string, options?: ParseEvaluatorResultOptions): EvaluatorResult;