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
@@ -0,0 +1,800 @@
1
+ const EXAMPLES = [
2
+ {
3
+ id: "dev_pipeline",
4
+ label: "Development Pipeline",
5
+ summary: "Plan, implement, verify, and review a scoped engineering change.",
6
+ workflow: {
7
+ name: "draft-dev-pipeline",
8
+ description: "Draft engineering workflow.",
9
+ steps: [
10
+ {
11
+ id: "plan",
12
+ role: "planner",
13
+ description: "Clarify task boundaries, assumptions, and acceptance criteria.",
14
+ prompt: "Plan {{task_id}} with constraints, risks, and verification shape.",
15
+ handoff: {
16
+ produces: [{ type: "summary", name: "implementation_plan", description: "Plan summary and acceptance criteria." }],
17
+ },
18
+ },
19
+ {
20
+ id: "implement",
21
+ role: "executor",
22
+ description: "Implement the approved scoped change.",
23
+ depends_on: ["plan"],
24
+ prompt: "Implement {{task_id}} according to the approved plan. Keep edits scoped.",
25
+ handoff: {
26
+ consumes: [{ type: "summary", name: "implementation_plan", from: "plan", description: "Approved plan." }],
27
+ produces: [{ type: "git_commit", description: "Implementation commit candidate." }],
28
+ },
29
+ },
30
+ {
31
+ id: "verify",
32
+ role: "test-engineer",
33
+ description: "Run build, tests, and targeted validation.",
34
+ depends_on: ["implement"],
35
+ prompt: "Verify {{task_id}} with build, tests, and focused manual checks where needed.",
36
+ handoff: {
37
+ consumes: [{ type: "git_commit", from: "implement", description: "Implementation commit candidate." }],
38
+ produces: [{ type: "test_result", path: "reports/{{task_id}}-verification.txt", description: "Verification result." }],
39
+ },
40
+ },
41
+ {
42
+ id: "review",
43
+ role: "reviewer",
44
+ description: "Review the change against acceptance criteria and evidence.",
45
+ depends_on: ["verify"],
46
+ prompt: "Review {{task_id}} for regressions, missing tests, and boundary violations.",
47
+ },
48
+ ],
49
+ retry_policy: {
50
+ max_retries: 1,
51
+ retry_from: "implement",
52
+ backoff: "none",
53
+ },
54
+ },
55
+ },
56
+ {
57
+ id: "evidence_review",
58
+ label: "Evidence Review",
59
+ summary: "Collect, classify, and summarize runtime or workflow evidence before human review.",
60
+ workflow: {
61
+ name: "draft-evidence-review",
62
+ description: "Draft evidence review workflow.",
63
+ steps: [
64
+ {
65
+ id: "collect",
66
+ role: "explore",
67
+ description: "Collect local evidence from read-only sources.",
68
+ prompt: "Collect evidence for {{task_id}} without mutating project state.",
69
+ handoff: {
70
+ produces: [{ type: "summary", name: "evidence_index", description: "Evidence source index." }],
71
+ },
72
+ },
73
+ {
74
+ id: "classify",
75
+ role: "quality-reviewer",
76
+ description: "Classify evidence authority, gaps, and unsupported claims.",
77
+ depends_on: ["collect"],
78
+ prompt: "Classify evidence authority for {{task_id}} without upgrading weak evidence.",
79
+ handoff: {
80
+ consumes: [{ type: "summary", name: "evidence_index", from: "collect", description: "Evidence source index." }],
81
+ produces: [{ type: "summary", name: "authority_review", description: "Authority review." }],
82
+ },
83
+ },
84
+ {
85
+ id: "human-review",
86
+ role: "reviewer",
87
+ description: "Prepare a human-readable review and open questions.",
88
+ depends_on: ["classify"],
89
+ prompt: "Prepare the review for {{task_id}} with unknown and unsupported fields visible.",
90
+ },
91
+ ],
92
+ },
93
+ },
94
+ {
95
+ id: "rewrite_governance",
96
+ label: "Rewrite Governance",
97
+ summary: "Lock intent and boundaries, rewrite safely, prove logic equivalence, and review evidence.",
98
+ workflow: {
99
+ name: "draft-rewrite-governance",
100
+ description: "Draft behavior-preserving rewrite workflow.",
101
+ steps: [
102
+ {
103
+ id: "intent-lock",
104
+ role: "planner",
105
+ description: "Record the exact rewrite intent and non-goals.",
106
+ prompt: "Lock the rewrite intent for {{task_id}}. Record non-goals and forbidden improvements.",
107
+ handoff: {
108
+ produces: [{ type: "summary", name: "intent_lock", description: "Intent, non-goals, and success definition." }],
109
+ },
110
+ },
111
+ {
112
+ id: "boundary-freeze",
113
+ role: "architect",
114
+ description: "Freeze read/write boundaries and allowed behavior deltas.",
115
+ depends_on: ["intent-lock"],
116
+ prompt: "Define source truth, write scope, allowed changes, and escalation rules for {{task_id}}.",
117
+ handoff: {
118
+ consumes: [{ type: "summary", name: "intent_lock", from: "intent-lock", description: "Intent lock." }],
119
+ produces: [{ type: "summary", name: "boundary_contract", description: "Read/write boundaries and allowed deltas." }],
120
+ },
121
+ },
122
+ {
123
+ id: "baseline",
124
+ role: "test-engineer",
125
+ description: "Capture current behavior and verification baseline.",
126
+ depends_on: ["boundary-freeze"],
127
+ prompt: "Capture baseline behavior and tests for {{task_id}} before rewrite work starts.",
128
+ handoff: {
129
+ produces: [{ type: "test_result", name: "baseline_result", description: "Baseline behavior and verification evidence." }],
130
+ },
131
+ },
132
+ {
133
+ id: "rewrite",
134
+ role: "executor",
135
+ description: "Apply the smallest behavior-preserving rewrite.",
136
+ depends_on: ["baseline"],
137
+ prompt: "Rewrite {{task_id}} inside the approved boundary. Preserve behavior before improving structure.",
138
+ handoff: {
139
+ consumes: [
140
+ { type: "summary", name: "boundary_contract", from: "boundary-freeze", description: "Boundary contract." },
141
+ { type: "test_result", name: "baseline_result", from: "baseline", description: "Baseline evidence." },
142
+ ],
143
+ produces: [{ type: "git_commit", description: "Rewrite candidate." }],
144
+ },
145
+ },
146
+ {
147
+ id: "equivalence",
148
+ role: "verifier",
149
+ description: "Verify logic equivalence against the baseline.",
150
+ depends_on: ["rewrite"],
151
+ prompt: "Check logic equivalence for {{task_id}}. Escalate any behavior delta.",
152
+ handoff: {
153
+ consumes: [{ type: "git_commit", from: "rewrite", description: "Rewrite candidate." }],
154
+ produces: [{ type: "test_result", name: "equivalence_result", description: "Equivalence verification result." }],
155
+ },
156
+ },
157
+ {
158
+ id: "review",
159
+ role: "reviewer",
160
+ description: "Review boundaries, equivalence evidence, and remaining risk.",
161
+ depends_on: ["equivalence"],
162
+ prompt: "Review {{task_id}} against intent lock, boundary contract, and equivalence evidence.",
163
+ },
164
+ ],
165
+ retry_policy: {
166
+ max_retries: 2,
167
+ retry_from: "rewrite",
168
+ backoff: "none",
169
+ },
170
+ },
171
+ },
172
+ ];
173
+ const SAFE_FLOW = [
174
+ "describe workflow in natural language",
175
+ "choose example shape",
176
+ "generate draft only",
177
+ "review YAML preview",
178
+ "human approval",
179
+ "manual DNA config edit",
180
+ "dna validate",
181
+ "dna sync",
182
+ ];
183
+ const FORBIDDEN = [
184
+ "write DNA config automatically",
185
+ "run dna sync automatically",
186
+ "edit runtime artifacts directly",
187
+ "treat draft YAML as policy truth",
188
+ "bypass human review",
189
+ ];
190
+ const EDITABLE_OPERATIONS = ["insert_step", "add_verifier", "tighten_boundary", "split_step", "replace_role"];
191
+ function slug(value) {
192
+ const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
193
+ return normalized || "workflow-draft";
194
+ }
195
+ function cloneWorkflow(workflow) {
196
+ return JSON.parse(JSON.stringify(workflow));
197
+ }
198
+ function uniqueStepId(steps, base) {
199
+ const existing = new Set(steps.map((step) => step.id));
200
+ let candidate = slug(base);
201
+ if (!candidate)
202
+ candidate = "new-step";
203
+ let suffix = 2;
204
+ while (existing.has(candidate)) {
205
+ candidate = `${slug(base)}-${suffix}`;
206
+ suffix++;
207
+ }
208
+ return candidate;
209
+ }
210
+ function selectedStepIndex(steps, targetStep) {
211
+ if (targetStep) {
212
+ const found = steps.findIndex((step) => step.id === targetStep);
213
+ if (found >= 0)
214
+ return found;
215
+ }
216
+ return Math.max(0, steps.length - 1);
217
+ }
218
+ function formatScalar(value) {
219
+ if (/^[A-Za-z0-9_./:-]+$/.test(value))
220
+ return value;
221
+ return JSON.stringify(value);
222
+ }
223
+ function formatYamlKey(value) {
224
+ if (/^[A-Za-z0-9_-]+$/.test(value))
225
+ return value;
226
+ return JSON.stringify(value);
227
+ }
228
+ function isYamlRecord(value) {
229
+ return typeof value === "object" && value !== null && !Array.isArray(value);
230
+ }
231
+ function appendMultilineString(lines, value, indent) {
232
+ const nested = " ".repeat(indent + 2);
233
+ for (const line of value.split("\n"))
234
+ lines.push(`${nested}${line}`);
235
+ }
236
+ function appendYamlPair(lines, key, value, indent) {
237
+ const prefix = " ".repeat(indent);
238
+ const yamlKey = formatYamlKey(key);
239
+ if (typeof value === "string") {
240
+ if (value.includes("\n")) {
241
+ lines.push(`${prefix}${yamlKey}: |`);
242
+ appendMultilineString(lines, value.endsWith("\n") ? value.slice(0, -1) : value, indent);
243
+ }
244
+ else {
245
+ lines.push(`${prefix}${yamlKey}: ${formatScalar(value)}`);
246
+ }
247
+ return;
248
+ }
249
+ if (typeof value === "number" || typeof value === "boolean") {
250
+ lines.push(`${prefix}${yamlKey}: ${String(value)}`);
251
+ return;
252
+ }
253
+ if (value === null || value === undefined) {
254
+ lines.push(`${prefix}${yamlKey}: null`);
255
+ return;
256
+ }
257
+ if (Array.isArray(value)) {
258
+ if (value.length === 0) {
259
+ lines.push(`${prefix}${yamlKey}: []`);
260
+ return;
261
+ }
262
+ lines.push(`${prefix}${yamlKey}:`);
263
+ appendYamlArray(lines, value, indent + 2);
264
+ return;
265
+ }
266
+ if (isYamlRecord(value)) {
267
+ const entries = Object.entries(value).filter(([, item]) => item !== undefined);
268
+ if (entries.length === 0) {
269
+ lines.push(`${prefix}${yamlKey}: {}`);
270
+ return;
271
+ }
272
+ lines.push(`${prefix}${yamlKey}:`);
273
+ for (const [childKey, childValue] of entries)
274
+ appendYamlPair(lines, childKey, childValue, indent + 2);
275
+ return;
276
+ }
277
+ lines.push(`${prefix}${yamlKey}: ${formatScalar(String(value))}`);
278
+ }
279
+ function appendYamlArray(lines, values, indent) {
280
+ const prefix = " ".repeat(indent);
281
+ for (const value of values) {
282
+ if (typeof value === "string") {
283
+ if (value.includes("\n")) {
284
+ lines.push(`${prefix}- |`);
285
+ appendMultilineString(lines, value.endsWith("\n") ? value.slice(0, -1) : value, indent);
286
+ }
287
+ else {
288
+ lines.push(`${prefix}- ${formatScalar(value)}`);
289
+ }
290
+ continue;
291
+ }
292
+ if (typeof value === "number" || typeof value === "boolean") {
293
+ lines.push(`${prefix}- ${String(value)}`);
294
+ continue;
295
+ }
296
+ if (value === null || value === undefined) {
297
+ lines.push(`${prefix}- null`);
298
+ continue;
299
+ }
300
+ if (Array.isArray(value)) {
301
+ lines.push(`${prefix}- items:`);
302
+ appendYamlArray(lines, value, indent + 2);
303
+ continue;
304
+ }
305
+ if (isYamlRecord(value)) {
306
+ const entries = Object.entries(value).filter(([, item]) => item !== undefined);
307
+ if (entries.length === 0) {
308
+ lines.push(`${prefix}- {}`);
309
+ continue;
310
+ }
311
+ const [[firstKey, firstValue], ...rest] = entries;
312
+ appendYamlArrayObjectFirstPair(lines, firstKey, firstValue, indent);
313
+ for (const [childKey, childValue] of rest)
314
+ appendYamlPair(lines, childKey, childValue, indent + 2);
315
+ continue;
316
+ }
317
+ lines.push(`${prefix}- ${formatScalar(String(value))}`);
318
+ }
319
+ }
320
+ function appendYamlArrayObjectFirstPair(lines, key, value, indent) {
321
+ const prefix = " ".repeat(indent);
322
+ const yamlKey = formatYamlKey(key);
323
+ if (typeof value === "string" && !value.includes("\n")) {
324
+ lines.push(`${prefix}- ${yamlKey}: ${formatScalar(value)}`);
325
+ return;
326
+ }
327
+ if (typeof value === "number" || typeof value === "boolean") {
328
+ lines.push(`${prefix}- ${yamlKey}: ${String(value)}`);
329
+ return;
330
+ }
331
+ if (value === null || value === undefined) {
332
+ lines.push(`${prefix}- ${yamlKey}: null`);
333
+ return;
334
+ }
335
+ lines.push(`${prefix}- ${yamlKey}:`);
336
+ if (typeof value === "string") {
337
+ appendMultilineString(lines, value.endsWith("\n") ? value.slice(0, -1) : value, indent);
338
+ }
339
+ else if (Array.isArray(value)) {
340
+ appendYamlArray(lines, value, indent + 2);
341
+ }
342
+ else if (isYamlRecord(value)) {
343
+ for (const [childKey, childValue] of Object.entries(value))
344
+ appendYamlPair(lines, childKey, childValue, indent + 2);
345
+ }
346
+ else {
347
+ lines.push(`${" ".repeat(indent + 2)}${formatScalar(String(value))}`);
348
+ }
349
+ }
350
+ function namespaceFromName(name) {
351
+ const parts = slug(name).split("-").filter(Boolean);
352
+ const candidate = parts.map((part) => part[0]).join("").slice(0, 8);
353
+ return candidate || "wf";
354
+ }
355
+ function formatHandoffArtifact(artifact, indent) {
356
+ const lines = [`${indent}- type: ${artifact.type}`];
357
+ if (artifact.path)
358
+ lines.push(`${indent} path: ${formatScalar(artifact.path)}`);
359
+ if (artifact.name)
360
+ lines.push(`${indent} name: ${formatScalar(artifact.name)}`);
361
+ if (artifact.from)
362
+ lines.push(`${indent} from: ${formatScalar(artifact.from)}`);
363
+ if (artifact.required === false)
364
+ lines.push(`${indent} required: false`);
365
+ lines.push(`${indent} description: ${formatScalar(artifact.description)}`);
366
+ return lines;
367
+ }
368
+ function formatEvidenceLinks(evidence) {
369
+ if (!evidence || evidence.length === 0)
370
+ return [];
371
+ const lines = ["evidence:"];
372
+ for (const link of evidence) {
373
+ lines.push(` - label: ${formatScalar(link.label)}`);
374
+ lines.push(` kind: ${link.kind}`);
375
+ lines.push(` path: ${formatScalar(link.path)}`);
376
+ if (link.description)
377
+ lines.push(` description: ${formatScalar(link.description)}`);
378
+ }
379
+ return lines;
380
+ }
381
+ function formatTemplateContext(context, fallbackWorkflow) {
382
+ const lines = [];
383
+ const extraSections = context?.extraSections ?? {};
384
+ for (const [key, value] of Object.entries(extraSections)) {
385
+ appendYamlPair(lines, key, value, 0);
386
+ }
387
+ if (context?.roles && Object.keys(context.roles).length > 0)
388
+ appendYamlPair(lines, "roles", context.roles, 0);
389
+ if (context?.controllers && Object.keys(context.controllers).length > 0)
390
+ appendYamlPair(lines, "controllers", context.controllers, 0);
391
+ if (context?.workflows && Object.keys(context.workflows).length > 0) {
392
+ appendYamlPair(lines, "workflows", context.workflows, 0);
393
+ }
394
+ else {
395
+ lines.push(workflowDraftToYaml(fallbackWorkflow).trimEnd());
396
+ }
397
+ return lines;
398
+ }
399
+ export function workflowDraftToYaml(workflow) {
400
+ const lines = ["workflow:", ` name: ${formatScalar(workflow.name)}`];
401
+ if (workflow.description)
402
+ lines.push(` description: ${formatScalar(workflow.description)}`);
403
+ lines.push(" steps:");
404
+ for (const step of workflow.steps) {
405
+ lines.push(` - id: ${formatScalar(step.id)}`);
406
+ lines.push(` role: ${formatScalar(step.role)}`);
407
+ if (step.description)
408
+ lines.push(` description: ${formatScalar(step.description)}`);
409
+ if (step.depends_on && step.depends_on.length > 0)
410
+ lines.push(` depends_on: [${step.depends_on.map(formatScalar).join(", ")}]`);
411
+ if (step.prompt)
412
+ lines.push(` prompt: ${formatScalar(step.prompt)}`);
413
+ if (step.checkpoints && step.checkpoints.length > 0) {
414
+ lines.push(" checkpoints:");
415
+ for (const checkpoint of step.checkpoints) {
416
+ lines.push(` - assert: ${formatScalar(checkpoint.assert)}`);
417
+ lines.push(` message: ${formatScalar(checkpoint.message)}`);
418
+ if (checkpoint.action)
419
+ lines.push(` action: ${checkpoint.action}`);
420
+ }
421
+ }
422
+ if (step.handoff?.consumes?.length || step.handoff?.produces?.length) {
423
+ lines.push(" handoff:");
424
+ if (step.handoff.consumes?.length) {
425
+ lines.push(" consumes:");
426
+ step.handoff.consumes.forEach((artifact) => lines.push(...formatHandoffArtifact(artifact, " ")));
427
+ }
428
+ if (step.handoff.produces?.length) {
429
+ lines.push(" produces:");
430
+ step.handoff.produces.forEach((artifact) => lines.push(...formatHandoffArtifact(artifact, " ")));
431
+ }
432
+ }
433
+ }
434
+ if (workflow.retry_policy) {
435
+ lines.push(" retry_policy:");
436
+ lines.push(` max_retries: ${workflow.retry_policy.max_retries}`);
437
+ if (workflow.retry_policy.retry_from)
438
+ lines.push(` retry_from: ${formatScalar(workflow.retry_policy.retry_from)}`);
439
+ if (workflow.retry_policy.backoff)
440
+ lines.push(` backoff: ${workflow.retry_policy.backoff}`);
441
+ }
442
+ return `${lines.join("\n")}\n`;
443
+ }
444
+ export function workflowDraftToTemplateYaml(params) {
445
+ const templateName = slug(params.templateName);
446
+ const namespace = namespaceFromName(templateName);
447
+ const header = [
448
+ "# Generated by dna workflow draft --save-template as an example-clone scaffold.",
449
+ "# This file's structure was not derived from the goal; use dna workflow authoring-packet for true workflow authoring.",
450
+ "# Review this file before running dna init, dna sync, or using it as policy truth.",
451
+ "version: \"0.1.0\"",
452
+ `id: template_${templateName.replaceAll("-", "_")}`,
453
+ `name: ${formatScalar(params.workflow.name)}`,
454
+ `purpose: ${formatScalar(params.workflow.description ?? `Draft workflow asset for ${params.workflow.name}`)}`,
455
+ "maturity: draft",
456
+ "fit:",
457
+ " - Reviewable workflow asset draft created from a human-described working method.",
458
+ "not_for:",
459
+ " - Runtime policy truth before human review, validation, and explicit adoption.",
460
+ "inputs:",
461
+ " - Natural-language workflow description",
462
+ " - Human review notes",
463
+ "outputs:",
464
+ " - C1 workflow asset YAML",
465
+ " - Reviewable workflow steps, roles, and handoffs",
466
+ "type: project",
467
+ `namespace: ${namespace}`,
468
+ "lineage:",
469
+ " source: dna workflow draft --save-template",
470
+ " generation_mode: example_clone_scaffold",
471
+ "cascade:",
472
+ " inherits: [\"species:default\"]",
473
+ " priority: 100",
474
+ "x_intentdna:",
475
+ " asset_status: draft",
476
+ " human_review_required: true",
477
+ ];
478
+ if (params.generatedAt)
479
+ header.push(` generated_at: ${formatScalar(params.generatedAt)}`);
480
+ if (params.lineage?.generatedFrom)
481
+ header.push(` generated_from: ${formatScalar(params.lineage.generatedFrom)}`);
482
+ if (params.lineage?.sourceAsset)
483
+ header.push(` source_asset: ${formatScalar(params.lineage.sourceAsset)}`);
484
+ if (params.lineage?.sourceCandidate)
485
+ header.push(` source_candidate: ${formatScalar(params.lineage.sourceCandidate)}`);
486
+ if (params.lineage?.sourceSuggestion)
487
+ header.push(` source_suggestion: ${formatScalar(params.lineage.sourceSuggestion)}`);
488
+ if (params.lineage?.sourceEvidence)
489
+ header.push(` source_evidence: ${formatScalar(params.lineage.sourceEvidence)}`);
490
+ const evidence = formatEvidenceLinks(params.evidence);
491
+ const evidenceBlock = evidence.length > 0 ? `${evidence.join("\n")}\n\n` : "";
492
+ return `${header.join("\n")}\n\n${evidenceBlock}${formatTemplateContext(params.templateContext, params.workflow).join("\n")}\n`;
493
+ }
494
+ export function workflowDraftExamples() {
495
+ return EXAMPLES.map((example) => ({
496
+ ...example,
497
+ workflow: cloneWorkflow(example.workflow),
498
+ }));
499
+ }
500
+ function inferDependencies(steps) {
501
+ const hasExplicit = steps.some((step) => step.depends_on && step.depends_on.length > 0);
502
+ if (hasExplicit)
503
+ return steps.map((step) => ({ ...step, depends_on: [...(step.depends_on ?? [])] }));
504
+ return steps.map((step, index) => index === 0 ? { ...step, depends_on: [] } : { ...step, depends_on: [steps[index - 1].id] });
505
+ }
506
+ function validateWorkflow(workflow) {
507
+ const messages = [];
508
+ if (!workflow.name.trim())
509
+ messages.push({ severity: "error", path: "workflow.name", message: "Workflow name is required." });
510
+ if (!Array.isArray(workflow.steps) || workflow.steps.length === 0) {
511
+ messages.push({ severity: "error", path: "workflow.steps", message: "Workflow must have at least one step." });
512
+ return { ok: false, messages, preview: { step_count: 0, parallel_groups: [], retry_max: 0, write_path: "draft_only" } };
513
+ }
514
+ const resolvedSteps = inferDependencies(workflow.steps);
515
+ const stepIds = new Set();
516
+ for (const [index, step] of resolvedSteps.entries()) {
517
+ const basePath = `workflow.steps.${index}`;
518
+ if (!step.id.trim())
519
+ messages.push({ severity: "error", path: `${basePath}.id`, message: "Step id is required." });
520
+ if (stepIds.has(step.id))
521
+ messages.push({ severity: "error", path: `${basePath}.id`, message: `Duplicate step id: ${step.id}.` });
522
+ stepIds.add(step.id);
523
+ if (!step.role.trim())
524
+ messages.push({ severity: "error", path: `${basePath}.role`, message: "Step role is required." });
525
+ }
526
+ for (const [index, step] of resolvedSteps.entries()) {
527
+ for (const dep of step.depends_on ?? []) {
528
+ if (dep === step.id)
529
+ messages.push({ severity: "error", path: `workflow.steps.${index}.depends_on`, message: `Step ${step.id} cannot depend on itself.` });
530
+ if (!stepIds.has(dep))
531
+ messages.push({ severity: "error", path: `workflow.steps.${index}.depends_on`, message: `Step ${step.id} depends on missing step ${dep}.` });
532
+ }
533
+ }
534
+ const inDegree = new Map();
535
+ const dependents = new Map();
536
+ resolvedSteps.forEach((step) => {
537
+ inDegree.set(step.id, step.depends_on?.length ?? 0);
538
+ dependents.set(step.id, []);
539
+ });
540
+ resolvedSteps.forEach((step) => {
541
+ (step.depends_on ?? []).forEach((dep) => dependents.get(dep)?.push(step.id));
542
+ });
543
+ const groups = [];
544
+ let queue = [...resolvedSteps.filter((step) => (inDegree.get(step.id) ?? 0) === 0).map((step) => step.id)];
545
+ const sorted = [];
546
+ while (queue.length > 0) {
547
+ groups.push([...queue]);
548
+ const next = [];
549
+ for (const id of queue) {
550
+ sorted.push(id);
551
+ for (const dependent of dependents.get(id) ?? []) {
552
+ const nextDegree = (inDegree.get(dependent) ?? 0) - 1;
553
+ inDegree.set(dependent, nextDegree);
554
+ if (nextDegree === 0)
555
+ next.push(dependent);
556
+ }
557
+ }
558
+ queue = next;
559
+ }
560
+ if (sorted.length < resolvedSteps.length) {
561
+ const cycleIds = resolvedSteps.map((step) => step.id).filter((id) => !sorted.includes(id));
562
+ messages.push({ severity: "error", path: "workflow.steps", message: `Cycle detected involving steps: ${cycleIds.join(", ")}.` });
563
+ }
564
+ if (workflow.retry_policy?.retry_from && !stepIds.has(workflow.retry_policy.retry_from)) {
565
+ messages.push({ severity: "error", path: "workflow.retry_policy.retry_from", message: "retry_from must reference an existing step." });
566
+ }
567
+ if (workflow.retry_policy && workflow.retry_policy.max_retries < 0) {
568
+ messages.push({ severity: "error", path: "workflow.retry_policy.max_retries", message: "max_retries must be >= 0." });
569
+ }
570
+ if (messages.length === 0) {
571
+ messages.push({
572
+ severity: "info",
573
+ path: "workflow",
574
+ message: "Draft matches workflow preview rules. Human approval and dna validate/sync are still required before runtime use.",
575
+ });
576
+ }
577
+ return {
578
+ ok: messages.every((message) => message.severity !== "error"),
579
+ messages,
580
+ preview: {
581
+ step_count: workflow.steps.length,
582
+ parallel_groups: groups,
583
+ retry_max: workflow.retry_policy?.max_retries ?? 0,
584
+ write_path: "draft_only",
585
+ },
586
+ };
587
+ }
588
+ export function buildWorkflowDesignPacket(params) {
589
+ const templateName = params.templateName ?? "<name>";
590
+ return {
591
+ workflow: params.workflow.name,
592
+ goal: params.goal,
593
+ stepCount: params.workflow.steps.length,
594
+ editableOperations: [...EDITABLE_OPERATIONS],
595
+ reviewCommands: [
596
+ `dna workflow edit-plan "<change>" --from-template ${templateName}`,
597
+ `dna assets inspect ${templateName} --evidence`,
598
+ `dna assets diff ${templateName} ${templateName}-v2`,
599
+ `dna assets adoption ${templateName}-v2`,
600
+ ],
601
+ adoptionCommands: [
602
+ `dna org bind --asset ${templateName}-v2 --status active --sync-target codex`,
603
+ "dna org review",
604
+ "dna sync --codex",
605
+ ],
606
+ mutationBoundary: "Drafting and editing are review-only. They do not configure, adopt, sync, or mutate runtime policy until a human explicitly runs the follow-up commands.",
607
+ userPrompt: "Describe workflow edits in natural language; use explicit operation, target step, or role options only when you need to override inference.",
608
+ };
609
+ }
610
+ function templateNameFromSource(source) {
611
+ if (!source)
612
+ return undefined;
613
+ const parts = source.split(":");
614
+ if ((parts[0] === "template" || parts[0] === "suggestions") && parts[1])
615
+ return parts[1];
616
+ return source;
617
+ }
618
+ export function createWorkflowDraft(input, generatedAt = new Date().toISOString()) {
619
+ const example = EXAMPLES.find((candidate) => candidate.id === input.exampleId) ?? EXAMPLES[0];
620
+ const workflow = cloneWorkflow(example.workflow);
621
+ const taskId = slug(input.goal);
622
+ workflow.name = `${workflow.name}-${taskId}`.slice(0, 96);
623
+ workflow.description = `${example.summary} User-supplied scaffold label: ${input.goal || "draft workflow"}`;
624
+ if (input.strictness === "high") {
625
+ workflow.retry_policy = {
626
+ max_retries: Math.max(workflow.retry_policy?.max_retries ?? 0, 2),
627
+ retry_from: workflow.retry_policy?.retry_from ?? workflow.steps[1]?.id ?? workflow.steps[0]?.id,
628
+ backoff: "none",
629
+ };
630
+ workflow.steps = workflow.steps.map((step) => ({
631
+ ...step,
632
+ checkpoints: step.checkpoints ?? [{
633
+ assert: "validation_evidence_present",
634
+ message: "Draft preview requires explicit validation evidence before handoff.",
635
+ action: "warn",
636
+ }],
637
+ }));
638
+ }
639
+ const validation = validateWorkflow(workflow);
640
+ return {
641
+ mode: "draft_only",
642
+ generated_at: generatedAt,
643
+ input,
644
+ scaffold: {
645
+ kind: "example_clone_scaffold",
646
+ source_example: example.id,
647
+ positioning: "This draft is a quick scaffold cloned from a built-in example. Its structure is not derived from the goal; use dna workflow authoring-packet for true agent-authored workflow creation.",
648
+ deprecated_primary_creation_path: "dna workflow authoring-packet",
649
+ },
650
+ workflow,
651
+ design_packet: buildWorkflowDesignPacket({ workflow, goal: input.goal }),
652
+ yaml: workflowDraftToYaml(workflow),
653
+ validation,
654
+ safe_flow: [...SAFE_FLOW],
655
+ forbidden: [...FORBIDDEN],
656
+ };
657
+ }
658
+ export function createWorkflowEditPlanFromWorkflow(baseWorkflow, input, generatedAt = new Date().toISOString()) {
659
+ const baseWorkflowClone = cloneWorkflow(baseWorkflow);
660
+ const proposed = cloneWorkflow(baseWorkflow);
661
+ const index = selectedStepIndex(proposed.steps, input.targetStep);
662
+ const target = proposed.steps[index]?.id ?? "workflow";
663
+ const role = input.role?.trim() || "verifier";
664
+ const operations = [];
665
+ if (input.operation === "insert_step") {
666
+ const previous = proposed.steps[index];
667
+ const newId = uniqueStepId(proposed.steps, input.instruction || "inserted-step");
668
+ const newStep = {
669
+ id: newId,
670
+ role,
671
+ description: input.instruction || "Inserted workflow step.",
672
+ depends_on: previous ? [previous.id] : [],
673
+ prompt: `Handle: ${input.instruction || "inserted workflow step"} for {{task_id}}.`,
674
+ handoff: {
675
+ produces: [{ type: "summary", name: `${newId}_summary`, description: "Inserted step summary." }],
676
+ },
677
+ };
678
+ proposed.steps.splice(index + 1, 0, newStep);
679
+ const next = proposed.steps[index + 2];
680
+ if (next)
681
+ next.depends_on = [...new Set([...(next.depends_on ?? []).filter((dep) => dep !== previous?.id), newId])];
682
+ operations.push({
683
+ operation: input.operation,
684
+ target,
685
+ summary: `Insert ${newId} after ${target}.`,
686
+ human_review_required: true,
687
+ });
688
+ }
689
+ if (input.operation === "add_verifier") {
690
+ const previous = proposed.steps[index];
691
+ const newId = uniqueStepId(proposed.steps, "verify-evidence");
692
+ proposed.steps.splice(index + 1, 0, {
693
+ id: newId,
694
+ role: role || "verifier",
695
+ description: input.instruction || "Verify evidence before handoff.",
696
+ depends_on: previous ? [previous.id] : [],
697
+ prompt: `Verify evidence for {{task_id}}: ${input.instruction || "check required artifacts and tests"}.`,
698
+ checkpoints: [{
699
+ assert: "verification_evidence_present",
700
+ message: "Verifier must produce explicit evidence before downstream review.",
701
+ action: "block",
702
+ }],
703
+ handoff: {
704
+ produces: [{ type: "test_result", name: "verification_result", description: "Verification evidence." }],
705
+ },
706
+ });
707
+ const next = proposed.steps[index + 2];
708
+ if (next)
709
+ next.depends_on = [...new Set([...(next.depends_on ?? []).filter((dep) => dep !== previous?.id), newId])];
710
+ operations.push({
711
+ operation: input.operation,
712
+ target,
713
+ summary: `Add verifier step after ${target}.`,
714
+ human_review_required: true,
715
+ });
716
+ }
717
+ if (input.operation === "tighten_boundary") {
718
+ const step = proposed.steps[index];
719
+ step.checkpoints = [
720
+ ...(step.checkpoints ?? []),
721
+ {
722
+ assert: "boundary_contract_satisfied",
723
+ message: input.instruction || "Boundary contract must be satisfied before this step can complete.",
724
+ action: "block",
725
+ },
726
+ ];
727
+ operations.push({
728
+ operation: input.operation,
729
+ target,
730
+ summary: `Add blocking boundary checkpoint to ${target}.`,
731
+ human_review_required: true,
732
+ });
733
+ }
734
+ if (input.operation === "split_step") {
735
+ const step = proposed.steps[index];
736
+ const firstId = uniqueStepId(proposed.steps, `${step.id}-prepare`);
737
+ const secondId = uniqueStepId([...proposed.steps, { ...step, id: firstId }], `${step.id}-execute`);
738
+ const originalDeps = step.depends_on ?? [];
739
+ proposed.steps.splice(index, 1, {
740
+ ...step,
741
+ id: firstId,
742
+ description: `Prepare: ${step.description ?? input.instruction}`,
743
+ depends_on: originalDeps,
744
+ }, {
745
+ ...step,
746
+ id: secondId,
747
+ description: `Execute: ${input.instruction || step.description || "split step execution"}`,
748
+ depends_on: [firstId],
749
+ });
750
+ for (const candidate of proposed.steps) {
751
+ if (candidate.depends_on?.includes(step.id)) {
752
+ candidate.depends_on = candidate.depends_on.map((dep) => dep === step.id ? secondId : dep);
753
+ }
754
+ }
755
+ operations.push({
756
+ operation: input.operation,
757
+ target,
758
+ summary: `Split ${target} into ${firstId} and ${secondId}.`,
759
+ human_review_required: true,
760
+ });
761
+ }
762
+ if (input.operation === "replace_role") {
763
+ const step = proposed.steps[index];
764
+ const oldRole = step.role;
765
+ step.role = role;
766
+ step.description = `${step.description ?? step.id} Role changed from ${oldRole} to ${role}. ${input.instruction}`.trim();
767
+ operations.push({
768
+ operation: input.operation,
769
+ target,
770
+ summary: `Replace role on ${target}: ${oldRole} -> ${role}.`,
771
+ human_review_required: true,
772
+ });
773
+ }
774
+ const validation = validateWorkflow(proposed);
775
+ return {
776
+ mode: "edit_plan_only",
777
+ generated_at: generatedAt,
778
+ input,
779
+ base_workflow: baseWorkflowClone,
780
+ proposed_workflow: proposed,
781
+ design_packet: buildWorkflowDesignPacket({ workflow: proposed, goal: input.goal, templateName: templateNameFromSource(input.source) }),
782
+ operations,
783
+ yaml: workflowDraftToYaml(proposed),
784
+ validation,
785
+ safe_flow: [
786
+ "generate edit plan only",
787
+ "review operation summary",
788
+ "review YAML preview",
789
+ "human approval",
790
+ "manual DNA config edit",
791
+ "dna validate",
792
+ "dna sync",
793
+ ],
794
+ forbidden: [...FORBIDDEN],
795
+ };
796
+ }
797
+ export function createWorkflowEditPlan(input, generatedAt = new Date().toISOString()) {
798
+ const baseDraft = createWorkflowDraft(input, generatedAt);
799
+ return createWorkflowEditPlanFromWorkflow(baseDraft.workflow, input, generatedAt);
800
+ }