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
@@ -2,10 +2,19 @@
2
2
  * dna templates — template catalog and deployment commands.
3
3
  */
4
4
  import { readdirSync, copyFileSync, existsSync, readFileSync, mkdirSync } from "node:fs";
5
- import { resolve, join } from "node:path";
5
+ import { mkdir, readdir, writeFile } from "node:fs/promises";
6
+ import { homedir } from "node:os";
7
+ import { resolve, join, basename, dirname } from "node:path";
6
8
  import { execSync } from "node:child_process";
7
9
  import { readFile } from "node:fs/promises";
8
10
  import { findTemplateCatalogEntry, listTemplateCatalog } from "../../templates/catalog.js";
11
+ import { buildTemplateMarketplaceCatalog, getTemplateMarketplaceMetadata, } from "../../templates/marketplace.js";
12
+ import { parseYAML } from "../../schema/yaml-parser.js";
13
+ import { findWorkflowAsset } from "../../assets/catalog.js";
14
+ import { resolveAuthToken } from "./auth.js";
15
+ import { missingCliApiTokenReason } from "./cloud-auth-guidance.js";
16
+ import { createWorkflowDraft, workflowDraftToTemplateYaml, } from "../../workflow/index.js";
17
+ import { WORKFLOW_ASSET_IDS } from "../../templates/metadata.js";
9
18
  /**
10
19
  * Resolve the global intentdna templates directory.
11
20
  * Strategy: npm root -g + /intentdna/dist/templates
@@ -54,12 +63,18 @@ export async function runTemplatesList(opts = {}) {
54
63
  return 1;
55
64
  }
56
65
  process.stderr.write(`Available templates (${templates.length}):\n`);
66
+ writeTemplateSourceSummary(templates);
57
67
  for (const template of templates) {
68
+ const marketplace = await getTemplateMarketplaceMetadata(template);
58
69
  const ns = template.namespace ? ` ns=${template.namespace}` : "";
59
70
  const version = template.version ? ` v${template.version}` : "";
60
71
  process.stderr.write(` - ${template.name} [${template.source}]${ns}${version}`);
61
72
  if (template.displayName)
62
73
  process.stderr.write(` — ${template.displayName}`);
74
+ process.stderr.write(`\n marketplace=${marketplace.visibility} harnesses=${marketplace.supportedHarnesses.join(",")} sync=${marketplace.syncSurfaces.join(",")}`);
75
+ process.stderr.write(`\n purpose=${marketplace.purpose}`);
76
+ process.stderr.write(`\n evidence=${marketplace.evidencePosture} maturity=${marketplace.maturity}`);
77
+ process.stderr.write(`\n review=${marketplace.review.inspectCommand} -> ${marketplace.review.adoptionCommand}`);
63
78
  process.stderr.write("\n");
64
79
  }
65
80
  return 0;
@@ -75,6 +90,50 @@ export async function runTemplatesShow(opts) {
75
90
  return 2;
76
91
  }
77
92
  const content = await readFile(template.path, "utf-8");
93
+ const marketplace = await getTemplateMarketplaceMetadata(template, { content });
94
+ if (opts.json) {
95
+ const data = parseYAML(content);
96
+ const selected = buildTemplateManagementItem(template, content, data, marketplace);
97
+ const result = {
98
+ schema_version: "intentdna.template_show.v1",
99
+ mode: "template_show",
100
+ selected: {
101
+ ...selected,
102
+ path: template.path,
103
+ nextCommands: [
104
+ selected.commands.init,
105
+ ...marketplace.install.syncCommands,
106
+ selected.commands.assetInspect,
107
+ selected.commands.assetAdoption,
108
+ selected.commands.orgBind,
109
+ selected.commands.orgReview,
110
+ selected.commands.orgRollout,
111
+ ],
112
+ },
113
+ commandEffects: {
114
+ mutation_performed: false,
115
+ network_performed: false,
116
+ runtime_authority: false,
117
+ online_required: false,
118
+ cloud_required: false,
119
+ syncs_harness: false,
120
+ executes_workflow: false,
121
+ },
122
+ boundaries: [
123
+ "dna templates show is local asset selection and inspection only; it does not initialize, sync, install, approve, or execute workflows.",
124
+ "Template marketplace metadata distributes workflow assets; it does not make runtime allow/warn/block/validate decisions.",
125
+ "Online and Supabase are not required for local template selection.",
126
+ ],
127
+ document: {
128
+ path: template.path,
129
+ language: "yaml",
130
+ lineCount: content.split(/\r?\n/).length,
131
+ content,
132
+ },
133
+ };
134
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
135
+ return 0;
136
+ }
78
137
  process.stderr.write(`Template: ${template.name}\n`);
79
138
  process.stderr.write(`Source: ${template.source}\n`);
80
139
  process.stderr.write(`Path: ${template.path}\n`);
@@ -88,12 +147,2103 @@ export async function runTemplatesShow(opts) {
88
147
  if (template.keywords.length > 0) {
89
148
  process.stderr.write(`Match keywords: ${template.keywords.join(", ")}\n`);
90
149
  }
150
+ process.stderr.write(`Marketplace visibility: ${marketplace.visibility}\n`);
151
+ process.stderr.write(`Purpose: ${marketplace.purpose}\n`);
152
+ process.stderr.write(`Fit:\n`);
153
+ for (const item of marketplace.fit)
154
+ process.stderr.write(` - ${item}\n`);
155
+ process.stderr.write(`Inputs:\n`);
156
+ for (const item of marketplace.inputs)
157
+ process.stderr.write(` - ${item}\n`);
158
+ process.stderr.write(`Outputs:\n`);
159
+ for (const item of marketplace.outputs)
160
+ process.stderr.write(` - ${item}\n`);
161
+ process.stderr.write(`Evidence posture: ${marketplace.evidencePosture}\n`);
162
+ writeMarketplaceEvidenceGap(marketplace);
163
+ process.stderr.write(`Maturity: ${marketplace.maturity}\n`);
164
+ process.stderr.write("Lifecycle:\n");
165
+ process.stderr.write(` state: ${marketplace.lifecycleGuide.state}\n`);
166
+ process.stderr.write(` meaning: ${marketplace.lifecycleGuide.meaning}\n`);
167
+ process.stderr.write(` available now:\n`);
168
+ for (const item of marketplace.lifecycleGuide.availableNow)
169
+ process.stderr.write(` - ${item}\n`);
170
+ process.stderr.write(` next: ${marketplace.lifecycleGuide.nextStep}\n`);
171
+ process.stderr.write(` must not imply:\n`);
172
+ for (const item of marketplace.lifecycleGuide.notAllowedToImply)
173
+ process.stderr.write(` - ${item}\n`);
174
+ process.stderr.write(`Supported harnesses: ${marketplace.supportedHarnesses.join(", ")}\n`);
175
+ process.stderr.write(`Required DNA version: ${marketplace.requiredDnaVersion}\n`);
176
+ process.stderr.write(`Sync surfaces: ${marketplace.syncSurfaces.join(", ")}\n`);
177
+ process.stderr.write(`Install: ${marketplace.install.command}\n`);
178
+ process.stderr.write("Sync route:\n");
179
+ for (const command of marketplace.install.syncCommands)
180
+ process.stderr.write(` - ${command}\n`);
181
+ process.stderr.write("Review route:\n");
182
+ process.stderr.write(` - ${marketplace.review.inspectCommand}\n`);
183
+ process.stderr.write(` - ${marketplace.review.adoptionCommand}\n`);
184
+ process.stderr.write(` - ${marketplace.review.orgBindCommand}\n`);
185
+ process.stderr.write(` - ${marketplace.review.orgReviewCommand}\n`);
186
+ process.stderr.write(` - ${marketplace.review.rolloutCommand}\n`);
187
+ process.stderr.write(`Review boundary: ${marketplace.review.boundary}\n`);
188
+ writeMarketplaceAssetRelations(marketplace);
189
+ if (marketplace.evidenceExamples.length > 0) {
190
+ process.stderr.write("Evidence examples:\n");
191
+ for (const example of marketplace.evidenceExamples) {
192
+ process.stderr.write(` - [${example.kind}] ${example.label}: ${example.path}\n`);
193
+ }
194
+ }
91
195
  process.stderr.write("\n---\n");
92
196
  process.stdout.write(content);
93
197
  if (!content.endsWith("\n"))
94
198
  process.stdout.write("\n");
95
199
  return 0;
96
200
  }
201
+ export async function runTemplatesMarketplace(opts = {}) {
202
+ const catalog = await buildTemplateMarketplaceCatalog({ projectDir: opts.projectDir });
203
+ if (opts.output) {
204
+ await mkdir(dirname(opts.output), { recursive: true });
205
+ await writeFile(opts.output, JSON.stringify(catalog, null, 2) + "\n", "utf-8");
206
+ }
207
+ if (opts.json) {
208
+ process.stdout.write(JSON.stringify(catalog, null, 2) + "\n");
209
+ return 0;
210
+ }
211
+ process.stderr.write("Template marketplace catalog\n");
212
+ process.stderr.write(`Schema: ${catalog.schema_version}\n`);
213
+ process.stderr.write(`Templates: ${catalog.totals.templates} public=${catalog.totals.public} personal=${catalog.totals.personal} internal_ready=${catalog.totals.internal_ready}\n`);
214
+ writeTemplateCatalogSourceSummary(catalog.totals);
215
+ process.stderr.write(`Harness support: claude_code=${catalog.totals.claude_code} codex_cli=${catalog.totals.codex_cli}\n`);
216
+ if (opts.output)
217
+ process.stderr.write(`Wrote: ${opts.output}\n`);
218
+ process.stderr.write("Installable templates:\n");
219
+ for (const item of catalog.items.slice(0, 20)) {
220
+ process.stderr.write(` - ${item.name} [${item.marketplace.visibility}/${item.marketplace.maturity}] harnesses=${item.marketplace.supportedHarnesses.join(",")} install="${item.marketplace.install.command}"\n`);
221
+ process.stderr.write(` ${item.marketplace.purpose}\n`);
222
+ process.stderr.write(` review="${item.marketplace.review.inspectCommand}" adoption="${item.marketplace.review.adoptionCommand}"\n`);
223
+ writeMarketplaceAssetRelations(item.marketplace, " ");
224
+ }
225
+ if (catalog.items.length > 20)
226
+ process.stderr.write(` ... ${catalog.items.length - 20} more\n`);
227
+ const personalDrafts = catalog.items.filter((item) => item.marketplace.visibility === "personal");
228
+ if (personalDrafts.length > 0) {
229
+ process.stderr.write("Personal drafts from .dna/templates:\n");
230
+ for (const item of personalDrafts) {
231
+ process.stderr.write(` - ${item.name} [personal/${item.marketplace.maturity}] review="${item.marketplace.review.inspectCommand}" adoption="${item.marketplace.review.adoptionCommand}"\n`);
232
+ }
233
+ }
234
+ const internalReady = catalog.items.filter((item) => item.marketplace.visibility === "internal_ready");
235
+ if (internalReady.length > 0) {
236
+ process.stderr.write("Internal-ready adoption candidates:\n");
237
+ for (const item of internalReady) {
238
+ process.stderr.write(` - ${item.name} [internal_ready/${item.marketplace.maturity}] review="${item.marketplace.review.inspectCommand}" org="${item.marketplace.review.orgBindCommand}"\n`);
239
+ }
240
+ }
241
+ process.stderr.write(`Boundary: ${catalog.boundaries[0]}\n`);
242
+ return 0;
243
+ }
244
+ function writeTemplateSourceSummary(templates) {
245
+ const builtin = templates.filter((template) => template.source === "builtin").length;
246
+ const project = templates.filter((template) => template.source === "project").length;
247
+ process.stderr.write(`Source split: builtin=${builtin} project=${project}\n`);
248
+ process.stderr.write("Count boundary: builtin templates are public assets from src/templates; project templates are personal drafts from .dna/templates.\n");
249
+ process.stderr.write("Lifecycle boundary: internal_ready is an adoption candidate, not org_private SaaS visibility; marketplace metadata does not install, approve, or execute workflows.\n");
250
+ }
251
+ function writeTemplateCatalogSourceSummary(totals) {
252
+ process.stderr.write("Source split: public=built-in marketplace assets, personal=project .dna/templates drafts, internal_ready=local adoption candidates.\n");
253
+ process.stderr.write(`Count boundary: this local catalog includes ${totals.public} public built-ins and ${totals.personal} project-local personal drafts; Online static projection may show only built-ins.\n`);
254
+ process.stderr.write("Lifecycle boundary: internal_ready is not org_private; org_private requires Supabase auth/RLS/storage/permissions/audit and publication receipts.\n");
255
+ }
256
+ function writeMarketplaceAssetRelations(marketplace, indent = "") {
257
+ if (marketplace.assetRelations.length === 0)
258
+ return;
259
+ process.stderr.write(`${indent}Related workflow assets:\n`);
260
+ for (const relation of marketplace.assetRelations) {
261
+ process.stderr.write(`${indent} - ${relation.relatedTemplate} [${relation.relation}]\n`);
262
+ process.stderr.write(`${indent} purpose=${relation.purpose}\n`);
263
+ for (const preserved of relation.preserves.slice(0, 3)) {
264
+ process.stderr.write(`${indent} preserves=${preserved}\n`);
265
+ }
266
+ for (const command of relation.nextCommands.slice(0, 3)) {
267
+ process.stderr.write(`${indent} next=${command}\n`);
268
+ }
269
+ for (const boundary of relation.notAllowedToImply.slice(0, 4)) {
270
+ process.stderr.write(`${indent} not_allowed=${boundary}\n`);
271
+ }
272
+ }
273
+ }
274
+ function writeMarketplaceEvidenceGap(marketplace, indent = "") {
275
+ const gap = marketplace.evidenceGap;
276
+ process.stderr.write(`${indent}Evidence route: ${gap.status}\n`);
277
+ process.stderr.write(`${indent} ${gap.summary}\n`);
278
+ process.stderr.write(`${indent} next commands:\n`);
279
+ for (const command of gap.nextCommands)
280
+ process.stderr.write(`${indent} - ${command}\n`);
281
+ process.stderr.write(`${indent} acceptance:\n`);
282
+ for (const criterion of gap.acceptanceCriteria)
283
+ process.stderr.write(`${indent} - ${criterion}\n`);
284
+ process.stderr.write(`${indent} not evidence:\n`);
285
+ for (const item of gap.notEvidence)
286
+ process.stderr.write(`${indent} - ${item}\n`);
287
+ }
288
+ function templatePublishBoundaries() {
289
+ return [
290
+ "Template publish uses CLI API token auth; it is not MCP authorization.",
291
+ "Org-private template publication is SaaS persistence and audit metadata only; it does not execute workflows.",
292
+ "Published templates remain workflow assets for local dna init/sync; dna-hook and local runtime evidence remain runtime truth.",
293
+ "runtime_authority is always false for template publish requests and receipts.",
294
+ "Source receipts are evidence references only and do not grant allow/warn/block/validate authority.",
295
+ "Dry-run performs no network request and creates no backend, auth, storage, or org-private SaaS state.",
296
+ ];
297
+ }
298
+ function templatePublicationsBoundaries() {
299
+ return [
300
+ "Template publication listing uses CLI API token auth; it is not MCP authorization.",
301
+ "Org-private template publication listing is a read-only SaaS projection; it does not execute workflows.",
302
+ "Listed templates remain workflow assets for local dna init/sync; dna-hook and local runtime evidence remain runtime truth.",
303
+ "runtime_authority is always false for template publication listing requests and receipts.",
304
+ "Org-private visibility is backed by Supabase organization membership, RLS, and audit metadata; it is not static catalog visibility.",
305
+ "Dry-run performs no network request and reads no backend, auth, storage, or org-private SaaS state.",
306
+ ];
307
+ }
308
+ function templateRevokeBoundaries() {
309
+ return [
310
+ "Template publication revoke uses CLI API token auth; it is not MCP authorization.",
311
+ "Org-private template publication revoke is SaaS metadata only; it does not execute workflows.",
312
+ "Revoked publications do not remove local template files or synced harness artifacts; local dna commands own local changes.",
313
+ "runtime_authority is always false for template publication revoke requests and receipts.",
314
+ "Org-private visibility is backed by Supabase organization membership, RLS, and audit metadata; it is not static catalog visibility.",
315
+ "Dry-run performs no network request and mutates no backend, auth, storage, org-private SaaS, or local runtime state.",
316
+ ];
317
+ }
318
+ function missingTemplateCloudEndpointReason(action) {
319
+ return `rejected: missing ${action} endpoint; set INTENTDNA_CLOUD_URL, SUPABASE_URL, or pass --endpoint`;
320
+ }
321
+ function templatePublicationLifecycle(params) {
322
+ const org = params.org?.trim() || "<org>";
323
+ const project = params.project?.trim() || "<project>";
324
+ const template = params.template?.trim() || "<template>";
325
+ const publicationId = params.publicationId?.trim() || "<publication-id>";
326
+ const endpointPresent = Boolean(params.endpoint);
327
+ const networkPerformed = params.networkPerformed ?? false;
328
+ const cloudRefs = asRecord(params.receipt?.cloud_refs);
329
+ const auditEvidencePresent = typeof cloudRefs?.audit_event_id === "string" && cloudRefs.audit_event_id.trim().length > 0;
330
+ const auditExpected = params.action === "publish" || params.action === "revoke";
331
+ const readyForLiveRequest = params.status === "dry_run" && endpointPresent && params.tokenPresent && !networkPerformed;
332
+ const actionReceiptComplete = (params.action === "publish" && params.status === "published" && auditEvidencePresent)
333
+ || (params.action === "list" && params.status === "ready")
334
+ || (params.action === "revoke" && params.status === "revoked" && auditEvidencePresent);
335
+ const lifecycleComplete = false;
336
+ const commandFor = (action, dryRun) => {
337
+ const dry = dryRun ? " --dry-run" : "";
338
+ if (action === "publish")
339
+ return `dna templates publish --org ${org} --project ${project} --template ${template} --receipt .dna/reports/org-review-receipt.json${dry} --json`;
340
+ if (action === "list")
341
+ return `dna templates publications --org ${org} --project ${project} --template ${template}${dry} --json`;
342
+ return `dna templates revoke --org ${org} --project ${project} --publication-id ${publicationId}${dry} --json`;
343
+ };
344
+ let nextAction = "Resolve the rejection reason, then rerun the dry-run before any live publication request.";
345
+ let nextCommands = [commandFor(params.action, true)];
346
+ if (!params.tokenPresent) {
347
+ nextAction = "Issue or store an IntentDNA CLI API token before org-private publication lifecycle requests.";
348
+ nextCommands = ["dna auth onboard --project-id <id> --json"];
349
+ }
350
+ else if (!endpointPresent) {
351
+ nextAction = "Set the Supabase project URL or pass an explicit Edge Function endpoint before live publication lifecycle requests.";
352
+ nextCommands = ["INTENTDNA_CLOUD_URL=https://<project-ref>.supabase.co " + commandFor(params.action, true)];
353
+ }
354
+ else if (readyForLiveRequest) {
355
+ nextAction = "Dry run is ready. Run the live request only after Supabase auth/RLS/audit functions are deployed and the source receipt is reviewed.";
356
+ nextCommands = [commandFor(params.action, false)];
357
+ }
358
+ else if (params.action === "publish" && params.status === "published" && !auditEvidencePresent) {
359
+ nextAction = "Publication was accepted, but audit_event_id is missing. Keep the receipt as incomplete lifecycle evidence and rerun function health/list checks before claiming org-private publication completion.";
360
+ nextCommands = [commandFor("list", false), "dna cloud function-health --json"];
361
+ }
362
+ else if (params.action === "publish" && params.status === "published") {
363
+ nextAction = "Publication receipt has audit evidence for this SaaS action. List org-private publications and keep RLS/member-role smoke evidence before claiming full lifecycle completion.";
364
+ nextCommands = [commandFor("list", false), commandFor("revoke", true)];
365
+ }
366
+ else if (params.action === "list" && params.status === "ready") {
367
+ nextAction = "Publication listing is available as read-only SaaS projection evidence. Full lifecycle completion still requires RLS denial proof, audit-backed publish/revoke receipts, and local dna gates.";
368
+ nextCommands = [commandFor("revoke", true), "dna org rollout"];
369
+ }
370
+ else if (params.action === "revoke" && params.status === "revoked" && !auditEvidencePresent) {
371
+ nextAction = "Publication revoke was accepted, but audit_event_id is missing. Keep the receipt as incomplete lifecycle evidence and rerun function health/list checks before claiming revoke completion.";
372
+ nextCommands = [commandFor("list", false), "dna cloud function-health --json"];
373
+ }
374
+ else if (params.action === "revoke" && params.status === "revoked") {
375
+ nextAction = "Publication revoke receipt has audit evidence for this SaaS action. List publications again and keep role/RLS smoke evidence before claiming full lifecycle completion.";
376
+ nextCommands = [commandFor("list", false)];
377
+ }
378
+ else if (networkPerformed) {
379
+ nextAction = "Inspect the returned SaaS receipt and audit metadata, fix the rejection, then rerun the matching dry-run.";
380
+ }
381
+ return {
382
+ visibility: "org_private",
383
+ authority: "supabase_auth_rls_audit_required",
384
+ local_runtime_authority: false,
385
+ action: params.action,
386
+ endpoint_present: endpointPresent,
387
+ auth_token_present: params.tokenPresent,
388
+ network_performed: networkPerformed,
389
+ ready_for_live_request: readyForLiveRequest,
390
+ action_receipt_complete: actionReceiptComplete,
391
+ lifecycle_complete: lifecycleComplete,
392
+ audit_expected: auditExpected,
393
+ audit_evidence_required: auditExpected,
394
+ audit_evidence_present: auditExpected ? auditEvidencePresent : false,
395
+ next_action: nextAction,
396
+ next_commands: nextCommands,
397
+ local_followup_gates: [
398
+ "dna org review",
399
+ "dna org rollout",
400
+ "dna sync --dry-run",
401
+ "dna sync",
402
+ "dna report package --json",
403
+ ],
404
+ receipt_authority_boundary: "Publication lifecycle receipts prove SaaS org-private metadata/audit state only; they do not prove local install, workflow approval, harness execution, or runtime policy authority.",
405
+ verification_gates: [
406
+ "dna org receipt --json produces a ready local source receipt for the asset.",
407
+ "dna templates publish dry-run succeeds before live publish.",
408
+ "Live publish/list/revoke receipts keep runtime_authority=false and visibility=org_private.",
409
+ "list-template-publications returns read-only org-private projection metadata.",
410
+ "Supabase RLS and Edge Function role checks reject unauthorized users before claiming org-private lifecycle completion.",
411
+ "Full lifecycle completion needs deployed Supabase evidence across publish, list, revoke, RLS denial, audit refs, and local dna follow-up gates.",
412
+ ],
413
+ boundaries: [
414
+ "This lifecycle block is explanatory readiness metadata; it does not publish, list, revoke, install, approve, or execute workflows.",
415
+ "Org-private visibility is SaaS-backed metadata only after Supabase auth/RLS/audit succeeds; static marketplace catalog must not claim org_private.",
416
+ "Local runtime truth remains dna/dna-hook plus local evidence; publication lifecycle receipts never grant allow/warn/block/validate authority.",
417
+ ],
418
+ };
419
+ }
420
+ function buildTemplateCloudEndpoint(opts, functionName) {
421
+ if (opts.endpoint?.trim())
422
+ return opts.endpoint.trim();
423
+ const env = opts.env ?? process.env;
424
+ const cloudUrl = env.INTENTDNA_CLOUD_URL?.trim()
425
+ || env.SUPABASE_URL?.trim()
426
+ || linkedSupabaseProjectUrl(env);
427
+ if (!cloudUrl)
428
+ return undefined;
429
+ return `${cloudUrl.replace(/\/+$/, "")}/functions/v1/${functionName}`;
430
+ }
431
+ function linkedSupabaseProjectUrl(env = process.env, readLinkedFiles = env === process.env) {
432
+ if (env.INTENTDNA_SUPABASE_DISABLE_LINKED_PROJECT === "1")
433
+ return undefined;
434
+ if (env.INTENTDNA_SUPABASE_LINKED_PROJECT_REF?.trim()) {
435
+ return `https://${env.INTENTDNA_SUPABASE_LINKED_PROJECT_REF.trim()}.supabase.co`;
436
+ }
437
+ if (!readLinkedFiles)
438
+ return undefined;
439
+ const directRef = existsSync("supabase/.temp/project-ref")
440
+ ? readFileSync("supabase/.temp/project-ref", "utf-8").trim()
441
+ : "";
442
+ if (directRef)
443
+ return `https://${directRef}.supabase.co`;
444
+ if (!existsSync("supabase/.temp/linked-project.json"))
445
+ return undefined;
446
+ try {
447
+ const linked = JSON.parse(readFileSync("supabase/.temp/linked-project.json", "utf-8"));
448
+ return typeof linked.ref === "string" && linked.ref.trim()
449
+ ? `https://${linked.ref.trim()}.supabase.co`
450
+ : undefined;
451
+ }
452
+ catch {
453
+ return undefined;
454
+ }
455
+ }
456
+ function buildPublishEndpoint(opts) {
457
+ return buildTemplateCloudEndpoint(opts, "publish-template");
458
+ }
459
+ function buildPublicationsEndpoint(opts) {
460
+ return buildTemplateCloudEndpoint(opts, "list-template-publications");
461
+ }
462
+ function buildRevokeEndpoint(opts) {
463
+ return buildTemplateCloudEndpoint(opts, "revoke-template-publication");
464
+ }
465
+ function receiptReference(value, schema) {
466
+ const record = asRecord(value);
467
+ if (!record)
468
+ return schema;
469
+ if (schema === "intentdna.org_review_receipt.v1") {
470
+ const organization = asRecord(record.organization);
471
+ const orgId = typeof organization?.id === "string" ? organization.id : undefined;
472
+ const generatedAt = typeof record.generated_at === "string" ? record.generated_at : undefined;
473
+ return [schema, orgId, generatedAt].filter(Boolean).join(":");
474
+ }
475
+ const envelopeRef = asRecord(record.envelope_ref);
476
+ const keyId = typeof envelopeRef?.key_id === "string" ? envelopeRef.key_id : undefined;
477
+ const sha = typeof envelopeRef?.sha256 === "string" ? envelopeRef.sha256 : undefined;
478
+ return [schema, sha, keyId].filter(Boolean).join(":");
479
+ }
480
+ function receiptOrgProjectMatches(candidate, org, project) {
481
+ const organization = asRecord(candidate.organization);
482
+ const candidateProject = asRecord(candidate.project);
483
+ const orgCandidates = [
484
+ candidate.organization_id,
485
+ organization?.id,
486
+ organization?.name,
487
+ ].filter((value) => typeof value === "string");
488
+ const projectCandidates = [
489
+ candidate.project_id,
490
+ candidate.project,
491
+ candidateProject?.id,
492
+ candidateProject?.name,
493
+ ].filter((value) => typeof value === "string");
494
+ return orgCandidates.includes(org) && projectCandidates.includes(project);
495
+ }
496
+ function receiptCandidatePublishReady(value) {
497
+ const candidate = asRecord(value);
498
+ return candidate?.publish_ready === true;
499
+ }
500
+ function receiptCandidateTemplate(value) {
501
+ const candidate = asRecord(value);
502
+ return typeof candidate?.asset === "string" ? candidate.asset : undefined;
503
+ }
504
+ function receiptCandidateContentHash(value) {
505
+ const candidate = asRecord(value);
506
+ return typeof candidate?.content_hash === "string" ? candidate.content_hash : undefined;
507
+ }
508
+ function receiptCandidateAssetVersion(value) {
509
+ const candidate = asRecord(value);
510
+ return typeof candidate?.asset_version === "string" ? candidate.asset_version : undefined;
511
+ }
512
+ function receiptCandidateReviewEvidence(value) {
513
+ const candidate = asRecord(value);
514
+ const evidence = Array.isArray(candidate?.review_evidence)
515
+ ? candidate.review_evidence
516
+ : Array.isArray(candidate?.reviewEvidence)
517
+ ? candidate.reviewEvidence
518
+ : [];
519
+ return evidence
520
+ .map((entry) => asRecord(entry))
521
+ .filter((entry) => Boolean(entry));
522
+ }
523
+ function receiptCandidateHasManagerReviewHandoff(value) {
524
+ return receiptCandidateReviewEvidence(value).some((evidence) => evidence.authority === "manager_review_handoff_only"
525
+ && (evidence.mode === "local_org_review_receipt"
526
+ || evidence.mode === "local_asset_diff_review_evidence")
527
+ && (typeof evidence.receipt_command === "string" || typeof evidence.receiptCommand === "string"));
528
+ }
529
+ function orgReceiptPublishCandidateReasons(record, params) {
530
+ const publicationPlan = asRecord(record.org_private_publication_plan);
531
+ const publicationPlanCandidates = Array.isArray(publicationPlan?.candidates)
532
+ ? publicationPlan.candidates
533
+ : [];
534
+ const rawCandidates = [
535
+ ...(Array.isArray(record.internal_ready_candidates) ? record.internal_ready_candidates : []),
536
+ ...publicationPlanCandidates,
537
+ ];
538
+ const candidates = rawCandidates
539
+ .map((candidate) => asRecord(candidate))
540
+ .filter((candidate) => Boolean(candidate));
541
+ const matching = candidates.filter((candidate) => receiptCandidateTemplate(candidate) === params.template
542
+ && receiptOrgProjectMatches(candidate, params.org, params.project));
543
+ if (matching.length === 0) {
544
+ return [`rejected: org review receipt does not include a matching internal-ready publication candidate for ${params.org}/${params.project}/${params.template}`];
545
+ }
546
+ if (!matching.some(receiptCandidatePublishReady)) {
547
+ return [`rejected: org review receipt candidate for ${params.org}/${params.project}/${params.template} is not publish_ready`];
548
+ }
549
+ if (params.contentHash && !matching.some((candidate) => receiptCandidateContentHash(candidate) === params.contentHash)) {
550
+ return [`rejected: org review receipt candidate for ${params.org}/${params.project}/${params.template} does not match template content_hash ${params.contentHash}`];
551
+ }
552
+ if (params.version && !matching.some((candidate) => {
553
+ const candidateVersion = receiptCandidateAssetVersion(candidate);
554
+ return !candidateVersion || candidateVersion === params.version;
555
+ })) {
556
+ return [`rejected: org review receipt candidate for ${params.org}/${params.project}/${params.template} does not match template version ${params.version}`];
557
+ }
558
+ if (!matching.some(receiptCandidateHasManagerReviewHandoff)) {
559
+ return [`rejected: org review receipt candidate for ${params.org}/${params.project}/${params.template} must include manager_review_handoff_only review evidence before publication`];
560
+ }
561
+ return [];
562
+ }
563
+ async function readTemplatePublishReceipt(path, options) {
564
+ let raw;
565
+ try {
566
+ raw = await readFile(path, "utf-8");
567
+ }
568
+ catch {
569
+ return { reasons: ["rejected: receipt file is missing"] };
570
+ }
571
+ let parsed;
572
+ try {
573
+ parsed = JSON.parse(raw);
574
+ }
575
+ catch {
576
+ return { reasons: ["rejected: receipt file is not valid JSON"] };
577
+ }
578
+ const record = asRecord(parsed);
579
+ if (!record)
580
+ return { reasons: ["rejected: receipt JSON must be an object"] };
581
+ const schema = record.schema_version;
582
+ if (schema !== "intentdna.org_review_receipt.v1" && schema !== "intentdna.report_ingest_receipt.v1") {
583
+ return { receipt: record, reasons: ["rejected: receipt schema must be intentdna.org_review_receipt.v1 or intentdna.report_ingest_receipt.v1"] };
584
+ }
585
+ const reasons = [];
586
+ if (schema === "intentdna.org_review_receipt.v1") {
587
+ if (record.cloud_authority !== false)
588
+ reasons.push("rejected: org review receipt must declare cloud_authority=false");
589
+ if (record.status !== "ready")
590
+ reasons.push("rejected: org review receipt must be ready before template publication");
591
+ if (options?.org && options.project && options.template) {
592
+ reasons.push(...orgReceiptPublishCandidateReasons(record, {
593
+ org: options.org,
594
+ project: options.project,
595
+ template: options.template,
596
+ contentHash: options.contentHash,
597
+ version: options.version,
598
+ }));
599
+ }
600
+ }
601
+ if (schema === "intentdna.report_ingest_receipt.v1") {
602
+ if (record.runtime_authority !== false)
603
+ reasons.push("rejected: report ingest receipt must declare runtime_authority=false");
604
+ if (record.trusted_cloud_ingestion !== true)
605
+ reasons.push("rejected: report ingest receipt must declare trusted_cloud_ingestion=true before template publication");
606
+ if (record.status !== "verified")
607
+ reasons.push("rejected: report ingest receipt must be verified before template publication");
608
+ }
609
+ return {
610
+ schema,
611
+ receipt: record,
612
+ ref: receiptReference(record, schema),
613
+ reasons,
614
+ };
615
+ }
616
+ function buildTemplatePublishResult(params) {
617
+ const fallbackTemplate = {
618
+ name: params.opts.template,
619
+ displayName: undefined,
620
+ version: "0.0.0",
621
+ contentHash: "unknown",
622
+ requiredDnaVersion: "unknown",
623
+ supportedHarnesses: [],
624
+ source: "project",
625
+ path: "",
626
+ };
627
+ const template = params.templateEntry && params.marketplace
628
+ ? {
629
+ name: params.templateEntry.name,
630
+ displayName: params.templateEntry.displayName,
631
+ version: params.templateEntry.version ?? "0.0.0",
632
+ contentHash: params.templateEntry.contentHash,
633
+ requiredDnaVersion: params.marketplace.requiredDnaVersion,
634
+ supportedHarnesses: params.marketplace.supportedHarnesses,
635
+ source: params.templateEntry.source,
636
+ path: params.templateEntry.path,
637
+ }
638
+ : fallbackTemplate;
639
+ const receipt = params.sourceReceipt;
640
+ return {
641
+ schema_version: "intentdna.template_publish_result.v1",
642
+ generated_at: new Date().toISOString(),
643
+ status: params.status,
644
+ endpoint: params.endpoint,
645
+ network_performed: params.networkPerformed ?? false,
646
+ runtime_authority: false,
647
+ request: {
648
+ schema_version: "intentdna.template_publish_request.v1",
649
+ organization: params.opts.org,
650
+ project: params.opts.project,
651
+ template: {
652
+ name: template.name,
653
+ display_name: template.displayName,
654
+ version: template.version,
655
+ content_hash: template.contentHash,
656
+ required_dna_version: template.requiredDnaVersion,
657
+ supported_harnesses: template.supportedHarnesses,
658
+ source: template.source,
659
+ path: template.path,
660
+ },
661
+ source_receipt: {
662
+ schema_version: params.receiptSchema ?? "intentdna.org_review_receipt.v1",
663
+ path: params.opts.receipt,
664
+ ref: params.receiptRef ?? params.opts.receipt,
665
+ status: typeof receipt?.status === "string" ? receipt.status : undefined,
666
+ cloud_authority: typeof receipt?.cloud_authority === "boolean" ? receipt.cloud_authority : undefined,
667
+ trusted_cloud_ingestion: typeof receipt?.trusted_cloud_ingestion === "boolean" ? receipt.trusted_cloud_ingestion : undefined,
668
+ runtime_authority: typeof receipt?.runtime_authority === "boolean" ? receipt.runtime_authority : undefined,
669
+ },
670
+ visibility: "org_private",
671
+ runtime_authority: false,
672
+ },
673
+ auth: {
674
+ token_present: params.tokenPresent,
675
+ source: params.authSource,
676
+ },
677
+ transport: {
678
+ network_performed: params.networkPerformed ?? false,
679
+ hidden_transport: false,
680
+ product_surface: "cli_to_supabase_edge_function",
681
+ mcp_visible: false,
682
+ },
683
+ publication_receipt: params.publicationReceipt,
684
+ publication_lifecycle: templatePublicationLifecycle({
685
+ action: "publish",
686
+ status: params.status,
687
+ endpoint: params.endpoint,
688
+ tokenPresent: params.tokenPresent,
689
+ networkPerformed: params.networkPerformed,
690
+ receipt: params.publicationReceipt,
691
+ org: params.opts.org,
692
+ project: params.opts.project,
693
+ template: params.opts.template,
694
+ }),
695
+ reasons: params.reasons,
696
+ boundaries: templatePublishBoundaries(),
697
+ };
698
+ }
699
+ function writeTemplatePublishSummary(result) {
700
+ process.stderr.write([
701
+ `Template publish: ${result.status}`,
702
+ `Template: ${result.request.template.name}`,
703
+ `Organization: ${result.request.organization}`,
704
+ `Project: ${result.request.project}`,
705
+ `Receipt: ${result.request.source_receipt.schema_version}`,
706
+ `Network performed: ${String(result.transport.network_performed)}`,
707
+ ...result.reasons.map((reason) => `Reason: ${reason}`),
708
+ ...result.boundaries.map((boundary) => `Boundary: ${boundary}`),
709
+ ].join("\n") + "\n");
710
+ }
711
+ function buildTemplatePublicationsResult(params) {
712
+ const publicationsValue = params.listingReceipt?.publications;
713
+ return {
714
+ schema_version: "intentdna.template_publications_result.v1",
715
+ generated_at: new Date().toISOString(),
716
+ status: params.status,
717
+ endpoint: params.endpoint,
718
+ network_performed: params.networkPerformed ?? false,
719
+ runtime_authority: false,
720
+ request: {
721
+ schema_version: "intentdna.template_publications_request.v1",
722
+ organization: params.opts.org?.trim() || undefined,
723
+ project: params.opts.project?.trim() || undefined,
724
+ template: params.opts.template?.trim() || undefined,
725
+ visibility: "org_private",
726
+ runtime_authority: false,
727
+ },
728
+ auth: {
729
+ token_present: params.tokenPresent,
730
+ source: params.authSource,
731
+ },
732
+ transport: {
733
+ network_performed: params.networkPerformed ?? false,
734
+ hidden_transport: false,
735
+ product_surface: "cli_to_supabase_edge_function",
736
+ mcp_visible: false,
737
+ },
738
+ publications: Array.isArray(publicationsValue)
739
+ ? publicationsValue.filter((item) => Boolean(asRecord(item)))
740
+ : [],
741
+ listing_receipt: params.listingReceipt,
742
+ publication_lifecycle: templatePublicationLifecycle({
743
+ action: "list",
744
+ status: params.status,
745
+ endpoint: params.endpoint,
746
+ tokenPresent: params.tokenPresent,
747
+ networkPerformed: params.networkPerformed,
748
+ receipt: params.listingReceipt,
749
+ org: params.opts.org,
750
+ project: params.opts.project,
751
+ template: params.opts.template,
752
+ }),
753
+ reasons: params.reasons,
754
+ boundaries: templatePublicationsBoundaries(),
755
+ };
756
+ }
757
+ function writeTemplatePublicationsSummary(result) {
758
+ process.stderr.write([
759
+ `Template publications: ${result.status}`,
760
+ `Organization: ${result.request.organization ?? "(token scope)"}`,
761
+ `Project: ${result.request.project ?? "(token scope)"}`,
762
+ `Template: ${result.request.template ?? "(all)"}`,
763
+ `Publications: ${result.publications.length}`,
764
+ `Network performed: ${String(result.transport.network_performed)}`,
765
+ ...result.reasons.map((reason) => `Reason: ${reason}`),
766
+ ...result.boundaries.map((boundary) => `Boundary: ${boundary}`),
767
+ ].join("\n") + "\n");
768
+ }
769
+ function buildTemplateRevokeResult(params) {
770
+ return {
771
+ schema_version: "intentdna.template_publication_revoke_result.v1",
772
+ generated_at: new Date().toISOString(),
773
+ status: params.status,
774
+ endpoint: params.endpoint,
775
+ network_performed: params.networkPerformed ?? false,
776
+ runtime_authority: false,
777
+ request: {
778
+ schema_version: "intentdna.template_publication_revoke_request.v1",
779
+ organization: params.opts.org?.trim() ?? "",
780
+ project: params.opts.project?.trim() ?? "",
781
+ publication_id: params.opts.publicationId?.trim() ?? "",
782
+ visibility: "org_private",
783
+ runtime_authority: false,
784
+ reason: params.opts.reason?.trim() || undefined,
785
+ },
786
+ auth: {
787
+ token_present: params.tokenPresent,
788
+ source: params.authSource,
789
+ },
790
+ transport: {
791
+ network_performed: params.networkPerformed ?? false,
792
+ hidden_transport: false,
793
+ product_surface: "cli_to_supabase_edge_function",
794
+ mcp_visible: false,
795
+ },
796
+ revoke_receipt: params.revokeReceipt,
797
+ publication_lifecycle: templatePublicationLifecycle({
798
+ action: "revoke",
799
+ status: params.status,
800
+ endpoint: params.endpoint,
801
+ tokenPresent: params.tokenPresent,
802
+ networkPerformed: params.networkPerformed,
803
+ receipt: params.revokeReceipt,
804
+ org: params.opts.org,
805
+ project: params.opts.project,
806
+ publicationId: params.opts.publicationId,
807
+ }),
808
+ reasons: params.reasons,
809
+ boundaries: templateRevokeBoundaries(),
810
+ };
811
+ }
812
+ function validateTemplateRemoteReceipt(params) {
813
+ const { functionName, receipt } = params;
814
+ if (!receipt)
815
+ return [`rejected: ${functionName} response body is not valid JSON receipt`];
816
+ const reasons = [];
817
+ if (receipt.schema_version !== params.expectedSchema) {
818
+ reasons.push(`rejected: ${functionName} receipt schema must be ${params.expectedSchema}`);
819
+ }
820
+ if (typeof receipt.status !== "string" || !params.allowedStatuses.includes(receipt.status)) {
821
+ reasons.push(`rejected: ${functionName} receipt status must be ${params.allowedStatuses.join(" or ")}`);
822
+ }
823
+ if (receipt.runtime_authority !== false) {
824
+ reasons.push(`rejected: ${functionName} receipt must declare runtime_authority=false`);
825
+ }
826
+ if (params.requireOrgPrivateVisibility && receipt.visibility !== "org_private") {
827
+ reasons.push(`rejected: ${functionName} receipt visibility must be org_private`);
828
+ }
829
+ if (params.requireUntrustedIngestion && receipt.trusted_cloud_ingestion !== false) {
830
+ reasons.push(`rejected: ${functionName} receipt must declare trusted_cloud_ingestion=false`);
831
+ }
832
+ if (params.requireLifecycleBoundary) {
833
+ if (typeof receipt.receipt_authority_boundary !== "string" || receipt.receipt_authority_boundary.trim().length === 0) {
834
+ reasons.push(`rejected: ${functionName} receipt must include receipt_authority_boundary`);
835
+ }
836
+ const gates = receipt.post_publication_local_gates;
837
+ const requiredGates = ["dna org review", "dna org rollout", "dna sync --dry-run", "dna sync", "dna report package --json"];
838
+ if (!Array.isArray(gates) || !requiredGates.every((gate) => gates.includes(gate))) {
839
+ reasons.push(`rejected: ${functionName} receipt must include post_publication_local_gates for local review, rollout, sync, and evidence`);
840
+ }
841
+ }
842
+ return reasons;
843
+ }
844
+ function reasonsFromRemoteReceipt(receipt, fallback) {
845
+ const reasons = receipt?.reasons;
846
+ if (!Array.isArray(reasons))
847
+ return fallback;
848
+ const normalized = reasons.map(String).filter(Boolean);
849
+ return normalized.length > 0 ? normalized : fallback;
850
+ }
851
+ function writeTemplateRevokeSummary(result) {
852
+ process.stderr.write([
853
+ `Template publication revoke: ${result.status}`,
854
+ `Organization: ${result.request.organization}`,
855
+ `Project: ${result.request.project}`,
856
+ `Publication: ${result.request.publication_id}`,
857
+ `Network performed: ${String(result.transport.network_performed)}`,
858
+ ...result.reasons.map((reason) => `Reason: ${reason}`),
859
+ ...result.boundaries.map((boundary) => `Boundary: ${boundary}`),
860
+ ].join("\n") + "\n");
861
+ }
862
+ export async function runTemplatesPublish(opts) {
863
+ const projectDir = resolve(opts.projectDir ?? process.cwd());
864
+ const missing = [
865
+ !opts.org?.trim() ? "--org" : undefined,
866
+ !opts.project?.trim() ? "--project" : undefined,
867
+ !opts.template?.trim() ? "--template" : undefined,
868
+ !opts.receipt?.trim() ? "--receipt" : undefined,
869
+ ].filter((item) => Boolean(item));
870
+ const auth = await resolveAuthToken({ env: opts.env, configDir: opts.configDir });
871
+ const endpoint = buildPublishEndpoint(opts);
872
+ const normalizedOpts = {
873
+ ...opts,
874
+ org: opts.org?.trim() ?? "",
875
+ project: opts.project?.trim() ?? "",
876
+ template: opts.template?.trim() ?? "",
877
+ receipt: opts.receipt?.trim() ?? "",
878
+ };
879
+ if (missing.length > 0) {
880
+ const result = buildTemplatePublishResult({
881
+ status: "rejected",
882
+ opts: normalizedOpts,
883
+ endpoint,
884
+ tokenPresent: Boolean(auth.token),
885
+ authSource: auth.source,
886
+ reasons: [`rejected: missing required option(s): ${missing.join(", ")}`],
887
+ });
888
+ if (opts.json)
889
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
890
+ else
891
+ writeTemplatePublishSummary(result);
892
+ return 2;
893
+ }
894
+ if (!auth.token) {
895
+ const result = buildTemplatePublishResult({
896
+ status: "rejected",
897
+ opts: normalizedOpts,
898
+ endpoint,
899
+ tokenPresent: false,
900
+ authSource: auth.source,
901
+ reasons: [missingCliApiTokenReason()],
902
+ });
903
+ if (opts.json)
904
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
905
+ else
906
+ writeTemplatePublishSummary(result);
907
+ return 2;
908
+ }
909
+ const templateEntry = await findTemplateCatalogEntry(normalizedOpts.template, { projectDir });
910
+ const marketplace = templateEntry ? await getTemplateMarketplaceMetadata(templateEntry) : undefined;
911
+ const asset = await findWorkflowAsset(normalizedOpts.template, { projectDir });
912
+ const receiptPath = resolve(projectDir, normalizedOpts.receipt);
913
+ const sourceReceipt = await readTemplatePublishReceipt(receiptPath, {
914
+ org: normalizedOpts.org,
915
+ project: normalizedOpts.project,
916
+ template: normalizedOpts.template,
917
+ contentHash: templateEntry?.contentHash,
918
+ version: templateEntry?.version,
919
+ });
920
+ const reasons = [
921
+ !templateEntry ? `rejected: unknown template ${normalizedOpts.template}` : undefined,
922
+ !asset ? `rejected: template ${normalizedOpts.template} is not a workflow asset candidate` : undefined,
923
+ ...sourceReceipt.reasons,
924
+ ].filter((item) => Boolean(item));
925
+ if (reasons.length > 0) {
926
+ const result = buildTemplatePublishResult({
927
+ status: "rejected",
928
+ opts: normalizedOpts,
929
+ endpoint,
930
+ tokenPresent: true,
931
+ authSource: auth.source,
932
+ templateEntry,
933
+ marketplace,
934
+ receiptSchema: sourceReceipt.schema,
935
+ receiptRef: sourceReceipt.ref,
936
+ sourceReceipt: sourceReceipt.receipt,
937
+ reasons,
938
+ });
939
+ if (opts.json)
940
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
941
+ else
942
+ writeTemplatePublishSummary(result);
943
+ return 1;
944
+ }
945
+ if (opts.dryRun) {
946
+ const result = buildTemplatePublishResult({
947
+ status: "dry_run",
948
+ opts: normalizedOpts,
949
+ endpoint,
950
+ tokenPresent: true,
951
+ authSource: auth.source,
952
+ templateEntry,
953
+ marketplace,
954
+ receiptSchema: sourceReceipt.schema,
955
+ receiptRef: sourceReceipt.ref,
956
+ sourceReceipt: sourceReceipt.receipt,
957
+ reasons: ["dry_run: template publish request is ready but no network request was performed"],
958
+ });
959
+ if (opts.json)
960
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
961
+ else
962
+ writeTemplatePublishSummary(result);
963
+ return 0;
964
+ }
965
+ if (!endpoint) {
966
+ const result = buildTemplatePublishResult({
967
+ status: "rejected",
968
+ opts: normalizedOpts,
969
+ tokenPresent: true,
970
+ authSource: auth.source,
971
+ templateEntry,
972
+ marketplace,
973
+ receiptSchema: sourceReceipt.schema,
974
+ receiptRef: sourceReceipt.ref,
975
+ sourceReceipt: sourceReceipt.receipt,
976
+ reasons: [missingTemplateCloudEndpointReason("publish")],
977
+ });
978
+ if (opts.json)
979
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
980
+ else
981
+ writeTemplatePublishSummary(result);
982
+ return 2;
983
+ }
984
+ const ready = buildTemplatePublishResult({
985
+ status: "pending",
986
+ opts: normalizedOpts,
987
+ endpoint,
988
+ tokenPresent: true,
989
+ authSource: auth.source,
990
+ templateEntry,
991
+ marketplace,
992
+ receiptSchema: sourceReceipt.schema,
993
+ receiptRef: sourceReceipt.ref,
994
+ sourceReceipt: sourceReceipt.receipt,
995
+ reasons: ["pending: template publish request prepared for Supabase Edge Function"],
996
+ });
997
+ const fetchImpl = opts.fetch ?? globalThis.fetch;
998
+ const response = await fetchImpl(endpoint, {
999
+ method: "POST",
1000
+ headers: {
1001
+ "content-type": "application/json",
1002
+ authorization: `Bearer ${auth.token}`,
1003
+ "x-intentdna-cli": "dna templates publish",
1004
+ },
1005
+ body: JSON.stringify({
1006
+ ...ready.request,
1007
+ source_receipt_json: sourceReceipt.receipt,
1008
+ }),
1009
+ });
1010
+ const text = await response.text();
1011
+ let parsed;
1012
+ try {
1013
+ const value = JSON.parse(text);
1014
+ parsed = sanitizeRecord(value);
1015
+ }
1016
+ catch {
1017
+ parsed = undefined;
1018
+ }
1019
+ const receiptReasons = validateTemplateRemoteReceipt({
1020
+ functionName: "publish-template",
1021
+ receipt: parsed,
1022
+ allowedStatuses: ["published", "pending", "rejected"],
1023
+ expectedSchema: "intentdna.template_publication_receipt.v1",
1024
+ requireOrgPrivateVisibility: true,
1025
+ requireUntrustedIngestion: true,
1026
+ requireLifecycleBoundary: true,
1027
+ });
1028
+ const publishedStatus = receiptReasons.length === 0 && parsed?.status === "published"
1029
+ ? "published"
1030
+ : receiptReasons.length === 0 && parsed?.status === "pending"
1031
+ ? "pending"
1032
+ : "rejected";
1033
+ const result = buildTemplatePublishResult({
1034
+ status: response.ok && receiptReasons.length === 0 ? publishedStatus : "rejected",
1035
+ opts: normalizedOpts,
1036
+ endpoint,
1037
+ tokenPresent: true,
1038
+ authSource: auth.source,
1039
+ templateEntry,
1040
+ marketplace,
1041
+ receiptSchema: sourceReceipt.schema,
1042
+ receiptRef: sourceReceipt.ref,
1043
+ sourceReceipt: sourceReceipt.receipt,
1044
+ networkPerformed: true,
1045
+ publicationReceipt: parsed,
1046
+ reasons: response.ok && parsed && receiptReasons.length === 0
1047
+ ? publishedStatus === "rejected"
1048
+ ? reasonsFromRemoteReceipt(parsed, ["rejected: publish-template returned a rejected receipt"])
1049
+ : [`${publishedStatus}: publish-template returned ${parsed.schema_version ?? "unknown schema"}`]
1050
+ : response.ok
1051
+ ? receiptReasons
1052
+ : [`rejected: publish-template endpoint returned HTTP ${response.status}`],
1053
+ });
1054
+ if (opts.json)
1055
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1056
+ else
1057
+ writeTemplatePublishSummary(result);
1058
+ return result.status === "published" || result.status === "pending" ? 0 : 1;
1059
+ }
1060
+ export async function runTemplatesRevoke(opts = {}) {
1061
+ const auth = await resolveAuthToken({ env: opts.env, configDir: opts.configDir });
1062
+ const endpoint = buildRevokeEndpoint(opts);
1063
+ const missing = [
1064
+ !opts.org?.trim() ? "--org" : undefined,
1065
+ !opts.project?.trim() ? "--project" : undefined,
1066
+ !opts.publicationId?.trim() ? "--publication-id" : undefined,
1067
+ ].filter((item) => Boolean(item));
1068
+ if (missing.length > 0) {
1069
+ const result = buildTemplateRevokeResult({
1070
+ status: "rejected",
1071
+ opts,
1072
+ endpoint,
1073
+ tokenPresent: Boolean(auth.token),
1074
+ authSource: auth.source,
1075
+ reasons: [`rejected: missing required option(s): ${missing.join(", ")}`],
1076
+ });
1077
+ if (opts.json)
1078
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1079
+ else
1080
+ writeTemplateRevokeSummary(result);
1081
+ return 2;
1082
+ }
1083
+ if (!auth.token) {
1084
+ const result = buildTemplateRevokeResult({
1085
+ status: "rejected",
1086
+ opts,
1087
+ endpoint,
1088
+ tokenPresent: false,
1089
+ authSource: auth.source,
1090
+ reasons: [missingCliApiTokenReason()],
1091
+ });
1092
+ if (opts.json)
1093
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1094
+ else
1095
+ writeTemplateRevokeSummary(result);
1096
+ return 2;
1097
+ }
1098
+ if (opts.dryRun) {
1099
+ const result = buildTemplateRevokeResult({
1100
+ status: "dry_run",
1101
+ opts,
1102
+ endpoint,
1103
+ tokenPresent: true,
1104
+ authSource: auth.source,
1105
+ reasons: ["dry_run: template publication revoke request is ready but no network request was performed"],
1106
+ });
1107
+ if (opts.json)
1108
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1109
+ else
1110
+ writeTemplateRevokeSummary(result);
1111
+ return 0;
1112
+ }
1113
+ if (!endpoint) {
1114
+ const result = buildTemplateRevokeResult({
1115
+ status: "rejected",
1116
+ opts,
1117
+ tokenPresent: true,
1118
+ authSource: auth.source,
1119
+ reasons: [missingTemplateCloudEndpointReason("revoke")],
1120
+ });
1121
+ if (opts.json)
1122
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1123
+ else
1124
+ writeTemplateRevokeSummary(result);
1125
+ return 2;
1126
+ }
1127
+ const request = buildTemplateRevokeResult({
1128
+ status: "pending",
1129
+ opts,
1130
+ endpoint,
1131
+ tokenPresent: true,
1132
+ authSource: auth.source,
1133
+ reasons: ["pending: template publication revoke request prepared for Supabase Edge Function"],
1134
+ }).request;
1135
+ const fetchImpl = opts.fetch ?? globalThis.fetch;
1136
+ const response = await fetchImpl(endpoint, {
1137
+ method: "POST",
1138
+ headers: {
1139
+ "content-type": "application/json",
1140
+ authorization: `Bearer ${auth.token}`,
1141
+ "x-intentdna-cli": "dna templates revoke",
1142
+ },
1143
+ body: JSON.stringify(request),
1144
+ });
1145
+ const text = await response.text();
1146
+ let parsed;
1147
+ try {
1148
+ const value = JSON.parse(text);
1149
+ parsed = sanitizeRecord(value);
1150
+ }
1151
+ catch {
1152
+ parsed = undefined;
1153
+ }
1154
+ const receiptReasons = validateTemplateRemoteReceipt({
1155
+ functionName: "revoke-template-publication",
1156
+ receipt: parsed,
1157
+ allowedStatuses: ["revoked", "pending", "rejected"],
1158
+ expectedSchema: "intentdna.template_publication_revoke_receipt.v1",
1159
+ requireOrgPrivateVisibility: true,
1160
+ requireUntrustedIngestion: true,
1161
+ requireLifecycleBoundary: true,
1162
+ });
1163
+ const remoteStatus = receiptReasons.length === 0 && parsed?.status === "revoked"
1164
+ ? "revoked"
1165
+ : receiptReasons.length === 0 && parsed?.status === "pending"
1166
+ ? "pending"
1167
+ : "rejected";
1168
+ const result = buildTemplateRevokeResult({
1169
+ status: response.ok && receiptReasons.length === 0 ? remoteStatus : "rejected",
1170
+ opts,
1171
+ endpoint,
1172
+ tokenPresent: true,
1173
+ authSource: auth.source,
1174
+ networkPerformed: true,
1175
+ revokeReceipt: parsed,
1176
+ reasons: response.ok && parsed && receiptReasons.length === 0
1177
+ ? remoteStatus === "rejected"
1178
+ ? reasonsFromRemoteReceipt(parsed, ["rejected: revoke-template-publication returned a rejected receipt"])
1179
+ : [`${remoteStatus}: revoke-template-publication returned ${parsed.schema_version ?? "unknown schema"}`]
1180
+ : response.ok
1181
+ ? receiptReasons
1182
+ : [`rejected: revoke-template-publication endpoint returned HTTP ${response.status}`],
1183
+ });
1184
+ if (opts.json)
1185
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1186
+ else
1187
+ writeTemplateRevokeSummary(result);
1188
+ return result.status === "revoked" || result.status === "pending" ? 0 : 1;
1189
+ }
1190
+ export async function runTemplatesPublications(opts = {}) {
1191
+ const auth = await resolveAuthToken({ env: opts.env, configDir: opts.configDir });
1192
+ const endpoint = buildPublicationsEndpoint(opts);
1193
+ if (!auth.token) {
1194
+ const result = buildTemplatePublicationsResult({
1195
+ status: "rejected",
1196
+ opts,
1197
+ endpoint,
1198
+ tokenPresent: false,
1199
+ authSource: auth.source,
1200
+ reasons: [missingCliApiTokenReason()],
1201
+ });
1202
+ if (opts.json)
1203
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1204
+ else
1205
+ writeTemplatePublicationsSummary(result);
1206
+ return 2;
1207
+ }
1208
+ if (opts.dryRun) {
1209
+ const result = buildTemplatePublicationsResult({
1210
+ status: "dry_run",
1211
+ opts,
1212
+ endpoint,
1213
+ tokenPresent: true,
1214
+ authSource: auth.source,
1215
+ reasons: ["dry_run: template publications request is ready but no network request was performed"],
1216
+ });
1217
+ if (opts.json)
1218
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1219
+ else
1220
+ writeTemplatePublicationsSummary(result);
1221
+ return 0;
1222
+ }
1223
+ if (!endpoint) {
1224
+ const result = buildTemplatePublicationsResult({
1225
+ status: "rejected",
1226
+ opts,
1227
+ tokenPresent: true,
1228
+ authSource: auth.source,
1229
+ reasons: [missingTemplateCloudEndpointReason("publications")],
1230
+ });
1231
+ if (opts.json)
1232
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1233
+ else
1234
+ writeTemplatePublicationsSummary(result);
1235
+ return 2;
1236
+ }
1237
+ const request = buildTemplatePublicationsResult({
1238
+ status: "pending",
1239
+ opts,
1240
+ endpoint,
1241
+ tokenPresent: true,
1242
+ authSource: auth.source,
1243
+ reasons: ["pending: template publications request prepared for Supabase Edge Function"],
1244
+ }).request;
1245
+ const fetchImpl = opts.fetch ?? globalThis.fetch;
1246
+ const response = await fetchImpl(endpoint, {
1247
+ method: "POST",
1248
+ headers: {
1249
+ "content-type": "application/json",
1250
+ authorization: `Bearer ${auth.token}`,
1251
+ "x-intentdna-cli": "dna templates publications",
1252
+ },
1253
+ body: JSON.stringify(request),
1254
+ });
1255
+ const text = await response.text();
1256
+ let parsed;
1257
+ try {
1258
+ const value = JSON.parse(text);
1259
+ parsed = sanitizeRecord(value);
1260
+ }
1261
+ catch {
1262
+ parsed = undefined;
1263
+ }
1264
+ const receiptReasons = validateTemplateRemoteReceipt({
1265
+ functionName: "list-template-publications",
1266
+ receipt: parsed,
1267
+ allowedStatuses: ["ready", "pending", "rejected"],
1268
+ expectedSchema: "intentdna.template_publications_list.v1",
1269
+ requireUntrustedIngestion: true,
1270
+ requireLifecycleBoundary: true,
1271
+ });
1272
+ const remoteStatus = receiptReasons.length === 0 && parsed?.status === "ready"
1273
+ ? "ready"
1274
+ : receiptReasons.length === 0 && parsed?.status === "pending"
1275
+ ? "pending"
1276
+ : "rejected";
1277
+ const result = buildTemplatePublicationsResult({
1278
+ status: response.ok && receiptReasons.length === 0 ? remoteStatus : "rejected",
1279
+ opts,
1280
+ endpoint,
1281
+ tokenPresent: true,
1282
+ authSource: auth.source,
1283
+ networkPerformed: true,
1284
+ listingReceipt: parsed,
1285
+ reasons: response.ok && parsed && receiptReasons.length === 0
1286
+ ? remoteStatus === "rejected"
1287
+ ? reasonsFromRemoteReceipt(parsed, ["rejected: list-template-publications returned a rejected receipt"])
1288
+ : [`${remoteStatus}: list-template-publications returned ${parsed.schema_version ?? "unknown schema"}`]
1289
+ : response.ok
1290
+ ? receiptReasons
1291
+ : [`rejected: list-template-publications endpoint returned HTTP ${response.status}`],
1292
+ });
1293
+ if (opts.json)
1294
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1295
+ else
1296
+ writeTemplatePublicationsSummary(result);
1297
+ return result.status === "ready" || result.status === "pending" ? 0 : 1;
1298
+ }
1299
+ function asRecord(value) {
1300
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
1301
+ }
1302
+ function removeSensitiveFields(value) {
1303
+ if (Array.isArray(value))
1304
+ return value.map((item) => removeSensitiveFields(item));
1305
+ if (!value || typeof value !== "object")
1306
+ return value;
1307
+ const output = {};
1308
+ for (const [key, item] of Object.entries(value)) {
1309
+ const normalized = key.toLowerCase();
1310
+ if (normalized === "raw_token"
1311
+ || normalized === "token_hash"
1312
+ || normalized === "hash"
1313
+ || normalized === "supabase_user_jwt"
1314
+ || normalized === "user_jwt"
1315
+ || normalized === "service_role_key"
1316
+ || normalized === "secret_key"
1317
+ || normalized === "token_pepper") {
1318
+ continue;
1319
+ }
1320
+ output[key] = removeSensitiveFields(item);
1321
+ }
1322
+ return output;
1323
+ }
1324
+ function sanitizeRecord(value) {
1325
+ const sanitized = removeSensitiveFields(value);
1326
+ return asRecord(sanitized);
1327
+ }
1328
+ function recordSize(value) {
1329
+ return Object.keys(asRecord(value) ?? {}).length;
1330
+ }
1331
+ function workflowStepCount(value) {
1332
+ const workflows = asRecord(value);
1333
+ if (!workflows)
1334
+ return 0;
1335
+ let count = 0;
1336
+ for (const workflow of Object.values(workflows)) {
1337
+ const steps = asRecord(workflow)?.steps;
1338
+ if (Array.isArray(steps))
1339
+ count += steps.length;
1340
+ }
1341
+ return count;
1342
+ }
1343
+ function unique(values) {
1344
+ return [...new Set(values.filter(Boolean))];
1345
+ }
1346
+ function isExternalReferenceTagged(content) {
1347
+ return /\b(OMC|OMX|oh-my-codex|Superpowers|SKILL\.md|\.codex\/skills)\b/i.test(content);
1348
+ }
1349
+ function deriveTemplateTags(entry, content, data) {
1350
+ const tags = [
1351
+ entry.source,
1352
+ entry.namespace ? `ns:${entry.namespace}` : "",
1353
+ recordSize(data.genes) > 0 ? "genes" : "",
1354
+ recordSize(data.roles) > 0 ? "roles" : "",
1355
+ recordSize(data.workflows) > 0 ? "workflow" : "",
1356
+ recordSize(data.controllers) > 0 ? "controller" : "",
1357
+ Array.isArray(data.evidence) ? "evidence-linked" : "",
1358
+ isExternalReferenceTagged(content) ? "external-reference" : "",
1359
+ ...entry.keywords.slice(0, 6).map((keyword) => `kw:${keyword}`),
1360
+ ];
1361
+ return unique(tags);
1362
+ }
1363
+ function buildTemplateCommands(name) {
1364
+ return {
1365
+ manage: `dna templates manage ${name}`,
1366
+ show: `dna templates show ${name}`,
1367
+ init: `dna init --template ${name}`,
1368
+ assetInspect: `dna assets inspect ${name} --evidence`,
1369
+ assetAdoption: `dna assets adoption ${name}`,
1370
+ orgBind: `dna org bind --asset ${name} --status active --sync-target codex`,
1371
+ orgReview: "dna org review",
1372
+ orgRollout: "dna org rollout",
1373
+ };
1374
+ }
1375
+ function buildTemplateManagementItem(entry, content, data, marketplace) {
1376
+ return {
1377
+ name: entry.name,
1378
+ displayName: entry.displayName,
1379
+ source: entry.source,
1380
+ namespace: entry.namespace,
1381
+ version: entry.version,
1382
+ contentHash: entry.contentHash,
1383
+ marketplace,
1384
+ tags: deriveTemplateTags(entry, content, data),
1385
+ stats: {
1386
+ genes: recordSize(data.genes),
1387
+ roles: recordSize(data.roles),
1388
+ workflows: recordSize(data.workflows),
1389
+ controllers: recordSize(data.controllers),
1390
+ workflowSteps: workflowStepCount(data.workflows),
1391
+ },
1392
+ commands: buildTemplateCommands(entry.name),
1393
+ };
1394
+ }
1395
+ function customTemplateRoutes(templateName) {
1396
+ return [
1397
+ {
1398
+ id: "blank_draft",
1399
+ label: "Author new workflow",
1400
+ command: "dna workflow authoring-packet \"Describe the workflow intent\" --json",
1401
+ writes: false,
1402
+ purpose: "Give an external agent the self-contained C3 contract for interview, YAML authoring, deterministic validation, and explicit human save.",
1403
+ nextCommands: [
1404
+ "dna workflow draft \"Describe the workflow intent\" --save-template <draft-name>",
1405
+ "dna assets inspect <draft-name> --evidence",
1406
+ "dna assets adoption <draft-name>",
1407
+ ],
1408
+ boundary: "Packet generation is read-only. Saved assets remain local candidates until validation and explicit human review; they are not organization-approved, synced, published, or executed.",
1409
+ scaffoldFallback: {
1410
+ command: "dna workflow draft \"Describe the workflow intent\" --save-template <draft-name>",
1411
+ writes: true,
1412
+ positioning: "Example-clone scaffold only; its structure is not derived from the goal.",
1413
+ },
1414
+ },
1415
+ {
1416
+ id: "adapt_existing",
1417
+ label: "Adapt existing",
1418
+ command: `dna workflow edit-plan "Describe the reviewed workflow change" --from-template ${templateName} --save-template ${templateName}-v2`,
1419
+ writes: true,
1420
+ purpose: "Change variables, steps, boundaries, or verification gates from an existing governed asset through a reviewable version.",
1421
+ nextCommands: [
1422
+ `dna assets diff ${templateName} ${templateName}-v2`,
1423
+ `dna assets adoption ${templateName}-v2`,
1424
+ ],
1425
+ boundary: "Adaptation does not replace or delete the source template; rollout still requires diff, adoption, org binding, and local sync gates.",
1426
+ },
1427
+ {
1428
+ id: "import_metadata",
1429
+ label: "Import metadata",
1430
+ command: "dna templates sources --focus gaps --json",
1431
+ writes: false,
1432
+ purpose: "Scan external skill/workflow references as metadata and coverage signals before drafting any IntentDNA asset.",
1433
+ nextCommands: [
1434
+ "dna templates sources <reference>",
1435
+ "dna templates sources <reference> --candidate",
1436
+ ],
1437
+ boundary: "External references are source material only; marketplace metadata must not treat raw runtime docs as policy truth.",
1438
+ },
1439
+ {
1440
+ id: "review_packet",
1441
+ label: "Review packet",
1442
+ command: "dna templates sources <reference>",
1443
+ writes: false,
1444
+ purpose: "Open the reference review packet or candidate preview before saving any project-local draft.",
1445
+ nextCommands: [
1446
+ "dna templates sources <reference> --candidate",
1447
+ "dna templates sources <reference> --candidate --save-template <draft-name>",
1448
+ ],
1449
+ boundary: "Review packets are read-only until an explicit --candidate --save-template command writes a project-local draft.",
1450
+ },
1451
+ ];
1452
+ }
1453
+ function topLevelSectionAnchors(lines) {
1454
+ const topLevelKeys = lines
1455
+ .map((line, index) => {
1456
+ const match = /^([A-Za-z_][A-Za-z0-9_-]*):/.exec(line);
1457
+ return match ? { key: match[1], line: index + 1 } : undefined;
1458
+ })
1459
+ .filter((item) => Boolean(item));
1460
+ const interesting = new Set(["variables", "evidence", "genes", "contexts", "context_files", "roles", "controllers", "workflows"]);
1461
+ return topLevelKeys
1462
+ .filter((item) => interesting.has(item.key))
1463
+ .map((item) => {
1464
+ const next = topLevelKeys.find((candidate) => candidate.line > item.line);
1465
+ return {
1466
+ id: `section:${item.key}`,
1467
+ label: item.key,
1468
+ kind: "section",
1469
+ startLine: item.line,
1470
+ endLine: (next?.line ?? lines.length + 1) - 1,
1471
+ };
1472
+ });
1473
+ }
1474
+ function workflowStepAnchors(lines) {
1475
+ const workflowSection = topLevelSectionAnchors(lines).find((anchor) => anchor.label === "workflows");
1476
+ if (!workflowSection)
1477
+ return [];
1478
+ const starts = [];
1479
+ for (let index = workflowSection.startLine - 1; index < workflowSection.endLine; index++) {
1480
+ const match = /^\s+- id:\s*["']?([^"'\s#]+)["']?/.exec(lines[index]);
1481
+ if (match)
1482
+ starts.push({ id: match[1], line: index + 1 });
1483
+ }
1484
+ return starts.map((start, index) => ({
1485
+ id: `step:${start.id}`,
1486
+ label: start.id,
1487
+ kind: "workflow_step",
1488
+ startLine: start.line,
1489
+ endLine: (starts[index + 1]?.line ?? workflowSection.endLine + 1) - 1,
1490
+ }));
1491
+ }
1492
+ function templateAnchors(content) {
1493
+ const lines = content.split("\n");
1494
+ return [...topLevelSectionAnchors(lines), ...workflowStepAnchors(lines)].sort((a, b) => a.startLine - b.startLine);
1495
+ }
1496
+ function defaultSourceDirs() {
1497
+ const home = homedir();
1498
+ return [
1499
+ join(home, ".codex", "skills"),
1500
+ join(home, ".agents", "skills"),
1501
+ ];
1502
+ }
1503
+ async function discoverSkillMarkdown(sourceDirs) {
1504
+ const discovered = new Map();
1505
+ const sourceRank = {
1506
+ codex_skill: 0,
1507
+ custom_skill: 1,
1508
+ agents_skill: 2,
1509
+ };
1510
+ for (const sourceRoot of sourceDirs) {
1511
+ let entries;
1512
+ try {
1513
+ entries = await readdir(sourceRoot);
1514
+ }
1515
+ catch {
1516
+ continue;
1517
+ }
1518
+ const sourceType = sourceRoot.includes(".codex")
1519
+ ? "codex_skill"
1520
+ : sourceRoot.includes(".agents")
1521
+ ? "agents_skill"
1522
+ : "custom_skill";
1523
+ for (const entry of entries.sort()) {
1524
+ const skillPath = join(sourceRoot, entry, "SKILL.md");
1525
+ if (!existsSync(skillPath))
1526
+ continue;
1527
+ const existing = discovered.get(entry);
1528
+ if (!existing || sourceRank[sourceType] < sourceRank[existing.sourceType]) {
1529
+ discovered.set(entry, { path: skillPath, sourceRoot, sourceType });
1530
+ }
1531
+ }
1532
+ }
1533
+ return [...discovered.values()];
1534
+ }
1535
+ function normalizeWords(value) {
1536
+ return unique(value
1537
+ .toLowerCase()
1538
+ .replace(/[^a-z0-9\u4e00-\u9fa5]+/g, " ")
1539
+ .split(/\s+/)
1540
+ .filter((word) => word.length >= 3));
1541
+ }
1542
+ function firstHeading(content, fallback) {
1543
+ const heading = content.split("\n").find((line) => /^#\s+/.test(line));
1544
+ return heading?.replace(/^#\s+/, "").trim() || fallback;
1545
+ }
1546
+ function sourceSummary(content) {
1547
+ const line = content
1548
+ .split("\n")
1549
+ .map((item) => item.trim())
1550
+ .find((item) => item.length > 0 && !item.startsWith("#") && !item.startsWith("<!--"));
1551
+ return line ? line.slice(0, 180) : "No summary line found in source document.";
1552
+ }
1553
+ function sourceSignals(id, title, content) {
1554
+ const text = `${id}\n${title}\n${content}`.toLowerCase();
1555
+ const checks = [
1556
+ ["workflow", /\bworkflow|工作流|流程\b/],
1557
+ ["verification", /\bverify|verification|test|qa|验证|测试\b/],
1558
+ ["planning", /\bplan|planning|prd|路线|计划\b/],
1559
+ ["review", /\breview|audit|critic|审查|评审\b/],
1560
+ ["parallel", /\bteam|parallel|subagent|worker|swarm|并行|团队\b/],
1561
+ ["browser", /\bbrowser|playwright|screenshot|visual|页面|截图\b/],
1562
+ ["deploy", /\bdeploy|vercel|release|发布|部署\b/],
1563
+ ["refactor", /\brefactor|cleanup|deslop|rewrite|重构|改写\b/],
1564
+ ["knowledge", /\bwiki|doc|knowledge|memory|知识|文档\b/],
1565
+ ["runtime", /\bcodex|claude|omx|omc|harness|runtime\b/],
1566
+ ];
1567
+ return checks.filter(([, pattern]) => pattern.test(text)).map(([signal]) => signal);
1568
+ }
1569
+ function sourcePriority(id, signals) {
1570
+ const weights = {
1571
+ workflow: 4,
1572
+ refactor: 4,
1573
+ verification: 3,
1574
+ planning: 3,
1575
+ review: 3,
1576
+ parallel: 3,
1577
+ deploy: 3,
1578
+ browser: 2,
1579
+ runtime: 2,
1580
+ knowledge: 1,
1581
+ };
1582
+ const signalScore = signals.reduce((sum, signal) => sum + (weights[signal] ?? 0), 0);
1583
+ const workflowSourceBoost = /^(ai-slop-cleaner|autopilot|code-review|deep-interview|frontend-design|frontend-skill|gh-|plan|playwright|ralph|ralplan|security-review|team|ultrawork|vercel-deploy|visual-verdict|web-clone|webapp-testing)$/.test(id)
1584
+ || id.startsWith("lark-workflow-")
1585
+ ? 5
1586
+ : 0;
1587
+ const apiWrapperPenalty = id.startsWith("lark-") && !id.startsWith("lark-workflow-") ? -4 : 0;
1588
+ const score = signalScore + workflowSourceBoost + apiWrapperPenalty;
1589
+ const reason = [
1590
+ signals.length > 0 ? `signals=${signals.join(",")}` : "signals=none",
1591
+ workflowSourceBoost > 0 ? "workflow-source-boost" : "",
1592
+ apiWrapperPenalty < 0 ? "api-wrapper-penalty" : "",
1593
+ ].filter(Boolean).join("; ");
1594
+ return { score, reason };
1595
+ }
1596
+ function scoreTemplateCoverage(sourceId, title, summary, signals, templates) {
1597
+ const sourceWords = new Set(normalizeWords(`${sourceId} ${title} ${summary} ${signals.join(" ")}`));
1598
+ const matches = templates
1599
+ .map((template) => {
1600
+ const templateWords = new Set(normalizeWords([
1601
+ template.name,
1602
+ template.displayName ?? "",
1603
+ template.namespace ?? "",
1604
+ ...template.keywords,
1605
+ ].join(" ")));
1606
+ const overlap = [...sourceWords].filter((word) => templateWords.has(word));
1607
+ const directNameHit = sourceId.includes(template.name) || template.name.includes(sourceId);
1608
+ const score = overlap.length + (directNameHit ? 4 : 0);
1609
+ return {
1610
+ name: template.name,
1611
+ score,
1612
+ reason: directNameHit ? "name overlap" : `keyword overlap: ${overlap.slice(0, 5).join(", ") || "none"}`,
1613
+ };
1614
+ })
1615
+ .filter((match) => match.score > 0)
1616
+ .sort((a, b) => b.score - a.score)
1617
+ .slice(0, 3);
1618
+ const best = matches[0]?.score ?? 0;
1619
+ const status = best >= 4 ? "covered" : best >= 2 ? "partial" : "candidate";
1620
+ const explanation = status === "covered"
1621
+ ? "A current template appears to cover this source; review only if the source changed materially."
1622
+ : status === "partial"
1623
+ ? "A related template exists, but this source may contain workflow details worth extracting."
1624
+ : "No strong template match was found; review this as a possible new workflow asset.";
1625
+ return { status, matchedTemplates: matches, explanation };
1626
+ }
1627
+ function sourceCandidateCommands(candidate) {
1628
+ const primaryTemplate = candidate.coverage.matchedTemplates[0]?.name;
1629
+ const commands = [
1630
+ {
1631
+ id: "review_source",
1632
+ command: `dna templates sources ${candidate.id}`,
1633
+ writes: false,
1634
+ purpose: "Open the reference review packet before deciding whether to refresh or draft a template.",
1635
+ },
1636
+ {
1637
+ id: "read_source",
1638
+ command: `sed -n '1,220p' ${candidate.path}`,
1639
+ writes: false,
1640
+ purpose: "Read the reference workflow/skill before extracting any template change.",
1641
+ },
1642
+ ];
1643
+ if (primaryTemplate) {
1644
+ commands.push({
1645
+ id: "manage_existing",
1646
+ command: `dna templates manage ${primaryTemplate}`,
1647
+ writes: false,
1648
+ purpose: "Compare the source against the closest current governed template.",
1649
+ });
1650
+ commands.push({
1651
+ id: "review_asset",
1652
+ command: `dna assets adoption ${primaryTemplate}`,
1653
+ writes: false,
1654
+ purpose: "Review whether the existing asset should be adopted as-is before drafting changes.",
1655
+ });
1656
+ }
1657
+ commands.push({
1658
+ id: "draft_candidate",
1659
+ command: `dna workflow authoring-packet "Extract ${candidate.id} as governed workflow asset" --json`,
1660
+ writes: false,
1661
+ purpose: "Only after source review, emit the C3 packet for external-agent candidate authoring and deterministic validation.",
1662
+ });
1663
+ commands.push({
1664
+ id: "scaffold_candidate",
1665
+ command: `dna workflow draft "Extract ${candidate.id} as governed workflow asset" --save-template ${candidate.id}-candidate`,
1666
+ writes: true,
1667
+ purpose: "Optional example-clone scaffold fallback; structure is not derived from the source intent.",
1668
+ deprecated: true,
1669
+ replacement: "draft_candidate",
1670
+ });
1671
+ return commands;
1672
+ }
1673
+ function buildSourceCandidate(source, content, templates) {
1674
+ const id = basename(source.path.replace(/\/SKILL\.md$/, ""));
1675
+ const title = firstHeading(content, id);
1676
+ const summary = sourceSummary(content);
1677
+ const signals = sourceSignals(id, title, content);
1678
+ const priority = sourcePriority(id, signals);
1679
+ const coverage = scoreTemplateCoverage(id, title, summary, signals, templates);
1680
+ const base = {
1681
+ id,
1682
+ title,
1683
+ path: source.path,
1684
+ sourceRoot: source.sourceRoot,
1685
+ sourceType: source.sourceType,
1686
+ summary,
1687
+ signals,
1688
+ priority,
1689
+ coverage,
1690
+ };
1691
+ return {
1692
+ ...base,
1693
+ recommendedCommands: sourceCandidateCommands(base),
1694
+ };
1695
+ }
1696
+ async function loadTemplateSourceCandidates(opts) {
1697
+ const sourceDirs = opts.sourceDirs?.length ? opts.sourceDirs : defaultSourceDirs();
1698
+ const templates = await listTemplateCatalog({ projectDir: opts.projectDir });
1699
+ const sources = await discoverSkillMarkdown(sourceDirs);
1700
+ const allCandidates = (await Promise.all(sources.map(async (source) => {
1701
+ const content = await readFile(source.path, "utf-8");
1702
+ return buildSourceCandidate(source, content, templates);
1703
+ }))).sort((a, b) => {
1704
+ const byPriority = b.priority.score - a.priority.score;
1705
+ if (byPriority !== 0)
1706
+ return byPriority;
1707
+ const rank = { candidate: 0, partial: 1, covered: 2 };
1708
+ const byStatus = rank[a.coverage.status] - rank[b.coverage.status];
1709
+ if (byStatus !== 0)
1710
+ return byStatus;
1711
+ return a.id.localeCompare(b.id);
1712
+ });
1713
+ return { sourceDirs, allCandidates };
1714
+ }
1715
+ function sourceReviewDecision(candidate) {
1716
+ const primaryTemplate = candidate.coverage.matchedTemplates[0]?.name;
1717
+ if (candidate.coverage.status === "covered" && primaryTemplate) {
1718
+ return {
1719
+ status: "refresh_existing_template",
1720
+ reason: "A governed template already appears to cover this source. Review source changes against that template before drafting any mutation.",
1721
+ primaryCommand: `dna templates manage ${primaryTemplate}`,
1722
+ requiresHumanReview: true,
1723
+ };
1724
+ }
1725
+ if (candidate.coverage.status === "partial" && primaryTemplate) {
1726
+ return {
1727
+ status: "review_partial_overlap",
1728
+ reason: "A related template exists, but the source may contain workflow details that should become a reviewed template update or variant.",
1729
+ primaryCommand: `dna templates manage ${primaryTemplate}`,
1730
+ requiresHumanReview: true,
1731
+ };
1732
+ }
1733
+ return {
1734
+ status: "draft_new_template_candidate",
1735
+ reason: "No strong template coverage was found. Treat this source as a possible new workflow asset and use the external-agent authoring packet after review.",
1736
+ primaryCommand: `dna workflow authoring-packet "Extract ${candidate.id} as governed workflow asset" --json`,
1737
+ requiresHumanReview: true,
1738
+ scaffoldFallback: `dna workflow draft "Extract ${candidate.id} as governed workflow asset" --save-template ${candidate.id}-candidate`,
1739
+ };
1740
+ }
1741
+ async function buildTemplateSourceReviewPacket(candidate) {
1742
+ const content = await readFile(candidate.path, "utf-8");
1743
+ const decision = sourceReviewDecision(candidate);
1744
+ return {
1745
+ mode: "template_source_review_packet",
1746
+ source: "external_workflow_references",
1747
+ candidate,
1748
+ decision,
1749
+ sourceDocument: {
1750
+ path: candidate.path,
1751
+ lineCount: content.split("\n").length,
1752
+ content,
1753
+ },
1754
+ reviewChecklist: [
1755
+ "Read the reference workflow/skill and identify actual reusable workflow behavior, not runtime-specific instructions.",
1756
+ "Compare against the closest governed template when one exists.",
1757
+ "Preserve IntentDNA boundaries: templates encode governed workflow assets, not raw external runtime docs.",
1758
+ "Author new candidate templates through the C3 packet and dna validate loop; use workflow draft only as an explicitly labeled scaffold fallback.",
1759
+ "Do not sync, deploy, or mutate evolution markers from a reference packet alone.",
1760
+ ],
1761
+ route: unique([
1762
+ candidate.recommendedCommands.find((command) => command.id === "read_source")?.command ?? `sed -n '1,220p' ${candidate.path}`,
1763
+ decision.primaryCommand,
1764
+ candidate.recommendedCommands.find((command) => command.id === "scaffold_candidate")?.command ?? `dna workflow draft "Extract ${candidate.id} as governed workflow asset" --save-template ${candidate.id}-candidate`,
1765
+ `dna assets diff <current-template> ${candidate.id}-candidate`,
1766
+ `dna assets adoption ${candidate.id}-candidate`,
1767
+ ]),
1768
+ boundary: "Read-only reference review packet. External workflow/skill sources are references only; IntentDNA templates change only through reviewed workflow drafts, diffs, and asset adoption.",
1769
+ };
1770
+ }
1771
+ function candidateTemplateName(sourceId) {
1772
+ return `${sourceId}-candidate`;
1773
+ }
1774
+ function normalizeTemplateSaveName(name) {
1775
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "source-candidate";
1776
+ }
1777
+ function workflowFromSourceCandidate(candidate) {
1778
+ const closestTemplate = candidate.coverage.matchedTemplates[0]?.name;
1779
+ const comparePrompt = closestTemplate
1780
+ ? `Compare ${candidate.id} against governed template ${closestTemplate}. Extract only reusable workflow behavior and mark runtime-specific instructions as reference-only.`
1781
+ : `Extract reusable workflow behavior from ${candidate.id}. Keep runtime-specific instructions as source references, not template truth.`;
1782
+ return {
1783
+ name: `source-${candidate.id}-candidate`,
1784
+ description: `Reviewable candidate workflow extracted from ${candidate.title}.`,
1785
+ steps: [
1786
+ {
1787
+ id: "read-source",
1788
+ role: "explore",
1789
+ description: "Read the source skill/workflow and identify reusable behavior.",
1790
+ prompt: `Read ${candidate.path}. Identify reusable workflow behavior, verification gates, handoff artifacts, and runtime-specific instructions.`,
1791
+ handoff: {
1792
+ produces: [{ type: "summary", name: "source_behavior_index", description: "Reusable behavior and runtime-only source notes." }],
1793
+ },
1794
+ },
1795
+ {
1796
+ id: "compare-governance",
1797
+ role: "architect",
1798
+ depends_on: ["read-source"],
1799
+ description: "Compare the source against existing governed templates and IntentDNA boundaries.",
1800
+ prompt: comparePrompt,
1801
+ handoff: {
1802
+ consumes: [{ type: "summary", name: "source_behavior_index", from: "read-source", description: "Source behavior index." }],
1803
+ produces: [{ type: "summary", name: "candidate_governance_decision", description: "Refresh, variant, or new-template decision with rationale." }],
1804
+ },
1805
+ },
1806
+ {
1807
+ id: "shape-candidate",
1808
+ role: "planner",
1809
+ depends_on: ["compare-governance"],
1810
+ description: "Shape a minimal workflow asset candidate for human review.",
1811
+ prompt: `Shape ${candidate.id} as a minimal IntentDNA workflow asset candidate. Preserve review, evidence, sync, deploy, and evolution boundaries.`,
1812
+ handoff: {
1813
+ consumes: [{ type: "summary", name: "candidate_governance_decision", from: "compare-governance", description: "Governance decision." }],
1814
+ produces: [{ type: "summary", name: "candidate_template_plan", description: "Candidate template plan and open questions." }],
1815
+ },
1816
+ },
1817
+ {
1818
+ id: "review-candidate",
1819
+ role: "reviewer",
1820
+ depends_on: ["shape-candidate"],
1821
+ description: "Review the candidate before diff, adoption, organization binding, sync, or deploy.",
1822
+ prompt: `Review ${candidate.id}-candidate for source fidelity, governance boundaries, missing verification, and whether it should update an existing template.`,
1823
+ handoff: {
1824
+ consumes: [{ type: "summary", name: "candidate_template_plan", from: "shape-candidate", description: "Candidate template plan." }],
1825
+ produces: [{ type: "summary", name: "candidate_review_verdict", description: "APPROVE, REQUEST_CHANGES, or REJECT with evidence." }],
1826
+ },
1827
+ },
1828
+ ],
1829
+ retry_policy: {
1830
+ max_retries: 1,
1831
+ retry_from: "shape-candidate",
1832
+ backoff: "none",
1833
+ },
1834
+ };
1835
+ }
1836
+ function sourceCandidateEvidence(candidate) {
1837
+ return [
1838
+ {
1839
+ label: `${candidate.id} source skill`,
1840
+ kind: "reference",
1841
+ path: candidate.path,
1842
+ description: "Source skill/workflow used as reference material for this candidate. It is not product truth until reviewed and adopted.",
1843
+ },
1844
+ ];
1845
+ }
1846
+ function buildSourceCandidatePreview(candidate, requestedTemplateName) {
1847
+ const templateName = normalizeTemplateSaveName(requestedTemplateName ?? candidateTemplateName(candidate.id));
1848
+ const workflow = workflowFromSourceCandidate(candidate);
1849
+ const goal = `Extract ${candidate.id} as governed workflow asset`;
1850
+ const draft = createWorkflowDraft({
1851
+ exampleId: "dev_pipeline",
1852
+ goal,
1853
+ strictness: "high",
1854
+ });
1855
+ const templateYaml = workflowDraftToTemplateYaml({
1856
+ templateName,
1857
+ workflow,
1858
+ generatedAt: draft.generated_at,
1859
+ lineage: {
1860
+ generatedFrom: "template_source_review_packet",
1861
+ sourceCandidate: candidate.id,
1862
+ sourceEvidence: candidate.path,
1863
+ },
1864
+ evidence: sourceCandidateEvidence(candidate),
1865
+ });
1866
+ const closestTemplate = candidate.coverage.matchedTemplates[0]?.name;
1867
+ const decision = sourceReviewDecision(candidate);
1868
+ return {
1869
+ mode: "template_source_candidate_preview",
1870
+ source: "external_workflow_references",
1871
+ candidate,
1872
+ templateName,
1873
+ decision,
1874
+ draft: {
1875
+ ...draft,
1876
+ workflow,
1877
+ design_packet: {
1878
+ ...draft.design_packet,
1879
+ workflow: workflow.name,
1880
+ goal,
1881
+ stepCount: workflow.steps.length,
1882
+ reviewCommands: [
1883
+ `dna templates sources ${candidate.id}`,
1884
+ `dna templates sources ${candidate.id} --candidate`,
1885
+ `dna assets diff ${closestTemplate ?? "<current-template>"} ${templateName}`,
1886
+ `dna assets adoption ${templateName}`,
1887
+ ],
1888
+ adoptionCommands: [
1889
+ `dna org bind --asset ${templateName} --status planned --sync-target codex`,
1890
+ "dna org rollout",
1891
+ "dna sync --codex",
1892
+ ],
1893
+ },
1894
+ yaml: templateYaml,
1895
+ validation: {
1896
+ ...draft.validation,
1897
+ preview: {
1898
+ ...draft.validation.preview,
1899
+ step_count: workflow.steps.length,
1900
+ parallel_groups: [
1901
+ ["read-source"],
1902
+ ["compare-governance"],
1903
+ ["shape-candidate"],
1904
+ ["review-candidate"],
1905
+ ],
1906
+ retry_max: workflow.retry_policy?.max_retries ?? 0,
1907
+ },
1908
+ },
1909
+ },
1910
+ templateYaml,
1911
+ reviewCommands: [
1912
+ `dna templates sources ${candidate.id}`,
1913
+ `dna templates manage ${closestTemplate ?? templateName}`,
1914
+ `dna assets diff ${closestTemplate ?? "<current-template>"} ${templateName}`,
1915
+ `dna assets adoption ${templateName}`,
1916
+ ],
1917
+ blockedUntilReview: [
1918
+ "Do not write this candidate into src/templates or .dna/templates without human review.",
1919
+ "Do not bind, rollout, sync, deploy, or mutate evolution markers from this preview alone.",
1920
+ "Do not treat raw external workflow instructions as IntentDNA policy truth.",
1921
+ ],
1922
+ boundary: "Read-only candidate preview. It prints a reviewable template YAML draft but does not write files, configure projects, sync runtimes, deploy, or mutate evolution markers.",
1923
+ };
1924
+ }
1925
+ function writeSourceCandidatePreview(preview) {
1926
+ process.stderr.write("Template reference candidate preview\n");
1927
+ process.stderr.write(`Reference: ${preview.candidate.id}\n`);
1928
+ process.stderr.write(`Template: ${preview.templateName}\n`);
1929
+ process.stderr.write(`Decision: ${preview.decision.status}\n`);
1930
+ process.stderr.write(`Workflow: ${preview.draft.workflow.name}\n`);
1931
+ process.stderr.write(`Steps: ${preview.draft.workflow.steps.length}\n`);
1932
+ process.stderr.write("\nReview commands\n");
1933
+ for (const command of preview.reviewCommands)
1934
+ process.stderr.write(` - ${command}\n`);
1935
+ process.stderr.write("\nBlocked until review\n");
1936
+ for (const blocker of preview.blockedUntilReview)
1937
+ process.stderr.write(` - ${blocker}\n`);
1938
+ process.stderr.write(`Boundary: ${preview.boundary}\n`);
1939
+ process.stderr.write("\n--- YAML preview is written to stdout ---\n");
1940
+ process.stdout.write(preview.templateYaml);
1941
+ if (!preview.templateYaml.endsWith("\n"))
1942
+ process.stdout.write("\n");
1943
+ }
1944
+ async function saveSourceCandidatePreview(params) {
1945
+ const projectDir = params.projectDir ?? process.cwd();
1946
+ const templateDir = join(projectDir, ".dna", "templates");
1947
+ const path = join(templateDir, `${params.preview.templateName}.dna.yaml`);
1948
+ if (existsSync(path)) {
1949
+ return {
1950
+ error: `Workflow asset draft already exists: ${params.preview.templateName}`,
1951
+ path,
1952
+ };
1953
+ }
1954
+ await mkdir(templateDir, { recursive: true });
1955
+ await writeFile(path, params.preview.templateYaml, "utf-8");
1956
+ const closestTemplate = params.preview.candidate.coverage.matchedTemplates[0]?.name ?? "<current-template>";
1957
+ return {
1958
+ mode: "saved_template_source_candidate",
1959
+ source: "external_workflow_references",
1960
+ templateName: params.preview.templateName,
1961
+ path,
1962
+ candidate: params.preview.candidate,
1963
+ decision: params.preview.decision,
1964
+ reviewCommands: params.preview.reviewCommands,
1965
+ nextCommands: [
1966
+ `dna assets inspect ${params.preview.templateName} --evidence`,
1967
+ `dna assets diff ${closestTemplate} ${params.preview.templateName}`,
1968
+ `dna assets adoption ${params.preview.templateName}`,
1969
+ `dna org bind --asset ${params.preview.templateName} --status planned --sync-target codex`,
1970
+ "dna org rollout",
1971
+ "dna sync --codex --dry-run",
1972
+ ],
1973
+ blockedUntilReview: [
1974
+ "Runtime sync remains blocked until asset diff/adoption and organization rollout review pass.",
1975
+ "Vercel deployment is unrelated unless a web/public surface changes.",
1976
+ "Evolution marker mutation remains blocked until evidence-backed suggestions are reviewed.",
1977
+ ],
1978
+ boundary: "Saved reference candidate as a project-local draft only. It is not adopted, organization-bound, synced, deployed, or evolved.",
1979
+ };
1980
+ }
1981
+ function writeSavedSourceCandidate(result) {
1982
+ process.stderr.write("Saved template reference candidate\n");
1983
+ process.stderr.write(`Template: ${result.templateName}\n`);
1984
+ process.stderr.write(`Path: ${result.path}\n`);
1985
+ process.stderr.write(`Decision: ${result.decision.status}\n`);
1986
+ process.stderr.write("\nNext commands\n");
1987
+ for (const command of result.nextCommands)
1988
+ process.stderr.write(` - ${command}\n`);
1989
+ process.stderr.write("\nBlocked until review\n");
1990
+ for (const blocker of result.blockedUntilReview)
1991
+ process.stderr.write(` - ${blocker}\n`);
1992
+ process.stderr.write(`Boundary: ${result.boundary}\n`);
1993
+ }
1994
+ function writeTemplateSourceReviewPacket(packet) {
1995
+ process.stderr.write("Template reference review packet\n");
1996
+ process.stderr.write(`Reference: ${packet.candidate.id}\n`);
1997
+ process.stderr.write(`Title: ${packet.candidate.title}\n`);
1998
+ process.stderr.write(`Path: ${packet.candidate.path}\n`);
1999
+ process.stderr.write(`Decision: ${packet.decision.status}\n`);
2000
+ process.stderr.write(`Reason: ${packet.decision.reason}\n`);
2001
+ process.stderr.write(`Primary: ${packet.decision.primaryCommand}\n`);
2002
+ if (packet.decision.scaffoldFallback)
2003
+ process.stderr.write(`Scaffold fallback: ${packet.decision.scaffoldFallback}\n`);
2004
+ process.stderr.write(`Coverage: ${packet.candidate.coverage.status}\n`);
2005
+ const matched = packet.candidate.coverage.matchedTemplates.map((match) => `${match.name}(${match.score})`).join(", ") || "none";
2006
+ process.stderr.write(`Matched: ${matched}\n`);
2007
+ process.stderr.write(`Signals: ${packet.candidate.signals.join(", ") || "none"}\n`);
2008
+ process.stderr.write(`Priority: ${packet.candidate.priority.score} (${packet.candidate.priority.reason})\n`);
2009
+ process.stderr.write("\nChecklist\n");
2010
+ for (const item of packet.reviewChecklist)
2011
+ process.stderr.write(` - ${item}\n`);
2012
+ process.stderr.write("\nRoute\n");
2013
+ for (const step of packet.route)
2014
+ process.stderr.write(` - ${step}\n`);
2015
+ process.stderr.write(`Boundary: ${packet.boundary}\n`);
2016
+ }
2017
+ export async function runTemplatesSources(opts = {}) {
2018
+ const focus = opts.focus ?? "all";
2019
+ const { sourceDirs, allCandidates } = await loadTemplateSourceCandidates(opts);
2020
+ if (opts.sourceId) {
2021
+ const candidate = allCandidates.find((item) => item.id === opts.sourceId);
2022
+ if (!candidate) {
2023
+ process.stderr.write(`Unknown template reference: ${opts.sourceId}\n`);
2024
+ process.stderr.write("Run `dna templates sources` to list available references.\n");
2025
+ return 2;
2026
+ }
2027
+ if (opts.saveTemplate && !opts.candidate) {
2028
+ process.stderr.write("Error: --save-template requires --candidate for reference candidates.\n");
2029
+ return 2;
2030
+ }
2031
+ if (opts.candidate) {
2032
+ const preview = buildSourceCandidatePreview(candidate, opts.saveTemplate);
2033
+ if (opts.saveTemplate) {
2034
+ const saved = await saveSourceCandidatePreview({ preview, projectDir: opts.projectDir });
2035
+ if ("error" in saved) {
2036
+ process.stderr.write(`${saved.error}\n`);
2037
+ process.stderr.write(`Path: ${saved.path}\n`);
2038
+ process.stderr.write("Review the existing draft before overwriting it.\n");
2039
+ return 2;
2040
+ }
2041
+ if (opts.json) {
2042
+ process.stdout.write(`${JSON.stringify(saved, null, 2)}\n`);
2043
+ return 0;
2044
+ }
2045
+ writeSavedSourceCandidate(saved);
2046
+ return 0;
2047
+ }
2048
+ if (opts.json) {
2049
+ process.stdout.write(`${JSON.stringify(preview, null, 2)}\n`);
2050
+ return 0;
2051
+ }
2052
+ writeSourceCandidatePreview(preview);
2053
+ return 0;
2054
+ }
2055
+ const packet = await buildTemplateSourceReviewPacket(candidate);
2056
+ if (opts.json) {
2057
+ process.stdout.write(`${JSON.stringify(packet, null, 2)}\n`);
2058
+ return 0;
2059
+ }
2060
+ writeTemplateSourceReviewPacket(packet);
2061
+ return 0;
2062
+ }
2063
+ const candidates = allCandidates.filter((candidate) => {
2064
+ if (focus === "refresh")
2065
+ return candidate.coverage.status === "covered" || candidate.coverage.status === "partial";
2066
+ if (focus === "gaps")
2067
+ return candidate.coverage.status === "candidate";
2068
+ return true;
2069
+ });
2070
+ const review = {
2071
+ mode: "template_source_candidates",
2072
+ source: "external_workflow_references",
2073
+ focus,
2074
+ totals: {
2075
+ sourceDirs: sourceDirs.length,
2076
+ sourcesScanned: allCandidates.length,
2077
+ sourcesVisible: candidates.length,
2078
+ covered: allCandidates.filter((candidate) => candidate.coverage.status === "covered").length,
2079
+ partial: allCandidates.filter((candidate) => candidate.coverage.status === "partial").length,
2080
+ candidates: allCandidates.filter((candidate) => candidate.coverage.status === "candidate").length,
2081
+ },
2082
+ sourceDirs,
2083
+ candidates,
2084
+ route: [
2085
+ focus === "refresh"
2086
+ ? "Review covered and partial sources first to decide whether existing templates need updates."
2087
+ : focus === "gaps"
2088
+ ? "Review uncovered sources first to decide whether a new workflow asset is worth drafting."
2089
+ : "Review high-priority sources first, then choose refresh or gaps focus when the queue is too broad.",
2090
+ "Compare the closest existing template with dna templates manage <template>.",
2091
+ "Draft candidate templates only after reference review, then use asset diff/adoption before promotion.",
2092
+ ],
2093
+ boundary: "This command is read-only. External workflow/skill sources are references only; IntentDNA templates change only through reviewed workflow drafts and asset adoption.",
2094
+ };
2095
+ if (opts.json) {
2096
+ process.stdout.write(`${JSON.stringify(review, null, 2)}\n`);
2097
+ return 0;
2098
+ }
2099
+ process.stderr.write("Template reference candidates\n");
2100
+ process.stderr.write(`Focus: ${review.focus}\n`);
2101
+ process.stderr.write(`Sources scanned: ${review.totals.sourcesScanned}\n`);
2102
+ process.stderr.write(`Visible: ${review.totals.sourcesVisible}\n`);
2103
+ process.stderr.write(`Covered: ${review.totals.covered}\n`);
2104
+ process.stderr.write(`Partial: ${review.totals.partial}\n`);
2105
+ process.stderr.write(`Candidates: ${review.totals.candidates}\n`);
2106
+ process.stderr.write("\nSource directories\n");
2107
+ for (const dir of review.sourceDirs)
2108
+ process.stderr.write(` - ${dir}\n`);
2109
+ process.stderr.write("\nReview queue\n");
2110
+ for (const candidate of review.candidates.slice(0, 12)) {
2111
+ const matched = candidate.coverage.matchedTemplates.map((match) => `${match.name}(${match.score})`).join(", ") || "none";
2112
+ process.stderr.write(` - [${candidate.coverage.status}] ${candidate.id}: ${candidate.title}\n`);
2113
+ process.stderr.write(` priority: ${candidate.priority.score} (${candidate.priority.reason})\n`);
2114
+ process.stderr.write(` signals: ${candidate.signals.join(", ") || "none"}\n`);
2115
+ process.stderr.write(` matched: ${matched}\n`);
2116
+ process.stderr.write(` why: ${candidate.coverage.explanation}\n`);
2117
+ for (const command of candidate.recommendedCommands) {
2118
+ process.stderr.write(` next: ${command.command}\n`);
2119
+ }
2120
+ }
2121
+ process.stderr.write("\nRoute\n");
2122
+ for (const step of review.route)
2123
+ process.stderr.write(` - ${step}\n`);
2124
+ process.stderr.write(`Boundary: ${review.boundary}\n`);
2125
+ return 0;
2126
+ }
2127
+ export async function runTemplatesManage(opts = {}) {
2128
+ const templates = await listTemplateCatalog({ projectDir: opts.projectDir });
2129
+ if (templates.length === 0) {
2130
+ process.stderr.write("No templates found.\n");
2131
+ return 1;
2132
+ }
2133
+ const selectedEntry = opts.name
2134
+ ? templates.find((entry) => entry.name === opts.name || entry.displayName === opts.name || entry.namespace === opts.name)
2135
+ : templates.find((entry) => entry.name === WORKFLOW_ASSET_IDS.default) ?? templates[0];
2136
+ if (!selectedEntry) {
2137
+ process.stderr.write(`Unknown template: ${opts.name}\n`);
2138
+ return 2;
2139
+ }
2140
+ const loaded = await Promise.all(templates.map(async (entry) => {
2141
+ const content = await readFile(entry.path, "utf-8");
2142
+ const data = asRecord(parseYAML(content)) ?? {};
2143
+ const marketplace = await getTemplateMarketplaceMetadata(entry, { content });
2144
+ return {
2145
+ entry,
2146
+ content,
2147
+ data,
2148
+ item: buildTemplateManagementItem(entry, content, data, marketplace),
2149
+ };
2150
+ }));
2151
+ const selected = loaded.find((item) => item.entry.name === selectedEntry.name) ?? loaded[0];
2152
+ const lineCount = selected.content.split("\n").length;
2153
+ const anchors = templateAnchors(selected.content);
2154
+ const allTags = unique(loaded.flatMap((item) => item.item.tags)).sort();
2155
+ const view = {
2156
+ mode: "template_management_view",
2157
+ layout: {
2158
+ left: ["tags", "source", "stats", "steps", "commands"],
2159
+ right: "full_yaml_file",
2160
+ followSelection: "Use anchor startLine/endLine to keep the YAML editor synced with the selected tag, section, or workflow step.",
2161
+ },
2162
+ selected: {
2163
+ ...selected.item,
2164
+ path: selected.entry.path,
2165
+ anchors,
2166
+ nextCommands: [
2167
+ selected.item.commands.show,
2168
+ selected.item.commands.assetInspect,
2169
+ selected.item.commands.assetAdoption,
2170
+ ],
2171
+ },
2172
+ catalog: {
2173
+ totals: {
2174
+ templates: loaded.length,
2175
+ builtin: loaded.filter((item) => item.entry.source === "builtin").length,
2176
+ project: loaded.filter((item) => item.entry.source === "project").length,
2177
+ externalReferenceTagged: loaded.filter((item) => item.item.tags.includes("external-reference")).length,
2178
+ },
2179
+ tags: allTags,
2180
+ items: loaded.map((item) => item.item),
2181
+ },
2182
+ customTemplateRoutes: customTemplateRoutes(selected.item.name),
2183
+ referenceReview: {
2184
+ status: "planned_import_path",
2185
+ source: "external_workflow_references",
2186
+ route: [
2187
+ "Read external workflow/skill references as source material.",
2188
+ "Extract role, step, verification, state, and evidence conventions into candidate workflow assets.",
2189
+ "Review candidate diff before promoting it into src/templates or .dna/templates.",
2190
+ ],
2191
+ boundary: "Do not copy external runtime docs directly into templates; IntentDNA stores governed workflow assets and keeps source systems as references only.",
2192
+ },
2193
+ document: {
2194
+ path: selected.entry.path,
2195
+ language: "yaml",
2196
+ lineCount,
2197
+ content: selected.content,
2198
+ },
2199
+ };
2200
+ if (opts.json) {
2201
+ process.stdout.write(`${JSON.stringify(view, null, 2)}\n`);
2202
+ return 0;
2203
+ }
2204
+ process.stderr.write("Template management view\n");
2205
+ process.stderr.write(`Selected: ${view.selected.name}`);
2206
+ if (view.selected.displayName)
2207
+ process.stderr.write(` — ${view.selected.displayName}`);
2208
+ process.stderr.write(`\nSource: ${view.selected.source}\n`);
2209
+ process.stderr.write(`Path: ${view.selected.path}\n`);
2210
+ process.stderr.write(`Tags: ${view.selected.tags.join(", ")}\n`);
2211
+ process.stderr.write(`Marketplace: ${view.selected.marketplace.visibility}; harnesses=${view.selected.marketplace.supportedHarnesses.join(", ")}; sync=${view.selected.marketplace.syncSurfaces.join(", ")}\n`);
2212
+ process.stderr.write(`Stats: genes=${view.selected.stats.genes}, roles=${view.selected.stats.roles}, workflows=${view.selected.stats.workflows}, steps=${view.selected.stats.workflowSteps}, controllers=${view.selected.stats.controllers}\n`);
2213
+ process.stderr.write("\nLayout\n");
2214
+ process.stderr.write(" left: tags + details + step index + commands\n");
2215
+ process.stderr.write(" right: full YAML file\n");
2216
+ process.stderr.write(` follow: ${view.layout.followSelection}\n`);
2217
+ process.stderr.write("\nStep/section index\n");
2218
+ for (const anchor of view.selected.anchors) {
2219
+ process.stderr.write(` - ${anchor.kind} ${anchor.label}: lines ${anchor.startLine}-${anchor.endLine}\n`);
2220
+ }
2221
+ process.stderr.write("\nCommands\n");
2222
+ for (const command of view.selected.nextCommands)
2223
+ process.stderr.write(` - ${command}\n`);
2224
+ process.stderr.write("\nCustom template routes\n");
2225
+ for (const route of view.customTemplateRoutes) {
2226
+ process.stderr.write(` ${route.id}. ${route.label}\n`);
2227
+ process.stderr.write(` command: ${route.command}\n`);
2228
+ process.stderr.write(` writes=${route.writes ? "yes" : "no"}\n`);
2229
+ process.stderr.write(` purpose: ${route.purpose}\n`);
2230
+ if (route.scaffoldFallback) {
2231
+ process.stderr.write(` scaffold fallback: ${route.scaffoldFallback.command}\n`);
2232
+ process.stderr.write(` scaffold positioning: ${route.scaffoldFallback.positioning}\n`);
2233
+ }
2234
+ process.stderr.write(` next: ${route.nextCommands.join(" | ")}\n`);
2235
+ process.stderr.write(` boundary: ${route.boundary}\n`);
2236
+ }
2237
+ process.stderr.write("\nExternal reference review\n");
2238
+ for (const step of view.referenceReview.route)
2239
+ process.stderr.write(` - ${step}\n`);
2240
+ process.stderr.write(` boundary: ${view.referenceReview.boundary}\n`);
2241
+ process.stderr.write("\n--- YAML is written to stdout ---\n");
2242
+ process.stdout.write(view.document.content);
2243
+ if (!view.document.content.endsWith("\n"))
2244
+ process.stdout.write("\n");
2245
+ return 0;
2246
+ }
97
2247
  export async function runTemplatesDeploy(opts = {}) {
98
2248
  try {
99
2249
  const cwd = process.cwd();