@prisma-next/cli 0.3.0-pr.99.5 → 0.3.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 (257) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +381 -128
  3. package/dist/agent-skill-mongo.md +106 -0
  4. package/dist/agent-skill-postgres.md +106 -0
  5. package/dist/cli-errors-BDCYR5ap.mjs +4 -0
  6. package/dist/cli-errors-DStABy9d.d.mts +3 -0
  7. package/dist/cli.d.mts +1 -0
  8. package/dist/cli.js +1 -2910
  9. package/dist/cli.mjs +261 -0
  10. package/dist/cli.mjs.map +1 -0
  11. package/dist/client-DiUkJAeN.mjs +987 -0
  12. package/dist/client-DiUkJAeN.mjs.map +1 -0
  13. package/dist/commands/contract-emit.d.mts +7 -0
  14. package/dist/commands/contract-emit.d.mts.map +1 -0
  15. package/dist/commands/contract-emit.mjs +9 -0
  16. package/dist/commands/contract-infer.d.mts +7 -0
  17. package/dist/commands/contract-infer.d.mts.map +1 -0
  18. package/dist/commands/contract-infer.mjs +10 -0
  19. package/dist/commands/db-init.d.mts +7 -0
  20. package/dist/commands/db-init.d.mts.map +1 -0
  21. package/dist/commands/db-init.mjs +126 -0
  22. package/dist/commands/db-init.mjs.map +1 -0
  23. package/dist/commands/db-schema.d.mts +7 -0
  24. package/dist/commands/db-schema.d.mts.map +1 -0
  25. package/dist/commands/db-schema.mjs +56 -0
  26. package/dist/commands/db-schema.mjs.map +1 -0
  27. package/dist/commands/db-sign.d.mts +7 -0
  28. package/dist/commands/db-sign.d.mts.map +1 -0
  29. package/dist/commands/db-sign.mjs +137 -0
  30. package/dist/commands/db-sign.mjs.map +1 -0
  31. package/dist/commands/db-update.d.mts +7 -0
  32. package/dist/commands/db-update.d.mts.map +1 -0
  33. package/dist/commands/db-update.mjs +123 -0
  34. package/dist/commands/db-update.mjs.map +1 -0
  35. package/dist/commands/db-verify.d.mts +7 -0
  36. package/dist/commands/db-verify.d.mts.map +1 -0
  37. package/dist/commands/db-verify.mjs +323 -0
  38. package/dist/commands/db-verify.mjs.map +1 -0
  39. package/dist/commands/migration-apply.d.mts +36 -0
  40. package/dist/commands/migration-apply.d.mts.map +1 -0
  41. package/dist/commands/migration-apply.mjs +245 -0
  42. package/dist/commands/migration-apply.mjs.map +1 -0
  43. package/dist/commands/migration-new.d.mts +8 -0
  44. package/dist/commands/migration-new.d.mts.map +1 -0
  45. package/dist/commands/migration-new.mjs +152 -0
  46. package/dist/commands/migration-new.mjs.map +1 -0
  47. package/dist/commands/migration-plan.d.mts +47 -0
  48. package/dist/commands/migration-plan.d.mts.map +1 -0
  49. package/dist/commands/migration-plan.mjs +313 -0
  50. package/dist/commands/migration-plan.mjs.map +1 -0
  51. package/dist/commands/migration-ref.d.mts +43 -0
  52. package/dist/commands/migration-ref.d.mts.map +1 -0
  53. package/dist/commands/migration-ref.mjs +195 -0
  54. package/dist/commands/migration-ref.mjs.map +1 -0
  55. package/dist/commands/migration-show.d.mts +28 -0
  56. package/dist/commands/migration-show.d.mts.map +1 -0
  57. package/dist/commands/migration-show.mjs +140 -0
  58. package/dist/commands/migration-show.mjs.map +1 -0
  59. package/dist/commands/migration-status.d.mts +86 -0
  60. package/dist/commands/migration-status.d.mts.map +1 -0
  61. package/dist/commands/migration-status.mjs +9 -0
  62. package/dist/commands/migration-verify.d.mts +16 -0
  63. package/dist/commands/migration-verify.d.mts.map +1 -0
  64. package/dist/commands/migration-verify.mjs +110 -0
  65. package/dist/commands/migration-verify.mjs.map +1 -0
  66. package/dist/config-loader-C4VXKl8f.mjs +43 -0
  67. package/dist/config-loader-C4VXKl8f.mjs.map +1 -0
  68. package/dist/{config-loader.d.ts → config-loader.d.mts} +8 -3
  69. package/dist/config-loader.d.mts.map +1 -0
  70. package/dist/config-loader.mjs +3 -0
  71. package/dist/contract-emit-D2wDXfyo.mjs +191 -0
  72. package/dist/contract-emit-D2wDXfyo.mjs.map +1 -0
  73. package/dist/contract-emit-Zm_sd1wQ.mjs +112 -0
  74. package/dist/contract-emit-Zm_sd1wQ.mjs.map +1 -0
  75. package/dist/contract-emit-kN-IkKTE.mjs +6 -0
  76. package/dist/contract-enrichment-CGW6mm-E.mjs +79 -0
  77. package/dist/contract-enrichment-CGW6mm-E.mjs.map +1 -0
  78. package/dist/contract-infer-DozZT511.mjs +90 -0
  79. package/dist/contract-infer-DozZT511.mjs.map +1 -0
  80. package/dist/exports/config-types.d.mts +2 -0
  81. package/dist/exports/config-types.mjs +3 -0
  82. package/dist/exports/control-api.d.mts +624 -0
  83. package/dist/exports/control-api.d.mts.map +1 -0
  84. package/dist/exports/control-api.mjs +8 -0
  85. package/dist/{load-ts-contract.d.ts → exports/index.d.mts} +12 -7
  86. package/dist/exports/index.d.mts.map +1 -0
  87. package/dist/exports/index.mjs +142 -0
  88. package/dist/exports/index.mjs.map +1 -0
  89. package/dist/extract-operation-statements-DZUJNmL3.mjs +13 -0
  90. package/dist/extract-operation-statements-DZUJNmL3.mjs.map +1 -0
  91. package/dist/extract-sql-ddl-DDMX-9mz.mjs +26 -0
  92. package/dist/extract-sql-ddl-DDMX-9mz.mjs.map +1 -0
  93. package/dist/framework-components-BAsliT4V.mjs +59 -0
  94. package/dist/framework-components-BAsliT4V.mjs.map +1 -0
  95. package/dist/init-6Pvm_esG.mjs +430 -0
  96. package/dist/init-6Pvm_esG.mjs.map +1 -0
  97. package/dist/inspect-live-schema-BYnhztxZ.mjs +91 -0
  98. package/dist/inspect-live-schema-BYnhztxZ.mjs.map +1 -0
  99. package/dist/migration-command-scaffold-CntCcntR.mjs +105 -0
  100. package/dist/migration-command-scaffold-CntCcntR.mjs.map +1 -0
  101. package/dist/migration-status-CJANY4yr.mjs +1583 -0
  102. package/dist/migration-status-CJANY4yr.mjs.map +1 -0
  103. package/dist/migrations-DTZBYXm1.mjs +173 -0
  104. package/dist/migrations-DTZBYXm1.mjs.map +1 -0
  105. package/dist/progress-adapter-B-YvmcDu.mjs +43 -0
  106. package/dist/progress-adapter-B-YvmcDu.mjs.map +1 -0
  107. package/dist/quick-reference-mongo.md +93 -0
  108. package/dist/quick-reference-postgres.md +91 -0
  109. package/dist/result-handler-oK_vA-Fn.mjs +697 -0
  110. package/dist/result-handler-oK_vA-Fn.mjs.map +1 -0
  111. package/dist/terminal-ui-C5k88MmW.mjs +274 -0
  112. package/dist/terminal-ui-C5k88MmW.mjs.map +1 -0
  113. package/dist/validate-contract-deps-esa-VQ0h.mjs +37 -0
  114. package/dist/validate-contract-deps-esa-VQ0h.mjs.map +1 -0
  115. package/dist/verify-DlFQ2FOw.mjs +385 -0
  116. package/dist/verify-DlFQ2FOw.mjs.map +1 -0
  117. package/package.json +87 -40
  118. package/src/cli.ts +118 -58
  119. package/src/commands/contract-emit.ts +101 -78
  120. package/src/commands/contract-infer-paths.ts +32 -0
  121. package/src/commands/contract-infer.ts +143 -0
  122. package/src/commands/db-init.ts +97 -219
  123. package/src/commands/db-schema.ts +77 -0
  124. package/src/commands/db-sign.ts +46 -73
  125. package/src/commands/db-update.ts +236 -0
  126. package/src/commands/db-verify.ts +409 -119
  127. package/src/commands/init/detect-package-manager.ts +47 -0
  128. package/src/commands/init/index.ts +21 -0
  129. package/src/commands/init/init.ts +203 -0
  130. package/src/commands/init/templates/agent-skill-mongo.md +106 -0
  131. package/src/commands/init/templates/agent-skill-postgres.md +106 -0
  132. package/src/commands/init/templates/agent-skill.ts +19 -0
  133. package/src/commands/init/templates/code-templates.ts +168 -0
  134. package/src/commands/init/templates/quick-reference-mongo.md +93 -0
  135. package/src/commands/init/templates/quick-reference-postgres.md +91 -0
  136. package/src/commands/init/templates/quick-reference.ts +19 -0
  137. package/src/commands/init/templates/render.ts +20 -0
  138. package/src/commands/init/templates/tsconfig.ts +35 -0
  139. package/src/commands/inspect-live-schema.ts +170 -0
  140. package/src/commands/migration-apply.ts +427 -0
  141. package/src/commands/migration-new.ts +260 -0
  142. package/src/commands/migration-plan.ts +519 -0
  143. package/src/commands/migration-ref.ts +305 -0
  144. package/src/commands/migration-show.ts +246 -0
  145. package/src/commands/migration-status.ts +864 -0
  146. package/src/commands/migration-verify.ts +180 -0
  147. package/src/config-loader.ts +13 -3
  148. package/src/control-api/client.ts +205 -183
  149. package/src/control-api/contract-enrichment.ts +119 -0
  150. package/src/control-api/errors.ts +9 -0
  151. package/src/control-api/operations/contract-emit.ts +181 -0
  152. package/src/control-api/operations/db-init.ts +53 -49
  153. package/src/control-api/operations/db-update.ts +220 -0
  154. package/src/control-api/operations/extract-operation-statements.ts +14 -0
  155. package/src/control-api/operations/extract-sql-ddl.ts +47 -0
  156. package/src/control-api/operations/migration-apply.ts +191 -0
  157. package/src/control-api/operations/migration-helpers.ts +49 -0
  158. package/src/control-api/types.ts +274 -52
  159. package/src/exports/config-types.ts +4 -3
  160. package/src/exports/control-api.ts +15 -5
  161. package/src/load-ts-contract.ts +30 -19
  162. package/src/utils/cli-errors.ts +14 -8
  163. package/src/utils/command-helpers.ts +302 -3
  164. package/src/utils/formatters/emit.ts +67 -0
  165. package/src/utils/formatters/errors.ts +82 -0
  166. package/src/utils/formatters/graph-migration-mapper.ts +240 -0
  167. package/src/utils/formatters/graph-render.ts +1323 -0
  168. package/src/utils/formatters/graph-types.ts +120 -0
  169. package/src/utils/formatters/help.ts +380 -0
  170. package/src/utils/formatters/helpers.ts +28 -0
  171. package/src/utils/formatters/migrations.ts +346 -0
  172. package/src/utils/formatters/styled.ts +212 -0
  173. package/src/utils/formatters/verify.ts +621 -0
  174. package/src/utils/framework-components.ts +13 -10
  175. package/src/utils/global-flags.ts +41 -23
  176. package/src/utils/migration-command-scaffold.ts +184 -0
  177. package/src/utils/migration-types.ts +12 -0
  178. package/src/utils/progress-adapter.ts +18 -29
  179. package/src/utils/result-handler.ts +12 -13
  180. package/src/utils/shutdown.ts +92 -0
  181. package/src/utils/suggest-command.ts +31 -0
  182. package/src/utils/terminal-ui.ts +276 -0
  183. package/src/utils/validate-contract-deps.ts +49 -0
  184. package/dist/chunk-AGOTG4L3.js +0 -965
  185. package/dist/chunk-AGOTG4L3.js.map +0 -1
  186. package/dist/chunk-HLLI4YL7.js +0 -180
  187. package/dist/chunk-HLLI4YL7.js.map +0 -1
  188. package/dist/chunk-HWYQOCAJ.js +0 -47
  189. package/dist/chunk-HWYQOCAJ.js.map +0 -1
  190. package/dist/chunk-VG2R7DGF.js +0 -735
  191. package/dist/chunk-VG2R7DGF.js.map +0 -1
  192. package/dist/cli.d.ts +0 -2
  193. package/dist/cli.d.ts.map +0 -1
  194. package/dist/cli.js.map +0 -1
  195. package/dist/commands/contract-emit.d.ts +0 -3
  196. package/dist/commands/contract-emit.d.ts.map +0 -1
  197. package/dist/commands/contract-emit.js +0 -10
  198. package/dist/commands/contract-emit.js.map +0 -1
  199. package/dist/commands/db-init.d.ts +0 -3
  200. package/dist/commands/db-init.d.ts.map +0 -1
  201. package/dist/commands/db-init.js +0 -257
  202. package/dist/commands/db-init.js.map +0 -1
  203. package/dist/commands/db-introspect.d.ts +0 -3
  204. package/dist/commands/db-introspect.d.ts.map +0 -1
  205. package/dist/commands/db-introspect.js +0 -155
  206. package/dist/commands/db-introspect.js.map +0 -1
  207. package/dist/commands/db-schema-verify.d.ts +0 -3
  208. package/dist/commands/db-schema-verify.d.ts.map +0 -1
  209. package/dist/commands/db-schema-verify.js +0 -171
  210. package/dist/commands/db-schema-verify.js.map +0 -1
  211. package/dist/commands/db-sign.d.ts +0 -3
  212. package/dist/commands/db-sign.d.ts.map +0 -1
  213. package/dist/commands/db-sign.js +0 -195
  214. package/dist/commands/db-sign.js.map +0 -1
  215. package/dist/commands/db-verify.d.ts +0 -3
  216. package/dist/commands/db-verify.d.ts.map +0 -1
  217. package/dist/commands/db-verify.js +0 -193
  218. package/dist/commands/db-verify.js.map +0 -1
  219. package/dist/config-loader.d.ts.map +0 -1
  220. package/dist/config-loader.js +0 -7
  221. package/dist/config-loader.js.map +0 -1
  222. package/dist/control-api/client.d.ts +0 -13
  223. package/dist/control-api/client.d.ts.map +0 -1
  224. package/dist/control-api/operations/db-init.d.ts +0 -29
  225. package/dist/control-api/operations/db-init.d.ts.map +0 -1
  226. package/dist/control-api/types.d.ts +0 -387
  227. package/dist/control-api/types.d.ts.map +0 -1
  228. package/dist/exports/config-types.d.ts +0 -3
  229. package/dist/exports/config-types.d.ts.map +0 -1
  230. package/dist/exports/config-types.js +0 -6
  231. package/dist/exports/config-types.js.map +0 -1
  232. package/dist/exports/control-api.d.ts +0 -13
  233. package/dist/exports/control-api.d.ts.map +0 -1
  234. package/dist/exports/control-api.js +0 -7
  235. package/dist/exports/control-api.js.map +0 -1
  236. package/dist/exports/index.d.ts +0 -4
  237. package/dist/exports/index.d.ts.map +0 -1
  238. package/dist/exports/index.js +0 -176
  239. package/dist/exports/index.js.map +0 -1
  240. package/dist/load-ts-contract.d.ts.map +0 -1
  241. package/dist/utils/cli-errors.d.ts +0 -7
  242. package/dist/utils/cli-errors.d.ts.map +0 -1
  243. package/dist/utils/command-helpers.d.ts +0 -12
  244. package/dist/utils/command-helpers.d.ts.map +0 -1
  245. package/dist/utils/framework-components.d.ts +0 -70
  246. package/dist/utils/framework-components.d.ts.map +0 -1
  247. package/dist/utils/global-flags.d.ts +0 -25
  248. package/dist/utils/global-flags.d.ts.map +0 -1
  249. package/dist/utils/output.d.ts +0 -142
  250. package/dist/utils/output.d.ts.map +0 -1
  251. package/dist/utils/progress-adapter.d.ts +0 -26
  252. package/dist/utils/progress-adapter.d.ts.map +0 -1
  253. package/dist/utils/result-handler.d.ts +0 -15
  254. package/dist/utils/result-handler.d.ts.map +0 -1
  255. package/src/commands/db-introspect.ts +0 -227
  256. package/src/commands/db-schema-verify.ts +0 -238
  257. package/src/utils/output.ts +0 -1471
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Maps MigrationGraph + status info to the generic graph renderer types.
3
+ */
4
+ import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
5
+ import { findPath } from '@prisma-next/migration-tools/dag';
6
+ import type { MigrationGraph } from '@prisma-next/migration-tools/types';
7
+ import { ifDefined } from '@prisma-next/utils/defined';
8
+
9
+ import type { StatusRef } from '../migration-types';
10
+ import {
11
+ type GraphEdge,
12
+ type GraphNode,
13
+ type GraphRenderOptions,
14
+ type NodeMarker,
15
+ RenderGraph,
16
+ } from './graph-types';
17
+
18
+ export type EdgeStatusKind = 'applied' | 'pending' | 'unreachable';
19
+
20
+ const STATUS_ICON: Record<EdgeStatusKind, string> = {
21
+ applied: ' ✓',
22
+ pending: ' ⧗',
23
+ unreachable: ' ✗',
24
+ };
25
+
26
+ /** Shorten a contract hash for display: strip sha256: prefix, take 7 chars. */
27
+ function shortHash(hash: string): string {
28
+ const stripped = hash.startsWith('sha256:') ? hash.slice(7) : hash;
29
+ return stripped.slice(0, 7);
30
+ }
31
+
32
+ function toShortId(hash: string): string {
33
+ return hash === EMPTY_CONTRACT_HASH ? '∅' : shortHash(hash);
34
+ }
35
+
36
+ /** Minimal per-edge status from the CLI's status result. */
37
+ export interface EdgeStatus {
38
+ readonly dirName: string;
39
+ readonly status: EdgeStatusKind;
40
+ }
41
+
42
+ export interface DraftEdge {
43
+ readonly from: string;
44
+ readonly to: string;
45
+ readonly dirName: string;
46
+ }
47
+
48
+ export interface MigrationGraphInput {
49
+ readonly graph: MigrationGraph;
50
+ readonly mode: 'online' | 'offline';
51
+ readonly markerHash?: string | undefined;
52
+ readonly contractHash: string;
53
+ readonly refs?: readonly StatusRef[] | undefined;
54
+ readonly activeRefHash?: string | undefined;
55
+ readonly activeRefName?: string | undefined;
56
+ /**
57
+ * Per-edge applied/pending status from the ledger. When provided, status
58
+ * icons (✓/⧗) are baked into edge labels. Undefined in offline mode.
59
+ */
60
+ readonly edgeStatuses?: readonly EdgeStatus[] | undefined;
61
+ /** Draft migrations to render as dashed edges. */
62
+ readonly draftEdges?: readonly DraftEdge[] | undefined;
63
+ }
64
+
65
+ export interface MigrationRenderInput {
66
+ readonly graph: RenderGraph;
67
+ readonly options: GraphRenderOptions;
68
+ /** All relevant paths (root→contract, root→marker, root→ref). */
69
+ readonly relevantPaths: readonly (readonly string[])[];
70
+ }
71
+
72
+ /**
73
+ * Convert a MigrationGraph + status info into the generic graph renderer types.
74
+ */
75
+ export function migrationGraphToRenderInput(input: MigrationGraphInput): MigrationRenderInput {
76
+ const { graph, mode, markerHash, contractHash, refs, activeRefHash, edgeStatuses } = input;
77
+
78
+ const statusByDirName = new Map(edgeStatuses?.map((e) => [e.dirName, e.status]));
79
+
80
+ // Build nodes
81
+ const nodeList: GraphNode[] = [];
82
+ for (const nodeId of graph.nodes) {
83
+ const markers: NodeMarker[] = [];
84
+
85
+ // DB marker
86
+ if (mode === 'online' && markerHash === nodeId) {
87
+ markers.push({ kind: 'db' });
88
+ }
89
+
90
+ // Ref markers
91
+ if (refs) {
92
+ for (const ref of refs) {
93
+ if (ref.hash === nodeId) {
94
+ markers.push({ kind: 'ref', name: ref.name, active: ref.active });
95
+ }
96
+ }
97
+ }
98
+
99
+ // Contract marker
100
+ if (contractHash === nodeId && contractHash !== EMPTY_CONTRACT_HASH) {
101
+ markers.push({ kind: 'contract', planned: true });
102
+ }
103
+
104
+ nodeList.push({
105
+ id: toShortId(nodeId),
106
+ markers: markers.length > 0 ? markers : undefined,
107
+ });
108
+ }
109
+
110
+ // Build edges
111
+ const edgeList: GraphEdge[] = [];
112
+
113
+ for (const [, entries] of graph.forwardChain) {
114
+ for (const entry of entries) {
115
+ const status = statusByDirName.get(entry.dirName);
116
+ const icon = status ? STATUS_ICON[status] : '';
117
+ const label = `${entry.dirName}${icon}`;
118
+
119
+ edgeList.push({
120
+ from: toShortId(entry.from),
121
+ to: toShortId(entry.to),
122
+ label,
123
+ ...ifDefined('colorHint', status),
124
+ });
125
+ }
126
+ }
127
+
128
+ // Compute paths to all interesting targets so the default view shows the
129
+ // minimal subgraph that covers everything relevant: contract, DB marker, ref.
130
+ const relevantPaths: string[][] = [];
131
+ const rootId = EMPTY_CONTRACT_HASH;
132
+
133
+ function addPathFromRoot(targetHash: string): void {
134
+ if (!graph.nodes.has(targetHash)) return;
135
+ const raw = findPath(graph, rootId, targetHash);
136
+ if (raw && raw.length > 0) {
137
+ relevantPaths.push([toShortId(rootId), ...raw.map((e) => toShortId(e.to))]);
138
+ }
139
+ }
140
+
141
+ function addPathBetween(fromHash: string, toHash: string): void {
142
+ /* v8 ignore next -- @preserve */
143
+ if (!graph.nodes.has(fromHash) || !graph.nodes.has(toHash)) return;
144
+ const raw = findPath(graph, fromHash, toHash);
145
+ if (raw && raw.length > 0) {
146
+ relevantPaths.push([toShortId(fromHash), ...raw.map((e) => toShortId(e.to))]);
147
+ }
148
+ }
149
+
150
+ // 1. Path to the DB marker
151
+ if (mode === 'online' && markerHash) {
152
+ addPathFromRoot(markerHash);
153
+ }
154
+
155
+ // 2. Path to the ref
156
+ if (activeRefHash && activeRefHash !== markerHash) {
157
+ addPathFromRoot(activeRefHash);
158
+ }
159
+
160
+ // 3. Path(s) to the contract — prefer continuing from marker and/or ref
161
+ // rather than an independent root→contract (which BFS may route through
162
+ // an unrelated branch). When both marker and ref exist, try both paths
163
+ // so that the diamond (two branches converging at the contract) is visible.
164
+ if (contractHash !== EMPTY_CONTRACT_HASH) {
165
+ let contractReached = false;
166
+
167
+ if (markerHash && markerHash !== contractHash) {
168
+ const markerReaches = findPath(graph, markerHash, contractHash);
169
+ if (markerReaches) {
170
+ addPathBetween(markerHash, contractHash);
171
+ contractReached = true;
172
+ }
173
+ }
174
+
175
+ if (activeRefHash && activeRefHash !== markerHash && activeRefHash !== contractHash) {
176
+ const refReaches = findPath(graph, activeRefHash, contractHash);
177
+ if (refReaches) {
178
+ addPathBetween(activeRefHash, contractHash);
179
+ contractReached = true;
180
+ }
181
+ }
182
+
183
+ if (!contractReached && contractHash !== (markerHash ?? activeRefHash)) {
184
+ addPathFromRoot(contractHash);
185
+ }
186
+ }
187
+
188
+ // Fall back: if no paths were found, try the tip of the forward chain.
189
+ if (relevantPaths.length === 0) {
190
+ const lastEdge = [...graph.forwardChain.values()].flat().pop();
191
+ const fallbackHash = lastEdge?.to ?? EMPTY_CONTRACT_HASH;
192
+ addPathFromRoot(fallbackHash);
193
+ }
194
+
195
+ // Spine target for rendering (edge coloring, detached node alignment).
196
+ let spineTargetHash: string;
197
+
198
+ if (activeRefHash && graph.nodes.has(activeRefHash)) {
199
+ spineTargetHash = activeRefHash;
200
+ } else if (contractHash !== EMPTY_CONTRACT_HASH && graph.nodes.has(contractHash)) {
201
+ spineTargetHash = contractHash;
202
+ } else {
203
+ const lastEdge = [...graph.forwardChain.values()].flat().pop();
204
+ spineTargetHash = lastEdge?.to ?? EMPTY_CONTRACT_HASH;
205
+ }
206
+
207
+ // Contract not in attested graph — connect from spine target with a dashed edge
208
+ if (contractHash !== EMPTY_CONTRACT_HASH && !graph.nodes.has(contractHash)) {
209
+ const contractMarkers: NodeMarker[] = [];
210
+ if (mode === 'online' && markerHash === contractHash) {
211
+ contractMarkers.push({ kind: 'db' });
212
+ }
213
+ contractMarkers.push({ kind: 'contract', planned: false });
214
+ nodeList.push({
215
+ id: shortHash(contractHash),
216
+ markers: contractMarkers,
217
+ });
218
+
219
+ const matchingDraft = input.draftEdges?.find((d) => d.to === contractHash);
220
+ const fromHash = matchingDraft?.from ?? spineTargetHash;
221
+ if (graph.nodes.has(fromHash) || fromHash === spineTargetHash) {
222
+ edgeList.push({
223
+ from: toShortId(fromHash),
224
+ to: shortHash(contractHash),
225
+ ...ifDefined('label', matchingDraft ? `${matchingDraft.dirName} [draft]` : undefined),
226
+ style: 'dashed',
227
+ });
228
+ }
229
+ }
230
+
231
+ return {
232
+ graph: new RenderGraph(nodeList, edgeList),
233
+ options: {
234
+ spineTarget: toShortId(spineTargetHash),
235
+ rootId: '∅',
236
+ colorize: true,
237
+ },
238
+ relevantPaths,
239
+ };
240
+ }