intentdna 1.9.0-rc.1 → 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 (33) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/cli/commands/run.js +72 -40
  4. package/dist/cli/commands/setup.d.ts +10 -0
  5. package/dist/cli/commands/setup.js +129 -36
  6. package/dist/cli/commands/sync.js +52 -12
  7. package/dist/cli/index.js +0 -0
  8. package/dist/hooks/cli.js +44 -4
  9. package/dist/hooks/enforcement-boundary.d.ts +1 -1
  10. package/dist/hooks/protocol.d.ts +1 -1
  11. package/dist/hooks/state.d.ts +5 -0
  12. package/dist/hooks/state.js +21 -6
  13. package/dist/mcp/index.js +0 -0
  14. package/dist/mcp/tools-state.js +251 -15
  15. package/dist/runtime/claude-sync-target.d.ts +2 -0
  16. package/dist/runtime/claude-sync-target.js +1 -1
  17. package/dist/runtime/diagnosis-contract-verifier.d.ts +11 -0
  18. package/dist/runtime/diagnosis-contract-verifier.js +417 -0
  19. package/dist/runtime/index.d.ts +2 -2
  20. package/dist/runtime/index.js +1 -1
  21. package/dist/runtime/plugin-adapter.d.ts +14 -1
  22. package/dist/runtime/plugin-adapter.js +23 -9
  23. package/dist/runtime/skill-adapter.js +24 -53
  24. package/dist/runtime/workflow-runtime-manifest.d.ts +48 -0
  25. package/dist/runtime/workflow-runtime-manifest.js +196 -0
  26. package/dist/runtime/workflow-variable-scopes.d.ts +12 -0
  27. package/dist/runtime/workflow-variable-scopes.js +55 -0
  28. package/dist/schema/types.d.ts +4 -0
  29. package/dist/templates/flutter-behavior-lock.dna.yaml +17 -0
  30. package/dist/templates/flutter-refactoring-rescue.dna.yaml +11 -0
  31. package/dist/templates/workflow-evidence-review.dna.yaml +8 -0
  32. package/package.json +3 -1
  33. package/scripts/sync-release-plugin.cjs +87 -0
@@ -9,7 +9,7 @@
9
9
  {
10
10
  "name": "intentdna",
11
11
  "description": "Declarative policy layer for AI agent behavior with plugin-managed hook runtime for Claude Code.",
12
- "version": "1.9.0-rc.1",
12
+ "version": "1.9.0-rc.2",
13
13
  "author": {
14
14
  "name": "Samuel"
15
15
  },
@@ -25,5 +25,5 @@
25
25
  ]
26
26
  }
27
27
  ],
28
- "version": "1.9.0-rc.1"
28
+ "version": "1.9.0-rc.2"
29
29
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intentdna",
3
- "version": "1.9.0-rc.1",
3
+ "version": "1.9.0-rc.2",
4
4
  "description": "Declarative policy layer for AI agent behavior",
5
5
  "author": {
6
6
  "name": "Samuel"
@@ -10,12 +10,13 @@ import { isAbsolute, join, relative, resolve, sep, } from "node:path";
10
10
  import { cascadeDNA } from "../../compiler/cascade.js";
11
11
  import { compileFromFiles, expandDNAInputFiles, loadDNA, } from "../../compiler/index.js";
12
12
  import { compileWorkflow } from "../../compiler/workflow.js";
13
- import { trustedEvidenceCaptureAttribution } from "../../governance/index.js";
14
13
  import { DNAStateManager } from "../../hooks/state-manager.js";
15
14
  import { appendEvidenceCaptureEvent, EVIDENCE_CAPTURE_SCHEMA_VERSION, } from "../../hooks/state.js";
16
15
  import { compileAllRolesToAgentMD, toKebabCase, writeAgentMDFiles, } from "../../runtime/agent-md.js";
17
16
  import { HandoffResolver } from "../../runtime/handoff-resolver.js";
18
17
  import { createCompiledIR, writeCompiledIR } from "../../runtime/plugin-adapter.js";
18
+ import { createWorkflowRuntimeManifest, WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES, } from "../../runtime/workflow-runtime-manifest.js";
19
+ import { defaultResolvedVariableScopes, indexWorkflowVariableScopes, resolvedVariablesForNamespace, } from "../../runtime/workflow-variable-scopes.js";
19
20
  import { ImmutableResultStore } from "../../runtime/result-store.js";
20
21
  import { RunController, } from "../../runtime/run-controller.js";
21
22
  import { DurableRunStore, RunStoreError, } from "../../runtime/run-store.js";
@@ -191,6 +192,8 @@ async function compileRunAssets(metadata) {
191
192
  for (const file of metadata.dna_files)
192
193
  dnas.push(await loadDNA(file));
193
194
  const cascaded = cascadeDNA(dnas);
195
+ const variableScopeIndex = indexWorkflowVariableScopes(dnas);
196
+ const defaultVariableScopes = defaultResolvedVariableScopes(variableScopeIndex);
194
197
  const ir = await compileFromFiles([...metadata.dna_files], {
195
198
  context: metadata.context ?? undefined,
196
199
  });
@@ -199,26 +202,31 @@ async function compileRunAssets(metadata) {
199
202
  throw new Error(`workflow '${metadata.workflow_name}' not found; available: `
200
203
  + `${Object.keys(cascaded.workflows).join(", ") || "(none)"}`);
201
204
  }
202
- const result = compileWorkflow(definition, {
203
- workflow_key: metadata.workflow_name,
204
- });
205
- if (!result.ok || !result.plan) {
206
- throw new Error("workflow compilation failed:\n"
207
- + result.errors.map((error) => ` ${error.path}: ${error.message}`).join("\n"));
205
+ const runtimePlans = [];
206
+ for (const [workflowKey, workflowDefinition] of Object.entries(cascaded.workflows)) {
207
+ const result = compileWorkflow(workflowDefinition, { workflow_key: workflowKey });
208
+ if (!result.ok || !result.plan) {
209
+ throw new Error(`workflow '${workflowKey}' compilation failed:\n`
210
+ + result.errors.map((error) => ` ${error.path}: ${error.message}`).join("\n"));
211
+ }
212
+ const namespace = variableScopeIndex.workflowNamespaces.get(workflowKey) ?? "";
213
+ runtimePlans.push({
214
+ plan: result.plan,
215
+ workflow_asset: variableScopeIndex.workflowAssets.get(workflowKey) ?? null,
216
+ resolved_variables: resolvedVariablesForNamespace(defaultVariableScopes, namespace, workflowKey === metadata.workflow_name ? { ...metadata.variables } : {}),
217
+ });
208
218
  }
209
- const plan = result.plan;
210
- const attribution = trustedEvidenceCaptureAttribution(ir, {
211
- workflowId: metadata.workflow_name,
212
- });
213
- const workflowAsset = attribution.workflowAsset === "unknown"
214
- ? null
215
- : attribution.workflowAsset;
219
+ const plan = runtimePlans.find((item) => item.plan.workflow_key === metadata.workflow_name)?.plan;
220
+ if (!plan)
221
+ throw new Error(`compiled workflow '${metadata.workflow_name}' is missing from runtime plans`);
222
+ const workflowAsset = runtimePlans.find((item) => item.plan.workflow_key === metadata.workflow_name)?.workflow_asset ?? null;
216
223
  if (metadata.workflow_asset !== null
217
224
  && workflowAsset !== metadata.workflow_asset) {
218
225
  throw new Error(`workflow asset changed from '${metadata.workflow_asset}' to '${workflowAsset ?? "unknown"}'`);
219
226
  }
220
227
  return {
221
228
  plan,
229
+ runtime_plans: runtimePlans,
222
230
  roles: cascaded.roles,
223
231
  ir,
224
232
  plan_digest: planDigest(plan, cascaded.roles),
@@ -227,6 +235,20 @@ async function compileRunAssets(metadata) {
227
235
  }
228
236
  async function compileCanonicalRun(metadata) {
229
237
  const assets = await compileRunAssets(metadata);
238
+ if (!assets.ir.compiled_ir_hash) {
239
+ throw new Error("Compiled IR hash is required for canonical workflow runtime bindings");
240
+ }
241
+ const workflowRuntime = createWorkflowRuntimeManifest({
242
+ plans: assets.runtime_plans.map((runtimePlan) => ({
243
+ plan: runtimePlan.plan,
244
+ resolvedVariables: runtimePlan.resolved_variables,
245
+ workflowAsset: runtimePlan.workflow_asset ?? undefined,
246
+ additionalRuntimeValues: assets.ir.verifier_specs?.filter((spec) => spec.workflow_name === runtimePlan.plan.workflow_key) ?? [],
247
+ })),
248
+ compiledAt: assets.ir.compiled_at,
249
+ compiledIrHash: assets.ir.compiled_ir_hash,
250
+ sourceDnaIds: assets.ir.source_dna_ids,
251
+ });
230
252
  const adapterOptions = {
231
253
  roles: assets.roles,
232
254
  variables: metadata.variables,
@@ -236,6 +258,7 @@ async function compileCanonicalRun(metadata) {
236
258
  };
237
259
  return {
238
260
  ...assets,
261
+ workflow_runtime: workflowRuntime,
239
262
  definitions: adaptWorkflowPlan(assets.plan, adapterOptions),
240
263
  retry_loop: adaptWorkflowRetryLoop(assets.plan, adapterOptions),
241
264
  };
@@ -243,7 +266,7 @@ async function compileCanonicalRun(metadata) {
243
266
  async function syncRuntimeArtifacts(compiled, metadata) {
244
267
  const agents = compileAllRolesToAgentMD(compiled.roles, compiled.ir);
245
268
  await writeAgentMDFiles(agents, metadata.agents_directory);
246
- await writeCompiledIR(metadata.project_directory, createCompiledIR(compiled.ir, compiled.roles));
269
+ await writeCompiledIR(metadata.project_directory, createCompiledIR(compiled.ir, compiled.roles, compiled.workflow_runtime));
247
270
  }
248
271
  function projectPathInWorkspace(metadata, workspaceDirectory, projectPath) {
249
272
  const projectRelative = relative(metadata.project_directory, projectPath);
@@ -262,7 +285,7 @@ async function syncAttemptRuntimeArtifacts(compiled, metadata, workspace) {
262
285
  const agents = compileAllRolesToAgentMD(compiled.roles, compiled.ir);
263
286
  await writeAgentMDFiles(agents, agentsDirectory);
264
287
  }
265
- await writeCompiledIR(workspace.working_directory, createCompiledIR(compiled.ir, compiled.roles));
288
+ await writeCompiledIR(workspace.working_directory, createCompiledIR(compiled.ir, compiled.roles, compiled.workflow_runtime));
266
289
  }
267
290
  function createProvider(metadata) {
268
291
  if (metadata.provider.kind === "codex") {
@@ -462,31 +485,40 @@ function createWorkspaceAwareExecutor(metadata, compiled, runStore) {
462
485
  let lifecycleRecorded = false;
463
486
  try {
464
487
  await syncAttemptRuntimeArtifacts(compiled, metadata, workspace);
465
- try {
466
- await state.writeWorkflowState({
467
- active: true,
468
- workflow_asset: metadata.workflow_asset ?? undefined,
469
- workflow: metadata.workflow_name,
470
- current_step: packet.step_id,
471
- current_role: packet.role,
472
- iteration: packet.workflow_round,
473
- session_id: packet.worker_session_id,
474
- worker_session_id: packet.worker_session_id,
475
- run_id: packet.run_id,
476
- step_id: packet.step_id,
477
- attempt_id: packet.attempt_id,
478
- started_at: packet.created_at,
479
- inputs: { ...metadata.variables },
480
- resolved_variables: {
481
- ...metadata.variables,
482
- round: String(packet.workflow_round),
483
- },
484
- });
485
- }
486
- catch (error) {
487
- log(`Workflow evidence state unavailable for ${packet.step_id}; `
488
- + `continuing fail-open: ${error instanceof Error ? error.message : String(error)}`);
488
+ const runtimeBinding = compiled.workflow_runtime.workflows[metadata.workflow_name];
489
+ if (!runtimeBinding)
490
+ throw new Error(`Missing runtime binding for ${metadata.workflow_name}`);
491
+ const canonicalArguments = metadata.variables.ARGUMENTS ??
492
+ metadata.variables.arguments ?? metadata.variables.task_id;
493
+ const runtimeInputs = {};
494
+ for (const name of runtimeBinding.runtime_input_refs) {
495
+ const value = WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES.has(name)
496
+ ? canonicalArguments
497
+ : metadata.variables[name];
498
+ if (value === undefined)
499
+ throw new Error(`Missing runtime input ${name} for ${metadata.workflow_name}`);
500
+ runtimeInputs[name] = value;
489
501
  }
502
+ await state.writeWorkflowState({
503
+ active: true,
504
+ workflow_asset: runtimeBinding.workflow_asset,
505
+ workflow: metadata.workflow_name,
506
+ current_step: packet.step_id,
507
+ current_role: packet.role,
508
+ iteration: packet.workflow_round,
509
+ session_id: packet.worker_session_id,
510
+ worker_session_id: packet.worker_session_id,
511
+ run_id: packet.run_id,
512
+ step_id: packet.step_id,
513
+ attempt_id: packet.attempt_id,
514
+ started_at: packet.created_at,
515
+ inputs: Object.keys(runtimeInputs).length > 0 ? runtimeInputs : undefined,
516
+ resolved_variables: runtimeBinding.resolved_variables,
517
+ resolved_variables_source: "compiled_manifest",
518
+ workflow_runtime_manifest_hash: compiled.workflow_runtime.manifest_hash,
519
+ workflow_runtime_compiled_ir_hash: compiled.workflow_runtime.compiled_ir_hash,
520
+ workflow_inputs_pinned: true,
521
+ });
490
522
  const inheritedProcessObserver = options?.onProcessStart;
491
523
  let execution = await executeWorkerAttempt(workerPacket, provider, {
492
524
  ...options,
@@ -19,4 +19,14 @@ export interface SetupOptions {
19
19
  yes: boolean;
20
20
  upgrade: boolean;
21
21
  }
22
+ /**
23
+ * Check if intentdna is already registered as a plugin.
24
+ */
25
+ export interface InstalledPluginInfo {
26
+ version: string | null;
27
+ enabled: boolean;
28
+ }
29
+ export declare function parseInstalledPluginInfo(output: string): InstalledPluginInfo | null;
30
+ export declare function releaseChannelForVersion(version: string | null): "latest" | "next";
31
+ export declare function shouldInstallLatestVersion(installedVersion: string, latestVersion: string): boolean;
22
32
  export declare function runSetup(opts: SetupOptions): Promise<number>;
@@ -79,21 +79,32 @@ async function resolvePackagePath() {
79
79
  }
80
80
  return null;
81
81
  }
82
- /**
83
- * Check if intentdna is already registered as a plugin.
84
- */
85
- function isPluginRegistered() {
82
+ export function parseInstalledPluginInfo(output) {
83
+ const pluginId = "intentdna@intentdna";
84
+ const pluginStart = output.indexOf(pluginId);
85
+ if (pluginStart === -1)
86
+ return null;
87
+ const block = output.slice(pluginStart).split(/\n\s*\n/, 1)[0];
88
+ return {
89
+ version: block.match(/Version:\s*([^\s]+)/)?.[1] ?? null,
90
+ enabled: /Status:\s*.*\benabled\b/i.test(block),
91
+ };
92
+ }
93
+ function getInstalledPluginInfo() {
86
94
  try {
87
95
  const result = execSync("claude plugin list 2>/dev/null", {
88
96
  encoding: "utf-8",
89
97
  timeout: 5000,
90
98
  });
91
- return result.includes("intentdna");
99
+ return parseInstalledPluginInfo(result);
92
100
  }
93
101
  catch {
94
- return false;
102
+ return null;
95
103
  }
96
104
  }
105
+ function isPluginRegistered() {
106
+ return getInstalledPluginInfo() !== null;
107
+ }
97
108
  // ── Main ──────────────────────────────────────────────────
98
109
  /**
99
110
  * Get the currently installed version of intentdna.
@@ -111,12 +122,10 @@ function getInstalledVersion() {
111
122
  return null;
112
123
  }
113
124
  }
114
- /**
115
- * Get the latest published version from npm registry.
116
- */
117
- function getLatestVersion() {
125
+ /** Get the version currently published on an npm dist-tag. */
126
+ function getPublishedVersion(channel) {
118
127
  try {
119
- return execSync("npm view intentdna version 2>/dev/null", {
128
+ return execSync(`npm view intentdna@${channel} version 2>/dev/null`, {
120
129
  encoding: "utf-8",
121
130
  timeout: 10000,
122
131
  }).trim();
@@ -125,8 +134,63 @@ function getLatestVersion() {
125
134
  return null;
126
135
  }
127
136
  }
137
+ function parseReleaseVersion(version) {
138
+ const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
139
+ if (!match)
140
+ return null;
141
+ return {
142
+ core: [Number(match[1]), Number(match[2]), Number(match[3])],
143
+ prerelease: match[4]?.split(".") ?? null,
144
+ };
145
+ }
146
+ function comparePrerelease(left, right) {
147
+ if (left === null && right === null)
148
+ return 0;
149
+ if (left === null)
150
+ return 1;
151
+ if (right === null)
152
+ return -1;
153
+ const length = Math.max(left.length, right.length);
154
+ for (let index = 0; index < length; index += 1) {
155
+ const leftPart = left[index];
156
+ const rightPart = right[index];
157
+ if (leftPart === undefined)
158
+ return -1;
159
+ if (rightPart === undefined)
160
+ return 1;
161
+ if (leftPart === rightPart)
162
+ continue;
163
+ const leftNumeric = /^\d+$/.test(leftPart);
164
+ const rightNumeric = /^\d+$/.test(rightPart);
165
+ if (leftNumeric && rightNumeric)
166
+ return Number(leftPart) < Number(rightPart) ? -1 : 1;
167
+ if (leftNumeric !== rightNumeric)
168
+ return leftNumeric ? -1 : 1;
169
+ return leftPart < rightPart ? -1 : 1;
170
+ }
171
+ return 0;
172
+ }
173
+ export function releaseChannelForVersion(version) {
174
+ if (!version)
175
+ return "latest";
176
+ return parseReleaseVersion(version)?.prerelease ? "next" : "latest";
177
+ }
178
+ export function shouldInstallLatestVersion(installedVersion, latestVersion) {
179
+ if (installedVersion === latestVersion)
180
+ return false;
181
+ const installed = parseReleaseVersion(installedVersion);
182
+ const latest = parseReleaseVersion(latestVersion);
183
+ if (!installed || !latest)
184
+ return true;
185
+ for (let index = 0; index < installed.core.length; index += 1) {
186
+ if (installed.core[index] === latest.core[index])
187
+ continue;
188
+ return installed.core[index] < latest.core[index];
189
+ }
190
+ return comparePrerelease(installed.prerelease, latest.prerelease) < 0;
191
+ }
128
192
  /**
129
- * Upgrade flow: npm update → re-register marketplace → re-install plugin.
193
+ * Upgrade flow: install from the current release channel → refresh marketplace → update and verify plugin.
130
194
  */
131
195
  async function runUpgrade(opts) {
132
196
  // Step 1: Detect claude CLI
@@ -138,20 +202,26 @@ async function runUpgrade(opts) {
138
202
  log(`Claude Code detected: ${claudeVersion}`);
139
203
  // Step 2: Check versions
140
204
  const currentVer = getInstalledVersion();
141
- const latestVer = getLatestVersion();
205
+ const releaseChannel = releaseChannelForVersion(currentVer);
206
+ const publishedVer = getPublishedVersion(releaseChannel);
142
207
  if (currentVer)
143
208
  log(`Current version: ${currentVer}`);
144
- if (latestVer)
145
- log(`Latest version: ${latestVer}`);
146
- if (currentVer && latestVer && currentVer === latestVer) {
147
- log("Already at latest version.");
209
+ log(`Target channel: ${releaseChannel}`);
210
+ if (publishedVer)
211
+ log(`Target version: ${publishedVer}`);
212
+ if (!publishedVer) {
213
+ log(`Upgrade failed: could not resolve intentdna@${releaseChannel} from npm.`);
214
+ return 1;
215
+ }
216
+ if (currentVer && !shouldInstallLatestVersion(currentVer, publishedVer)) {
217
+ log(`Already at the ${releaseChannel} channel version or newer; refusing to downgrade.`);
148
218
  }
149
219
  else {
150
220
  // Step 3: npm update
151
221
  log("");
152
- log("Updating intentdna...");
222
+ log(`Updating intentdna from the ${releaseChannel} channel...`);
153
223
  try {
154
- execSync("npm update -g intentdna", {
224
+ execSync(`npm install -g intentdna@${releaseChannel}`, {
155
225
  encoding: "utf-8",
156
226
  timeout: 60000,
157
227
  stdio: ["inherit", "pipe", "pipe"],
@@ -162,10 +232,19 @@ async function runUpgrade(opts) {
162
232
  catch (err) {
163
233
  const msg = err instanceof Error ? err.message : String(err);
164
234
  log(`npm update failed: ${msg}`);
165
- log("Try manually: npm update -g intentdna");
235
+ log(`Try manually: npm install -g intentdna@${releaseChannel}`);
166
236
  return 1;
167
237
  }
168
238
  }
239
+ const upgradedVersion = getInstalledVersion();
240
+ if (!upgradedVersion) {
241
+ log("Upgrade failed: could not verify the installed npm package version.");
242
+ return 1;
243
+ }
244
+ if (shouldInstallLatestVersion(upgradedVersion, publishedVer)) {
245
+ log(`Upgrade failed: installed npm version ${upgradedVersion} is older than ${releaseChannel} channel version ${publishedVer}.`);
246
+ return 1;
247
+ }
169
248
  // Step 4: Re-resolve package path
170
249
  const pkgPath = await resolvePackagePath();
171
250
  if (!pkgPath) {
@@ -173,11 +252,11 @@ async function runUpgrade(opts) {
173
252
  return 1;
174
253
  }
175
254
  log(`Package path: ${pkgPath}`);
176
- // Step 5: Re-register marketplace (picks up new plugin.json version)
255
+ // Step 5: Refresh marketplace (picks up new plugin.json version)
177
256
  try {
178
257
  log("");
179
- log(`Re-registering marketplace: ${pkgPath}`);
180
- execSync(`claude plugin marketplace add "${pkgPath}"`, {
258
+ log("Refreshing marketplace: intentdna");
259
+ execSync("claude plugin marketplace update intentdna", {
181
260
  encoding: "utf-8",
182
261
  timeout: 30000,
183
262
  stdio: ["inherit", "pipe", "pipe"],
@@ -186,35 +265,49 @@ async function runUpgrade(opts) {
186
265
  }
187
266
  catch (err) {
188
267
  const msg = err instanceof Error ? err.message : String(err);
189
- log(`Warning: marketplace re-register failed: ${msg}`);
268
+ log(`Marketplace refresh failed: ${msg}`);
269
+ log(`Try manually: claude plugin marketplace add "${pkgPath}"`);
270
+ return 1;
190
271
  }
191
- // Step 6: Re-install plugin (picks up new hooks.json)
272
+ // Step 6: Update plugin (picks up new hooks.json and MCP runtime)
192
273
  try {
193
- const installCmd = `claude plugin install intentdna@intentdna --scope ${opts.scope}`;
194
- log(`Re-installing plugin: ${installCmd}`);
195
- execSync(installCmd, {
274
+ const updateCmd = "claude plugin update intentdna@intentdna";
275
+ log(`Updating plugin: ${updateCmd}`);
276
+ execSync(updateCmd, {
196
277
  encoding: "utf-8",
197
278
  timeout: 30000,
198
279
  stdio: ["inherit", "pipe", "pipe"],
199
280
  });
200
- log("Plugin re-installed.");
281
+ log("Plugin updated.");
201
282
  }
202
283
  catch (err) {
203
284
  const msg = err instanceof Error ? err.message : String(err);
204
- log(`Warning: plugin re-install failed: ${msg}`);
285
+ log(`Plugin update failed: ${msg}`);
286
+ log(`Try manually: claude plugin install intentdna@intentdna --scope ${opts.scope}`);
287
+ return 1;
205
288
  }
206
289
  // Step 7: Verify
207
- if (isPluginRegistered()) {
290
+ const plugin = getInstalledPluginInfo();
291
+ if (!plugin?.version) {
208
292
  log("");
209
- log("Upgrade complete. Run 'dna sync' to recompile your DNA templates.");
210
- return 0;
293
+ log("Upgrade failed: the IntentDNA Claude plugin version could not be verified.");
294
+ log("Try: claude plugin list");
295
+ return 1;
211
296
  }
212
- else {
297
+ if (plugin.version !== upgradedVersion) {
213
298
  log("");
214
- log("Upgrade completed but plugin verification failed.");
215
- log("Try: claude plugin list");
299
+ log(`Upgrade failed: Claude plugin version ${plugin.version} does not match npm version ${upgradedVersion}.`);
300
+ return 1;
301
+ }
302
+ if (!plugin.enabled) {
303
+ log("");
304
+ log("Upgrade failed: the version-matched IntentDNA Claude plugin is disabled.");
216
305
  return 1;
217
306
  }
307
+ log("");
308
+ log(`Upgrade complete: npm and Claude plugin are both ${upgradedVersion}.`);
309
+ log("Restart Claude Code, then run 'dna sync' in each project before resuming workflows.");
310
+ return 0;
218
311
  }
219
312
  export async function runSetup(opts) {
220
313
  // Upgrade mode
@@ -28,6 +28,8 @@ import { removeWorkflowScripts } from "../../runtime/workflow-runner.js";
28
28
  import { compileWorkflowToSkill, compileControllerToSkill, writeSkillFiles, removeSkillFiles } from "../../runtime/skill-adapter.js";
29
29
  import { getControllerKindMetadata } from "../../schema/controller-registry.js";
30
30
  import { createCodexSyncTargetPlan, executeCodexSyncTargetPlan, formatCodexSyncTargetPlanSummary, removeCodexSyncTargetArtifacts } from "../../runtime/codex-sync-target.js";
31
+ import { createWorkflowRuntimeManifest } from "../../runtime/workflow-runtime-manifest.js";
32
+ import { flattenResolvedVariableScopes, indexWorkflowVariableScopes, resolvedVariablesForNamespace, } from "../../runtime/workflow-variable-scopes.js";
31
33
  import { trackedOutputsForSyncTargetPlan } from "../../runtime/sync-target-plan.js";
32
34
  import { getPackageVersion } from "../util/version.js";
33
35
  import { cascadeDNA } from "../../compiler/cascade.js";
@@ -167,6 +169,24 @@ async function resolveVariables(rawVars, configPath, options) {
167
169
  }
168
170
  return resolved;
169
171
  }
172
+ async function resolveVariableScopes(dnas, configPath, options) {
173
+ const index = indexWorkflowVariableScopes(dnas);
174
+ const variableNamespaces = [...index.rawByNamespace.entries()]
175
+ .filter(([, raw]) => Object.keys(raw).length > 0)
176
+ .map(([namespace]) => namespace);
177
+ const resolvedByNamespace = new Map();
178
+ for (const [namespace, raw] of index.rawByNamespace) {
179
+ const writableConfigPath = variableNamespaces.length === 1 ? configPath : null;
180
+ resolvedByNamespace.set(namespace, await resolveVariables(raw, writableConfigPath, options));
181
+ }
182
+ return {
183
+ flat: flattenResolvedVariableScopes(index, resolvedByNamespace),
184
+ workflowNamespaces: index.workflowNamespaces,
185
+ workflowAssets: index.workflowAssets,
186
+ controllerNamespaces: index.controllerNamespaces,
187
+ forNamespace: (namespace) => resolvedVariablesForNamespace(resolvedByNamespace, namespace),
188
+ };
189
+ }
170
190
  /**
171
191
  * Auto-detect DNA config file in current directory (legacy single-config).
172
192
  * Priority: .dna/config.yaml > .dna/config.yml > .dna/config.json > .dna.yaml > .dna.yml > .dna.json
@@ -783,13 +803,39 @@ export async function runSync(opts) {
783
803
  trackedByLock: true,
784
804
  });
785
805
  }
806
+ const cascadedForRuntime = cascadeDNA(loadedDNAs);
807
+ const variableConfigPath = opts.files.length === 1 ? opts.files[0] : null;
808
+ const variableScopes = await resolveVariableScopes(loadedDNAs, variableConfigPath, { dryRun: opts.dryRun });
809
+ const variables = variableScopes.flat;
810
+ const workflowPlans = [];
811
+ for (const [workflowKey, workflowDef] of Object.entries(cascadedForRuntime.workflows)) {
812
+ const result = compileWorkflow(workflowDef, { workflow_key: workflowKey });
813
+ if (!result.ok || !result.plan) {
814
+ throw new Error(`Workflow '${workflowKey}' compilation failed:\n${result.errors.map((error) => ` ${error.path}: ${error.message}`).join("\n")}`);
815
+ }
816
+ workflowPlans.push(result.plan);
817
+ }
818
+ if (!ir.compiled_ir_hash) {
819
+ throw new Error("Compiled IR hash is required for workflow runtime bindings");
820
+ }
821
+ const workflowRuntime = createWorkflowRuntimeManifest({
822
+ plans: workflowPlans.map((plan) => ({
823
+ plan,
824
+ resolvedVariables: variableScopes.forNamespace(variableScopes.workflowNamespaces.get(plan.workflow_key ?? plan.source_workflow) ?? ""),
825
+ workflowAsset: variableScopes.workflowAssets.get(plan.workflow_key ?? plan.source_workflow),
826
+ additionalRuntimeValues: ir.verifier_specs?.filter((spec) => spec.workflow_name === (plan.workflow_key ?? plan.source_workflow)) ?? [],
827
+ })),
828
+ compiledAt: ir.compiled_at,
829
+ compiledIrHash: ir.compiled_ir_hash,
830
+ sourceDnaIds: ir.source_dna_ids,
831
+ });
786
832
  // Step 3.5: Execute Claude Code core target plan (compiled IR, MCP, settings hooks)
787
833
  {
788
- const cascadedForCore = cascadeDNA(loadedDNAs);
789
834
  const claudeCorePlan = createClaudeCoreSyncTargetPlan({
790
835
  projectDir,
791
836
  ir,
792
- roles: cascadedForCore.roles,
837
+ roles: cascadedForRuntime.roles,
838
+ workflowRuntime,
793
839
  mode: opts.plugin ? "plugin" : "bin",
794
840
  settingsPath: opts.settingsPath,
795
841
  });
@@ -913,14 +959,6 @@ export async function runSync(opts) {
913
959
  const workflows = cascadedForSkills.workflows;
914
960
  const controllers = cascadedForSkills.controllers;
915
961
  const roles = cascadedForSkills.roles;
916
- // Collect and resolve variables from all DNA files
917
- const rawVars = {};
918
- for (const d of dnas) {
919
- if (d.variables)
920
- Object.assign(rawVars, d.variables);
921
- }
922
- const configPath = opts.files.length === 1 ? opts.files[0] : null;
923
- const variables = await resolveVariables(rawVars, configPath, { dryRun: opts.dryRun });
924
962
  // Step 8: Generate project-root .mcp.json only for Claude Code skills.
925
963
  if (skillSurface === "claude_code") {
926
964
  const mcpDeps = {};
@@ -947,14 +985,16 @@ export async function runSync(opts) {
947
985
  for (const [name, wfDef] of Object.entries(workflows)) {
948
986
  const result = compileWorkflow(wfDef, { workflow_key: name });
949
987
  if (result.ok) {
950
- skillResults.push(compileWorkflowToSkill(result.plan, roles, ir, variables, { surface: skillSurface }));
988
+ const workflowVariables = variableScopes.forNamespace(variableScopes.workflowNamespaces.get(name) ?? "");
989
+ skillResults.push(compileWorkflowToSkill(result.plan, roles, ir, workflowVariables, { surface: skillSurface }));
951
990
  }
952
991
  }
953
992
  if (skillSurface === "claude_code") {
954
993
  for (const [name, controllerDef] of Object.entries(controllers)) {
955
994
  const controllerKind = getControllerKindMetadata(controllerDef.kind);
956
995
  if (controllerKind?.supportsSkillGeneration) {
957
- skillResults.push(compileControllerToSkill(name, controllerDef, variables, workflows));
996
+ const controllerVariables = variableScopes.forNamespace(variableScopes.controllerNamespaces.get(name) ?? "");
997
+ skillResults.push(compileControllerToSkill(name, controllerDef, controllerVariables, workflows));
958
998
  }
959
999
  }
960
1000
  }
package/dist/cli/index.js CHANGED
File without changes