@sdt-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.
- package/dist/advise-tests-6DRSZMBL.js +87 -0
- package/dist/advise-tests-6DRSZMBL.js.map +1 -0
- package/dist/ai-G4MJWHTM.js +89 -0
- package/dist/ai-G4MJWHTM.js.map +1 -0
- package/dist/anonymize-QR6JGXA7.js +123 -0
- package/dist/anonymize-QR6JGXA7.js.map +1 -0
- package/dist/approval-YVHYTV53.js +73 -0
- package/dist/approval-YVHYTV53.js.map +1 -0
- package/dist/approval-chain-54KKJZS3.js +120 -0
- package/dist/approval-chain-54KKJZS3.js.map +1 -0
- package/dist/audit-log-QZFH7LUX.js +159 -0
- package/dist/audit-log-QZFH7LUX.js.map +1 -0
- package/dist/backlog-V2YUIQDL.js +76 -0
- package/dist/backlog-V2YUIQDL.js.map +1 -0
- package/dist/bisect-GEVYAVL5.js +111 -0
- package/dist/bisect-GEVYAVL5.js.map +1 -0
- package/dist/bookmarks-57LKS7P6.js +107 -0
- package/dist/bookmarks-57LKS7P6.js.map +1 -0
- package/dist/branch-W2MGMPSH.js +88 -0
- package/dist/branch-W2MGMPSH.js.map +1 -0
- package/dist/build-VNIQFKSP.js +23 -0
- package/dist/build-VNIQFKSP.js.map +1 -0
- package/dist/catalog-JLB5VCEV.js +137 -0
- package/dist/catalog-JLB5VCEV.js.map +1 -0
- package/dist/changelog-M7XGDYSY.js +220 -0
- package/dist/changelog-M7XGDYSY.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-EWXM4KJN.js +25 -0
- package/dist/chunk-EWXM4KJN.js.map +1 -0
- package/dist/chunk-JP2EZLR5.js +50 -0
- package/dist/chunk-JP2EZLR5.js.map +1 -0
- package/dist/chunk-VM2H4LAO.js +15 -0
- package/dist/chunk-VM2H4LAO.js.map +1 -0
- package/dist/chunk-ZWY4ZRHL.js +44 -0
- package/dist/chunk-ZWY4ZRHL.js.map +1 -0
- package/dist/cli.js +506 -19014
- package/dist/cli.js.map +1 -1
- package/dist/compare-5O6UTWPJ.js +405 -0
- package/dist/compare-5O6UTWPJ.js.map +1 -0
- package/dist/compare-profiles-7ZSNIW7B.js +218 -0
- package/dist/compare-profiles-7ZSNIW7B.js.map +1 -0
- package/dist/completion-I5U5VVAX.js +82 -0
- package/dist/completion-I5U5VVAX.js.map +1 -0
- package/dist/connection-SYTH4V53.js +110 -0
- package/dist/connection-SYTH4V53.js.map +1 -0
- package/dist/cost-estimate-TJDDH6TO.js +328 -0
- package/dist/cost-estimate-TJDDH6TO.js.map +1 -0
- package/dist/data-compare-UK2UXAS3.js +134 -0
- package/dist/data-compare-UK2UXAS3.js.map +1 -0
- package/dist/data-fit-Q45ENBRL.js +125 -0
- package/dist/data-fit-Q45ENBRL.js.map +1 -0
- package/dist/deploy-status-UUHKVDTI.js +58 -0
- package/dist/deploy-status-UUHKVDTI.js.map +1 -0
- package/dist/design-PO6UPBL7.js +138 -0
- package/dist/design-PO6UPBL7.js.map +1 -0
- package/dist/diagnose-6IFMELFR.js +145 -0
- package/dist/diagnose-6IFMELFR.js.map +1 -0
- package/dist/discover-A7OSZAHK.js +78 -0
- package/dist/discover-A7OSZAHK.js.map +1 -0
- package/dist/docs-CVRKGUSW.js +177 -0
- package/dist/docs-CVRKGUSW.js.map +1 -0
- package/dist/drift-XDA3BDYN.js +226 -0
- package/dist/drift-XDA3BDYN.js.map +1 -0
- package/dist/drift-gate-V7QSIOGZ.js +94 -0
- package/dist/drift-gate-V7QSIOGZ.js.map +1 -0
- package/dist/error-lookup-7ZWCZJ44.js +56 -0
- package/dist/error-lookup-7ZWCZJ44.js.map +1 -0
- package/dist/errorReporting-ZRNJ3VW7.js +109 -0
- package/dist/errorReporting-ZRNJ3VW7.js.map +1 -0
- package/dist/exec-PKBHLI7T.js +121 -0
- package/dist/exec-PKBHLI7T.js.map +1 -0
- package/dist/explain-LWKJOTL7.js +192 -0
- package/dist/explain-LWKJOTL7.js.map +1 -0
- package/dist/explorer-QOVM6VBD.js +61 -0
- package/dist/explorer-QOVM6VBD.js.map +1 -0
- package/dist/export-IYYBZ5HE.js +42 -0
- package/dist/export-IYYBZ5HE.js.map +1 -0
- package/dist/extract-VMMVRQVT.js +102 -0
- package/dist/extract-VMMVRQVT.js.map +1 -0
- package/dist/features-LE6BDZ2S.js +59 -0
- package/dist/features-LE6BDZ2S.js.map +1 -0
- package/dist/feedback-M7DM2EQC.js +161 -0
- package/dist/feedback-M7DM2EQC.js.map +1 -0
- package/dist/find-EME2JG2I.js +176 -0
- package/dist/find-EME2JG2I.js.map +1 -0
- package/dist/format-TRLWLMGS.js +141 -0
- package/dist/format-TRLWLMGS.js.map +1 -0
- package/dist/generate-6NAZGZDV.js +152 -0
- package/dist/generate-6NAZGZDV.js.map +1 -0
- package/dist/graph-QNQDAUO7.js +161 -0
- package/dist/graph-QNQDAUO7.js.map +1 -0
- package/dist/history-RONA7ZTI.js +199 -0
- package/dist/history-RONA7ZTI.js.map +1 -0
- package/dist/hosts-YBXY2ZG5.js +49 -0
- package/dist/hosts-YBXY2ZG5.js.map +1 -0
- package/dist/impact-T2JSANHS.js +59 -0
- package/dist/impact-T2JSANHS.js.map +1 -0
- package/dist/import-AELYLY6A.js +32 -0
- package/dist/import-AELYLY6A.js.map +1 -0
- package/dist/index.cjs +36 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +60 -25
- package/dist/index.js.map +1 -1
- package/dist/init-SWRRJMGI.js +57 -0
- package/dist/init-SWRRJMGI.js.map +1 -0
- package/dist/install-hooks-6SIAGTAF.js +109 -0
- package/dist/install-hooks-6SIAGTAF.js.map +1 -0
- package/dist/license-OAF22PLZ.js +46 -0
- package/dist/license-OAF22PLZ.js.map +1 -0
- package/dist/lineage-EW66XJ6O.js +552 -0
- package/dist/lineage-EW66XJ6O.js.map +1 -0
- package/dist/lint-FQ2OTYTQ.js +143 -0
- package/dist/lint-FQ2OTYTQ.js.map +1 -0
- package/dist/mcp-3QI4TH4N.js +344 -0
- package/dist/mcp-3QI4TH4N.js.map +1 -0
- package/dist/migrate-from-dbt-JVTXPWKQ.js +156 -0
- package/dist/migrate-from-dbt-JVTXPWKQ.js.map +1 -0
- package/dist/migrate-platform-NTRTOGNR.js +91 -0
- package/dist/migrate-platform-NTRTOGNR.js.map +1 -0
- package/dist/optimize-CJYWMAWA.js +105 -0
- package/dist/optimize-CJYWMAWA.js.map +1 -0
- package/dist/perf-LL2CPCJF.js +205 -0
- package/dist/perf-LL2CPCJF.js.map +1 -0
- package/dist/pii-FBDRDQ2E.js +136 -0
- package/dist/pii-FBDRDQ2E.js.map +1 -0
- package/dist/pilot-CCQERKPH.js +29 -0
- package/dist/pilot-CCQERKPH.js.map +1 -0
- package/dist/pr-comment-S5FF4QRX.js +79 -0
- package/dist/pr-comment-S5FF4QRX.js.map +1 -0
- package/dist/preview-5U4YVCRM.js +47 -0
- package/dist/preview-5U4YVCRM.js.map +1 -0
- package/dist/profile-7VC57KD2.js +101 -0
- package/dist/profile-7VC57KD2.js.map +1 -0
- package/dist/promote-AASEFTIA.js +408 -0
- package/dist/promote-AASEFTIA.js.map +1 -0
- package/dist/publish-Y2J56K4Y.js +715 -0
- package/dist/publish-Y2J56K4Y.js.map +1 -0
- package/dist/purge-QMXZKCMD.js +57 -0
- package/dist/purge-QMXZKCMD.js.map +1 -0
- package/dist/query-log-6OM4GI7W.js +112 -0
- package/dist/query-log-6OM4GI7W.js.map +1 -0
- package/dist/refactor-LTZQLJ35.js +5799 -0
- package/dist/refactor-LTZQLJ35.js.map +1 -0
- package/dist/refresh-4TY2AGOU.js +38 -0
- package/dist/refresh-4TY2AGOU.js.map +1 -0
- package/dist/replay-OOC25FZN.js +117 -0
- package/dist/replay-OOC25FZN.js.map +1 -0
- package/dist/revert-ODMUVJW6.js +110 -0
- package/dist/revert-ODMUVJW6.js.map +1 -0
- package/dist/review-XXPWOBFP.js +158 -0
- package/dist/review-XXPWOBFP.js.map +1 -0
- package/dist/rollback-suggest-6G2HEKFR.js +79 -0
- package/dist/rollback-suggest-6G2HEKFR.js.map +1 -0
- package/dist/safer-alternative-QFVNLG3L.js +89 -0
- package/dist/safer-alternative-QFVNLG3L.js.map +1 -0
- package/dist/safety-7QWRSUEZ.js +168 -0
- package/dist/safety-7QWRSUEZ.js.map +1 -0
- package/dist/savings-RHIXP6IT.js +95 -0
- package/dist/savings-RHIXP6IT.js.map +1 -0
- package/dist/scan-secrets-5YCQ4UCU.js +54 -0
- package/dist/scan-secrets-5YCQ4UCU.js.map +1 -0
- package/dist/schema-CIZXCQD2.js +429 -0
- package/dist/schema-CIZXCQD2.js.map +1 -0
- package/dist/script-K7CIN2P6.js +153 -0
- package/dist/script-K7CIN2P6.js.map +1 -0
- package/dist/search-BUZ5NXZZ.js +151 -0
- package/dist/search-BUZ5NXZZ.js.map +1 -0
- package/dist/seed-76QAK276.js +96 -0
- package/dist/seed-76QAK276.js.map +1 -0
- package/dist/sketch-PTLKDIK3.js +88 -0
- package/dist/sketch-PTLKDIK3.js.map +1 -0
- package/dist/snapshot-XLPR2OZ5.js +177 -0
- package/dist/snapshot-XLPR2OZ5.js.map +1 -0
- package/dist/snippets-EK4DK5CN.js +74 -0
- package/dist/snippets-EK4DK5CN.js.map +1 -0
- package/dist/standards-7T2UY6DD.js +241 -0
- package/dist/standards-7T2UY6DD.js.map +1 -0
- package/dist/suggest-VGRYSAR6.js +39 -0
- package/dist/suggest-VGRYSAR6.js.map +1 -0
- package/dist/suggest-constraints-MY5WKUHA.js +160 -0
- package/dist/suggest-constraints-MY5WKUHA.js.map +1 -0
- package/dist/suite-TRNGZWQM.js +88 -0
- package/dist/suite-TRNGZWQM.js.map +1 -0
- package/dist/telemetry-3U2QLA2S.js +75 -0
- package/dist/telemetry-3U2QLA2S.js.map +1 -0
- package/dist/template-ZERIXVXF.js +403 -0
- package/dist/template-ZERIXVXF.js.map +1 -0
- package/dist/test-5M2ED3WT.js +169 -0
- package/dist/test-5M2ED3WT.js.map +1 -0
- package/dist/trial-U732FONV.js +31 -0
- package/dist/trial-U732FONV.js.map +1 -0
- package/dist/validate-T6D2WCOK.js +106 -0
- package/dist/validate-T6D2WCOK.js.map +1 -0
- package/dist/verify-KXVASEEG.js +76 -0
- package/dist/verify-KXVASEEG.js.map +1 -0
- package/dist/watch-I6K4BNMA.js +80 -0
- package/dist/watch-I6K4BNMA.js.map +1 -0
- package/dist/xcompare-TPFLQO6W.js +87 -0
- package/dist/xcompare-TPFLQO6W.js.map +1 -0
- package/package.json +2 -2
- package/dist/cli.cjs +0 -19040
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.d.cts +0 -1
- package/dist/cli.d.ts +0 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-VM2H4LAO.js";
|
|
4
|
+
import "./chunk-DGUM43GV.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/safer-alternative.ts
|
|
7
|
+
import { promises as fs } from "fs";
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
import { ai, saferAlternative } from "@sdt-tools/core";
|
|
10
|
+
function saferAlternativeCommand() {
|
|
11
|
+
const cmd = new Command("safer-alternative");
|
|
12
|
+
cmd.description(
|
|
13
|
+
"AI-assist: propose a safer DDL alternative for a safety finding. Requires a configured AI provider (sdt ai status)."
|
|
14
|
+
).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. TABLE, VIEW, STREAM).").requiredOption("--reason <text>", "Short reason text from the safety classifier.").option(
|
|
15
|
+
"--gate <gate>",
|
|
16
|
+
"Optional SafetyGate the finding raised (e.g. REQUIRE_ALLOW_DROP_COLUMN)."
|
|
17
|
+
).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(
|
|
18
|
+
"--ai-max-spend <usd>",
|
|
19
|
+
"Refuse the call if today's estimated spend \u2265 this (USD). 0 = no cap.",
|
|
20
|
+
"0"
|
|
21
|
+
).action(async (opts) => {
|
|
22
|
+
const dangerousSql = await readInput(
|
|
23
|
+
opts.sql,
|
|
24
|
+
'--sql is required (use a path or "-" for stdin).'
|
|
25
|
+
);
|
|
26
|
+
const contextSql = opts.context ? await fs.readFile(String(opts.context), "utf8") : void 0;
|
|
27
|
+
const finding = {
|
|
28
|
+
code: String(opts.code),
|
|
29
|
+
category: "DESTRUCTIVE",
|
|
30
|
+
fqn: String(opts.fqn),
|
|
31
|
+
objectType: String(opts.objectType),
|
|
32
|
+
reason: String(opts.reason),
|
|
33
|
+
gate: opts.gate ? String(opts.gate) : void 0
|
|
34
|
+
};
|
|
35
|
+
const result = await saferAlternative.suggestSaferAlternative(
|
|
36
|
+
{
|
|
37
|
+
finding,
|
|
38
|
+
dangerousSql,
|
|
39
|
+
contextSql,
|
|
40
|
+
intentNotes: opts.intent ? String(opts.intent) : void 0
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
completeFn: async (prompt) => {
|
|
44
|
+
const r = await ai.complete([{ role: "user", content: prompt }], {
|
|
45
|
+
feature: "safer-alternative",
|
|
46
|
+
maxSpendUsd: Number(opts.aiMaxSpend ?? "0") || 0
|
|
47
|
+
});
|
|
48
|
+
return r.text;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
if (String(opts.format).toLowerCase() === "json") {
|
|
53
|
+
const { rawModelText: _omit, ...keep } = result;
|
|
54
|
+
console.log(JSON.stringify(keep, null, 2));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
logger.success(
|
|
58
|
+
`Confidence: ${result.confidence}${result.parseFailed ? " (parse failed)" : ""}`
|
|
59
|
+
);
|
|
60
|
+
logger.dim(`Reasoning: ${result.reasoning}`);
|
|
61
|
+
if (result.alternativeSql) {
|
|
62
|
+
console.log("");
|
|
63
|
+
console.log("--- Proposed safer DDL ---");
|
|
64
|
+
console.log(result.alternativeSql);
|
|
65
|
+
} else {
|
|
66
|
+
logger.warn("No safer alternative SQL was returned.");
|
|
67
|
+
}
|
|
68
|
+
if (result.requiredGates.length > 0) {
|
|
69
|
+
logger.dim(`Required gates: ${result.requiredGates.join(", ")}`);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return cmd;
|
|
73
|
+
}
|
|
74
|
+
async function readInput(pathOrDash, missingMessage) {
|
|
75
|
+
if (!pathOrDash) throw new Error(missingMessage);
|
|
76
|
+
const p = String(pathOrDash);
|
|
77
|
+
if (p === "-") {
|
|
78
|
+
const chunks = [];
|
|
79
|
+
for await (const chunk of process.stdin) {
|
|
80
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
81
|
+
}
|
|
82
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
83
|
+
}
|
|
84
|
+
return fs.readFile(p, "utf8");
|
|
85
|
+
}
|
|
86
|
+
export {
|
|
87
|
+
saferAlternativeCommand
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=safer-alternative-QFVNLG3L.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/safer-alternative.ts"],"sourcesContent":["/**\n * `sdt safer-alternative` — call the AI provider to suggest a safer DDL\n * alternative for a flagged-as-dangerous migration step.\n *\n * Composes `@sdt-tools/core/saferAlternative.suggestSaferAlternative` with the\n * configured AI provider (`@sdt-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. TABLE, STREAM)\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 '@sdt-tools/core';\nimport { logger } from '../util/logger.js';\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 (sdt 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. TABLE, VIEW, STREAM).')\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 // Strip rawModelText from JSON output by default — it bloats the\n // payload for downstream tooling. Keep it accessible via --format text.\n const { rawModelText: _omit, ...keep } = result;\n console.log(JSON.stringify(keep, null, 2));\n return;\n }\n\n logger.success(\n `Confidence: ${result.confidence}${result.parseFailed ? ' (parse failed)' : ''}`,\n );\n logger.dim(`Reasoning: ${result.reasoning}`);\n if (result.alternativeSql) {\n console.log('');\n console.log('--- Proposed safer DDL ---');\n console.log(result.alternativeSql);\n } else {\n logger.warn('No safer alternative SQL was returned.');\n }\n if (result.requiredGates.length > 0) {\n logger.dim(`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;AAG3C,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,yCAAyC,EAChF,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;AAGhD,YAAM,EAAE,cAAc,OAAO,GAAG,KAAK,IAAI;AACzC,cAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AACzC;AAAA,IACF;AAEA,WAAO;AAAA,MACL,eAAe,OAAO,UAAU,GAAG,OAAO,cAAc,oBAAoB,EAAE;AAAA,IAChF;AACA,WAAO,IAAI,cAAc,OAAO,SAAS,EAAE;AAC3C,QAAI,OAAO,gBAAgB;AACzB,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,4BAA4B;AACxC,cAAQ,IAAI,OAAO,cAAc;AAAA,IACnC,OAAO;AACL,aAAO,KAAK,wCAAwC;AAAA,IACtD;AACA,QAAI,OAAO,cAAc,SAAS,GAAG;AACnC,aAAO,IAAI,mBAAmB,OAAO,cAAc,KAAK,IAAI,CAAC,EAAE;AAAA,IACjE;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,168 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-VM2H4LAO.js";
|
|
4
|
+
import "./chunk-DGUM43GV.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/safety.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { safety as safetyApi } from "@sdt-tools/core";
|
|
9
|
+
function safetyCommand() {
|
|
10
|
+
const cmd = new Command("safety");
|
|
11
|
+
cmd.description(
|
|
12
|
+
"Inspect the safety-finding catalog. See `sdt safety list` and `sdt safety explain <code>`."
|
|
13
|
+
);
|
|
14
|
+
cmd.command("list").description("List every known finding code with category + one-line summary.").option("--format <format>", "Output format: table | json", "table").option(
|
|
15
|
+
"--category <kind>",
|
|
16
|
+
"Filter to one category: unrecoverable | destructive | expensive | warning. Default: all."
|
|
17
|
+
).action((opts) => {
|
|
18
|
+
const codes = safetyApi.listFindingCodes();
|
|
19
|
+
let entries = codes.map((c) => safetyApi.explainFinding(c)).filter((e) => e !== void 0);
|
|
20
|
+
if (opts.category) {
|
|
21
|
+
const want = opts.category.toUpperCase();
|
|
22
|
+
const valid = ["UNRECOVERABLE", "DESTRUCTIVE", "EXPENSIVE", "WARNING"];
|
|
23
|
+
if (!valid.includes(want)) {
|
|
24
|
+
logger.error(
|
|
25
|
+
`Unknown --category "${opts.category}". Use one of: ${valid.join(" | ").toLowerCase()}.`
|
|
26
|
+
);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
entries = entries.filter((e) => e.category === want);
|
|
31
|
+
}
|
|
32
|
+
if ((opts.format ?? "table") === "json") {
|
|
33
|
+
process.stdout.write(JSON.stringify(entries, null, 2) + "\n");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (entries.length === 0) {
|
|
37
|
+
logger.dim("(no entries match the filter)");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
printList(entries);
|
|
41
|
+
});
|
|
42
|
+
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) => {
|
|
43
|
+
const code = codeArg.toUpperCase();
|
|
44
|
+
const entry = safetyApi.explainFinding(code);
|
|
45
|
+
if (!entry) {
|
|
46
|
+
logger.error(`Unknown safety finding code: "${codeArg}"`);
|
|
47
|
+
logger.dim(" Try: sdt safety list");
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const format = (opts.format ?? "text").toLowerCase();
|
|
52
|
+
if (format === "json") {
|
|
53
|
+
process.stdout.write(JSON.stringify(entry, null, 2) + "\n");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (format === "markdown") {
|
|
57
|
+
process.stdout.write(renderMarkdown(entry) + "\n");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
printDeep(entry);
|
|
61
|
+
});
|
|
62
|
+
return cmd;
|
|
63
|
+
}
|
|
64
|
+
function printList(entries) {
|
|
65
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
66
|
+
for (const e of entries) {
|
|
67
|
+
const arr = byCategory.get(e.category) ?? [];
|
|
68
|
+
arr.push(e);
|
|
69
|
+
byCategory.set(e.category, arr);
|
|
70
|
+
}
|
|
71
|
+
const order = [
|
|
72
|
+
"UNRECOVERABLE",
|
|
73
|
+
"DESTRUCTIVE",
|
|
74
|
+
"EXPENSIVE",
|
|
75
|
+
"WARNING"
|
|
76
|
+
];
|
|
77
|
+
for (const cat of order) {
|
|
78
|
+
const arr = byCategory.get(cat);
|
|
79
|
+
if (!arr || arr.length === 0) continue;
|
|
80
|
+
console.log(`# ${cat} (${arr.length})`);
|
|
81
|
+
for (const e of arr) {
|
|
82
|
+
console.log(` ${e.code}`);
|
|
83
|
+
console.log(` ${e.title}`);
|
|
84
|
+
console.log(` ${e.summary}`);
|
|
85
|
+
}
|
|
86
|
+
console.log("");
|
|
87
|
+
}
|
|
88
|
+
console.log("Use `sdt safety explain <code>` for the full per-finding write-up.");
|
|
89
|
+
}
|
|
90
|
+
function printDeep(entry) {
|
|
91
|
+
console.log(`${entry.code} [${entry.category}]`);
|
|
92
|
+
console.log(entry.title);
|
|
93
|
+
console.log("");
|
|
94
|
+
console.log("Summary");
|
|
95
|
+
console.log(` ${entry.summary}`);
|
|
96
|
+
console.log("");
|
|
97
|
+
console.log("Why this is dangerous");
|
|
98
|
+
for (const line of wrap(entry.whyDangerous, 76)) {
|
|
99
|
+
console.log(` ${line}`);
|
|
100
|
+
}
|
|
101
|
+
console.log("");
|
|
102
|
+
console.log("What cannot be reversed");
|
|
103
|
+
for (const item of entry.cannotBeReversed) console.log(` - ${item}`);
|
|
104
|
+
console.log("");
|
|
105
|
+
console.log("Safer alternatives");
|
|
106
|
+
for (const item of entry.saferAlternatives) console.log(` - ${item}`);
|
|
107
|
+
console.log("");
|
|
108
|
+
if (entry.requiredGates.length > 0) {
|
|
109
|
+
console.log("Required gates to allow this finding through");
|
|
110
|
+
for (const g of entry.requiredGates) console.log(` - ${g}`);
|
|
111
|
+
console.log("");
|
|
112
|
+
}
|
|
113
|
+
if (entry.example) {
|
|
114
|
+
console.log("Example");
|
|
115
|
+
console.log(` ${entry.example}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function renderMarkdown(entry) {
|
|
119
|
+
const lines = [];
|
|
120
|
+
lines.push(`## ${entry.code} \u2014 ${entry.title}`);
|
|
121
|
+
lines.push("");
|
|
122
|
+
lines.push(`**Category:** ${entry.category}`);
|
|
123
|
+
lines.push("");
|
|
124
|
+
lines.push(`**Summary:** ${entry.summary}`);
|
|
125
|
+
lines.push("");
|
|
126
|
+
lines.push("### Why this is dangerous");
|
|
127
|
+
lines.push(entry.whyDangerous);
|
|
128
|
+
lines.push("");
|
|
129
|
+
lines.push("### What cannot be reversed");
|
|
130
|
+
for (const item of entry.cannotBeReversed) lines.push(`- ${item}`);
|
|
131
|
+
lines.push("");
|
|
132
|
+
lines.push("### Safer alternatives");
|
|
133
|
+
for (const item of entry.saferAlternatives) lines.push(`- ${item}`);
|
|
134
|
+
if (entry.requiredGates.length > 0) {
|
|
135
|
+
lines.push("");
|
|
136
|
+
lines.push("### Required gates");
|
|
137
|
+
for (const g of entry.requiredGates) lines.push(`- \`${g}\``);
|
|
138
|
+
}
|
|
139
|
+
if (entry.example) {
|
|
140
|
+
lines.push("");
|
|
141
|
+
lines.push("### Example");
|
|
142
|
+
lines.push("```");
|
|
143
|
+
lines.push(entry.example);
|
|
144
|
+
lines.push("```");
|
|
145
|
+
}
|
|
146
|
+
return lines.join("\n");
|
|
147
|
+
}
|
|
148
|
+
function wrap(text, width) {
|
|
149
|
+
const words = text.split(/\s+/);
|
|
150
|
+
const lines = [];
|
|
151
|
+
let current = "";
|
|
152
|
+
for (const word of words) {
|
|
153
|
+
if (current.length === 0) {
|
|
154
|
+
current = word;
|
|
155
|
+
} else if (current.length + 1 + word.length <= width) {
|
|
156
|
+
current += " " + word;
|
|
157
|
+
} else {
|
|
158
|
+
lines.push(current);
|
|
159
|
+
current = word;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (current.length > 0) lines.push(current);
|
|
163
|
+
return lines;
|
|
164
|
+
}
|
|
165
|
+
export {
|
|
166
|
+
safetyCommand
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=safety-7QWRSUEZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/safety.ts"],"sourcesContent":["/**\n * `sdt safety` — inspect safety-finding codes.\n *\n * Subcommands:\n * sdt safety list — list every known finding code with a one-line summary\n * sdt 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 `sdt compare` / `sdt publish`. When the user\n * sees a finding they don't understand, they can copy the code and ask:\n *\n * sdt safety explain DROP_UNRECOVERABLE\n *\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 } from '@sdt-tools/core';\nimport type { FindingExplanation, SafetyFindingCode } from '@sdt-tools/core/safety';\nimport { logger } from '../util/logger.js';\n\nexport function safetyCommand(): Command {\n const cmd = new Command('safety');\n cmd.description(\n 'Inspect the safety-finding catalog. See `sdt safety list` and `sdt 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 logger.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 logger.dim('(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 logger.error(`Unknown safety finding code: \"${codeArg}\"`);\n logger.dim(' Try: sdt 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 `sdt 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\n/** Word-wrap a paragraph to fit `width` characters per line. */\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 "@sdt-tools/core";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
var DEFAULT_STORE = path.join(os.homedir(), ".sdt", "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 SDT 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 SDT 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(" sdt savings --reset \xB7 sdt savings --format json\n")
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
return cmd;
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
savingsCommand
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=savings-RHIXP6IT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/savings.ts"],"sourcesContent":["/**\n * `sdt savings` — display estimated AI token savings accumulated by using\n * deterministic SDT surfaces instead of LLM calls.\n *\n * DSC.8: token-savings counter CLI.\n *\n * $ sdt savings\n * $ sdt savings --format json\n * $ sdt savings --top 5\n * $ sdt savings --reset\n *\n * Persistence: `~/.sdt/token-savings.json` (override with `--store <path>`).\n * The substrate (`@sdt-tools/core TokenSavingsCounter`) is pure in-memory; this\n * file is the disk-I/O boundary.\n *\n * Mirrors `Databricks/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 '@sdt-tools/core';\nimport chalk from 'chalk';\n\nconst DEFAULT_STORE = path.join(os.homedir(), '.sdt', '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 SDT 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 ' SDT 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(' sdt savings --reset · sdt 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 { pac, project, secretScanner } from "@sdt-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>", ".sdtproj or .sdtpac 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(".sdtpac")) {
|
|
45
|
+
const c = await pac.readPac(sourcePath);
|
|
46
|
+
return c.model;
|
|
47
|
+
}
|
|
48
|
+
const loaded = await project.loadProject(sourcePath);
|
|
49
|
+
return await project.parseProjectModel(loaded);
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
scanSecretsCommand
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=scan-secrets-5YCQ4UCU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/scan-secrets.ts"],"sourcesContent":["/**\n * `sdt scan-secrets` — scan a project's DDL bodies for hardcoded credentials.\n *\n * Covers: passwords (IDENTIFIED BY), OAuth client_secret literals, AWS keys,\n * OpenAI keys, GitHub tokens, Slack tokens, JWTs, PEM private-key blocks,\n * Snowflake account-locator URLs, and generic high-entropy 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 `ddt scan-secrets`.\n */\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { pac, project, secretScanner } from '@sdt-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>', '.sdtproj or .sdtpac 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('.sdtpac')) {\n const c = await pac.readPac(sourcePath);\n return c.model;\n }\n const loaded = await project.loadProject(sourcePath);\n return await project.parseProjectModel(loaded);\n}\n"],"mappings":";;;AAaA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AACjB,SAAS,eAAe;AACxB,SAAS,KAAK,SAAS,qBAAqB;AAErC,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,QAAQ,YAAY,UAAU;AACnD,SAAO,MAAM,QAAQ,kBAAkB,MAAM;AAC/C;","names":[]}
|