@ddt-tools/cli 0.2.0 → 0.2.4

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 (203) hide show
  1. package/dist/advise-tests-YNMKVJCD.js +87 -0
  2. package/dist/advise-tests-YNMKVJCD.js.map +1 -0
  3. package/dist/ai-NTNPYEKZ.js +86 -0
  4. package/dist/ai-NTNPYEKZ.js.map +1 -0
  5. package/dist/anonymize-LERTWUQO.js +139 -0
  6. package/dist/anonymize-LERTWUQO.js.map +1 -0
  7. package/dist/approval-GGZGKIU4.js +73 -0
  8. package/dist/approval-GGZGKIU4.js.map +1 -0
  9. package/dist/approval-chain-GWJKZHVU.js +118 -0
  10. package/dist/approval-chain-GWJKZHVU.js.map +1 -0
  11. package/dist/audit-log-2PH55BU4.js +159 -0
  12. package/dist/audit-log-2PH55BU4.js.map +1 -0
  13. package/dist/backlog-QNXGOUF4.js +76 -0
  14. package/dist/backlog-QNXGOUF4.js.map +1 -0
  15. package/dist/bisect-W3XKKRWG.js +111 -0
  16. package/dist/bisect-W3XKKRWG.js.map +1 -0
  17. package/dist/bookmarks-XVOGXGMC.js +107 -0
  18. package/dist/bookmarks-XVOGXGMC.js.map +1 -0
  19. package/dist/branch-S3I2IJGQ.js +103 -0
  20. package/dist/branch-S3I2IJGQ.js.map +1 -0
  21. package/dist/build-MP3JQEFO.js +20 -0
  22. package/dist/build-MP3JQEFO.js.map +1 -0
  23. package/dist/catalog-3J3NFNXP.js +137 -0
  24. package/dist/catalog-3J3NFNXP.js.map +1 -0
  25. package/dist/changelog-ZQAH3ULB.js +216 -0
  26. package/dist/changelog-ZQAH3ULB.js.map +1 -0
  27. package/dist/chunk-2FT6HXKS.js +55 -0
  28. package/dist/chunk-2FT6HXKS.js.map +1 -0
  29. package/dist/chunk-DGUM43GV.js +11 -0
  30. package/dist/chunk-DGUM43GV.js.map +1 -0
  31. package/dist/chunk-DL3V7UJ2.js +25 -0
  32. package/dist/chunk-DL3V7UJ2.js.map +1 -0
  33. package/dist/chunk-VM2H4LAO.js +15 -0
  34. package/dist/chunk-VM2H4LAO.js.map +1 -0
  35. package/dist/chunk-XFXG347C.js +40 -0
  36. package/dist/chunk-XFXG347C.js.map +1 -0
  37. package/dist/cli.js +499 -19402
  38. package/dist/cli.js.map +1 -1
  39. package/dist/compare-P7JOV76O.js +379 -0
  40. package/dist/compare-P7JOV76O.js.map +1 -0
  41. package/dist/compare-profiles-H33CXZPD.js +219 -0
  42. package/dist/compare-profiles-H33CXZPD.js.map +1 -0
  43. package/dist/completion-ZSNCQKJ2.js +89 -0
  44. package/dist/completion-ZSNCQKJ2.js.map +1 -0
  45. package/dist/connection-CDGVEFUC.js +148 -0
  46. package/dist/connection-CDGVEFUC.js.map +1 -0
  47. package/dist/cost-estimate-S2MKHT2H.js +321 -0
  48. package/dist/cost-estimate-S2MKHT2H.js.map +1 -0
  49. package/dist/data-compare-46ZI7KHL.js +128 -0
  50. package/dist/data-compare-46ZI7KHL.js.map +1 -0
  51. package/dist/data-fit-WGEPLD5S.js +127 -0
  52. package/dist/data-fit-WGEPLD5S.js.map +1 -0
  53. package/dist/deploy-status-4H5KJFRC.js +58 -0
  54. package/dist/deploy-status-4H5KJFRC.js.map +1 -0
  55. package/dist/design-ILX3ZSWW.js +135 -0
  56. package/dist/design-ILX3ZSWW.js.map +1 -0
  57. package/dist/diagnose-WPUL67E4.js +150 -0
  58. package/dist/diagnose-WPUL67E4.js.map +1 -0
  59. package/dist/discover-DEO2R5T6.js +78 -0
  60. package/dist/discover-DEO2R5T6.js.map +1 -0
  61. package/dist/docs-QNY3MUVO.js +183 -0
  62. package/dist/docs-QNY3MUVO.js.map +1 -0
  63. package/dist/drift-FDRNPWQA.js +233 -0
  64. package/dist/drift-FDRNPWQA.js.map +1 -0
  65. package/dist/drift-gate-6BWWWMHW.js +103 -0
  66. package/dist/drift-gate-6BWWWMHW.js.map +1 -0
  67. package/dist/error-lookup-4R3Y4RBC.js +56 -0
  68. package/dist/error-lookup-4R3Y4RBC.js.map +1 -0
  69. package/dist/errorReporting-3LPE2IJY.js +109 -0
  70. package/dist/errorReporting-3LPE2IJY.js.map +1 -0
  71. package/dist/exec-JOLH5LPT.js +122 -0
  72. package/dist/exec-JOLH5LPT.js.map +1 -0
  73. package/dist/explain-NS26WE2Y.js +189 -0
  74. package/dist/explain-NS26WE2Y.js.map +1 -0
  75. package/dist/explorer-GSYYYOAL.js +58 -0
  76. package/dist/explorer-GSYYYOAL.js.map +1 -0
  77. package/dist/extract-4LWEZG4O.js +152 -0
  78. package/dist/extract-4LWEZG4O.js.map +1 -0
  79. package/dist/features-KQV4OFIZ.js +54 -0
  80. package/dist/features-KQV4OFIZ.js.map +1 -0
  81. package/dist/feedback-CBLGXUEG.js +158 -0
  82. package/dist/feedback-CBLGXUEG.js.map +1 -0
  83. package/dist/find-SMXRCZ76.js +176 -0
  84. package/dist/find-SMXRCZ76.js.map +1 -0
  85. package/dist/format-HMGG6MY3.js +277 -0
  86. package/dist/format-HMGG6MY3.js.map +1 -0
  87. package/dist/generate-W7VLBDLI.js +160 -0
  88. package/dist/generate-W7VLBDLI.js.map +1 -0
  89. package/dist/graph-YYL5UYCJ.js +168 -0
  90. package/dist/graph-YYL5UYCJ.js.map +1 -0
  91. package/dist/history-GDRFP4PG.js +184 -0
  92. package/dist/history-GDRFP4PG.js.map +1 -0
  93. package/dist/hosts-DRFZTMIJ.js +45 -0
  94. package/dist/hosts-DRFZTMIJ.js.map +1 -0
  95. package/dist/impact-A4NU6CB2.js +63 -0
  96. package/dist/impact-A4NU6CB2.js.map +1 -0
  97. package/dist/import-2RNYDL4E.js +79 -0
  98. package/dist/import-2RNYDL4E.js.map +1 -0
  99. package/dist/index.cjs +11 -5
  100. package/dist/index.cjs.map +1 -1
  101. package/dist/index.js +8 -2
  102. package/dist/index.js.map +1 -1
  103. package/dist/init-EAOGNGXI.js +54 -0
  104. package/dist/init-EAOGNGXI.js.map +1 -0
  105. package/dist/install-hooks-G3Y5LVXK.js +109 -0
  106. package/dist/install-hooks-G3Y5LVXK.js.map +1 -0
  107. package/dist/license-Z5YSC7XQ.js +43 -0
  108. package/dist/license-Z5YSC7XQ.js.map +1 -0
  109. package/dist/lineage-C5CGVP36.js +555 -0
  110. package/dist/lineage-C5CGVP36.js.map +1 -0
  111. package/dist/lint-AQFPZ3WG.js +144 -0
  112. package/dist/lint-AQFPZ3WG.js.map +1 -0
  113. package/dist/mcp-F7FND5X7.js +343 -0
  114. package/dist/mcp-F7FND5X7.js.map +1 -0
  115. package/dist/migrate-from-dbt-K4ELOWUD.js +156 -0
  116. package/dist/migrate-from-dbt-K4ELOWUD.js.map +1 -0
  117. package/dist/migrate-platform-E7VZFPO5.js +91 -0
  118. package/dist/migrate-platform-E7VZFPO5.js.map +1 -0
  119. package/dist/optimize-WUJ5ZN5Y.js +109 -0
  120. package/dist/optimize-WUJ5ZN5Y.js.map +1 -0
  121. package/dist/perf-UULZSREY.js +200 -0
  122. package/dist/perf-UULZSREY.js.map +1 -0
  123. package/dist/pii-QHU32VML.js +146 -0
  124. package/dist/pii-QHU32VML.js.map +1 -0
  125. package/dist/pilot-BR6GVK32.js +29 -0
  126. package/dist/pilot-BR6GVK32.js.map +1 -0
  127. package/dist/pr-comment-2FOA3EXG.js +81 -0
  128. package/dist/pr-comment-2FOA3EXG.js.map +1 -0
  129. package/dist/preview-XNY422OU.js +46 -0
  130. package/dist/preview-XNY422OU.js.map +1 -0
  131. package/dist/profile-SQTBNKYS.js +98 -0
  132. package/dist/profile-SQTBNKYS.js.map +1 -0
  133. package/dist/promote-FSGUPIPD.js +417 -0
  134. package/dist/promote-FSGUPIPD.js.map +1 -0
  135. package/dist/publish-AYCRMCE2.js +739 -0
  136. package/dist/publish-AYCRMCE2.js.map +1 -0
  137. package/dist/purge-Y5IOTXKA.js +56 -0
  138. package/dist/purge-Y5IOTXKA.js.map +1 -0
  139. package/dist/query-log-SDDGMJLJ.js +112 -0
  140. package/dist/query-log-SDDGMJLJ.js.map +1 -0
  141. package/dist/refactor-TC7S43F2.js +5809 -0
  142. package/dist/refactor-TC7S43F2.js.map +1 -0
  143. package/dist/refresh-MDJYOYV5.js +39 -0
  144. package/dist/refresh-MDJYOYV5.js.map +1 -0
  145. package/dist/replay-E4664A5K.js +118 -0
  146. package/dist/replay-E4664A5K.js.map +1 -0
  147. package/dist/revert-QWQWCJJB.js +111 -0
  148. package/dist/revert-QWQWCJJB.js.map +1 -0
  149. package/dist/review-7CAVLD67.js +164 -0
  150. package/dist/review-7CAVLD67.js.map +1 -0
  151. package/dist/rollback-suggest-C6D5YFCA.js +79 -0
  152. package/dist/rollback-suggest-C6D5YFCA.js.map +1 -0
  153. package/dist/safer-alternative-QR4QEFUV.js +84 -0
  154. package/dist/safer-alternative-QR4QEFUV.js.map +1 -0
  155. package/dist/safety-OFWUFLK4.js +165 -0
  156. package/dist/safety-OFWUFLK4.js.map +1 -0
  157. package/dist/savings-MEBE4TXI.js +95 -0
  158. package/dist/savings-MEBE4TXI.js.map +1 -0
  159. package/dist/scan-secrets-XCUBMLHL.js +54 -0
  160. package/dist/scan-secrets-XCUBMLHL.js.map +1 -0
  161. package/dist/schema-7JZIG6QR.js +447 -0
  162. package/dist/schema-7JZIG6QR.js.map +1 -0
  163. package/dist/script-BMYVBHFR.js +167 -0
  164. package/dist/script-BMYVBHFR.js.map +1 -0
  165. package/dist/search-TA3C3AZT.js +151 -0
  166. package/dist/search-TA3C3AZT.js.map +1 -0
  167. package/dist/seed-W4Q3L2IU.js +101 -0
  168. package/dist/seed-W4Q3L2IU.js.map +1 -0
  169. package/dist/sketch-6B2V6FJV.js +83 -0
  170. package/dist/sketch-6B2V6FJV.js.map +1 -0
  171. package/dist/snapshot-YMVS322L.js +171 -0
  172. package/dist/snapshot-YMVS322L.js.map +1 -0
  173. package/dist/snippets-EVTN63OU.js +74 -0
  174. package/dist/snippets-EVTN63OU.js.map +1 -0
  175. package/dist/standards-FGJW3CQL.js +238 -0
  176. package/dist/standards-FGJW3CQL.js.map +1 -0
  177. package/dist/suggest-V3LVIFZ5.js +44 -0
  178. package/dist/suggest-V3LVIFZ5.js.map +1 -0
  179. package/dist/suggest-constraints-EX2FCWOQ.js +154 -0
  180. package/dist/suggest-constraints-EX2FCWOQ.js.map +1 -0
  181. package/dist/suite-YTQ3CNX5.js +85 -0
  182. package/dist/suite-YTQ3CNX5.js.map +1 -0
  183. package/dist/telemetry-KOIY3NEQ.js +90 -0
  184. package/dist/telemetry-KOIY3NEQ.js.map +1 -0
  185. package/dist/template-MUJ6X6LN.js +396 -0
  186. package/dist/template-MUJ6X6LN.js.map +1 -0
  187. package/dist/test-XFSQHR2S.js +169 -0
  188. package/dist/test-XFSQHR2S.js.map +1 -0
  189. package/dist/trial-GFTGYCR3.js +31 -0
  190. package/dist/trial-GFTGYCR3.js.map +1 -0
  191. package/dist/validate-LFDEZFFH.js +107 -0
  192. package/dist/validate-LFDEZFFH.js.map +1 -0
  193. package/dist/verify-KRDYOJCR.js +76 -0
  194. package/dist/verify-KRDYOJCR.js.map +1 -0
  195. package/dist/watch-FSG23RR3.js +80 -0
  196. package/dist/watch-FSG23RR3.js.map +1 -0
  197. package/dist/xcompare-U4TXTTIR.js +87 -0
  198. package/dist/xcompare-U4TXTTIR.js.map +1 -0
  199. package/package.json +2 -2
  200. package/dist/cli.cjs +0 -19298
  201. package/dist/cli.cjs.map +0 -1
  202. package/dist/cli.d.cts +0 -1
  203. package/dist/cli.d.ts +0 -1
@@ -0,0 +1,168 @@
1
+ import {
2
+ attachExplainFlag,
3
+ runExplain
4
+ } from "./chunk-XFXG347C.js";
5
+ import "./chunk-DGUM43GV.js";
6
+
7
+ // src/commands/graph.ts
8
+ import { promises as fs } from "fs";
9
+ import path from "path";
10
+ import { Command } from "commander";
11
+ import {
12
+ buildDependencyGraph,
13
+ diffDependencyGraphs,
14
+ loadProject,
15
+ pac,
16
+ parseProjectModel,
17
+ renderGraphDot,
18
+ renderGraphMermaid
19
+ } from "@ddt-tools/core";
20
+ function graphCommand() {
21
+ const cmd = new Command("graph");
22
+ cmd.description("Build an object-dependency DAG and emit it as Mermaid or DOT.").requiredOption("--source <path>", ".ddtproj or .ddtpac to analyze.").option("-o, --out <path>", "Output file path. Defaults to stdout.").option(
23
+ "--format <fmt>",
24
+ "mermaid | dot | md (Mermaid wrapped in a markdown fenced block)",
25
+ "mermaid"
26
+ ).option("--json", "Emit the raw { nodes, edges } JSON instead of a renderer.", false).option(
27
+ "--compare-to <path>",
28
+ "A second .ddtproj or .ddtpac. When set, emit added / removed / changed nodes + edges (the DAG delta) instead of the static graph."
29
+ ).action(async (opts) => {
30
+ const model = await loadModel(String(opts.source));
31
+ const graph = buildDependencyGraph(model);
32
+ let diff;
33
+ if (opts.compareTo) {
34
+ const otherModel = await loadModel(String(opts.compareTo));
35
+ const otherGraph = buildDependencyGraph(otherModel);
36
+ diff = diffDependencyGraphs(otherGraph, graph);
37
+ }
38
+ const fmt = String(opts.format ?? "mermaid").toLowerCase();
39
+ let payload;
40
+ if (opts.json) {
41
+ payload = JSON.stringify(diff ?? graph, null, 2);
42
+ } else if (diff) {
43
+ payload = renderDiffMarkdown(diff);
44
+ } else if (fmt === "mermaid") {
45
+ payload = renderGraphMermaid(graph);
46
+ } else if (fmt === "dot") {
47
+ payload = renderGraphDot(graph);
48
+ } else if (fmt === "md") {
49
+ payload = `# Schema dependency graph
50
+
51
+ ${graph.nodes.length} node(s), ${graph.edges.length} edge(s).
52
+
53
+ \`\`\`mermaid
54
+ ${renderGraphMermaid(graph)}
55
+ \`\`\`
56
+ `;
57
+ } else {
58
+ throw new Error(`Unknown --format: ${fmt}. Use mermaid | dot | md.`);
59
+ }
60
+ if (opts.out) {
61
+ const out = path.resolve(String(opts.out));
62
+ await fs.mkdir(path.dirname(out), { recursive: true });
63
+ await fs.writeFile(out, payload + (payload.endsWith("\n") ? "" : "\n"), "utf8");
64
+ console.error(
65
+ `Wrote ${out} (${payload.length} bytes, ${graph.nodes.length} nodes, ${graph.edges.length} edges).`
66
+ );
67
+ } else {
68
+ process.stdout.write(payload + (payload.endsWith("\n") ? "" : "\n"));
69
+ }
70
+ await runExplain(
71
+ {
72
+ feature: "graph.explain",
73
+ systemPrompt: diff ? "You are a data-platform architect narrating a dependency-graph DELTA between two snapshots. Walk through added / removed / changed nodes and edges in plain English. Call out structural shifts (new sources, dropped consumers, type changes) and likely-but-not-yet-broken downstream consequences." : "You are a data-platform architect explaining a dependency graph. Describe the dominant data-flow shape, point out hot or fragile nodes, and recommend any decomposition the graph suggests."
74
+ },
75
+ opts,
76
+ () => diff ? buildDiffPrompt(diff) : buildGraphPrompt(graph)
77
+ );
78
+ });
79
+ attachExplainFlag(cmd);
80
+ return cmd;
81
+ }
82
+ function renderDiffMarkdown(d) {
83
+ const lines = ["# Dependency-graph delta", ""];
84
+ lines.push(
85
+ `**Summary**: ${d.summary.nodeDelta >= 0 ? "+" : ""}${d.summary.nodeDelta} nodes, ${d.summary.edgeDelta >= 0 ? "+" : ""}${d.summary.edgeDelta} edges. ${d.addedNodes.length} added node(s), ${d.removedNodes.length} removed node(s), ${d.changedNodes.length} changed node(s).`
86
+ );
87
+ lines.push("");
88
+ if (d.addedNodes.length > 0) {
89
+ lines.push("## Added nodes");
90
+ for (const n of d.addedNodes) lines.push(`- \`${n.fqn}\` (${n.objectType})`);
91
+ lines.push("");
92
+ }
93
+ if (d.removedNodes.length > 0) {
94
+ lines.push("## Removed nodes");
95
+ for (const n of d.removedNodes) lines.push(`- \`${n.fqn}\` (${n.objectType})`);
96
+ lines.push("");
97
+ }
98
+ if (d.changedNodes.length > 0) {
99
+ lines.push("## Changed nodes (objectType)");
100
+ for (const c of d.changedNodes) lines.push(`- \`${c.fqn}\`: ${c.before} \u2192 ${c.after}`);
101
+ lines.push("");
102
+ }
103
+ if (d.addedEdges.length > 0) {
104
+ lines.push("## Added edges");
105
+ for (const e of d.addedEdges) lines.push(`- \`${e.from}\` \u2192 \`${e.to}\` (${e.kind})`);
106
+ lines.push("");
107
+ }
108
+ if (d.removedEdges.length > 0) {
109
+ lines.push("## Removed edges");
110
+ for (const e of d.removedEdges) lines.push(`- \`${e.from}\` \u2192 \`${e.to}\` (${e.kind})`);
111
+ }
112
+ return lines.join("\n");
113
+ }
114
+ function buildGraphPrompt(g) {
115
+ const sample = g.edges.slice(0, 40).map((e) => ` - ${e.from} -> ${e.to}`);
116
+ return [
117
+ `Dependency graph: ${g.nodes.length} nodes, ${g.edges.length} edges.`,
118
+ "",
119
+ "Edges (up to 40):",
120
+ ...sample,
121
+ "",
122
+ "Narrate this graph in plain English."
123
+ ].join("\n");
124
+ }
125
+ function buildDiffPrompt(d) {
126
+ const lines = [];
127
+ lines.push(
128
+ `Graph delta: ${d.summary.nodeDelta >= 0 ? "+" : ""}${d.summary.nodeDelta} nodes, ${d.summary.edgeDelta >= 0 ? "+" : ""}${d.summary.edgeDelta} edges.`
129
+ );
130
+ if (d.addedNodes.length > 0) {
131
+ lines.push("", `Added nodes (${d.addedNodes.length}, up to 20):`);
132
+ for (const n of d.addedNodes.slice(0, 20)) lines.push(` + ${n.fqn} (${n.objectType})`);
133
+ }
134
+ if (d.removedNodes.length > 0) {
135
+ lines.push("", `Removed nodes (${d.removedNodes.length}, up to 20):`);
136
+ for (const n of d.removedNodes.slice(0, 20)) lines.push(` - ${n.fqn} (${n.objectType})`);
137
+ }
138
+ if (d.changedNodes.length > 0) {
139
+ lines.push("", `Changed nodes (${d.changedNodes.length}):`);
140
+ for (const c of d.changedNodes.slice(0, 20))
141
+ lines.push(` ~ ${c.fqn}: ${c.before} -> ${c.after}`);
142
+ }
143
+ if (d.addedEdges.length > 0) {
144
+ lines.push("", `Added edges (${d.addedEdges.length}, up to 20):`);
145
+ for (const e of d.addedEdges.slice(0, 20)) lines.push(` + ${e.from} -> ${e.to}`);
146
+ }
147
+ if (d.removedEdges.length > 0) {
148
+ lines.push("", `Removed edges (${d.removedEdges.length}, up to 20):`);
149
+ for (const e of d.removedEdges.slice(0, 20)) lines.push(` - ${e.from} -> ${e.to}`);
150
+ }
151
+ lines.push(
152
+ "",
153
+ "Narrate this delta in plain English. Highlight structural shifts and likely downstream consequences."
154
+ );
155
+ return lines.join("\n");
156
+ }
157
+ async function loadModel(sourcePath) {
158
+ if (sourcePath.endsWith(".ddtpac")) {
159
+ const c = await pac.readPac(sourcePath);
160
+ return c.model;
161
+ }
162
+ const loaded = await loadProject(sourcePath);
163
+ return await parseProjectModel(loaded);
164
+ }
165
+ export {
166
+ graphCommand
167
+ };
168
+ //# sourceMappingURL=graph-YYL5UYCJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/graph.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n buildDependencyGraph,\n diffDependencyGraphs,\n loadProject,\n pac,\n parseProjectModel,\n renderGraphDot,\n renderGraphMermaid,\n type DatabricksObject,\n type DependencyGraph,\n type DependencyGraphDiff,\n} from '@ddt-tools/core';\nimport { attachExplainFlag, runExplain } from '../util/ai-explain.js';\n\n/**\n * `ddt graph` — emit an object-dependency DAG (Mermaid or DOT) from a\n * `.ddtproj` or `.ddtpac`. Lets reviewers see blast radius before a\n * drop. Inspired by DBeaver's \"Object References\" panel — but at the\n * cross-object level rather than column level.\n *\n * With `--compare-to <other-source>`, diffs the two graphs and (when\n * `--explain` is on) narrates the DAG changes in plain English — the\n * AI Phase 6 \"lineage explanation\" surface.\n */\nexport function graphCommand(): Command {\n const cmd = new Command('graph');\n cmd\n .description('Build an object-dependency DAG and emit it as Mermaid or DOT.')\n .requiredOption('--source <path>', '.ddtproj or .ddtpac to analyze.')\n .option('-o, --out <path>', 'Output file path. Defaults to stdout.')\n .option(\n '--format <fmt>',\n 'mermaid | dot | md (Mermaid wrapped in a markdown fenced block)',\n 'mermaid',\n )\n .option('--json', 'Emit the raw { nodes, edges } JSON instead of a renderer.', false)\n .option(\n '--compare-to <path>',\n 'A second .ddtproj or .ddtpac. When set, emit added / removed / changed nodes + edges (the DAG delta) instead of the static graph.',\n )\n .action(async (opts) => {\n const model = await loadModel(String(opts.source));\n const graph = buildDependencyGraph(model);\n\n let diff: DependencyGraphDiff | undefined;\n if (opts.compareTo) {\n const otherModel = await loadModel(String(opts.compareTo));\n const otherGraph = buildDependencyGraph(otherModel);\n diff = diffDependencyGraphs(otherGraph, graph);\n }\n\n const fmt = String(opts.format ?? 'mermaid').toLowerCase();\n let payload: string;\n if (opts.json) {\n payload = JSON.stringify(diff ?? graph, null, 2);\n } else if (diff) {\n payload = renderDiffMarkdown(diff);\n } else if (fmt === 'mermaid') {\n payload = renderGraphMermaid(graph);\n } else if (fmt === 'dot') {\n payload = renderGraphDot(graph);\n } else if (fmt === 'md') {\n payload = `# Schema dependency graph\\n\\n${graph.nodes.length} node(s), ${graph.edges.length} edge(s).\\n\\n\\`\\`\\`mermaid\\n${renderGraphMermaid(graph)}\\n\\`\\`\\`\\n`;\n } else {\n throw new Error(`Unknown --format: ${fmt}. Use mermaid | dot | md.`);\n }\n\n if (opts.out) {\n const out = path.resolve(String(opts.out));\n await fs.mkdir(path.dirname(out), { recursive: true });\n await fs.writeFile(out, payload + (payload.endsWith('\\n') ? '' : '\\n'), 'utf8');\n console.error(\n `Wrote ${out} (${payload.length} bytes, ${graph.nodes.length} nodes, ${graph.edges.length} edges).`,\n );\n } else {\n process.stdout.write(payload + (payload.endsWith('\\n') ? '' : '\\n'));\n }\n\n await runExplain(\n {\n feature: 'graph.explain',\n systemPrompt: diff\n ? 'You are a data-platform architect narrating a dependency-graph DELTA between two snapshots. Walk through added / removed / changed nodes and edges in plain English. Call out structural shifts (new sources, dropped consumers, type changes) and likely-but-not-yet-broken downstream consequences.'\n : 'You are a data-platform architect explaining a dependency graph. Describe the dominant data-flow shape, point out hot or fragile nodes, and recommend any decomposition the graph suggests.',\n },\n opts as { explain?: boolean },\n () => (diff ? buildDiffPrompt(diff) : buildGraphPrompt(graph)),\n );\n });\n attachExplainFlag(cmd);\n return cmd;\n}\n\nfunction renderDiffMarkdown(d: DependencyGraphDiff): string {\n const lines: string[] = ['# Dependency-graph delta', ''];\n lines.push(\n `**Summary**: ${d.summary.nodeDelta >= 0 ? '+' : ''}${d.summary.nodeDelta} nodes, ` +\n `${d.summary.edgeDelta >= 0 ? '+' : ''}${d.summary.edgeDelta} edges. ` +\n `${d.addedNodes.length} added node(s), ${d.removedNodes.length} removed node(s), ` +\n `${d.changedNodes.length} changed node(s).`,\n );\n lines.push('');\n if (d.addedNodes.length > 0) {\n lines.push('## Added nodes');\n for (const n of d.addedNodes) lines.push(`- \\`${n.fqn}\\` (${n.objectType})`);\n lines.push('');\n }\n if (d.removedNodes.length > 0) {\n lines.push('## Removed nodes');\n for (const n of d.removedNodes) lines.push(`- \\`${n.fqn}\\` (${n.objectType})`);\n lines.push('');\n }\n if (d.changedNodes.length > 0) {\n lines.push('## Changed nodes (objectType)');\n for (const c of d.changedNodes) lines.push(`- \\`${c.fqn}\\`: ${c.before} → ${c.after}`);\n lines.push('');\n }\n if (d.addedEdges.length > 0) {\n lines.push('## Added edges');\n for (const e of d.addedEdges) lines.push(`- \\`${e.from}\\` → \\`${e.to}\\` (${e.kind})`);\n lines.push('');\n }\n if (d.removedEdges.length > 0) {\n lines.push('## Removed edges');\n for (const e of d.removedEdges) lines.push(`- \\`${e.from}\\` → \\`${e.to}\\` (${e.kind})`);\n }\n return lines.join('\\n');\n}\n\nfunction buildGraphPrompt(g: DependencyGraph): string {\n const sample = g.edges.slice(0, 40).map((e) => ` - ${e.from} -> ${e.to}`);\n return [\n `Dependency graph: ${g.nodes.length} nodes, ${g.edges.length} edges.`,\n '',\n 'Edges (up to 40):',\n ...sample,\n '',\n 'Narrate this graph in plain English.',\n ].join('\\n');\n}\n\nfunction buildDiffPrompt(d: DependencyGraphDiff): string {\n const lines: string[] = [];\n lines.push(\n `Graph delta: ${d.summary.nodeDelta >= 0 ? '+' : ''}${d.summary.nodeDelta} nodes, ` +\n `${d.summary.edgeDelta >= 0 ? '+' : ''}${d.summary.edgeDelta} edges.`,\n );\n if (d.addedNodes.length > 0) {\n lines.push('', `Added nodes (${d.addedNodes.length}, up to 20):`);\n for (const n of d.addedNodes.slice(0, 20)) lines.push(` + ${n.fqn} (${n.objectType})`);\n }\n if (d.removedNodes.length > 0) {\n lines.push('', `Removed nodes (${d.removedNodes.length}, up to 20):`);\n for (const n of d.removedNodes.slice(0, 20)) lines.push(` - ${n.fqn} (${n.objectType})`);\n }\n if (d.changedNodes.length > 0) {\n lines.push('', `Changed nodes (${d.changedNodes.length}):`);\n for (const c of d.changedNodes.slice(0, 20))\n lines.push(` ~ ${c.fqn}: ${c.before} -> ${c.after}`);\n }\n if (d.addedEdges.length > 0) {\n lines.push('', `Added edges (${d.addedEdges.length}, up to 20):`);\n for (const e of d.addedEdges.slice(0, 20)) lines.push(` + ${e.from} -> ${e.to}`);\n }\n if (d.removedEdges.length > 0) {\n lines.push('', `Removed edges (${d.removedEdges.length}, up to 20):`);\n for (const e of d.removedEdges.slice(0, 20)) lines.push(` - ${e.from} -> ${e.to}`);\n }\n lines.push(\n '',\n 'Narrate this delta in plain English. Highlight structural shifts and likely downstream consequences.',\n );\n return lines.join('\\n');\n}\n\nasync function loadModel(sourcePath: string): Promise<DatabricksObject[]> {\n if (sourcePath.endsWith('.ddtpac')) {\n const c = await pac.readPac(sourcePath);\n return c.model;\n }\n const loaded = await loadProject(sourcePath);\n return (await parseProjectModel(loaded)) as DatabricksObject[];\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,184 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/history.ts
4
+ import { promises as fs } from "fs";
5
+ import path from "path";
6
+ import { Command } from "commander";
7
+ import { createConnection, getProfile, queryHistory } from "@ddt-tools/core";
8
+ function historyCommand() {
9
+ const cmd = new Command("history");
10
+ cmd.description(
11
+ "List deploy manifests (default) or query live system.query.* via --last/--query/--verify."
12
+ ).option(
13
+ "--dir <path>",
14
+ "Directory containing *.json manifests (manifest mode).",
15
+ "./.ddt/history"
16
+ ).option("--limit <n>", "Manifest mode: most-recent N entries. Live mode: max rows.", "20").option("--json", "Emit JSON instead of human-readable table.", false).option(
17
+ "--full-sql",
18
+ "Include full SQL text in the output (default truncated to 120 chars).",
19
+ false
20
+ ).option(
21
+ "--connection <profile>",
22
+ "Connection profile (required for --last / --query / --verify)."
23
+ ).option("--last [n]", "Live mode: show the N most-recent queries. Default 20.").option("--query <id>", "Live mode: look up a single query by its statement_id.").option(
24
+ "--verify <tag>",
25
+ "Live mode: read all queries tagged with this string. Use the `DDT-TAG` auto-tag from `ddt publish --apply`."
26
+ ).option("--since <iso>", "Live mode: only include entries after this ISO 8601 timestamp.").action(
27
+ async (opts) => {
28
+ const isLive = opts.last !== void 0 || opts.query !== void 0 || opts.verify !== void 0;
29
+ if (isLive) await runLive(opts);
30
+ else await runManifests(opts);
31
+ }
32
+ );
33
+ return cmd;
34
+ }
35
+ async function runManifests(opts) {
36
+ const dir = path.resolve(String(opts.dir ?? "./.ddt/history"));
37
+ let files;
38
+ try {
39
+ const entries2 = await fs.readdir(dir);
40
+ files = entries2.filter((f) => f.endsWith(".json")).map((f) => path.join(dir, f));
41
+ } catch (err) {
42
+ if (err.code === "ENOENT") {
43
+ console.error(
44
+ `No history directory at ${dir}. Pass --dir <path> or pipe deploys to write manifests there.`
45
+ );
46
+ process.exitCode = 1;
47
+ return;
48
+ }
49
+ throw err;
50
+ }
51
+ const entries = [];
52
+ for (const f of files) {
53
+ try {
54
+ const raw = await fs.readFile(f, "utf8");
55
+ const m = JSON.parse(raw);
56
+ const stat = await fs.stat(f);
57
+ entries.push({ path: f, mtime: stat.mtime, manifest: m });
58
+ } catch {
59
+ }
60
+ }
61
+ entries.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
62
+ const limit = parseInt(String(opts.limit ?? 20), 10);
63
+ const visible = entries.slice(0, limit > 0 ? limit : entries.length);
64
+ if (opts.json) {
65
+ console.log(
66
+ JSON.stringify(
67
+ visible.map((e) => ({ path: e.path, mtime: e.mtime.toISOString(), ...e.manifest })),
68
+ null,
69
+ 2
70
+ )
71
+ );
72
+ return;
73
+ }
74
+ if (visible.length === 0) {
75
+ console.log(`No manifests found in ${dir}.`);
76
+ return;
77
+ }
78
+ console.log(`History: ${visible.length} of ${entries.length} deploys at ${dir}`);
79
+ console.log("");
80
+ console.log(
81
+ " DEPLOYED AT WORKSPACE FINAL STATE STEPS"
82
+ );
83
+ console.log(
84
+ " \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500"
85
+ );
86
+ for (const e of visible) {
87
+ const m = e.manifest;
88
+ const when = m.deployedAt ? new Date(m.deployedAt).toISOString().slice(0, 19).replace("T", " ") : e.mtime.toISOString().slice(0, 19).replace("T", " ");
89
+ const host = (m.workspaceHost ?? "?").padEnd(34).slice(0, 34);
90
+ const state = (m.finalState ?? "?").padEnd(18).slice(0, 18);
91
+ const succ = m.steps?.filter((s) => s.status === "SUCCESS").length ?? 0;
92
+ const tot = m.steps?.length ?? 0;
93
+ console.log(` ${when} ${host} ${state} ${succ}/${tot}`);
94
+ if (opts.fullSql) {
95
+ for (const s of m.steps ?? []) {
96
+ if (s.forwardSql) console.log(` [forward] ${s.fqn ?? ""}
97
+ ${indent(s.forwardSql, 8)}`);
98
+ }
99
+ }
100
+ }
101
+ console.log("");
102
+ console.log(
103
+ `Pass --json for machine-readable output, --limit <n> to control depth, --full-sql to expand each step.`
104
+ );
105
+ }
106
+ async function runLive(opts) {
107
+ if (!opts.connection) {
108
+ console.error("Live history mode requires --connection <profile>.");
109
+ process.exitCode = 1;
110
+ return;
111
+ }
112
+ const profile = await getProfile(String(opts.connection));
113
+ const conn = createConnection(profile);
114
+ await conn.connect();
115
+ try {
116
+ const reader = new queryHistory.DatabricksQueryHistoryReader(conn);
117
+ const limit = Math.max(1, parseInt(String(opts.limit ?? "20"), 10) || 20);
118
+ const since = opts.since ? new Date(opts.since) : void 0;
119
+ let entries = [];
120
+ if (opts.query) {
121
+ const e = await reader.readById(String(opts.query));
122
+ entries = e ? [e] : [];
123
+ } else if (opts.verify) {
124
+ entries = await reader.readByTag(String(opts.verify), {
125
+ ...since ? { since } : {},
126
+ limit
127
+ });
128
+ } else {
129
+ const n = typeof opts.last === "string" ? parseInt(opts.last, 10) || limit : limit;
130
+ const user = profile.auth.user ?? profile.auth.principalEmail ?? "";
131
+ entries = await reader.readByUser(user, {
132
+ ...since ? { since } : {},
133
+ limit: n
134
+ });
135
+ }
136
+ if (opts.json) {
137
+ process.stdout.write(JSON.stringify(entries, null, 2) + "\n");
138
+ return;
139
+ }
140
+ renderEntries(entries, Boolean(opts.fullSql));
141
+ } finally {
142
+ await conn.disconnect();
143
+ }
144
+ }
145
+ function renderEntries(entries, fullSql) {
146
+ if (entries.length === 0) {
147
+ console.log("No matching queries found.");
148
+ return;
149
+ }
150
+ console.log(`${entries.length} quer${entries.length === 1 ? "y" : "ies"}:`);
151
+ console.log("");
152
+ console.log(" STARTED STATUS DURATION USER STATEMENT_ID");
153
+ console.log(
154
+ " \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
155
+ );
156
+ for (const e of entries) {
157
+ const when = e.startedAt.slice(0, 19).replace("T", " ");
158
+ const status = e.status.toUpperCase().padEnd(8);
159
+ const dur = `${e.durationMs}ms`.padEnd(8);
160
+ const user = (e.user ?? "?").padEnd(20).slice(0, 20);
161
+ console.log(` ${when} ${status} ${dur} ${user} ${e.queryId}`);
162
+ const sql = fullSql ? e.sqlText : truncate(e.sqlText, 120);
163
+ if (sql)
164
+ console.log(
165
+ ` ${sql.split("\n").map((l) => l.trim()).filter(Boolean).join(" ")}`
166
+ );
167
+ if (e.queryTag) console.log(` tag: ${e.queryTag}`);
168
+ if (e.errorMessage) console.log(` ERROR: ${e.errorMessage}`);
169
+ }
170
+ }
171
+ function indent(text, n) {
172
+ const pad = " ".repeat(n);
173
+ return text.split("\n").map((l) => pad + l).join("\n");
174
+ }
175
+ function truncate(s, max) {
176
+ if (!s) return "";
177
+ const collapsed = s.replace(/\s+/g, " ").trim();
178
+ if (collapsed.length <= max) return collapsed;
179
+ return collapsed.slice(0, max - 1) + "\u2026";
180
+ }
181
+ export {
182
+ historyCommand
183
+ };
184
+ //# sourceMappingURL=history-GDRFP4PG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/history.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { createConnection, getProfile, queryHistory } from '@ddt-tools/core';\n\ntype QueryHistoryEntry = queryHistory.QueryHistoryEntry;\n\n/**\n * `ddt history` — two modes.\n *\n * 1. **Manifest mode** (default) — read deploy manifests from a\n * directory and show a chronological summary.\n *\n * 2. **Live mode** (triggered by `--last`, `--query`, or `--verify`)\n * — query the workspace `system.query.*` tables via the\n * DatabricksQueryHistoryReader. Requires `--connection <profile>`.\n *\n * Mirrors `sdt history`.\n */\ninterface Manifest {\n version: 1;\n deployedAt?: string;\n workspaceHost?: string;\n finalState?: string;\n failedStepId?: string | null;\n steps?: Array<{\n status?: string;\n objectType?: string;\n fqn?: string;\n forwardSql?: string;\n reverseSql?: string;\n }>;\n}\n\nexport function historyCommand(): Command {\n const cmd = new Command('history');\n cmd\n .description(\n 'List deploy manifests (default) or query live system.query.* via --last/--query/--verify.',\n )\n .option(\n '--dir <path>',\n 'Directory containing *.json manifests (manifest mode).',\n './.ddt/history',\n )\n .option('--limit <n>', 'Manifest mode: most-recent N entries. Live mode: max rows.', '20')\n .option('--json', 'Emit JSON instead of human-readable table.', false)\n .option(\n '--full-sql',\n 'Include full SQL text in the output (default truncated to 120 chars).',\n false,\n )\n .option(\n '--connection <profile>',\n 'Connection profile (required for --last / --query / --verify).',\n )\n .option('--last [n]', 'Live mode: show the N most-recent queries. Default 20.')\n .option('--query <id>', 'Live mode: look up a single query by its statement_id.')\n .option(\n '--verify <tag>',\n 'Live mode: read all queries tagged with this string. Use the `DDT-TAG` auto-tag from `ddt publish --apply`.',\n )\n .option('--since <iso>', 'Live mode: only include entries after this ISO 8601 timestamp.')\n .action(\n async (opts: {\n dir?: string;\n limit?: string;\n json?: boolean;\n fullSql?: boolean;\n connection?: string;\n last?: boolean | string;\n query?: string;\n verify?: string;\n since?: string;\n }) => {\n const isLive =\n opts.last !== undefined || opts.query !== undefined || opts.verify !== undefined;\n if (isLive) await runLive(opts);\n else await runManifests(opts);\n },\n );\n return cmd;\n}\n\nasync function runManifests(opts: {\n dir?: string;\n limit?: string;\n json?: boolean;\n fullSql?: boolean;\n}): Promise<void> {\n const dir = path.resolve(String(opts.dir ?? './.ddt/history'));\n let files: string[];\n try {\n const entries = await fs.readdir(dir);\n files = entries.filter((f) => f.endsWith('.json')).map((f) => path.join(dir, f));\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n console.error(\n `No history directory at ${dir}. Pass --dir <path> or pipe deploys to write manifests there.`,\n );\n process.exitCode = 1;\n return;\n }\n throw err;\n }\n\n const entries: Array<{ path: string; mtime: Date; manifest: Manifest }> = [];\n for (const f of files) {\n try {\n const raw = await fs.readFile(f, 'utf8');\n const m = JSON.parse(raw) as Manifest;\n const stat = await fs.stat(f);\n entries.push({ path: f, mtime: stat.mtime, manifest: m });\n } catch {\n // skip files that don't parse\n }\n }\n entries.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n\n const limit = parseInt(String(opts.limit ?? 20), 10);\n const visible = entries.slice(0, limit > 0 ? limit : entries.length);\n\n if (opts.json) {\n console.log(\n JSON.stringify(\n visible.map((e) => ({ path: e.path, mtime: e.mtime.toISOString(), ...e.manifest })),\n null,\n 2,\n ),\n );\n return;\n }\n\n if (visible.length === 0) {\n console.log(`No manifests found in ${dir}.`);\n return;\n }\n console.log(`History: ${visible.length} of ${entries.length} deploys at ${dir}`);\n console.log('');\n console.log(\n ' DEPLOYED AT WORKSPACE FINAL STATE STEPS',\n );\n console.log(\n ' ─────────────────── ────────────────────────────────── ────────────────── ─────',\n );\n for (const e of visible) {\n const m = e.manifest;\n const when = m.deployedAt\n ? new Date(m.deployedAt).toISOString().slice(0, 19).replace('T', ' ')\n : e.mtime.toISOString().slice(0, 19).replace('T', ' ');\n const host = (m.workspaceHost ?? '?').padEnd(34).slice(0, 34);\n const state = (m.finalState ?? '?').padEnd(18).slice(0, 18);\n const succ = m.steps?.filter((s) => s.status === 'SUCCESS').length ?? 0;\n const tot = m.steps?.length ?? 0;\n console.log(` ${when} ${host} ${state} ${succ}/${tot}`);\n if (opts.fullSql) {\n for (const s of m.steps ?? []) {\n if (s.forwardSql) console.log(` [forward] ${s.fqn ?? ''}\\n${indent(s.forwardSql, 8)}`);\n }\n }\n }\n console.log('');\n console.log(\n `Pass --json for machine-readable output, --limit <n> to control depth, --full-sql to expand each step.`,\n );\n}\n\nasync function runLive(opts: {\n connection?: string;\n last?: boolean | string;\n query?: string;\n verify?: string;\n since?: string;\n limit?: string;\n json?: boolean;\n fullSql?: boolean;\n}): Promise<void> {\n if (!opts.connection) {\n console.error('Live history mode requires --connection <profile>.');\n process.exitCode = 1;\n return;\n }\n const profile = await getProfile(String(opts.connection));\n const conn = createConnection(profile);\n await conn.connect();\n try {\n const reader = new queryHistory.DatabricksQueryHistoryReader(conn);\n const limit = Math.max(1, parseInt(String(opts.limit ?? '20'), 10) || 20);\n const since = opts.since ? new Date(opts.since) : undefined;\n\n let entries: QueryHistoryEntry[] = [];\n if (opts.query) {\n const e = await reader.readById(String(opts.query));\n entries = e ? [e] : [];\n } else if (opts.verify) {\n entries = await reader.readByTag(String(opts.verify), {\n ...(since ? { since } : {}),\n limit,\n });\n } else {\n const n = typeof opts.last === 'string' ? parseInt(opts.last, 10) || limit : limit;\n const user =\n (profile.auth as { user?: string; principalEmail?: string }).user ??\n (profile.auth as { principalEmail?: string }).principalEmail ??\n '';\n entries = await reader.readByUser(user, {\n ...(since ? { since } : {}),\n limit: n,\n });\n }\n\n if (opts.json) {\n process.stdout.write(JSON.stringify(entries, null, 2) + '\\n');\n return;\n }\n renderEntries(entries, Boolean(opts.fullSql));\n } finally {\n await conn.disconnect();\n }\n}\n\nfunction renderEntries(entries: readonly QueryHistoryEntry[], fullSql: boolean): void {\n if (entries.length === 0) {\n console.log('No matching queries found.');\n return;\n }\n console.log(`${entries.length} quer${entries.length === 1 ? 'y' : 'ies'}:`);\n console.log('');\n console.log(' STARTED STATUS DURATION USER STATEMENT_ID');\n console.log(\n ' ─────────────────── ──────── ──────── ───────────────────── ────────────────────────────',\n );\n for (const e of entries) {\n const when = e.startedAt.slice(0, 19).replace('T', ' ');\n const status = e.status.toUpperCase().padEnd(8);\n const dur = `${e.durationMs}ms`.padEnd(8);\n const user = (e.user ?? '?').padEnd(20).slice(0, 20);\n console.log(` ${when} ${status} ${dur} ${user} ${e.queryId}`);\n const sql = fullSql ? e.sqlText : truncate(e.sqlText, 120);\n if (sql)\n console.log(\n ` ${sql\n .split('\\n')\n .map((l: string) => l.trim())\n .filter(Boolean)\n .join(' ')}`,\n );\n if (e.queryTag) console.log(` tag: ${e.queryTag}`);\n if (e.errorMessage) console.log(` ERROR: ${e.errorMessage}`);\n }\n}\n\nfunction indent(text: string, n: number): string {\n const pad = ' '.repeat(n);\n return text\n .split('\\n')\n .map((l) => pad + l)\n .join('\\n');\n}\n\nfunction truncate(s: string, max: number): string {\n if (!s) return '';\n const collapsed = s.replace(/\\s+/g, ' ').trim();\n if (collapsed.length <= max) return collapsed;\n return collapsed.slice(0, max - 1) + '…';\n}\n"],"mappings":"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","names":["entries"]}
@@ -0,0 +1,45 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/hosts.ts
4
+ import { Command } from "commander";
5
+ import { hosts as hostsApi } from "@ddt-tools/core";
6
+ function hostsCommand() {
7
+ const cmd = new Command("hosts");
8
+ cmd.description(
9
+ "Browse every host DDT plugs into \u2014 CLI, VS Code, MCP agents, Databricks Workflows, dbt, Airflow, etc. Shows supported versions and the host-specific tailoring notes."
10
+ );
11
+ cmd.command("list").description("List every host adapter with its status and version range.").action(() => {
12
+ const rows = hostsApi.listHosts();
13
+ const nameWidth = Math.max(...rows.map((h) => h.name.length), 4);
14
+ let lastKind = "";
15
+ for (const host of rows) {
16
+ if (host.kind !== lastKind) {
17
+ if (lastKind !== "") process.stdout.write("\n");
18
+ process.stdout.write(`\u2500\u2500 ${host.kind.toUpperCase()} \u2500\u2500
19
+ `);
20
+ lastKind = host.kind;
21
+ }
22
+ const pad = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
23
+ const versions = hostsApi.hostSummaryLine(host).split(" \xB7 ")[1] ?? "";
24
+ process.stdout.write(` ${pad(host.name, nameWidth)} ${versions}
25
+ `);
26
+ }
27
+ process.stdout.write(
28
+ "\nRun `ddt hosts show <id>` for tailoring notes. See docs/HOST_INTEGRATIONS.md for the per-host playbook.\n"
29
+ );
30
+ });
31
+ cmd.command("show <id>").description("Print supported versions, idioms, and tailoring notes for one host.").action((id) => {
32
+ const host = hostsApi.getHost(id);
33
+ if (!host) {
34
+ console.error(`Unknown host id: "${id}". Run \`ddt hosts list\` to see all ids.`);
35
+ process.exitCode = 1;
36
+ return;
37
+ }
38
+ process.stdout.write(hostsApi.hostDetailMessage(host) + "\n");
39
+ });
40
+ return cmd;
41
+ }
42
+ export {
43
+ hostsCommand
44
+ };
45
+ //# sourceMappingURL=hosts-DRFZTMIJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/hosts.ts"],"sourcesContent":["/**\n * `ddt hosts` — browse every host DDT plugs into, with version-support\n * + tailoring notes. Companion to `ddt features`: features tell you\n * what we do; hosts tell you where you can do it.\n */\nimport { Command } from 'commander';\nimport { hosts as hostsApi } from '@ddt-tools/core';\n\nexport function hostsCommand(): Command {\n const cmd = new Command('hosts');\n cmd.description(\n 'Browse every host DDT plugs into — CLI, VS Code, MCP agents, Databricks Workflows, dbt, Airflow, etc. Shows supported versions and the host-specific tailoring notes.',\n );\n\n cmd\n .command('list')\n .description('List every host adapter with its status and version range.')\n .action(() => {\n const rows = hostsApi.listHosts();\n const nameWidth = Math.max(...rows.map((h) => h.name.length), 4);\n let lastKind = '';\n for (const host of rows) {\n if (host.kind !== lastKind) {\n if (lastKind !== '') process.stdout.write('\\n');\n process.stdout.write(`── ${host.kind.toUpperCase()} ──\\n`);\n lastKind = host.kind;\n }\n const pad = (s: string, w: number): string => s + ' '.repeat(Math.max(0, w - s.length));\n const versions = hostsApi.hostSummaryLine(host).split(' · ')[1] ?? '';\n process.stdout.write(` ${pad(host.name, nameWidth)} ${versions}\\n`);\n }\n process.stdout.write(\n '\\nRun `ddt hosts show <id>` for tailoring notes. See docs/HOST_INTEGRATIONS.md for the per-host playbook.\\n',\n );\n });\n\n cmd\n .command('show <id>')\n .description('Print supported versions, idioms, and tailoring notes for one host.')\n .action((id: string) => {\n const host = hostsApi.getHost(id);\n if (!host) {\n console.error(`Unknown host id: \"${id}\". Run \\`ddt hosts list\\` to see all ids.`);\n process.exitCode = 1;\n return;\n }\n process.stdout.write(hostsApi.hostDetailMessage(host) + '\\n');\n });\n\n return cmd;\n}\n"],"mappings":";;;AAKA,SAAS,eAAe;AACxB,SAAS,SAAS,gBAAgB;AAE3B,SAAS,eAAwB;AACtC,QAAM,MAAM,IAAI,QAAQ,OAAO;AAC/B,MAAI;AAAA,IACF;AAAA,EACF;AAEA,MACG,QAAQ,MAAM,EACd,YAAY,4DAA4D,EACxE,OAAO,MAAM;AACZ,UAAM,OAAO,SAAS,UAAU;AAChC,UAAM,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,KAAK,MAAM,GAAG,CAAC;AAC/D,QAAI,WAAW;AACf,eAAW,QAAQ,MAAM;AACvB,UAAI,KAAK,SAAS,UAAU;AAC1B,YAAI,aAAa,GAAI,SAAQ,OAAO,MAAM,IAAI;AAC9C,gBAAQ,OAAO,MAAM,gBAAM,KAAK,KAAK,YAAY,CAAC;AAAA,CAAO;AACzD,mBAAW,KAAK;AAAA,MAClB;AACA,YAAM,MAAM,CAAC,GAAW,MAAsB,IAAI,IAAI,OAAO,KAAK,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC;AACtF,YAAM,WAAW,SAAS,gBAAgB,IAAI,EAAE,MAAM,QAAK,EAAE,CAAC,KAAK;AACnE,cAAQ,OAAO,MAAM,KAAK,IAAI,KAAK,MAAM,SAAS,CAAC,KAAK,QAAQ;AAAA,CAAI;AAAA,IACtE;AACA,YAAQ,OAAO;AAAA,MACb;AAAA,IACF;AAAA,EACF,CAAC;AAEH,MACG,QAAQ,WAAW,EACnB,YAAY,qEAAqE,EACjF,OAAO,CAAC,OAAe;AACtB,UAAM,OAAO,SAAS,QAAQ,EAAE;AAChC,QAAI,CAAC,MAAM;AACT,cAAQ,MAAM,qBAAqB,EAAE,2CAA2C;AAChF,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,YAAQ,OAAO,MAAM,SAAS,kBAAkB,IAAI,IAAI,IAAI;AAAA,EAC9D,CAAC;AAEH,SAAO;AACT;","names":[]}
@@ -0,0 +1,63 @@
1
+ import {
2
+ attachExplainFlag,
3
+ runExplain
4
+ } from "./chunk-XFXG347C.js";
5
+ import "./chunk-DGUM43GV.js";
6
+
7
+ // src/commands/impact.ts
8
+ import { promises as fs } from "fs";
9
+ import path from "path";
10
+ import { Command } from "commander";
11
+ import {
12
+ loadProject,
13
+ pac,
14
+ parseProjectModel,
15
+ review
16
+ } from "@ddt-tools/core";
17
+ function impactCommand() {
18
+ const cmd = new Command("impact");
19
+ cmd.description(
20
+ "Single-FQN blast-radius: who feeds it, who reads from it, what findings apply to it."
21
+ ).argument("<fqn>", "Fully-qualified name to analyze (e.g. main.gold.orders).").requiredOption("--source <path>", ".ddtproj or .ddtpac to analyze.").option("-o, --out <path>", "Output file path. Defaults to stdout.").action(async (fqn, opts) => {
22
+ const sourcePath = String(opts.source);
23
+ const model = await loadModel(sourcePath);
24
+ const md = review.renderImpactReport(model, String(fqn), { source: sourcePath });
25
+ await emit(md, opts.out);
26
+ await runExplain(
27
+ {
28
+ feature: "impact.explain",
29
+ systemPrompt: "You are a senior Databricks data engineer explaining an impact analysis to a developer who is about to change one object. Be very direct about the blast radius and what they should test before merging."
30
+ },
31
+ opts,
32
+ () => `Impact report for ${fqn} follows:
33
+
34
+ ${md}
35
+
36
+ Explain the blast radius and what the engineer should pay attention to.`
37
+ );
38
+ });
39
+ attachExplainFlag(cmd);
40
+ return cmd;
41
+ }
42
+ async function loadModel(sourcePath) {
43
+ if (sourcePath.endsWith(".ddtpac")) {
44
+ const c = await pac.readPac(sourcePath);
45
+ return c.model;
46
+ }
47
+ const loaded = await loadProject(sourcePath);
48
+ return await parseProjectModel(loaded);
49
+ }
50
+ async function emit(payload, out) {
51
+ if (out) {
52
+ const p = path.resolve(String(out));
53
+ await fs.mkdir(path.dirname(p), { recursive: true });
54
+ await fs.writeFile(p, payload + (payload.endsWith("\n") ? "" : "\n"), "utf8");
55
+ console.error(`Wrote ${p} (${payload.length} bytes).`);
56
+ } else {
57
+ process.stdout.write(payload + (payload.endsWith("\n") ? "" : "\n"));
58
+ }
59
+ }
60
+ export {
61
+ impactCommand
62
+ };
63
+ //# sourceMappingURL=impact-A4NU6CB2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/impact.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n loadProject,\n pac,\n parseProjectModel,\n review,\n type DatabricksObject,\n} from '@ddt-tools/core';\nimport { attachExplainFlag, runExplain } from '../util/ai-explain.js';\n\n/**\n * `ddt impact <fqn>` — single-FQN blast-radius. Mirrors `sdt impact`.\n */\nexport function impactCommand(): Command {\n const cmd = new Command('impact');\n cmd\n .description(\n 'Single-FQN blast-radius: who feeds it, who reads from it, what findings apply to it.',\n )\n .argument('<fqn>', 'Fully-qualified name to analyze (e.g. main.gold.orders).')\n .requiredOption('--source <path>', '.ddtproj or .ddtpac to analyze.')\n .option('-o, --out <path>', 'Output file path. Defaults to stdout.')\n .action(async (fqn: string, opts: { source: string; out?: string; explain?: boolean }) => {\n const sourcePath = String(opts.source);\n const model = await loadModel(sourcePath);\n const md = review.renderImpactReport(model, String(fqn), { source: sourcePath });\n await emit(md, opts.out);\n await runExplain(\n {\n feature: 'impact.explain',\n systemPrompt:\n 'You are a senior Databricks data engineer explaining an impact analysis to a developer who is about to change one object. Be very direct about the blast radius and what they should test before merging.',\n },\n opts,\n () =>\n `Impact report for ${fqn} follows:\\n\\n${md}\\n\\nExplain the blast radius and what the engineer should pay attention to.`,\n );\n });\n attachExplainFlag(cmd);\n return cmd;\n}\n\nasync function loadModel(sourcePath: string): Promise<DatabricksObject[]> {\n if (sourcePath.endsWith('.ddtpac')) {\n const c = await pac.readPac(sourcePath);\n return c.model;\n }\n const loaded = await loadProject(sourcePath);\n return await parseProjectModel(loaded);\n}\n\nasync function emit(payload: string, out: unknown): Promise<void> {\n if (out) {\n const p = path.resolve(String(out));\n await fs.mkdir(path.dirname(p), { recursive: true });\n await fs.writeFile(p, payload + (payload.endsWith('\\n') ? '' : '\\n'), 'utf8');\n console.error(`Wrote ${p} (${payload.length} bytes).`);\n } else {\n process.stdout.write(payload + (payload.endsWith('\\n') ? '' : '\\n'));\n }\n}\n"],"mappings":";;;;;;;AAAA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AACjB,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAMA,SAAS,gBAAyB;AACvC,QAAM,MAAM,IAAI,QAAQ,QAAQ;AAChC,MACG;AAAA,IACC;AAAA,EACF,EACC,SAAS,SAAS,0DAA0D,EAC5E,eAAe,mBAAmB,iCAAiC,EACnE,OAAO,oBAAoB,uCAAuC,EAClE,OAAO,OAAO,KAAa,SAA8D;AACxF,UAAM,aAAa,OAAO,KAAK,MAAM;AACrC,UAAM,QAAQ,MAAM,UAAU,UAAU;AACxC,UAAM,KAAK,OAAO,mBAAmB,OAAO,OAAO,GAAG,GAAG,EAAE,QAAQ,WAAW,CAAC;AAC/E,UAAM,KAAK,IAAI,KAAK,GAAG;AACvB,UAAM;AAAA,MACJ;AAAA,QACE,SAAS;AAAA,QACT,cACE;AAAA,MACJ;AAAA,MACA;AAAA,MACA,MACE,qBAAqB,GAAG;AAAA;AAAA,EAAgB,EAAE;AAAA;AAAA;AAAA,IAC9C;AAAA,EACF,CAAC;AACH,oBAAkB,GAAG;AACrB,SAAO;AACT;AAEA,eAAe,UAAU,YAAiD;AACxE,MAAI,WAAW,SAAS,SAAS,GAAG;AAClC,UAAM,IAAI,MAAM,IAAI,QAAQ,UAAU;AACtC,WAAO,EAAE;AAAA,EACX;AACA,QAAM,SAAS,MAAM,YAAY,UAAU;AAC3C,SAAO,MAAM,kBAAkB,MAAM;AACvC;AAEA,eAAe,KAAK,SAAiB,KAA6B;AAChE,MAAI,KAAK;AACP,UAAM,IAAI,KAAK,QAAQ,OAAO,GAAG,CAAC;AAClC,UAAM,GAAG,MAAM,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;AACnD,UAAM,GAAG,UAAU,GAAG,WAAW,QAAQ,SAAS,IAAI,IAAI,KAAK,OAAO,MAAM;AAC5E,YAAQ,MAAM,SAAS,CAAC,KAAK,QAAQ,MAAM,UAAU;AAAA,EACvD,OAAO;AACL,YAAQ,OAAO,MAAM,WAAW,QAAQ,SAAS,IAAI,IAAI,KAAK,KAAK;AAAA,EACrE;AACF;","names":[]}
@@ -0,0 +1,79 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/import.ts
4
+ import { promises as fs } from "fs";
5
+ import path from "path";
6
+ import { Command } from "commander";
7
+ import { importScript, loadProject, parseScript } from "@ddt-tools/core";
8
+ function importCommand() {
9
+ const cmd = new Command("import");
10
+ cmd.description(
11
+ "Parse a SQL script and write each DDL statement into the .ddtproj tree under its canonical folder."
12
+ ).requiredOption("--script <path>", "Path to the SQL script to import.").requiredOption("-p, --project <path>", "Path to the .ddtproj file.").option("--dry-run", "Report what would be written without touching disk.", false).option("--force", "Overwrite existing files (default refuses on conflict).", false).option(
13
+ "--ignore-errors",
14
+ "Import classifiable statements even when others have errors. Default refuses on any error.",
15
+ false
16
+ ).action(async (opts) => {
17
+ const scriptPath = path.resolve(String(opts.script));
18
+ const projectPath = path.resolve(String(opts.project));
19
+ const sql = await fs.readFile(scriptPath, "utf8");
20
+ const parsed = parseScript(sql);
21
+ printParseReport(scriptPath, parsed);
22
+ if (parsed.totalErrors > 0 && !opts.ignoreErrors) {
23
+ console.error(
24
+ `
25
+ Refusing to import: ${parsed.totalErrors} parse error(s) above. Fix the script and re-run, or pass --ignore-errors to import the clean statements.`
26
+ );
27
+ process.exitCode = 2;
28
+ return;
29
+ }
30
+ const loaded = await loadProject(projectPath);
31
+ const result = await importScript(parsed, loaded, {
32
+ dryRun: !!opts.dryRun,
33
+ force: !!opts.force
34
+ });
35
+ console.log("");
36
+ const verb = opts.dryRun ? "would write" : "wrote";
37
+ for (const item of result.imported) {
38
+ const rel = path.relative(loaded.rootDir, item.targetPath);
39
+ console.log(
40
+ ` ${verb} ${item.statement.objectType} ${qualified(item.statement)} \u2192 ${rel}`
41
+ );
42
+ }
43
+ for (const item of result.skipped) {
44
+ const head = item.statement.objectType && item.statement.fqn ? `${item.statement.objectType} ${qualified(item.statement)}` : `<unclassified statement at line ${item.statement.startLine}>`;
45
+ console.log(` skip ${head} \u2014 ${item.reason}`);
46
+ }
47
+ console.log("");
48
+ console.log(
49
+ `Summary: ${result.imported.length} ${verb}, ${result.skipped.length} skipped, ${parsed.totalErrors} errors, ${parsed.totalWarnings} warnings.`
50
+ );
51
+ if (result.imported.length === 0 && result.skipped.length > 0) {
52
+ process.exitCode = 1;
53
+ }
54
+ });
55
+ return cmd;
56
+ }
57
+ function qualified(stmt) {
58
+ if (!stmt.fqn) return "<no fqn>";
59
+ return [stmt.fqn.database, stmt.fqn.schema, stmt.fqn.name].filter(Boolean).join(".");
60
+ }
61
+ function printParseReport(scriptPath, parsed) {
62
+ console.log(`Parsed ${parsed.statements.length} statement(s) from ${scriptPath}.`);
63
+ if (parsed.totalErrors === 0 && parsed.totalWarnings === 0) {
64
+ console.log("All statements classified cleanly.");
65
+ return;
66
+ }
67
+ for (const stmt of parsed.statements) {
68
+ if (stmt.errors.length === 0 && stmt.warnings.length === 0) continue;
69
+ const head = stmt.objectType && stmt.fqn ? `${stmt.objectType} ${qualified(stmt)}` : "<unclassified>";
70
+ console.log(`
71
+ [line ${stmt.startLine}\u2013${stmt.endLine}] ${head}`);
72
+ for (const e of stmt.errors) console.log(` ERROR: ${e}`);
73
+ for (const w of stmt.warnings) console.log(` warning: ${w}`);
74
+ }
75
+ }
76
+ export {
77
+ importCommand
78
+ };
79
+ //# sourceMappingURL=import-2RNYDL4E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/import.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { importScript, loadProject, parseScript, type ParsedStatement } from '@ddt-tools/core';\n\n/**\n * `ddt import` — parse a SQL script and apply each DDL statement to a\n * `.ddtproj` tree, writing each into the canonical folder for its object\n * type (`catalogs/<cat>/schemas/<sch>/<type-folder>/<name>.sql`).\n *\n * Default mode is safe:\n * - Parses the whole script first; refuses to write anything if any\n * statement has hard errors. The user reviews the report, fixes the\n * script, and re-runs.\n * - Refuses to overwrite existing files. Pass `--force` to clobber.\n * - `--dry-run` shows the plan without writing.\n */\nexport function importCommand(): Command {\n const cmd = new Command('import');\n cmd\n .description(\n 'Parse a SQL script and write each DDL statement into the .ddtproj tree under its canonical folder.',\n )\n .requiredOption('--script <path>', 'Path to the SQL script to import.')\n .requiredOption('-p, --project <path>', 'Path to the .ddtproj file.')\n .option('--dry-run', 'Report what would be written without touching disk.', false)\n .option('--force', 'Overwrite existing files (default refuses on conflict).', false)\n .option(\n '--ignore-errors',\n 'Import classifiable statements even when others have errors. Default refuses on any error.',\n false,\n )\n .action(async (opts) => {\n const scriptPath = path.resolve(String(opts.script));\n const projectPath = path.resolve(String(opts.project));\n const sql = await fs.readFile(scriptPath, 'utf8');\n const parsed = parseScript(sql);\n\n // Up-front parse report — surfaces every error/warning verbatim.\n printParseReport(scriptPath, parsed);\n\n if (parsed.totalErrors > 0 && !opts.ignoreErrors) {\n console.error(\n `\\nRefusing to import: ${parsed.totalErrors} parse error(s) above. ` +\n `Fix the script and re-run, or pass --ignore-errors to import the clean statements.`,\n );\n process.exitCode = 2;\n return;\n }\n\n const loaded = await loadProject(projectPath);\n const result = await importScript(parsed, loaded, {\n dryRun: !!opts.dryRun,\n force: !!opts.force,\n });\n\n console.log('');\n const verb = opts.dryRun ? 'would write' : 'wrote';\n for (const item of result.imported) {\n const rel = path.relative(loaded.rootDir, item.targetPath);\n console.log(\n ` ${verb} ${item.statement.objectType} ${qualified(item.statement)} → ${rel}`,\n );\n }\n for (const item of result.skipped) {\n const head =\n item.statement.objectType && item.statement.fqn\n ? `${item.statement.objectType} ${qualified(item.statement)}`\n : `<unclassified statement at line ${item.statement.startLine}>`;\n console.log(` skip ${head} — ${item.reason}`);\n }\n console.log('');\n console.log(\n `Summary: ${result.imported.length} ${verb}, ${result.skipped.length} skipped, ` +\n `${parsed.totalErrors} errors, ${parsed.totalWarnings} warnings.`,\n );\n\n if (result.imported.length === 0 && result.skipped.length > 0) {\n process.exitCode = 1;\n }\n });\n return cmd;\n}\n\nfunction qualified(stmt: ParsedStatement): string {\n if (!stmt.fqn) return '<no fqn>';\n return [stmt.fqn.database, stmt.fqn.schema, stmt.fqn.name].filter(Boolean).join('.');\n}\n\nfunction printParseReport(scriptPath: string, parsed: ReturnType<typeof parseScript>): void {\n console.log(`Parsed ${parsed.statements.length} statement(s) from ${scriptPath}.`);\n if (parsed.totalErrors === 0 && parsed.totalWarnings === 0) {\n console.log('All statements classified cleanly.');\n return;\n }\n for (const stmt of parsed.statements) {\n if (stmt.errors.length === 0 && stmt.warnings.length === 0) continue;\n const head =\n stmt.objectType && stmt.fqn ? `${stmt.objectType} ${qualified(stmt)}` : '<unclassified>';\n console.log(`\\n[line ${stmt.startLine}–${stmt.endLine}] ${head}`);\n for (const e of stmt.errors) console.log(` ERROR: ${e}`);\n for (const w of stmt.warnings) console.log(` warning: ${w}`);\n }\n}\n"],"mappings":"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","names":[]}