intentdna 1.7.5 → 1.8.1

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 (154) 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 +8 -1
  38. package/dist/cli/commands/run.js +129 -57
  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 +703 -36
  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 +9 -0
  53. package/dist/hooks/cli.js +170 -411
  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 +164 -39
  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/runtime/verifier.d.ts +51 -0
  83. package/dist/runtime/verifier.js +462 -0
  84. package/dist/schema/controller-registry.d.ts +8 -1
  85. package/dist/schema/controller-registry.js +8 -0
  86. package/dist/schema/types.d.ts +35 -23
  87. package/dist/schema/types.js +10 -1
  88. package/dist/schema/validate.js +22 -22
  89. package/dist/schema/validators/controllers.js +38 -20
  90. package/dist/schema/workflow-authoring-contract.d.ts +29 -0
  91. package/dist/schema/workflow-authoring-contract.js +109 -0
  92. package/dist/schema/yaml-parser.js +18 -0
  93. package/dist/templates/api-backend.dna.yaml +19 -0
  94. package/dist/templates/brainstorming-first.dna.yaml +19 -0
  95. package/dist/templates/code-cleanup.dna.yaml +19 -0
  96. package/dist/templates/code-review-pipeline.dna.yaml +18 -0
  97. package/dist/templates/documentation-writer.dna.yaml +19 -0
  98. package/dist/templates/flutter-behavior-lock.dna.yaml +19 -0
  99. package/dist/templates/flutter-refactoring-rescue.dna.yaml +19 -0
  100. package/dist/templates/flutter-rewrite-diagnosis-review.dna.yaml +210 -0
  101. package/dist/templates/flutter-rewrite-fix-review-loop.dna.yaml +260 -0
  102. package/dist/templates/flutter-rewrite-new.dna.yaml +506 -0
  103. package/dist/templates/flutter-rewrite.dna.yaml +100 -4
  104. package/dist/templates/frontend-quality.dna.yaml +19 -0
  105. package/dist/templates/full-pipeline.dna.yaml +20 -0
  106. package/dist/templates/marketplace.d.ts +92 -0
  107. package/dist/templates/marketplace.js +502 -0
  108. package/dist/templates/metadata.d.ts +25 -0
  109. package/dist/templates/metadata.js +136 -3
  110. package/dist/templates/mobile-dev.dna.yaml +19 -0
  111. package/dist/templates/multi-agent-handoff-coordination.dna.yaml +156 -0
  112. package/dist/templates/multi-perspective-review.dna.yaml +19 -0
  113. package/dist/templates/persistent-executor.dna.yaml +19 -0
  114. package/dist/templates/qa-loop.dna.yaml +18 -0
  115. package/dist/templates/release-evidence-gate.dna.yaml +131 -0
  116. package/dist/templates/requirements-gate.dna.yaml +19 -0
  117. package/dist/templates/research-orchestration.dna.yaml +19 -0
  118. package/dist/templates/root-cause-analysis.dna.yaml +19 -0
  119. package/dist/templates/safe-refactoring.dna.yaml +321 -0
  120. package/dist/templates/secure-dev.dna.yaml +19 -0
  121. package/dist/templates/subagent-parallel.dna.yaml +19 -0
  122. package/dist/templates/systematic-debugging.dna.yaml +19 -0
  123. package/dist/templates/tdd-strict.dna.yaml +19 -0
  124. package/dist/templates/ui-visual-regression-qa.dna.yaml +133 -0
  125. package/dist/templates/workflow-author.dna.yaml +302 -0
  126. package/dist/templates/workflow-blocked-scope-triage.dna.yaml +242 -0
  127. package/dist/templates/workflow-completion-audit.dna.yaml +254 -0
  128. package/dist/templates/workflow-cross-boundary-resource-approval.dna.yaml +249 -0
  129. package/dist/templates/workflow-evidence-review.dna.yaml +198 -0
  130. package/dist/templates/workflow-module-inventory.dna.yaml +257 -0
  131. package/dist/templates/workflow-runtime-smoke-review.dna.yaml +252 -0
  132. package/dist/templates/workflow-verification-command-routing.dna.yaml +278 -0
  133. package/dist/workflow/authoring-packet.d.ts +67 -0
  134. package/dist/workflow/authoring-packet.js +218 -0
  135. package/dist/workflow/draft.d.ts +159 -0
  136. package/dist/workflow/draft.js +800 -0
  137. package/dist/workflow/index.d.ts +3 -0
  138. package/dist/workflow/index.js +3 -0
  139. package/dist/workflow/suggestion-review.d.ts +65 -0
  140. package/dist/workflow/suggestion-review.js +245 -0
  141. package/package.json +1 -1
  142. package/spec/README.md +15 -48
  143. package/spec/codex-adapter-contract.md +176 -0
  144. package/spec/sync-target-plan.md +158 -0
  145. package/spec/workflow-capability-ir.md +153 -0
  146. package/spec/agent-dispatch-fix.md +0 -116
  147. package/spec/control-plane-convergence-handoff-2026-04-24.md +0 -432
  148. package/spec/flutter-rewrite-template-optimization.md +0 -301
  149. package/spec/foundation-hardening.md +0 -178
  150. package/spec/hooks-infra-harness-hardening.md +0 -741
  151. package/spec/multi-config.md +0 -172
  152. package/spec/parallel-isolation.md +0 -268
  153. package/spec/template-version-namespace-fix.md +0 -653
  154. package/spec/workflow-pipeline.md +0 -101
@@ -19,26 +19,54 @@ function resolveContainedPath(baseDir, ...segments) {
19
19
  }
20
20
  return target;
21
21
  }
22
+ function isHumanRole(role) {
23
+ return role?.model?.trim().toLowerCase() === "human";
24
+ }
25
+ function isInteractiveRole(role) {
26
+ return role?.model?.trim().toLowerCase() === "interactive";
27
+ }
22
28
  // ── Compile Controller → Skill ─────────────────────────────
23
29
  export function compileControllerToSkill(controllerKey, controller, variables, workflows) {
24
30
  const kindMetadata = getControllerKindMetadata(controller.kind);
25
31
  if (!kindMetadata?.supportsSkillGeneration) {
26
32
  throw new Error(`Unsupported controller kind: ${controller.kind}`);
27
33
  }
34
+ const diagnosisWorkflowName = controller.diagnosis_workflow;
35
+ const fixWorkflowName = controller.fix_workflow;
36
+ const diagnosisArtifactPath = controller.diagnosis_artifact_path;
37
+ const diagnosisReviewArtifactPath = controller.diagnosis_review_artifact_path;
38
+ const reviewArtifactPath = controller.review_artifact_path;
39
+ const analyzerRole = controller.roles.analyzer;
40
+ const analysisReviewerRole = controller.roles.analysis_reviewer;
41
+ const surgeonRole = controller.roles.surgeon;
42
+ const fixReviewerRole = controller.roles.fix_reviewer;
43
+ const testRunnerRole = controller.roles.test_runner;
44
+ if (!diagnosisWorkflowName ||
45
+ !fixWorkflowName ||
46
+ !diagnosisArtifactPath ||
47
+ !diagnosisReviewArtifactPath ||
48
+ !reviewArtifactPath ||
49
+ !analyzerRole ||
50
+ !analysisReviewerRole ||
51
+ !surgeonRole ||
52
+ !fixReviewerRole ||
53
+ !testRunnerRole) {
54
+ throw new Error(`Controller '${controllerKey}' is missing fix-loop skill generation fields`);
55
+ }
28
56
  const skillName = `dna-${toKebabCase(controllerKey)}`;
29
57
  assertSafeGeneratedName(skillName, "skill name");
30
- const analyzerAgent = `dna-${toKebabCase(controller.roles.analyzer)}`;
31
- const analysisReviewerAgent = `dna-${toKebabCase(controller.roles.analysis_reviewer)}`;
32
- const surgeonAgent = `dna-${toKebabCase(controller.roles.surgeon)}`;
33
- const fixReviewerAgent = `dna-${toKebabCase(controller.roles.fix_reviewer)}`;
34
- const testRunnerAgent = `dna-${toKebabCase(controller.roles.test_runner)}`;
35
- const diagnosisWorkflow = workflows?.[controller.diagnosis_workflow];
36
- const fixWorkflow = workflows?.[controller.fix_workflow];
37
- const diagnosisAnalyzeStep = requireStepByRole(diagnosisWorkflow, controller.roles.analyzer, controller.diagnosis_workflow);
38
- const diagnosisReviewStep = requireStepByRole(diagnosisWorkflow, controller.roles.analysis_reviewer, controller.diagnosis_workflow);
39
- const fixBugsStep = requireStepByRole(fixWorkflow, controller.roles.surgeon, controller.fix_workflow);
40
- const reviewChangesStep = requireStepByRole(fixWorkflow, controller.roles.fix_reviewer, controller.fix_workflow);
41
- const verifyReportStep = requireStepByRole(fixWorkflow, controller.roles.test_runner, controller.fix_workflow);
58
+ const analyzerAgent = `dna-${toKebabCase(analyzerRole)}`;
59
+ const analysisReviewerAgent = `dna-${toKebabCase(analysisReviewerRole)}`;
60
+ const surgeonAgent = `dna-${toKebabCase(surgeonRole)}`;
61
+ const fixReviewerAgent = `dna-${toKebabCase(fixReviewerRole)}`;
62
+ const testRunnerAgent = `dna-${toKebabCase(testRunnerRole)}`;
63
+ const diagnosisWorkflow = workflows?.[diagnosisWorkflowName];
64
+ const fixWorkflow = workflows?.[fixWorkflowName];
65
+ const diagnosisAnalyzeStep = requireStepByRole(diagnosisWorkflow, analyzerRole, diagnosisWorkflowName);
66
+ const diagnosisReviewStep = requireStepByRole(diagnosisWorkflow, analysisReviewerRole, diagnosisWorkflowName);
67
+ const fixBugsStep = requireStepByRole(fixWorkflow, surgeonRole, fixWorkflowName);
68
+ const reviewChangesStep = requireStepByRole(fixWorkflow, fixReviewerRole, fixWorkflowName);
69
+ const verifyReportStep = requireStepByRole(fixWorkflow, testRunnerRole, fixWorkflowName);
42
70
  const stepPrompt = (step) => step.prompt || step.description || "";
43
71
  const policy = normalizeControllerPolicy(controller);
44
72
  const lines = [];
@@ -63,8 +91,8 @@ export function compileControllerToSkill(controllerKey, controller, variables, w
63
91
  lines.push("<Controller>");
64
92
  lines.push(`kind: ${controller.kind}`);
65
93
  lines.push(`max_rounds: ${controller.max_rounds}`);
66
- lines.push(`diagnosis_workflow: ${controller.diagnosis_workflow}`);
67
- lines.push(`fix_workflow: ${controller.fix_workflow}`);
94
+ lines.push(`diagnosis_workflow: ${diagnosisWorkflowName}`);
95
+ lines.push(`fix_workflow: ${fixWorkflowName}`);
68
96
  lines.push(`progress_artifact_path: ${controller.progress_artifact_path}`);
69
97
  lines.push("</Controller>");
70
98
  lines.push("");
@@ -74,16 +102,17 @@ export function compileControllerToSkill(controllerKey, controller, variables, w
74
102
  lines.push(" - If it contains '/', '.', '..', spaces, braces, glob characters, or path separators, stop with the configured stop report.");
75
103
  lines.push(" - Do not read or write controller artifacts until the module argument is safe.");
76
104
  lines.push("1. Check diagnosis artifact freshness before any fix round.");
77
- lines.push(` - Diagnosis artifact: ${controller.diagnosis_artifact_path}`);
78
- lines.push(` - Diagnosis review artifact: ${controller.diagnosis_review_artifact_path}`);
105
+ lines.push(` - Diagnosis artifact: ${diagnosisArtifactPath}`);
106
+ lines.push(` - Diagnosis review artifact: ${diagnosisReviewArtifactPath}`);
79
107
  lines.push("2. Create a stable controller_run_id once for this invocation before using any artifact path.");
80
108
  lines.push(" - Reuse the same controller_run_id in every child Agent prompt and in every progress validation.");
81
109
  lines.push(" - Do not regenerate controller_run_id inside fix rounds.");
82
- lines.push("3. Reuse diagnosis only when all diagnosis reuse criteria pass:");
110
+ lines.push("3. Reuse the diagnosis artifact only when all diagnosis reuse criteria pass:");
83
111
  for (const criterion of policy.diagnosis_reuse_criteria) {
84
112
  lines.push(` - ${criterion}`);
85
113
  }
86
- lines.push(`4. If diagnosis is missing, invalid, stale, or scope-changed, run diagnosis once with fresh context and sequential gates:`);
114
+ lines.push(" Reuse skips fresh analyzer and analysis_reviewer agents; it never skips the declared diagnosis contract completion gates or the persisted diagnosis review JSON approval gate before any surgeon step.");
115
+ lines.push(`4. If diagnosis is missing, invalid, stale, or scope-changed, run the analyzer once with fresh context:`);
87
116
  lines.push(" a. Spawn fresh analyzer agent with controller_run_id, then wait for completion.");
88
117
  lines.push(" ```");
89
118
  lines.push(" Agent(");
@@ -91,6 +120,7 @@ export function compileControllerToSkill(controllerKey, controller, variables, w
91
120
  lines.push(` prompt="controller_run_id=<controller_run_id>. ${escapePrompt(stepPrompt(diagnosisAnalyzeStep))}"`);
92
121
  lines.push(" )");
93
122
  lines.push(" ```");
123
+ pushWorkflowStepVerifierLines(lines, diagnosisAnalyzeStep, " ");
94
124
  lines.push(` b. Diagnosis artifact gate: read ${controller.diagnosis_artifact_path}; validate it exists, is non-empty, names the safe module argument, and is current for this module before continuing.`);
95
125
  lines.push(" c. Spawn fresh analysis_reviewer agent with controller_run_id, then wait for completion.");
96
126
  lines.push(" ```");
@@ -99,9 +129,12 @@ export function compileControllerToSkill(controllerKey, controller, variables, w
99
129
  lines.push(` prompt="controller_run_id=<controller_run_id>; expected_review_artifact=${controller.diagnosis_review_artifact_path}. ${escapePrompt(stepPrompt(diagnosisReviewStep))}"`);
100
130
  lines.push(" )");
101
131
  lines.push(" ```");
102
- lines.push(` d. Diagnosis review JSON gate: read ${controller.diagnosis_review_artifact_path}; require existing valid JSON with verdict APPROVE before fix rounds.`);
132
+ pushWorkflowStepVerifierLines(lines, diagnosisReviewStep, " ");
133
+ lines.push("5. If the diagnosis artifact was reused, do not spawn analyzer or analysis_reviewer; rerun the declared diagnosis contract completion gates against the existing artifacts:");
134
+ pushWorkflowStepVerifierLines(lines, diagnosisReviewStep, " ");
135
+ lines.push(`6. Diagnosis review JSON gate: read ${controller.diagnosis_review_artifact_path}; require existing valid JSON with verdict APPROVE before fix rounds.`);
103
136
  lines.push(" If the review JSON is missing, invalid, or verdict is not APPROVE, stop immediately with the configured stop report; do NOT enter fix rounds.");
104
- lines.push(`5. For round 1..${controller.max_rounds}, set current_round to the loop number and run one fresh context fix round with sequential gates:`);
137
+ lines.push(`7. For round 1..${controller.max_rounds}, set current_round to the loop number and run one fresh context fix round with sequential gates:`);
105
138
  lines.push(" a. Spawn fresh surgeon agent with controller_run_id and current_round, then wait for completion.");
106
139
  lines.push(" ```");
107
140
  lines.push(" Agent(");
@@ -109,6 +142,7 @@ export function compileControllerToSkill(controllerKey, controller, variables, w
109
142
  lines.push(` prompt="controller_run_id=<controller_run_id>; current_round=<current_round>. Fresh context round for $ARGUMENTS. ${escapePrompt(stepPrompt(fixBugsStep))}"`);
110
143
  lines.push(" )");
111
144
  lines.push(" ```");
145
+ pushWorkflowStepVerifierLines(lines, fixBugsStep, " ");
112
146
  lines.push(" b. Fix handoff gate: validate the current round handoff and fix commit, or an explicit no-commit handoff for current_round, before review.");
113
147
  lines.push(" c. Spawn fresh fix_reviewer agent with controller_run_id, current_round, and expected review artifact path, then wait for completion.");
114
148
  lines.push(" ```");
@@ -117,6 +151,7 @@ export function compileControllerToSkill(controllerKey, controller, variables, w
117
151
  lines.push(` prompt="controller_run_id=<controller_run_id>; current_round=<current_round>; expected_review_artifact=${controller.review_artifact_path}. Fresh context review for $ARGUMENTS. ${escapePrompt(stepPrompt(reviewChangesStep))}"`);
118
152
  lines.push(" )");
119
153
  lines.push(" ```");
154
+ pushWorkflowStepVerifierLines(lines, reviewChangesStep, " ");
120
155
  lines.push(` d. Review JSON gate: read ${controller.review_artifact_path}; validate it exists, is valid JSON, module equals the safe module argument, round equals the provided current_round, run_id equals the provided controller_run_id, updated_at is present, and verdict is APPROVE or REQUEST_CHANGES before verification.`);
121
156
  lines.push(" e. Spawn fresh test_runner agent only after the review gate passes; include controller_run_id and current_round, then wait for completion.");
122
157
  lines.push(" ```");
@@ -125,6 +160,7 @@ export function compileControllerToSkill(controllerKey, controller, variables, w
125
160
  lines.push(` prompt="controller_run_id=<controller_run_id>; current_round=<current_round>. Fresh context verification for $ARGUMENTS. The progress JSON run_id must equal the provided controller_run_id and round must equal the provided current_round; do not use manual-run during controller execution. ${escapePrompt(stepPrompt(verifyReportStep))}"`);
126
161
  lines.push(" )");
127
162
  lines.push(" ```");
163
+ pushWorkflowStepVerifierLines(lines, verifyReportStep, " ");
128
164
  lines.push(` f. Progress JSON read/provenance validation: read ${controller.progress_artifact_path}. The controller must read this JSON sidecar, not Markdown prose.`);
129
165
  lines.push(" g. Verify progress JSON provenance before branching:");
130
166
  for (const check of policy.progress_provenance_checks) {
@@ -135,7 +171,7 @@ export function compileControllerToSkill(controllerKey, controller, variables, w
135
171
  for (const branch of policy.verdict_branches) {
136
172
  lines.push(` - ${branch}`);
137
173
  }
138
- lines.push(`6. If max ${controller.max_rounds} rounds is reached, stop with the configured stop report.`);
174
+ lines.push(`8. If max ${controller.max_rounds} rounds is reached, stop with the configured stop report.`);
139
175
  lines.push("</Steps>");
140
176
  lines.push("");
141
177
  lines.push("<Verdict_Mapping>");
@@ -195,6 +231,37 @@ function requireStepByRole(workflow, role, workflowName) {
195
231
  }
196
232
  return step;
197
233
  }
234
+ function pushWorkflowStepVerifierLines(lines, step, indent) {
235
+ if (step.completion?.length) {
236
+ lines.push(`${indent}Completion gates for workflow step '${step.id}' must pass in declaration order before treating the step as complete:`);
237
+ for (let i = 0; i < step.completion.length; i++) {
238
+ lines.push(`${indent} ${i + 1}. ${describeCompletionCheck(step.completion[i])}`);
239
+ }
240
+ }
241
+ if (step.checkpoints?.length) {
242
+ lines.push(`${indent}Checkpoint gates for workflow step '${step.id}' must pass in declaration order before transitioning:`);
243
+ for (let i = 0; i < step.checkpoints.length; i++) {
244
+ const checkpoint = step.checkpoints[i];
245
+ const action = checkpoint.action ?? "block";
246
+ const command = checkpoint.command ? `; command: ${checkpoint.command}` : "";
247
+ lines.push(`${indent} ${i + 1}. ${checkpoint.assert}: ${checkpoint.message} (${action}${command})`);
248
+ }
249
+ }
250
+ if (step.completion?.some((check) => check.command_success) || step.checkpoints?.some((check) => check.command)) {
251
+ lines.push(`${indent}Command-backed completion/checkpoint gates must use runtime verifier_policy semantics: first authorize the declared raw command against exact allow_commands or allow_command_prefixes, then resolve templates safely, execute the exact resolved command, require exit code 0 for pass, treat policy denial as exit_code=126, and treat any non-zero exit code as blocking failure unless the checkpoint action is warn.`);
252
+ }
253
+ }
254
+ function describeCompletionCheck(check) {
255
+ if (check.file_exists)
256
+ return `file_exists: ${check.file_exists}`;
257
+ if (check.file_not_empty)
258
+ return `file_not_empty: ${check.file_not_empty}`;
259
+ if (check.file_contains)
260
+ return `file_contains: ${check.file_contains.path} matches ${check.file_contains.pattern}`;
261
+ if (check.command_success)
262
+ return `command_success: execute exact command "${check.command_success}", require exit code 0, and block on any non-zero exit code`;
263
+ return "invalid completion check: exactly one completion check field is required";
264
+ }
198
265
  function createSkillLauncher(params) {
199
266
  const lines = [];
200
267
  lines.push("---");
@@ -289,9 +356,9 @@ function renderControllerPolicyLines(lines, controller) {
289
356
  function renderControllerPolicyText(text, controller) {
290
357
  const replacements = {
291
358
  "$progress_artifact_path": controller.progress_artifact_path,
292
- "$diagnosis_artifact_path": controller.diagnosis_artifact_path,
293
- "$diagnosis_review_artifact_path": controller.diagnosis_review_artifact_path,
294
- "$review_artifact_path": controller.review_artifact_path,
359
+ "$diagnosis_artifact_path": controller.diagnosis_artifact_path ?? "",
360
+ "$diagnosis_review_artifact_path": controller.diagnosis_review_artifact_path ?? "",
361
+ "$review_artifact_path": controller.review_artifact_path ?? "",
295
362
  };
296
363
  let rendered = text;
297
364
  for (const [token, value] of Object.entries(replacements)) {
@@ -381,9 +448,10 @@ function workflowSkillMapEntry(skillName, dirName, plan) {
381
448
  produces,
382
449
  };
383
450
  }
384
- export function compileWorkflowToSkill(plan, roles, ir, variables) {
451
+ export function compileWorkflowToSkill(plan, roles, ir, variables, options) {
385
452
  const lines = [];
386
453
  const skillName = `dna-${toKebabCase(plan.name)}`;
454
+ const surface = options?.surface ?? "claude_code";
387
455
  assertSafeGeneratedName(skillName, "skill name");
388
456
  // Frontmatter
389
457
  lines.push("---");
@@ -492,8 +560,10 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
492
560
  lines.push("</Advisory_Context>");
493
561
  lines.push("");
494
562
  }
495
- // Steps — each step MUST be executed via Agent() tool call
563
+ // Steps — agent-owned steps are delegated; human-owned steps stay in the main session.
496
564
  const usedRoles = new Set(plan.steps.map((s) => s.role));
565
+ const hasHumanSteps = plan.steps.some((step) => isHumanRole(roles[step.role]));
566
+ const hasInteractiveSteps = plan.steps.some((step) => isInteractiveRole(roles[step.role]));
497
567
  // Pre-compute handoff targets: steps that are pointed to by another step's handoff_to
498
568
  const handoffTargets = new Set();
499
569
  for (const step of plan.steps) {
@@ -508,6 +578,8 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
508
578
  stepNumber++;
509
579
  const step = plan.steps.find((s) => s.id === stepId);
510
580
  const agentType = `dna-${toKebabCase(step.role)}`;
581
+ const humanOwned = isHumanRole(roles[step.role]);
582
+ const interactiveOwned = isInteractiveRole(roles[step.role]);
511
583
  const runIf = step.run_if ? `\n **Condition**: ${step.run_if}` : "";
512
584
  const optional = step.optional ? " (optional)" : "";
513
585
  // Build agent prompt: anti-recursion + role identity + task
@@ -536,14 +608,34 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
536
608
  lines.push(`${stepNumber}. **${step.id}**${optional}`);
537
609
  lines.push(` ${step.description}${runIf}`);
538
610
  lines.push("");
539
- lines.push(` Execute with Agent tool — DO NOT perform this work yourself:`);
540
- lines.push(` \`\`\``);
541
- lines.push(` Agent(`);
542
- lines.push(` subagent_type="${agentType}",`);
543
- lines.push(` prompt="${agentPrompt}"`);
544
- lines.push(` )`);
545
- lines.push(` \`\`\``);
546
- lines.push(` Wait for agent to complete. Read the output before proceeding.`);
611
+ if (humanOwned) {
612
+ lines.push(" **Human-owned gate (`model: human`)**");
613
+ lines.push(` Ask the user in the main session: ${step.prompt || step.description}`);
614
+ lines.push(" Do not delegate this decision, infer approval, or continue from silence. Continue only after an explicit human response.");
615
+ }
616
+ else if (interactiveOwned) {
617
+ lines.push(" **Main-session external-agent step (`model: interactive`)**");
618
+ lines.push(` Perform directly in the main session: ${step.prompt || step.description}`);
619
+ lines.push(" This step may interview the user or edit the candidate YAML inside its declared scope. Do not delegate away the conversation context.");
620
+ }
621
+ else if (surface === "codex_cli") {
622
+ lines.push(" Delegate to a Codex native subagent with this role contract; do not discard the role boundaries:");
623
+ lines.push(" ```text");
624
+ lines.push(` role: ${agentType}`);
625
+ lines.push(` prompt: ${agentPrompt}`);
626
+ lines.push(" ```");
627
+ lines.push(" Wait for the subagent to complete and read its output before proceeding.");
628
+ }
629
+ else {
630
+ lines.push(` Execute with Agent tool — DO NOT perform this work yourself:`);
631
+ lines.push(` \`\`\``);
632
+ lines.push(` Agent(`);
633
+ lines.push(` subagent_type="${agentType}",`);
634
+ lines.push(` prompt="${agentPrompt}"`);
635
+ lines.push(` )`);
636
+ lines.push(` \`\`\``);
637
+ lines.push(` Wait for agent to complete. Read the output before proceeding.`);
638
+ }
547
639
  if (step.handoff?.produces && step.handoff.produces.length > 0) {
548
640
  const artifacts = step.handoff.produces.map(p => p.description).join(", ");
549
641
  lines.push(` Verify produced artifacts: ${artifacts}`);
@@ -565,14 +657,39 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
565
657
  }
566
658
  lines.push("</Steps>");
567
659
  lines.push("");
568
- // Execution Policy force Agent dispatch
660
+ if (plan.retry.max_retries > 0) {
661
+ lines.push("<Retry_Policy>");
662
+ lines.push(`- max_retries: ${plan.retry.max_retries}`);
663
+ lines.push(`- retry_from: ${plan.retry.retry_from ?? "first_failed_step"}`);
664
+ lines.push(`- backoff: ${plan.retry.backoff}`);
665
+ lines.push("- On a blocking checkpoint or validator failure, preserve the exact diagnostics and return them to the retry_from step.");
666
+ lines.push("- Repeat only the retry slice and its dependent steps; do not continue to save, adoption, or sync while validation is red.");
667
+ lines.push("- Stop and report the remaining diagnostics when the retry budget is exhausted.");
668
+ lines.push("</Retry_Policy>");
669
+ lines.push("");
670
+ }
671
+ // Execution Policy — force delegation while preserving explicit human gates.
569
672
  lines.push("<Execution_Policy>");
570
- lines.push("- **CRITICAL**: Each step MUST be executed by spawning an Agent using the Agent tool with the specified subagent_type. DO NOT perform any step's work directly in the main session.");
673
+ if (surface === "codex_cli") {
674
+ lines.push("- **CRITICAL**: Delegate each delegated-agent step to a Codex native subagent using the specified role contract. Do not collapse distinct roles into an unreviewed single pass.");
675
+ }
676
+ else {
677
+ lines.push(hasHumanSteps || hasInteractiveSteps
678
+ ? "- **CRITICAL**: Each delegated-agent step MUST be executed by spawning an Agent using the Agent tool with the specified subagent_type. DO NOT perform delegated-agent work directly in the main session."
679
+ : "- **CRITICAL**: Each step MUST be executed by spawning an Agent using the Agent tool with the specified subagent_type. DO NOT perform any step's work directly in the main session.");
680
+ }
571
681
  lines.push("- Use the task tracker to track each step only as pending/in_progress/completed; use deleted only for skipped dependent tasks.");
572
- lines.push("- After completing each step, immediately proceed to the next — do not stop, summarize, or wait for confirmation.");
682
+ lines.push(hasHumanSteps
683
+ ? "- Proceed automatically between non-human steps, but stop at every `model: human` step and require an explicit user response before continuing."
684
+ : "- After completing each step, immediately proceed to the next — do not stop, summarize, or wait for confirmation.");
685
+ if (hasInteractiveSteps) {
686
+ lines.push("- Execute `model: interactive` steps in the main session so interview answers and candidate edits remain in the user-visible conversation.");
687
+ }
573
688
  lines.push("- If a step cannot complete, do not use a failed status. Keep the current task in_progress or record the blocker in the task description/output, delete skipped dependent tasks, and continue only to non-dependent steps.");
574
- lines.push("- Do not ask the user for permission between steps — the workflow is pre-approved.");
575
- lines.push("- Wait for each Agent to complete and read its output before proceeding to the next step.");
689
+ lines.push(hasHumanSteps
690
+ ? "- Never auto-approve, delegate, or synthesize a human-owned decision."
691
+ : "- Do not ask the user for permission between steps — the workflow is pre-approved.");
692
+ lines.push("- Wait for each delegated agent to complete and read its output before proceeding to the next step.");
576
693
  lines.push("</Execution_Policy>");
577
694
  lines.push("");
578
695
  // Tool_Usage — only if roles have permissions or scope
@@ -585,9 +702,15 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
585
702
  const denied = role.tool_permissions?.deny?.join(", ") || "none";
586
703
  const writeScope = role.scope?.write?.join(", ") || "none";
587
704
  toolUsageLines.push(`### ${roleName}`);
705
+ toolUsageLines.push(`- Owner: ${isHumanRole(role) ? "human" : isInteractiveRole(role) ? "main_session_external_agent" : "delegated_agent"}`);
706
+ toolUsageLines.push(`- Description: ${role.description}`);
588
707
  toolUsageLines.push(`- Allowed: ${allowed}`);
589
708
  toolUsageLines.push(`- Denied: ${denied}`);
590
709
  toolUsageLines.push(`- Write scope: ${writeScope}`);
710
+ for (const instruction of role.instructions ?? [])
711
+ toolUsageLines.push(`- Instruction: ${instruction}`);
712
+ for (const criterion of role.success_criteria ?? [])
713
+ toolUsageLines.push(`- Success criterion: ${criterion}`);
591
714
  }
592
715
  if (toolUsageLines.length > 0) {
593
716
  lines.push("<Tool_Usage>");
@@ -631,6 +754,8 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
631
754
  for (const cp of step.checkpoints) {
632
755
  const action = cp.action || "block";
633
756
  lines.push(`- ${cp.assert}: ${cp.message} (${action})`);
757
+ if (cp.command)
758
+ lines.push(` command: ${cp.command}`);
634
759
  }
635
760
  }
636
761
  lines.push("</Checkpoints>");
@@ -0,0 +1,99 @@
1
+ import type { SyncOutputArtifactId } from "./output-artifact-registry.js";
2
+ import type { PlannedArtifact, SyncTarget } from "./sync-target-plan.js";
3
+ export declare const SYNC_ARTIFACT_REGISTRY_PATH = ".dna/sync/registry.json";
4
+ export declare const SYNC_ARTIFACT_REGISTRY_SCHEMA = "intentdna.sync_artifact_registry.v1";
5
+ export type SyncArtifactOwnership = PlannedArtifact["ownership"] | "unknown";
6
+ export type SyncArtifactStatus = "ok" | "missing" | "drifted" | "untracked";
7
+ export interface SyncArtifactRegistryTemplate {
8
+ sourceDnaIds: string[];
9
+ configFiles: string[];
10
+ compiledIrHash?: string;
11
+ }
12
+ export interface SyncArtifactRegistryEntry {
13
+ artifact: SyncOutputArtifactId;
14
+ kind: string;
15
+ path: string;
16
+ relativePath: string;
17
+ target: SyncTarget | "markdown" | "generic";
18
+ adapter?: SyncTarget;
19
+ installMode?: string;
20
+ ownership: SyncArtifactOwnership;
21
+ trackedByLock: boolean;
22
+ sha256?: string;
23
+ missingAtSync?: boolean;
24
+ }
25
+ export interface SyncArtifactRegistry {
26
+ schema: typeof SYNC_ARTIFACT_REGISTRY_SCHEMA;
27
+ syncedAt: string;
28
+ projectDir: string;
29
+ dnaVersion?: string;
30
+ mode: "write";
31
+ template: SyncArtifactRegistryTemplate;
32
+ artifacts: SyncArtifactRegistryEntry[];
33
+ }
34
+ export interface SyncArtifactRegistryInputEntry {
35
+ artifact: SyncOutputArtifactId;
36
+ path: string;
37
+ kind?: string;
38
+ target?: SyncArtifactRegistryEntry["target"];
39
+ adapter?: SyncTarget;
40
+ installMode?: string;
41
+ ownership?: SyncArtifactOwnership;
42
+ trackedByLock?: boolean;
43
+ }
44
+ export interface WriteSyncArtifactRegistryOptions {
45
+ projectDir: string;
46
+ dnaVersion?: string;
47
+ template: SyncArtifactRegistryTemplate;
48
+ artifacts: SyncArtifactRegistryInputEntry[];
49
+ }
50
+ export interface SyncArtifactInspection extends SyncArtifactRegistryEntry {
51
+ status: SyncArtifactStatus;
52
+ currentSha256?: string;
53
+ reason: string;
54
+ }
55
+ export interface SyncArtifactRegistryInspection {
56
+ registry: SyncArtifactRegistry | null;
57
+ registryPath: string;
58
+ artifacts: SyncArtifactInspection[];
59
+ totals: {
60
+ artifacts: number;
61
+ ok: number;
62
+ missing: number;
63
+ drifted: number;
64
+ untracked: number;
65
+ };
66
+ }
67
+ export interface RemoveSyncArtifactsOptions {
68
+ projectDir?: string;
69
+ dryRun?: boolean;
70
+ }
71
+ export interface RemoveSyncArtifactsResult {
72
+ schema_version: "intentdna.sync_remove_result.v1";
73
+ mode: "sync_registry_remove";
74
+ registryPath: string;
75
+ removed: string[];
76
+ skipped: Array<{
77
+ path: string;
78
+ reason: string;
79
+ }>;
80
+ preservation: {
81
+ note: string;
82
+ kept: Array<{
83
+ path: string;
84
+ reason: string;
85
+ explanation: string;
86
+ }>;
87
+ nextCommands: string[];
88
+ };
89
+ dryRun: boolean;
90
+ totals: {
91
+ removable: number;
92
+ skipped: number;
93
+ };
94
+ boundary: string;
95
+ }
96
+ export declare function writeSyncArtifactRegistry(options: WriteSyncArtifactRegistryOptions): Promise<SyncArtifactRegistry>;
97
+ export declare function readSyncArtifactRegistry(projectDir?: string): Promise<SyncArtifactRegistry | null>;
98
+ export declare function inspectSyncArtifactRegistry(projectDir?: string): Promise<SyncArtifactRegistryInspection>;
99
+ export declare function removeSyncArtifacts(options?: RemoveSyncArtifactsOptions): Promise<RemoveSyncArtifactsResult>;
@@ -0,0 +1,194 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
3
+ import { dirname, relative, resolve } from "node:path";
4
+ export const SYNC_ARTIFACT_REGISTRY_PATH = ".dna/sync/registry.json";
5
+ export const SYNC_ARTIFACT_REGISTRY_SCHEMA = "intentdna.sync_artifact_registry.v1";
6
+ function fileSha256(content) {
7
+ return createHash("sha256").update(content, "utf-8").digest("hex");
8
+ }
9
+ async function computeFileHash(path) {
10
+ try {
11
+ return fileSha256(await readFile(path, "utf-8"));
12
+ }
13
+ catch {
14
+ return undefined;
15
+ }
16
+ }
17
+ function registryPath(projectDir) {
18
+ return resolve(projectDir, SYNC_ARTIFACT_REGISTRY_PATH);
19
+ }
20
+ async function canonicalPath(path) {
21
+ return realpath(path).catch(() => resolve(path));
22
+ }
23
+ async function normalizeEntry(projectDir, entry) {
24
+ const canonicalProjectDir = await canonicalPath(projectDir);
25
+ const absolutePath = resolve(projectDir, entry.path);
26
+ const canonicalArtifactPath = await canonicalPath(absolutePath);
27
+ return {
28
+ artifact: entry.artifact,
29
+ kind: entry.kind ?? entry.artifact,
30
+ path: canonicalArtifactPath,
31
+ relativePath: relative(canonicalProjectDir, canonicalArtifactPath) || ".",
32
+ target: entry.target ?? entry.adapter ?? "generic",
33
+ adapter: entry.adapter,
34
+ installMode: entry.installMode,
35
+ ownership: entry.ownership ?? "unknown",
36
+ trackedByLock: entry.trackedByLock ?? true,
37
+ };
38
+ }
39
+ function uniqueArtifacts(artifacts) {
40
+ const byKey = new Map();
41
+ for (const artifact of artifacts) {
42
+ byKey.set(`${artifact.artifact}:${artifact.path}:${artifact.kind}`, artifact);
43
+ }
44
+ return [...byKey.values()].sort((a, b) => a.target.localeCompare(b.target) ||
45
+ a.relativePath.localeCompare(b.relativePath) ||
46
+ a.kind.localeCompare(b.kind));
47
+ }
48
+ export async function writeSyncArtifactRegistry(options) {
49
+ const projectDir = await canonicalPath(options.projectDir);
50
+ const artifacts = uniqueArtifacts(await Promise.all(options.artifacts.map((entry) => normalizeEntry(projectDir, entry))));
51
+ const withHashes = [];
52
+ for (const artifact of artifacts) {
53
+ const sha256 = await computeFileHash(artifact.path);
54
+ withHashes.push(sha256 ? { ...artifact, sha256 } : { ...artifact, missingAtSync: true });
55
+ }
56
+ const registry = {
57
+ schema: SYNC_ARTIFACT_REGISTRY_SCHEMA,
58
+ syncedAt: new Date().toISOString(),
59
+ projectDir,
60
+ dnaVersion: options.dnaVersion,
61
+ mode: "write",
62
+ template: options.template,
63
+ artifacts: withHashes,
64
+ };
65
+ const path = registryPath(options.projectDir);
66
+ await mkdir(dirname(path), { recursive: true });
67
+ await writeFile(path, JSON.stringify(registry, null, 2) + "\n", "utf-8");
68
+ return registry;
69
+ }
70
+ export async function readSyncArtifactRegistry(projectDir = process.cwd()) {
71
+ try {
72
+ const raw = await readFile(registryPath(projectDir), "utf-8");
73
+ const parsed = JSON.parse(raw);
74
+ if (parsed.schema !== SYNC_ARTIFACT_REGISTRY_SCHEMA || !Array.isArray(parsed.artifacts))
75
+ return null;
76
+ return parsed;
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ }
82
+ async function inspectEntry(entry) {
83
+ if (!entry.sha256) {
84
+ const exists = await stat(entry.path).then(() => true, () => false);
85
+ return {
86
+ ...entry,
87
+ status: exists ? "untracked" : "missing",
88
+ reason: exists ? "No sync-time hash recorded for this artifact." : "Artifact did not exist when inspected.",
89
+ };
90
+ }
91
+ const currentSha256 = await computeFileHash(entry.path);
92
+ if (!currentSha256) {
93
+ return { ...entry, status: "missing", reason: "Artifact path is missing." };
94
+ }
95
+ if (currentSha256 !== entry.sha256) {
96
+ return { ...entry, status: "drifted", currentSha256, reason: "Artifact hash differs from last sync." };
97
+ }
98
+ return { ...entry, status: "ok", currentSha256, reason: "Artifact matches last sync." };
99
+ }
100
+ export async function inspectSyncArtifactRegistry(projectDir = process.cwd()) {
101
+ const registry = await readSyncArtifactRegistry(projectDir);
102
+ const artifacts = registry ? await Promise.all(registry.artifacts.map(inspectEntry)) : [];
103
+ const totals = {
104
+ artifacts: artifacts.length,
105
+ ok: artifacts.filter((entry) => entry.status === "ok").length,
106
+ missing: artifacts.filter((entry) => entry.status === "missing").length,
107
+ drifted: artifacts.filter((entry) => entry.status === "drifted").length,
108
+ untracked: artifacts.filter((entry) => entry.status === "untracked").length,
109
+ };
110
+ return { registry, registryPath: registryPath(projectDir), artifacts, totals };
111
+ }
112
+ function isSafeToRemove(entry) {
113
+ if (entry.status !== "ok")
114
+ return false;
115
+ if (!entry.sha256 || entry.currentSha256 !== entry.sha256)
116
+ return false;
117
+ return entry.ownership === "intentdna_owned" || entry.ownership === "diagnostic";
118
+ }
119
+ function removalSkipExplanation(entry) {
120
+ if (entry.status === "drifted") {
121
+ return "Preserved because the file changed after the last sync; review the diff or rerun dna sync before cleanup.";
122
+ }
123
+ if (entry.status === "missing") {
124
+ return "No file was deleted because the registry path is already missing.";
125
+ }
126
+ if (entry.status === "untracked") {
127
+ return "Preserved because no sync-time hash proves IntentDNA still owns the current file.";
128
+ }
129
+ if (entry.ownership === "merge_preserve_user") {
130
+ return "Preserved because the artifact is a merge-preserve user configuration surface.";
131
+ }
132
+ if (entry.ownership === "owned_block") {
133
+ return "Preserved because IntentDNA owns only an injected block inside this user file.";
134
+ }
135
+ if (entry.ownership === "unknown") {
136
+ return "Preserved because ownership is unknown.";
137
+ }
138
+ return "Preserved because this artifact is outside the safe remove set.";
139
+ }
140
+ export async function removeSyncArtifacts(options = {}) {
141
+ const projectDir = options.projectDir ?? process.cwd();
142
+ const inspection = await inspectSyncArtifactRegistry(projectDir);
143
+ const removed = [];
144
+ const skipped = [];
145
+ const kept = [];
146
+ for (const artifact of inspection.artifacts) {
147
+ if (!isSafeToRemove(artifact)) {
148
+ const reason = `${artifact.ownership}/${artifact.status}`;
149
+ skipped.push({ path: artifact.path, reason });
150
+ kept.push({ path: artifact.path, reason, explanation: removalSkipExplanation(artifact) });
151
+ continue;
152
+ }
153
+ if (!options.dryRun)
154
+ await rm(artifact.path, { recursive: true, force: true });
155
+ removed.push(artifact.path);
156
+ }
157
+ if (inspection.registry && !options.dryRun && skipped.length === 0) {
158
+ await rm(inspection.registryPath, { force: true });
159
+ removed.push(inspection.registryPath);
160
+ }
161
+ else if (inspection.registry && !options.dryRun) {
162
+ const keptPaths = new Set(skipped.map((entry) => entry.path));
163
+ const preservedRegistry = {
164
+ ...inspection.registry,
165
+ artifacts: inspection.registry.artifacts.filter((artifact) => keptPaths.has(artifact.path)),
166
+ };
167
+ await writeFile(inspection.registryPath, JSON.stringify(preservedRegistry, null, 2) + "\n", "utf-8");
168
+ }
169
+ else if (inspection.registry && skipped.length === 0) {
170
+ removed.push(inspection.registryPath);
171
+ }
172
+ return {
173
+ schema_version: "intentdna.sync_remove_result.v1",
174
+ mode: "sync_registry_remove",
175
+ registryPath: inspection.registryPath,
176
+ removed,
177
+ skipped,
178
+ preservation: {
179
+ note: "Safe remove preserves user-owned, merge-preserve, owned-block, drifted, missing, untracked, and unknown-ownership registry entries.",
180
+ kept,
181
+ nextCommands: [
182
+ "dna sync status",
183
+ "dna sync doctor",
184
+ "dna sync remove --dry-run",
185
+ ],
186
+ },
187
+ dryRun: options.dryRun === true,
188
+ totals: {
189
+ removable: removed.length,
190
+ skipped: skipped.length,
191
+ },
192
+ boundary: "remove only deletes hash-matching IntentDNA-owned/diagnostic artifacts from the sync registry; merge-preserve/user-owned files are skipped",
193
+ };
194
+ }