@ddt-tools/cli 0.2.4 → 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/cli.js +15 -10
- package/dist/cli.js.map +1 -1
- package/dist/{compare-P7JOV76O.js → compare-IOEATL6G.js} +69 -13
- package/dist/compare-IOEATL6G.js.map +1 -0
- package/dist/{errorReporting-3LPE2IJY.js → errorReporting-LX6WT4JH.js} +2 -2
- package/dist/{errorReporting-3LPE2IJY.js.map → errorReporting-LX6WT4JH.js.map} +1 -1
- package/dist/import-EGOVKTLX.js +29 -0
- package/dist/import-EGOVKTLX.js.map +1 -0
- package/dist/{import-2RNYDL4E.js → import-script-R5RXPDH6.js} +5 -5
- package/dist/import-script-R5RXPDH6.js.map +1 -0
- package/dist/{mcp-F7FND5X7.js → mcp-6ZXOAF7S.js} +2 -2
- package/dist/{mcp-F7FND5X7.js.map → mcp-6ZXOAF7S.js.map} +1 -1
- package/dist/{publish-AYCRMCE2.js → publish-HLP3XHM5.js} +35 -8
- package/dist/publish-HLP3XHM5.js.map +1 -0
- package/package.json +2 -2
- package/dist/compare-P7JOV76O.js.map +0 -1
- package/dist/import-2RNYDL4E.js.map +0 -1
- package/dist/publish-AYCRMCE2.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/util/errorReporting.ts"],"sourcesContent":["/**\n * CLI error-reporting wiring (ERR.2).\n *\n * Lazily imported from `cli.ts` hooks so the cold-start path pays zero cost\n * until a command actually runs (preAction) or fails (catch handler).\n *\n * Responsibilities:\n * - first-run consent prompt (TTY only, default Yes, one keystroke)\n * - crash-hook installation when consent allows capture\n * - end-of-run drain: spool → `POST /errors` when consent is `on`\n * - manual \"Report this error? [y/N]\" prompt when consent is NOT `on`\n * and a command fails\n *\n * @see @ddt-tools/core/errorReport (capture substrate + consent + transport)\n */\nimport { createInterface } from 'node:readline';\n// Subpath import, NOT the `@ddt-tools/core` barrel — this module runs in the\n// preAction hook of every command; the barrel would drag ~85 core modules in.\nimport * as errorReport from '@ddt-tools/core/errorReport';\nimport { logger } from './logger.js';\n\n/** Commands that must never trigger the consent prompt or auto-drain. */\nconst EXEMPT_COMMANDS = new Set(['telemetry', 'feedback', 'help', 'completion']);\n\nlet activeConsent: errorReport.ErrorReportConsent = 'unset';\nlet uninstallHooks: (() => void) | null = null;\n\n/**\n * preAction hook body. Reads (and on first run, prompts for) consent, then\n * installs crash capture unless the user opted out.\n */\nexport async function setupErrorReporting(commandName: string): Promise<void> {\n if (EXEMPT_COMMANDS.has(commandName)) return;\n const stored = await errorReport.readConsent();\n activeConsent = stored.consent;\n\n // First-run consent prompt — explicit, one keystroke, default Yes.\n // Skipped when not interactive (CI, pipes) so scripted runs never block.\n if (\n shouldPromptFirstRun(stored.consent) &&\n process.stdout.isTTY &&\n process.stdin.isTTY &&\n !isCi()\n ) {\n // Beta install-time messaging — shown ONCE, immediately before the very\n // first consent question (true first run, consent still `unset`).\n printBetaNotice();\n activeConsent = (await promptFirstRunConsent()) ? 'on' : 'off';\n await errorReport.writeConsent(activeConsent === 'on' ? 'on' : 'off');\n }\n\n // Crash capture is installed unless the user said no. Capture is local-only;\n // nothing leaves the machine without `isErrorReportingEnabled` saying so.\n if (activeConsent !== 'off') {\n uninstallHooks = errorReport.installProcessHooks();\n }\n}\n\n/**\n * postAction hook body. Flushes buffered events and, when consent is `on`,\n * drains the spool to the Worker. No-ops fast when there is nothing to send.\n * `transport` is injectable for tests.\n */\nexport async function finishErrorReporting(\n productVersion: string,\n transport: errorReport.TransportOptions = {},\n): Promise<void> {\n await errorReport.flushErrorEvents();\n\n // Anonymous usage ping (opt-in) — gated internally by the SAME consent +\n // env opt-outs as error reporting and throttled to once per 24h. It is\n // awaited here (not detached) so the postAction lifecycle has a\n // deterministic completion point: `sendUsagePing` is self-bounded (3s hard\n // timeout) and never throws, so awaiting it can block command exit by at\n // most that timeout — the same bound the spool drain below already imposes.\n // Detaching it left the send racing process teardown, which both dropped\n // pings in practice and made the wiring untestable without a sleep.\n // `transport.fetchImpl` is threaded through so tests can intercept it.\n await errorReport\n .sendUsagePing({\n product: 'ddt',\n version: productVersion,\n surface: 'cli',\n ...(transport.fetchImpl ? { fetchImpl: transport.fetchImpl } : {}),\n })\n .catch(() => undefined);\n\n if (!errorReport.isErrorReportingEnabled(activeConsent)) return;\n const spooled = await errorReport.listSpooled(transport.dir);\n if (spooled.length === 0) return;\n const result = await errorReport.drainSpool({ productVersion }, transport);\n if (result.sent > 0) {\n logger.dim(` (${result.sent} error report${result.sent === 1 ? '' : 's'} sent — thank you)`);\n }\n}\n\n/**\n * Top-level command-failure handler. Reports the error as `handled` (real\n * crashes go through `uncaughtExceptionMonitor`), then either auto-sends\n * (consent `on`) or offers a one-keystroke manual report.\n * `transport` is injectable for tests.\n */\nexport async function reportCliFailure(\n err: unknown,\n productVersion: string,\n transport: errorReport.TransportOptions = {},\n): Promise<void> {\n errorReport.reportError(err, 'handled', 'cli:main');\n await errorReport.flushErrorEvents(transport.dir);\n\n if (errorReport.isErrorReportingEnabled(activeConsent)) {\n await errorReport.drainSpool({ productVersion }, transport);\n return;\n }\n\n // Manual push path — only when interactive.\n if (!process.stdout.isTTY || !process.stdin.isTTY || isCi()) return;\n const yes = await promptYesNo('Report this error to the DDT team? [y/N] ', false);\n if (!yes) return;\n const result = await errorReport.drainSpool({ productVersion }, transport);\n if (result.sent > 0) logger.dim(' Report sent — thank you.');\n else logger.dim(' Could not reach the error endpoint; the report is queued locally.');\n}\n\n/** Uninstall crash hooks (tests). */\nexport function teardownErrorReporting(): void {\n uninstallHooks?.();\n uninstallHooks = null;\n activeConsent = 'unset';\n}\n\n/** Current consent as seen by the hooks (tests). */\nexport function activeConsentForTests(): errorReport.ErrorReportConsent {\n return activeConsent;\n}\n\n/** DDT version surfaced in the first-run beta notice. */\nconst BETA_VERSION = '0.2.4';\n\n/**\n * Whether the first-run flow (beta notice + consent prompt) should fire.\n * True ONLY on true first run — when consent has never been decided\n * (`unset`). Exported so the \"first run only\" contract is unit-testable\n * without faking a TTY.\n */\nexport function shouldPromptFirstRun(consent: errorReport.ErrorReportConsent): boolean {\n return consent === 'unset';\n}\n\n/**\n * Beta install-time messaging — printed ONCE, on true first run (consent\n * `unset`), immediately before the consent question. Tells the user, at\n * install time: it's a 30-day public beta with all features free; what\n * happens after the beta (core stays free forever, Pro features keep\n * working but show license notices); that AI features are bring-your-own\n * key; and how to report a bug. Plain ASCII box to match CLI output style.\n */\nexport function printBetaNotice(): void {\n const lines = [\n '┌─────────────────────────────────────────────────────────┐',\n `│ DDT ${BETA_VERSION} — Public Beta │`,\n '│ • All features are free during the beta. │',\n '│ • After the beta: core features stay free forever; │',\n '│ Pro features keep working and show license notices. │',\n '│ • AI features use your own API key (never ours). │',\n '│ • Found a bug? Run: ddt feedback \"<what happened>\" │',\n '└─────────────────────────────────────────────────────────┘',\n ];\n for (const line of lines) logger.info(line);\n}\n\nasync function promptFirstRunConsent(): Promise<boolean> {\n logger.info('DDT can report errors automatically (sanitized diagnostics + OS context,');\n logger.info('never your SQL, identifiers, or credentials) so they get fixed fast.');\n logger.info(errorReport.CONSENT_WARNING);\n return promptYesNo('Enable automatic error reporting? [Y/n] ', true);\n}\n\nfunction promptYesNo(question: string, defaultYes: boolean): Promise<boolean> {\n return new Promise((resolve) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(question, (answer) => {\n rl.close();\n const normalized = answer.trim().toLowerCase();\n if (normalized === '') resolve(defaultYes);\n else resolve(normalized === 'y' || normalized === 'yes');\n });\n });\n}\n\nfunction isCi(): boolean {\n return process.env['CI'] === 'true';\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/util/errorReporting.ts"],"sourcesContent":["/**\n * CLI error-reporting wiring (ERR.2).\n *\n * Lazily imported from `cli.ts` hooks so the cold-start path pays zero cost\n * until a command actually runs (preAction) or fails (catch handler).\n *\n * Responsibilities:\n * - first-run consent prompt (TTY only, default Yes, one keystroke)\n * - crash-hook installation when consent allows capture\n * - end-of-run drain: spool → `POST /errors` when consent is `on`\n * - manual \"Report this error? [y/N]\" prompt when consent is NOT `on`\n * and a command fails\n *\n * @see @ddt-tools/core/errorReport (capture substrate + consent + transport)\n */\nimport { createInterface } from 'node:readline';\n// Subpath import, NOT the `@ddt-tools/core` barrel — this module runs in the\n// preAction hook of every command; the barrel would drag ~85 core modules in.\nimport * as errorReport from '@ddt-tools/core/errorReport';\nimport { logger } from './logger.js';\n\n/** Commands that must never trigger the consent prompt or auto-drain. */\nconst EXEMPT_COMMANDS = new Set(['telemetry', 'feedback', 'help', 'completion']);\n\nlet activeConsent: errorReport.ErrorReportConsent = 'unset';\nlet uninstallHooks: (() => void) | null = null;\n\n/**\n * preAction hook body. Reads (and on first run, prompts for) consent, then\n * installs crash capture unless the user opted out.\n */\nexport async function setupErrorReporting(commandName: string): Promise<void> {\n if (EXEMPT_COMMANDS.has(commandName)) return;\n const stored = await errorReport.readConsent();\n activeConsent = stored.consent;\n\n // First-run consent prompt — explicit, one keystroke, default Yes.\n // Skipped when not interactive (CI, pipes) so scripted runs never block.\n if (\n shouldPromptFirstRun(stored.consent) &&\n process.stdout.isTTY &&\n process.stdin.isTTY &&\n !isCi()\n ) {\n // Beta install-time messaging — shown ONCE, immediately before the very\n // first consent question (true first run, consent still `unset`).\n printBetaNotice();\n activeConsent = (await promptFirstRunConsent()) ? 'on' : 'off';\n await errorReport.writeConsent(activeConsent === 'on' ? 'on' : 'off');\n }\n\n // Crash capture is installed unless the user said no. Capture is local-only;\n // nothing leaves the machine without `isErrorReportingEnabled` saying so.\n if (activeConsent !== 'off') {\n uninstallHooks = errorReport.installProcessHooks();\n }\n}\n\n/**\n * postAction hook body. Flushes buffered events and, when consent is `on`,\n * drains the spool to the Worker. No-ops fast when there is nothing to send.\n * `transport` is injectable for tests.\n */\nexport async function finishErrorReporting(\n productVersion: string,\n transport: errorReport.TransportOptions = {},\n): Promise<void> {\n await errorReport.flushErrorEvents();\n\n // Anonymous usage ping (opt-in) — gated internally by the SAME consent +\n // env opt-outs as error reporting and throttled to once per 24h. It is\n // awaited here (not detached) so the postAction lifecycle has a\n // deterministic completion point: `sendUsagePing` is self-bounded (3s hard\n // timeout) and never throws, so awaiting it can block command exit by at\n // most that timeout — the same bound the spool drain below already imposes.\n // Detaching it left the send racing process teardown, which both dropped\n // pings in practice and made the wiring untestable without a sleep.\n // `transport.fetchImpl` is threaded through so tests can intercept it.\n await errorReport\n .sendUsagePing({\n product: 'ddt',\n version: productVersion,\n surface: 'cli',\n ...(transport.fetchImpl ? { fetchImpl: transport.fetchImpl } : {}),\n })\n .catch(() => undefined);\n\n if (!errorReport.isErrorReportingEnabled(activeConsent)) return;\n const spooled = await errorReport.listSpooled(transport.dir);\n if (spooled.length === 0) return;\n const result = await errorReport.drainSpool({ productVersion }, transport);\n if (result.sent > 0) {\n logger.dim(` (${result.sent} error report${result.sent === 1 ? '' : 's'} sent — thank you)`);\n }\n}\n\n/**\n * Top-level command-failure handler. Reports the error as `handled` (real\n * crashes go through `uncaughtExceptionMonitor`), then either auto-sends\n * (consent `on`) or offers a one-keystroke manual report.\n * `transport` is injectable for tests.\n */\nexport async function reportCliFailure(\n err: unknown,\n productVersion: string,\n transport: errorReport.TransportOptions = {},\n): Promise<void> {\n errorReport.reportError(err, 'handled', 'cli:main');\n await errorReport.flushErrorEvents(transport.dir);\n\n if (errorReport.isErrorReportingEnabled(activeConsent)) {\n await errorReport.drainSpool({ productVersion }, transport);\n return;\n }\n\n // Manual push path — only when interactive.\n if (!process.stdout.isTTY || !process.stdin.isTTY || isCi()) return;\n const yes = await promptYesNo('Report this error to the DDT team? [y/N] ', false);\n if (!yes) return;\n const result = await errorReport.drainSpool({ productVersion }, transport);\n if (result.sent > 0) logger.dim(' Report sent — thank you.');\n else logger.dim(' Could not reach the error endpoint; the report is queued locally.');\n}\n\n/** Uninstall crash hooks (tests). */\nexport function teardownErrorReporting(): void {\n uninstallHooks?.();\n uninstallHooks = null;\n activeConsent = 'unset';\n}\n\n/** Current consent as seen by the hooks (tests). */\nexport function activeConsentForTests(): errorReport.ErrorReportConsent {\n return activeConsent;\n}\n\n/** DDT version surfaced in the first-run beta notice. */\nconst BETA_VERSION = '0.2.5';\n\n/**\n * Whether the first-run flow (beta notice + consent prompt) should fire.\n * True ONLY on true first run — when consent has never been decided\n * (`unset`). Exported so the \"first run only\" contract is unit-testable\n * without faking a TTY.\n */\nexport function shouldPromptFirstRun(consent: errorReport.ErrorReportConsent): boolean {\n return consent === 'unset';\n}\n\n/**\n * Beta install-time messaging — printed ONCE, on true first run (consent\n * `unset`), immediately before the consent question. Tells the user, at\n * install time: it's a 30-day public beta with all features free; what\n * happens after the beta (core stays free forever, Pro features keep\n * working but show license notices); that AI features are bring-your-own\n * key; and how to report a bug. Plain ASCII box to match CLI output style.\n */\nexport function printBetaNotice(): void {\n const lines = [\n '┌─────────────────────────────────────────────────────────┐',\n `│ DDT ${BETA_VERSION} — Public Beta │`,\n '│ • All features are free during the beta. │',\n '│ • After the beta: core features stay free forever; │',\n '│ Pro features keep working and show license notices. │',\n '│ • AI features use your own API key (never ours). │',\n '│ • Found a bug? Run: ddt feedback \"<what happened>\" │',\n '└─────────────────────────────────────────────────────────┘',\n ];\n for (const line of lines) logger.info(line);\n}\n\nasync function promptFirstRunConsent(): Promise<boolean> {\n logger.info('DDT can report errors automatically (sanitized diagnostics + OS context,');\n logger.info('never your SQL, identifiers, or credentials) so they get fixed fast.');\n logger.info(errorReport.CONSENT_WARNING);\n return promptYesNo('Enable automatic error reporting? [Y/n] ', true);\n}\n\nfunction promptYesNo(question: string, defaultYes: boolean): Promise<boolean> {\n return new Promise((resolve) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(question, (answer) => {\n rl.close();\n const normalized = answer.trim().toLowerCase();\n if (normalized === '') resolve(defaultYes);\n else resolve(normalized === 'y' || normalized === 'yes');\n });\n });\n}\n\nfunction isCi(): boolean {\n return process.env['CI'] === 'true';\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "./chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// src/commands/import.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { importers as importersApi } from "@ddt-tools/core";
|
|
6
|
+
function importCommand() {
|
|
7
|
+
return new Command("import").description("Convert artifacts from other tools into a DDT project.").requiredOption(
|
|
8
|
+
"--from <source>",
|
|
9
|
+
"Source format (liquibase, flyway, dbt, terraform-state, sql-files, cross-engine-ddl, databricks-workspace)"
|
|
10
|
+
).requiredOption("--source-path <path>", "Source artifact path (file or directory)").requiredOption("--output <dir>", "Output directory for the generated DDT project").option("--connection <profile>", "Connection profile (required for databricks-workspace)").action(
|
|
11
|
+
async (opts) => {
|
|
12
|
+
const source = opts.from;
|
|
13
|
+
const importer = importersApi.getImporter(source);
|
|
14
|
+
const result = await importer.import({
|
|
15
|
+
sourcePath: opts.sourcePath,
|
|
16
|
+
outputDir: opts.output,
|
|
17
|
+
connectionProfile: opts.connection
|
|
18
|
+
});
|
|
19
|
+
console.log(
|
|
20
|
+
`Imported ${result.filesCreated} files. Project written to ${result.projectPath}`
|
|
21
|
+
);
|
|
22
|
+
for (const w of result.warnings) console.warn(w);
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
importCommand
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=import-EGOVKTLX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/import.ts"],"sourcesContent":["/**\n * `ddt import` — convert artifacts from other tools into a `.ddtproj`.\n *\n * Sources (per the DDT importer registry):\n * - liquibase (changelog XML/YAML/SQL)\n * - flyway (versioned / repeatable migration tree)\n * - dbt (compiled manifest.json / project)\n * - terraform-state (`.tfstate` from the Databricks provider)\n * - sql-files (flat dir of `.sql`)\n * - cross-engine-ddl (foreign-dialect DDL, best-effort translation)\n * - databricks-workspace (live workspace → project, needs --connection)\n *\n * To split a single SQL script into an existing project tree instead, use\n * `ddt import-script`. Byte-aligned with `sdt import`.\n */\nimport { Command } from 'commander';\n\nimport { importers as importersApi } from '@ddt-tools/core';\n\nexport function importCommand(): Command {\n return new Command('import')\n .description('Convert artifacts from other tools into a DDT project.')\n .requiredOption(\n '--from <source>',\n 'Source format (liquibase, flyway, dbt, terraform-state, sql-files, cross-engine-ddl, databricks-workspace)',\n )\n .requiredOption('--source-path <path>', 'Source artifact path (file or directory)')\n .requiredOption('--output <dir>', 'Output directory for the generated DDT project')\n .option('--connection <profile>', 'Connection profile (required for databricks-workspace)')\n .action(\n async (opts: { from: string; sourcePath: string; output: string; connection?: string }) => {\n const source = opts.from as Parameters<typeof importersApi.getImporter>[0];\n const importer = importersApi.getImporter(source);\n const result = await importer.import({\n sourcePath: opts.sourcePath,\n outputDir: opts.output,\n connectionProfile: opts.connection,\n });\n console.log(\n `Imported ${result.filesCreated} files. Project written to ${result.projectPath}`,\n );\n for (const w of result.warnings) console.warn(w);\n },\n );\n}\n"],"mappings":";;;AAeA,SAAS,eAAe;AAExB,SAAS,aAAa,oBAAoB;AAEnC,SAAS,gBAAyB;AACvC,SAAO,IAAI,QAAQ,QAAQ,EACxB,YAAY,wDAAwD,EACpE;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,eAAe,wBAAwB,0CAA0C,EACjF,eAAe,kBAAkB,gDAAgD,EACjF,OAAO,0BAA0B,wDAAwD,EACzF;AAAA,IACC,OAAO,SAAoF;AACzF,YAAM,SAAS,KAAK;AACpB,YAAM,WAAW,aAAa,YAAY,MAAM;AAChD,YAAM,SAAS,MAAM,SAAS,OAAO;AAAA,QACnC,YAAY,KAAK;AAAA,QACjB,WAAW,KAAK;AAAA,QAChB,mBAAmB,KAAK;AAAA,MAC1B,CAAC;AACD,cAAQ;AAAA,QACN,YAAY,OAAO,YAAY,8BAA8B,OAAO,WAAW;AAAA,MACjF;AACA,iBAAW,KAAK,OAAO,SAAU,SAAQ,KAAK,CAAC;AAAA,IACjD;AAAA,EACF;AACJ;","names":[]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import "./chunk-DGUM43GV.js";
|
|
2
2
|
|
|
3
|
-
// src/commands/import.ts
|
|
3
|
+
// src/commands/import-script.ts
|
|
4
4
|
import { promises as fs } from "fs";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
import { importScript, loadProject, parseScript } from "@ddt-tools/core";
|
|
8
|
-
function
|
|
9
|
-
const cmd = new Command("import");
|
|
8
|
+
function importScriptCommand() {
|
|
9
|
+
const cmd = new Command("import-script");
|
|
10
10
|
cmd.description(
|
|
11
11
|
"Parse a SQL script and write each DDL statement into the .ddtproj tree under its canonical folder."
|
|
12
12
|
).requiredOption("--script <path>", "Path to the SQL script to import.").requiredOption("-p, --project <path>", "Path to the .ddtproj file.").option("--dry-run", "Report what would be written without touching disk.", false).option("--force", "Overwrite existing files (default refuses on conflict).", false).option(
|
|
@@ -74,6 +74,6 @@ function printParseReport(scriptPath, parsed) {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
export {
|
|
77
|
-
|
|
77
|
+
importScriptCommand
|
|
78
78
|
};
|
|
79
|
-
//# sourceMappingURL=import-
|
|
79
|
+
//# sourceMappingURL=import-script-R5RXPDH6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/import-script.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { importScript, loadProject, parseScript, type ParsedStatement } from '@ddt-tools/core';\n\n/**\n * `ddt import-script` — parse a SQL script and apply each DDL statement to a\n * `.ddtproj` tree, writing each into the canonical folder for its object\n * type (`catalogs/<cat>/schemas/<sch>/<type-folder>/<name>.sql`).\n *\n * This is the script→tree splitter. For converting another tool's artifacts\n * (Liquibase / Flyway / dbt / Terraform / …) into a fresh project, use\n * `ddt import` instead. Byte-aligned with `sdt import-script`.\n *\n * Default mode is safe:\n * - Parses the whole script first; refuses to write anything if any\n * statement has hard errors. The user reviews the report, fixes the\n * script, and re-runs.\n * - Refuses to overwrite existing files. Pass `--force` to clobber.\n * - `--dry-run` shows the plan without writing.\n */\nexport function importScriptCommand(): Command {\n const cmd = new Command('import-script');\n cmd\n .description(\n 'Parse a SQL script and write each DDL statement into the .ddtproj tree under its canonical folder.',\n )\n .requiredOption('--script <path>', 'Path to the SQL script to import.')\n .requiredOption('-p, --project <path>', 'Path to the .ddtproj file.')\n .option('--dry-run', 'Report what would be written without touching disk.', false)\n .option('--force', 'Overwrite existing files (default refuses on conflict).', false)\n .option(\n '--ignore-errors',\n 'Import classifiable statements even when others have errors. Default refuses on any error.',\n false,\n )\n .action(async (opts) => {\n const scriptPath = path.resolve(String(opts.script));\n const projectPath = path.resolve(String(opts.project));\n const sql = await fs.readFile(scriptPath, 'utf8');\n const parsed = parseScript(sql);\n\n // Up-front parse report — surfaces every error/warning verbatim.\n printParseReport(scriptPath, parsed);\n\n if (parsed.totalErrors > 0 && !opts.ignoreErrors) {\n console.error(\n `\\nRefusing to import: ${parsed.totalErrors} parse error(s) above. ` +\n `Fix the script and re-run, or pass --ignore-errors to import the clean statements.`,\n );\n process.exitCode = 2;\n return;\n }\n\n const loaded = await loadProject(projectPath);\n const result = await importScript(parsed, loaded, {\n dryRun: !!opts.dryRun,\n force: !!opts.force,\n });\n\n console.log('');\n const verb = opts.dryRun ? 'would write' : 'wrote';\n for (const item of result.imported) {\n const rel = path.relative(loaded.rootDir, item.targetPath);\n console.log(\n ` ${verb} ${item.statement.objectType} ${qualified(item.statement)} → ${rel}`,\n );\n }\n for (const item of result.skipped) {\n const head =\n item.statement.objectType && item.statement.fqn\n ? `${item.statement.objectType} ${qualified(item.statement)}`\n : `<unclassified statement at line ${item.statement.startLine}>`;\n console.log(` skip ${head} — ${item.reason}`);\n }\n console.log('');\n console.log(\n `Summary: ${result.imported.length} ${verb}, ${result.skipped.length} skipped, ` +\n `${parsed.totalErrors} errors, ${parsed.totalWarnings} warnings.`,\n );\n\n if (result.imported.length === 0 && result.skipped.length > 0) {\n process.exitCode = 1;\n }\n });\n return cmd;\n}\n\nfunction qualified(stmt: ParsedStatement): string {\n if (!stmt.fqn) return '<no fqn>';\n return [stmt.fqn.database, stmt.fqn.schema, stmt.fqn.name].filter(Boolean).join('.');\n}\n\nfunction printParseReport(scriptPath: string, parsed: ReturnType<typeof parseScript>): void {\n console.log(`Parsed ${parsed.statements.length} statement(s) from ${scriptPath}.`);\n if (parsed.totalErrors === 0 && parsed.totalWarnings === 0) {\n console.log('All statements classified cleanly.');\n return;\n }\n for (const stmt of parsed.statements) {\n if (stmt.errors.length === 0 && stmt.warnings.length === 0) continue;\n const head =\n stmt.objectType && stmt.fqn ? `${stmt.objectType} ${qualified(stmt)}` : '<unclassified>';\n console.log(`\\n[line ${stmt.startLine}–${stmt.endLine}] ${head}`);\n for (const e of stmt.errors) console.log(` ERROR: ${e}`);\n for (const w of stmt.warnings) console.log(` warning: ${w}`);\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
features
|
|
18
18
|
} from "@ddt-tools/core";
|
|
19
19
|
var SERVER_NAME = "ddt";
|
|
20
|
-
var SERVER_VERSION = "0.2.
|
|
20
|
+
var SERVER_VERSION = "0.2.5";
|
|
21
21
|
var PROTOCOL_VERSION = "2024-11-05";
|
|
22
22
|
var TOOLS = [
|
|
23
23
|
{
|
|
@@ -340,4 +340,4 @@ function send(msg) {
|
|
|
340
340
|
export {
|
|
341
341
|
mcpCommand
|
|
342
342
|
};
|
|
343
|
-
//# sourceMappingURL=mcp-
|
|
343
|
+
//# sourceMappingURL=mcp-6ZXOAF7S.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/mcp.ts"],"sourcesContent":["/**\n * `ddt mcp` — Model Context Protocol server (stdio transport).\n *\n * Exposes DDT's read-only operations as MCP tools so AI agents (Claude\n * Desktop, Cursor, Continue, Claude Code, MCP-aware IDE plugins) can\n * call them. Speaks JSON-RPC 2.0 over newline-delimited JSON on stdio.\n *\n * To wire into Claude Desktop / Cursor / etc., add to the MCP config:\n *\n * {\n * \"mcpServers\": {\n * \"ddt\": { \"command\": \"ddt\", \"args\": [\"mcp\"] }\n * }\n * }\n *\n * Tools exposed:\n * - ddt.validate — schema check on a .ddtproj\n * - ddt.compare — pac↔pac diff (offline)\n * - ddt.docs — generate HTML schema docs\n * - ddt.erd — generate Mermaid ER diagram (Markdown)\n * - ddt.safety_assess — classify a diff's safety findings\n * - ddt.narrate_diff — structured payload (diff + safety + prompt\n * scaffold) so the calling agent's LLM can\n * generate a plain-English narration.\n * - ddt.detect_pii — scan a project/pac for likely PII columns.\n * - ddt.suggest_safer — saferAlternatives entries for every\n * UNRECOVERABLE / DESTRUCTIVE finding.\n * - ddt.feature_search — search the DDT feature catalog by keyword.\n *\n * Live-network tools (extract, publish) are deliberately NOT exposed\n * — they require profile credentials and we'd rather an agent not act\n * on those without explicit user invocation.\n */\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n CompareEngine,\n PacSource,\n renderDocsReport,\n renderErdMarkdown,\n loadProject,\n parseProjectModel,\n discoverObjectFiles,\n pac,\n pii,\n safety,\n features,\n type DatabricksObject,\n} from '@ddt-tools/core';\n\ninterface JsonRpcRequest {\n jsonrpc: '2.0';\n id?: number | string | null;\n method: string;\n params?: unknown;\n}\n\ninterface JsonRpcResponse {\n jsonrpc: '2.0';\n id: number | string | null;\n result?: unknown;\n error?: { code: number; message: string; data?: unknown };\n}\n\nconst SERVER_NAME = 'ddt';\nconst SERVER_VERSION = '0.2.4';\nconst PROTOCOL_VERSION = '2024-11-05';\n\ninterface ToolDef {\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n handler: (args: Record<string, unknown>) => Promise<string>;\n}\n\nconst TOOLS: ToolDef[] = [\n {\n name: 'ddt.validate',\n description:\n 'Validate a .ddtproj file: parse JSON, check schema with zod, list object files. Returns a JSON summary of project structure.',\n inputSchema: {\n type: 'object',\n properties: {\n projectPath: { type: 'string', description: 'Absolute path to a .ddtproj file.' },\n },\n required: ['projectPath'],\n },\n handler: async (args) => {\n const projectPath = path.resolve(String(args.projectPath));\n const loaded = await loadProject(projectPath);\n const files = await discoverObjectFiles(loaded);\n return JSON.stringify(\n {\n ok: true,\n name: loaded.project.name,\n version: loaded.project.version,\n scope: loaded.project.scope,\n objectFileCount: files.length,\n profiles: Object.keys(loaded.project.deploymentProfiles ?? {}),\n },\n null,\n 2,\n );\n },\n },\n {\n name: 'ddt.compare',\n description:\n 'Compare two .ddtpac files (offline). Returns the diff JSON: added / removed / modified objects, with per-object field changes.',\n inputSchema: {\n type: 'object',\n properties: {\n sourcePac: { type: 'string', description: 'Absolute path to the source .ddtpac.' },\n targetPac: { type: 'string', description: 'Absolute path to the target .ddtpac.' },\n },\n required: ['sourcePac', 'targetPac'],\n },\n handler: async (args) => {\n const source = new PacSource(path.resolve(String(args.sourcePac)));\n const target = new PacSource(path.resolve(String(args.targetPac)));\n const engine = new CompareEngine();\n const result = await engine.compare(source, target);\n return JSON.stringify(result, null, 2);\n },\n },\n {\n name: 'ddt.docs',\n description:\n 'Generate self-contained HTML schema documentation from a .ddtproj or .ddtpac. Returns the HTML body as a string.',\n inputSchema: {\n type: 'object',\n properties: {\n source: { type: 'string', description: 'Absolute path to a .ddtproj or .ddtpac.' },\n title: { type: 'string', description: 'Optional doc title.' },\n },\n required: ['source'],\n },\n handler: async (args) => {\n const model = await loadModel(String(args.source));\n return renderDocsReport(model, {\n title: typeof args.title === 'string' ? args.title : 'Schema docs',\n });\n },\n },\n {\n name: 'ddt.erd',\n description: 'Generate a Mermaid ER diagram (Markdown) from a .ddtproj or .ddtpac.',\n inputSchema: {\n type: 'object',\n properties: {\n source: { type: 'string', description: 'Absolute path to a .ddtproj or .ddtpac.' },\n title: { type: 'string', description: 'Optional diagram title.' },\n },\n required: ['source'],\n },\n handler: async (args) => {\n const model = await loadModel(String(args.source));\n return renderErdMarkdown(\n model,\n typeof args.title === 'string' ? args.title : 'Schema ER diagram',\n );\n },\n },\n {\n name: 'ddt.safety_assess',\n description:\n 'Given two .ddtpac files, run the safety classifier on the diff. Returns UNRECOVERABLE / DESTRUCTIVE / EXPENSIVE / WARNING findings with the gate option each would need.',\n inputSchema: {\n type: 'object',\n properties: {\n sourcePac: { type: 'string', description: 'Source .ddtpac (desired state).' },\n targetPac: { type: 'string', description: 'Target .ddtpac (current state).' },\n },\n required: ['sourcePac', 'targetPac'],\n },\n handler: async (args) => {\n const source = new PacSource(path.resolve(String(args.sourcePac)));\n const target = new PacSource(path.resolve(String(args.targetPac)));\n const engine = new CompareEngine();\n const diff = await engine.compare(source, target);\n const assessment = safety.assess(diff);\n return JSON.stringify(assessment, null, 2);\n },\n },\n {\n name: 'ddt.narrate_diff',\n description:\n \"Build a structured payload (diff summary + per-object changes + safety findings + a system-prompt scaffold) the calling agent's LLM can use to generate a plain-English narration of a schema diff. Returns JSON the agent should compose into its own completion.\",\n inputSchema: {\n type: 'object',\n properties: {\n sourcePac: { type: 'string', description: 'Source .ddtpac (desired state).' },\n targetPac: { type: 'string', description: 'Target .ddtpac (current state).' },\n },\n required: ['sourcePac', 'targetPac'],\n },\n handler: async (args) => {\n const source = new PacSource(path.resolve(String(args.sourcePac)));\n const target = new PacSource(path.resolve(String(args.targetPac)));\n const engine = new CompareEngine();\n const diff = await engine.compare(source, target);\n const assessment = safety.assess(diff);\n const payload = {\n summary: diff.summary,\n objects: diff.objects.map((o) => ({\n kind: o.kind,\n objectType: o.identity.objectType,\n fqn: o.identity.fqn,\n })),\n safety: assessment,\n promptScaffold: {\n system:\n 'You are a senior Databricks UC engineer reviewing a schema migration. ' +\n 'Narrate the change in 2-3 short paragraphs: (1) the intent, ' +\n '(2) the safest order of operations, (3) the risks to watch ' +\n 'for. Be concrete; reference the FQNs.',\n userTemplate: 'Here is the diff JSON and the safety assessment. Generate the narration.',\n },\n };\n return JSON.stringify(payload, null, 2);\n },\n },\n {\n name: 'ddt.detect_pii',\n description:\n 'Scan a project or pac for columns that look like PII (email, phone, ssn, dob, credit-card, national-id, health, ip-address, name, address, generic). Returns each candidate with its category, confidence tier, and the reason the heuristic flagged it.',\n inputSchema: {\n type: 'object',\n properties: {\n source: { type: 'string', description: 'Absolute path to a .ddtproj or .ddtpac.' },\n },\n required: ['source'],\n },\n handler: async (args) => {\n const model = await loadModel(String(args.source));\n const candidates = pii.detectPiiCandidates(model);\n return JSON.stringify({ count: candidates.length, candidates }, null, 2);\n },\n },\n {\n name: 'ddt.suggest_safer',\n description:\n 'For every UNRECOVERABLE / DESTRUCTIVE finding the safety classifier produced on a diff, return the catalog\\'s saferAlternatives entries (e.g. \"instead of DROP TABLE, RENAME with a date suffix and let the retention policy expire it\"). The agent can paste these into a PR comment or CodeLens suggestion.',\n inputSchema: {\n type: 'object',\n properties: {\n sourcePac: { type: 'string', description: 'Source .ddtpac (desired).' },\n targetPac: { type: 'string', description: 'Target .ddtpac (current).' },\n },\n required: ['sourcePac', 'targetPac'],\n },\n handler: async (args) => {\n const source = new PacSource(path.resolve(String(args.sourcePac)));\n const target = new PacSource(path.resolve(String(args.targetPac)));\n const engine = new CompareEngine();\n const diff = await engine.compare(source, target);\n const assessment = safety.assess(diff);\n const dangerous = [...assessment.unrecoverable, ...assessment.destructive];\n const suggestions = dangerous.map((f) => {\n const explanation = safety.explainFinding(f.code);\n return {\n finding: { code: f.code, category: f.category, fqn: f.fqn, reason: f.reason },\n saferAlternatives: explanation?.saferAlternatives ?? [],\n requiredGates: explanation?.requiredGates ?? [],\n };\n });\n return JSON.stringify({ count: suggestions.length, suggestions }, null, 2);\n },\n },\n {\n name: 'ddt.feature_search',\n description:\n 'Search the DDT feature catalog by keyword. Returns features whose id, name, summary, or synonyms match the query. Useful for discovering which tier unlocks a capability or finding the right command for a task.',\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description:\n 'Search term — feature name, keyword, or capability (e.g. \"drift\", \"slice\", \"AI\").',\n },\n },\n required: ['query'],\n },\n handler: async (args) => {\n const q = String(args.query ?? '')\n .toLowerCase()\n .trim();\n const matches = features.DDT_FEATURE_CATALOG.filter(\n (f) =>\n f.id.includes(q) ||\n f.name.toLowerCase().includes(q) ||\n f.summary.toLowerCase().includes(q) ||\n f.synonyms?.some((s) => s.toLowerCase().includes(q)) ||\n f.whenToUse?.toLowerCase().includes(q),\n );\n return JSON.stringify(\n {\n count: matches.length,\n features: matches.map((f) => ({\n id: f.id,\n name: f.name,\n tier: f.tier,\n status: f.status,\n summary: f.summary,\n unlockHow: f.unlockHow,\n useCases: f.useCases,\n relatedFeatures: f.relatedFeatures,\n })),\n },\n null,\n 2,\n );\n },\n },\n];\n\nasync function loadModel(sourcePath: string): Promise<DatabricksObject[]> {\n const abs = path.resolve(sourcePath);\n if (abs.endsWith('.ddtpac')) {\n const pacContents = await pac.readPac(abs);\n return pacContents.model;\n }\n const loaded = await loadProject(abs);\n const parsed = await parseProjectModel(loaded);\n return parsed as DatabricksObject[];\n}\n\nexport function mcpCommand(): Command {\n const cmd = new Command('mcp');\n cmd\n .description(\n 'Start the DDT Model Context Protocol server on stdio (for Claude / Cursor / agents).',\n )\n .action(async () => {\n await runMcpStdio();\n });\n return cmd;\n}\n\nasync function runMcpStdio(): Promise<void> {\n await new Promise<void>((resolve) => {\n let buffer = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => {\n buffer += chunk;\n let nl = buffer.indexOf('\\n');\n while (nl !== -1) {\n const line = buffer.slice(0, nl).trim();\n buffer = buffer.slice(nl + 1);\n if (line.length > 0) {\n void handleLine(line);\n }\n nl = buffer.indexOf('\\n');\n }\n });\n process.stdin.on('end', () => resolve());\n process.stdin.on('close', () => resolve());\n });\n}\n\nasync function handleLine(line: string): Promise<void> {\n let req: JsonRpcRequest;\n try {\n req = JSON.parse(line) as JsonRpcRequest;\n } catch (err) {\n send({\n jsonrpc: '2.0',\n id: null,\n error: { code: -32700, message: 'Parse error', data: String(err) },\n });\n return;\n }\n const id = req.id ?? null;\n try {\n const result = await dispatch(req);\n if (id !== null) {\n send({ jsonrpc: '2.0', id, result });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n send({ jsonrpc: '2.0', id, error: { code: -32603, message } });\n }\n}\n\nasync function dispatch(req: JsonRpcRequest): Promise<unknown> {\n switch (req.method) {\n case 'initialize':\n return {\n protocolVersion: PROTOCOL_VERSION,\n serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },\n capabilities: { tools: {} },\n };\n case 'notifications/initialized':\n return undefined;\n case 'tools/list':\n return {\n tools: TOOLS.map((t) => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema,\n })),\n };\n case 'tools/call': {\n const params = (req.params ?? {}) as { name?: string; arguments?: Record<string, unknown> };\n const tool = TOOLS.find((t) => t.name === params.name);\n if (!tool) throw new Error(`Unknown tool: ${String(params.name)}`);\n const output = await tool.handler(params.arguments ?? {});\n return { content: [{ type: 'text', text: output }] };\n }\n case 'ping':\n return {};\n default:\n throw new Error(`Method not found: ${req.method}`);\n }\n}\n\nfunction send(msg: JsonRpcResponse): void {\n process.stdout.write(`${JSON.stringify(msg)}\\n`);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/commands/mcp.ts"],"sourcesContent":["/**\n * `ddt mcp` — Model Context Protocol server (stdio transport).\n *\n * Exposes DDT's read-only operations as MCP tools so AI agents (Claude\n * Desktop, Cursor, Continue, Claude Code, MCP-aware IDE plugins) can\n * call them. Speaks JSON-RPC 2.0 over newline-delimited JSON on stdio.\n *\n * To wire into Claude Desktop / Cursor / etc., add to the MCP config:\n *\n * {\n * \"mcpServers\": {\n * \"ddt\": { \"command\": \"ddt\", \"args\": [\"mcp\"] }\n * }\n * }\n *\n * Tools exposed:\n * - ddt.validate — schema check on a .ddtproj\n * - ddt.compare — pac↔pac diff (offline)\n * - ddt.docs — generate HTML schema docs\n * - ddt.erd — generate Mermaid ER diagram (Markdown)\n * - ddt.safety_assess — classify a diff's safety findings\n * - ddt.narrate_diff — structured payload (diff + safety + prompt\n * scaffold) so the calling agent's LLM can\n * generate a plain-English narration.\n * - ddt.detect_pii — scan a project/pac for likely PII columns.\n * - ddt.suggest_safer — saferAlternatives entries for every\n * UNRECOVERABLE / DESTRUCTIVE finding.\n * - ddt.feature_search — search the DDT feature catalog by keyword.\n *\n * Live-network tools (extract, publish) are deliberately NOT exposed\n * — they require profile credentials and we'd rather an agent not act\n * on those without explicit user invocation.\n */\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n CompareEngine,\n PacSource,\n renderDocsReport,\n renderErdMarkdown,\n loadProject,\n parseProjectModel,\n discoverObjectFiles,\n pac,\n pii,\n safety,\n features,\n type DatabricksObject,\n} from '@ddt-tools/core';\n\ninterface JsonRpcRequest {\n jsonrpc: '2.0';\n id?: number | string | null;\n method: string;\n params?: unknown;\n}\n\ninterface JsonRpcResponse {\n jsonrpc: '2.0';\n id: number | string | null;\n result?: unknown;\n error?: { code: number; message: string; data?: unknown };\n}\n\nconst SERVER_NAME = 'ddt';\nconst SERVER_VERSION = '0.2.5';\nconst PROTOCOL_VERSION = '2024-11-05';\n\ninterface ToolDef {\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n handler: (args: Record<string, unknown>) => Promise<string>;\n}\n\nconst TOOLS: ToolDef[] = [\n {\n name: 'ddt.validate',\n description:\n 'Validate a .ddtproj file: parse JSON, check schema with zod, list object files. Returns a JSON summary of project structure.',\n inputSchema: {\n type: 'object',\n properties: {\n projectPath: { type: 'string', description: 'Absolute path to a .ddtproj file.' },\n },\n required: ['projectPath'],\n },\n handler: async (args) => {\n const projectPath = path.resolve(String(args.projectPath));\n const loaded = await loadProject(projectPath);\n const files = await discoverObjectFiles(loaded);\n return JSON.stringify(\n {\n ok: true,\n name: loaded.project.name,\n version: loaded.project.version,\n scope: loaded.project.scope,\n objectFileCount: files.length,\n profiles: Object.keys(loaded.project.deploymentProfiles ?? {}),\n },\n null,\n 2,\n );\n },\n },\n {\n name: 'ddt.compare',\n description:\n 'Compare two .ddtpac files (offline). Returns the diff JSON: added / removed / modified objects, with per-object field changes.',\n inputSchema: {\n type: 'object',\n properties: {\n sourcePac: { type: 'string', description: 'Absolute path to the source .ddtpac.' },\n targetPac: { type: 'string', description: 'Absolute path to the target .ddtpac.' },\n },\n required: ['sourcePac', 'targetPac'],\n },\n handler: async (args) => {\n const source = new PacSource(path.resolve(String(args.sourcePac)));\n const target = new PacSource(path.resolve(String(args.targetPac)));\n const engine = new CompareEngine();\n const result = await engine.compare(source, target);\n return JSON.stringify(result, null, 2);\n },\n },\n {\n name: 'ddt.docs',\n description:\n 'Generate self-contained HTML schema documentation from a .ddtproj or .ddtpac. Returns the HTML body as a string.',\n inputSchema: {\n type: 'object',\n properties: {\n source: { type: 'string', description: 'Absolute path to a .ddtproj or .ddtpac.' },\n title: { type: 'string', description: 'Optional doc title.' },\n },\n required: ['source'],\n },\n handler: async (args) => {\n const model = await loadModel(String(args.source));\n return renderDocsReport(model, {\n title: typeof args.title === 'string' ? args.title : 'Schema docs',\n });\n },\n },\n {\n name: 'ddt.erd',\n description: 'Generate a Mermaid ER diagram (Markdown) from a .ddtproj or .ddtpac.',\n inputSchema: {\n type: 'object',\n properties: {\n source: { type: 'string', description: 'Absolute path to a .ddtproj or .ddtpac.' },\n title: { type: 'string', description: 'Optional diagram title.' },\n },\n required: ['source'],\n },\n handler: async (args) => {\n const model = await loadModel(String(args.source));\n return renderErdMarkdown(\n model,\n typeof args.title === 'string' ? args.title : 'Schema ER diagram',\n );\n },\n },\n {\n name: 'ddt.safety_assess',\n description:\n 'Given two .ddtpac files, run the safety classifier on the diff. Returns UNRECOVERABLE / DESTRUCTIVE / EXPENSIVE / WARNING findings with the gate option each would need.',\n inputSchema: {\n type: 'object',\n properties: {\n sourcePac: { type: 'string', description: 'Source .ddtpac (desired state).' },\n targetPac: { type: 'string', description: 'Target .ddtpac (current state).' },\n },\n required: ['sourcePac', 'targetPac'],\n },\n handler: async (args) => {\n const source = new PacSource(path.resolve(String(args.sourcePac)));\n const target = new PacSource(path.resolve(String(args.targetPac)));\n const engine = new CompareEngine();\n const diff = await engine.compare(source, target);\n const assessment = safety.assess(diff);\n return JSON.stringify(assessment, null, 2);\n },\n },\n {\n name: 'ddt.narrate_diff',\n description:\n \"Build a structured payload (diff summary + per-object changes + safety findings + a system-prompt scaffold) the calling agent's LLM can use to generate a plain-English narration of a schema diff. Returns JSON the agent should compose into its own completion.\",\n inputSchema: {\n type: 'object',\n properties: {\n sourcePac: { type: 'string', description: 'Source .ddtpac (desired state).' },\n targetPac: { type: 'string', description: 'Target .ddtpac (current state).' },\n },\n required: ['sourcePac', 'targetPac'],\n },\n handler: async (args) => {\n const source = new PacSource(path.resolve(String(args.sourcePac)));\n const target = new PacSource(path.resolve(String(args.targetPac)));\n const engine = new CompareEngine();\n const diff = await engine.compare(source, target);\n const assessment = safety.assess(diff);\n const payload = {\n summary: diff.summary,\n objects: diff.objects.map((o) => ({\n kind: o.kind,\n objectType: o.identity.objectType,\n fqn: o.identity.fqn,\n })),\n safety: assessment,\n promptScaffold: {\n system:\n 'You are a senior Databricks UC engineer reviewing a schema migration. ' +\n 'Narrate the change in 2-3 short paragraphs: (1) the intent, ' +\n '(2) the safest order of operations, (3) the risks to watch ' +\n 'for. Be concrete; reference the FQNs.',\n userTemplate: 'Here is the diff JSON and the safety assessment. Generate the narration.',\n },\n };\n return JSON.stringify(payload, null, 2);\n },\n },\n {\n name: 'ddt.detect_pii',\n description:\n 'Scan a project or pac for columns that look like PII (email, phone, ssn, dob, credit-card, national-id, health, ip-address, name, address, generic). Returns each candidate with its category, confidence tier, and the reason the heuristic flagged it.',\n inputSchema: {\n type: 'object',\n properties: {\n source: { type: 'string', description: 'Absolute path to a .ddtproj or .ddtpac.' },\n },\n required: ['source'],\n },\n handler: async (args) => {\n const model = await loadModel(String(args.source));\n const candidates = pii.detectPiiCandidates(model);\n return JSON.stringify({ count: candidates.length, candidates }, null, 2);\n },\n },\n {\n name: 'ddt.suggest_safer',\n description:\n 'For every UNRECOVERABLE / DESTRUCTIVE finding the safety classifier produced on a diff, return the catalog\\'s saferAlternatives entries (e.g. \"instead of DROP TABLE, RENAME with a date suffix and let the retention policy expire it\"). The agent can paste these into a PR comment or CodeLens suggestion.',\n inputSchema: {\n type: 'object',\n properties: {\n sourcePac: { type: 'string', description: 'Source .ddtpac (desired).' },\n targetPac: { type: 'string', description: 'Target .ddtpac (current).' },\n },\n required: ['sourcePac', 'targetPac'],\n },\n handler: async (args) => {\n const source = new PacSource(path.resolve(String(args.sourcePac)));\n const target = new PacSource(path.resolve(String(args.targetPac)));\n const engine = new CompareEngine();\n const diff = await engine.compare(source, target);\n const assessment = safety.assess(diff);\n const dangerous = [...assessment.unrecoverable, ...assessment.destructive];\n const suggestions = dangerous.map((f) => {\n const explanation = safety.explainFinding(f.code);\n return {\n finding: { code: f.code, category: f.category, fqn: f.fqn, reason: f.reason },\n saferAlternatives: explanation?.saferAlternatives ?? [],\n requiredGates: explanation?.requiredGates ?? [],\n };\n });\n return JSON.stringify({ count: suggestions.length, suggestions }, null, 2);\n },\n },\n {\n name: 'ddt.feature_search',\n description:\n 'Search the DDT feature catalog by keyword. Returns features whose id, name, summary, or synonyms match the query. Useful for discovering which tier unlocks a capability or finding the right command for a task.',\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description:\n 'Search term — feature name, keyword, or capability (e.g. \"drift\", \"slice\", \"AI\").',\n },\n },\n required: ['query'],\n },\n handler: async (args) => {\n const q = String(args.query ?? '')\n .toLowerCase()\n .trim();\n const matches = features.DDT_FEATURE_CATALOG.filter(\n (f) =>\n f.id.includes(q) ||\n f.name.toLowerCase().includes(q) ||\n f.summary.toLowerCase().includes(q) ||\n f.synonyms?.some((s) => s.toLowerCase().includes(q)) ||\n f.whenToUse?.toLowerCase().includes(q),\n );\n return JSON.stringify(\n {\n count: matches.length,\n features: matches.map((f) => ({\n id: f.id,\n name: f.name,\n tier: f.tier,\n status: f.status,\n summary: f.summary,\n unlockHow: f.unlockHow,\n useCases: f.useCases,\n relatedFeatures: f.relatedFeatures,\n })),\n },\n null,\n 2,\n );\n },\n },\n];\n\nasync function loadModel(sourcePath: string): Promise<DatabricksObject[]> {\n const abs = path.resolve(sourcePath);\n if (abs.endsWith('.ddtpac')) {\n const pacContents = await pac.readPac(abs);\n return pacContents.model;\n }\n const loaded = await loadProject(abs);\n const parsed = await parseProjectModel(loaded);\n return parsed as DatabricksObject[];\n}\n\nexport function mcpCommand(): Command {\n const cmd = new Command('mcp');\n cmd\n .description(\n 'Start the DDT Model Context Protocol server on stdio (for Claude / Cursor / agents).',\n )\n .action(async () => {\n await runMcpStdio();\n });\n return cmd;\n}\n\nasync function runMcpStdio(): Promise<void> {\n await new Promise<void>((resolve) => {\n let buffer = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => {\n buffer += chunk;\n let nl = buffer.indexOf('\\n');\n while (nl !== -1) {\n const line = buffer.slice(0, nl).trim();\n buffer = buffer.slice(nl + 1);\n if (line.length > 0) {\n void handleLine(line);\n }\n nl = buffer.indexOf('\\n');\n }\n });\n process.stdin.on('end', () => resolve());\n process.stdin.on('close', () => resolve());\n });\n}\n\nasync function handleLine(line: string): Promise<void> {\n let req: JsonRpcRequest;\n try {\n req = JSON.parse(line) as JsonRpcRequest;\n } catch (err) {\n send({\n jsonrpc: '2.0',\n id: null,\n error: { code: -32700, message: 'Parse error', data: String(err) },\n });\n return;\n }\n const id = req.id ?? null;\n try {\n const result = await dispatch(req);\n if (id !== null) {\n send({ jsonrpc: '2.0', id, result });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n send({ jsonrpc: '2.0', id, error: { code: -32603, message } });\n }\n}\n\nasync function dispatch(req: JsonRpcRequest): Promise<unknown> {\n switch (req.method) {\n case 'initialize':\n return {\n protocolVersion: PROTOCOL_VERSION,\n serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },\n capabilities: { tools: {} },\n };\n case 'notifications/initialized':\n return undefined;\n case 'tools/list':\n return {\n tools: TOOLS.map((t) => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema,\n })),\n };\n case 'tools/call': {\n const params = (req.params ?? {}) as { name?: string; arguments?: Record<string, unknown> };\n const tool = TOOLS.find((t) => t.name === params.name);\n if (!tool) throw new Error(`Unknown tool: ${String(params.name)}`);\n const output = await tool.handler(params.arguments ?? {});\n return { content: [{ type: 'text', text: output }] };\n }\n case 'ping':\n return {};\n default:\n throw new Error(`Method not found: ${req.method}`);\n }\n}\n\nfunction send(msg: JsonRpcResponse): void {\n process.stdout.write(`${JSON.stringify(msg)}\\n`);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -15,6 +15,7 @@ import path from "path";
|
|
|
15
15
|
import {
|
|
16
16
|
CompareEngine,
|
|
17
17
|
DatabricksExecutor,
|
|
18
|
+
LiveSource,
|
|
18
19
|
PacSource,
|
|
19
20
|
ProjectSource,
|
|
20
21
|
ScriptGenerator,
|
|
@@ -45,12 +46,14 @@ import {
|
|
|
45
46
|
} from "@ddt-tools/core";
|
|
46
47
|
function publishCommand() {
|
|
47
48
|
const cmd = new Command("publish");
|
|
48
|
-
cmd.description(
|
|
49
|
+
cmd.description(
|
|
50
|
+
"Generate (and optionally apply) a migration script from source \u2192 target. Shared form with `sdt publish`: `--source <desired> --connection <live-target>` compares the desired state against the live workspace. Or pass `--target <offline>` to diff against a `.ddtproj` / `.ddtpac` instead."
|
|
51
|
+
).option(
|
|
49
52
|
"--source <path>",
|
|
50
53
|
"Source: .ddtproj or .ddtpac (the desired state). Required for a normal publish; omit only with --restore-from-snapshot."
|
|
51
54
|
).option(
|
|
52
55
|
"--target <path>",
|
|
53
|
-
"Target: .ddtproj or .ddtpac (the
|
|
56
|
+
"Target / current state: .ddtproj or .ddtpac (offline). Omit to compare the source against the live workspace at --connection (the `sdt publish` model). One of --target or --connection is required for a normal publish."
|
|
54
57
|
).option(
|
|
55
58
|
"--restore-from-snapshot <batchId>",
|
|
56
59
|
"Recovery mode: skip the compare step and emit DROP/CREATE \u2026 SHALLOW CLONE against the snapshot batch <batchId> from the registry. Dry-run by default; pass --apply --yes to execute. This is the command printed by TRUST.4's post-deploy-smoke + TRUST.8's restore-hint when a deploy fails \u2014 they tell the operator to run `ddt publish --restore-from-snapshot <id>`."
|
|
@@ -65,7 +68,10 @@ function publishCommand() {
|
|
|
65
68
|
"--apply",
|
|
66
69
|
"Apply the migration against --connection. Requires --connection. Default false.",
|
|
67
70
|
false
|
|
68
|
-
).option(
|
|
71
|
+
).option(
|
|
72
|
+
"--connection <name>",
|
|
73
|
+
"Connection profile. Doubles as the live compare target when --target is omitted, and as the apply executor (required with --apply)."
|
|
74
|
+
).option(
|
|
69
75
|
"--yes",
|
|
70
76
|
"Skip interactive confirmation when applying. Required with --apply (no TTY today).",
|
|
71
77
|
false
|
|
@@ -152,17 +158,31 @@ function publishCommand() {
|
|
|
152
158
|
await runRestoreFromSnapshot(opts);
|
|
153
159
|
return;
|
|
154
160
|
}
|
|
155
|
-
if (!opts.source
|
|
161
|
+
if (!opts.source) {
|
|
162
|
+
throw new Error("--source <path> is required (unless --restore-from-snapshot is given).");
|
|
163
|
+
}
|
|
164
|
+
if (!opts.target && !opts.connection) {
|
|
156
165
|
throw new Error(
|
|
157
|
-
"
|
|
166
|
+
"Provide a current state to diff the source against: --target <.ddtproj|.ddtpac> (offline) or --connection <profile> (the live workspace, mirroring `sdt publish`). With neither there is nothing to compare."
|
|
158
167
|
);
|
|
159
168
|
}
|
|
160
169
|
const nameMapping = await buildMappingFromOptions(opts);
|
|
161
170
|
const sourcePath = String(opts.source);
|
|
162
|
-
const targetPath = String(opts.target);
|
|
163
171
|
const engine = new CompareEngine();
|
|
164
172
|
const src = sourcePath.endsWith(".ddtpac") ? new PacSource(sourcePath, "source") : new ProjectSource(sourcePath, "source");
|
|
165
|
-
|
|
173
|
+
let tgt;
|
|
174
|
+
if (opts.target) {
|
|
175
|
+
const targetPath = String(opts.target);
|
|
176
|
+
tgt = targetPath.endsWith(".ddtpac") ? new PacSource(targetPath, "target") : new ProjectSource(targetPath, "target");
|
|
177
|
+
} else {
|
|
178
|
+
const scope = await resolveSourceScope(sourcePath);
|
|
179
|
+
const liveProfile = await getProfile(String(opts.connection));
|
|
180
|
+
tgt = new LiveSource(
|
|
181
|
+
createConnection(liveProfile),
|
|
182
|
+
scope,
|
|
183
|
+
`${String(opts.connection)} (live)`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
166
186
|
let loadedSrcPac;
|
|
167
187
|
if (sourcePath.endsWith(".ddtpac")) {
|
|
168
188
|
loadedSrcPac = await pacNs.readPac(sourcePath);
|
|
@@ -587,6 +607,13 @@ function printPlanReport(diffSummary, scriptSummary, assessment, steps) {
|
|
|
587
607
|
console.log(`${steps.length} step(s) planned (default-safe selection)`);
|
|
588
608
|
console.log(`${reversible} step(s) reversible \xB7 ${steps.length - reversible} irreversible`);
|
|
589
609
|
}
|
|
610
|
+
async function resolveSourceScope(sourcePath) {
|
|
611
|
+
const scope = sourcePath.endsWith(".ddtpac") ? (await pacNs.readPac(sourcePath)).manifest.scope : (await loadProject(sourcePath)).project.scope;
|
|
612
|
+
return {
|
|
613
|
+
...scope.catalog ? { catalog: scope.catalog } : {},
|
|
614
|
+
...scope.schema ? { schema: scope.schema } : {}
|
|
615
|
+
};
|
|
616
|
+
}
|
|
590
617
|
async function postWebhook(url, payload) {
|
|
591
618
|
const isSlackOrTeams = /hooks\.slack\.com|webhook\.office\.com/.test(url);
|
|
592
619
|
let body;
|
|
@@ -736,4 +763,4 @@ export {
|
|
|
736
763
|
publishCommand,
|
|
737
764
|
resolveQueryTag
|
|
738
765
|
};
|
|
739
|
-
//# sourceMappingURL=publish-
|
|
766
|
+
//# sourceMappingURL=publish-HLP3XHM5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/publish.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport {\n CompareEngine,\n DatabricksExecutor,\n LiveSource,\n PacSource,\n ProjectSource,\n ScriptGenerator,\n ai,\n aiPreflight,\n approval,\n buildDeployManifest,\n buildExecutionPlan,\n colorizeMigrationScript,\n compileSlice,\n createConnection,\n defaultSafePlan,\n executePlan,\n getProfile,\n lintSql,\n loadProject,\n mergeDeployOptions,\n pac as pacNs,\n pacFreshness,\n protectedObjects,\n queryExecution,\n recordDeployChangelog,\n renderHtmlReport,\n resolveProfile,\n safety,\n testFramework,\n validatePlan,\n type CompareSource,\n type StepResult,\n} from '@ddt-tools/core';\nimport { addMappingFlags, buildMappingFromOptions, describeMapping } from '../util/mapping.js';\nimport { attachRelatedOptions } from '../util/help-catalog.js';\n\n/**\n * `ddt publish` — produce a migration script that drives a target toward a\n * source's desired state. `--dry-run` (default) generates the script and\n * prints the safety assessment + default-safe plan summary.\n * `--apply` executes the plan against the named connection profile via\n * `DatabricksExecutor` + `executePlan`, with automatic rollback on\n * partial failure.\n */\nexport function publishCommand(): Command {\n const cmd = new Command('publish');\n cmd\n .description(\n 'Generate (and optionally apply) a migration script from source → target. ' +\n 'Shared form with `sdt publish`: `--source <desired> --connection <live-target>` ' +\n 'compares the desired state against the live workspace. Or pass `--target <offline>` ' +\n 'to diff against a `.ddtproj` / `.ddtpac` instead.',\n )\n .option(\n '--source <path>',\n 'Source: .ddtproj or .ddtpac (the desired state). Required for a normal publish; omit only with --restore-from-snapshot.',\n )\n .option(\n '--target <path>',\n 'Target / current state: .ddtproj or .ddtpac (offline). Omit to compare the source against the live workspace at --connection (the `sdt publish` model). One of --target or --connection is required for a normal publish.',\n )\n .option(\n '--restore-from-snapshot <batchId>',\n 'Recovery mode: skip the compare step and emit DROP/CREATE … SHALLOW CLONE against the snapshot batch <batchId> from the registry. ' +\n 'Dry-run by default; pass --apply --yes to execute. ' +\n \"This is the command printed by TRUST.4's post-deploy-smoke + TRUST.8's restore-hint when a deploy fails — they tell the operator to run `ddt publish --restore-from-snapshot <id>`.\",\n )\n .option(\n '--snapshot-dir <path>',\n 'Snapshot registry directory used with --restore-from-snapshot.',\n '.ddt/snapshots',\n )\n .option(\n '--out <path>',\n 'Write the generated SQL to this path (currently honored on the --restore-from-snapshot recovery path).',\n )\n .option('--dry-run', 'Print the migration script and safety assessment without applying.', true)\n .option(\n '--apply',\n 'Apply the migration against --connection. Requires --connection. Default false.',\n false,\n )\n .option(\n '--connection <name>',\n 'Connection profile. Doubles as the live compare target when --target is omitted, and as the apply executor (required with --apply).',\n )\n .option(\n '--yes',\n 'Skip interactive confirmation when applying. Required with --apply (no TTY today).',\n false,\n )\n .option('--variables <kv>', 'Comma-separated KEY=VALUE pairs for $(VAR) substitution.')\n .option('--ignore-case', 'Compare object FQNs case-insensitively.', false)\n .option(\n '--no-rollback',\n 'Skip rollback on partial failure (leaves DIRTY state). Default rollback ON.',\n )\n .option(\n '--require-reversible',\n 'Refuse before any DDL runs if any step lacks reverse SQL.',\n false,\n )\n .option(\n '--manifest <path>',\n 'Write a JSON deploy manifest (every step + status + duration + reverse SQL) to <path>. Audit-friendly.',\n )\n .option('--report-html <path>', 'Write a self-contained HTML compare-report to <path>.')\n .option(\n '--no-lint',\n 'Skip the lint gate. Default: lint the generated script and refuse --apply on ERROR-severity findings.',\n )\n .option(\n '--webhook <url>',\n 'POST a deploy-summary JSON to this URL after --apply completes. Or set $DDT_NOTIFY_WEBHOOK.',\n )\n .option(\n '--profile <name>',\n 'Use the deploymentProfiles[<name>] overlay from --source (.ddtproj). ' +\n 'Pulls profile.connection, profile.variables, profile.deployOptions on top of project defaults.',\n )\n .option(\n '--changelog [catalog]',\n 'After --apply, append a row to <catalog>.__ddt.deploy_log on the workspace. ' +\n \"Defaults catalog to the connection profile's default catalog. Liquibase-style audit table.\",\n )\n .option(\n '--no-slice',\n \"Disable the source's Project Slice (if it has one). Default: a sliced source partitions the diff automatically and target objects outside the slice are left untouched.\",\n )\n .option(\n '--query-tag <tag>',\n 'Tag attached to every executed statement so system.query.history can attribute the deploy. ' +\n 'Prepended as `-- DDT-TAG: <tag>` comment. ' +\n \"Defaults to 'ddt:<projectName>@<projectVersion>:<unix-ms>' when omitted.\",\n )\n .option(\n '--color <mode>',\n 'Color stdout: auto | always | never. Honors NO_COLOR / DDT_NO_COLOR env vars in auto mode.',\n 'auto',\n )\n .option(\n '--ai-preflight',\n \"Before --apply, ask AI to grade the operator's plain-language deploy description against the actual diff + safety findings. Blocks on 'mismatch' unless --confirm-after-preflight-mismatch is also passed.\",\n false,\n )\n .option(\n '--ai-preflight-text <narrative>',\n 'Non-interactive form of --ai-preflight. Supplies the narrative directly instead of reading from stdin.',\n )\n .option(\n '--strict-ai-preflight',\n \"Also block on 'partial' (not just 'mismatch'). Useful for production deploys.\",\n false,\n )\n .option(\n '--confirm-after-preflight-mismatch',\n 'Proceed even when --ai-preflight returns a mismatch verdict. Acknowledges that the AI grader flagged a discrepancy.',\n false,\n )\n .option(\n '--require-approvals <n>',\n 'Multi-approver gate: refuse --apply until <n> distinct approvers have signed off via `ddt approval add <deploy-id> --as <user>`. Team-tier.',\n )\n .option(\n '--approver <ids>',\n 'Comma-separated allow-list of approver IDs honoured by --require-approvals. Empty = any approver counts.',\n )\n .option(\n '--deploy-id <id>',\n 'Stable deploy identifier paired with --require-approvals (default: <connection>:<catalog>.<schema>).',\n )\n .option(\n '--approvals-root <path>',\n 'Approvals directory (default: .ddt/approvals).',\n path.join('.ddt', 'approvals'),\n )\n .option(\n '--allow-protected <fqns>',\n 'Comma-separated FQNs to exempt from the .ddt-protection.json gate. Repeatable.',\n (val: string, prev: string[]) => [...prev, val],\n [] as string[],\n )\n .option(\n '--allow-all-protected',\n 'Bypass all protection gates defined in .ddt-protection.json. Use with care.',\n false,\n )\n .option(\n '--freshness <mode>',\n 'Pac age check: warn (log if stale, continue), strict (block if stale), skip (no check). Default warn.',\n 'warn',\n )\n .option(\n '--post-deploy-tests <project>',\n 'After a successful --apply, discover YAML tests under <project>/tests/ and run them against the same connection. Exits 2 on any blocking (error-severity) DQ failure. Emits POST_DEPLOY_DQ_FAILED hint with --manifest recipe when tests fail.',\n );\n addMappingFlags(cmd);\n cmd.action(async (opts) => {\n // Recovery mode — branch out of the normal compare/apply flow and emit\n // DROP/CREATE … SHALLOW CLONE from a recorded snapshot batch.\n if (opts.restoreFromSnapshot) {\n await runRestoreFromSnapshot(opts);\n return;\n }\n if (!opts.source) {\n throw new Error('--source <path> is required (unless --restore-from-snapshot is given).');\n }\n if (!opts.target && !opts.connection) {\n throw new Error(\n 'Provide a current state to diff the source against: --target <.ddtproj|.ddtpac> (offline) ' +\n 'or --connection <profile> (the live workspace, mirroring `sdt publish`). With neither there is nothing to compare.',\n );\n }\n const nameMapping = await buildMappingFromOptions(opts);\n const sourcePath = String(opts.source);\n const engine = new CompareEngine();\n const src: CompareSource = sourcePath.endsWith('.ddtpac')\n ? new PacSource(sourcePath, 'source')\n : new ProjectSource(sourcePath, 'source');\n\n // Target: an offline artifact (--target) or, when omitted, the live\n // workspace at --connection. The live path mirrors `sdt publish`, which\n // always diffs the desired-state pac against the live --connection target.\n // The live target's scope (catalog / schema) is derived from the source.\n let tgt: CompareSource;\n if (opts.target) {\n const targetPath = String(opts.target);\n tgt = targetPath.endsWith('.ddtpac')\n ? new PacSource(targetPath, 'target')\n : new ProjectSource(targetPath, 'target');\n } else {\n const scope = await resolveSourceScope(sourcePath);\n const liveProfile = await getProfile(String(opts.connection));\n tgt = new LiveSource(\n createConnection(liveProfile),\n scope,\n `${String(opts.connection)} (live)`,\n );\n }\n\n // Freshness gate — only fires when source is a .ddtpac, before any network call.\n let loadedSrcPac: Awaited<ReturnType<typeof pacNs.readPac>> | undefined;\n if (sourcePath.endsWith('.ddtpac')) {\n loadedSrcPac = await pacNs.readPac(sourcePath);\n const freshnessMode = (opts.freshness as pacFreshness.FreshnessMode | undefined) ?? 'warn';\n if (freshnessMode !== 'skip') {\n const fr = pacFreshness.checkPacFreshness(loadedSrcPac.manifest.builtAt);\n if (fr.status !== 'fresh') {\n const msg = pacFreshness.formatFreshnessWarning(fr, sourcePath);\n if (freshnessMode === 'strict') {\n console.error(msg);\n process.exitCode = 1;\n return;\n } else {\n console.warn(msg);\n }\n }\n }\n }\n\n // Auto-load slice from the source. .ddtpac carries it in manifest;\n // .ddtproj has it as a top-level field. --no-slice bypasses.\n let slice: ReturnType<typeof compileSlice> | undefined;\n if (opts.slice !== false) {\n if (sourcePath.endsWith('.ddtpac')) {\n const srcPac = loadedSrcPac ?? (await pacNs.readPac(sourcePath));\n if (srcPac.manifest.slice) slice = compileSlice(srcPac.manifest.slice);\n } else {\n const loaded = await loadProject(sourcePath);\n if (loaded.project.slice) slice = compileSlice(loaded.project.slice);\n }\n }\n\n const result = await engine.compare(src, tgt, {\n ignoreCase: !!opts.ignoreCase,\n ...(nameMapping ? { nameMapping } : {}),\n ...(slice ? { sliceFilter: slice } : {}),\n });\n const mappingSummary = describeMapping(nameMapping);\n if (mappingSummary) console.log(`Mapping: ${mappingSummary}`);\n if (slice) {\n const outside = result.outsideScope?.length ?? 0;\n const refs = result.referenced?.length ?? 0;\n console.log(\n `Slice active: ${result.objects.length} owned · ${outside} outside scope (untouched) · ${refs} referenced`,\n );\n }\n\n if (opts.reportHtml) {\n const html = renderHtmlReport(result, {\n title: `Publish ${result.source.label} → ${result.target.label}`,\n safety: safety.assess(result),\n });\n const htmlPath = path.resolve(String(opts.reportHtml));\n await fs.mkdir(path.dirname(htmlPath), { recursive: true });\n await fs.writeFile(htmlPath, html, 'utf8');\n console.log(`Wrote HTML report → ${htmlPath}`);\n }\n\n // --profile overlay (project-level deployOptions + variables + connection).\n // VARSYNTAX.2 — also reads from `.ddtpac` manifest's deploymentProfiles\n // snapshot when --source is a built pac (writable since the pac builder\n // snapshots loaded.project.deploymentProfiles into the manifest).\n let profileDeployment: ReturnType<typeof mergeDeployOptions>['deployment'] | undefined;\n let profileVariables: Record<string, string> = {};\n let profileConnection: string | undefined;\n if (opts.profile) {\n const profileName = String(opts.profile);\n if (sourcePath.endsWith('.ddtpac')) {\n const srcPac = loadedSrcPac ?? (await pacNs.readPac(sourcePath));\n const block = srcPac.manifest.deploymentProfiles?.[profileName];\n if (!block) {\n const available = Object.keys(srcPac.manifest.deploymentProfiles ?? {});\n throw new Error(\n `--profile ${profileName}: no deploymentProfile by that name in the pac manifest. ` +\n (available.length === 0\n ? 'The pac carries no deploymentProfiles (was it built before VARSYNTAX.2, or does the .ddtproj declare any?).'\n : `Available: ${available.join(', ')}.`),\n );\n }\n if (block.variables) profileVariables = { ...block.variables };\n if (block.connection) profileConnection = block.connection;\n // pac-snapshot path does NOT overlay profile-level deployOptions —\n // those are already merged into manifest.deployOptions at build\n // time. Leave profileDeployment undefined; the caller falls back\n // to `mergeDeployOptions().deployment`.\n console.log(\n `Profile: ${profileName}${profileConnection ? ` (connection=${profileConnection})` : ''}`,\n );\n } else {\n const loaded = await loadProject(sourcePath);\n const resolved = resolveProfile(loaded, profileName);\n profileDeployment = resolved.deployment;\n profileVariables = resolved.variables;\n profileConnection = resolved.connection;\n console.log(`Profile: ${resolved.name} (connection=${resolved.connection})`);\n }\n }\n\n const assessment = safety.assess(result);\n const deployment = profileDeployment ?? mergeDeployOptions().deployment;\n const variables = { ...profileVariables, ...(parseVariables(opts.variables) ?? {}) };\n\n // VARSYNTAX.3 — assemble the deploy context so `$(DDT_PROFILE)`,\n // `$(DDT_PROJECT_NAME)`, `$(DDT_PROJECT_VERSION)`, `$(DDT_PLATFORM)`,\n // `$(DDT_TIMESTAMP)`, `$(DDT_USER)` substitute without the operator\n // having to pass them via --variables. projectName / projectVersion\n // come from whichever source was loaded (.ddtpac manifest or\n // .ddtproj); profile defaults to undefined unless --profile was used.\n let ctxProjectName: string | undefined;\n let ctxProjectVersion: string | undefined;\n if (sourcePath.endsWith('.ddtpac')) {\n const srcPac = loadedSrcPac ?? (await pacNs.readPac(sourcePath));\n ctxProjectName = srcPac.manifest.projectName;\n ctxProjectVersion = srcPac.manifest.projectVersion;\n } else {\n try {\n const loaded = await loadProject(sourcePath);\n ctxProjectName = loaded.project.name;\n ctxProjectVersion = loaded.project.version;\n } catch {\n // best-effort; built-in vars stay defaulted\n }\n }\n const generateContext: {\n profile?: string;\n projectName?: string;\n projectVersion?: string;\n user?: string;\n } = {\n ...(opts.profile ? { profile: String(opts.profile) } : {}),\n ...(ctxProjectName ? { projectName: ctxProjectName } : {}),\n ...(ctxProjectVersion ? { projectVersion: ctxProjectVersion } : {}),\n };\n\n const generator = new ScriptGenerator();\n const script = generator.generate(result, {\n variables,\n deployment,\n context: generateContext,\n });\n\n const plan = defaultSafePlan(result);\n const validated = validatePlan(result, plan);\n const steps = buildExecutionPlan(result, validated, { deployment });\n\n // Protection gate — block operations on objects declared in .ddt-protection.json.\n const protConfig = await protectedObjects.loadProtectionConfig(path.dirname(sourcePath));\n if (protConfig) {\n const ops = result.objects\n .filter((o) => o.kind !== 'unchanged' && o.kind !== 'added')\n .map((o) => ({\n fqn: o.identity.fqn,\n objectType: String(o.identity.objectType),\n kind: o.kind,\n }));\n const allowedFqns = ((opts.allowProtected as string[]) ?? []).flatMap((s: string) =>\n s.split(',').map((f: string) => f.trim()),\n );\n const violations = protectedObjects.checkProtectedObjects(ops, protConfig, {\n allowedFqns,\n allowAll: Boolean(opts.allowAllProtected),\n });\n if (violations.length > 0) {\n console.error(protectedObjects.formatProtectionViolations(violations));\n process.exitCode = 1;\n return;\n }\n }\n\n printPlanReport(result.summary, script.summary, assessment, steps);\n\n // ── apply path ──────────────────────────────────────────────────────\n if (opts.apply) {\n const connectionName = opts.connection ?? profileConnection;\n if (!connectionName) {\n throw new Error(\n '--apply requires --connection <profile-name> or --profile <env> (supplies connection).',\n );\n }\n if (!opts.yes) {\n throw new Error(\n '--apply requires --yes. The CLI does not currently prompt interactively. Pass --yes to confirm.',\n );\n }\n if (assessment.blocked) {\n console.error(\n `Refusing to apply: safety classifier blocked the deploy (${assessment.blockReason}).`,\n );\n process.exitCode = 2;\n return;\n }\n\n // Approval gate (Team-tier): refuse to apply until N distinct\n // approvers have recorded approval tokens for this deploy id.\n if (opts.requireApprovals !== undefined && opts.requireApprovals !== false) {\n const required = Number(opts.requireApprovals) || 0;\n const allowed = opts.approver\n ? String(opts.approver)\n .split(',')\n .map((s: string) => s.trim())\n .filter(Boolean)\n : [];\n const deployId = String(opts.deployId ?? `${connectionName}:default`);\n const approvalsRoot = String(opts.approvalsRoot);\n const file = await approval.readApprovalFile(approvalsRoot, deployId);\n const outcome = approval.evaluateApprovalGate(\n { deployId, required, allowedApprovers: allowed },\n file.approvals,\n );\n if (!outcome.satisfied) {\n console.error(\n 'Approval gate blocked deploy: ' +\n (outcome.blockReason ?? 'gate not satisfied') +\n `. Record approvals with \\`ddt approval add ${deployId} --as <user>\\`.`,\n );\n process.exitCode = 2;\n return;\n }\n console.error(\n ` approvals: ${outcome.satisfiedBy.length}/${required} ` +\n `(${outcome.satisfiedBy.join(', ')})`,\n );\n }\n\n // AI preflight grader: ask the operator to type what they expect\n // this deploy to do, then let an AI compare that narrative to the\n // actual SQL + safety findings. Blocks deploy on `mismatch`.\n if (opts.aiPreflight) {\n const narrative = await readPreflightNarrative(opts.aiPreflightText);\n if (!narrative) {\n console.error(\n '--ai-preflight needs a narrative. Provide it inline via --ai-preflight-text or via stdin.',\n );\n process.exitCode = 2;\n return;\n }\n console.log('Grading deploy narrative with AI...');\n try {\n const findings = [\n ...assessment.unrecoverable.map((f) => ({\n code: String(f.code),\n fqn: f.fqn,\n reason: f.reason,\n })),\n ...assessment.destructive.map((f) => ({\n code: String(f.code),\n fqn: f.fqn,\n reason: f.reason,\n })),\n ...assessment.expensive.map((f) => ({\n code: String(f.code),\n fqn: f.fqn,\n reason: f.reason,\n })),\n ];\n const compareSummary = `added ${result.summary.added}, modified ${result.summary.modified}, removed ${result.summary.removed}, unchanged ${result.summary.unchanged}`;\n const verdict = await aiPreflight.gradePreflight(\n {\n narrative,\n compareSummary,\n safetyFindings: findings,\n diffSampleDdl: script.sql,\n target: `${result.target.label}@${connectionName ?? 'unknown'}`,\n },\n {\n completeFn: async (prompt) => {\n const r = await ai.complete([{ role: 'user', content: prompt }], {\n feature: 'ai-preflight',\n });\n return r.text;\n },\n },\n );\n console.log(` preflight verdict: ${verdict.verdict}`);\n if (verdict.reasoning) console.log(` preflight reasoning: ${verdict.reasoning}`);\n for (const d of verdict.discrepancies) console.log(` • ${d}`);\n\n const isBlocking =\n verdict.verdict === 'mismatch' ||\n (opts.strictAiPreflight && verdict.verdict === 'partial');\n if (isBlocking && !opts.confirmAfterPreflightMismatch) {\n console.error(\n `Refusing to apply: AI preflight returned \"${verdict.verdict}\". Re-run with --confirm-after-preflight-mismatch to override, or revise the narrative.`,\n );\n process.exitCode = 2;\n return;\n }\n if (verdict.parseFailed) {\n console.warn(\n ' preflight verdict could not be parsed — continuing without a clean alignment signal.',\n );\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(\n `--ai-preflight failed: ${msg}. Configure an AI provider via \\`ddt ai status\\` or omit --ai-preflight.`,\n );\n process.exitCode = 2;\n return;\n }\n }\n\n // Lint gate — refuse --apply when the generated script trips an\n // ERROR-severity lint rule (L003 ADD NOT NULL no DEFAULT, L007\n // REFUSED placeholder). Opt out with --no-lint when you've\n // reviewed the script and want to proceed.\n if (opts.lint !== false) {\n const lint = lintSql(script.sql);\n if (lint.errorCount > 0) {\n console.error('');\n console.error(\n `Refusing to apply: lint gate caught ${lint.errorCount} ERROR-severity finding(s).`,\n );\n for (const f of lint.findings) {\n if (f.severity !== 'ERROR') continue;\n console.error(` ${f.rule} line ${f.line}: ${f.message}`);\n if (f.suggestion) console.error(` → ${f.suggestion}`);\n }\n console.error('');\n console.error('Resolve the findings or re-run with --no-lint to override.');\n process.exitCode = 2;\n return;\n }\n if (lint.warningCount > 0) {\n console.log(`Lint: ${lint.warningCount} warning(s) — proceeding.`);\n }\n }\n\n const profile = await getProfile(String(connectionName));\n const conn = createConnection(profile);\n const queryTag = resolveQueryTag(\n opts.queryTag,\n sourcePath,\n result.source.label,\n ctxProjectName,\n ctxProjectVersion,\n );\n if (queryTag) console.log(`Query tag: ${queryTag}`);\n try {\n await conn.connect();\n const executor = new DatabricksExecutor(conn, {\n ...(queryTag ? { queryTag } : {}),\n onStatement: (sql) => {\n const head = sql.split('\\n')[0]?.slice(0, 100) ?? '';\n console.log(` → ${head}${sql.length > 100 ? ' …' : ''}`);\n },\n });\n console.log('');\n console.log('--- APPLYING ---');\n const report = await executePlan(steps, executor, {\n rollbackOnFailure: opts.rollback !== false,\n requireReversible: !!opts.requireReversible,\n onStepStart: (step) => {\n console.log(`▶ ${step.objectType} ${step.fqn} — ${step.description}`);\n },\n onStepEnd: (r: StepResult) => {\n const tag =\n r.status === 'SUCCESS'\n ? '✓'\n : r.status === 'ROLLED_BACK'\n ? '↩'\n : r.status === 'FAILED'\n ? '✗'\n : r.status === 'SKIPPED'\n ? '-'\n : '?';\n console.log(\n ` ${tag} ${r.status}${r.durationMs ? ` (${r.durationMs}ms)` : ''}${r.error ? `: ${r.error}` : ''}`,\n );\n },\n });\n console.log('');\n console.log('--- DEPLOYMENT REPORT ---');\n console.log(`finalState: ${report.finalState}`);\n if (report.failedStepId) console.log(`failedStepId: ${report.failedStepId}`);\n if (report.preflightErrors?.length) {\n console.log('preflightErrors:');\n for (const e of report.preflightErrors) console.log(` ${e}`);\n }\n const manifestJson = JSON.stringify(\n buildDeployManifest(report, profile.auth.host),\n null,\n 2,\n );\n if (opts.manifest) {\n const mPath = path.resolve(String(opts.manifest));\n await fs.mkdir(path.dirname(mPath), { recursive: true });\n await fs.writeFile(mPath, manifestJson + '\\n', 'utf8');\n console.log(`Wrote deploy manifest → ${mPath}`);\n }\n\n // Target-side changelog table — best-effort. Failures are reported\n // (the deploy already succeeded) but never thrown.\n if (opts.changelog) {\n const catalog = opts.changelog === true ? undefined : String(opts.changelog);\n const r = await recordDeployChangelog(\n conn,\n report,\n result.source,\n result.target,\n manifestJson,\n '0.3.0',\n catalog ? { catalog } : {},\n );\n if (r.ok) {\n console.log(`Changelog: deploy_id=${r.entry.deployId} appended.`);\n } else {\n console.error(`Changelog write failed (deploy still completed): ${r.error}`);\n }\n }\n\n // Webhook notification — fire-and-forget POST with a small JSON\n // summary. Slack / Teams / generic JSON receivers all work.\n const webhookUrl = opts.webhook ?? process.env['DDT_NOTIFY_WEBHOOK'];\n if (webhookUrl) {\n try {\n await postWebhook(String(webhookUrl), {\n workspaceHost: profile.auth.host,\n source: result.source.label,\n target: result.target.label,\n finalState: report.finalState,\n failedStepId: report.failedStepId,\n stepsTotal: report.steps.length,\n stepsSucceeded: report.steps.filter((s) => s.status === 'SUCCESS').length,\n stepsFailed: report.steps.filter((s) => s.status === 'FAILED').length,\n });\n console.log(`Webhook notified → ${webhookUrl}`);\n } catch (err) {\n console.error(\n `Webhook POST failed (deploy still completed): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n if (report.finalState === 'DIRTY') {\n console.error(\n 'DIRTY: rollback could not complete. Manual intervention required — see report above.',\n );\n process.exitCode = 1;\n } else if (report.finalState !== 'CLEAN_SUCCESS') {\n process.exitCode = 1;\n }\n\n if (opts.postDeployTests && report.finalState === 'CLEAN_SUCCESS') {\n const projectRoot = path.resolve(String(opts.postDeployTests));\n console.log('Running post-deploy DQ tests from ' + projectRoot + '...');\n const testFiles = await testFramework.discoverTests(projectRoot);\n if (testFiles.length === 0) {\n console.warn(\n ' POST_DEPLOY_DQ: no tests found under ' + projectRoot + '/tests/ — skipping.',\n );\n } else {\n const dqReport = await testFramework.runTestFiles(testFiles, conn);\n process.stdout.write(testFramework.renderTextReport(dqReport));\n if (dqReport.summary.blocking) {\n console.error(\n 'POST_DEPLOY_DQ_FAILED: ' +\n dqReport.summary.fail +\n ' fail / ' +\n dqReport.summary.error +\n ' error.' +\n (opts.manifest\n ? ' Recover: `ddt revert --manifest ' + String(opts.manifest) + '`'\n : ' Inspect tests/ and re-run after fixing data issues.'),\n );\n process.exitCode = 2;\n } else {\n console.log(\n ' POST_DEPLOY_DQ: ' +\n dqReport.summary.pass +\n '/' +\n dqReport.summary.total +\n ' pass.',\n );\n }\n }\n }\n } finally {\n await conn.disconnect();\n }\n return;\n }\n\n // ── dry-run path (default) ──────────────────────────────────────────\n // Colorize the script for terminal output so destructive lines and safety\n // banners are unmissable; ANSI is stripped in auto mode when stdout is\n // not a TTY (e.g. piped to a file or CI log).\n const colorMode = (opts.color as 'auto' | 'always' | 'never' | undefined) ?? 'auto';\n console.log('');\n console.log('--- MIGRATION SCRIPT ---');\n console.log(colorizeMigrationScript(script.sql, { mode: colorMode }));\n });\n attachRelatedOptions(cmd, [\n 'deployment.allowDropTable',\n 'deployment.allowDropColumn',\n 'deployment.allowUnrecoverableDrop',\n 'deployment.allowNarrowingTypes',\n 'deployment.allowTableRebuild',\n 'deployment.blockOnPossibleDataLoss',\n 'deployment.preserveTargetOnlyObjects',\n ]);\n return cmd;\n}\n\nfunction printPlanReport(\n diffSummary: { added: number; removed: number; modified: number; unchanged: number },\n scriptSummary: { destructive: number; refused: number },\n assessment: ReturnType<typeof safety.assess>,\n steps: ReturnType<typeof buildExecutionPlan>,\n): void {\n console.log('--- COMPARE SUMMARY ---');\n console.log(\n `+${diffSummary.added} -${diffSummary.removed} ~${diffSummary.modified} =${diffSummary.unchanged}` +\n (scriptSummary.destructive ? ` (destructive=${scriptSummary.destructive})` : '') +\n (scriptSummary.refused ? ` (refused=${scriptSummary.refused})` : ''),\n );\n console.log('');\n console.log('--- SAFETY ASSESSMENT ---');\n console.log(`unrecoverable=${assessment.unrecoverable.length}`);\n console.log(`destructive=${assessment.destructive.length}`);\n console.log(`expensive=${assessment.expensive.length}`);\n console.log(`warnings=${assessment.warnings.length}`);\n if (assessment.blocked) console.log(`BLOCKED: ${assessment.blockReason}`);\n console.log('');\n console.log('--- DEFAULT-SAFE PLAN ---');\n const reversible = steps.filter((st) => st.reverseSql).length;\n console.log(`${steps.length} step(s) planned (default-safe selection)`);\n console.log(`${reversible} step(s) reversible · ${steps.length - reversible} irreversible`);\n}\n\n/**\n * Derive the live-target scope (catalog / schema) from the source artifact.\n * Both `.ddtpac` (manifest.scope) and `.ddtproj` (project.scope) carry a\n * `ProjectScope`. Used to scope the live `LiveSource` when `ddt publish` is\n * run without `--target` (the `sdt publish` model — diff against live).\n */\nasync function resolveSourceScope(\n sourcePath: string,\n): Promise<{ catalog?: string; schema?: string }> {\n const scope = sourcePath.endsWith('.ddtpac')\n ? (await pacNs.readPac(sourcePath)).manifest.scope\n : (await loadProject(sourcePath)).project.scope;\n return {\n ...(scope.catalog ? { catalog: scope.catalog } : {}),\n ...(scope.schema ? { schema: scope.schema } : {}),\n };\n}\n\nasync function postWebhook(url: string, payload: unknown): Promise<void> {\n const isSlackOrTeams = /hooks\\.slack\\.com|webhook\\.office\\.com/.test(url);\n let body: string;\n if (isSlackOrTeams) {\n // Both Slack and Teams accept a simple { text: \"...\" } shape.\n const summary = payload as {\n finalState: string;\n target: string;\n stepsTotal: number;\n stepsSucceeded: number;\n stepsFailed: number;\n };\n body = JSON.stringify({\n text: `DDT publish → ${summary.target} · ${summary.finalState} · ${summary.stepsSucceeded}/${summary.stepsTotal} steps ok${summary.stepsFailed ? ` · ${summary.stepsFailed} failed` : ''}`,\n });\n } else {\n body = JSON.stringify(payload);\n }\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n });\n if (!res.ok) {\n throw new Error(`HTTP ${res.status} ${res.statusText}`);\n }\n}\n\n// PD7.2 — parse `--variables KEY=VALUE,KEY=VALUE,...` using indexOf split\n// so a `=` inside the value (base64, JWT, connection-string fragments) is\n// preserved instead of being treated as a second delimiter. Mirrors the SDT\n// parsePublishVariables contract.\nexport function parseVariables(raw: unknown): Record<string, string> | undefined {\n if (!raw) return undefined;\n const out: Record<string, string> = {};\n for (const pair of String(raw).split(',')) {\n const eq = pair.indexOf('=');\n if (eq <= 0) continue;\n const k = pair.slice(0, eq).trim();\n const v = pair.slice(eq + 1).trim();\n if (k.length > 0) out[k] = v;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * Resolve the effective query tag. Honors an explicit `--query-tag`\n * flag value verbatim; otherwise auto-generates\n * `ddt:<projectName>@<projectVersion>:<unix-ms>` using the supplied\n * project context (pac manifest or .ddtproj). When projectName is\n * unknown, falls back to the source-path basename / sourceLabel; when\n * projectVersion is unknown the `@<projectVersion>` segment is omitted\n * so the tag remains valid. Returns undefined only when the user\n * explicitly passed `--query-tag ''`.\n */\nexport function resolveQueryTag(\n raw: unknown,\n sourcePath: string,\n sourceLabel: string,\n projectName: string | undefined,\n projectVersion: string | undefined,\n): string | undefined {\n if (raw !== undefined) {\n const s = String(raw).trim();\n return s.length === 0 ? undefined : s;\n }\n const fallback = sourcePath.endsWith('.ddtpac')\n ? path.basename(sourcePath, '.ddtpac')\n : sourceLabel;\n const base = projectName ?? fallback;\n const version = projectVersion ? `@${projectVersion}` : '';\n return `ddt:${base}${version}:${Date.now()}`;\n}\n\n/**\n * `ddt publish --restore-from-snapshot <batchId>` recovery path.\n *\n * Loads the batch from the registry, emits the restore SQL (DROP target +\n * CREATE … SHALLOW CLONE from snapshot, or a MANUAL hint row for\n * materialised / streaming objects), and either prints it (dry-run / no\n * --apply) or executes it against the live target (--apply --yes).\n *\n * Skips: pac freshness, compare, safety classifier, AI preflight, approval\n * gate, lint gate, manifest writing. Recovery is an emergency lever — the\n * gates that protect the forward path don't apply when we're restoring a\n * known-good snapshot the safety classifier itself authored.\n */\nasync function runRestoreFromSnapshot(opts: Record<string, unknown>): Promise<void> {\n const batchId = String(opts.restoreFromSnapshot);\n const registryDir = path.resolve(String(opts.snapshotDir ?? '.ddt/snapshots'));\n\n let batch: safety.SnapshotBatch;\n try {\n batch = await safety.readSnapshotBatch(registryDir, batchId);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Snapshot batch \"${batchId}\" not found in ${registryDir}: ${msg}`);\n process.exitCode = 1;\n return;\n }\n\n const sqlLines = safety.emitRestoreFromSnapshotSql(batch);\n const sql = sqlLines.join('\\n');\n\n console.log(`Restoring from batch ${batch.batchId} (createdAt=${batch.createdAt})`);\n console.log(\n ` ${batch.entries.length} object(s) recorded; ${sqlLines.filter((l) => !l.startsWith('--')).length} statement(s) will run.`,\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, sql + '\\n', 'utf8');\n console.log(`Wrote restore SQL → ${out}`);\n }\n\n // The DDT publish defaults --dry-run to true; recovery follows the same\n // convention: only --apply executes anything.\n if (!opts.apply) {\n const colorMode = (opts.color as 'auto' | 'always' | 'never' | undefined) ?? 'auto';\n process.stdout.write(colorizeMigrationScript(sql, { mode: colorMode }) + '\\n');\n return;\n }\n\n if (!opts.yes) {\n console.error(\n '--apply requires --yes (restore replaces target tables via DROP + SHALLOW CLONE).',\n );\n process.exitCode = 1;\n return;\n }\n if (!opts.connection) {\n console.error('--apply --restore-from-snapshot requires --connection <profile>.');\n process.exitCode = 1;\n return;\n }\n\n const profile = await getProfile(String(opts.connection));\n const conn = createConnection(profile);\n let executed = 0;\n let failed = 0;\n try {\n await conn.connect();\n for (const stmt of queryExecution.splitStatements(sql).map((s) => s.sql)) {\n try {\n await conn.executeRows(stmt);\n executed += 1;\n const head = stmt.split('\\n')[0]?.slice(0, 100) ?? '';\n console.log(' → ' + head + (stmt.length > 100 ? ' …' : ''));\n } catch (err) {\n failed += 1;\n const msg = err instanceof Error ? err.message : String(err);\n console.error(` [restore ${batch.batchId}] ${msg}\\n ${stmt}`);\n }\n }\n } finally {\n await conn.disconnect();\n }\n\n if (failed === 0) {\n console.log(`Restore complete. ${executed} statement(s) executed.`);\n } else {\n console.error(\n `Restore finished with failures: ${executed} ok, ${failed} failed. ` +\n `Inspect the snapshot tables under ${registryDir} and recover by hand.`,\n );\n process.exitCode = 1;\n }\n}\n\n/**\n * Read the AI-preflight narrative from --ai-preflight-text (preferred for CI)\n * or from stdin (interactive). Returns trimmed text, or undefined when no\n * narrative was supplied.\n */\nasync function readPreflightNarrative(inlineText: unknown): Promise<string | undefined> {\n if (typeof inlineText === 'string' && inlineText.trim().length > 0) {\n return inlineText.trim();\n }\n if (process.stdin.isTTY) {\n process.stdout.write(\n '\\n--ai-preflight is active. In one paragraph, describe what you expect this deploy to do.\\nEnd with an empty line (or Ctrl-D):\\n> ',\n );\n }\n process.stdin.setEncoding('utf8');\n let buffer = '';\n await new Promise<void>((resolve) => {\n const onData = (chunk: string): void => {\n buffer += chunk;\n if (/\\n\\s*\\n/.test(buffer)) {\n process.stdin.removeListener('data', onData);\n process.stdin.removeListener('end', onEnd);\n resolve();\n }\n };\n const onEnd = (): void => {\n process.stdin.removeListener('data', onData);\n resolve();\n };\n process.stdin.on('data', onData);\n process.stdin.on('end', onEnd);\n });\n const trimmed = buffer.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ddt-tools/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "DDT command-line interface. `ddt` lets you extract, build, compare, and deploy Databricks Unity Catalog schemas.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Varol Consulting LLC",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"commander": "^14.0.0",
|
|
30
30
|
"ora": "^9.4.0",
|
|
31
31
|
"zod": "^4.4.3",
|
|
32
|
-
"@ddt-tools/core": "0.2.
|
|
32
|
+
"@ddt-tools/core": "0.2.5"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^25.9.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/compare.ts","../src/util/color.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n CompareEngine,\n PacSource,\n ai,\n compileSlice,\n pac as pacNs,\n renderHtmlReport,\n safety,\n typecheck,\n writeCompareHistory,\n} from '@ddt-tools/core';\nimport { addMappingFlags, buildMappingFromOptions, describeMapping } from '../util/mapping.js';\nimport { attachRelatedOptions } from '../util/help-catalog.js';\nimport { makeColorizer, resolveColorMode } from '../util/color.js';\n\n/**\n * `ddt compare` — pac↔pac compare today. Project↔pac and project↔live require\n * the SQL parser pass (v0.3 polish) + a live driver session.\n *\n * Supports logical-name mapping (`--map`, `--map-file`) so two pacs whose\n * database / schema names differ can compare semantically. See\n * `docs/LOGICAL_NAME_MAPPING.md`.\n */\nexport function compareCommand(): Command {\n const cmd = new Command('compare');\n cmd\n .description('Compare two .ddtpac files (pac↔pac); project / live compare pending v0.3 polish.')\n .requiredOption('--source <path>', 'Source .ddtpac (the desired state).')\n .requiredOption('--target <path>', 'Target .ddtpac (the current state).')\n .option('--ignore-case', 'Compare object FQNs case-insensitively.', false)\n .option(\n '--json',\n 'Emit a JSON CompareResult instead of human-readable output. (Alias for --format json.)',\n false,\n )\n .option(\n '--format <fmt>',\n 'Output format: summary | json | markdown. Default summary.',\n 'summary',\n )\n .option('--report-html <path>', 'Also write a self-contained HTML compare-report to <path>.')\n .option(\n '--no-slice',\n \"Disable the source pac's Project Slice (if it has one). Default: a sliced pac is partitioned automatically.\",\n )\n .option(\n '--explain',\n 'After the diff, call the configured AI provider to narrate each change in plain English with reasoning. Requires `ddt ai` to be configured.',\n false,\n )\n .option(\n '--color <mode>',\n 'Colorize severity output: always | never | auto. Default auto (color on TTY).',\n 'auto',\n )\n .option(\n '--type-safe',\n 'After the compare, run the TYPECHECK.1 impact analyzer. Exits with code 2 if the configured --break-on threshold is reached (default `error`). CI-friendly gate — pair with `--format json` for machine-readable output.',\n false,\n )\n .option(\n '--break-on <severity>',\n 'TYPECHECK.2 threshold for --type-safe: `error` (exit 2 on any error ripple, default) | `warning` (exit 2 on any error OR warning ripple, strict CI mode).',\n 'error',\n )\n .option(\n '--write-impact [path]',\n 'Write the TYPECHECK.1 impact analysis to a JSON file the VS Code extension can surface as squiggle-underlines. Default path: `.ddt/impact.json` resolved relative to CWD. Composes with `--type-safe` — the file is written before the gate decides exit code.',\n )\n .option(\n '--no-history',\n 'Skip writing the compare-history audit record (AUDITCMP.1). Default: every compare run writes a record to `.ddt/history/compare/`, exportable via `ddt audit-log emit`.',\n );\n addMappingFlags(cmd);\n cmd.action(async (opts) => {\n const nameMapping = await buildMappingFromOptions(opts);\n const engine = new CompareEngine();\n const source = new PacSource(String(opts.source), 'source');\n const target = new PacSource(String(opts.target), 'target');\n\n // Auto-pull slice from the source pac's manifest unless --no-slice was set.\n let slice: ReturnType<typeof compileSlice> | undefined;\n if (opts.slice !== false) {\n const srcPac = await pacNs.readPac(String(opts.source));\n if (srcPac.manifest.slice) slice = compileSlice(srcPac.manifest.slice);\n }\n\n const startedAt = new Date().toISOString();\n const result = await engine.compare(source, target, {\n ignoreCase: !!opts.ignoreCase,\n ...(nameMapping ? { nameMapping } : {}),\n ...(slice ? { sliceFilter: slice } : {}),\n });\n\n // AUDITCMP.1 — compare operations leave the same audit trail deploys\n // do. Written before the format-specific early returns so every\n // output mode is covered. Best-effort: a failed write never breaks\n // the compare itself.\n if (opts.history !== false) {\n const finishedAt = new Date().toISOString();\n try {\n const histAssessment = safety.assess(result);\n const changed = result.summary.added + result.summary.removed + result.summary.modified;\n await writeCompareHistory(path.dirname(path.resolve(String(opts.source))), {\n startedAt,\n finishedAt,\n durationMs: Date.parse(finishedAt) - Date.parse(startedAt),\n outcome: histAssessment.blocked\n ? 'BLOCKED'\n : changed === 0\n ? 'NO_CHANGES'\n : 'CHANGES_FOUND',\n source: { kind: result.source.kind, label: result.source.label },\n target: { kind: result.target.kind, label: result.target.label },\n summary: result.summary,\n sliceApplied: Boolean(slice),\n ...(histAssessment.blocked && histAssessment.blockReason\n ? { blockReason: histAssessment.blockReason }\n : {}),\n });\n } catch {\n /* audit trail is best-effort; never fail the compare over it */\n }\n }\n\n if (opts.reportHtml) {\n const html = renderHtmlReport(result, {\n title: `Compare ${result.source.label} → ${result.target.label}`,\n safety: safety.assess(result),\n });\n const htmlPath = path.resolve(String(opts.reportHtml));\n await fs.mkdir(path.dirname(htmlPath), { recursive: true });\n await fs.writeFile(htmlPath, html, 'utf8');\n console.error(`Wrote ${htmlPath} (${html.length} bytes).`);\n }\n\n const fmt = opts.json ? 'json' : String(opts.format ?? 'summary').toLowerCase();\n if (fmt === 'json') {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n if (fmt === 'markdown') {\n const assessment = safety.assess(result);\n console.log(renderCompareMarkdown(result, assessment));\n return;\n }\n const s = result.summary;\n console.log(`Source: ${result.source.kind}:${result.source.label}`);\n console.log(`Target: ${result.target.kind}:${result.target.label}`);\n const mappingSummary = describeMapping(nameMapping);\n if (mappingSummary) console.log(`Mapping: ${mappingSummary}`);\n if (slice) {\n const outside = result.outsideScope?.length ?? 0;\n const refs = result.referenced?.length ?? 0;\n console.log(\n `Slice active: ${result.objects.length} owned · ${outside} outside scope (untouched) · ${refs} referenced`,\n );\n }\n console.log(`Summary: +${s.added} -${s.removed} ~${s.modified} =${s.unchanged}`);\n for (const o of result.objects) {\n if (o.kind === 'unchanged') continue;\n const glyph = o.kind === 'added' ? '+' : o.kind === 'removed' ? '-' : '~';\n console.log(` ${glyph} ${o.identity.objectType} ${o.identity.fqn}`);\n if (o.kind === 'modified' && o.changes) {\n for (const c of o.changes) {\n console.log(\n ` • ${c.path}: ${JSON.stringify(c.target)} → ${JSON.stringify(c.source)}`,\n );\n }\n }\n }\n\n // Reversibility-grouped safety findings — Unrecoverable first (highest\n // urgency), then Data-impacting, then Reversible. Empty buckets are\n // skipped, and an empty assessment is silent.\n const assessment = safety.assess(result);\n const buckets = safety.groupByReversibility(assessment);\n const total =\n buckets.unrecoverable.length + buckets.dataImpacting.length + buckets.reversible.length;\n if (total > 0) {\n const colorize = makeColorizer(resolveColorMode(opts.color));\n console.log('');\n console.log('Safety findings (grouped by reversibility):');\n const block = safety.formatReversibilityBuckets(buckets);\n const colored = colorize.applyToBlock(block);\n for (const line of colored.split('\\n')) console.log(' ' + line);\n if (assessment.blocked && assessment.blockReason) {\n console.error(' ' + colorize.unrecoverable('BLOCKED: ' + assessment.blockReason));\n }\n }\n\n // AI-narrated diff (--explain). Calls the configured provider with a\n // structured prompt containing the diff summary + per-object changes\n // + safety findings.\n if (opts.explain) {\n console.log('');\n console.log('AI explanation:');\n try {\n const userPrompt = buildExplainPrompt(result, assessment);\n const reply = await ai.complete(\n [\n { role: 'system', content: SYSTEM_PROMPT },\n { role: 'user', content: userPrompt },\n ],\n { feature: 'compare.explain' },\n );\n for (const line of reply.text.split('\\n')) console.log(' ' + line);\n } catch (err) {\n console.error(' --explain failed: ' + (err instanceof Error ? err.message : String(err)));\n console.error(\n ' Run `ddt ai status` to verify your AI provider is configured (`ddt ai test` to send a probe).',\n );\n }\n }\n\n // --write-impact: TYPECHECK.4 — write the impact analysis to a\n // JSON file the VS Code provider reads. Always runs the analyzer\n // (even when --type-safe is off) so the editor surface is\n // independent of the CI-gate flag. Composes with --type-safe.\n if (opts.writeImpact !== undefined) {\n await writeImpactJson(\n result,\n opts.writeImpact === true ? undefined : String(opts.writeImpact),\n );\n }\n\n // --type-safe: TYPECHECK.2 CI gate. Runs the TYPECHECK.1 impact\n // analyzer and exits with code 2 if any error-severity ripple\n // (column-drop or table-drop reaching a dependent object) is\n // detected. Stays silent on a clean diff so this composes well\n // with --format json (which already returned above).\n if (opts.typeSafe) {\n const breakOnRaw = String(opts.breakOn ?? 'error').toLowerCase();\n if (breakOnRaw !== 'error' && breakOnRaw !== 'warning') {\n throw new Error(\n `--break-on must be 'error' or 'warning' (got '${opts.breakOn}'). 'error' fails on any error-severity ripple (default); 'warning' fails on any warning or error ripple (strict CI).`,\n );\n }\n const breakOn = breakOnRaw as 'error' | 'warning';\n const impactFindings = typecheck.analyzeImpact(result);\n const impactSummary = typecheck.summarizeImpact(impactFindings);\n if (impactSummary.findingsCount > 0) {\n console.log('');\n console.log(\n `Type-safety gate (--break-on ${breakOn}) — ${impactSummary.findingsCount} breaking change(s); ${impactSummary.errors} error ripple(s), ${impactSummary.warnings} warning ripple(s); ${impactSummary.affectedObjects.length} dependent object(s).`,\n );\n for (const f of impactFindings) {\n for (const r of f.ripples) {\n const tag = r.severity === 'error' ? 'ERROR' : 'WARN ';\n console.log(\n ` ${tag} ${r.fqn} ← ${f.breakingChangeOn.fqn} (${f.breakingChangeOn.kind})`,\n );\n }\n }\n const failOnError = impactSummary.errors > 0;\n const failOnWarning = breakOn === 'warning' && impactSummary.warnings > 0;\n if (failOnError) {\n console.error(\n `Type-safety gate FAILED — ${impactSummary.errors} error ripple(s). Fix the dependents (drop / update / re-bind) before re-running compare, OR rerun with the change scoped out of the diff.`,\n );\n process.exitCode = 2;\n } else if (failOnWarning) {\n console.error(\n `Type-safety gate FAILED (strict --break-on warning) — ${impactSummary.warnings} warning ripple(s). Tighten the dependent SQL to accept the new type, or drop --break-on warning to allow.`,\n );\n process.exitCode = 2;\n }\n } else {\n console.log('Type-safety gate PASSED — no breaking-change ripples detected.');\n }\n }\n });\n attachRelatedOptions(cmd, [\n 'compare.ignoreCase',\n 'compare.ignoreComments',\n 'compare.ignoreFormattingDifferences',\n 'compare.ignoreClusterBy',\n 'compare.ignorePartitioning',\n 'compare.ignoreStorageLocation',\n ]);\n return cmd;\n}\n\n/**\n * TYPECHECK.4 — write the impact analysis JSON to disk so the VS Code\n * provider can surface it as squiggle-underlines. Defaults to\n * `<cwd>/.ddt/impact.json` (DDT compare today is pac↔pac so we don't\n * resolve a project root from the source arg — that wires in when the\n * project-source variant of compare ships).\n */\nasync function writeImpactJson(\n result: Parameters<typeof typecheck.analyzeImpact>[0],\n explicitPath: string | undefined,\n): Promise<void> {\n const findings = typecheck.analyzeImpact(result);\n const summary = typecheck.summarizeImpact(findings);\n const file: typecheck.ImpactFile = {\n version: typecheck.IMPACT_FILE_VERSION,\n generatedAt: new Date().toISOString(),\n source: `${result.source.kind}:${result.source.label}`,\n target: `${result.target.kind}:${result.target.label}`,\n findings,\n summary,\n };\n const outPath = explicitPath ? path.resolve(explicitPath) : path.resolve('.ddt', 'impact.json');\n await fs.mkdir(path.dirname(outPath), { recursive: true });\n await fs.writeFile(outPath, typecheck.serializeImpactFile(file));\n console.log(\n `Wrote ${outPath} — ${summary.findingsCount} finding(s), ${summary.errors} error / ${summary.warnings} warning ripple(s).`,\n );\n}\n\nexport const SYSTEM_PROMPT = `You are a senior Databricks Unity Catalog DBA reviewing a schema diff. Your job is to narrate the changes in plain English to a teammate who hasn't seen the underlying SQL. For each change, briefly say:\n - what it is (e.g. \"a new fact table is added\")\n - why a reasonable engineer might do this (the intent)\n - what to watch out for (the risk, if any)\n\nBe concrete; use the FQNs you're given. Keep it to 3-6 sentences per object. Don't repeat the raw diff back — explain it.`;\n\nexport function buildExplainPrompt(\n result: {\n source: { label: string };\n target: { label: string };\n objects: Array<{\n kind: string;\n identity: { fqn: string; objectType: string };\n changes?: unknown[];\n }>;\n summary: { added: number; removed: number; modified: number; unchanged: number };\n },\n assessment: {\n unrecoverable: unknown[];\n destructive: unknown[];\n expensive: unknown[];\n warnings: unknown[];\n },\n): string {\n const lines: string[] = [];\n lines.push(`Source: ${result.source.label}`);\n lines.push(`Target: ${result.target.label}`);\n lines.push(\n `Summary: +${result.summary.added} added, -${result.summary.removed} removed, ~${result.summary.modified} modified.`,\n );\n lines.push(\n `Safety: ${assessment.unrecoverable.length} unrecoverable, ${assessment.destructive.length} destructive, ${assessment.expensive.length} expensive, ${assessment.warnings.length} warnings.`,\n );\n lines.push('');\n lines.push('Changes (up to 40):');\n const changed = result.objects.filter((o) => o.kind !== 'unchanged').slice(0, 40);\n for (const o of changed) {\n lines.push(`- ${o.kind} ${o.identity.objectType} ${o.identity.fqn}`);\n }\n if (result.objects.filter((o) => o.kind !== 'unchanged').length > 40) {\n lines.push(\n ` (… ${result.objects.filter((o) => o.kind !== 'unchanged').length - 40} more truncated)`,\n );\n }\n lines.push('');\n lines.push(\n 'Please narrate this diff in plain English. Group related changes together when it helps.',\n );\n return lines.join('\\n');\n}\n\nfunction renderCompareMarkdown(\n result: {\n source: { kind: string; label: string };\n target: { kind: string; label: string };\n objects: Array<{ kind: string; identity: { fqn: string; objectType: string } }>;\n summary: { added: number; removed: number; modified: number; unchanged: number };\n },\n assessment: {\n unrecoverable: unknown[];\n destructive: unknown[];\n expensive: unknown[];\n warnings: unknown[];\n blocked?: boolean;\n blockReason?: string;\n },\n): string {\n const lines: string[] = [];\n lines.push(\n `# Compare: ${result.source.kind}:${result.source.label} → ${result.target.kind}:${result.target.label}`,\n );\n lines.push('');\n lines.push(\n `**Summary**: +${result.summary.added} added · -${result.summary.removed} removed · ~${result.summary.modified} modified · =${result.summary.unchanged} unchanged.`,\n );\n lines.push('');\n lines.push('**Safety**:');\n lines.push(`- 🛑 Unrecoverable: ${assessment.unrecoverable.length}`);\n lines.push(`- 🔥 Destructive: ${assessment.destructive.length}`);\n lines.push(`- ⏱ Expensive: ${assessment.expensive.length}`);\n lines.push(`- ⚠ Warnings: ${assessment.warnings.length}`);\n if (assessment.blocked) {\n lines.push('');\n lines.push(\n `> **BLOCKED**: ${assessment.blockReason ?? 'safety classifier refuses to proceed'}`,\n );\n }\n lines.push('');\n const changed = result.objects.filter((o) => o.kind !== 'unchanged');\n if (changed.length === 0) {\n lines.push('_No object-level changes._');\n return lines.join('\\n');\n }\n lines.push('| Kind | Type | FQN |');\n lines.push('| --- | --- | --- |');\n for (const o of changed) {\n lines.push(`| ${o.kind} | \\`${o.identity.objectType}\\` | \\`${o.identity.fqn}\\` |`);\n }\n return lines.join('\\n');\n}\n","/**\n * ANSI severity coloring for CLI output, DDT side. Paired with\n * `Snowflake/packages/cli/src/util/color.ts` byte-for-byte at the\n * API level.\n */\nexport type ColorMode = 'always' | 'never' | 'auto';\n\nconst ANSI = {\n reset: '\\x1b[0m',\n bold: '\\x1b[1m',\n red: '\\x1b[31m',\n yellow: '\\x1b[33m',\n cyan: '\\x1b[36m',\n green: '\\x1b[32m',\n gray: '\\x1b[90m',\n};\n\nexport function resolveColorMode(flag: string | undefined): boolean {\n const mode = (flag ?? 'auto').toLowerCase() as ColorMode;\n if (mode === 'always') return true;\n if (mode === 'never') return false;\n if (process.env.NO_COLOR) return false;\n return Boolean(process.stdout.isTTY);\n}\n\nexport interface Colorizer {\n unrecoverable(s: string): string;\n destructive(s: string): string;\n expensive(s: string): string;\n warning(s: string): string;\n safe(s: string): string;\n dim(s: string): string;\n applyToBlock(block: string): string;\n}\n\nexport function makeColorizer(enabled: boolean): Colorizer {\n if (!enabled) {\n const identity = (s: string): string => s;\n return {\n unrecoverable: identity,\n destructive: identity,\n expensive: identity,\n warning: identity,\n safe: identity,\n dim: identity,\n applyToBlock: identity,\n };\n }\n const wrap =\n (codes: string) =>\n (s: string): string =>\n `${codes}${s}${ANSI.reset}`;\n return {\n unrecoverable: wrap(ANSI.bold + ANSI.red),\n destructive: wrap(ANSI.red),\n expensive: wrap(ANSI.yellow),\n warning: wrap(ANSI.cyan),\n safe: wrap(ANSI.green),\n dim: wrap(ANSI.gray),\n applyToBlock(block: string): string {\n return block\n .split('\\n')\n .map((line) => {\n if (/UNRECOVERABLE/.test(line) || line.includes('🛑'))\n return ANSI.bold + ANSI.red + line + ANSI.reset;\n if (/DESTRUCTIVE/.test(line)) return ANSI.red + line + ANSI.reset;\n if (/EXPENSIVE/.test(line)) return ANSI.yellow + line + ANSI.reset;\n if (/WARNING/.test(line) || line.includes('⚠')) return ANSI.cyan + line + ANSI.reset;\n if (/\\bSAFE\\b|\\bOK\\b/.test(line) || line.includes('✓'))\n return ANSI.green + line + ANSI.reset;\n return line;\n })\n .join('\\n');\n },\n };\n}\n"],"mappings":";;;;;;;;;;;AAAA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AACjB,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACNP,IAAM,OAAO;AAAA,EACX,OAAO;AAAA,EACP,MAAM;AAAA,EACN,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AAEO,SAAS,iBAAiB,MAAmC;AAClE,QAAM,QAAQ,QAAQ,QAAQ,YAAY;AAC1C,MAAI,SAAS,SAAU,QAAO;AAC9B,MAAI,SAAS,QAAS,QAAO;AAC7B,MAAI,QAAQ,IAAI,SAAU,QAAO;AACjC,SAAO,QAAQ,QAAQ,OAAO,KAAK;AACrC;AAYO,SAAS,cAAc,SAA6B;AACzD,MAAI,CAAC,SAAS;AACZ,UAAM,WAAW,CAAC,MAAsB;AACxC,WAAO;AAAA,MACL,eAAe;AAAA,MACf,aAAa;AAAA,MACb,WAAW;AAAA,MACX,SAAS;AAAA,MACT,MAAM;AAAA,MACN,KAAK;AAAA,MACL,cAAc;AAAA,IAChB;AAAA,EACF;AACA,QAAM,OACJ,CAAC,UACD,CAAC,MACC,GAAG,KAAK,GAAG,CAAC,GAAG,KAAK,KAAK;AAC7B,SAAO;AAAA,IACL,eAAe,KAAK,KAAK,OAAO,KAAK,GAAG;AAAA,IACxC,aAAa,KAAK,KAAK,GAAG;AAAA,IAC1B,WAAW,KAAK,KAAK,MAAM;AAAA,IAC3B,SAAS,KAAK,KAAK,IAAI;AAAA,IACvB,MAAM,KAAK,KAAK,KAAK;AAAA,IACrB,KAAK,KAAK,KAAK,IAAI;AAAA,IACnB,aAAa,OAAuB;AAClC,aAAO,MACJ,MAAM,IAAI,EACV,IAAI,CAAC,SAAS;AACb,YAAI,gBAAgB,KAAK,IAAI,KAAK,KAAK,SAAS,WAAI;AAClD,iBAAO,KAAK,OAAO,KAAK,MAAM,OAAO,KAAK;AAC5C,YAAI,cAAc,KAAK,IAAI,EAAG,QAAO,KAAK,MAAM,OAAO,KAAK;AAC5D,YAAI,YAAY,KAAK,IAAI,EAAG,QAAO,KAAK,SAAS,OAAO,KAAK;AAC7D,YAAI,UAAU,KAAK,IAAI,KAAK,KAAK,SAAS,QAAG,EAAG,QAAO,KAAK,OAAO,OAAO,KAAK;AAC/E,YAAI,kBAAkB,KAAK,IAAI,KAAK,KAAK,SAAS,QAAG;AACnD,iBAAO,KAAK,QAAQ,OAAO,KAAK;AAClC,eAAO;AAAA,MACT,CAAC,EACA,KAAK,IAAI;AAAA,IACd;AAAA,EACF;AACF;;;ADjDO,SAAS,iBAA0B;AACxC,QAAM,MAAM,IAAI,QAAQ,SAAS;AACjC,MACG,YAAY,uFAAkF,EAC9F,eAAe,mBAAmB,qCAAqC,EACvE,eAAe,mBAAmB,qCAAqC,EACvE,OAAO,iBAAiB,2CAA2C,KAAK,EACxE;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC,OAAO,wBAAwB,4DAA4D,EAC3F;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF;AACF,kBAAgB,GAAG;AACnB,MAAI,OAAO,OAAO,SAAS;AACzB,UAAM,cAAc,MAAM,wBAAwB,IAAI;AACtD,UAAM,SAAS,IAAI,cAAc;AACjC,UAAM,SAAS,IAAI,UAAU,OAAO,KAAK,MAAM,GAAG,QAAQ;AAC1D,UAAM,SAAS,IAAI,UAAU,OAAO,KAAK,MAAM,GAAG,QAAQ;AAG1D,QAAI;AACJ,QAAI,KAAK,UAAU,OAAO;AACxB,YAAM,SAAS,MAAM,MAAM,QAAQ,OAAO,KAAK,MAAM,CAAC;AACtD,UAAI,OAAO,SAAS,MAAO,SAAQ,aAAa,OAAO,SAAS,KAAK;AAAA,IACvE;AAEA,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,UAAM,SAAS,MAAM,OAAO,QAAQ,QAAQ,QAAQ;AAAA,MAClD,YAAY,CAAC,CAAC,KAAK;AAAA,MACnB,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,MACrC,GAAI,QAAQ,EAAE,aAAa,MAAM,IAAI,CAAC;AAAA,IACxC,CAAC;AAMD,QAAI,KAAK,YAAY,OAAO;AAC1B,YAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAC1C,UAAI;AACF,cAAM,iBAAiB,OAAO,OAAO,MAAM;AAC3C,cAAM,UAAU,OAAO,QAAQ,QAAQ,OAAO,QAAQ,UAAU,OAAO,QAAQ;AAC/E,cAAM,oBAAoB,KAAK,QAAQ,KAAK,QAAQ,OAAO,KAAK,MAAM,CAAC,CAAC,GAAG;AAAA,UACzE;AAAA,UACA;AAAA,UACA,YAAY,KAAK,MAAM,UAAU,IAAI,KAAK,MAAM,SAAS;AAAA,UACzD,SAAS,eAAe,UACpB,YACA,YAAY,IACV,eACA;AAAA,UACN,QAAQ,EAAE,MAAM,OAAO,OAAO,MAAM,OAAO,OAAO,OAAO,MAAM;AAAA,UAC/D,QAAQ,EAAE,MAAM,OAAO,OAAO,MAAM,OAAO,OAAO,OAAO,MAAM;AAAA,UAC/D,SAAS,OAAO;AAAA,UAChB,cAAc,QAAQ,KAAK;AAAA,UAC3B,GAAI,eAAe,WAAW,eAAe,cACzC,EAAE,aAAa,eAAe,YAAY,IAC1C,CAAC;AAAA,QACP,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,QAAI,KAAK,YAAY;AACnB,YAAM,OAAO,iBAAiB,QAAQ;AAAA,QACpC,OAAO,WAAW,OAAO,OAAO,KAAK,WAAM,OAAO,OAAO,KAAK;AAAA,QAC9D,QAAQ,OAAO,OAAO,MAAM;AAAA,MAC9B,CAAC;AACD,YAAM,WAAW,KAAK,QAAQ,OAAO,KAAK,UAAU,CAAC;AACrD,YAAM,GAAG,MAAM,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAC1D,YAAM,GAAG,UAAU,UAAU,MAAM,MAAM;AACzC,cAAQ,MAAM,SAAS,QAAQ,KAAK,KAAK,MAAM,UAAU;AAAA,IAC3D;AAEA,UAAM,MAAM,KAAK,OAAO,SAAS,OAAO,KAAK,UAAU,SAAS,EAAE,YAAY;AAC9E,QAAI,QAAQ,QAAQ;AAClB,cAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC3C;AAAA,IACF;AACA,QAAI,QAAQ,YAAY;AACtB,YAAMA,cAAa,OAAO,OAAO,MAAM;AACvC,cAAQ,IAAI,sBAAsB,QAAQA,WAAU,CAAC;AACrD;AAAA,IACF;AACA,UAAM,IAAI,OAAO;AACjB,YAAQ,IAAI,WAAW,OAAO,OAAO,IAAI,IAAI,OAAO,OAAO,KAAK,EAAE;AAClE,YAAQ,IAAI,WAAW,OAAO,OAAO,IAAI,IAAI,OAAO,OAAO,KAAK,EAAE;AAClE,UAAM,iBAAiB,gBAAgB,WAAW;AAClD,QAAI,eAAgB,SAAQ,IAAI,YAAY,cAAc,EAAE;AAC5D,QAAI,OAAO;AACT,YAAM,UAAU,OAAO,cAAc,UAAU;AAC/C,YAAM,OAAO,OAAO,YAAY,UAAU;AAC1C,cAAQ;AAAA,QACN,iBAAiB,OAAO,QAAQ,MAAM,eAAY,OAAO,mCAAgC,IAAI;AAAA,MAC/F;AAAA,IACF;AACA,YAAQ,IAAI,aAAa,EAAE,KAAK,KAAK,EAAE,OAAO,KAAK,EAAE,QAAQ,KAAK,EAAE,SAAS,EAAE;AAC/E,eAAW,KAAK,OAAO,SAAS;AAC9B,UAAI,EAAE,SAAS,YAAa;AAC5B,YAAM,QAAQ,EAAE,SAAS,UAAU,MAAM,EAAE,SAAS,YAAY,MAAM;AACtE,cAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,SAAS,UAAU,IAAI,EAAE,SAAS,GAAG,EAAE;AACnE,UAAI,EAAE,SAAS,cAAc,EAAE,SAAS;AACtC,mBAAW,KAAK,EAAE,SAAS;AACzB,kBAAQ;AAAA,YACN,gBAAW,EAAE,IAAI,KAAK,KAAK,UAAU,EAAE,MAAM,CAAC,WAAM,KAAK,UAAU,EAAE,MAAM,CAAC;AAAA,UAC9E;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAKA,UAAM,aAAa,OAAO,OAAO,MAAM;AACvC,UAAM,UAAU,OAAO,qBAAqB,UAAU;AACtD,UAAM,QACJ,QAAQ,cAAc,SAAS,QAAQ,cAAc,SAAS,QAAQ,WAAW;AACnF,QAAI,QAAQ,GAAG;AACb,YAAM,WAAW,cAAc,iBAAiB,KAAK,KAAK,CAAC;AAC3D,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,6CAA6C;AACzD,YAAM,QAAQ,OAAO,2BAA2B,OAAO;AACvD,YAAM,UAAU,SAAS,aAAa,KAAK;AAC3C,iBAAW,QAAQ,QAAQ,MAAM,IAAI,EAAG,SAAQ,IAAI,OAAO,IAAI;AAC/D,UAAI,WAAW,WAAW,WAAW,aAAa;AAChD,gBAAQ,MAAM,OAAO,SAAS,cAAc,cAAc,WAAW,WAAW,CAAC;AAAA,MACnF;AAAA,IACF;AAKA,QAAI,KAAK,SAAS;AAChB,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,iBAAiB;AAC7B,UAAI;AACF,cAAM,aAAa,mBAAmB,QAAQ,UAAU;AACxD,cAAM,QAAQ,MAAM,GAAG;AAAA,UACrB;AAAA,YACE,EAAE,MAAM,UAAU,SAAS,cAAc;AAAA,YACzC,EAAE,MAAM,QAAQ,SAAS,WAAW;AAAA,UACtC;AAAA,UACA,EAAE,SAAS,kBAAkB;AAAA,QAC/B;AACA,mBAAW,QAAQ,MAAM,KAAK,MAAM,IAAI,EAAG,SAAQ,IAAI,OAAO,IAAI;AAAA,MACpE,SAAS,KAAK;AACZ,gBAAQ,MAAM,0BAA0B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AACzF,gBAAQ;AAAA,UACN;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAMA,QAAI,KAAK,gBAAgB,QAAW;AAClC,YAAM;AAAA,QACJ;AAAA,QACA,KAAK,gBAAgB,OAAO,SAAY,OAAO,KAAK,WAAW;AAAA,MACjE;AAAA,IACF;AAOA,QAAI,KAAK,UAAU;AACjB,YAAM,aAAa,OAAO,KAAK,WAAW,OAAO,EAAE,YAAY;AAC/D,UAAI,eAAe,WAAW,eAAe,WAAW;AACtD,cAAM,IAAI;AAAA,UACR,iDAAiD,KAAK,OAAO;AAAA,QAC/D;AAAA,MACF;AACA,YAAM,UAAU;AAChB,YAAM,iBAAiB,UAAU,cAAc,MAAM;AACrD,YAAM,gBAAgB,UAAU,gBAAgB,cAAc;AAC9D,UAAI,cAAc,gBAAgB,GAAG;AACnC,gBAAQ,IAAI,EAAE;AACd,gBAAQ;AAAA,UACN,gCAAgC,OAAO,YAAO,cAAc,aAAa,wBAAwB,cAAc,MAAM,qBAAqB,cAAc,QAAQ,uBAAuB,cAAc,gBAAgB,MAAM;AAAA,QAC7N;AACA,mBAAW,KAAK,gBAAgB;AAC9B,qBAAW,KAAK,EAAE,SAAS;AACzB,kBAAM,MAAM,EAAE,aAAa,UAAU,UAAU;AAC/C,oBAAQ;AAAA,cACN,KAAK,GAAG,KAAK,EAAE,GAAG,aAAQ,EAAE,iBAAiB,GAAG,MAAM,EAAE,iBAAiB,IAAI;AAAA,YAC/E;AAAA,UACF;AAAA,QACF;AACA,cAAM,cAAc,cAAc,SAAS;AAC3C,cAAM,gBAAgB,YAAY,aAAa,cAAc,WAAW;AACxE,YAAI,aAAa;AACf,kBAAQ;AAAA,YACN,kCAA6B,cAAc,MAAM;AAAA,UACnD;AACA,kBAAQ,WAAW;AAAA,QACrB,WAAW,eAAe;AACxB,kBAAQ;AAAA,YACN,8DAAyD,cAAc,QAAQ;AAAA,UACjF;AACA,kBAAQ,WAAW;AAAA,QACrB;AAAA,MACF,OAAO;AACL,gBAAQ,IAAI,qEAAgE;AAAA,MAC9E;AAAA,IACF;AAAA,EACF,CAAC;AACD,uBAAqB,KAAK;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,SAAO;AACT;AASA,eAAe,gBACb,QACA,cACe;AACf,QAAM,WAAW,UAAU,cAAc,MAAM;AAC/C,QAAM,UAAU,UAAU,gBAAgB,QAAQ;AAClD,QAAM,OAA6B;AAAA,IACjC,SAAS,UAAU;AAAA,IACnB,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,QAAQ,GAAG,OAAO,OAAO,IAAI,IAAI,OAAO,OAAO,KAAK;AAAA,IACpD,QAAQ,GAAG,OAAO,OAAO,IAAI,IAAI,OAAO,OAAO,KAAK;AAAA,IACpD;AAAA,IACA;AAAA,EACF;AACA,QAAM,UAAU,eAAe,KAAK,QAAQ,YAAY,IAAI,KAAK,QAAQ,QAAQ,aAAa;AAC9F,QAAM,GAAG,MAAM,KAAK,QAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;AACzD,QAAM,GAAG,UAAU,SAAS,UAAU,oBAAoB,IAAI,CAAC;AAC/D,UAAQ;AAAA,IACN,SAAS,OAAO,WAAM,QAAQ,aAAa,gBAAgB,QAAQ,MAAM,YAAY,QAAQ,QAAQ;AAAA,EACvG;AACF;AAEO,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAOtB,SAAS,mBACd,QAUA,YAMQ;AACR,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,WAAW,OAAO,OAAO,KAAK,EAAE;AAC3C,QAAM,KAAK,WAAW,OAAO,OAAO,KAAK,EAAE;AAC3C,QAAM;AAAA,IACJ,aAAa,OAAO,QAAQ,KAAK,YAAY,OAAO,QAAQ,OAAO,cAAc,OAAO,QAAQ,QAAQ;AAAA,EAC1G;AACA,QAAM;AAAA,IACJ,WAAW,WAAW,cAAc,MAAM,mBAAmB,WAAW,YAAY,MAAM,iBAAiB,WAAW,UAAU,MAAM,eAAe,WAAW,SAAS,MAAM;AAAA,EACjL;AACA,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,qBAAqB;AAChC,QAAM,UAAU,OAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,MAAM,GAAG,EAAE;AAChF,aAAW,KAAK,SAAS;AACvB,UAAM,KAAK,KAAK,EAAE,IAAI,IAAI,EAAE,SAAS,UAAU,IAAI,EAAE,SAAS,GAAG,EAAE;AAAA,EACrE;AACA,MAAI,OAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,SAAS,IAAI;AACpE,UAAM;AAAA,MACJ,aAAQ,OAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,SAAS,EAAE;AAAA,IAC1E;AAAA,EACF;AACA,QAAM,KAAK,EAAE;AACb,QAAM;AAAA,IACJ;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,sBACP,QAMA,YAQQ;AACR,QAAM,QAAkB,CAAC;AACzB,QAAM;AAAA,IACJ,cAAc,OAAO,OAAO,IAAI,IAAI,OAAO,OAAO,KAAK,WAAM,OAAO,OAAO,IAAI,IAAI,OAAO,OAAO,KAAK;AAAA,EACxG;AACA,QAAM,KAAK,EAAE;AACb,QAAM;AAAA,IACJ,iBAAiB,OAAO,QAAQ,KAAK,gBAAa,OAAO,QAAQ,OAAO,kBAAe,OAAO,QAAQ,QAAQ,mBAAgB,OAAO,QAAQ,SAAS;AAAA,EACxJ;AACA,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,aAAa;AACxB,QAAM,KAAK,8BAAuB,WAAW,cAAc,MAAM,EAAE;AACnE,QAAM,KAAK,+BAAwB,WAAW,YAAY,MAAM,EAAE;AAClE,QAAM,KAAK,4BAAuB,WAAW,UAAU,MAAM,EAAE;AAC/D,QAAM,KAAK,6BAAwB,WAAW,SAAS,MAAM,EAAE;AAC/D,MAAI,WAAW,SAAS;AACtB,UAAM,KAAK,EAAE;AACb,UAAM;AAAA,MACJ,kBAAkB,WAAW,eAAe,sCAAsC;AAAA,IACpF;AAAA,EACF;AACA,QAAM,KAAK,EAAE;AACb,QAAM,UAAU,OAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,WAAW;AACnE,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,KAAK,4BAA4B;AACvC,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AACA,QAAM,KAAK,uBAAuB;AAClC,QAAM,KAAK,qBAAqB;AAChC,aAAW,KAAK,SAAS;AACvB,UAAM,KAAK,KAAK,EAAE,IAAI,QAAQ,EAAE,SAAS,UAAU,UAAU,EAAE,SAAS,GAAG,MAAM;AAAA,EACnF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;","names":["assessment"]}
|