@ddt-tools/cli 0.2.0 → 0.2.5

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 (205) 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 +504 -19402
  38. package/dist/cli.js.map +1 -1
  39. package/dist/compare-IOEATL6G.js +435 -0
  40. package/dist/compare-IOEATL6G.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-LX6WT4JH.js +109 -0
  70. package/dist/errorReporting-LX6WT4JH.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-EGOVKTLX.js +29 -0
  98. package/dist/import-EGOVKTLX.js.map +1 -0
  99. package/dist/import-script-R5RXPDH6.js +79 -0
  100. package/dist/import-script-R5RXPDH6.js.map +1 -0
  101. package/dist/index.cjs +11 -5
  102. package/dist/index.cjs.map +1 -1
  103. package/dist/index.js +8 -2
  104. package/dist/index.js.map +1 -1
  105. package/dist/init-EAOGNGXI.js +54 -0
  106. package/dist/init-EAOGNGXI.js.map +1 -0
  107. package/dist/install-hooks-G3Y5LVXK.js +109 -0
  108. package/dist/install-hooks-G3Y5LVXK.js.map +1 -0
  109. package/dist/license-Z5YSC7XQ.js +43 -0
  110. package/dist/license-Z5YSC7XQ.js.map +1 -0
  111. package/dist/lineage-C5CGVP36.js +555 -0
  112. package/dist/lineage-C5CGVP36.js.map +1 -0
  113. package/dist/lint-AQFPZ3WG.js +144 -0
  114. package/dist/lint-AQFPZ3WG.js.map +1 -0
  115. package/dist/mcp-6ZXOAF7S.js +343 -0
  116. package/dist/mcp-6ZXOAF7S.js.map +1 -0
  117. package/dist/migrate-from-dbt-K4ELOWUD.js +156 -0
  118. package/dist/migrate-from-dbt-K4ELOWUD.js.map +1 -0
  119. package/dist/migrate-platform-E7VZFPO5.js +91 -0
  120. package/dist/migrate-platform-E7VZFPO5.js.map +1 -0
  121. package/dist/optimize-WUJ5ZN5Y.js +109 -0
  122. package/dist/optimize-WUJ5ZN5Y.js.map +1 -0
  123. package/dist/perf-UULZSREY.js +200 -0
  124. package/dist/perf-UULZSREY.js.map +1 -0
  125. package/dist/pii-QHU32VML.js +146 -0
  126. package/dist/pii-QHU32VML.js.map +1 -0
  127. package/dist/pilot-BR6GVK32.js +29 -0
  128. package/dist/pilot-BR6GVK32.js.map +1 -0
  129. package/dist/pr-comment-2FOA3EXG.js +81 -0
  130. package/dist/pr-comment-2FOA3EXG.js.map +1 -0
  131. package/dist/preview-XNY422OU.js +46 -0
  132. package/dist/preview-XNY422OU.js.map +1 -0
  133. package/dist/profile-SQTBNKYS.js +98 -0
  134. package/dist/profile-SQTBNKYS.js.map +1 -0
  135. package/dist/promote-FSGUPIPD.js +417 -0
  136. package/dist/promote-FSGUPIPD.js.map +1 -0
  137. package/dist/publish-HLP3XHM5.js +766 -0
  138. package/dist/publish-HLP3XHM5.js.map +1 -0
  139. package/dist/purge-Y5IOTXKA.js +56 -0
  140. package/dist/purge-Y5IOTXKA.js.map +1 -0
  141. package/dist/query-log-SDDGMJLJ.js +112 -0
  142. package/dist/query-log-SDDGMJLJ.js.map +1 -0
  143. package/dist/refactor-TC7S43F2.js +5809 -0
  144. package/dist/refactor-TC7S43F2.js.map +1 -0
  145. package/dist/refresh-MDJYOYV5.js +39 -0
  146. package/dist/refresh-MDJYOYV5.js.map +1 -0
  147. package/dist/replay-E4664A5K.js +118 -0
  148. package/dist/replay-E4664A5K.js.map +1 -0
  149. package/dist/revert-QWQWCJJB.js +111 -0
  150. package/dist/revert-QWQWCJJB.js.map +1 -0
  151. package/dist/review-7CAVLD67.js +164 -0
  152. package/dist/review-7CAVLD67.js.map +1 -0
  153. package/dist/rollback-suggest-C6D5YFCA.js +79 -0
  154. package/dist/rollback-suggest-C6D5YFCA.js.map +1 -0
  155. package/dist/safer-alternative-QR4QEFUV.js +84 -0
  156. package/dist/safer-alternative-QR4QEFUV.js.map +1 -0
  157. package/dist/safety-OFWUFLK4.js +165 -0
  158. package/dist/safety-OFWUFLK4.js.map +1 -0
  159. package/dist/savings-MEBE4TXI.js +95 -0
  160. package/dist/savings-MEBE4TXI.js.map +1 -0
  161. package/dist/scan-secrets-XCUBMLHL.js +54 -0
  162. package/dist/scan-secrets-XCUBMLHL.js.map +1 -0
  163. package/dist/schema-7JZIG6QR.js +447 -0
  164. package/dist/schema-7JZIG6QR.js.map +1 -0
  165. package/dist/script-BMYVBHFR.js +167 -0
  166. package/dist/script-BMYVBHFR.js.map +1 -0
  167. package/dist/search-TA3C3AZT.js +151 -0
  168. package/dist/search-TA3C3AZT.js.map +1 -0
  169. package/dist/seed-W4Q3L2IU.js +101 -0
  170. package/dist/seed-W4Q3L2IU.js.map +1 -0
  171. package/dist/sketch-6B2V6FJV.js +83 -0
  172. package/dist/sketch-6B2V6FJV.js.map +1 -0
  173. package/dist/snapshot-YMVS322L.js +171 -0
  174. package/dist/snapshot-YMVS322L.js.map +1 -0
  175. package/dist/snippets-EVTN63OU.js +74 -0
  176. package/dist/snippets-EVTN63OU.js.map +1 -0
  177. package/dist/standards-FGJW3CQL.js +238 -0
  178. package/dist/standards-FGJW3CQL.js.map +1 -0
  179. package/dist/suggest-V3LVIFZ5.js +44 -0
  180. package/dist/suggest-V3LVIFZ5.js.map +1 -0
  181. package/dist/suggest-constraints-EX2FCWOQ.js +154 -0
  182. package/dist/suggest-constraints-EX2FCWOQ.js.map +1 -0
  183. package/dist/suite-YTQ3CNX5.js +85 -0
  184. package/dist/suite-YTQ3CNX5.js.map +1 -0
  185. package/dist/telemetry-KOIY3NEQ.js +90 -0
  186. package/dist/telemetry-KOIY3NEQ.js.map +1 -0
  187. package/dist/template-MUJ6X6LN.js +396 -0
  188. package/dist/template-MUJ6X6LN.js.map +1 -0
  189. package/dist/test-XFSQHR2S.js +169 -0
  190. package/dist/test-XFSQHR2S.js.map +1 -0
  191. package/dist/trial-GFTGYCR3.js +31 -0
  192. package/dist/trial-GFTGYCR3.js.map +1 -0
  193. package/dist/validate-LFDEZFFH.js +107 -0
  194. package/dist/validate-LFDEZFFH.js.map +1 -0
  195. package/dist/verify-KRDYOJCR.js +76 -0
  196. package/dist/verify-KRDYOJCR.js.map +1 -0
  197. package/dist/watch-FSG23RR3.js +80 -0
  198. package/dist/watch-FSG23RR3.js.map +1 -0
  199. package/dist/xcompare-U4TXTTIR.js +87 -0
  200. package/dist/xcompare-U4TXTTIR.js.map +1 -0
  201. package/package.json +2 -2
  202. package/dist/cli.cjs +0 -19298
  203. package/dist/cli.cjs.map +0 -1
  204. package/dist/cli.d.cts +0 -1
  205. package/dist/cli.d.ts +0 -1
@@ -0,0 +1,84 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/safer-alternative.ts
4
+ import { promises as fs } from "fs";
5
+ import { Command } from "commander";
6
+ import { ai, saferAlternative } from "@ddt-tools/core";
7
+ function saferAlternativeCommand() {
8
+ const cmd = new Command("safer-alternative");
9
+ cmd.description(
10
+ "AI-assist: propose a safer DDL alternative for a safety finding. Requires a configured AI provider (ddt ai status)."
11
+ ).requiredOption("--code <code>", "SafetyFindingCode (e.g. COLUMN_DROP, DROP_UNRECOVERABLE).").requiredOption("--fqn <fqn>", "Object FQN the finding refers to.").requiredOption("--object-type <type>", "Object type (e.g. MANAGED_TABLE, VIEW, FUNCTION).").requiredOption("--reason <text>", "Short reason text from the safety classifier.").option(
12
+ "--gate <gate>",
13
+ "Optional SafetyGate the finding raised (e.g. REQUIRE_ALLOW_DROP_COLUMN)."
14
+ ).option("--sql <path>", 'Path to a file with the dangerous DDL. Use "-" to read from stdin.').option("--context <path>", "Optional path to the surrounding source DDL.").option("--intent <text>", "Optional human-authored intent notes.").option("--format <fmt>", "Output format: text | json. Default text.", "text").option(
15
+ "--ai-max-spend <usd>",
16
+ "Refuse the call if today's estimated spend \u2265 this (USD). 0 = no cap.",
17
+ "0"
18
+ ).action(async (opts) => {
19
+ const dangerousSql = await readInput(
20
+ opts.sql,
21
+ '--sql is required (use a path or "-" for stdin).'
22
+ );
23
+ const contextSql = opts.context ? await fs.readFile(String(opts.context), "utf8") : void 0;
24
+ const finding = {
25
+ code: String(opts.code),
26
+ category: "DESTRUCTIVE",
27
+ fqn: String(opts.fqn),
28
+ objectType: String(opts.objectType),
29
+ reason: String(opts.reason),
30
+ gate: opts.gate ? String(opts.gate) : void 0
31
+ };
32
+ const result = await saferAlternative.suggestSaferAlternative(
33
+ {
34
+ finding,
35
+ dangerousSql,
36
+ contextSql,
37
+ intentNotes: opts.intent ? String(opts.intent) : void 0
38
+ },
39
+ {
40
+ completeFn: async (prompt) => {
41
+ const r = await ai.complete([{ role: "user", content: prompt }], {
42
+ feature: "safer-alternative",
43
+ maxSpendUsd: Number(opts.aiMaxSpend ?? "0") || 0
44
+ });
45
+ return r.text;
46
+ }
47
+ }
48
+ );
49
+ if (String(opts.format).toLowerCase() === "json") {
50
+ const { rawModelText: _omit, ...keep } = result;
51
+ console.log(JSON.stringify(keep, null, 2));
52
+ return;
53
+ }
54
+ console.log(`Confidence: ${result.confidence}${result.parseFailed ? " (parse failed)" : ""}`);
55
+ console.log(`Reasoning: ${result.reasoning}`);
56
+ if (result.alternativeSql) {
57
+ console.log("");
58
+ console.log("--- Proposed safer DDL ---");
59
+ console.log(result.alternativeSql);
60
+ } else {
61
+ console.warn("No safer alternative SQL was returned.");
62
+ }
63
+ if (result.requiredGates.length > 0) {
64
+ console.log(`Required gates: ${result.requiredGates.join(", ")}`);
65
+ }
66
+ });
67
+ return cmd;
68
+ }
69
+ async function readInput(pathOrDash, missingMessage) {
70
+ if (!pathOrDash) throw new Error(missingMessage);
71
+ const p = String(pathOrDash);
72
+ if (p === "-") {
73
+ const chunks = [];
74
+ for await (const chunk of process.stdin) {
75
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
76
+ }
77
+ return Buffer.concat(chunks).toString("utf8");
78
+ }
79
+ return fs.readFile(p, "utf8");
80
+ }
81
+ export {
82
+ saferAlternativeCommand
83
+ };
84
+ //# sourceMappingURL=safer-alternative-QR4QEFUV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/safer-alternative.ts"],"sourcesContent":["/**\n * `ddt safer-alternative` — call the AI provider to suggest a safer DDL\n * alternative for a flagged-as-dangerous migration step.\n *\n * Composes `@ddt-tools/core/saferAlternative.suggestSaferAlternative` with the\n * configured AI provider (`@ddt-tools/core/ai.complete`). The CLI is the thin\n * adapter; all the prompt-building and parsing lives in core so the\n * VS Code CodeLens / MCP server / future hosts share the same behavior.\n *\n * Inputs (all surfaced as flags):\n * --code SafetyFindingCode (e.g. COLUMN_DROP, DROP_UNRECOVERABLE)\n * --fqn Object FQN the finding refers to\n * --object-type Object type (e.g. MANAGED_TABLE, VIEW)\n * --reason Short reason from the safety classifier\n * --gate Optional SafetyGate (preserved on the suggestion)\n * --sql <path> Path to a file with the dangerous DDL (or \"-\" for stdin)\n * --context <path> Optional path to the surrounding source DDL\n * --intent <text> Optional human-authored intent notes\n * --format text | json (default text)\n */\nimport { promises as fs } from 'node:fs';\nimport { Command } from 'commander';\nimport { ai, saferAlternative, type safety } from '@ddt-tools/core';\n\nexport function saferAlternativeCommand(): Command {\n const cmd = new Command('safer-alternative');\n cmd\n .description(\n 'AI-assist: propose a safer DDL alternative for a safety finding. Requires a configured AI provider (ddt ai status).',\n )\n .requiredOption('--code <code>', 'SafetyFindingCode (e.g. COLUMN_DROP, DROP_UNRECOVERABLE).')\n .requiredOption('--fqn <fqn>', 'Object FQN the finding refers to.')\n .requiredOption('--object-type <type>', 'Object type (e.g. MANAGED_TABLE, VIEW, FUNCTION).')\n .requiredOption('--reason <text>', 'Short reason text from the safety classifier.')\n .option(\n '--gate <gate>',\n 'Optional SafetyGate the finding raised (e.g. REQUIRE_ALLOW_DROP_COLUMN).',\n )\n .option('--sql <path>', 'Path to a file with the dangerous DDL. Use \"-\" to read from stdin.')\n .option('--context <path>', 'Optional path to the surrounding source DDL.')\n .option('--intent <text>', 'Optional human-authored intent notes.')\n .option('--format <fmt>', 'Output format: text | json. Default text.', 'text')\n .option(\n '--ai-max-spend <usd>',\n \"Refuse the call if today's estimated spend ≥ this (USD). 0 = no cap.\",\n '0',\n )\n .action(async (opts) => {\n const dangerousSql = await readInput(\n opts.sql,\n '--sql is required (use a path or \"-\" for stdin).',\n );\n const contextSql = opts.context ? await fs.readFile(String(opts.context), 'utf8') : undefined;\n\n const finding: safety.SafetyFinding = {\n code: String(opts.code) as safety.SafetyFindingCode,\n category: 'DESTRUCTIVE',\n fqn: String(opts.fqn),\n objectType: String(opts.objectType) as safety.SafetyFinding['objectType'],\n reason: String(opts.reason),\n gate: opts.gate ? (String(opts.gate) as safety.SafetyGate) : undefined,\n };\n\n const result = await saferAlternative.suggestSaferAlternative(\n {\n finding,\n dangerousSql,\n contextSql,\n intentNotes: opts.intent ? String(opts.intent) : undefined,\n },\n {\n completeFn: async (prompt) => {\n const r = await ai.complete([{ role: 'user', content: prompt }], {\n feature: 'safer-alternative',\n maxSpendUsd: Number(opts.aiMaxSpend ?? '0') || 0,\n });\n return r.text;\n },\n },\n );\n\n if (String(opts.format).toLowerCase() === 'json') {\n const { rawModelText: _omit, ...keep } = result;\n console.log(JSON.stringify(keep, null, 2));\n return;\n }\n\n console.log(`Confidence: ${result.confidence}${result.parseFailed ? ' (parse failed)' : ''}`);\n console.log(`Reasoning: ${result.reasoning}`);\n if (result.alternativeSql) {\n console.log('');\n console.log('--- Proposed safer DDL ---');\n console.log(result.alternativeSql);\n } else {\n console.warn('No safer alternative SQL was returned.');\n }\n if (result.requiredGates.length > 0) {\n console.log(`Required gates: ${result.requiredGates.join(', ')}`);\n }\n });\n return cmd;\n}\n\nasync function readInput(pathOrDash: unknown, missingMessage: string): Promise<string> {\n if (!pathOrDash) throw new Error(missingMessage);\n const p = String(pathOrDash);\n if (p === '-') {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : (chunk as Buffer));\n }\n return Buffer.concat(chunks).toString('utf8');\n }\n return fs.readFile(p, 'utf8');\n}\n"],"mappings":";;;AAoBA,SAAS,YAAY,UAAU;AAC/B,SAAS,eAAe;AACxB,SAAS,IAAI,wBAAqC;AAE3C,SAAS,0BAAmC;AACjD,QAAM,MAAM,IAAI,QAAQ,mBAAmB;AAC3C,MACG;AAAA,IACC;AAAA,EACF,EACC,eAAe,iBAAiB,2DAA2D,EAC3F,eAAe,eAAe,mCAAmC,EACjE,eAAe,wBAAwB,mDAAmD,EAC1F,eAAe,mBAAmB,+CAA+C,EACjF;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,gBAAgB,oEAAoE,EAC3F,OAAO,oBAAoB,8CAA8C,EACzE,OAAO,mBAAmB,uCAAuC,EACjE,OAAO,kBAAkB,6CAA6C,MAAM,EAC5E;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC,OAAO,OAAO,SAAS;AACtB,UAAM,eAAe,MAAM;AAAA,MACzB,KAAK;AAAA,MACL;AAAA,IACF;AACA,UAAM,aAAa,KAAK,UAAU,MAAM,GAAG,SAAS,OAAO,KAAK,OAAO,GAAG,MAAM,IAAI;AAEpF,UAAM,UAAgC;AAAA,MACpC,MAAM,OAAO,KAAK,IAAI;AAAA,MACtB,UAAU;AAAA,MACV,KAAK,OAAO,KAAK,GAAG;AAAA,MACpB,YAAY,OAAO,KAAK,UAAU;AAAA,MAClC,QAAQ,OAAO,KAAK,MAAM;AAAA,MAC1B,MAAM,KAAK,OAAQ,OAAO,KAAK,IAAI,IAA0B;AAAA,IAC/D;AAEA,UAAM,SAAS,MAAM,iBAAiB;AAAA,MACpC;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA,aAAa,KAAK,SAAS,OAAO,KAAK,MAAM,IAAI;AAAA,MACnD;AAAA,MACA;AAAA,QACE,YAAY,OAAO,WAAW;AAC5B,gBAAM,IAAI,MAAM,GAAG,SAAS,CAAC,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC,GAAG;AAAA,YAC/D,SAAS;AAAA,YACT,aAAa,OAAO,KAAK,cAAc,GAAG,KAAK;AAAA,UACjD,CAAC;AACD,iBAAO,EAAE;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,KAAK,MAAM,EAAE,YAAY,MAAM,QAAQ;AAChD,YAAM,EAAE,cAAc,OAAO,GAAG,KAAK,IAAI;AACzC,cAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AACzC;AAAA,IACF;AAEA,YAAQ,IAAI,eAAe,OAAO,UAAU,GAAG,OAAO,cAAc,oBAAoB,EAAE,EAAE;AAC5F,YAAQ,IAAI,cAAc,OAAO,SAAS,EAAE;AAC5C,QAAI,OAAO,gBAAgB;AACzB,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,4BAA4B;AACxC,cAAQ,IAAI,OAAO,cAAc;AAAA,IACnC,OAAO;AACL,cAAQ,KAAK,wCAAwC;AAAA,IACvD;AACA,QAAI,OAAO,cAAc,SAAS,GAAG;AACnC,cAAQ,IAAI,mBAAmB,OAAO,cAAc,KAAK,IAAI,CAAC,EAAE;AAAA,IAClE;AAAA,EACF,CAAC;AACH,SAAO;AACT;AAEA,eAAe,UAAU,YAAqB,gBAAyC;AACrF,MAAI,CAAC,WAAY,OAAM,IAAI,MAAM,cAAc;AAC/C,QAAM,IAAI,OAAO,UAAU;AAC3B,MAAI,MAAM,KAAK;AACb,UAAM,SAAmB,CAAC;AAC1B,qBAAiB,SAAS,QAAQ,OAAO;AACvC,aAAO,KAAK,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAK,KAAgB;AAAA,IAChF;AACA,WAAO,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM;AAAA,EAC9C;AACA,SAAO,GAAG,SAAS,GAAG,MAAM;AAC9B;","names":[]}
@@ -0,0 +1,165 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/safety.ts
4
+ import { Command } from "commander";
5
+ import { safety as safetyApi } from "@ddt-tools/core";
6
+ function safetyCommand() {
7
+ const cmd = new Command("safety");
8
+ cmd.description(
9
+ "Inspect the safety-finding catalog. See `ddt safety list` and `ddt safety explain <code>`."
10
+ );
11
+ cmd.command("list").description("List every known finding code with category + one-line summary.").option("--format <format>", "Output format: table | json", "table").option(
12
+ "--category <kind>",
13
+ "Filter to one category: unrecoverable | destructive | expensive | warning. Default: all."
14
+ ).action((opts) => {
15
+ const codes = safetyApi.listFindingCodes();
16
+ let entries = codes.map((c) => safetyApi.explainFinding(c)).filter((e) => e !== void 0);
17
+ if (opts.category) {
18
+ const want = opts.category.toUpperCase();
19
+ const valid = ["UNRECOVERABLE", "DESTRUCTIVE", "EXPENSIVE", "WARNING"];
20
+ if (!valid.includes(want)) {
21
+ console.error(
22
+ `Unknown --category "${opts.category}". Use one of: ${valid.join(" | ").toLowerCase()}.`
23
+ );
24
+ process.exitCode = 1;
25
+ return;
26
+ }
27
+ entries = entries.filter((e) => e.category === want);
28
+ }
29
+ if ((opts.format ?? "table") === "json") {
30
+ process.stdout.write(JSON.stringify(entries, null, 2) + "\n");
31
+ return;
32
+ }
33
+ if (entries.length === 0) {
34
+ console.log("(no entries match the filter)");
35
+ return;
36
+ }
37
+ printList(entries);
38
+ });
39
+ cmd.command("explain").description('Expand a finding code into a deep "why this is dangerous" page.').argument("<code>", "The finding code (e.g. DROP_UNRECOVERABLE, COLUMN_TYPE_CHANGE)").option("--format <format>", "Output format: text | json | markdown", "text").action((codeArg, opts) => {
40
+ const code = codeArg.toUpperCase();
41
+ const entry = safetyApi.explainFinding(code);
42
+ if (!entry) {
43
+ console.error(`Unknown safety finding code: "${codeArg}"`);
44
+ console.error(" Try: ddt safety list");
45
+ process.exitCode = 1;
46
+ return;
47
+ }
48
+ const format = (opts.format ?? "text").toLowerCase();
49
+ if (format === "json") {
50
+ process.stdout.write(JSON.stringify(entry, null, 2) + "\n");
51
+ return;
52
+ }
53
+ if (format === "markdown") {
54
+ process.stdout.write(renderMarkdown(entry) + "\n");
55
+ return;
56
+ }
57
+ printDeep(entry);
58
+ });
59
+ return cmd;
60
+ }
61
+ function printList(entries) {
62
+ const byCategory = /* @__PURE__ */ new Map();
63
+ for (const e of entries) {
64
+ const arr = byCategory.get(e.category) ?? [];
65
+ arr.push(e);
66
+ byCategory.set(e.category, arr);
67
+ }
68
+ const order = [
69
+ "UNRECOVERABLE",
70
+ "DESTRUCTIVE",
71
+ "EXPENSIVE",
72
+ "WARNING"
73
+ ];
74
+ for (const cat of order) {
75
+ const arr = byCategory.get(cat);
76
+ if (!arr || arr.length === 0) continue;
77
+ console.log(`# ${cat} (${arr.length})`);
78
+ for (const e of arr) {
79
+ console.log(` ${e.code}`);
80
+ console.log(` ${e.title}`);
81
+ console.log(` ${e.summary}`);
82
+ }
83
+ console.log("");
84
+ }
85
+ console.log("Use `ddt safety explain <code>` for the full per-finding write-up.");
86
+ }
87
+ function printDeep(entry) {
88
+ console.log(`${entry.code} [${entry.category}]`);
89
+ console.log(entry.title);
90
+ console.log("");
91
+ console.log("Summary");
92
+ console.log(` ${entry.summary}`);
93
+ console.log("");
94
+ console.log("Why this is dangerous");
95
+ for (const line of wrap(entry.whyDangerous, 76)) {
96
+ console.log(` ${line}`);
97
+ }
98
+ console.log("");
99
+ console.log("What cannot be reversed");
100
+ for (const item of entry.cannotBeReversed) console.log(` - ${item}`);
101
+ console.log("");
102
+ console.log("Safer alternatives");
103
+ for (const item of entry.saferAlternatives) console.log(` - ${item}`);
104
+ console.log("");
105
+ if (entry.requiredGates.length > 0) {
106
+ console.log("Required gates to allow this finding through");
107
+ for (const g of entry.requiredGates) console.log(` - ${g}`);
108
+ console.log("");
109
+ }
110
+ if (entry.example) {
111
+ console.log("Example");
112
+ console.log(` ${entry.example}`);
113
+ }
114
+ }
115
+ function renderMarkdown(entry) {
116
+ const lines = [];
117
+ lines.push(`## ${entry.code} \u2014 ${entry.title}`);
118
+ lines.push("");
119
+ lines.push(`**Category:** ${entry.category}`);
120
+ lines.push("");
121
+ lines.push(`**Summary:** ${entry.summary}`);
122
+ lines.push("");
123
+ lines.push("### Why this is dangerous");
124
+ lines.push(entry.whyDangerous);
125
+ lines.push("");
126
+ lines.push("### What cannot be reversed");
127
+ for (const item of entry.cannotBeReversed) lines.push(`- ${item}`);
128
+ lines.push("");
129
+ lines.push("### Safer alternatives");
130
+ for (const item of entry.saferAlternatives) lines.push(`- ${item}`);
131
+ if (entry.requiredGates.length > 0) {
132
+ lines.push("");
133
+ lines.push("### Required gates");
134
+ for (const g of entry.requiredGates) lines.push(`- \`${g}\``);
135
+ }
136
+ if (entry.example) {
137
+ lines.push("");
138
+ lines.push("### Example");
139
+ lines.push("```");
140
+ lines.push(entry.example);
141
+ lines.push("```");
142
+ }
143
+ return lines.join("\n");
144
+ }
145
+ function wrap(text, width) {
146
+ const words = text.split(/\s+/);
147
+ const lines = [];
148
+ let current = "";
149
+ for (const word of words) {
150
+ if (current.length === 0) {
151
+ current = word;
152
+ } else if (current.length + 1 + word.length <= width) {
153
+ current += " " + word;
154
+ } else {
155
+ lines.push(current);
156
+ current = word;
157
+ }
158
+ }
159
+ if (current.length > 0) lines.push(current);
160
+ return lines;
161
+ }
162
+ export {
163
+ safetyCommand
164
+ };
165
+ //# sourceMappingURL=safety-OFWUFLK4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/safety.ts"],"sourcesContent":["/**\n * `ddt safety` — inspect safety-finding codes.\n *\n * Subcommands:\n * ddt safety list — list every known finding code with a one-line summary\n * ddt safety explain <code> — deep explainer for a single code: why it is dangerous,\n * what cannot be reversed, safer alternatives, required gates\n *\n * The finding codes are stable identifiers attached to every `SafetyFinding`\n * the classifier emits during `ddt compare` / `ddt publish`. When the user\n * sees a finding they don't understand, they can copy the code and ask:\n *\n * ddt safety explain DROP_UNRECOVERABLE\n *\n * Mirrors `Snowflake/packages/cli/src/commands/safety.ts` — paired-file rule.\n * See `docs/UX_PLAYBOOK.md` §3 (dangerous-op UX) and\n * `docs/AI_FEATURES.md` use case C2.\n */\nimport { Command } from 'commander';\nimport { safety as safetyApi, type safety as safetyNs } from '@ddt-tools/core';\n\ntype FindingExplanation = safetyNs.FindingExplanation;\ntype SafetyFindingCode = safetyNs.SafetyFindingCode;\n\nexport function safetyCommand(): Command {\n const cmd = new Command('safety');\n cmd.description(\n 'Inspect the safety-finding catalog. See `ddt safety list` and `ddt safety explain <code>`.',\n );\n\n cmd\n .command('list')\n .description('List every known finding code with category + one-line summary.')\n .option('--format <format>', 'Output format: table | json', 'table')\n .option(\n '--category <kind>',\n 'Filter to one category: unrecoverable | destructive | expensive | warning. Default: all.',\n )\n .action((opts: { format?: string; category?: string }) => {\n const codes = safetyApi.listFindingCodes();\n let entries = codes\n .map((c) => safetyApi.explainFinding(c))\n .filter((e): e is FindingExplanation => e !== undefined);\n if (opts.category) {\n const want = opts.category.toUpperCase();\n const valid = ['UNRECOVERABLE', 'DESTRUCTIVE', 'EXPENSIVE', 'WARNING'];\n if (!valid.includes(want)) {\n console.error(\n `Unknown --category \"${opts.category}\". Use one of: ${valid.join(' | ').toLowerCase()}.`,\n );\n process.exitCode = 1;\n return;\n }\n entries = entries.filter((e) => e.category === want);\n }\n if ((opts.format ?? 'table') === 'json') {\n process.stdout.write(JSON.stringify(entries, null, 2) + '\\n');\n return;\n }\n if (entries.length === 0) {\n console.log('(no entries match the filter)');\n return;\n }\n printList(entries);\n });\n\n cmd\n .command('explain')\n .description('Expand a finding code into a deep \"why this is dangerous\" page.')\n .argument('<code>', 'The finding code (e.g. DROP_UNRECOVERABLE, COLUMN_TYPE_CHANGE)')\n .option('--format <format>', 'Output format: text | json | markdown', 'text')\n .action((codeArg: string, opts: { format?: string }) => {\n const code = codeArg.toUpperCase() as SafetyFindingCode;\n const entry = safetyApi.explainFinding(code);\n if (!entry) {\n console.error(`Unknown safety finding code: \"${codeArg}\"`);\n console.error(' Try: ddt safety list');\n process.exitCode = 1;\n return;\n }\n const format = (opts.format ?? 'text').toLowerCase();\n if (format === 'json') {\n process.stdout.write(JSON.stringify(entry, null, 2) + '\\n');\n return;\n }\n if (format === 'markdown') {\n process.stdout.write(renderMarkdown(entry) + '\\n');\n return;\n }\n printDeep(entry);\n });\n\n return cmd;\n}\n\nfunction printList(entries: readonly FindingExplanation[]): void {\n const byCategory = new Map<FindingExplanation['category'], FindingExplanation[]>();\n for (const e of entries) {\n const arr = byCategory.get(e.category) ?? [];\n arr.push(e);\n byCategory.set(e.category, arr);\n }\n const order: FindingExplanation['category'][] = [\n 'UNRECOVERABLE',\n 'DESTRUCTIVE',\n 'EXPENSIVE',\n 'WARNING',\n ];\n for (const cat of order) {\n const arr = byCategory.get(cat);\n if (!arr || arr.length === 0) continue;\n console.log(`# ${cat} (${arr.length})`);\n for (const e of arr) {\n console.log(` ${e.code}`);\n console.log(` ${e.title}`);\n console.log(` ${e.summary}`);\n }\n console.log('');\n }\n console.log('Use `ddt safety explain <code>` for the full per-finding write-up.');\n}\n\nfunction printDeep(entry: FindingExplanation): void {\n console.log(`${entry.code} [${entry.category}]`);\n console.log(entry.title);\n console.log('');\n console.log('Summary');\n console.log(` ${entry.summary}`);\n console.log('');\n console.log('Why this is dangerous');\n for (const line of wrap(entry.whyDangerous, 76)) {\n console.log(` ${line}`);\n }\n console.log('');\n console.log('What cannot be reversed');\n for (const item of entry.cannotBeReversed) console.log(` - ${item}`);\n console.log('');\n console.log('Safer alternatives');\n for (const item of entry.saferAlternatives) console.log(` - ${item}`);\n console.log('');\n if (entry.requiredGates.length > 0) {\n console.log('Required gates to allow this finding through');\n for (const g of entry.requiredGates) console.log(` - ${g}`);\n console.log('');\n }\n if (entry.example) {\n console.log('Example');\n console.log(` ${entry.example}`);\n }\n}\n\nfunction renderMarkdown(entry: FindingExplanation): string {\n const lines: string[] = [];\n lines.push(`## ${entry.code} — ${entry.title}`);\n lines.push('');\n lines.push(`**Category:** ${entry.category}`);\n lines.push('');\n lines.push(`**Summary:** ${entry.summary}`);\n lines.push('');\n lines.push('### Why this is dangerous');\n lines.push(entry.whyDangerous);\n lines.push('');\n lines.push('### What cannot be reversed');\n for (const item of entry.cannotBeReversed) lines.push(`- ${item}`);\n lines.push('');\n lines.push('### Safer alternatives');\n for (const item of entry.saferAlternatives) lines.push(`- ${item}`);\n if (entry.requiredGates.length > 0) {\n lines.push('');\n lines.push('### Required gates');\n for (const g of entry.requiredGates) lines.push(`- \\`${g}\\``);\n }\n if (entry.example) {\n lines.push('');\n lines.push('### Example');\n lines.push('```');\n lines.push(entry.example);\n lines.push('```');\n }\n return lines.join('\\n');\n}\n\nfunction wrap(text: string, width: number): string[] {\n const words = text.split(/\\s+/);\n const lines: string[] = [];\n let current = '';\n for (const word of words) {\n if (current.length === 0) {\n current = word;\n } else if (current.length + 1 + word.length <= width) {\n current += ' ' + word;\n } else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,95 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/savings.ts
4
+ import { promises as fs } from "fs";
5
+ import os from "os";
6
+ import path from "path";
7
+ import { Command } from "commander";
8
+ import { discovery } from "@ddt-tools/core";
9
+ import chalk from "chalk";
10
+ var DEFAULT_STORE = path.join(os.homedir(), ".ddt", "token-savings.json");
11
+ async function loadCounter(storePath) {
12
+ try {
13
+ const raw = await fs.readFile(storePath, "utf8");
14
+ return discovery.TokenSavingsCounter.deserialize(JSON.parse(raw));
15
+ } catch {
16
+ return new discovery.TokenSavingsCounter();
17
+ }
18
+ }
19
+ async function saveCounter(storePath, counter) {
20
+ await fs.mkdir(path.dirname(storePath), { recursive: true });
21
+ await fs.writeFile(storePath, JSON.stringify(counter.serialize(), null, 2) + "\n", "utf8");
22
+ }
23
+ function savingsCommand() {
24
+ const cmd = new Command("savings");
25
+ cmd.description("Show estimated AI token savings from using deterministic DDT surfaces.").option("--store <path>", "Override the savings JSON store path.", DEFAULT_STORE).option("--format <fmt>", "Output format: text | json.", "text").option("--top <n>", "Show top N channels by tokens saved (text format only).", "5").option("--reset", "Clear all recorded savings and exit.").action(async (opts) => {
26
+ const storePath = String(opts.store ?? DEFAULT_STORE);
27
+ const fmt = String(opts.format ?? "text").toLowerCase();
28
+ const topN = Math.max(1, Math.floor(parseInt(String(opts.top ?? "5"), 10) || 5));
29
+ if (opts.reset) {
30
+ const counter2 = new discovery.TokenSavingsCounter();
31
+ await saveCounter(storePath, counter2);
32
+ process.stdout.write(chalk.green("\u2705 Savings counter reset.\n"));
33
+ return;
34
+ }
35
+ const counter = await loadCounter(storePath);
36
+ const summary = counter.summary();
37
+ if (fmt === "json") {
38
+ const weeks2 = counter.summarizeByWeek();
39
+ const top2 = counter.topChannels(topN);
40
+ process.stdout.write(
41
+ JSON.stringify({ summary, weeklyBreakdown: weeks2, topChannels: top2 }, null, 2) + "\n"
42
+ );
43
+ return;
44
+ }
45
+ if (summary.eventCount === 0) {
46
+ process.stdout.write(
47
+ chalk.dim(
48
+ " No savings recorded yet.\n DDT records a saving every time you use explain, discover, lineage, or similar\n deterministic surfaces instead of asking an AI.\n"
49
+ )
50
+ );
51
+ return;
52
+ }
53
+ const thousands = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n);
54
+ process.stdout.write("\n");
55
+ process.stdout.write(
56
+ chalk.bold(` ${thousands(summary.totalTokens)} tokens saved`) + chalk.dim(` across ${summary.eventCount} interactions`) + "\n"
57
+ );
58
+ if (summary.sinceFirst) {
59
+ process.stdout.write(chalk.dim(` Tracking since ${summary.sinceFirst.slice(0, 10)}
60
+ `));
61
+ }
62
+ process.stdout.write("\n");
63
+ const top = counter.topChannels(topN);
64
+ if (top.length > 0) {
65
+ process.stdout.write(chalk.dim(" Top surfaces:\n"));
66
+ for (const { channel, tokens } of top) {
67
+ process.stdout.write(
68
+ ` ${chalk.cyan(channel.padEnd(28))}${thousands(tokens)} tokens
69
+ `
70
+ );
71
+ }
72
+ process.stdout.write("\n");
73
+ }
74
+ const weeks = counter.summarizeByWeek().slice(0, 4);
75
+ if (weeks.length > 0) {
76
+ process.stdout.write(chalk.dim(" Weekly:\n"));
77
+ for (const bucket of weeks) {
78
+ process.stdout.write(
79
+ ` ${chalk.dim(bucket.week.padEnd(10))}${thousands(bucket.tokens)} tokens
80
+ `
81
+ );
82
+ }
83
+ process.stdout.write("\n");
84
+ }
85
+ process.stdout.write(
86
+ chalk.dim(` Store: ${storePath}
87
+ `) + chalk.dim(" ddt savings --reset \xB7 ddt savings --format json\n")
88
+ );
89
+ });
90
+ return cmd;
91
+ }
92
+ export {
93
+ savingsCommand
94
+ };
95
+ //# sourceMappingURL=savings-MEBE4TXI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/savings.ts"],"sourcesContent":["/**\n * `ddt savings` — display estimated AI token savings accumulated by using\n * deterministic DDT surfaces instead of LLM calls.\n *\n * DSC.8: token-savings counter CLI.\n *\n * $ ddt savings\n * $ ddt savings --format json\n * $ ddt savings --top 5\n * $ ddt savings --reset\n *\n * Persistence: `~/.ddt/token-savings.json` (override with `--store <path>`).\n * The substrate (`@ddt-tools/core TokenSavingsCounter`) is pure in-memory; this\n * file is the disk-I/O boundary.\n *\n * Mirrors `Snowflake/packages/cli/src/commands/savings.ts`.\n */\nimport { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { discovery } from '@ddt-tools/core';\nimport chalk from 'chalk';\n\nconst DEFAULT_STORE = path.join(os.homedir(), '.ddt', 'token-savings.json');\n\nasync function loadCounter(storePath: string): Promise<discovery.TokenSavingsCounter> {\n try {\n const raw = await fs.readFile(storePath, 'utf8');\n return discovery.TokenSavingsCounter.deserialize(JSON.parse(raw) as unknown);\n } catch {\n return new discovery.TokenSavingsCounter();\n }\n}\n\nasync function saveCounter(\n storePath: string,\n counter: discovery.TokenSavingsCounter,\n): Promise<void> {\n await fs.mkdir(path.dirname(storePath), { recursive: true });\n await fs.writeFile(storePath, JSON.stringify(counter.serialize(), null, 2) + '\\n', 'utf8');\n}\n\nexport function savingsCommand(): Command {\n const cmd = new Command('savings');\n cmd\n .description('Show estimated AI token savings from using deterministic DDT surfaces.')\n .option('--store <path>', 'Override the savings JSON store path.', DEFAULT_STORE)\n .option('--format <fmt>', 'Output format: text | json.', 'text')\n .option('--top <n>', 'Show top N channels by tokens saved (text format only).', '5')\n .option('--reset', 'Clear all recorded savings and exit.')\n .action(async (opts) => {\n const storePath = String(opts.store ?? DEFAULT_STORE);\n const fmt = String(opts.format ?? 'text').toLowerCase();\n const topN = Math.max(1, Math.floor(parseInt(String(opts.top ?? '5'), 10) || 5));\n\n if (opts.reset) {\n const counter = new discovery.TokenSavingsCounter();\n await saveCounter(storePath, counter);\n process.stdout.write(chalk.green('✅ Savings counter reset.\\n'));\n return;\n }\n\n const counter = await loadCounter(storePath);\n const summary = counter.summary();\n\n if (fmt === 'json') {\n const weeks = counter.summarizeByWeek();\n const top = counter.topChannels(topN);\n process.stdout.write(\n JSON.stringify({ summary, weeklyBreakdown: weeks, topChannels: top }, null, 2) + '\\n',\n );\n return;\n }\n\n if (summary.eventCount === 0) {\n process.stdout.write(\n chalk.dim(\n ' No savings recorded yet.\\n' +\n ' DDT records a saving every time you use explain, discover, lineage, or similar\\n' +\n ' deterministic surfaces instead of asking an AI.\\n',\n ),\n );\n return;\n }\n\n const thousands = (n: number): string =>\n n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);\n\n process.stdout.write('\\n');\n process.stdout.write(\n chalk.bold(` ${thousands(summary.totalTokens)} tokens saved`) +\n chalk.dim(` across ${summary.eventCount} interactions`) +\n '\\n',\n );\n if (summary.sinceFirst) {\n process.stdout.write(chalk.dim(` Tracking since ${summary.sinceFirst.slice(0, 10)}\\n`));\n }\n process.stdout.write('\\n');\n\n const top = counter.topChannels(topN);\n if (top.length > 0) {\n process.stdout.write(chalk.dim(' Top surfaces:\\n'));\n for (const { channel, tokens } of top) {\n process.stdout.write(\n ` ${chalk.cyan(channel.padEnd(28))}${thousands(tokens)} tokens\\n`,\n );\n }\n process.stdout.write('\\n');\n }\n\n const weeks = counter.summarizeByWeek().slice(0, 4);\n if (weeks.length > 0) {\n process.stdout.write(chalk.dim(' Weekly:\\n'));\n for (const bucket of weeks) {\n process.stdout.write(\n ` ${chalk.dim(bucket.week.padEnd(10))}${thousands(bucket.tokens)} tokens\\n`,\n );\n }\n process.stdout.write('\\n');\n }\n\n process.stdout.write(\n chalk.dim(` Store: ${storePath}\\n`) +\n chalk.dim(' ddt savings --reset · ddt savings --format json\\n'),\n );\n });\n\n return cmd;\n}\n"],"mappings":"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","names":["counter","weeks","top"]}
@@ -0,0 +1,54 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/scan-secrets.ts
4
+ import { promises as fs } from "fs";
5
+ import path from "path";
6
+ import { Command } from "commander";
7
+ import { loadProject, pac, parseProjectModel, secretScanner } from "@ddt-tools/core";
8
+ function scanSecretsCommand() {
9
+ const cmd = new Command("scan-secrets");
10
+ cmd.description("Scan a project or pac for hardcoded credentials in DDL bodies.").requiredOption("--source <path>", ".ddtproj or .ddtpac to scan.").option(
11
+ "--strict",
12
+ "Exit 1 on any finding (errors or warnings). Default: exit 1 on errors only.",
13
+ false
14
+ ).option("--format <fmt>", "table | json | markdown. Default table.", "table").option("-o, --out <path>", "Output file path. Defaults to stdout.").action(async (opts) => {
15
+ const sourcePath = String(opts.source);
16
+ const model = await loadModel(sourcePath);
17
+ const result = secretScanner.scanSecrets(model, { strict: opts.strict });
18
+ const fmt = (opts.format ?? "table").toLowerCase();
19
+ let payload;
20
+ if (fmt === "json") {
21
+ payload = JSON.stringify(result, null, 2);
22
+ } else if (fmt === "markdown") {
23
+ payload = secretScanner.formatSecretReportMarkdown(result);
24
+ } else {
25
+ payload = secretScanner.formatSecretReport(result);
26
+ }
27
+ if (opts.out) {
28
+ const out = path.resolve(String(opts.out));
29
+ await fs.mkdir(path.dirname(out), { recursive: true });
30
+ await fs.writeFile(out, payload + (payload.endsWith("\n") ? "" : "\n"), "utf8");
31
+ console.error(
32
+ `Wrote ${out} (${result.findings.length} finding(s): ${result.errorCount} error(s), ${result.warningCount} warning(s)).`
33
+ );
34
+ } else {
35
+ process.stdout.write(payload + (payload.endsWith("\n") ? "" : "\n"));
36
+ }
37
+ if (result.errorCount > 0 || opts.strict && result.findings.length > 0) {
38
+ process.exitCode = 1;
39
+ }
40
+ });
41
+ return cmd;
42
+ }
43
+ async function loadModel(sourcePath) {
44
+ if (sourcePath.endsWith(".ddtpac")) {
45
+ const c = await pac.readPac(sourcePath);
46
+ return c.model;
47
+ }
48
+ const loaded = await loadProject(sourcePath);
49
+ return await parseProjectModel(loaded);
50
+ }
51
+ export {
52
+ scanSecretsCommand
53
+ };
54
+ //# sourceMappingURL=scan-secrets-XCUBMLHL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/scan-secrets.ts"],"sourcesContent":["/**\n * `ddt scan-secrets` — scan a project's DDL bodies for hardcoded credentials.\n *\n * Covers: Databricks PATs (dapi*), workspace URLs with embedded tokens,\n * OAuth client_secret literals, AWS keys, OpenAI keys, GitHub tokens,\n * Slack tokens, JWTs, PEM private-key blocks, and generic credential fields.\n *\n * Exit codes:\n * 0 — clean (or only warnings in non-strict mode)\n * 1 — errors found (always) or any finding in --strict mode\n *\n * Mirrors `sdt scan-secrets`.\n */\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { loadProject, pac, parseProjectModel, secretScanner } from '@ddt-tools/core';\n\nexport function scanSecretsCommand(): Command {\n const cmd = new Command('scan-secrets');\n cmd\n .description('Scan a project or pac for hardcoded credentials in DDL bodies.')\n .requiredOption('--source <path>', '.ddtproj or .ddtpac to scan.')\n .option(\n '--strict',\n 'Exit 1 on any finding (errors or warnings). Default: exit 1 on errors only.',\n false,\n )\n .option('--format <fmt>', 'table | json | markdown. Default table.', 'table')\n .option('-o, --out <path>', 'Output file path. Defaults to stdout.')\n .action(async (opts: { source: string; strict?: boolean; format?: string; out?: string }) => {\n const sourcePath = String(opts.source);\n const model = await loadModel(sourcePath);\n const result = secretScanner.scanSecrets(model, { strict: opts.strict });\n\n const fmt = (opts.format ?? 'table').toLowerCase();\n let payload: string;\n if (fmt === 'json') {\n payload = JSON.stringify(result, null, 2);\n } else if (fmt === 'markdown') {\n payload = secretScanner.formatSecretReportMarkdown(result);\n } else {\n payload = secretScanner.formatSecretReport(result);\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} (${result.findings.length} finding(s): ${result.errorCount} error(s), ${result.warningCount} warning(s)).`,\n );\n } else {\n process.stdout.write(payload + (payload.endsWith('\\n') ? '' : '\\n'));\n }\n\n if (result.errorCount > 0 || (opts.strict && result.findings.length > 0)) {\n process.exitCode = 1;\n }\n });\n\n return cmd;\n}\n\nasync function loadModel(sourcePath: string) {\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"],"mappings":";;;AAaA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AACjB,SAAS,eAAe;AACxB,SAAS,aAAa,KAAK,mBAAmB,qBAAqB;AAE5D,SAAS,qBAA8B;AAC5C,QAAM,MAAM,IAAI,QAAQ,cAAc;AACtC,MACG,YAAY,gEAAgE,EAC5E,eAAe,mBAAmB,8BAA8B,EAChE;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC,OAAO,kBAAkB,2CAA2C,OAAO,EAC3E,OAAO,oBAAoB,uCAAuC,EAClE,OAAO,OAAO,SAA8E;AAC3F,UAAM,aAAa,OAAO,KAAK,MAAM;AACrC,UAAM,QAAQ,MAAM,UAAU,UAAU;AACxC,UAAM,SAAS,cAAc,YAAY,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC;AAEvE,UAAM,OAAO,KAAK,UAAU,SAAS,YAAY;AACjD,QAAI;AACJ,QAAI,QAAQ,QAAQ;AAClB,gBAAU,KAAK,UAAU,QAAQ,MAAM,CAAC;AAAA,IAC1C,WAAW,QAAQ,YAAY;AAC7B,gBAAU,cAAc,2BAA2B,MAAM;AAAA,IAC3D,OAAO;AACL,gBAAU,cAAc,mBAAmB,MAAM;AAAA,IACnD;AAEA,QAAI,KAAK,KAAK;AACZ,YAAM,MAAM,KAAK,QAAQ,OAAO,KAAK,GAAG,CAAC;AACzC,YAAM,GAAG,MAAM,KAAK,QAAQ,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;AACrD,YAAM,GAAG,UAAU,KAAK,WAAW,QAAQ,SAAS,IAAI,IAAI,KAAK,OAAO,MAAM;AAC9E,cAAQ;AAAA,QACN,SAAS,GAAG,KAAK,OAAO,SAAS,MAAM,gBAAgB,OAAO,UAAU,cAAc,OAAO,YAAY;AAAA,MAC3G;AAAA,IACF,OAAO;AACL,cAAQ,OAAO,MAAM,WAAW,QAAQ,SAAS,IAAI,IAAI,KAAK,KAAK;AAAA,IACrE;AAEA,QAAI,OAAO,aAAa,KAAM,KAAK,UAAU,OAAO,SAAS,SAAS,GAAI;AACxE,cAAQ,WAAW;AAAA,IACrB;AAAA,EACF,CAAC;AAEH,SAAO;AACT;AAEA,eAAe,UAAU,YAAoB;AAC3C,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;","names":[]}