@topogram/cli 0.3.64 → 0.3.66

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 (278) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +716 -0
  3. package/src/adoption/plan.js +12 -703
  4. package/src/adoption/reporting.js +1 -1
  5. package/src/agent-brief.js +7 -21
  6. package/src/agent-ops/query-builders/auth.js +375 -0
  7. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  8. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  9. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  10. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  11. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  12. package/src/agent-ops/query-builders/change-risk.js +25 -0
  13. package/src/agent-ops/query-builders/common.js +149 -0
  14. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  15. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  16. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  17. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  18. package/src/agent-ops/query-builders/work-packets.js +417 -0
  19. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  20. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  21. package/src/agent-ops/query-builders/workflow-presets-core.js +677 -0
  22. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  23. package/src/agent-ops/query-builders.d.ts +26 -26
  24. package/src/agent-ops/query-builders.js +42 -5021
  25. package/src/archive/jsonl.js +2 -2
  26. package/src/archive/resolver-bridge.js +1 -1
  27. package/src/archive/unarchive.js +2 -1
  28. package/src/catalog/constants.js +10 -0
  29. package/src/catalog/copy.js +65 -0
  30. package/src/catalog/diagnostics.js +15 -0
  31. package/src/catalog/entries.js +42 -0
  32. package/src/catalog/files.js +67 -0
  33. package/src/catalog/provenance.js +123 -0
  34. package/src/catalog/source.js +150 -0
  35. package/src/catalog/validation.js +252 -0
  36. package/src/catalog.d.ts +2 -0
  37. package/src/catalog.js +18 -746
  38. package/src/cli/command-parsers/project.js +3 -0
  39. package/src/cli/command-parsers/shared.js +1 -1
  40. package/src/cli/commands/agent.js +2 -2
  41. package/src/cli/commands/catalog/check.js +31 -0
  42. package/src/cli/commands/catalog/copy.js +59 -0
  43. package/src/cli/commands/catalog/doctor.js +248 -0
  44. package/src/cli/commands/catalog/help.js +21 -0
  45. package/src/cli/commands/catalog/list.js +52 -0
  46. package/src/cli/commands/catalog/runner.js +92 -0
  47. package/src/cli/commands/catalog/shared.js +17 -0
  48. package/src/cli/commands/catalog/show.js +134 -0
  49. package/src/cli/commands/catalog.js +30 -615
  50. package/src/cli/commands/check.js +3 -3
  51. package/src/cli/commands/doctor.js +2 -9
  52. package/src/cli/commands/generator-policy/package-info.js +162 -0
  53. package/src/cli/commands/generator-policy/payloads.js +372 -0
  54. package/src/cli/commands/generator-policy/printers.js +159 -0
  55. package/src/cli/commands/generator-policy/runner.js +81 -0
  56. package/src/cli/commands/generator-policy/shared.js +39 -0
  57. package/src/cli/commands/generator-policy.js +15 -783
  58. package/src/cli/commands/import/adopt.js +170 -0
  59. package/src/cli/commands/import/check.js +91 -0
  60. package/src/cli/commands/import/diff.js +84 -0
  61. package/src/cli/commands/import/help.js +47 -0
  62. package/src/cli/commands/import/paths.js +269 -0
  63. package/src/cli/commands/import/plan.js +292 -0
  64. package/src/cli/commands/import/refresh.js +471 -0
  65. package/src/cli/commands/import/status-history.js +196 -0
  66. package/src/cli/commands/import/workspace.js +233 -0
  67. package/src/cli/commands/import.js +33 -1732
  68. package/src/cli/commands/migrate.js +153 -0
  69. package/src/cli/commands/package/constants.js +17 -0
  70. package/src/cli/commands/package/doctor.js +240 -0
  71. package/src/cli/commands/package/help.js +27 -0
  72. package/src/cli/commands/package/lockfile.js +135 -0
  73. package/src/cli/commands/package/npm.js +97 -0
  74. package/src/cli/commands/package/reporting.js +35 -0
  75. package/src/cli/commands/package/runner.js +33 -0
  76. package/src/cli/commands/package/shared.js +9 -0
  77. package/src/cli/commands/package/update-cli.js +252 -0
  78. package/src/cli/commands/package/versions.js +35 -0
  79. package/src/cli/commands/package.js +29 -813
  80. package/src/cli/commands/query/change-plan.js +68 -0
  81. package/src/cli/commands/query/definitions.js +202 -0
  82. package/src/cli/commands/query/import-adopt.js +121 -0
  83. package/src/cli/commands/query/runner/artifacts.js +102 -0
  84. package/src/cli/commands/query/runner/boundaries.js +211 -0
  85. package/src/cli/commands/query/runner/change.js +182 -0
  86. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  87. package/src/cli/commands/query/runner/index.js +31 -0
  88. package/src/cli/commands/query/runner/output.js +12 -0
  89. package/src/cli/commands/query/runner/workflow.js +241 -0
  90. package/src/cli/commands/query/runner.js +3 -0
  91. package/src/cli/commands/query/workflow-context.js +5 -0
  92. package/src/cli/commands/query/workspace.js +270 -0
  93. package/src/cli/commands/query.js +9 -1300
  94. package/src/cli/commands/source.js +3 -12
  95. package/src/cli/commands/template/baseline.js +100 -0
  96. package/src/cli/commands/template/check.js +467 -0
  97. package/src/cli/commands/template/constants.js +8 -0
  98. package/src/cli/commands/template/diagnostics.js +26 -0
  99. package/src/cli/commands/template/help.js +28 -0
  100. package/src/cli/commands/template/lifecycle.js +404 -0
  101. package/src/cli/commands/template/list-show.js +287 -0
  102. package/src/cli/commands/template/policy.js +422 -0
  103. package/src/cli/commands/template/shared.js +127 -0
  104. package/src/cli/commands/template/updates.js +352 -0
  105. package/src/cli/commands/template-runner.js +6 -6
  106. package/src/cli/commands/template.js +41 -2143
  107. package/src/cli/commands/trust.js +1 -1
  108. package/src/cli/commands/workflow.js +6 -1
  109. package/src/cli/dispatcher.js +6 -1
  110. package/src/cli/help.js +15 -14
  111. package/src/cli/migration-guidance.js +1 -1
  112. package/src/cli/output-safety.js +2 -1
  113. package/src/cli/path-normalization.js +3 -13
  114. package/src/generator/api/contracts.js +497 -0
  115. package/src/generator/api/metadata.js +221 -0
  116. package/src/generator/api/openapi.js +559 -0
  117. package/src/generator/api/schema.js +124 -0
  118. package/src/generator/api/types.d.ts +98 -0
  119. package/src/generator/api.js +3 -1195
  120. package/src/generator/context/domain-page.js +1 -1
  121. package/src/generator/context/shared/domain-sdlc.js +282 -0
  122. package/src/generator/context/shared/maintained-boundary.js +665 -0
  123. package/src/generator/context/shared/metrics.js +85 -0
  124. package/src/generator/context/shared/primitives.js +64 -0
  125. package/src/generator/context/shared/relationships.js +453 -0
  126. package/src/generator/context/shared/summaries.js +263 -0
  127. package/src/generator/context/shared/types.d.ts +207 -0
  128. package/src/generator/context/shared.d.ts +42 -0
  129. package/src/generator/context/shared.js +80 -1390
  130. package/src/generator/context/slice/core.js +397 -0
  131. package/src/generator/context/slice/sdlc.js +417 -0
  132. package/src/generator/context/slice/ui-packets.js +183 -0
  133. package/src/generator/context/slice.js +2 -859
  134. package/src/generator/context/task-mode.js +2 -2
  135. package/src/generator/registry/index.js +507 -0
  136. package/src/generator/registry.js +18 -504
  137. package/src/generator/runtime/environment/index.js +666 -0
  138. package/src/generator/runtime/environment.js +4 -666
  139. package/src/generator/runtime/runtime-check/index.js +554 -0
  140. package/src/generator/runtime/runtime-check.js +4 -554
  141. package/src/generator/runtime/shared/index.js +572 -0
  142. package/src/generator/runtime/shared.js +19 -570
  143. package/src/generator/sdlc/doc-page.js +1 -1
  144. package/src/generator/shared.d.ts +2 -0
  145. package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
  146. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +1 -1
  147. package/src/generator/surfaces/shared.d.ts +3 -0
  148. package/src/generator/widget-conformance/behavior-report.js +258 -0
  149. package/src/generator/widget-conformance/checks.js +371 -0
  150. package/src/generator/widget-conformance/projection-context.js +200 -0
  151. package/src/generator/widget-conformance/report.js +166 -0
  152. package/src/generator/widget-conformance/types.d.ts +121 -0
  153. package/src/generator/widget-conformance.js +3 -824
  154. package/src/import/core/context.d.ts +3 -0
  155. package/src/import/core/context.js +5 -7
  156. package/src/import/core/contracts.d.ts +1 -0
  157. package/src/import/core/registry.d.ts +4 -0
  158. package/src/import/core/runner/candidates.js +337 -0
  159. package/src/import/core/runner/options.js +22 -0
  160. package/src/import/core/runner/reports.js +51 -0
  161. package/src/import/core/runner/run.js +79 -0
  162. package/src/import/core/runner/tracks.js +150 -0
  163. package/src/import/core/runner/ui-drafts.js +393 -0
  164. package/src/import/core/runner.js +3 -698
  165. package/src/import/core/shared/api-routes.js +221 -0
  166. package/src/import/core/shared/candidates.js +97 -0
  167. package/src/import/core/shared/files.js +177 -0
  168. package/src/import/core/shared/next-app.js +389 -0
  169. package/src/import/core/shared/types.d.ts +51 -0
  170. package/src/import/core/shared/ui-routes.js +230 -0
  171. package/src/import/core/shared.js +60 -861
  172. package/src/new-project/constants.js +128 -0
  173. package/src/new-project/create.js +90 -0
  174. package/src/new-project/json.js +28 -0
  175. package/src/new-project/metadata.js +96 -0
  176. package/src/new-project/package-spec.js +161 -0
  177. package/src/new-project/project-files.js +351 -0
  178. package/src/new-project/template-policy.js +269 -0
  179. package/src/new-project/template-resolution.js +370 -0
  180. package/src/new-project/template-snapshots.js +442 -0
  181. package/src/new-project/template-updates.js +512 -0
  182. package/src/new-project/types.d.ts +83 -0
  183. package/src/new-project.js +6 -2277
  184. package/src/parser.d.ts +87 -1
  185. package/src/parser.js +118 -0
  186. package/src/policy/review-boundaries.d.ts +15 -0
  187. package/src/project-config/index.js +591 -0
  188. package/src/project-config.js +19 -561
  189. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  190. package/src/resolver/enrich/bug.js +2 -0
  191. package/src/resolver/enrich/pitch.js +2 -0
  192. package/src/resolver/enrich/requirement.js +2 -0
  193. package/src/resolver/enrich/task.js +2 -0
  194. package/src/resolver/index.js +19 -2089
  195. package/src/resolver/normalize.js +384 -1
  196. package/src/resolver/plans.js +168 -0
  197. package/src/resolver/projections-api.js +494 -0
  198. package/src/resolver/projections-db.js +133 -0
  199. package/src/resolver/projections-ui.js +317 -0
  200. package/src/resolver/shapes.js +251 -0
  201. package/src/resolver/shared.js +278 -0
  202. package/src/resolver/widgets.js +132 -0
  203. package/src/sdlc/adopt.js +6 -5
  204. package/src/sdlc/paths.js +3 -5
  205. package/src/sdlc/scaffold.js +2 -1
  206. package/src/template-trust/constants.js +62 -0
  207. package/src/template-trust/content.js +258 -0
  208. package/src/template-trust/diff.js +92 -0
  209. package/src/template-trust/policy.js +61 -0
  210. package/src/template-trust/record.js +90 -0
  211. package/src/template-trust/status.js +182 -0
  212. package/src/template-trust.js +24 -687
  213. package/src/text-helpers.d.ts +1 -0
  214. package/src/topogram-types.d.ts +69 -0
  215. package/src/validator/common.js +488 -0
  216. package/src/validator/data-model.js +237 -0
  217. package/src/validator/docs.js +167 -0
  218. package/src/validator/expressions.js +146 -1
  219. package/src/validator/index.d.ts +23 -0
  220. package/src/validator/index.js +32 -3585
  221. package/src/validator/kinds.d.ts +41 -0
  222. package/src/validator/kinds.js +2 -0
  223. package/src/validator/model-helpers.js +46 -0
  224. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  225. package/src/validator/per-kind/bug.js +6 -0
  226. package/src/validator/per-kind/domain.js +15 -2
  227. package/src/validator/per-kind/pitch.js +7 -0
  228. package/src/validator/per-kind/requirement.js +5 -0
  229. package/src/validator/per-kind/task.js +7 -0
  230. package/src/validator/per-kind/widget.js +14 -0
  231. package/src/validator/projections/api-http-async.js +410 -0
  232. package/src/validator/projections/api-http-authz.js +88 -0
  233. package/src/validator/projections/api-http-core.js +205 -0
  234. package/src/validator/projections/api-http-policies.js +339 -0
  235. package/src/validator/projections/api-http-responses.js +233 -0
  236. package/src/validator/projections/api-http.js +44 -0
  237. package/src/validator/projections/db.js +353 -0
  238. package/src/validator/projections/generator-defaults.js +45 -0
  239. package/src/validator/projections/helpers.js +87 -0
  240. package/src/validator/projections/ui-helpers.js +214 -0
  241. package/src/validator/projections/ui-navigation.js +344 -0
  242. package/src/validator/projections/ui-structure.js +364 -0
  243. package/src/validator/projections/ui-widgets.js +493 -0
  244. package/src/validator/projections/ui.js +46 -0
  245. package/src/validator/registry.js +48 -1
  246. package/src/validator/utils.d.ts +20 -0
  247. package/src/validator/utils.js +115 -12
  248. package/src/widget-behavior.d.ts +1 -0
  249. package/src/workflows/import-app/api/collect.js +221 -0
  250. package/src/workflows/import-app/api/openapi.js +257 -0
  251. package/src/workflows/import-app/api/routes.js +327 -0
  252. package/src/workflows/import-app/api/sources.js +22 -0
  253. package/src/workflows/import-app/api.js +2 -797
  254. package/src/workflows/reconcile/adoption-plan/build.js +212 -0
  255. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  256. package/src/workflows/reconcile/adoption-plan/outputs.js +153 -0
  257. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  258. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  259. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  260. package/src/workflows/reconcile/adoption-plan.js +30 -740
  261. package/src/workflows/reconcile/auth/closures.js +115 -0
  262. package/src/workflows/reconcile/auth/formatters.js +142 -0
  263. package/src/workflows/reconcile/auth/inference.js +330 -0
  264. package/src/workflows/reconcile/auth/roles.js +122 -0
  265. package/src/workflows/reconcile/auth.js +35 -690
  266. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  267. package/src/workflows/reconcile/bundle-core.js +12 -598
  268. package/src/workflows/reconcile/candidate-model.js +18 -2
  269. package/src/workflows/reconcile/canonical-surface.js +1 -1
  270. package/src/workflows/reconcile/impacts/adoption-plan.js +196 -0
  271. package/src/workflows/reconcile/impacts/indexes.js +105 -0
  272. package/src/workflows/reconcile/impacts/patches.js +252 -0
  273. package/src/workflows/reconcile/impacts/reports.js +80 -0
  274. package/src/workflows/reconcile/impacts.js +14 -623
  275. package/src/workflows/reconcile/renderers.js +41 -6
  276. package/src/workflows/shared.js +5 -11
  277. package/src/workspace-docs.d.ts +29 -0
  278. package/src/workspace-paths.js +328 -0
@@ -0,0 +1,677 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import {
5
+ CANONICAL_TASK_MODES,
6
+ PROVIDER_PRESET_MANUAL_DECISION_CATEGORIES,
7
+ WORKFLOW_REVIEW_BLOCKERS,
8
+ stableOrderedUnion,
9
+ stableSortedStrings
10
+ } from "./common.js";
11
+ import { DEFAULT_TOPO_FOLDER_NAME, resolveTopoRoot } from "../../workspace-paths.js";
12
+ export function workflowPresetReviewClass(preset) {
13
+ const categories = stableSortedStrings([
14
+ ...(preset?.review_policy?.escalate_categories || []),
15
+ ...(preset?.review_escalation_categories || [])
16
+ ]);
17
+ if (categories.some((category) => PROVIDER_PRESET_MANUAL_DECISION_CATEGORIES.has(category))) {
18
+ return "manual_decision";
19
+ }
20
+ return "review_required";
21
+ }
22
+
23
+ export function readJsonArtifactsFromDir(dirPath) {
24
+ if (!dirPath || !fs.existsSync(dirPath)) {
25
+ return [];
26
+ }
27
+ return fs.readdirSync(dirPath)
28
+ .filter((entry) => entry.endsWith(".json"))
29
+ .map((entry) => {
30
+ const filePath = path.join(dirPath, entry);
31
+ try {
32
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
33
+ return { parsed, filePath };
34
+ } catch {
35
+ return null;
36
+ }
37
+ })
38
+ .filter(Boolean);
39
+ }
40
+
41
+ export function normalizePresetAppliesTo(appliesTo = {}) {
42
+ return {
43
+ task_classes: stableSortedStrings(appliesTo.task_classes || appliesTo.task_modes || []),
44
+ provider_ids: stableSortedStrings(appliesTo.provider_ids || []),
45
+ provider_kinds: stableSortedStrings(appliesTo.provider_kinds || []),
46
+ outputs: stableSortedStrings(appliesTo.outputs || []),
47
+ integration_categories: stableSortedStrings(appliesTo.integration_categories || []),
48
+ query_families: stableSortedStrings(appliesTo.query_families || [])
49
+ };
50
+ }
51
+
52
+ export function normalizePresetActivation(activation = {}) {
53
+ return {
54
+ ...normalizePresetAppliesTo(activation || {}),
55
+ manual_only: Boolean(activation?.manual_only)
56
+ };
57
+ }
58
+
59
+ export function normalizeProviderWorkflowPresetManifest(manifest, {
60
+ filePath = null
61
+ } = {}) {
62
+ if (!manifest || typeof manifest !== "object") return null;
63
+ const provider = manifest.provider && typeof manifest.provider === "object" ? manifest.provider : {};
64
+ if (!provider.id) return null;
65
+ const exportsSection = manifest.exports && typeof manifest.exports === "object" ? manifest.exports : {};
66
+ const reviewDefaults = manifest.review_defaults && typeof manifest.review_defaults === "object" ? manifest.review_defaults : {};
67
+ const requirements = manifest.requirements && typeof manifest.requirements === "object" ? manifest.requirements : {};
68
+ const rawWorkflowPresets = Array.isArray(exportsSection.workflow_presets) ? exportsSection.workflow_presets : [];
69
+ const workflowPresets = rawWorkflowPresets
70
+ .filter((entry) => entry && typeof entry === "object")
71
+ .map((entry) => {
72
+ const recommendedTaskMode = entry.recommended_task_mode || null;
73
+ const validation = {
74
+ missing_path: !entry.path,
75
+ invalid_kind: Boolean(entry.kind) && entry.kind !== "provider_workflow_preset",
76
+ invalid_recommended_task_mode: Boolean(recommendedTaskMode) && !CANONICAL_TASK_MODES.has(recommendedTaskMode),
77
+ invalid_review_default: Boolean(reviewDefaults.workflow_presets) && !WORKFLOW_REVIEW_BLOCKERS.has(reviewDefaults.workflow_presets) && reviewDefaults.workflow_presets !== "safe"
78
+ };
79
+ return {
80
+ id: entry.id || path.basename(entry.path || "", path.extname(entry.path || "")) || null,
81
+ label: entry.label || entry.id || null,
82
+ path: entry.path || null,
83
+ kind: entry.kind || "provider_workflow_preset",
84
+ applies_to: normalizePresetAppliesTo(entry.applies_to || {}),
85
+ proof_ref: entry.proof_ref || null,
86
+ recommended_task_mode: recommendedTaskMode,
87
+ validation,
88
+ valid: !Object.values(validation).some(Boolean)
89
+ };
90
+ })
91
+ .filter((entry) => entry.id);
92
+ return {
93
+ provider: {
94
+ id: provider.id,
95
+ kind: provider.kind || null,
96
+ display_name: provider.display_name || provider.name || provider.id,
97
+ version: provider.version || null
98
+ },
99
+ file_path: filePath,
100
+ workflow_core_version: requirements.workflow_core_version || null,
101
+ review_defaults: {
102
+ workflow_presets: reviewDefaults.workflow_presets || "review_required"
103
+ },
104
+ exports: {
105
+ workflow_presets: workflowPresets
106
+ }
107
+ };
108
+ }
109
+
110
+ export function normalizeWorkflowPresetArtifact(preset, {
111
+ kind,
112
+ filePath = null,
113
+ provenance = null
114
+ } = {}) {
115
+ if (!preset || typeof preset !== "object") {
116
+ return null;
117
+ }
118
+
119
+ const normalizedKind = preset.kind || kind || null;
120
+ if (!["provider_workflow_preset", "team_workflow_preset"].includes(normalizedKind)) {
121
+ return null;
122
+ }
123
+
124
+ const adoptionState = preset.adoption_state || preset.state || (normalizedKind === "team_workflow_preset" ? "accept" : "stage");
125
+ const sourcePriority = Number.isFinite(preset.source_priority)
126
+ ? preset.source_priority
127
+ : (normalizedKind === "team_workflow_preset" ? 200 : 100);
128
+ const priority = Number.isFinite(preset.priority) ? preset.priority : sourcePriority;
129
+ const toolHints = preset.tool_hints && typeof preset.tool_hints === "object" ? preset.tool_hints : {};
130
+ const provider = preset.provider && typeof preset.provider === "object" ? preset.provider : {};
131
+
132
+ return {
133
+ id: preset.id || path.basename(filePath || "", ".json") || null,
134
+ label: preset.label || preset.id || path.basename(filePath || "", ".json") || null,
135
+ kind: normalizedKind,
136
+ applies_to: normalizePresetAppliesTo(preset.applies_to || {}),
137
+ recommended_task_mode: preset.recommended_task_mode || null,
138
+ preferred_queries: stableOrderedUnion(preset.preferred_queries || []),
139
+ artifact_load_order: stableOrderedUnion(preset.artifact_load_order || []),
140
+ review_policy: {
141
+ block_on: stableSortedStrings((preset.review_policy?.block_on || []).filter((entry) => WORKFLOW_REVIEW_BLOCKERS.has(entry))),
142
+ escalate_categories: stableSortedStrings(preset.review_policy?.escalate_categories || [])
143
+ },
144
+ verification_policy: {
145
+ required: stableOrderedUnion(preset.verification_policy?.required || []),
146
+ recommended: stableOrderedUnion(preset.verification_policy?.recommended || []),
147
+ require_output_specific_checks: Boolean(preset.verification_policy?.require_output_specific_checks),
148
+ require_maintained_checks_when_seams_affected: Boolean(preset.verification_policy?.require_maintained_checks_when_seams_affected)
149
+ },
150
+ multi_agent_policy: {
151
+ allowed: preset.multi_agent_policy?.allowed,
152
+ default_strategy: preset.multi_agent_policy?.default_strategy || null
153
+ },
154
+ handoff_defaults: {
155
+ required_fields: stableOrderedUnion(preset.handoff_defaults?.required_fields || [])
156
+ },
157
+ tool_hints: toolHints,
158
+ active: preset.active !== false,
159
+ activation: normalizePresetActivation(preset.activation || {}),
160
+ priority,
161
+ provenance: preset.provenance || provenance || {
162
+ source_path: filePath,
163
+ provider_id: provider.id || preset.provider_id || null
164
+ },
165
+ source_priority: sourcePriority,
166
+ adoption_state: adoptionState,
167
+ provider: {
168
+ id: provider.id || preset.provider_id || null,
169
+ kind: provider.kind || preset.provider_kind || null
170
+ },
171
+ refresh_baseline: preset.refresh_baseline || null,
172
+ derived_from: preset.derived_from || null,
173
+ file_path: filePath,
174
+ review_class: workflowPresetReviewClass(preset)
175
+ };
176
+ }
177
+
178
+ export function loadWorkflowPresetArtifacts(workspaceRoot) {
179
+ if (!workspaceRoot) {
180
+ return { provider_presets: [], team_presets: [], provider_manifests: [] };
181
+ }
182
+ const topogramRoot = resolveTopoRoot(workspaceRoot);
183
+ const providerDir = path.join(topogramRoot, "candidates", "providers", "workflow-presets");
184
+ const providerManifestDir = path.join(topogramRoot, "candidates", "providers", "manifests");
185
+ const teamDirs = [
186
+ path.join(topogramRoot, "workflow-presets"),
187
+ path.join(topogramRoot, DEFAULT_TOPO_FOLDER_NAME, "workflow-presets")
188
+ ];
189
+
190
+ const providerPresets = readJsonArtifactsFromDir(providerDir)
191
+ .map(({ parsed, filePath }) => normalizeWorkflowPresetArtifact(parsed, {
192
+ kind: "provider_workflow_preset",
193
+ filePath,
194
+ provenance: {
195
+ source_path: filePath,
196
+ provider_id: parsed?.provider?.id || parsed?.provider_id || null
197
+ }
198
+ }))
199
+ .filter(Boolean);
200
+
201
+ const teamPresets = teamDirs.flatMap((teamDir) => readJsonArtifactsFromDir(teamDir))
202
+ .map(({ parsed, filePath }) => normalizeWorkflowPresetArtifact(parsed, {
203
+ kind: "team_workflow_preset",
204
+ filePath,
205
+ provenance: {
206
+ source_path: filePath,
207
+ provider_id: parsed?.provider?.id || parsed?.provider_id || null
208
+ }
209
+ }))
210
+ .filter(Boolean);
211
+
212
+ const providerManifests = readJsonArtifactsFromDir(providerManifestDir)
213
+ .map(({ parsed, filePath }) => normalizeProviderWorkflowPresetManifest(parsed, { filePath }))
214
+ .filter(Boolean);
215
+
216
+ return {
217
+ provider_presets: providerPresets,
218
+ team_presets: teamPresets,
219
+ provider_manifests: providerManifests
220
+ };
221
+ }
222
+
223
+ export function activeProviderPreset(preset) {
224
+ return preset.active !== false && ["accept", "accepted"].includes(preset.adoption_state);
225
+ }
226
+
227
+ export function activeTeamPreset(preset) {
228
+ if (preset.active === false) return false;
229
+ return !["reject", "rejected", "stage", "staged"].includes(preset.adoption_state);
230
+ }
231
+
232
+ export function presetAppliesToContext(preset, selectors = {}) {
233
+ const applies = preset.applies_to || {};
234
+ const selectedOutputs = stableSortedStrings(selectors.outputs || []);
235
+ const presetId = selectors.preset_id || null;
236
+ const providerId = selectors.provider_id || null;
237
+ const providerKinds = stableSortedStrings(selectors.provider_kinds || []);
238
+ const taskClass = selectors.task_class || selectors.mode || null;
239
+ const queryFamily = selectors.query_family || null;
240
+ const integrationCategories = stableSortedStrings(selectors.integration_categories || []);
241
+
242
+ if (presetId && preset.id !== presetId) {
243
+ return false;
244
+ }
245
+ if (providerId && (preset.provider?.id || preset.provenance?.provider_id) && (preset.provider?.id || preset.provenance?.provider_id) !== providerId) {
246
+ return false;
247
+ }
248
+ if (applies.task_classes.length > 0 && (!taskClass || !applies.task_classes.includes(taskClass))) {
249
+ return false;
250
+ }
251
+ if (applies.provider_ids.length > 0 && (!providerId || !applies.provider_ids.includes(providerId))) {
252
+ return false;
253
+ }
254
+ if (applies.provider_kinds.length > 0 && !applies.provider_kinds.some((kind) => providerKinds.includes(kind))) {
255
+ return false;
256
+ }
257
+ if (applies.outputs.length > 0 && !applies.outputs.some((outputId) => selectedOutputs.includes(outputId))) {
258
+ return false;
259
+ }
260
+ if (applies.integration_categories.length > 0 && !applies.integration_categories.some((category) => integrationCategories.includes(category))) {
261
+ return false;
262
+ }
263
+ if (applies.query_families.length > 0 && (!queryFamily || !applies.query_families.includes(queryFamily))) {
264
+ return false;
265
+ }
266
+ const activation = preset.activation || normalizePresetActivation();
267
+ if (activation.task_classes.length > 0 && (!taskClass || !activation.task_classes.includes(taskClass))) {
268
+ return false;
269
+ }
270
+ if (activation.provider_ids.length > 0 && (!providerId || !activation.provider_ids.includes(providerId))) {
271
+ return false;
272
+ }
273
+ if (activation.provider_kinds.length > 0 && !activation.provider_kinds.some((kind) => providerKinds.includes(kind))) {
274
+ return false;
275
+ }
276
+ if (activation.outputs.length > 0 && !activation.outputs.some((outputId) => selectedOutputs.includes(outputId))) {
277
+ return false;
278
+ }
279
+ if (activation.integration_categories.length > 0 && !activation.integration_categories.some((category) => integrationCategories.includes(category))) {
280
+ return false;
281
+ }
282
+ if (activation.query_families.length > 0 && (!queryFamily || !activation.query_families.includes(queryFamily))) {
283
+ return false;
284
+ }
285
+ if (activation.manual_only && selectors.manual_context !== true) {
286
+ return false;
287
+ }
288
+ return true;
289
+ }
290
+
291
+ export function summarizeWorkflowPreset(preset, selectors = {}) {
292
+ const escalateCategories = preset.review_policy?.escalate_categories || [];
293
+ const verificationImpact = stableOrderedUnion([
294
+ ...(preset.verification_policy?.required || []),
295
+ ...(preset.verification_policy?.recommended || [])
296
+ ]);
297
+ return {
298
+ id: preset.id,
299
+ label: preset.label,
300
+ kind: preset.kind,
301
+ provider_id: preset.provider?.id || preset.provenance?.provider_id || null,
302
+ provider_kind: preset.provider?.kind || null,
303
+ adoption_state: preset.adoption_state,
304
+ active: preset.active !== false,
305
+ activation: preset.activation || normalizePresetActivation(),
306
+ priority: preset.priority ?? preset.source_priority,
307
+ applies_to: preset.applies_to,
308
+ recommended_task_mode: preset.recommended_task_mode || null,
309
+ review_class: preset.review_class,
310
+ review_escalation_categories: escalateCategories,
311
+ verification_impact: verificationImpact,
312
+ multi_agent_hint_impact: {
313
+ allowed: preset.multi_agent_policy?.allowed,
314
+ default_strategy: preset.multi_agent_policy?.default_strategy || null
315
+ },
316
+ indirect_maintained_impact: Boolean(
317
+ escalateCategories.includes("maintained_boundary") ||
318
+ (preset.applies_to?.outputs || []).some((outputId) => String(outputId).startsWith("maintained"))
319
+ ),
320
+ active_for_context: presetAppliesToContext(preset, selectors),
321
+ provenance: preset.provenance || null
322
+ };
323
+ }
324
+
325
+ export function workflowPresetComparableShape(preset) {
326
+ return {
327
+ recommended_task_mode: preset?.recommended_task_mode || null,
328
+ preferred_queries: stableOrderedUnion(preset?.preferred_queries || []),
329
+ artifact_load_order: stableOrderedUnion(preset?.artifact_load_order || []),
330
+ review_policy: {
331
+ block_on: stableSortedStrings(preset?.review_policy?.block_on || []),
332
+ escalate_categories: stableSortedStrings(preset?.review_policy?.escalate_categories || [])
333
+ },
334
+ verification_policy: {
335
+ required: stableOrderedUnion(preset?.verification_policy?.required || []),
336
+ recommended: stableOrderedUnion(preset?.verification_policy?.recommended || []),
337
+ require_output_specific_checks: Boolean(preset?.verification_policy?.require_output_specific_checks),
338
+ require_maintained_checks_when_seams_affected: Boolean(preset?.verification_policy?.require_maintained_checks_when_seams_affected)
339
+ },
340
+ multi_agent_policy: {
341
+ allowed: preset?.multi_agent_policy?.allowed,
342
+ default_strategy: preset?.multi_agent_policy?.default_strategy || null
343
+ },
344
+ handoff_defaults: {
345
+ required_fields: stableOrderedUnion(preset?.handoff_defaults?.required_fields || [])
346
+ },
347
+ tool_hints: preset?.tool_hints || {},
348
+ applies_to: normalizePresetAppliesTo(preset?.applies_to || {}),
349
+ review_class: preset?.review_class || workflowPresetReviewClass(preset || {})
350
+ };
351
+ }
352
+
353
+ export function workflowPresetFingerprint(preset) {
354
+ return JSON.stringify(workflowPresetComparableShape(preset));
355
+ }
356
+
357
+ export function workflowPresetDerivedSource(derivedFrom = null) {
358
+ if (!derivedFrom || typeof derivedFrom !== "object") return null;
359
+ return {
360
+ provider_id: derivedFrom.provider_id || null,
361
+ provider_preset_id: derivedFrom.provider_preset_id || derivedFrom.preset_id || derivedFrom.id || null,
362
+ source_path: derivedFrom.source_path || null,
363
+ source_fingerprint: derivedFrom.source_fingerprint || null
364
+ };
365
+ }
366
+
367
+ export function providerPresetMatchKey(preset) {
368
+ return `${preset?.provider?.id || preset?.provenance?.provider_id || "unknown"}::${preset?.id || "unknown"}`;
369
+ }
370
+
371
+ export function findDerivedTeamPreset(teamPresets = [], providerPreset) {
372
+ const matchKey = providerPresetMatchKey(providerPreset);
373
+ return teamPresets.find((preset) => {
374
+ const derived = workflowPresetDerivedSource(preset?.derived_from);
375
+ if (!derived) return false;
376
+ const derivedKey = `${derived.provider_id || "unknown"}::${derived.provider_preset_id || "unknown"}`;
377
+ return derivedKey === matchKey;
378
+ }) || null;
379
+ }
380
+
381
+ export function diffArrayValues(current = [], previous = []) {
382
+ const normalizedCurrent = stableOrderedUnion(current);
383
+ const normalizedPrevious = stableOrderedUnion(previous);
384
+ return {
385
+ current: normalizedCurrent,
386
+ previous: normalizedPrevious,
387
+ added: normalizedCurrent.filter((entry) => !normalizedPrevious.includes(entry)),
388
+ removed: normalizedPrevious.filter((entry) => !normalizedCurrent.includes(entry))
389
+ };
390
+ }
391
+
392
+ export function diffObjectKeys(current = {}, previous = {}) {
393
+ const keys = stableSortedStrings([...Object.keys(current || {}), ...Object.keys(previous || {})]);
394
+ const changed = {};
395
+ for (const key of keys) {
396
+ const currentValue = current?.[key];
397
+ const previousValue = previous?.[key];
398
+ if (JSON.stringify(currentValue) !== JSON.stringify(previousValue)) {
399
+ changed[key] = {
400
+ current: currentValue,
401
+ previous: previousValue
402
+ };
403
+ }
404
+ }
405
+ return changed;
406
+ }
407
+
408
+ export function workflowPresetChangedFields(currentPreset, previousPreset) {
409
+ const current = workflowPresetComparableShape(currentPreset);
410
+ const previous = workflowPresetComparableShape(previousPreset);
411
+ const changedFields = [];
412
+ for (const field of [
413
+ "recommended_task_mode",
414
+ "preferred_queries",
415
+ "artifact_load_order",
416
+ "review_policy",
417
+ "verification_policy",
418
+ "multi_agent_policy",
419
+ "handoff_defaults",
420
+ "tool_hints",
421
+ "applies_to",
422
+ "review_class"
423
+ ]) {
424
+ if (JSON.stringify(current[field]) !== JSON.stringify(previous[field])) {
425
+ changedFields.push(field);
426
+ }
427
+ }
428
+ return changedFields;
429
+ }
430
+
431
+ export function workflowPresetDeltaPayload(currentPreset, previousPreset) {
432
+ const current = workflowPresetComparableShape(currentPreset);
433
+ const previous = workflowPresetComparableShape(previousPreset);
434
+ return {
435
+ changed_fields: workflowPresetChangedFields(currentPreset, previousPreset),
436
+ review_class_delta: {
437
+ current: current.review_class,
438
+ previous: previous.review_class
439
+ },
440
+ recommended_task_mode_delta: {
441
+ current: current.recommended_task_mode,
442
+ previous: previous.recommended_task_mode
443
+ },
444
+ preferred_queries_delta: diffArrayValues(current.preferred_queries, previous.preferred_queries),
445
+ review_policy_delta: {
446
+ block_on: diffArrayValues(current.review_policy.block_on, previous.review_policy.block_on),
447
+ escalate_categories: diffArrayValues(current.review_policy.escalate_categories, previous.review_policy.escalate_categories)
448
+ },
449
+ verification_policy_delta: {
450
+ required: diffArrayValues(current.verification_policy.required, previous.verification_policy.required),
451
+ recommended: diffArrayValues(current.verification_policy.recommended, previous.verification_policy.recommended),
452
+ flags: diffObjectKeys({
453
+ require_output_specific_checks: current.verification_policy.require_output_specific_checks,
454
+ require_maintained_checks_when_seams_affected: current.verification_policy.require_maintained_checks_when_seams_affected
455
+ }, {
456
+ require_output_specific_checks: previous.verification_policy.require_output_specific_checks,
457
+ require_maintained_checks_when_seams_affected: previous.verification_policy.require_maintained_checks_when_seams_affected
458
+ })
459
+ },
460
+ multi_agent_policy_delta: diffObjectKeys(current.multi_agent_policy, previous.multi_agent_policy),
461
+ tool_hints_delta: diffObjectKeys(current.tool_hints, previous.tool_hints)
462
+ };
463
+ }
464
+
465
+ export function refreshBaselineForProviderPreset(preset) {
466
+ const baseline = preset?.refresh_baseline;
467
+ if (!baseline || typeof baseline !== "object") return null;
468
+ return normalizeWorkflowPresetArtifact({
469
+ ...baseline,
470
+ kind: "provider_workflow_preset",
471
+ provider: {
472
+ ...(preset?.provider || {}),
473
+ ...(baseline.provider || {})
474
+ }
475
+ }, {
476
+ kind: "provider_workflow_preset",
477
+ provenance: {
478
+ source_path: baseline.source_path || null,
479
+ provider_id: baseline?.provider?.id || preset?.provider?.id || null
480
+ }
481
+ });
482
+ }
483
+
484
+ export function workflowPresetRequiresFreshReview(changeStatus, currentPreset, delta) {
485
+ if (changeStatus === "orphaned_customization") return true;
486
+ if (changeStatus === "locally_customized") return true;
487
+ if (changeStatus !== "changed") return false;
488
+ return Boolean(
489
+ (delta.changed_fields || []).some((field) => [
490
+ "review_policy",
491
+ "verification_policy",
492
+ "multi_agent_policy",
493
+ "recommended_task_mode",
494
+ "review_class"
495
+ ].includes(field))
496
+ );
497
+ }
498
+
499
+ export function defaultLocalWorkflowPresetPath(providerId, presetId) {
500
+ return `workflow-presets/provider.${providerId}.${presetId}.json`;
501
+ }
502
+
503
+ export function customizationStatusForPreset(currentPreset, derivedTeamPreset = null) {
504
+ const providerId = currentPreset?.provider?.id || currentPreset?.provenance?.provider_id || null;
505
+ const recommendedLocalPath = defaultLocalWorkflowPresetPath(providerId || "provider", currentPreset?.id || "preset");
506
+ const sourceFingerprint = currentPreset ? workflowPresetFingerprint(currentPreset) : null;
507
+ const derivedSource = workflowPresetDerivedSource(derivedTeamPreset?.derived_from);
508
+ const fingerprintMatches = Boolean(
509
+ derivedTeamPreset &&
510
+ derivedSource?.source_fingerprint &&
511
+ sourceFingerprint &&
512
+ derivedSource.source_fingerprint === sourceFingerprint
513
+ );
514
+ let status = null;
515
+ if (currentPreset?.adoption_state === "customize") {
516
+ status = !derivedTeamPreset
517
+ ? "ready_to_customize"
518
+ : fingerprintMatches
519
+ ? "customization_present"
520
+ : "customization_stale";
521
+ } else if (derivedTeamPreset) {
522
+ status = fingerprintMatches ? "customization_present" : "customization_stale";
523
+ }
524
+ return {
525
+ status,
526
+ derived_preset_exists: Boolean(derivedTeamPreset),
527
+ fingerprint_matches_current_source: fingerprintMatches,
528
+ recommended_local_path: recommendedLocalPath,
529
+ source_fingerprint: sourceFingerprint
530
+ };
531
+ }
532
+
533
+ export function recommendedCustomizationAction(currentPreset, customizationStatus) {
534
+ if (!customizationStatus) return null;
535
+ if (customizationStatus.status === "customization_orphaned") {
536
+ return "remove_or_replace_orphaned_customization";
537
+ }
538
+ if (currentPreset?.adoption_state === "customize" && customizationStatus.status === "ready_to_customize") {
539
+ return "create_local_customization";
540
+ }
541
+ if (customizationStatus.status === "customization_stale") {
542
+ return "refresh_local_customization";
543
+ }
544
+ if (customizationStatus.status === "customization_present") {
545
+ return "review_existing_customization";
546
+ }
547
+ return null;
548
+ }
549
+
550
+ export function providerManifestWorkflowPresetDeclarations(providerManifests = [], providerPresets = []) {
551
+ return providerManifests.flatMap((manifest) =>
552
+ (manifest.exports?.workflow_presets || []).map((entry) => {
553
+ const importedPreset = providerPresets.find((preset) =>
554
+ (preset.provider?.id || preset.provenance?.provider_id) === manifest.provider.id &&
555
+ preset.id === entry.id
556
+ ) || null;
557
+ return {
558
+ provider_id: manifest.provider.id,
559
+ provider_kind: manifest.provider.kind || null,
560
+ manifest_path: manifest.file_path || null,
561
+ workflow_core_version: manifest.workflow_core_version || null,
562
+ review_default: manifest.review_defaults?.workflow_presets || "review_required",
563
+ declaration_id: entry.id,
564
+ label: entry.label || entry.id,
565
+ path: entry.path || null,
566
+ applies_to: entry.applies_to,
567
+ proof_ref: entry.proof_ref || null,
568
+ valid: entry.valid,
569
+ validation: entry.validation,
570
+ imported: Boolean(importedPreset),
571
+ imported_preset_id: importedPreset?.id || null,
572
+ import_status: importedPreset ? "imported" : "not_imported"
573
+ };
574
+ })
575
+ ).sort((a, b) => `${a.provider_id}:${a.declaration_id}`.localeCompare(`${b.provider_id}:${b.declaration_id}`));
576
+ }
577
+
578
+ export function skippedPresetReason(preset, selectors = {}) {
579
+ if (preset.active === false) return "inactive";
580
+ const applies = preset.applies_to || {};
581
+ const activation = preset.activation || normalizePresetActivation();
582
+ const selectedOutputs = stableSortedStrings(selectors.outputs || []);
583
+ const providerId = selectors.provider_id || null;
584
+ const providerKinds = stableSortedStrings(selectors.provider_kinds || []);
585
+ const taskClass = selectors.task_class || selectors.mode || null;
586
+ const queryFamily = selectors.query_family || null;
587
+ const integrationCategories = stableSortedStrings(selectors.integration_categories || []);
588
+ if (applies.task_classes.length > 0 && (!taskClass || !applies.task_classes.includes(taskClass))) return "applies_to_task_class_mismatch";
589
+ if (applies.provider_ids.length > 0 && (!providerId || !applies.provider_ids.includes(providerId))) return "applies_to_provider_mismatch";
590
+ if (applies.provider_kinds.length > 0 && !applies.provider_kinds.some((kind) => providerKinds.includes(kind))) return "applies_to_provider_kind_mismatch";
591
+ if (applies.outputs.length > 0 && !applies.outputs.some((outputId) => selectedOutputs.includes(outputId))) return "applies_to_output_mismatch";
592
+ if (applies.integration_categories.length > 0 && !applies.integration_categories.some((category) => integrationCategories.includes(category))) return "applies_to_integration_category_mismatch";
593
+ if (applies.query_families.length > 0 && (!queryFamily || !applies.query_families.includes(queryFamily))) return "applies_to_query_family_mismatch";
594
+ if (activation.task_classes.length > 0 && (!taskClass || !activation.task_classes.includes(taskClass))) return "activation_task_class_mismatch";
595
+ if (activation.provider_ids.length > 0 && (!providerId || !activation.provider_ids.includes(providerId))) return "activation_provider_mismatch";
596
+ if (activation.provider_kinds.length > 0 && !activation.provider_kinds.some((kind) => providerKinds.includes(kind))) return "activation_provider_kind_mismatch";
597
+ if (activation.outputs.length > 0 && !activation.outputs.some((outputId) => selectedOutputs.includes(outputId))) return "activation_output_mismatch";
598
+ if (activation.integration_categories.length > 0 && !activation.integration_categories.some((category) => integrationCategories.includes(category))) return "activation_integration_category_mismatch";
599
+ if (activation.query_families.length > 0 && (!queryFamily || !activation.query_families.includes(queryFamily))) return "activation_query_family_mismatch";
600
+ if (activation.manual_only && selectors.manual_context !== true) return "manual_only";
601
+ return "not_applicable";
602
+ }
603
+
604
+ export function customizationTemplateFromProviderPreset(currentPreset, workspaceRoot = null) {
605
+ const providerId = currentPreset?.provider?.id || currentPreset?.provenance?.provider_id || "provider";
606
+ const presetId = currentPreset?.id || "preset";
607
+ const resolvedWorkspaceRoot = workspaceRoot ? path.resolve(workspaceRoot) : null;
608
+ const sourcePath = currentPreset?.file_path
609
+ ? resolvedWorkspaceRoot
610
+ ? path.relative(resolvedWorkspaceRoot, currentPreset.file_path)
611
+ : currentPreset.file_path
612
+ : currentPreset?.provenance?.source_path || null;
613
+ const template = {
614
+ id: `provider.${providerId}.${presetId}`,
615
+ label: `Customized ${currentPreset?.label || presetId}`,
616
+ kind: "team_workflow_preset",
617
+ adoption_state: "accept",
618
+ derived_from: {
619
+ provider_id: providerId,
620
+ provider_preset_id: presetId,
621
+ source_path: sourcePath,
622
+ source_fingerprint: workflowPresetFingerprint(currentPreset)
623
+ },
624
+ applies_to: normalizePresetAppliesTo(currentPreset?.applies_to || {}),
625
+ recommended_task_mode: currentPreset?.recommended_task_mode || null,
626
+ preferred_queries: stableOrderedUnion(currentPreset?.preferred_queries || []),
627
+ artifact_load_order: stableOrderedUnion(currentPreset?.artifact_load_order || []),
628
+ review_policy: {
629
+ block_on: stableSortedStrings(currentPreset?.review_policy?.block_on || []),
630
+ escalate_categories: stableSortedStrings(currentPreset?.review_policy?.escalate_categories || [])
631
+ },
632
+ verification_policy: {
633
+ required: stableOrderedUnion(currentPreset?.verification_policy?.required || []),
634
+ recommended: stableOrderedUnion(currentPreset?.verification_policy?.recommended || []),
635
+ require_output_specific_checks: Boolean(currentPreset?.verification_policy?.require_output_specific_checks),
636
+ require_maintained_checks_when_seams_affected: Boolean(currentPreset?.verification_policy?.require_maintained_checks_when_seams_affected)
637
+ },
638
+ multi_agent_policy: {
639
+ allowed: currentPreset?.multi_agent_policy?.allowed,
640
+ default_strategy: currentPreset?.multi_agent_policy?.default_strategy || null
641
+ },
642
+ handoff_defaults: {
643
+ required_fields: stableOrderedUnion(currentPreset?.handoff_defaults?.required_fields || [])
644
+ },
645
+ tool_hints: currentPreset?.tool_hints || {}
646
+ };
647
+ return template;
648
+ }
649
+
650
+ export function buildImportPlanNextAction(defaultNextAction, workflowPresetState = null) {
651
+ const missingDeclaredCount = workflowPresetState?.provider_manifest_summary?.missing_declared_workflow_preset_count || 0;
652
+ if (missingDeclaredCount > 0) {
653
+ return {
654
+ kind: "import_declared_workflow_preset",
655
+ label: "Import declared workflow presets",
656
+ reason: `${missingDeclaredCount} manifest-declared workflow preset(s) are available but not yet imported into candidate space.`
657
+ };
658
+ }
659
+ const surfaces = workflowPresetState?.workflow_preset_surfaces || [];
660
+ const refreshSurface = surfaces.find((surface) => surface.recommended_customization_action === "refresh_local_customization");
661
+ if (refreshSurface) {
662
+ return {
663
+ kind: "refresh_workflow_preset_customization",
664
+ label: "Refresh workflow preset customization",
665
+ reason: `Provider workflow preset ${refreshSurface.id} has a stale local customization that should be refreshed before adoption proceeds.`
666
+ };
667
+ }
668
+ const createSurface = surfaces.find((surface) => surface.recommended_customization_action === "create_local_customization");
669
+ if (createSurface) {
670
+ return {
671
+ kind: "customize_workflow_preset",
672
+ label: "Create local workflow preset customization",
673
+ reason: `Provider workflow preset ${createSurface.id} is marked customize and needs a derived local team preset.`
674
+ };
675
+ }
676
+ return defaultNextAction || null;
677
+ }