@topogram/cli 0.3.34

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 (257) hide show
  1. package/ARCHITECTURE.md +67 -0
  2. package/CHANGELOG.md +240 -0
  3. package/README.md +223 -0
  4. package/package.json +51 -0
  5. package/src/adoption/index.js +3 -0
  6. package/src/adoption/plan.js +702 -0
  7. package/src/adoption/reporting.js +464 -0
  8. package/src/adoption/review-groups.js +313 -0
  9. package/src/agent-ops/query-builders.js +5012 -0
  10. package/src/archive/archive.js +141 -0
  11. package/src/archive/compact.js +26 -0
  12. package/src/archive/jsonl.js +70 -0
  13. package/src/archive/resolver-bridge.js +82 -0
  14. package/src/archive/schema.js +87 -0
  15. package/src/archive/unarchive.js +108 -0
  16. package/src/catalog.js +752 -0
  17. package/src/cli/catalog-alias.js +166 -0
  18. package/src/cli.js +9738 -0
  19. package/src/component-behavior.js +173 -0
  20. package/src/example-implementation.js +91 -0
  21. package/src/format.js +19 -0
  22. package/src/generator/adapters.d.ts +4 -0
  23. package/src/generator/adapters.js +325 -0
  24. package/src/generator/api.d.ts +1 -0
  25. package/src/generator/api.js +1196 -0
  26. package/src/generator/check.js +355 -0
  27. package/src/generator/component-conformance.js +767 -0
  28. package/src/generator/components.js +39 -0
  29. package/src/generator/context/bundle.js +291 -0
  30. package/src/generator/context/diff.js +256 -0
  31. package/src/generator/context/digest.js +182 -0
  32. package/src/generator/context/domain-coverage.js +94 -0
  33. package/src/generator/context/domain-page.js +137 -0
  34. package/src/generator/context/index.js +42 -0
  35. package/src/generator/context/report.js +121 -0
  36. package/src/generator/context/shared.js +1397 -0
  37. package/src/generator/context/slice.js +703 -0
  38. package/src/generator/context/task-mode.js +466 -0
  39. package/src/generator/docs.js +327 -0
  40. package/src/generator/index.js +161 -0
  41. package/src/generator/native/parity-bundle.js +311 -0
  42. package/src/generator/output.js +300 -0
  43. package/src/generator/registry.js +482 -0
  44. package/src/generator/runtime/app-bundle.js +456 -0
  45. package/src/generator/runtime/bundle-shared.js +166 -0
  46. package/src/generator/runtime/compile-check.js +163 -0
  47. package/src/generator/runtime/deployment.js +287 -0
  48. package/src/generator/runtime/environment.js +635 -0
  49. package/src/generator/runtime/index.js +32 -0
  50. package/src/generator/runtime/runtime-check.js +554 -0
  51. package/src/generator/runtime/shared.js +515 -0
  52. package/src/generator/runtime/smoke.js +219 -0
  53. package/src/generator/schema.js +204 -0
  54. package/src/generator/sdlc/board.js +66 -0
  55. package/src/generator/sdlc/doc-page.js +53 -0
  56. package/src/generator/sdlc/index.js +23 -0
  57. package/src/generator/sdlc/release-notes.js +62 -0
  58. package/src/generator/sdlc/traceability-matrix.js +65 -0
  59. package/src/generator/shared.js +29 -0
  60. package/src/generator/surfaces/contracts.js +146 -0
  61. package/src/generator/surfaces/databases/contract.js +40 -0
  62. package/src/generator/surfaces/databases/index.js +84 -0
  63. package/src/generator/surfaces/databases/lifecycle-shared.d.ts +1 -0
  64. package/src/generator/surfaces/databases/lifecycle-shared.js +612 -0
  65. package/src/generator/surfaces/databases/migration-plan.js +281 -0
  66. package/src/generator/surfaces/databases/postgres/capabilities.js +14 -0
  67. package/src/generator/surfaces/databases/postgres/drizzle.js +99 -0
  68. package/src/generator/surfaces/databases/postgres/index.js +9 -0
  69. package/src/generator/surfaces/databases/postgres/lifecycle.js +16 -0
  70. package/src/generator/surfaces/databases/postgres/prisma.js +159 -0
  71. package/src/generator/surfaces/databases/postgres/sql-migration.js +102 -0
  72. package/src/generator/surfaces/databases/postgres/sql-schema.js +34 -0
  73. package/src/generator/surfaces/databases/shared.d.ts +1 -0
  74. package/src/generator/surfaces/databases/shared.js +350 -0
  75. package/src/generator/surfaces/databases/snapshot.js +96 -0
  76. package/src/generator/surfaces/databases/sqlite/capabilities.js +14 -0
  77. package/src/generator/surfaces/databases/sqlite/index.js +8 -0
  78. package/src/generator/surfaces/databases/sqlite/lifecycle.js +16 -0
  79. package/src/generator/surfaces/databases/sqlite/prisma.js +143 -0
  80. package/src/generator/surfaces/databases/sqlite/sql-migration.js +65 -0
  81. package/src/generator/surfaces/databases/sqlite/sql-schema.js +27 -0
  82. package/src/generator/surfaces/index.js +25 -0
  83. package/src/generator/surfaces/native/swiftui-app.js +38 -0
  84. package/src/generator/surfaces/native/swiftui-templates/Package.swift.txt +20 -0
  85. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +26 -0
  86. package/src/generator/surfaces/native/swiftui-templates/runtime/DynamicScreens.swift +682 -0
  87. package/src/generator/surfaces/native/swiftui-templates/runtime/TodoAPIClient.swift +156 -0
  88. package/src/generator/surfaces/native/swiftui-templates/runtime/TodoSwiftUIApp.swift +44 -0
  89. package/src/generator/surfaces/native/swiftui-templates/runtime/Visibility.swift +183 -0
  90. package/src/generator/surfaces/services/express.d.ts +1 -0
  91. package/src/generator/surfaces/services/express.js +766 -0
  92. package/src/generator/surfaces/services/hono.d.ts +1 -0
  93. package/src/generator/surfaces/services/hono.js +204 -0
  94. package/src/generator/surfaces/services/index.js +42 -0
  95. package/src/generator/surfaces/services/persistence-wiring.js +240 -0
  96. package/src/generator/surfaces/services/runtime-helpers.js +631 -0
  97. package/src/generator/surfaces/services/server-contract.js +80 -0
  98. package/src/generator/surfaces/services/stateless.d.ts +1 -0
  99. package/src/generator/surfaces/services/stateless.js +97 -0
  100. package/src/generator/surfaces/shared.js +64 -0
  101. package/src/generator/surfaces/web/api-client.js +1 -0
  102. package/src/generator/surfaces/web/forms.js +1 -0
  103. package/src/generator/surfaces/web/index.d.ts +2 -0
  104. package/src/generator/surfaces/web/index.js +53 -0
  105. package/src/generator/surfaces/web/react-components.js +248 -0
  106. package/src/generator/surfaces/web/react.js +538 -0
  107. package/src/generator/surfaces/web/routes.js +1 -0
  108. package/src/generator/surfaces/web/screens.js +1 -0
  109. package/src/generator/surfaces/web/shared.js +369 -0
  110. package/src/generator/surfaces/web/sveltekit-actions.js +28 -0
  111. package/src/generator/surfaces/web/sveltekit-components.js +234 -0
  112. package/src/generator/surfaces/web/sveltekit.js +426 -0
  113. package/src/generator/surfaces/web/ui-web-contract.js +65 -0
  114. package/src/generator/surfaces/web/vanilla.js +239 -0
  115. package/src/generator/verification.js +84 -0
  116. package/src/generator.js +1 -0
  117. package/src/import/core/context.js +52 -0
  118. package/src/import/core/contracts.js +23 -0
  119. package/src/import/core/registry.js +81 -0
  120. package/src/import/core/runner.js +646 -0
  121. package/src/import/core/shared.js +910 -0
  122. package/src/import/enrichers/auth-session.js +18 -0
  123. package/src/import/enrichers/django-rest.js +226 -0
  124. package/src/import/enrichers/doc-linking.js +20 -0
  125. package/src/import/enrichers/rails-controllers.js +246 -0
  126. package/src/import/enrichers/rails-models.js +130 -0
  127. package/src/import/enrichers/workflow-target-state.js +10 -0
  128. package/src/import/extractors/api/aspnet-core.js +304 -0
  129. package/src/import/extractors/api/django-routes.js +318 -0
  130. package/src/import/extractors/api/express.js +154 -0
  131. package/src/import/extractors/api/fastify.js +371 -0
  132. package/src/import/extractors/api/flutter-dio.js +135 -0
  133. package/src/import/extractors/api/generic-route-fallback.js +90 -0
  134. package/src/import/extractors/api/graphql-code-first.js +565 -0
  135. package/src/import/extractors/api/graphql-sdl.js +309 -0
  136. package/src/import/extractors/api/jaxrs.js +303 -0
  137. package/src/import/extractors/api/micronaut.js +213 -0
  138. package/src/import/extractors/api/next-route.js +50 -0
  139. package/src/import/extractors/api/next-server-action.js +51 -0
  140. package/src/import/extractors/api/nextauth.js +52 -0
  141. package/src/import/extractors/api/openapi-code.js +242 -0
  142. package/src/import/extractors/api/openapi.js +232 -0
  143. package/src/import/extractors/api/rails-routes.js +230 -0
  144. package/src/import/extractors/api/react-native-repository.js +128 -0
  145. package/src/import/extractors/api/retrofit.js +103 -0
  146. package/src/import/extractors/api/spring-web.js +372 -0
  147. package/src/import/extractors/api/swift-webapi.js +116 -0
  148. package/src/import/extractors/api/trpc.js +212 -0
  149. package/src/import/extractors/db/django-models.js +232 -0
  150. package/src/import/extractors/db/dotnet-models.js +93 -0
  151. package/src/import/extractors/db/drizzle.js +242 -0
  152. package/src/import/extractors/db/ef-core.js +221 -0
  153. package/src/import/extractors/db/flutter-entities.js +120 -0
  154. package/src/import/extractors/db/jpa.js +120 -0
  155. package/src/import/extractors/db/liquibase.js +180 -0
  156. package/src/import/extractors/db/mybatis-xml.js +145 -0
  157. package/src/import/extractors/db/prisma.js +185 -0
  158. package/src/import/extractors/db/rails-schema.js +175 -0
  159. package/src/import/extractors/db/react-native-entities.js +95 -0
  160. package/src/import/extractors/db/room.js +193 -0
  161. package/src/import/extractors/db/snapshot.js +112 -0
  162. package/src/import/extractors/db/sql.js +180 -0
  163. package/src/import/extractors/db/swiftdata.js +137 -0
  164. package/src/import/extractors/ui/android-compose.js +230 -0
  165. package/src/import/extractors/ui/backend-only.js +70 -0
  166. package/src/import/extractors/ui/blazor.js +227 -0
  167. package/src/import/extractors/ui/flutter-screens.js +152 -0
  168. package/src/import/extractors/ui/maui-xaml.js +135 -0
  169. package/src/import/extractors/ui/next-app-router.js +83 -0
  170. package/src/import/extractors/ui/next-pages-router.js +141 -0
  171. package/src/import/extractors/ui/razor-pages.js +181 -0
  172. package/src/import/extractors/ui/react-native-screens.js +166 -0
  173. package/src/import/extractors/ui/react-router.js +139 -0
  174. package/src/import/extractors/ui/sveltekit.js +123 -0
  175. package/src/import/extractors/ui/swiftui.js +193 -0
  176. package/src/import/extractors/ui/uikit.js +175 -0
  177. package/src/import/extractors/verification/generic.js +290 -0
  178. package/src/import/extractors/workflows/generic.js +137 -0
  179. package/src/import/index.js +7 -0
  180. package/src/import/provenance.js +158 -0
  181. package/src/new-project.js +2107 -0
  182. package/src/parser.js +439 -0
  183. package/src/policy/review-boundaries.js +165 -0
  184. package/src/project-config.js +535 -0
  185. package/src/proofs/backend-parity.js +19 -0
  186. package/src/proofs/contract-audit.js +220 -0
  187. package/src/proofs/ios-parity.js +7 -0
  188. package/src/proofs/issues-parity.js +10 -0
  189. package/src/proofs/web-parity.js +50 -0
  190. package/src/realization/api/build-api-realization.js +5 -0
  191. package/src/realization/api/index.js +1 -0
  192. package/src/realization/backend/build-backend-runtime-realization.js +82 -0
  193. package/src/realization/backend/index.d.ts +1 -0
  194. package/src/realization/backend/index.js +4 -0
  195. package/src/realization/db/build-db-realization.js +17 -0
  196. package/src/realization/db/index.js +3 -0
  197. package/src/realization/db/migration-plan.js +5 -0
  198. package/src/realization/db/snapshot.js +5 -0
  199. package/src/realization/ui/build-ui-shared-realization.js +305 -0
  200. package/src/realization/ui/build-web-realization.js +189 -0
  201. package/src/realization/ui/index.js +2 -0
  202. package/src/reconcile/docs.js +280 -0
  203. package/src/reconcile/index.js +3 -0
  204. package/src/reconcile/journeys.js +441 -0
  205. package/src/resolver/docs.js +1 -0
  206. package/src/resolver/enrich/acceptance-criterion.js +14 -0
  207. package/src/resolver/enrich/bug.js +12 -0
  208. package/src/resolver/enrich/component.js +2 -0
  209. package/src/resolver/enrich/index.js +1 -0
  210. package/src/resolver/enrich/pitch.js +18 -0
  211. package/src/resolver/enrich/requirement.js +20 -0
  212. package/src/resolver/enrich/task.js +16 -0
  213. package/src/resolver/expressions.js +1 -0
  214. package/src/resolver/index.js +2422 -0
  215. package/src/resolver/normalize.js +1 -0
  216. package/src/resolver.js +1 -0
  217. package/src/sdlc/adopt.js +65 -0
  218. package/src/sdlc/check.js +86 -0
  219. package/src/sdlc/dod/acceptance-criterion.js +22 -0
  220. package/src/sdlc/dod/bug.js +26 -0
  221. package/src/sdlc/dod/document.js +23 -0
  222. package/src/sdlc/dod/index.js +25 -0
  223. package/src/sdlc/dod/pitch.js +23 -0
  224. package/src/sdlc/dod/requirement.js +34 -0
  225. package/src/sdlc/dod/task.js +39 -0
  226. package/src/sdlc/explain.js +116 -0
  227. package/src/sdlc/history.js +80 -0
  228. package/src/sdlc/paths.js +11 -0
  229. package/src/sdlc/release.js +106 -0
  230. package/src/sdlc/scaffold.js +89 -0
  231. package/src/sdlc/status-filter.js +54 -0
  232. package/src/sdlc/transition.js +112 -0
  233. package/src/sdlc/transitions/acceptance-criterion.js +28 -0
  234. package/src/sdlc/transitions/bug.js +31 -0
  235. package/src/sdlc/transitions/document.js +29 -0
  236. package/src/sdlc/transitions/index.js +56 -0
  237. package/src/sdlc/transitions/pitch.js +34 -0
  238. package/src/sdlc/transitions/requirement.js +31 -0
  239. package/src/sdlc/transitions/task.js +34 -0
  240. package/src/template-trust.js +597 -0
  241. package/src/validator/expressions.js +1 -0
  242. package/src/validator/index.js +3424 -0
  243. package/src/validator/kinds.js +346 -0
  244. package/src/validator/per-kind/acceptance-criterion.js +91 -0
  245. package/src/validator/per-kind/bug.js +77 -0
  246. package/src/validator/per-kind/component.js +274 -0
  247. package/src/validator/per-kind/domain.js +205 -0
  248. package/src/validator/per-kind/pitch.js +101 -0
  249. package/src/validator/per-kind/requirement.js +75 -0
  250. package/src/validator/per-kind/task.js +96 -0
  251. package/src/validator/registry.js +1 -0
  252. package/src/validator/utils.js +12 -0
  253. package/src/validator.js +1 -0
  254. package/src/workflows.js +7597 -0
  255. package/src/workspace-docs.js +265 -0
  256. package/template-helpers/react.js +5 -0
  257. package/template-helpers/sveltekit.js +5 -0
@@ -0,0 +1,464 @@
1
+ import path from "node:path";
2
+
3
+ import { stableStringify } from "../format.js";
4
+
5
+ function ensureTrailingNewline(value) {
6
+ return value.endsWith("\n") ? value : `${value}\n`;
7
+ }
8
+
9
+ function normalizeReportPath(value) {
10
+ return String(value || "").replaceAll("\\", "/").replaceAll(path.sep, "/");
11
+ }
12
+
13
+ function formatClaimValueInline(value) {
14
+ return value == null ? "_dynamic_" : `\`${value}\``;
15
+ }
16
+
17
+ function formatAuthClaimHintInline(hint) {
18
+ return `claim \`${hint.claim}\` = ${formatClaimValueInline(hint.claim_value)} (${hint.confidence})`;
19
+ }
20
+
21
+ function formatAuthPermissionHintInline(hint) {
22
+ return `permission \`${hint.permission}\` (${hint.confidence})`;
23
+ }
24
+
25
+ function formatClosureInline(value) {
26
+ return value || "unresolved";
27
+ }
28
+
29
+ function formatRoleFollowupInline(item) {
30
+ if (!item?.followup_action) {
31
+ return "";
32
+ }
33
+ if (item.followup_action === "link_role_to_docs" && (item.followup_doc_ids || []).length > 0) {
34
+ return ` -> link role to docs ${(item.followup_doc_ids || []).map((entry) => `\`${entry}\``).join(", ")}`;
35
+ }
36
+ if (item.followup_action === "promote_role") {
37
+ return " -> promote role";
38
+ }
39
+ return " -> review only";
40
+ }
41
+
42
+ function formatDocLinkAuthRoleFollowupInline(item) {
43
+ if (!(item?.auth_role_followups || []).length) {
44
+ return "";
45
+ }
46
+ return `\n - auth role follow-up: ${(item.auth_role_followups || []).map((entry) => `${entry.followup_label} for \`${entry.role_id}\``).join(", ")}`;
47
+ }
48
+
49
+ function previewFollowupRank(action) {
50
+ return {
51
+ promote_role: 0,
52
+ link_role_to_docs: 1,
53
+ review_only: 2
54
+ }[action] ?? 9;
55
+ }
56
+
57
+ function buildPreviewFollowupGuidance(summary) {
58
+ if (!summary?.adopt_selector || summary.adopt_write_mode || !summary?.next_bundle) {
59
+ return [];
60
+ }
61
+ const guidance = [];
62
+ const seenRoles = new Set();
63
+ for (const item of summary.next_bundle.recommended_actor_role_actions || []) {
64
+ if (!item.auth_relevant || !item.followup_action) {
65
+ continue;
66
+ }
67
+ seenRoles.add(item.item);
68
+ guidance.push({
69
+ role_id: item.item,
70
+ action: item.followup_action,
71
+ doc_ids: item.followup_doc_ids || [],
72
+ source: "actor_role_action",
73
+ reason: item.followup_reason || null
74
+ });
75
+ }
76
+ for (const suggestion of summary.next_bundle.recommended_doc_link_actions || []) {
77
+ for (const followup of suggestion.auth_role_followups || []) {
78
+ if (seenRoles.has(followup.role_id)) {
79
+ continue;
80
+ }
81
+ seenRoles.add(followup.role_id);
82
+ guidance.push({
83
+ role_id: followup.role_id,
84
+ action: followup.followup_action,
85
+ doc_ids: [suggestion.doc_id],
86
+ source: "doc_link_action",
87
+ reason: null
88
+ });
89
+ }
90
+ }
91
+ return guidance.sort((a, b) =>
92
+ previewFollowupRank(a.action) - previewFollowupRank(b.action) ||
93
+ a.role_id.localeCompare(b.role_id)
94
+ );
95
+ }
96
+
97
+ export function renderPreviewFollowupMarkdown(summary) {
98
+ const guidance = summary?.preview_followup_guidance || buildPreviewFollowupGuidance(summary);
99
+ if (!(guidance || []).length) {
100
+ return "";
101
+ }
102
+ return (
103
+ "## Preview Follow-Up Guidance\n\n" +
104
+ guidance.map((item) => {
105
+ if (item.action === "promote_role") {
106
+ return `- role \`${item.role_id}\`: promote role first${item.reason ? ` (${item.reason})` : ""}`;
107
+ }
108
+ if (item.action === "link_role_to_docs") {
109
+ return `- role \`${item.role_id}\`: patch docs first${item.doc_ids.length ? ` in ${item.doc_ids.map((entry) => `\`${entry}\``).join(", ")}` : ""}${item.reason ? ` (${item.reason})` : ""}`;
110
+ }
111
+ return `- role \`${item.role_id}\`: review only for now${item.reason ? ` (${item.reason})` : ""}`;
112
+ }).join("\n") +
113
+ "\n\n"
114
+ );
115
+ }
116
+
117
+ function reviewGroupSelector(groupId) {
118
+ const normalized = String(groupId || "");
119
+ if (normalized.startsWith("projection_review:")) {
120
+ return `projection-review:${normalized.slice("projection_review:".length)}`;
121
+ }
122
+ if (normalized.startsWith("ui_review:")) {
123
+ return `ui-review:${normalized.slice("ui_review:".length)}`;
124
+ }
125
+ if (normalized.startsWith("workflow_review:")) {
126
+ return `workflow-review:${normalized.slice("workflow_review:".length)}`;
127
+ }
128
+ return null;
129
+ }
130
+
131
+ export function buildBundleNextAction(bundle) {
132
+ if (!bundle) {
133
+ return null;
134
+ }
135
+ const nextReviewGroup = bundle.next_review_groups?.[0] || null;
136
+ if (bundle.is_complete) {
137
+ return {
138
+ kind: "complete",
139
+ label: "No action required",
140
+ selector: null,
141
+ reason: "This bundle has no blocked, approved, or pending work left.",
142
+ unlock_review_group_id: null,
143
+ unlock_review_selector: null,
144
+ safe_adopt_now_count: 0
145
+ };
146
+ }
147
+ if (nextReviewGroup) {
148
+ return {
149
+ kind: "review_bundle",
150
+ label: "Review this bundle next",
151
+ selector: bundle.recommend_bundle_review_selector || null,
152
+ reason: nextReviewGroup.reason || `This bundle is blocked by ${nextReviewGroup.type} review.`,
153
+ unlock_review_group_id: nextReviewGroup.id,
154
+ unlock_review_selector: reviewGroupSelector(nextReviewGroup.id),
155
+ safe_adopt_now_count: bundle.approved_items || 0
156
+ };
157
+ }
158
+ if (bundle.recommend_from_plan) {
159
+ return {
160
+ kind: "adopt_from_plan",
161
+ label: "Adopt approved items now",
162
+ selector: "from-plan",
163
+ reason: `${bundle.approved_items || 0} approved item(s) are ready to promote safely.`,
164
+ unlock_review_group_id: null,
165
+ unlock_review_selector: null,
166
+ safe_adopt_now_count: bundle.approved_items || 0
167
+ };
168
+ }
169
+ if ((bundle.pending_items || 0) > 0) {
170
+ return {
171
+ kind: "inspect_pending",
172
+ label: "Inspect pending items",
173
+ selector: null,
174
+ reason: `${bundle.pending_items} pending item(s) remain, but nothing is approved yet.`,
175
+ unlock_review_group_id: null,
176
+ unlock_review_selector: null,
177
+ safe_adopt_now_count: 0
178
+ };
179
+ }
180
+ return {
181
+ kind: "inspect_bundle",
182
+ label: "Inspect bundle state",
183
+ selector: null,
184
+ reason: "This bundle still has unresolved adoption work.",
185
+ unlock_review_group_id: null,
186
+ unlock_review_selector: null,
187
+ safe_adopt_now_count: bundle.approved_items || 0
188
+ };
189
+ }
190
+
191
+ export function annotateBundlePriorities(bundlePriorities) {
192
+ return (bundlePriorities || []).map((bundle) => ({
193
+ ...bundle,
194
+ next_action: buildBundleNextAction(bundle)
195
+ }));
196
+ }
197
+
198
+ export function attachBundleOperatorHints(bundlePriorities, candidateModelBundles) {
199
+ const bundleMap = new Map(
200
+ (candidateModelBundles || []).flatMap((bundle) => [
201
+ [bundle.slug, bundle],
202
+ [bundle.id, bundle]
203
+ ]).filter((entry) => entry[0])
204
+ );
205
+ return (bundlePriorities || []).map((bundle) => {
206
+ const matchedBundle = bundleMap.get(bundle.bundle);
207
+ return {
208
+ ...bundle,
209
+ auth_permission_hints: matchedBundle?.auth_permission_hints || [],
210
+ auth_claim_hints: matchedBundle?.auth_claim_hints || [],
211
+ auth_ownership_hints: matchedBundle?.auth_ownership_hints || [],
212
+ auth_role_guidance: matchedBundle?.auth_role_guidance || [],
213
+ auth_closure_summary: matchedBundle?.operator_summary?.authClosureSummary || null,
214
+ auth_aging_summary: matchedBundle?.operator_summary?.authAging || null,
215
+ operator_summary: matchedBundle?.operator_summary || null
216
+ };
217
+ });
218
+ }
219
+
220
+ export function renderNextBestActionMarkdown(bundle) {
221
+ if (!bundle) {
222
+ return "## Next Best Action\n\n- None\n\n";
223
+ }
224
+ const action = bundle.next_action || buildBundleNextAction(bundle);
225
+ const lines = [
226
+ "## Next Best Action",
227
+ "",
228
+ `- Bundle: \`${bundle.bundle}\``,
229
+ `- Action: ${action.label}`,
230
+ `- Why now: ${action.reason}`,
231
+ `- Selector: ${action.selector ? `\`${action.selector}\`` : "_none_"}`,
232
+ `- Safe to adopt now: ${action.safe_adopt_now_count || 0} approved item(s)`
233
+ ];
234
+ if (action.unlock_review_group_id) {
235
+ lines.push(`- Unlock review group: \`${action.unlock_review_group_id}\``);
236
+ }
237
+ if (action.unlock_review_selector) {
238
+ lines.push(`- Unlock selector: \`${action.unlock_review_selector}\``);
239
+ }
240
+ if ((bundle.auth_permission_hints || []).length > 0) {
241
+ lines.push(`- Permission review: Review ${bundle.auth_permission_hints.length} inferred permission hint(s) before promoting auth-sensitive items from this bundle.`);
242
+ for (const hint of bundle.auth_permission_hints) {
243
+ lines.push(` - ${formatAuthPermissionHintInline(hint)}`);
244
+ lines.push(` - Closure: ${formatClosureInline(hint.closure_state)}`);
245
+ if (hint.closure_reason) {
246
+ lines.push(` - Closure reason: ${hint.closure_reason}`);
247
+ }
248
+ if (hint.why_inferred) {
249
+ lines.push(` - Why inferred: ${hint.why_inferred}`);
250
+ }
251
+ if (hint.review_guidance) {
252
+ lines.push(` - Review next: ${hint.review_guidance}`);
253
+ }
254
+ }
255
+ }
256
+ if ((bundle.auth_claim_hints || []).length > 0) {
257
+ lines.push(`- Auth review: Review ${bundle.auth_claim_hints.length} inferred claim hint(s) before promoting auth-sensitive items from this bundle.`);
258
+ for (const hint of bundle.auth_claim_hints) {
259
+ lines.push(` - ${formatAuthClaimHintInline(hint)}`);
260
+ lines.push(` - Closure: ${formatClosureInline(hint.closure_state)}`);
261
+ if (hint.closure_reason) {
262
+ lines.push(` - Closure reason: ${hint.closure_reason}`);
263
+ }
264
+ if (hint.why_inferred) {
265
+ lines.push(` - Why inferred: ${hint.why_inferred}`);
266
+ }
267
+ if (hint.review_guidance) {
268
+ lines.push(` - Review next: ${hint.review_guidance}`);
269
+ }
270
+ }
271
+ }
272
+ if ((bundle.auth_ownership_hints || []).length > 0) {
273
+ lines.push(`- Ownership review: Review ${bundle.auth_ownership_hints.length} inferred ownership hint(s) before promoting auth-sensitive items from this bundle.`);
274
+ for (const hint of bundle.auth_ownership_hints) {
275
+ lines.push(` - ownership \`${hint.ownership}\` field \`${hint.ownership_field}\` (${hint.confidence})`);
276
+ lines.push(` - Closure: ${formatClosureInline(hint.closure_state)}`);
277
+ if (hint.closure_reason) {
278
+ lines.push(` - Closure reason: ${hint.closure_reason}`);
279
+ }
280
+ if (hint.why_inferred) {
281
+ lines.push(` - Why inferred: ${hint.why_inferred}`);
282
+ }
283
+ if (hint.review_guidance) {
284
+ lines.push(` - Review next: ${hint.review_guidance}`);
285
+ }
286
+ }
287
+ }
288
+ if ((bundle.auth_role_guidance || []).length > 0) {
289
+ lines.push(`- Participant review: Review ${bundle.auth_role_guidance.length} auth-relevant role hint(s) before promoting auth-sensitive participant changes from this bundle.`);
290
+ for (const entry of bundle.auth_role_guidance) {
291
+ lines.push(` - role \`${entry.role_id}\` (${entry.confidence}) -> ${entry.followup_label}`);
292
+ if (entry.why_inferred) {
293
+ lines.push(` - Why inferred: ${entry.why_inferred}`);
294
+ }
295
+ if (entry.followup_reason) {
296
+ lines.push(` - Why this follow-up: ${entry.followup_reason}`);
297
+ }
298
+ if (entry.review_guidance) {
299
+ lines.push(` - Review next: ${entry.review_guidance}`);
300
+ }
301
+ }
302
+ }
303
+ if (bundle.auth_closure_summary && bundle.auth_closure_summary.status !== "no_auth_hints") {
304
+ lines.push(`- Auth closure score: ${bundle.auth_closure_summary.label} (adopted=${bundle.auth_closure_summary.adopted}, deferred=${bundle.auth_closure_summary.deferred}, unresolved=${bundle.auth_closure_summary.unresolved})`);
305
+ lines.push(` - Why this score: ${bundle.auth_closure_summary.reason}`);
306
+ if (bundle.auth_closure_summary.status === "high_risk") {
307
+ lines.push(" - Priority note: This bundle should be reviewed ahead of lower-risk bundles with similar adoption pressure.");
308
+ }
309
+ }
310
+ if (bundle.auth_aging_summary && bundle.auth_aging_summary.escalationLevel !== "none") {
311
+ lines.push(`- Auth escalation: ${bundle.auth_aging_summary.escalationLevel === "stale_high_risk" ? "escalated" : "fresh attention"} (high-risk runs=${bundle.auth_aging_summary.repeatCount})`);
312
+ lines.push(` - Why this escalation: ${bundle.auth_aging_summary.escalationReason}`);
313
+ if (bundle.auth_aging_summary.escalationLevel === "stale_high_risk") {
314
+ lines.push(" - Escalation note: This bundle has stayed unresolved and high risk across multiple reconcile runs.");
315
+ }
316
+ }
317
+ return `${lines.join("\n")}\n\n`;
318
+ }
319
+
320
+ export function renderBundlePriorityActionsMarkdown(bundlePriorities) {
321
+ if (!(bundlePriorities || []).length) {
322
+ return "## Bundle Priorities\n\n- None\n\n";
323
+ }
324
+ return (
325
+ "## Bundle Priorities\n\n" +
326
+ bundlePriorities.map((bundle) => {
327
+ const action = bundle.next_action || buildBundleNextAction(bundle);
328
+ return `- \`${bundle.bundle}\`: action=${action.selector ? `\`${action.selector}\`` : action.kind}, why=${action.reason}, safe-now=${action.safe_adopt_now_count || 0}${(bundle.auth_permission_hints || []).length ? `, permission-hints=${bundle.auth_permission_hints.length}` : ""}${(bundle.auth_claim_hints || []).length ? `, auth-hints=${bundle.auth_claim_hints.length}` : ""}${(bundle.auth_ownership_hints || []).length ? `, ownership-hints=${bundle.auth_ownership_hints.length}` : ""}${bundle.auth_closure_summary && bundle.auth_closure_summary.status !== "no_auth_hints" ? `, auth-closure=${bundle.auth_closure_summary.status}` : ""}${bundle.auth_aging_summary && bundle.auth_aging_summary.escalationLevel !== "none" ? `, auth-aging=${bundle.auth_aging_summary.escalationLevel}, high-risk-runs=${bundle.auth_aging_summary.repeatCount}` : ""}${bundle.auth_risk_rank > 0 ? `, auth-priority=${bundle.auth_risk_rank}` : ""}`;
329
+ }).join("\n") +
330
+ "\n\n"
331
+ );
332
+ }
333
+
334
+ export function buildPromotedCanonicalItems(planItems, selectedItems, writtenCanonicalFiles, selector, adoptionItemKey, refreshedCanonicalFiles = []) {
335
+ const itemMap = new Map((planItems || []).map((item) => [adoptionItemKey(item), item]));
336
+ const writtenSet = new Set((writtenCanonicalFiles || []).map((item) => normalizeReportPath(item)));
337
+ const updatedSet = new Set((refreshedCanonicalFiles || []).map((item) => normalizeReportPath(item)));
338
+ return [...new Set(selectedItems || [])]
339
+ .map((key) => itemMap.get(key))
340
+ .filter(Boolean)
341
+ .filter((item) => item.canonical_rel_path && writtenSet.has(normalizeReportPath(item.canonical_rel_path)))
342
+ .map((item) => ({
343
+ selector: selector || null,
344
+ bundle: item.bundle,
345
+ item: item.item,
346
+ kind: item.kind,
347
+ track: item.track || null,
348
+ source_path: item.source_path || null,
349
+ canonical_rel_path: normalizeReportPath(item.canonical_rel_path),
350
+ canonical_path: item.canonical_path || `topogram/${normalizeReportPath(item.canonical_rel_path)}`,
351
+ suggested_action: item.suggested_action || null,
352
+ change_type: updatedSet.has(normalizeReportPath(item.canonical_rel_path)) ? "update" : "create"
353
+ }))
354
+ .sort((a, b) =>
355
+ (a.bundle || "").localeCompare(b.bundle || "") ||
356
+ (a.track || "").localeCompare(b.track || "") ||
357
+ (a.kind || "").localeCompare(b.kind || "") ||
358
+ (a.item || "").localeCompare(b.item || "")
359
+ );
360
+ }
361
+
362
+ function summarizeCanonicalChangeTypes(items) {
363
+ return (items || []).reduce(
364
+ (acc, item) => {
365
+ if (item.change_type === "update") {
366
+ acc.updates += 1;
367
+ } else {
368
+ acc.creates += 1;
369
+ }
370
+ return acc;
371
+ },
372
+ { creates: 0, updates: 0 }
373
+ );
374
+ }
375
+
376
+ export function renderPromotedCanonicalItemsMarkdown(items, options = {}) {
377
+ if (!(items || []).length) {
378
+ return "";
379
+ }
380
+ const includeItemId = options.includeItemId !== false;
381
+ const includeSummary = options.includeSummary !== false;
382
+ const title = options.title || "## Promoted Canonical Items";
383
+ const counts = summarizeCanonicalChangeTypes(items);
384
+ return (
385
+ `${title}\n\n` +
386
+ `${includeSummary ? `- Creates: ${counts.creates}\n- Updates: ${counts.updates}\n\n` : ""}` +
387
+ items
388
+ .map((item) =>
389
+ includeItemId
390
+ ? `- [${item.bundle}] \`${item.item}\` \`${item.source_path}\` -> \`${item.canonical_rel_path}\` (${item.change_type || "create"})`
391
+ : `- [${item.bundle}] \`${item.source_path}\` -> \`${item.canonical_rel_path}\` (${item.change_type || "create"})`
392
+ )
393
+ .join("\n") +
394
+ "\n\n"
395
+ );
396
+ }
397
+
398
+ export function renderPreviewRiskMarkdown(summary) {
399
+ if (!summary?.adopt_selector || summary.adopt_write_mode) {
400
+ return "";
401
+ }
402
+ return (
403
+ "## Remaining Risk After Preview\n\n" +
404
+ `- Blocked items after selector: ${summary.blocked_item_count}\n` +
405
+ `- Projection review groups still needed: ${summary.projection_review_groups.length}\n` +
406
+ `- UI review groups still needed: ${summary.ui_review_groups.length}\n` +
407
+ `- Workflow review groups still needed: ${summary.workflow_review_groups.length}\n\n`
408
+ );
409
+ }
410
+
411
+ export function buildAdoptionStatusSummary(report, selectNextBundle) {
412
+ const bundlePriorities = annotateBundlePriorities(attachBundleOperatorHints(report.bundle_priorities, report.candidate_model_bundles));
413
+ const nextBundle = selectNextBundle(bundlePriorities);
414
+ const baseSummary = {
415
+ type: "adoption_status",
416
+ workspace: report.workspace,
417
+ bootstrapped_topogram_root: report.bootstrapped_topogram_root,
418
+ adoption_plan_path: report.adoption_plan_path,
419
+ adopt_selector: report.adopt_selector || null,
420
+ adopt_write_mode: Boolean(report.adopt_write_mode),
421
+ approved_review_groups: report.approved_review_groups,
422
+ approved_item_count: report.approved_items.length,
423
+ applied_item_count: report.applied_items.length,
424
+ blocked_item_count: report.blocked_items.length,
425
+ promoted_canonical_items: report.promoted_canonical_items || [],
426
+ bundle_blockers: report.bundle_blockers,
427
+ bundle_priorities: bundlePriorities,
428
+ next_bundle: nextBundle,
429
+ projection_review_groups: report.projection_review_groups,
430
+ ui_review_groups: report.ui_review_groups,
431
+ workflow_review_groups: report.workflow_review_groups
432
+ };
433
+ return {
434
+ ...baseSummary,
435
+ preview_followup_guidance: buildPreviewFollowupGuidance(baseSummary)
436
+ };
437
+ }
438
+
439
+ export function buildAdoptionStatusFiles(summary, formatDocLinkSuggestionInline, formatDocDriftSummaryInline, formatDocMetadataPatchInline) {
440
+ const canonicalChangeTitle = summary.adopt_selector && !summary.adopt_write_mode
441
+ ? "## Preview Canonical Changes"
442
+ : "## Promoted Canonical Items";
443
+ return {
444
+ "candidates/reconcile/adoption-status.json": `${stableStringify(summary)}\n`,
445
+ "candidates/reconcile/adoption-status.md": ensureTrailingNewline(
446
+ `# Adoption Status\n\n` +
447
+ `## Summary\n\n` +
448
+ `- Plan: \`${summary.adoption_plan_path}\`\n` +
449
+ `- Selector: \`${summary.adopt_selector || "none"}\`\n` +
450
+ `- Write mode: ${summary.adopt_write_mode ? "yes" : "no"}\n` +
451
+ `- Approved items: ${summary.approved_item_count}\n` +
452
+ `- Applied items: ${summary.applied_item_count}\n` +
453
+ `- Blocked items: ${summary.blocked_item_count}\n\n` +
454
+ `${renderPromotedCanonicalItemsMarkdown(summary.promoted_canonical_items || [], { includeItemId: false, title: canonicalChangeTitle })}` +
455
+ `${renderPreviewRiskMarkdown(summary)}` +
456
+ `${renderPreviewFollowupMarkdown(summary)}` +
457
+ `${renderNextBestActionMarkdown(summary.next_bundle)}` +
458
+ `${summary.next_bundle ? `## Next Bundle\n\n- \`${summary.next_bundle.bundle}\`\n- Next review: ${summary.next_bundle.next_review_groups.length ? `\`${summary.next_bundle.next_review_groups[0].id}\`` : "_none_"}\n- Bundle review: ${summary.next_bundle.recommend_bundle_review_selector ? `\`${summary.next_bundle.recommend_bundle_review_selector}\`` : "_none_"}\n- Ready for from-plan: ${summary.next_bundle.recommend_from_plan ? "yes" : "no"}${summary.next_bundle.recommended_actor_role_actions?.length ? `\n- Suggested actor/role actions:\n${summary.next_bundle.recommended_actor_role_actions.map((item) => ` - ${item.kind} \`${item.item}\` (${item.confidence})${item.auth_relevant ? " auth-relevant" : ""}${formatRoleFollowupInline(item)}`).join("\n")}` : ""}${summary.next_bundle.recommended_doc_link_actions?.length ? `\n- Suggested doc link updates:\n${summary.next_bundle.recommended_doc_link_actions.map((item) => ` - ${formatDocLinkSuggestionInline(item).replace(/^doc /, "")}${formatDocLinkAuthRoleFollowupInline(item)}`).join("\n")}` : ""}${summary.next_bundle.recommended_doc_drift_actions?.length ? `\n- Suggested doc drift reviews:\n${summary.next_bundle.recommended_doc_drift_actions.map((item) => ` - ${formatDocDriftSummaryInline(item)}`).join("\n")}` : ""}${summary.next_bundle.recommended_doc_metadata_patch_actions?.length ? `\n- Suggested doc metadata patches:\n${summary.next_bundle.recommended_doc_metadata_patch_actions.map((item) => ` - ${formatDocMetadataPatchInline(item)}`).join("\n")}` : ""}\n\n` : "## Next Bundle\n\n- None\n\n"}` +
459
+ `${renderBundlePriorityActionsMarkdown(summary.bundle_priorities)}` +
460
+ `## Approved Review Groups\n\n` +
461
+ `${summary.approved_review_groups.length ? summary.approved_review_groups.map((item) => `- \`${item}\``).join("\n") : "- None"}\n`
462
+ )
463
+ };
464
+ }