intentdna 1.7.4 → 1.8.0

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 (152) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/LICENSE +201 -0
  4. package/README.md +143 -194
  5. package/dist/assets/candidates.d.ts +124 -0
  6. package/dist/assets/candidates.js +1249 -0
  7. package/dist/assets/catalog.d.ts +101 -0
  8. package/dist/assets/catalog.js +311 -0
  9. package/dist/assets/evidence.d.ts +93 -0
  10. package/dist/assets/evidence.js +391 -0
  11. package/dist/assets/index.d.ts +4 -0
  12. package/dist/assets/index.js +4 -0
  13. package/dist/assets/metadata.d.ts +19 -0
  14. package/dist/assets/metadata.js +227 -0
  15. package/dist/cli/commands/assets.d.ts +65 -0
  16. package/dist/cli/commands/assets.js +2871 -0
  17. package/dist/cli/commands/auth.d.ts +322 -0
  18. package/dist/cli/commands/auth.js +1592 -0
  19. package/dist/cli/commands/cloud-auth-guidance.d.ts +2 -0
  20. package/dist/cli/commands/cloud-auth-guidance.js +4 -0
  21. package/dist/cli/commands/cloud.d.ts +1359 -0
  22. package/dist/cli/commands/cloud.js +8364 -0
  23. package/dist/cli/commands/evolve.js +18 -2
  24. package/dist/cli/commands/generate.js +25 -2
  25. package/dist/cli/commands/guard.d.ts +1 -1
  26. package/dist/cli/commands/guard.js +1 -1
  27. package/dist/cli/commands/guide.d.ts +15 -0
  28. package/dist/cli/commands/guide.js +1747 -0
  29. package/dist/cli/commands/init.d.ts +12 -0
  30. package/dist/cli/commands/init.js +95 -60
  31. package/dist/cli/commands/org.d.ts +16 -0
  32. package/dist/cli/commands/org.js +2393 -0
  33. package/dist/cli/commands/release.d.ts +59 -0
  34. package/dist/cli/commands/release.js +716 -0
  35. package/dist/cli/commands/report.d.ts +82 -0
  36. package/dist/cli/commands/report.js +1516 -0
  37. package/dist/cli/commands/run.d.ts +6 -1
  38. package/dist/cli/commands/run.js +95 -26
  39. package/dist/cli/commands/sync.d.ts +8 -1
  40. package/dist/cli/commands/sync.js +436 -141
  41. package/dist/cli/commands/templates.d.ts +64 -0
  42. package/dist/cli/commands/templates.js +2151 -1
  43. package/dist/cli/commands/workflow.d.ts +26 -0
  44. package/dist/cli/commands/workflow.js +3053 -0
  45. package/dist/cli/index.js +719 -42
  46. package/dist/compiler/cascade.js +4 -9
  47. package/dist/compiler/compile.js +1 -0
  48. package/dist/governance/evidence-capture-attribution.d.ts +12 -0
  49. package/dist/governance/evidence-capture-attribution.js +46 -0
  50. package/dist/governance/index.d.ts +2 -0
  51. package/dist/governance/index.js +1 -0
  52. package/dist/hooks/cli.d.ts +6 -0
  53. package/dist/hooks/cli.js +168 -12
  54. package/dist/hooks/enforce.d.ts +1 -0
  55. package/dist/hooks/protocol.d.ts +1 -1
  56. package/dist/hooks/protocol.js +1 -1
  57. package/dist/hooks/state.d.ts +41 -0
  58. package/dist/hooks/state.js +126 -2
  59. package/dist/index.d.ts +3 -0
  60. package/dist/index.js +3 -0
  61. package/dist/mcp/tools-state.js +2 -0
  62. package/dist/organization/index.d.ts +121 -0
  63. package/dist/organization/index.js +413 -0
  64. package/dist/runtime/claude-sync-target.d.ts +49 -0
  65. package/dist/runtime/claude-sync-target.js +280 -0
  66. package/dist/runtime/codex-adapter.d.ts +254 -0
  67. package/dist/runtime/codex-adapter.js +1204 -0
  68. package/dist/runtime/codex-sync-target.d.ts +33 -0
  69. package/dist/runtime/codex-sync-target.js +241 -0
  70. package/dist/runtime/index.d.ts +8 -0
  71. package/dist/runtime/index.js +4 -0
  72. package/dist/runtime/output-artifact-registry.d.ts +1 -1
  73. package/dist/runtime/output-artifact-registry.js +2 -0
  74. package/dist/runtime/settings-adapter.d.ts +1 -1
  75. package/dist/runtime/settings-adapter.js +1 -1
  76. package/dist/runtime/skill-adapter.d.ts +5 -1
  77. package/dist/runtime/skill-adapter.js +120 -34
  78. package/dist/runtime/sync-artifact-registry.d.ts +99 -0
  79. package/dist/runtime/sync-artifact-registry.js +194 -0
  80. package/dist/runtime/sync-target-plan.d.ts +45 -0
  81. package/dist/runtime/sync-target-plan.js +19 -0
  82. package/dist/schema/controller-registry.d.ts +8 -1
  83. package/dist/schema/controller-registry.js +8 -0
  84. package/dist/schema/types.d.ts +35 -23
  85. package/dist/schema/types.js +10 -1
  86. package/dist/schema/validate.js +22 -22
  87. package/dist/schema/validators/controllers.js +38 -20
  88. package/dist/schema/workflow-authoring-contract.d.ts +29 -0
  89. package/dist/schema/workflow-authoring-contract.js +109 -0
  90. package/dist/schema/yaml-parser.js +18 -0
  91. package/dist/templates/api-backend.dna.yaml +19 -0
  92. package/dist/templates/brainstorming-first.dna.yaml +19 -0
  93. package/dist/templates/code-cleanup.dna.yaml +19 -0
  94. package/dist/templates/code-review-pipeline.dna.yaml +18 -0
  95. package/dist/templates/documentation-writer.dna.yaml +19 -0
  96. package/dist/templates/flutter-behavior-lock.dna.yaml +19 -0
  97. package/dist/templates/flutter-refactoring-rescue.dna.yaml +19 -0
  98. package/dist/templates/flutter-rewrite-diagnosis-review.dna.yaml +210 -0
  99. package/dist/templates/flutter-rewrite-fix-review-loop.dna.yaml +260 -0
  100. package/dist/templates/flutter-rewrite-new.dna.yaml +506 -0
  101. package/dist/templates/flutter-rewrite.dna.yaml +20 -0
  102. package/dist/templates/frontend-quality.dna.yaml +19 -0
  103. package/dist/templates/full-pipeline.dna.yaml +20 -0
  104. package/dist/templates/marketplace.d.ts +92 -0
  105. package/dist/templates/marketplace.js +502 -0
  106. package/dist/templates/metadata.d.ts +25 -0
  107. package/dist/templates/metadata.js +136 -3
  108. package/dist/templates/mobile-dev.dna.yaml +19 -0
  109. package/dist/templates/multi-agent-handoff-coordination.dna.yaml +156 -0
  110. package/dist/templates/multi-perspective-review.dna.yaml +19 -0
  111. package/dist/templates/persistent-executor.dna.yaml +19 -0
  112. package/dist/templates/qa-loop.dna.yaml +18 -0
  113. package/dist/templates/release-evidence-gate.dna.yaml +131 -0
  114. package/dist/templates/requirements-gate.dna.yaml +19 -0
  115. package/dist/templates/research-orchestration.dna.yaml +19 -0
  116. package/dist/templates/root-cause-analysis.dna.yaml +19 -0
  117. package/dist/templates/safe-refactoring.dna.yaml +321 -0
  118. package/dist/templates/secure-dev.dna.yaml +19 -0
  119. package/dist/templates/subagent-parallel.dna.yaml +19 -0
  120. package/dist/templates/systematic-debugging.dna.yaml +19 -0
  121. package/dist/templates/tdd-strict.dna.yaml +19 -0
  122. package/dist/templates/ui-visual-regression-qa.dna.yaml +133 -0
  123. package/dist/templates/workflow-author.dna.yaml +302 -0
  124. package/dist/templates/workflow-blocked-scope-triage.dna.yaml +242 -0
  125. package/dist/templates/workflow-completion-audit.dna.yaml +254 -0
  126. package/dist/templates/workflow-cross-boundary-resource-approval.dna.yaml +249 -0
  127. package/dist/templates/workflow-evidence-review.dna.yaml +198 -0
  128. package/dist/templates/workflow-module-inventory.dna.yaml +257 -0
  129. package/dist/templates/workflow-runtime-smoke-review.dna.yaml +252 -0
  130. package/dist/templates/workflow-verification-command-routing.dna.yaml +278 -0
  131. package/dist/workflow/authoring-packet.d.ts +67 -0
  132. package/dist/workflow/authoring-packet.js +218 -0
  133. package/dist/workflow/draft.d.ts +159 -0
  134. package/dist/workflow/draft.js +800 -0
  135. package/dist/workflow/index.d.ts +3 -0
  136. package/dist/workflow/index.js +3 -0
  137. package/dist/workflow/suggestion-review.d.ts +65 -0
  138. package/dist/workflow/suggestion-review.js +245 -0
  139. package/package.json +1 -1
  140. package/spec/README.md +15 -48
  141. package/spec/codex-adapter-contract.md +176 -0
  142. package/spec/sync-target-plan.md +158 -0
  143. package/spec/workflow-capability-ir.md +153 -0
  144. package/spec/agent-dispatch-fix.md +0 -116
  145. package/spec/control-plane-convergence-handoff-2026-04-24.md +0 -432
  146. package/spec/flutter-rewrite-template-optimization.md +0 -301
  147. package/spec/foundation-hardening.md +0 -178
  148. package/spec/hooks-infra-harness-hardening.md +0 -741
  149. package/spec/multi-config.md +0 -172
  150. package/spec/parallel-isolation.md +0 -268
  151. package/spec/template-version-namespace-fix.md +0 -653
  152. package/spec/workflow-pipeline.md +0 -101
@@ -20,14 +20,19 @@ import { findTemplateCatalogEntry } from "../../templates/catalog.js";
20
20
  import { compileToMarkdown, injectIntoFile, removeFromFile } from "../../runtime/markdown.js";
21
21
  import { syncInjectMarkdownTargets } from "../../runtime/markdown-target-registry.js";
22
22
  import { getSyncOutputArtifactMetadata } from "../../runtime/output-artifact-registry.js";
23
+ import { inspectSyncArtifactRegistry, removeSyncArtifacts, writeSyncArtifactRegistry, } from "../../runtime/sync-artifact-registry.js";
23
24
  import { compileWorkflow } from "../../compiler/workflow.js";
24
25
  import { compileAllRolesToAgentMD, writeAgentMDFiles, removeAgentMDFiles } from "../../runtime/agent-md.js";
26
+ import { createClaudeCoreSyncTargetPlan, executeClaudeCoreSyncTargetPlan, formatClaudeCoreSyncTargetPlanDryRun, removeLegacyBashHooks } from "../../runtime/claude-sync-target.js";
25
27
  import { removeWorkflowScripts } from "../../runtime/workflow-runner.js";
26
28
  import { compileWorkflowToSkill, compileControllerToSkill, writeSkillFiles, removeSkillFiles } from "../../runtime/skill-adapter.js";
27
- import { compilePluginSettings, detectEnabledEvents, mergeSettingsFile, recommendHookTimeouts } from "../../runtime/settings-adapter.js";
28
- import { createCompiledIR, writeCompiledIR } from "../../runtime/plugin-adapter.js";
29
+ import { getControllerKindMetadata } from "../../schema/controller-registry.js";
30
+ import { createCodexSyncTargetPlan, executeCodexSyncTargetPlan, formatCodexSyncTargetPlanSummary, removeCodexSyncTargetArtifacts } from "../../runtime/codex-sync-target.js";
31
+ import { trackedOutputsForSyncTargetPlan } from "../../runtime/sync-target-plan.js";
32
+ import { getPackageVersion } from "../util/version.js";
29
33
  import { cascadeDNA } from "../../compiler/cascade.js";
30
34
  import { EvolutionEngine } from "../../evolution/index.js";
35
+ import { readOrganizationProfile, reviewOrganization } from "../../organization/index.js";
31
36
  const VALID_TARGETS = syncInjectMarkdownTargets();
32
37
  /**
33
38
  * Detect whether intentdna is running as a Claude Code plugin or standalone binary.
@@ -66,33 +71,6 @@ async function fileExists(path) {
66
71
  }
67
72
  }
68
73
  const DNA_SENTINEL = "intentdna:managed";
69
- const LEGACY_BASH_HOOKS = [
70
- "dna-pre-tool-use.sh",
71
- "dna-post-tool-use.sh",
72
- "dna-user-prompt-submit.sh",
73
- "dna-subagent-stop.sh",
74
- "dna-pre-compact.sh",
75
- "dna-notification.sh",
76
- ];
77
- /**
78
- * Remove legacy bash hook scripts written by previous Intent DNA versions.
79
- * Only removes files that contain the sentinel comment.
80
- */
81
- async function removeLegacyBashHooks(hooksDir) {
82
- let removed = 0;
83
- for (const file of LEGACY_BASH_HOOKS) {
84
- const path = resolve(hooksDir, file);
85
- try {
86
- const content = await readFile(path, "utf-8");
87
- if (content.includes("intentdna:managed")) {
88
- await unlink(path);
89
- removed++;
90
- }
91
- }
92
- catch { /* File doesn't exist, skip */ }
93
- }
94
- return removed;
95
- }
96
74
  /**
97
75
  * Remove stale DNA-managed files from a directory.
98
76
  * Only removes files matching activeNamespaces (dna-{ns}-*).
@@ -193,8 +171,8 @@ async function resolveVariables(rawVars, configPath, options) {
193
171
  * Auto-detect DNA config file in current directory (legacy single-config).
194
172
  * Priority: .dna/config.yaml > .dna/config.yml > .dna/config.json > .dna.yaml > .dna.yml > .dna.json
195
173
  */
196
- export async function autoDetectConfigs() {
197
- return detectDNAConfigs(process.cwd());
174
+ export async function autoDetectConfigs(projectDir = process.cwd()) {
175
+ return detectDNAConfigs(projectDir);
198
176
  }
199
177
  /**
200
178
  * Read the current package version from package.json.
@@ -292,11 +270,32 @@ async function computeFileHash(filePath) {
292
270
  const content = await readFile(filePath, "utf-8");
293
271
  return fileSha256(content);
294
272
  }
295
- function trackSyncOutput(trackedOutputs, artifact, path) {
273
+ function trackSyncOutput(trackedOutputs, artifact, path, metadata) {
296
274
  if (!getSyncOutputArtifactMetadata(artifact)) {
297
275
  throw new Error(`Unknown sync output artifact: ${artifact}`);
298
276
  }
299
- trackedOutputs.push({ artifact, path: resolve(path) });
277
+ trackedOutputs.push({ artifact, path, ...metadata });
278
+ }
279
+ function trackSyncTargetPlanOutputs(trackedOutputs, plan, projectDir) {
280
+ const installMode = "hookInstallMode" in plan && typeof plan.hookInstallMode === "string"
281
+ ? plan.hookInstallMode
282
+ : undefined;
283
+ for (const output of trackedOutputsForSyncTargetPlan(plan)) {
284
+ const planned = plan.plannedArtifacts.find((artifact) => {
285
+ if (artifact.outputArtifact !== output.artifact)
286
+ return false;
287
+ return artifact.path === output.path || resolve(artifact.path) === resolve(output.path);
288
+ });
289
+ const outputPath = resolve(projectDir, output.path);
290
+ trackSyncOutput(trackedOutputs, output.artifact, outputPath, {
291
+ kind: planned?.kind,
292
+ target: plan.target,
293
+ adapter: plan.target,
294
+ installMode,
295
+ ownership: planned?.ownership,
296
+ trackedByLock: planned?.trackedByLock,
297
+ });
298
+ }
300
299
  }
301
300
  function summarizeSyncOutputs(trackedOutputs, dryRun) {
302
301
  if (trackedOutputs.length === 0)
@@ -318,6 +317,122 @@ function lockTrackedOutputPaths(trackedOutputs) {
318
317
  .filter((output) => getSyncOutputArtifactMetadata(output.artifact)?.trackedByLock)
319
318
  .map((output) => output.path);
320
319
  }
320
+ function buildSyncRunJsonResult(opts, projectDir, files, trackedOutputs, registryArtifacts) {
321
+ const targets = [...new Set(trackedOutputs.map((output) => output.adapter ?? output.target ?? "generic"))].sort();
322
+ return {
323
+ schema_version: "intentdna.sync_result.v1",
324
+ mode: "sync",
325
+ status: opts.dryRun ? "dry_run" : "synced",
326
+ dryRun: opts.dryRun === true,
327
+ projectDir,
328
+ files,
329
+ commandEffects: {
330
+ mutation_performed: opts.dryRun !== true,
331
+ network_performed: false,
332
+ runtime_authority: false,
333
+ syncs_harness: opts.dryRun !== true,
334
+ executes_workflow: false,
335
+ },
336
+ targets,
337
+ counts: {
338
+ tracked_outputs: trackedOutputs.length,
339
+ lock_tracked_outputs: lockTrackedOutputPaths(trackedOutputs).length,
340
+ registry_artifacts: registryArtifacts,
341
+ },
342
+ outputs: trackedOutputs.map((output) => {
343
+ const metadata = getSyncOutputArtifactMetadata(output.artifact);
344
+ return {
345
+ artifact: output.artifact,
346
+ label: metadata?.label ?? output.artifact,
347
+ path: output.path,
348
+ kind: output.kind,
349
+ target: output.target,
350
+ adapter: output.adapter,
351
+ installMode: output.installMode,
352
+ ownership: output.ownership,
353
+ trackedByLock: metadata?.trackedByLock === true,
354
+ };
355
+ }),
356
+ registryPath: opts.dryRun ? undefined : resolve(projectDir, ".dna", "sync", "registry.json"),
357
+ boundaries: [
358
+ "dna sync installs, distributes, and injects local artifacts only; it does not execute workflows.",
359
+ "dna-hook and local runtime evidence remain runtime truth for allow/warn/block/validate decisions.",
360
+ "Online and marketplace surfaces do not become runtime authority from sync output.",
361
+ "Network upload, Supabase deployment, and trusted cloud ingestion are not performed by dna sync.",
362
+ ],
363
+ };
364
+ }
365
+ function canonicalOrganizationSyncTarget(target) {
366
+ const normalized = target.toLowerCase();
367
+ if (normalized === "codex" || normalized === "codex_cli")
368
+ return "codex";
369
+ if (normalized === "claude" || normalized === "claude_code")
370
+ return "claude";
371
+ return normalized;
372
+ }
373
+ function requestedOrganizationSyncTargets(opts) {
374
+ const targets = new Set();
375
+ if (opts.codex)
376
+ targets.add("codex");
377
+ if (opts.inject || opts.hooksDir || opts.agentsDir || opts.workflowDir || opts.skillsDir || opts.settingsPath) {
378
+ targets.add("claude");
379
+ }
380
+ return targets;
381
+ }
382
+ function syncTargetMatches(requestedTargets, actualTargets) {
383
+ return actualTargets.some((target) => requestedTargets.has(canonicalOrganizationSyncTarget(target)));
384
+ }
385
+ async function writeOrganizationRolloutGateIfBlocked(opts, projectDir) {
386
+ const requestedTargets = requestedOrganizationSyncTargets(opts);
387
+ if (requestedTargets.size === 0)
388
+ return;
389
+ const profile = await readOrganizationProfile({ projectDir });
390
+ if (!profile.profile)
391
+ return;
392
+ const profileTargets = profile.profile.projects.flatMap((project) => project.syncTargets);
393
+ if (!syncTargetMatches(requestedTargets, profileTargets))
394
+ return;
395
+ const review = await reviewOrganization({ projectDir });
396
+ if (!review)
397
+ return;
398
+ const blockedGates = review.projects.flatMap((project) => project.assetGates
399
+ .filter((gate) => !gate.syncReady && syncTargetMatches(requestedTargets, gate.syncTargets))
400
+ .map((gate) => ({
401
+ project: project.name,
402
+ asset: gate.asset,
403
+ evidenceGate: gate.evidenceGate,
404
+ syncTargets: gate.syncTargets,
405
+ nextCommand: gate.nextCommand,
406
+ reason: gate.reason,
407
+ })));
408
+ if (blockedGates.length === 0)
409
+ return;
410
+ const blockedTargets = [...new Set(blockedGates.flatMap((gate) => gate.syncTargets
411
+ .map(canonicalOrganizationSyncTarget)
412
+ .filter((target) => requestedTargets.has(target))))].sort().join(", ");
413
+ const primaryCommand = organizationPrimarySyncBlockCommand(blockedGates);
414
+ process.stderr.write("Organization rollout gate:\n");
415
+ process.stderr.write(` Status: blocked for ${blockedTargets}\n`);
416
+ process.stderr.write(` Primary command: ${primaryCommand}\n`);
417
+ process.stderr.write(" Blocked assets:\n");
418
+ for (const gate of blockedGates.slice(0, 5)) {
419
+ process.stderr.write(` - ${gate.project}/${gate.asset}: ${gate.reason}\n`);
420
+ process.stderr.write(` next: ${gate.nextCommand}\n`);
421
+ }
422
+ process.stderr.write(` Resolve before sync: ${primaryCommand}\n`);
423
+ process.stderr.write(" Recheck before sync: dna org rollout\n");
424
+ process.stderr.write(" Boundary: adapter output below is diagnostic until organization rollout shows the target ready.\n");
425
+ }
426
+ function organizationPrimarySyncBlockCommand(blockedGates) {
427
+ const hasOpenEvidence = blockedGates.some((gate) => gate.evidenceGate === "open");
428
+ if (hasOpenEvidence)
429
+ return blockedGates[0]?.nextCommand ?? "dna org review";
430
+ const hasMissingConfiguration = blockedGates.some((gate) => gate.evidenceGate !== "open" &&
431
+ gate.nextCommand.startsWith("dna init --template "));
432
+ if (hasMissingConfiguration)
433
+ return "dna org configure-plan";
434
+ return blockedGates[0]?.nextCommand ?? "dna org review";
435
+ }
321
436
  async function generateLockFile(lockPath, dnaSources, outputFiles, provenance, compiledIrHash) {
322
437
  const outputs = {};
323
438
  for (const f of outputFiles) {
@@ -333,10 +448,152 @@ async function generateLockFile(lockPath, dnaSources, outputFiles, provenance, c
333
448
  await mkdir(dirname(lockPath), { recursive: true });
334
449
  await writeFileAsync(lockPath, JSON.stringify(lock, null, 2) + "\n", "utf-8");
335
450
  }
451
+ function syncRegistryHealth(inspection) {
452
+ if (!inspection.registry)
453
+ return "missing_registry";
454
+ if (inspection.totals.missing > 0 || inspection.totals.drifted > 0 || inspection.totals.untracked > 0) {
455
+ return "attention_required";
456
+ }
457
+ return "ok";
458
+ }
459
+ function syncRegistryCommandEffects() {
460
+ return {
461
+ mutation_performed: false,
462
+ network_performed: false,
463
+ runtime_authority: false,
464
+ syncs_harness: false,
465
+ executes_workflow: false,
466
+ };
467
+ }
468
+ function syncRegistryBoundaries() {
469
+ return [
470
+ "dna sync status and doctor inspect local registry artifacts only.",
471
+ "They do not execute workflows or make allow/warn/block/validate decisions.",
472
+ "They do not call Online, Supabase, marketplace, or hidden transport.",
473
+ "Runtime truth remains local dna/dna-hook and the synced harness artifacts.",
474
+ ];
475
+ }
476
+ function syncRegistryNextCommands(status) {
477
+ if (status === "missing_registry")
478
+ return ["dna sync --dry-run", "dna sync"];
479
+ if (status === "attention_required")
480
+ return ["dna sync", "dna sync doctor", "dna sync remove --dry-run"];
481
+ return ["dna sync doctor", "dna sync remove --dry-run"];
482
+ }
483
+ function printSyncRegistryStatus(json = false, projectDir = process.cwd()) {
484
+ return inspectSyncArtifactRegistry(projectDir).then((inspection) => {
485
+ if (json) {
486
+ const status = syncRegistryHealth(inspection);
487
+ const packet = {
488
+ schema_version: "intentdna.sync_status.v1",
489
+ mode: "sync_registry_status",
490
+ status,
491
+ ok: status === "ok",
492
+ projectDir,
493
+ commandEffects: syncRegistryCommandEffects(),
494
+ boundaries: syncRegistryBoundaries(),
495
+ nextCommands: syncRegistryNextCommands(status),
496
+ ...inspection,
497
+ };
498
+ process.stdout.write(JSON.stringify(packet, null, 2) + "\n");
499
+ return inspection.registry ? 0 : 1;
500
+ }
501
+ if (!inspection.registry) {
502
+ process.stderr.write(`No sync artifact registry found at ${inspection.registryPath}\n`);
503
+ process.stderr.write("Run `dna sync` to install runtime artifacts before checking status.\n");
504
+ return 1;
505
+ }
506
+ process.stderr.write("IntentDNA sync artifact status\n");
507
+ process.stderr.write(`Registry: ${inspection.registryPath}\n`);
508
+ process.stderr.write(`Synced: ${inspection.registry.syncedAt}\n`);
509
+ process.stderr.write(`DNA sources: ${inspection.registry.template.sourceDnaIds.join(", ") || "unknown"}\n`);
510
+ process.stderr.write(`Artifacts: ${inspection.totals.artifacts} total, ${inspection.totals.ok} ok, ${inspection.totals.missing} missing, ${inspection.totals.drifted} drifted, ${inspection.totals.untracked} untracked\n`);
511
+ for (const artifact of inspection.artifacts) {
512
+ process.stderr.write(` - [${artifact.status}] ${artifact.relativePath} (${artifact.kind}, ${artifact.ownership})\n`);
513
+ if (artifact.status !== "ok")
514
+ process.stderr.write(` ${artifact.reason}\n`);
515
+ }
516
+ return inspection.totals.missing > 0 || inspection.totals.drifted > 0 ? 1 : 0;
517
+ });
518
+ }
519
+ async function runSyncRegistryDoctor(json = false, projectDir = process.cwd()) {
520
+ const inspection = await inspectSyncArtifactRegistry(projectDir);
521
+ const ok = !!inspection.registry && inspection.totals.missing === 0 && inspection.totals.drifted === 0;
522
+ if (json) {
523
+ const status = syncRegistryHealth(inspection);
524
+ const packet = {
525
+ schema_version: "intentdna.sync_doctor.v1",
526
+ mode: "sync_registry_doctor",
527
+ status,
528
+ ok,
529
+ projectDir,
530
+ commandEffects: syncRegistryCommandEffects(),
531
+ boundaries: syncRegistryBoundaries(),
532
+ nextCommands: syncRegistryNextCommands(status),
533
+ ...inspection,
534
+ };
535
+ process.stdout.write(JSON.stringify(packet, null, 2) + "\n");
536
+ return ok ? 0 : 1;
537
+ }
538
+ if (!inspection.registry) {
539
+ process.stderr.write(`Sync doctor: no registry found at ${inspection.registryPath}\n`);
540
+ process.stderr.write("Next: run `dna sync` for the target project.\n");
541
+ return 1;
542
+ }
543
+ process.stderr.write(`Sync doctor: ${ok ? "ok" : "attention required"}\n`);
544
+ process.stderr.write(`Registry: ${inspection.registryPath}\n`);
545
+ if (ok) {
546
+ process.stderr.write(`All ${inspection.totals.artifacts} artifact(s) match the last sync.\n`);
547
+ return 0;
548
+ }
549
+ for (const artifact of inspection.artifacts.filter((entry) => entry.status !== "ok")) {
550
+ process.stderr.write(` - [${artifact.status}] ${artifact.relativePath}: ${artifact.reason}\n`);
551
+ }
552
+ process.stderr.write("Next: rerun `dna sync` to repair drift, or `dna sync remove` to clean safe IntentDNA-owned artifacts.\n");
553
+ return 1;
554
+ }
555
+ async function runSyncRegistryRemove(opts) {
556
+ const result = await removeSyncArtifacts({ dryRun: opts.dryRun, projectDir: opts.projectDir });
557
+ if (opts.json) {
558
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
559
+ return 0;
560
+ }
561
+ process.stderr.write(opts.dryRun ? "Dry-run registry remove plan:\n" : "IntentDNA registry remove:\n");
562
+ if (result.removed.length === 0) {
563
+ process.stderr.write(" No safe registry-tracked artifacts removed.\n");
564
+ }
565
+ else {
566
+ for (const path of result.removed)
567
+ process.stderr.write(` removed: ${path}\n`);
568
+ }
569
+ for (const skipped of result.skipped) {
570
+ process.stderr.write(` skipped: ${skipped.path} (${skipped.reason})\n`);
571
+ }
572
+ if (result.preservation.kept.length > 0) {
573
+ process.stderr.write("Preserved files:\n");
574
+ for (const kept of result.preservation.kept) {
575
+ process.stderr.write(` - ${kept.path}\n`);
576
+ process.stderr.write(` reason: ${kept.reason}\n`);
577
+ process.stderr.write(` why: ${kept.explanation}\n`);
578
+ }
579
+ process.stderr.write("Next safe checks:\n");
580
+ for (const command of result.preservation.nextCommands)
581
+ process.stderr.write(` - ${command}\n`);
582
+ }
583
+ process.stderr.write(`Boundary: ${result.boundary}.\n`);
584
+ return 0;
585
+ }
336
586
  export async function runSync(opts) {
587
+ const projectDir = resolve(opts.projectDir ?? process.cwd());
588
+ if (opts.action === "status")
589
+ return printSyncRegistryStatus(opts.json, projectDir);
590
+ if (opts.action === "doctor")
591
+ return runSyncRegistryDoctor(opts.json, projectDir);
592
+ if (opts.action === "remove-registry")
593
+ return runSyncRegistryRemove({ ...opts, projectDir });
337
594
  // ── Auto-detect mode: zero-config when no files/outputs specified ──
338
595
  if (!opts.remove && opts.files.length === 0) {
339
- const autoFiles = await autoDetectConfigs();
596
+ const autoFiles = await autoDetectConfigs(projectDir);
340
597
  if (autoFiles.length > 0) {
341
598
  opts.files = autoFiles;
342
599
  if (autoFiles.length === 1) {
@@ -351,16 +608,19 @@ export async function runSync(opts) {
351
608
  }
352
609
  }
353
610
  }
354
- if (!opts.remove && !opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir && !opts.settingsPath) {
611
+ if (!opts.remove && opts.codex && !opts.skillsDir) {
612
+ opts.skillsDir = resolve(projectDir, ".agents", "skills");
613
+ process.stderr.write("Codex workflow projection: .agents/skills\n");
614
+ }
615
+ if (!opts.remove && !opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir && !opts.settingsPath && !opts.codex) {
355
616
  // Auto-detect Claude Code environment → fill all outputs
356
- const cwd = process.cwd();
357
- const hasClaudeDir = await fileExists(resolve(cwd, ".claude"));
358
- const hasClaudeMd = await fileExists(resolve(cwd, "CLAUDE.md"));
617
+ const hasClaudeDir = await fileExists(resolve(projectDir, ".claude"));
618
+ const hasClaudeMd = await fileExists(resolve(projectDir, "CLAUDE.md"));
359
619
  if (hasClaudeDir || hasClaudeMd || opts.files.length > 0) {
360
- opts.inject = resolve(cwd, "CLAUDE.md");
361
- opts.agentsDir = resolve(cwd, ".claude", "agents");
362
- opts.skillsDir = resolve(cwd, ".claude", "skills");
363
- opts.settingsPath = resolve(cwd, ".claude", "settings.json");
620
+ opts.inject = resolve(projectDir, "CLAUDE.md");
621
+ opts.agentsDir = resolve(projectDir, ".claude", "agents");
622
+ opts.skillsDir = resolve(projectDir, ".claude", "skills");
623
+ opts.settingsPath = resolve(projectDir, ".claude", "settings.json");
364
624
  process.stderr.write(`Auto-detect: Claude Code → CLAUDE.md + agents + skills + settings\n`);
365
625
  }
366
626
  }
@@ -373,8 +633,8 @@ export async function runSync(opts) {
373
633
  opts.plugin = resolvedMode === "plugin";
374
634
  // Handle --remove
375
635
  if (opts.remove) {
376
- if (!opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir) {
377
- process.stderr.write("Error: --inject, --hooks, --agents, --workflow, or --skills is required with --remove\n");
636
+ if (!opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir && !opts.codex) {
637
+ process.stderr.write("Error: --inject, --hooks, --agents, --workflow, --skills, or --codex is required with --remove\n");
378
638
  return 2;
379
639
  }
380
640
  if (opts.inject) {
@@ -387,9 +647,9 @@ export async function runSync(opts) {
387
647
  }
388
648
  }
389
649
  if (opts.hooksDir) {
390
- const count = await removeLegacyBashHooks(opts.hooksDir);
391
- if (count > 0) {
392
- process.stderr.write(`Removed ${count} legacy bash hook(s) from ${opts.hooksDir}\n`);
650
+ const removed = await removeLegacyBashHooks(opts.hooksDir);
651
+ if (removed.length > 0) {
652
+ process.stderr.write(`Removed ${removed.length} legacy bash hook(s) from ${opts.hooksDir}\n`);
393
653
  }
394
654
  else {
395
655
  process.stderr.write(`No legacy bash hooks found in ${opts.hooksDir}\n`);
@@ -422,14 +682,23 @@ export async function runSync(opts) {
422
682
  process.stderr.write(`No Intent DNA skill dirs found in ${opts.skillsDir}\n`);
423
683
  }
424
684
  }
685
+ if (opts.codex) {
686
+ const removed = await removeCodexSyncTargetArtifacts(projectDir);
687
+ if (removed.length > 0) {
688
+ process.stderr.write(`Removed ${removed.length} Intent DNA Codex adapter artifact(s)\n`);
689
+ }
690
+ else {
691
+ process.stderr.write("No Intent DNA Codex adapter artifacts found\n");
692
+ }
693
+ }
425
694
  return 0;
426
695
  }
427
696
  if (opts.files.length === 0) {
428
697
  process.stderr.write("Error: no DNA files specified\n");
429
698
  return 2;
430
699
  }
431
- if (!opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir && !opts.settingsPath) {
432
- process.stderr.write("Error: --inject <path>, --hooks <dir>, --agents <dir>, --workflow <dir>, --skills <dir>, or --settings <path> is required\n");
700
+ if (!opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir && !opts.settingsPath && !opts.codex) {
701
+ process.stderr.write("Error: --inject <path>, --hooks <dir>, --agents <dir>, --workflow <dir>, --skills <dir>, --settings <path>, or --codex is required\n");
433
702
  return 2;
434
703
  }
435
704
  if (opts.inject && !VALID_TARGETS.includes(opts.target)) {
@@ -437,6 +706,22 @@ export async function runSync(opts) {
437
706
  return 2;
438
707
  }
439
708
  const target = opts.target;
709
+ opts.files = opts.files.map((file) => resolve(projectDir, file));
710
+ if (opts.inject)
711
+ opts.inject = resolve(projectDir, opts.inject);
712
+ if (opts.hooksDir)
713
+ opts.hooksDir = resolve(projectDir, opts.hooksDir);
714
+ if (opts.agentsDir)
715
+ opts.agentsDir = resolve(projectDir, opts.agentsDir);
716
+ if (opts.workflowDir)
717
+ opts.workflowDir = resolve(projectDir, opts.workflowDir);
718
+ if (opts.skillsDir)
719
+ opts.skillsDir = resolve(projectDir, opts.skillsDir);
720
+ if (opts.settingsPath)
721
+ opts.settingsPath = resolve(projectDir, opts.settingsPath);
722
+ if (opts.lockFile)
723
+ opts.lockFile = resolve(projectDir, opts.lockFile);
724
+ await writeOrganizationRolloutGateIfBlocked(opts, projectDir);
440
725
  try {
441
726
  // Track all output file paths for lock file generation
442
727
  const trackedOutputs = [];
@@ -489,65 +774,32 @@ export async function runSync(opts) {
489
774
  process.stderr.write(`No changes needed in ${opts.inject}\n`);
490
775
  }
491
776
  }
492
- trackSyncOutput(trackedOutputs, "markdown-injection", opts.inject);
777
+ trackSyncOutput(trackedOutputs, "markdown-injection", opts.inject, {
778
+ kind: "markdown_injection",
779
+ target: "markdown",
780
+ ownership: "owned_block",
781
+ trackedByLock: true,
782
+ });
493
783
  }
494
- // Step 3.5: Write compiled IR (needed for both plugin and bin mode)
784
+ // Step 3.5: Execute Claude Code core target plan (compiled IR, MCP, settings hooks)
495
785
  {
496
- const cwd = process.cwd();
497
- const dnas = loadedDNAs;
498
- const cascadedForIR = cascadeDNA(dnas);
499
- const roles = cascadedForIR.roles;
500
- const compiled = createCompiledIR(ir, roles);
501
- const irPath = resolve(cwd, ".dna", "compiled", "ir.json");
502
- if (opts.dryRun) {
503
- JSON.stringify(compiled);
504
- process.stderr.write(`Dry-run: would write compiled IR: ${irPath}\n`);
505
- }
506
- else {
507
- const writtenIrPath = await writeCompiledIR(cwd, compiled);
508
- process.stderr.write(`Wrote compiled IR: ${writtenIrPath}\n`);
509
- }
510
- trackSyncOutput(trackedOutputs, "compiled-ir", irPath);
511
- // Clean up old bash hooks if they exist
512
- const oldHooksDir = resolve(cwd, ".claude", "hooks");
786
+ const cascadedForCore = cascadeDNA(loadedDNAs);
787
+ const claudeCorePlan = createClaudeCoreSyncTargetPlan({
788
+ projectDir,
789
+ ir,
790
+ roles: cascadedForCore.roles,
791
+ mode: opts.plugin ? "plugin" : "bin",
792
+ settingsPath: opts.settingsPath,
793
+ });
513
794
  if (opts.dryRun) {
514
- process.stderr.write(`Dry-run: would clean legacy bash hooks in ${oldHooksDir}\n`);
795
+ process.stderr.write(formatClaudeCoreSyncTargetPlanDryRun(claudeCorePlan));
515
796
  }
516
797
  else {
517
- const removed = await removeLegacyBashHooks(oldHooksDir);
518
- if (removed > 0) {
519
- process.stderr.write(`Cleaned ${removed} legacy bash hook(s) from ${oldHooksDir}\n`);
520
- }
521
- }
522
- // G2/G3: Register built-in DNA MCP server in .claude/.mcp.json
523
- const mcpPath = resolve(cwd, ".claude", ".mcp.json");
524
- if (opts.dryRun) {
525
- process.stderr.write(`Dry-run: would register dna-mcp server in ${mcpPath}\n`);
798
+ const result = await executeClaudeCoreSyncTargetPlan(claudeCorePlan, projectDir);
799
+ for (const message of result.messages)
800
+ process.stderr.write(`${message}\n`);
526
801
  }
527
- else {
528
- await registerDNAMCPServer(cwd);
529
- }
530
- trackSyncOutput(trackedOutputs, "mcp-config", mcpPath);
531
- }
532
- // Step 3.6: Register hooks in settings.json (bin mode only)
533
- // Plugin mode: hooks managed by plugin framework — skip settings.json
534
- if (!opts.plugin && opts.settingsPath) {
535
- const enabledEvents = detectEnabledEvents(ir);
536
- if (enabledEvents.length > 0) {
537
- const pluginSettings = compilePluginSettings(enabledEvents, 10, recommendHookTimeouts(ir));
538
- if (opts.dryRun) {
539
- JSON.stringify(pluginSettings);
540
- process.stderr.write(`Dry-run: would register ${enabledEvents.length} hook event(s) in ${opts.settingsPath}\n`);
541
- }
542
- else {
543
- await mergeSettingsFile(pluginSettings, opts.settingsPath);
544
- process.stderr.write(`Bin mode: registered ${enabledEvents.length} hook event(s) in ${opts.settingsPath}\n`);
545
- }
546
- trackSyncOutput(trackedOutputs, "settings-hooks", opts.settingsPath);
547
- }
548
- }
549
- else if (opts.plugin && opts.settingsPath) {
550
- process.stderr.write(`Plugin mode: hooks managed by plugin framework (skipping settings.json)\n`);
802
+ trackSyncTargetPlanOutputs(trackedOutputs, claudeCorePlan, projectDir);
551
803
  }
552
804
  // Extract active namespaces from loaded DNAs for namespace-scoped cleanup
553
805
  const activeNamespaces = loadedDNAs
@@ -572,7 +824,8 @@ export async function runSync(opts) {
572
824
  process.stderr.write(`No roles defined in DNA files\n`);
573
825
  }
574
826
  else {
575
- const results = compileAllRolesToAgentMD(roles, ir);
827
+ const agentRoles = Object.fromEntries(Object.entries(roles).filter(([, role]) => !["human", "interactive"].includes(role.model?.trim().toLowerCase() ?? "")));
828
+ const results = compileAllRolesToAgentMD(agentRoles, ir);
576
829
  const written = results.map((result) => resolve(opts.agentsDir, result.fileName));
577
830
  if (opts.dryRun) {
578
831
  process.stderr.write(`Dry-run: would generate ${written.length} agent file(s) in ${opts.agentsDir}\n`);
@@ -585,7 +838,13 @@ export async function runSync(opts) {
585
838
  }
586
839
  }
587
840
  for (const file of written)
588
- trackSyncOutput(trackedOutputs, "agents", file);
841
+ trackSyncOutput(trackedOutputs, "agents", file, {
842
+ kind: "agent_md",
843
+ target: "claude_code",
844
+ adapter: "claude_code",
845
+ ownership: "intentdna_owned",
846
+ trackedByLock: true,
847
+ });
589
848
  }
590
849
  }
591
850
  // Step 6: Generate workflow script(s)
@@ -624,13 +883,19 @@ export async function runSync(opts) {
624
883
  await writeWorkflowScript(shellResult, opts.workflowDir);
625
884
  process.stderr.write(`Generated workflow script: ${written}\n`);
626
885
  }
627
- trackSyncOutput(trackedOutputs, "workflows", written);
886
+ trackSyncOutput(trackedOutputs, "workflows", written, {
887
+ kind: "workflow_script",
888
+ target: "generic",
889
+ ownership: "intentdna_owned",
890
+ trackedByLock: true,
891
+ });
628
892
  }
629
893
  }
630
894
  }
631
895
  }
632
896
  // Step 7: Generate skill files from workflows
633
897
  if (opts.skillsDir) {
898
+ const skillSurface = opts.codex ? "codex_cli" : "claude_code";
634
899
  // Clean stale DNA-managed skills before regenerating
635
900
  if (opts.dryRun) {
636
901
  process.stderr.write(`Dry-run: would clean stale skill dir(s) in ${opts.skillsDir}\n`);
@@ -654,16 +919,15 @@ export async function runSync(opts) {
654
919
  }
655
920
  const configPath = opts.files.length === 1 ? opts.files[0] : null;
656
921
  const variables = await resolveVariables(rawVars, configPath, { dryRun: opts.dryRun });
657
- // Step 8: Generate .claude/.mcp.json from MCP dependencies (inside skillsDir block, shares variables)
658
- {
922
+ // Step 8: Generate .claude/.mcp.json only for Claude Code skills.
923
+ if (skillSurface === "claude_code") {
659
924
  const mcpDeps = {};
660
925
  for (const d of dnas) {
661
926
  if (d.mcp)
662
927
  Object.assign(mcpDeps, d.mcp);
663
928
  }
664
929
  if (Object.keys(mcpDeps).length > 0) {
665
- const cwd = process.cwd();
666
- const mcpJsonPath = resolve(cwd, ".claude", ".mcp.json");
930
+ const mcpJsonPath = resolve(projectDir, ".claude", ".mcp.json");
667
931
  if (opts.dryRun) {
668
932
  process.stderr.write(`Dry-run: would write ${Object.keys(mcpDeps).length} MCP server(s) to ${mcpJsonPath}\n`);
669
933
  }
@@ -681,11 +945,19 @@ export async function runSync(opts) {
681
945
  for (const [name, wfDef] of Object.entries(workflows)) {
682
946
  const result = compileWorkflow(wfDef, { workflow_key: name });
683
947
  if (result.ok) {
684
- skillResults.push(compileWorkflowToSkill(result.plan, roles, ir, variables));
948
+ skillResults.push(compileWorkflowToSkill(result.plan, roles, ir, variables, { surface: skillSurface }));
949
+ }
950
+ }
951
+ if (skillSurface === "claude_code") {
952
+ for (const [name, controllerDef] of Object.entries(controllers)) {
953
+ const controllerKind = getControllerKindMetadata(controllerDef.kind);
954
+ if (controllerKind?.supportsSkillGeneration) {
955
+ skillResults.push(compileControllerToSkill(name, controllerDef, variables, workflows));
956
+ }
685
957
  }
686
958
  }
687
- for (const [name, controllerDef] of Object.entries(controllers)) {
688
- skillResults.push(compileControllerToSkill(name, controllerDef, variables, workflows));
959
+ else if (Object.keys(controllers).length > 0) {
960
+ process.stderr.write("codex_cli unsupported: controller_skill_projection\n");
689
961
  }
690
962
  if (skillResults.length > 0) {
691
963
  let written = skillResults.flatMap((result) => {
@@ -708,7 +980,13 @@ export async function runSync(opts) {
708
980
  }
709
981
  }
710
982
  for (const file of written)
711
- trackSyncOutput(trackedOutputs, "skills-controllers", file);
983
+ trackSyncOutput(trackedOutputs, "skills-controllers", file, {
984
+ kind: "skill_controller",
985
+ target: skillSurface,
986
+ adapter: skillSurface,
987
+ ownership: "intentdna_owned",
988
+ trackedByLock: true,
989
+ });
712
990
  }
713
991
  }
714
992
  }
@@ -717,7 +995,26 @@ export async function runSync(opts) {
717
995
  const gateCount = ir.pre_execution_gates.length;
718
996
  const filterCount = ir.tool_filters.length;
719
997
  process.stderr.write(` ${directiveCount} directives, ${gateCount} gates, ${filterCount} filters\n`);
720
- // Step 9: Generate .dna/lock file (only when --lock is specified or outputs exist)
998
+ // Step 9: Generate Codex CLI adapter artifacts
999
+ if (opts.codex) {
1000
+ const packageVersion = await getPackageVersion() ?? "0.1.0";
1001
+ const codexPlan = createCodexSyncTargetPlan(ir, {
1002
+ hookInstallMode: opts.codexInstallMode ?? "project_fallback",
1003
+ packageVersion,
1004
+ });
1005
+ if (opts.dryRun) {
1006
+ process.stderr.write(`Dry-run: would write Codex CLI adapter artifacts (${codexPlan.hookInstallMode})\n`);
1007
+ process.stderr.write(formatCodexSyncTargetPlanSummary(codexPlan));
1008
+ }
1009
+ else {
1010
+ const written = await executeCodexSyncTargetPlan(codexPlan, { projectDir, dogfood: opts.codexDogfood });
1011
+ process.stderr.write(`Generated ${written.length} Codex CLI adapter artifact(s)\n`);
1012
+ for (const f of written)
1013
+ process.stderr.write(` ${f}\n`);
1014
+ }
1015
+ trackSyncTargetPlanOutputs(trackedOutputs, codexPlan, projectDir);
1016
+ }
1017
+ // Step 10: Generate .dna/lock file (only when --lock is specified or outputs exist)
721
1018
  if (opts.lockFile && trackedOutputs.length > 0) {
722
1019
  const lockPath = resolve(opts.lockFile);
723
1020
  if (opts.dryRun) {
@@ -727,11 +1024,35 @@ export async function runSync(opts) {
727
1024
  await generateLockFile(lockPath, opts.files, lockTrackedOutputPaths(trackedOutputs), ir.provenance, ir.compiled_ir_hash);
728
1025
  process.stderr.write(`Lock file: ${lockPath}\n`);
729
1026
  }
730
- trackSyncOutput(trackedOutputs, "lock-file", lockPath);
1027
+ trackSyncOutput(trackedOutputs, "lock-file", lockPath, {
1028
+ kind: "sync_lock_file",
1029
+ target: "generic",
1030
+ ownership: "diagnostic",
1031
+ trackedByLock: false,
1032
+ });
1033
+ }
1034
+ let registryArtifacts = 0;
1035
+ if (!opts.dryRun && trackedOutputs.length > 0) {
1036
+ const packageVersion = await getPackageVersion() ?? undefined;
1037
+ const registry = await writeSyncArtifactRegistry({
1038
+ projectDir,
1039
+ dnaVersion: packageVersion,
1040
+ template: {
1041
+ sourceDnaIds: ir.source_dna_ids,
1042
+ configFiles: expandedFiles.map((file) => resolve(file)),
1043
+ compiledIrHash: ir.compiled_ir_hash,
1044
+ },
1045
+ artifacts: trackedOutputs,
1046
+ });
1047
+ registryArtifacts = registry.artifacts.length;
1048
+ process.stderr.write(`Sync artifact registry: ${resolve(projectDir, ".dna", "sync", "registry.json")} (${registry.artifacts.length} artifact(s))\n`);
731
1049
  }
732
1050
  if (opts.dryRun || opts.postSummary) {
733
1051
  summarizeSyncOutputs(trackedOutputs, opts.dryRun === true);
734
1052
  }
1053
+ if (opts.json) {
1054
+ process.stdout.write(JSON.stringify(buildSyncRunJsonResult(opts, projectDir, expandedFiles.map((file) => resolve(file)), trackedOutputs, registryArtifacts), null, 2) + "\n");
1055
+ }
735
1056
  return 0;
736
1057
  }
737
1058
  catch (err) {
@@ -787,29 +1108,3 @@ async function writeMCPConfig(mcpDeps, mcpJsonPath, variables) {
787
1108
  function substituteVariables(template, vars) {
788
1109
  return template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
789
1110
  }
790
- /**
791
- * G2/G3: Register the built-in DNA MCP server (dna-mcp) in .claude/.mcp.json.
792
- * Idempotent: only adds if not already present.
793
- */
794
- async function registerDNAMCPServer(cwd) {
795
- const mcpJsonPath = resolve(cwd, ".claude", ".mcp.json");
796
- let existing = {};
797
- try {
798
- const raw = await readFile(mcpJsonPath, "utf-8");
799
- existing = JSON.parse(raw);
800
- }
801
- catch { /* doesn't exist yet */ }
802
- const mcpServers = (existing.mcpServers ?? {});
803
- // Don't overwrite if user already configured it
804
- if (mcpServers["intentdna"])
805
- return mcpJsonPath;
806
- mcpServers["intentdna"] = {
807
- command: "dna-mcp",
808
- args: ["--project-dir", cwd],
809
- };
810
- existing.mcpServers = mcpServers;
811
- await mkdir(dirname(mcpJsonPath), { recursive: true });
812
- await writeFileAsync(mcpJsonPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
813
- process.stderr.write(`MCP: registered dna-mcp server in ${mcpJsonPath}\n`);
814
- return mcpJsonPath;
815
- }