@rawsql-ts/ztd-cli 0.20.0 → 0.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/LICENSE +21 -0
  2. package/dist/commands/agents.d.ts +2 -0
  3. package/dist/commands/agents.js +68 -0
  4. package/dist/commands/agents.js.map +1 -0
  5. package/dist/commands/checkContract.d.ts +46 -0
  6. package/dist/commands/checkContract.js +359 -0
  7. package/dist/commands/checkContract.js.map +1 -0
  8. package/dist/commands/connectionOptions.d.ts +12 -0
  9. package/dist/commands/connectionOptions.js +22 -0
  10. package/dist/commands/connectionOptions.js.map +1 -0
  11. package/dist/commands/ddl.d.ts +7 -0
  12. package/dist/commands/ddl.js +145 -0
  13. package/dist/commands/ddl.js.map +1 -0
  14. package/dist/commands/describe.d.ts +23 -0
  15. package/dist/commands/describe.js +399 -0
  16. package/dist/commands/describe.js.map +1 -0
  17. package/dist/commands/diff.d.ts +24 -0
  18. package/dist/commands/diff.js +73 -0
  19. package/dist/commands/diff.js.map +1 -0
  20. package/dist/commands/genEntities.d.ts +14 -0
  21. package/dist/commands/genEntities.js +58 -0
  22. package/dist/commands/genEntities.js.map +1 -0
  23. package/dist/commands/init.d.ts +104 -0
  24. package/dist/commands/init.js +1480 -0
  25. package/dist/commands/init.js.map +1 -0
  26. package/dist/commands/lint.d.ts +89 -0
  27. package/dist/commands/lint.js +501 -0
  28. package/dist/commands/lint.js.map +1 -0
  29. package/dist/commands/modelGen.d.ts +60 -0
  30. package/dist/commands/modelGen.js +572 -0
  31. package/dist/commands/modelGen.js.map +1 -0
  32. package/dist/commands/options.d.ts +9 -0
  33. package/dist/commands/options.js +48 -0
  34. package/dist/commands/options.js.map +1 -0
  35. package/dist/commands/perf.d.ts +9 -0
  36. package/dist/commands/perf.js +374 -0
  37. package/dist/commands/perf.js.map +1 -0
  38. package/dist/commands/pull.d.ts +21 -0
  39. package/dist/commands/pull.js +115 -0
  40. package/dist/commands/pull.js.map +1 -0
  41. package/dist/commands/query.d.ts +9 -0
  42. package/dist/commands/query.js +377 -0
  43. package/dist/commands/query.js.map +1 -0
  44. package/dist/commands/testEvidence.d.ts +237 -0
  45. package/dist/commands/testEvidence.js +1220 -0
  46. package/dist/commands/testEvidence.js.map +1 -0
  47. package/dist/commands/ztdConfig.d.ts +30 -0
  48. package/dist/commands/ztdConfig.js +224 -0
  49. package/dist/commands/ztdConfig.js.map +1 -0
  50. package/dist/commands/ztdConfigCommand.d.ts +18 -0
  51. package/dist/commands/ztdConfigCommand.js +268 -0
  52. package/dist/commands/ztdConfigCommand.js.map +1 -0
  53. package/dist/index.d.ts +4 -0
  54. package/dist/index.js +127 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/perf/benchmark.d.ts +277 -0
  57. package/dist/perf/benchmark.js +2186 -0
  58. package/dist/perf/benchmark.js.map +1 -0
  59. package/dist/perf/sandbox.d.ts +73 -0
  60. package/dist/perf/sandbox.js +492 -0
  61. package/dist/perf/sandbox.js.map +1 -0
  62. package/dist/query/analysis.d.ts +20 -0
  63. package/dist/query/analysis.js +192 -0
  64. package/dist/query/analysis.js.map +1 -0
  65. package/dist/query/analyzeColumnUsage.d.ts +10 -0
  66. package/dist/query/analyzeColumnUsage.js +451 -0
  67. package/dist/query/analyzeColumnUsage.js.map +1 -0
  68. package/dist/query/analyzeTableUsage.d.ts +10 -0
  69. package/dist/query/analyzeTableUsage.js +318 -0
  70. package/dist/query/analyzeTableUsage.js.map +1 -0
  71. package/dist/query/execute.d.ts +40 -0
  72. package/dist/query/execute.js +784 -0
  73. package/dist/query/execute.js.map +1 -0
  74. package/dist/query/format.d.ts +1 -0
  75. package/dist/query/format.js +9 -0
  76. package/dist/query/format.js.map +1 -0
  77. package/dist/query/lint.d.ts +29 -0
  78. package/dist/query/lint.js +340 -0
  79. package/dist/query/lint.js.map +1 -0
  80. package/dist/query/location.d.ts +18 -0
  81. package/dist/query/location.js +204 -0
  82. package/dist/query/location.js.map +1 -0
  83. package/dist/query/patch.d.ts +21 -0
  84. package/dist/query/patch.js +151 -0
  85. package/dist/query/patch.js.map +1 -0
  86. package/dist/query/planner.d.ts +31 -0
  87. package/dist/query/planner.js +134 -0
  88. package/dist/query/planner.js.map +1 -0
  89. package/dist/query/report.d.ts +7 -0
  90. package/dist/query/report.js +19 -0
  91. package/dist/query/report.js.map +1 -0
  92. package/dist/query/scalarFilterAnalysis.d.ts +6 -0
  93. package/dist/query/scalarFilterAnalysis.js +212 -0
  94. package/dist/query/scalarFilterAnalysis.js.map +1 -0
  95. package/dist/query/slice.d.ts +17 -0
  96. package/dist/query/slice.js +204 -0
  97. package/dist/query/slice.js.map +1 -0
  98. package/dist/query/structure.d.ts +24 -0
  99. package/dist/query/structure.js +135 -0
  100. package/dist/query/structure.js.map +1 -0
  101. package/dist/query/targets.d.ts +2 -0
  102. package/dist/query/targets.js +6 -0
  103. package/dist/query/targets.js.map +1 -0
  104. package/dist/query/types.d.ts +97 -0
  105. package/dist/query/types.js +3 -0
  106. package/dist/query/types.js.map +1 -0
  107. package/dist/specs/sql/activeOrders.catalog.d.ts +12 -0
  108. package/dist/specs/sql/activeOrders.catalog.js +36 -0
  109. package/dist/specs/sql/activeOrders.catalog.js.map +1 -0
  110. package/dist/specs/sql/usersList.catalog.d.ts +8 -0
  111. package/dist/specs/sql/usersList.catalog.js +14 -0
  112. package/dist/specs/sql/usersList.catalog.js.map +1 -0
  113. package/dist/specs/sqlCatalogDefinition.d.ts +20 -0
  114. package/dist/specs/sqlCatalogDefinition.js +10 -0
  115. package/dist/specs/sqlCatalogDefinition.js.map +1 -0
  116. package/dist/utils/agentCli.d.ts +23 -0
  117. package/dist/utils/agentCli.js +84 -0
  118. package/dist/utils/agentCli.js.map +1 -0
  119. package/dist/utils/agentSafety.d.ts +4 -0
  120. package/dist/utils/agentSafety.js +50 -0
  121. package/dist/utils/agentSafety.js.map +1 -0
  122. package/dist/utils/agents.d.ts +31 -0
  123. package/dist/utils/agents.js +362 -0
  124. package/dist/utils/agents.js.map +1 -0
  125. package/dist/utils/collectSqlFiles.d.ts +9 -0
  126. package/dist/utils/collectSqlFiles.js +58 -0
  127. package/dist/utils/collectSqlFiles.js.map +1 -0
  128. package/dist/utils/connectionSummary.d.ts +3 -0
  129. package/dist/utils/connectionSummary.js +29 -0
  130. package/dist/utils/connectionSummary.js.map +1 -0
  131. package/dist/utils/dbConnection.d.ts +31 -0
  132. package/dist/utils/dbConnection.js +151 -0
  133. package/dist/utils/dbConnection.js.map +1 -0
  134. package/dist/utils/fs.d.ts +1 -0
  135. package/dist/utils/fs.js +12 -0
  136. package/dist/utils/fs.js.map +1 -0
  137. package/dist/utils/modelGenBinder.d.ts +8 -0
  138. package/dist/utils/modelGenBinder.js +31 -0
  139. package/dist/utils/modelGenBinder.js.map +1 -0
  140. package/dist/utils/modelGenRender.d.ts +29 -0
  141. package/dist/utils/modelGenRender.js +158 -0
  142. package/dist/utils/modelGenRender.js.map +1 -0
  143. package/dist/utils/modelGenScanner.d.ts +24 -0
  144. package/dist/utils/modelGenScanner.js +196 -0
  145. package/dist/utils/modelGenScanner.js.map +1 -0
  146. package/dist/utils/modelProbe.d.ts +14 -0
  147. package/dist/utils/modelProbe.js +121 -0
  148. package/dist/utils/modelProbe.js.map +1 -0
  149. package/dist/utils/normalizePulledSchema.d.ts +12 -0
  150. package/dist/utils/normalizePulledSchema.js +213 -0
  151. package/dist/utils/normalizePulledSchema.js.map +1 -0
  152. package/dist/utils/optionalDependencies.d.ts +43 -0
  153. package/dist/utils/optionalDependencies.js +134 -0
  154. package/dist/utils/optionalDependencies.js.map +1 -0
  155. package/dist/utils/pgDump.d.ts +12 -0
  156. package/dist/utils/pgDump.js +58 -0
  157. package/dist/utils/pgDump.js.map +1 -0
  158. package/dist/utils/queryFingerprint.d.ts +14 -0
  159. package/dist/utils/queryFingerprint.js +34 -0
  160. package/dist/utils/queryFingerprint.js.map +1 -0
  161. package/dist/utils/sqlCatalogDiscovery.d.ts +44 -0
  162. package/dist/utils/sqlCatalogDiscovery.js +166 -0
  163. package/dist/utils/sqlCatalogDiscovery.js.map +1 -0
  164. package/dist/utils/sqlCatalogStatements.d.ts +20 -0
  165. package/dist/utils/sqlCatalogStatements.js +23 -0
  166. package/dist/utils/sqlCatalogStatements.js.map +1 -0
  167. package/dist/utils/sqlLintHelpers.d.ts +18 -0
  168. package/dist/utils/sqlLintHelpers.js +270 -0
  169. package/dist/utils/sqlLintHelpers.js.map +1 -0
  170. package/dist/utils/telemetry.d.ts +71 -0
  171. package/dist/utils/telemetry.js +597 -0
  172. package/dist/utils/telemetry.js.map +1 -0
  173. package/dist/utils/typeMapper.d.ts +4 -0
  174. package/dist/utils/typeMapper.js +79 -0
  175. package/dist/utils/typeMapper.js.map +1 -0
  176. package/dist/utils/ztdProjectConfig.d.ts +41 -0
  177. package/dist/utils/ztdProjectConfig.js +182 -0
  178. package/dist/utils/ztdProjectConfig.js.map +1 -0
  179. package/package.json +19 -20
@@ -0,0 +1,1220 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TestEvidenceRuntimeError = void 0;
7
+ exports.resolveTestEvidenceExitCode = resolveTestEvidenceExitCode;
8
+ exports.registerTestEvidenceCommand = registerTestEvidenceCommand;
9
+ exports.runTestEvidenceSpecification = runTestEvidenceSpecification;
10
+ exports.formatTestEvidenceOutput = formatTestEvidenceOutput;
11
+ exports.stableStringify = stableStringify;
12
+ exports.buildTestEvidencePrDiff = buildTestEvidencePrDiff;
13
+ exports.formatTestEvidencePrMarkdown = formatTestEvidencePrMarkdown;
14
+ exports.runTestEvidencePr = runTestEvidencePr;
15
+ exports.applyEvidenceOutputControls = applyEvidenceOutputControls;
16
+ exports.formatTestDocumentationOutput = formatTestDocumentationOutput;
17
+ const node_fs_1 = require("node:fs");
18
+ const node_module_1 = require("node:module");
19
+ const node_path_1 = __importDefault(require("node:path"));
20
+ const node_child_process_1 = require("node:child_process");
21
+ const test_evidence_core_1 = require("@rawsql-ts/test-evidence-core");
22
+ const test_evidence_renderer_md_1 = require("@rawsql-ts/test-evidence-renderer-md");
23
+ const sqlCatalogDiscovery_1 = require("../utils/sqlCatalogDiscovery");
24
+ const agentCli_1 = require("../utils/agentCli");
25
+ /**
26
+ * Runtime/configuration error for test evidence command (maps to exit code 2).
27
+ */
28
+ class TestEvidenceRuntimeError extends Error {
29
+ constructor(message, options) {
30
+ super(message);
31
+ this.exitCode = 2;
32
+ this.name = 'TestEvidenceRuntimeError';
33
+ this.code = options === null || options === void 0 ? void 0 : options.code;
34
+ }
35
+ }
36
+ exports.TestEvidenceRuntimeError = TestEvidenceRuntimeError;
37
+ /**
38
+ * Resolve command exit code for test evidence generation.
39
+ * @param args.result Completed generation result when execution succeeded.
40
+ * @param args.error Error thrown while generating evidence.
41
+ * @returns 0 when generation succeeded, 2 for runtime/configuration errors, 1 for other failures.
42
+ */
43
+ function resolveTestEvidenceExitCode(args) {
44
+ if (args.error) {
45
+ return args.error instanceof TestEvidenceRuntimeError ? 2 : 1;
46
+ }
47
+ if (!args.result) {
48
+ return 2;
49
+ }
50
+ return 0;
51
+ }
52
+ /**
53
+ * Register `ztd evidence` command on the CLI root program.
54
+ */
55
+ function registerTestEvidenceCommand(program) {
56
+ const evidenceCommand = program
57
+ .command('evidence')
58
+ .alias('test-evidence')
59
+ .description('Generate deterministic test specification evidence artifacts')
60
+ .option('--mode <mode>', 'Evidence mode (specification)', 'specification')
61
+ .option('--format <format>', 'Output format (json|markdown|both)', 'both')
62
+ .option('--out-dir <path>', 'Output directory', '.ztd/test-evidence')
63
+ .option('--specs-dir <path>', 'Override SQL catalog specs directory (default: src/catalog/specs)')
64
+ .option('--tests-dir <path>', 'Override tests directory (default: tests)')
65
+ .option('--spec-module <path>', 'Explicit evidence module path (default: tests/specs/index)')
66
+ .option('--json <payload>', 'Pass evidence options as a JSON object')
67
+ .option('--summary-only', 'Write only summary counts without catalog or case detail payloads')
68
+ .option('--limit <count>', 'Limit returned catalogs and cases in generated artifacts')
69
+ .action((options) => {
70
+ var _a;
71
+ try {
72
+ const merged = options.json ? { ...options, ...(0, agentCli_1.parseJsonPayload)(options.json, '--json') } : options;
73
+ const mode = normalizeMode(String(merged.mode));
74
+ const format = normalizeFormat(String(merged.format));
75
+ const report = applyEvidenceOutputControls(runTestEvidenceSpecification({
76
+ mode,
77
+ rootDir: process.env.ZTD_PROJECT_ROOT,
78
+ specsDir: merged.specsDir,
79
+ testsDir: merged.testsDir,
80
+ specModule: merged.specModule
81
+ }), {
82
+ summaryOnly: Boolean(merged.summaryOnly),
83
+ limit: normalizeEvidenceLimit(merged.limit)
84
+ });
85
+ const sourceRootDir = node_path_1.default.resolve((_a = process.env.ZTD_PROJECT_ROOT) !== null && _a !== void 0 ? _a : process.cwd());
86
+ writeArtifacts({
87
+ report,
88
+ format,
89
+ outDir: node_path_1.default.resolve(process.cwd(), String(merged.outDir)),
90
+ sourceRootDir
91
+ });
92
+ process.exitCode = resolveTestEvidenceExitCode({ result: report });
93
+ }
94
+ catch (error) {
95
+ process.exitCode = resolveTestEvidenceExitCode({ error });
96
+ console.error(error instanceof Error ? error.message : String(error));
97
+ }
98
+ });
99
+ evidenceCommand
100
+ .command('test-doc')
101
+ .description('Generate human-readable Markdown test documentation from ZTD test assets')
102
+ .option('--out <path>', 'Output markdown path', '.ztd/test-evidence/test-documentation.md')
103
+ .option('--specs-dir <path>', 'Override SQL catalog specs directory (default: src/catalog/specs)')
104
+ .option('--tests-dir <path>', 'Override tests directory (default: tests)')
105
+ .option('--spec-module <path>', 'Explicit evidence module path (default: tests/specs/index)')
106
+ .option('--json <payload>', 'Pass test-doc options as a JSON object')
107
+ .action((options) => {
108
+ var _a, _b;
109
+ try {
110
+ const merged = options.json ? { ...options, ...(0, agentCli_1.parseJsonPayload)(options.json, '--json') } : options;
111
+ const report = runTestEvidenceSpecification({
112
+ mode: 'specification',
113
+ rootDir: process.env.ZTD_PROJECT_ROOT,
114
+ specsDir: merged.specsDir,
115
+ testsDir: merged.testsDir,
116
+ specModule: merged.specModule
117
+ });
118
+ const sourceRootDir = node_path_1.default.resolve((_a = process.env.ZTD_PROJECT_ROOT) !== null && _a !== void 0 ? _a : process.cwd());
119
+ writeTestDocumentationArtifact({
120
+ report,
121
+ outPath: node_path_1.default.resolve(process.cwd(), String((_b = merged.out) !== null && _b !== void 0 ? _b : '.ztd/test-evidence/test-documentation.md')),
122
+ sourceRootDir
123
+ });
124
+ process.exitCode = resolveTestEvidenceExitCode({ result: report });
125
+ }
126
+ catch (error) {
127
+ process.exitCode = resolveTestEvidenceExitCode({ error });
128
+ console.error(error instanceof Error ? error.message : String(error));
129
+ }
130
+ });
131
+ evidenceCommand
132
+ .command('pr')
133
+ .description('Generate PR diff evidence from base/head specification JSON projections')
134
+ .option('--base <ref>', 'Base git ref', 'main')
135
+ .option('--head <ref>', 'Head git ref', 'HEAD')
136
+ .option('--base-mode <mode>', 'Base resolution mode (merge-base|ref)', 'merge-base')
137
+ .option('--allow-empty-base', 'Allow empty base evidence when head has catalogs')
138
+ .option('--removed-detail <level>', 'Removed case detail level (none|input|full)', 'input')
139
+ .option('--out-dir <path>', 'Output directory', 'artifacts/test-evidence')
140
+ .option('--specs-dir <path>', 'Override SQL catalog specs directory (default: src/catalog/specs)')
141
+ .option('--tests-dir <path>', 'Override tests directory (default: tests)')
142
+ .option('--spec-module <path>', 'Explicit evidence module path (default: tests/specs/index)')
143
+ .option('--json <payload>', 'Pass PR evidence options as a JSON object')
144
+ .option('--summary-only', 'Write PR evidence with summary-only base/head projections')
145
+ .option('--limit <count>', 'Limit returned catalogs and cases in base/head projections')
146
+ .action((options) => {
147
+ var _a, _b, _c, _d, _e;
148
+ try {
149
+ const merged = options.json ? { ...options, ...(0, agentCli_1.parseJsonPayload)(options.json, '--json') } : options;
150
+ const output = runTestEvidencePr({
151
+ baseRef: String((_a = merged.base) !== null && _a !== void 0 ? _a : 'main'),
152
+ headRef: String((_b = merged.head) !== null && _b !== void 0 ? _b : 'HEAD'),
153
+ baseMode: normalizeBaseMode(String((_c = merged.baseMode) !== null && _c !== void 0 ? _c : 'merge-base')),
154
+ allowEmptyBase: Boolean(merged.allowEmptyBase),
155
+ removedDetail: normalizeRemovedDetail(String((_d = merged.removedDetail) !== null && _d !== void 0 ? _d : 'input')),
156
+ outDir: String((_e = merged.outDir) !== null && _e !== void 0 ? _e : 'artifacts/test-evidence'),
157
+ rootDir: process.env.ZTD_PROJECT_ROOT,
158
+ specsDir: merged.specsDir,
159
+ testsDir: merged.testsDir,
160
+ specModule: merged.specModule,
161
+ summaryOnly: Boolean(merged.summaryOnly),
162
+ limit: normalizeEvidenceLimit(merged.limit)
163
+ });
164
+ process.exitCode = resolveTestEvidenceExitCode({ result: output.headReport });
165
+ }
166
+ catch (error) {
167
+ process.exitCode = resolveTestEvidenceExitCode({ error });
168
+ console.error(error instanceof Error ? error.message : String(error));
169
+ }
170
+ });
171
+ }
172
+ function normalizeMode(mode) {
173
+ const normalized = mode.trim().toLowerCase();
174
+ if (normalized !== 'specification') {
175
+ throw new TestEvidenceRuntimeError(`Unsupported mode: ${mode}`);
176
+ }
177
+ return 'specification';
178
+ }
179
+ function normalizeFormat(format) {
180
+ const normalized = format.trim().toLowerCase();
181
+ if (normalized === 'json' || normalized === 'markdown' || normalized === 'both') {
182
+ return normalized;
183
+ }
184
+ throw new TestEvidenceRuntimeError(`Unsupported format: ${format}`);
185
+ }
186
+ function normalizeBaseMode(mode) {
187
+ const normalized = mode.trim().toLowerCase();
188
+ if (normalized === 'merge-base' || normalized === 'ref') {
189
+ return normalized;
190
+ }
191
+ throw new TestEvidenceRuntimeError(`Unsupported base-mode: ${mode}`);
192
+ }
193
+ function normalizeRemovedDetail(level) {
194
+ const normalized = level.trim().toLowerCase();
195
+ if (normalized === 'none' || normalized === 'input' || normalized === 'full') {
196
+ return normalized;
197
+ }
198
+ throw new TestEvidenceRuntimeError(`Unsupported removed-detail: ${level}`);
199
+ }
200
+ /**
201
+ * Build deterministic specification evidence from SQL catalog specs and test-case catalog exports.
202
+ */
203
+ function runTestEvidenceSpecification(options) {
204
+ var _a;
205
+ const root = node_path_1.default.resolve((_a = options.rootDir) !== null && _a !== void 0 ? _a : process.cwd());
206
+ const specsDir = options.specsDir ? node_path_1.default.resolve(root, options.specsDir) : node_path_1.default.resolve(root, 'src', 'catalog', 'specs');
207
+ const testsDir = options.testsDir ? node_path_1.default.resolve(root, options.testsDir) : node_path_1.default.resolve(root, 'tests');
208
+ const sqlSpecFiles = (0, node_fs_1.existsSync)(specsDir) ? (0, sqlCatalogDiscovery_1.walkSqlCatalogSpecFiles)(specsDir) : [];
209
+ const evidenceModule = loadEvidenceModule(root, testsDir, options.specModule);
210
+ const testCaseCatalogFiles = (0, node_fs_1.existsSync)(testsDir) ? walkFiles(testsDir, isTestCaseCatalogFile) : [];
211
+ const legacyTestCases = evidenceModule ? [] : testCaseCatalogFiles.flatMap((filePath) => loadTestCaseCatalogEvidence(root, filePath));
212
+ const testCaseCatalogs = evidenceModule
213
+ ? readTestCaseCatalogsFromModule(evidenceModule)
214
+ .map((catalog) => ({
215
+ ...catalog,
216
+ cases: [...catalog.cases].sort((a, b) => a.id.localeCompare(b.id))
217
+ }))
218
+ .sort((a, b) => a.id.localeCompare(b.id))
219
+ : [];
220
+ const testCasesFromModule = flattenTestCaseCatalogs(testCaseCatalogs);
221
+ const sqlCaseCatalogsFromModule = evidenceModule ? readSqlCaseCatalogsFromModule(evidenceModule) : [];
222
+ if (sqlSpecFiles.length === 0 && testCasesFromModule.length === 0 && legacyTestCases.length === 0 && sqlCaseCatalogsFromModule.length === 0) {
223
+ throw new TestEvidenceRuntimeError(`No catalog specs or test-case evidence exports were found. Checked specsDir=${specsDir}, testsDir=${testsDir}`, { code: 'NO_SPECS_FOUND' });
224
+ }
225
+ const sqlCatalogs = sqlSpecFiles
226
+ .flatMap((filePath) => (0, sqlCatalogDiscovery_1.loadSqlCatalogSpecsFromFile)(filePath, (message) => new TestEvidenceRuntimeError(message)))
227
+ .map((loaded) => toSqlEvidence(root, loaded))
228
+ .sort((a, b) => a.id.localeCompare(b.id) || a.specFile.localeCompare(b.specFile));
229
+ const testCases = [...testCasesFromModule, ...legacyTestCases]
230
+ .sort((a, b) => a.id.localeCompare(b.id) || a.filePath.localeCompare(b.filePath));
231
+ const sqlCaseCatalogs = sqlCaseCatalogsFromModule
232
+ .sort((a, b) => a.id.localeCompare(b.id));
233
+ return {
234
+ schemaVersion: 1,
235
+ mode: options.mode,
236
+ summary: {
237
+ sqlCatalogCount: sqlCatalogs.length,
238
+ sqlCaseCatalogCount: sqlCaseCatalogs.length,
239
+ testCaseCount: testCases.length,
240
+ specFilesScanned: sqlSpecFiles.length,
241
+ testFilesScanned: evidenceModule ? 1 : testCaseCatalogFiles.length
242
+ },
243
+ sqlCatalogs,
244
+ sqlCaseCatalogs,
245
+ testCaseCatalogs,
246
+ testCases
247
+ };
248
+ }
249
+ /**
250
+ * Render deterministic JSON or Markdown output text.
251
+ */
252
+ function formatTestEvidenceOutput(report, format, context) {
253
+ var _a;
254
+ if (format === 'json') {
255
+ return `${JSON.stringify(report, null, 2)}\n`;
256
+ }
257
+ if ((_a = report.display) === null || _a === void 0 ? void 0 : _a.summaryOnly) {
258
+ const lines = [
259
+ '# Test Specification Summary',
260
+ '',
261
+ `- schemaVersion: ${report.schemaVersion}`,
262
+ `- mode: ${report.mode}`,
263
+ `- sqlCatalogCount: ${report.summary.sqlCatalogCount}`,
264
+ `- sqlCaseCatalogCount: ${report.summary.sqlCaseCatalogCount}`,
265
+ `- testCaseCount: ${report.summary.testCaseCount}`,
266
+ `- specFilesScanned: ${report.summary.specFilesScanned}`,
267
+ `- testFilesScanned: ${report.summary.testFilesScanned}`,
268
+ `- truncated: ${report.display.truncated}`,
269
+ report.display.limit !== undefined ? `- limit: ${report.display.limit}` : ''
270
+ ].filter(Boolean);
271
+ return `${lines.join('\n')}\n`;
272
+ }
273
+ const model = (0, test_evidence_core_1.buildSpecificationModel)(report);
274
+ return `${(0, test_evidence_renderer_md_1.renderSpecificationMarkdown)(model, {
275
+ definitionLinks: resolveDefinitionLinkOptions(context)
276
+ })}\n`;
277
+ }
278
+ /**
279
+ * Stable stringify that sorts object keys recursively for deterministic fingerprinting.
280
+ */
281
+ function stableStringify(value) {
282
+ return (0, test_evidence_core_1.stableStringify)(value);
283
+ }
284
+ /**
285
+ * Build deterministic PR diff JSON from base/head specification reports.
286
+ */
287
+ function buildTestEvidencePrDiff(args) {
288
+ return (0, test_evidence_core_1.buildDiffJson)({
289
+ base: {
290
+ ref: args.base.ref,
291
+ sha: args.base.sha,
292
+ previewJson: args.base.report
293
+ },
294
+ head: {
295
+ ref: args.head.ref,
296
+ sha: args.head.sha,
297
+ previewJson: args.head.report
298
+ },
299
+ baseMode: args.baseMode
300
+ });
301
+ }
302
+ /**
303
+ * Render PR-focused markdown from diff JSON.
304
+ */
305
+ function formatTestEvidencePrMarkdown(diff, options) {
306
+ return (0, test_evidence_renderer_md_1.renderDiffMarkdown)(diff, {
307
+ definitionLinks: resolveDefinitionLinkOptions({
308
+ markdownPath: options === null || options === void 0 ? void 0 : options.markdownPath,
309
+ sourceRootDir: options === null || options === void 0 ? void 0 : options.sourceRootDir
310
+ })
311
+ });
312
+ }
313
+ function resolveDefinitionLinkOptions(context) {
314
+ var _a, _b, _c;
315
+ const serverUrl = (_a = process.env.GITHUB_SERVER_URL) === null || _a === void 0 ? void 0 : _a.trim();
316
+ const repository = (_b = process.env.GITHUB_REPOSITORY) === null || _b === void 0 ? void 0 : _b.trim();
317
+ const ref = (_c = process.env.GITHUB_SHA) === null || _c === void 0 ? void 0 : _c.trim();
318
+ if (serverUrl && repository && ref) {
319
+ return {
320
+ mode: 'github',
321
+ github: {
322
+ serverUrl,
323
+ repository,
324
+ ref
325
+ }
326
+ };
327
+ }
328
+ if ((context === null || context === void 0 ? void 0 : context.markdownPath) && (context === null || context === void 0 ? void 0 : context.sourceRootDir)) {
329
+ return {
330
+ mode: 'path',
331
+ path: {
332
+ markdownDir: node_path_1.default.dirname(context.markdownPath),
333
+ sourceRootDir: context.sourceRootDir
334
+ }
335
+ };
336
+ }
337
+ return { mode: 'path' };
338
+ }
339
+ /**
340
+ * Generate base/head reports, compute PR diff JSON, and write PR artifacts.
341
+ */
342
+ function runTestEvidencePr(options) {
343
+ var _a;
344
+ const root = node_path_1.default.resolve((_a = options.rootDir) !== null && _a !== void 0 ? _a : process.cwd());
345
+ const tempRoot = node_path_1.default.resolve(root, '.tmp', 'test-evidence-worktree');
346
+ (0, node_fs_1.mkdirSync)(tempRoot, { recursive: true });
347
+ const createdWorktrees = [];
348
+ const resolvedHeadSha = resolveGitSha(root, options.headRef);
349
+ const resolvedBaseSha = options.baseMode === 'merge-base'
350
+ ? resolveGitMergeBase(root, options.baseRef, options.headRef)
351
+ : resolveGitSha(root, options.baseRef);
352
+ const currentHeadSha = resolveGitSha(root, 'HEAD');
353
+ try {
354
+ const headMaterialized = materializeEvidenceForRef({
355
+ repoRoot: root,
356
+ ref: options.headRef,
357
+ resolvedSha: resolvedHeadSha,
358
+ allowCurrentWorkspace: resolvedHeadSha === currentHeadSha,
359
+ tempRoot,
360
+ createdWorktrees,
361
+ specsDir: options.specsDir,
362
+ testsDir: options.testsDir,
363
+ specModule: options.specModule,
364
+ summaryOnly: options.summaryOnly,
365
+ limit: options.limit
366
+ });
367
+ const baseMaterialized = materializeEvidenceForRef({
368
+ repoRoot: root,
369
+ ref: options.baseRef,
370
+ resolvedSha: resolvedBaseSha,
371
+ allowCurrentWorkspace: resolvedBaseSha === currentHeadSha,
372
+ tempRoot,
373
+ createdWorktrees,
374
+ specsDir: options.specsDir,
375
+ testsDir: options.testsDir,
376
+ specModule: options.specModule,
377
+ summaryOnly: options.summaryOnly,
378
+ limit: options.limit
379
+ });
380
+ console.log(`base preview: ${baseMaterialized.previewJsonPath}`);
381
+ console.log(`head preview: ${headMaterialized.previewJsonPath}`);
382
+ const diff = buildTestEvidencePrDiff({
383
+ base: { ref: options.baseRef, sha: baseMaterialized.sha, report: baseMaterialized.report },
384
+ head: { ref: options.headRef, sha: headMaterialized.sha, report: headMaterialized.report },
385
+ baseMode: options.baseMode
386
+ });
387
+ if (!options.allowEmptyBase && diff.totals.base.catalogs === 0 && diff.totals.head.catalogs > 0) {
388
+ throw new TestEvidenceRuntimeError('Base test evidence is empty.\nThis likely indicates preview generation failed at base ref.\nIf this is intentional, re-run with --allow-empty-base.');
389
+ }
390
+ const outDir = node_path_1.default.resolve(root, options.outDir);
391
+ (0, node_fs_1.mkdirSync)(outDir, { recursive: true });
392
+ const diffJsonPath = node_path_1.default.join(outDir, 'test-specification.pr.json');
393
+ const diffMdPath = node_path_1.default.join(outDir, 'test-specification.pr.md');
394
+ (0, node_fs_1.writeFileSync)(diffJsonPath, `${JSON.stringify(diff, null, 2)}\n`, 'utf8');
395
+ (0, node_fs_1.writeFileSync)(diffMdPath, formatTestEvidencePrMarkdown(diff, {
396
+ removedDetail: options.removedDetail,
397
+ markdownPath: diffMdPath,
398
+ sourceRootDir: root
399
+ }), 'utf8');
400
+ console.log(`wrote: ${diffJsonPath}`);
401
+ console.log(`wrote: ${diffMdPath}`);
402
+ return {
403
+ baseReport: baseMaterialized.report,
404
+ headReport: headMaterialized.report,
405
+ diff
406
+ };
407
+ }
408
+ finally {
409
+ cleanupWorktrees(root, createdWorktrees);
410
+ }
411
+ }
412
+ function writeArtifacts(args) {
413
+ var _a;
414
+ (0, node_fs_1.mkdirSync)(args.outDir, { recursive: true });
415
+ const writtenFiles = [];
416
+ if (args.format === 'json' || args.format === 'both') {
417
+ const jsonPath = node_path_1.default.join(args.outDir, 'test-specification.json');
418
+ (0, node_fs_1.writeFileSync)(jsonPath, formatTestEvidenceOutput(args.report, 'json'), 'utf8');
419
+ writtenFiles.push(jsonPath);
420
+ }
421
+ if (args.format === 'markdown' || args.format === 'both') {
422
+ const markdownPaths = ((_a = args.report.display) === null || _a === void 0 ? void 0 : _a.summaryOnly)
423
+ ? writeSpecificationSummaryMarkdown(args.report, args.outDir)
424
+ : writeSpecificationMarkdownArtifacts(args.report, args.outDir, args.sourceRootDir);
425
+ writtenFiles.push(...markdownPaths);
426
+ }
427
+ for (const filePath of writtenFiles.sort((a, b) => a.localeCompare(b))) {
428
+ console.log(`wrote: ${filePath}`);
429
+ }
430
+ }
431
+ function writeSpecificationSummaryMarkdown(report, outDir) {
432
+ const summaryPath = node_path_1.default.join(outDir, 'test-specification.summary.md');
433
+ (0, node_fs_1.writeFileSync)(summaryPath, formatTestEvidenceOutput(report, 'markdown'), 'utf8');
434
+ return [summaryPath];
435
+ }
436
+ function writeSpecificationMarkdownArtifacts(report, outDir, sourceRootDir) {
437
+ var _a, _b;
438
+ const indexFileName = 'test-specification.index.md';
439
+ const model = (0, test_evidence_core_1.buildSpecificationModel)(report);
440
+ const catalogs = [...model.catalogs].sort((a, b) => a.catalogId.localeCompare(b.catalogId));
441
+ const written = [];
442
+ const catalogRows = [];
443
+ for (const catalog of catalogs) {
444
+ const catalogSlug = toSpecificationSlug(catalog.catalogId);
445
+ const catalogFileName = `test-specification.catalog.${catalogSlug}.md`;
446
+ const catalogPath = node_path_1.default.join(outDir, catalogFileName);
447
+ const catalogDefinitionLinks = resolveDefinitionLinkOptions({ markdownPath: catalogPath, sourceRootDir });
448
+ const catalogLines = [];
449
+ catalogLines.push(`# ${catalog.catalogId} Test Cases`);
450
+ catalogLines.push('');
451
+ catalogLines.push(`- schemaVersion: ${model.schemaVersion}`);
452
+ catalogLines.push(`- index: [Unit Test Index](./${indexFileName})`);
453
+ catalogLines.push(`- title: ${catalog.title}`);
454
+ const definitionPath = (_a = catalog.definition) !== null && _a !== void 0 ? _a : findCatalogDefinitionPath(report, catalog.catalogId);
455
+ catalogLines.push(`- definition: ${formatDefinitionLinkMarkdown(definitionPath, catalogDefinitionLinks)}`);
456
+ if (catalog.description) {
457
+ catalogLines.push(`- description: ${catalog.description}`);
458
+ }
459
+ if (Array.isArray(catalog.refs) && catalog.refs.length > 0) {
460
+ catalogLines.push('- refs:');
461
+ for (const ref of catalog.refs) {
462
+ catalogLines.push(` - [${ref.label}](${ref.url})`);
463
+ }
464
+ }
465
+ catalogLines.push(`- tests: ${catalog.cases.length}`);
466
+ if (catalog.kind === 'sql') {
467
+ catalogLines.push(`- fixtures: ${((_b = catalog.fixtures) !== null && _b !== void 0 ? _b : []).join(', ') || '(none)'}`);
468
+ }
469
+ catalogLines.push('');
470
+ const sortedCases = [...catalog.cases].sort((a, b) => a.id.localeCompare(b.id));
471
+ for (const testCase of sortedCases) {
472
+ catalogLines.push(`## ${testCase.id} - ${testCase.title}`);
473
+ catalogLines.push(`- expected: ${testCase.expected}`);
474
+ catalogLines.push(`- tags: ${formatCaseTags(testCase.tags)}`);
475
+ catalogLines.push(`- focus: ${formatCaseFocus(testCase.focus)}`);
476
+ if (Array.isArray(testCase.refs) && testCase.refs.length > 0) {
477
+ catalogLines.push('- refs:');
478
+ for (const ref of testCase.refs) {
479
+ catalogLines.push(` - [${ref.label}](${ref.url})`);
480
+ }
481
+ }
482
+ catalogLines.push('### input');
483
+ catalogLines.push('```json');
484
+ catalogLines.push(stringifyStablePretty(testCase.input));
485
+ catalogLines.push('```');
486
+ if (testCase.expected === 'throws') {
487
+ catalogLines.push('### error');
488
+ catalogLines.push('```json');
489
+ catalogLines.push(stringifyErrorPretty(testCase.error));
490
+ catalogLines.push('```');
491
+ }
492
+ else {
493
+ catalogLines.push('### output');
494
+ catalogLines.push('```json');
495
+ catalogLines.push(stringifyStablePretty(testCase.output));
496
+ catalogLines.push('```');
497
+ }
498
+ catalogLines.push('');
499
+ }
500
+ catalogLines.push('');
501
+ (0, node_fs_1.writeFileSync)(catalogPath, `${catalogLines.join('\n')}\n`, 'utf8');
502
+ written.push(catalogPath);
503
+ catalogRows.push({
504
+ fileName: catalogFileName,
505
+ catalogId: catalog.catalogId,
506
+ title: catalog.title,
507
+ tests: catalog.cases.length
508
+ });
509
+ }
510
+ const indexPath = node_path_1.default.join(outDir, indexFileName);
511
+ const indexLines = [];
512
+ indexLines.push('# Unit Test Index');
513
+ indexLines.push('');
514
+ indexLines.push(`- catalogs: ${catalogRows.length}`);
515
+ indexLines.push('');
516
+ indexLines.push('## Catalog Files');
517
+ indexLines.push('');
518
+ for (const row of catalogRows.sort((a, b) => a.fileName.localeCompare(b.fileName))) {
519
+ indexLines.push(`- [${row.catalogId}](./${row.fileName})`);
520
+ indexLines.push(` - title: ${row.title}`);
521
+ indexLines.push(` - tests: ${row.tests}`);
522
+ }
523
+ indexLines.push('');
524
+ (0, node_fs_1.writeFileSync)(indexPath, `${indexLines.join('\n')}\n`, 'utf8');
525
+ written.push(indexPath);
526
+ return written;
527
+ }
528
+ function applyEvidenceOutputControls(report, options) {
529
+ const summaryOnly = Boolean(options.summaryOnly);
530
+ const limit = options.limit;
531
+ if (summaryOnly) {
532
+ return {
533
+ ...report,
534
+ sqlCatalogs: [],
535
+ sqlCaseCatalogs: [],
536
+ testCaseCatalogs: [],
537
+ testCases: [],
538
+ display: {
539
+ summaryOnly: true,
540
+ limit,
541
+ truncated: report.sqlCatalogs.length > 0 ||
542
+ report.sqlCaseCatalogs.length > 0 ||
543
+ report.testCaseCatalogs.length > 0 ||
544
+ report.testCases.length > 0
545
+ }
546
+ };
547
+ }
548
+ if (limit === undefined) {
549
+ return report;
550
+ }
551
+ const limited = {
552
+ ...report,
553
+ sqlCatalogs: report.sqlCatalogs.slice(0, limit),
554
+ sqlCaseCatalogs: report.sqlCaseCatalogs.slice(0, limit).map((catalog) => ({
555
+ ...catalog,
556
+ cases: catalog.cases.slice(0, limit)
557
+ })),
558
+ testCaseCatalogs: report.testCaseCatalogs.slice(0, limit).map((catalog) => ({
559
+ ...catalog,
560
+ cases: catalog.cases.slice(0, limit)
561
+ })),
562
+ testCases: report.testCases.slice(0, limit),
563
+ display: {
564
+ summaryOnly: false,
565
+ limit,
566
+ truncated: report.sqlCatalogs.length > limit ||
567
+ report.sqlCaseCatalogs.length > limit ||
568
+ report.testCaseCatalogs.length > limit ||
569
+ report.testCases.length > limit ||
570
+ report.sqlCaseCatalogs.some((catalog) => catalog.cases.length > limit) ||
571
+ report.testCaseCatalogs.some((catalog) => catalog.cases.length > limit)
572
+ }
573
+ };
574
+ return limited;
575
+ }
576
+ function normalizeEvidenceLimit(value) {
577
+ if (value === undefined) {
578
+ return undefined;
579
+ }
580
+ const parsed = Number(value);
581
+ if (!Number.isInteger(parsed) || parsed <= 0) {
582
+ throw new TestEvidenceRuntimeError(`Unsupported limit: ${value}. Use a positive integer.`);
583
+ }
584
+ return parsed;
585
+ }
586
+ function findCatalogDefinitionPath(report, catalogId) {
587
+ const functionCatalog = report.testCaseCatalogs.find((catalog) => catalog.id === catalogId);
588
+ if (functionCatalog === null || functionCatalog === void 0 ? void 0 : functionCatalog.definitionPath) {
589
+ return functionCatalog.definitionPath;
590
+ }
591
+ const sqlCatalog = report.sqlCaseCatalogs.find((catalog) => catalog.id === catalogId);
592
+ if (sqlCatalog === null || sqlCatalog === void 0 ? void 0 : sqlCatalog.definitionPath) {
593
+ return sqlCatalog.definitionPath;
594
+ }
595
+ return undefined;
596
+ }
597
+ function toSpecificationSlug(definition) {
598
+ if (definition === '(unknown)') {
599
+ return 'unknown';
600
+ }
601
+ const normalized = definition
602
+ .replace(/\\/g, '/')
603
+ .replace(/^\//, '')
604
+ .replace(/[^a-zA-Z0-9/_-]+/g, '-')
605
+ .replace(/\/+/g, '__')
606
+ .replace(/-+/g, '-')
607
+ .replace(/^[-_]+|[-_]+$/g, '');
608
+ return normalized || 'unknown';
609
+ }
610
+ function formatDefinitionLinkMarkdown(definitionPath, options) {
611
+ if (!definitionPath) {
612
+ return '(unknown)';
613
+ }
614
+ const normalizedPath = definitionPath.replace(/\\/g, '/').replace(/^\/+/, '');
615
+ if (!normalizedPath) {
616
+ return '(unknown)';
617
+ }
618
+ if ((options === null || options === void 0 ? void 0 : options.mode) === 'github' && options.github) {
619
+ const serverUrl = options.github.serverUrl.replace(/\/+$/, '');
620
+ const repository = options.github.repository.trim();
621
+ const ref = options.github.ref.trim();
622
+ if (serverUrl && repository && ref) {
623
+ const encodedPath = normalizedPath.split('/').map((part) => encodeURIComponent(part)).join('/');
624
+ const url = `${serverUrl}/${repository}/blob/${encodeURIComponent(ref)}/${encodedPath}`;
625
+ return `[${normalizedPath}](${url})`;
626
+ }
627
+ }
628
+ if ((options === null || options === void 0 ? void 0 : options.mode) === 'path' && options.path) {
629
+ const absoluteTarget = node_path_1.default.resolve(options.path.sourceRootDir, normalizedPath);
630
+ const absoluteMarkdownDir = node_path_1.default.resolve(options.path.markdownDir);
631
+ const relativePath = node_path_1.default.relative(absoluteMarkdownDir, absoluteTarget).replace(/\\/g, '/');
632
+ return `[${normalizedPath}](${relativePath || normalizedPath})`;
633
+ }
634
+ return `[${normalizedPath}](${normalizedPath})`;
635
+ }
636
+ function materializeEvidenceForRef(args) {
637
+ const toReport = (rootDir) => {
638
+ try {
639
+ return applyEvidenceOutputControls(runTestEvidenceSpecification({
640
+ mode: 'specification',
641
+ rootDir,
642
+ specsDir: args.specsDir,
643
+ testsDir: args.testsDir,
644
+ specModule: args.specModule
645
+ }), {
646
+ summaryOnly: args.summaryOnly,
647
+ limit: args.limit
648
+ });
649
+ }
650
+ catch (error) {
651
+ if (error instanceof TestEvidenceRuntimeError &&
652
+ error.code === 'NO_SPECS_FOUND') {
653
+ return createEmptySpecificationEvidence();
654
+ }
655
+ throw error;
656
+ }
657
+ };
658
+ if (args.allowCurrentWorkspace) {
659
+ const report = toReport(args.repoRoot);
660
+ return {
661
+ sha: args.resolvedSha,
662
+ report,
663
+ previewJsonPath: writePreviewSnapshot(args.repoRoot, report)
664
+ };
665
+ }
666
+ const worktreeDir = (0, node_fs_1.mkdtempSync)(node_path_1.default.join(args.tempRoot, 'wt-'));
667
+ args.createdWorktrees.push(worktreeDir);
668
+ runGitCommand(args.repoRoot, ['worktree', 'add', '--detach', worktreeDir, args.resolvedSha]);
669
+ const report = toReport(worktreeDir);
670
+ return {
671
+ sha: args.resolvedSha,
672
+ report,
673
+ previewJsonPath: writePreviewSnapshot(worktreeDir, report)
674
+ };
675
+ }
676
+ function createEmptySpecificationEvidence() {
677
+ return {
678
+ schemaVersion: 1,
679
+ mode: 'specification',
680
+ summary: {
681
+ sqlCatalogCount: 0,
682
+ sqlCaseCatalogCount: 0,
683
+ testCaseCount: 0,
684
+ specFilesScanned: 0,
685
+ testFilesScanned: 0
686
+ },
687
+ sqlCatalogs: [],
688
+ sqlCaseCatalogs: [],
689
+ testCaseCatalogs: [],
690
+ testCases: []
691
+ };
692
+ }
693
+ function writePreviewSnapshot(rootDir, report) {
694
+ const previewDir = node_path_1.default.resolve(rootDir, 'artifacts', 'test-evidence');
695
+ (0, node_fs_1.mkdirSync)(previewDir, { recursive: true });
696
+ const previewPath = node_path_1.default.join(previewDir, 'test-specification.preview.json');
697
+ (0, node_fs_1.writeFileSync)(previewPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
698
+ return previewPath;
699
+ }
700
+ function cleanupWorktrees(repoRoot, worktreeDirs) {
701
+ for (const worktreeDir of [...worktreeDirs].reverse()) {
702
+ try {
703
+ runGitCommand(repoRoot, ['worktree', 'remove', '--force', worktreeDir]);
704
+ }
705
+ catch {
706
+ (0, node_fs_1.rmSync)(worktreeDir, { recursive: true, force: true });
707
+ }
708
+ }
709
+ }
710
+ function runGitCommand(repoRoot, args) {
711
+ try {
712
+ return (0, node_child_process_1.execFileSync)('git', args, {
713
+ cwd: repoRoot,
714
+ stdio: ['ignore', 'pipe', 'pipe'],
715
+ encoding: 'utf8'
716
+ }).trim();
717
+ }
718
+ catch (error) {
719
+ const message = error instanceof Error ? error.message : String(error);
720
+ throw new TestEvidenceRuntimeError(`Git command failed: git ${args.join(' ')} (${message})`);
721
+ }
722
+ }
723
+ function resolveGitSha(repoRoot, ref) {
724
+ return runGitCommand(repoRoot, ['rev-parse', ref]);
725
+ }
726
+ function resolveGitMergeBase(repoRoot, baseRef, headRef) {
727
+ return runGitCommand(repoRoot, ['merge-base', baseRef, headRef]);
728
+ }
729
+ function toSqlEvidence(rootDir, loaded) {
730
+ var _a, _b, _c;
731
+ const id = typeof loaded.spec.id === 'string' && loaded.spec.id.trim().length > 0
732
+ ? loaded.spec.id.trim()
733
+ : `<missing-id:${node_path_1.default.basename(loaded.filePath)}>`;
734
+ const sqlFile = typeof loaded.spec.sqlFile === 'string' && loaded.spec.sqlFile.trim().length > 0
735
+ ? loaded.spec.sqlFile.trim()
736
+ : null;
737
+ const paramsShape = ((_a = loaded.spec.params) === null || _a === void 0 ? void 0 : _a.shape) === 'named' || ((_b = loaded.spec.params) === null || _b === void 0 ? void 0 : _b.shape) === 'positional'
738
+ ? loaded.spec.params.shape
739
+ : 'unknown';
740
+ const resolvedSqlPath = sqlFile ? node_path_1.default.resolve(node_path_1.default.dirname(loaded.filePath), sqlFile) : null;
741
+ const specFile = normalizePath(node_path_1.default.relative(rootDir, loaded.filePath));
742
+ return {
743
+ kind: 'sql-catalog',
744
+ id,
745
+ specFile,
746
+ sqlFile,
747
+ sqlFileResolved: Boolean(resolvedSqlPath && (0, node_fs_1.existsSync)(resolvedSqlPath)),
748
+ paramsShape,
749
+ hasOutputMapping: ((_c = loaded.spec.output) === null || _c === void 0 ? void 0 : _c.mapping) !== undefined
750
+ };
751
+ }
752
+ function loadEvidenceModule(rootDir, testsDir, specModule) {
753
+ const moduleCandidates = resolveEvidenceModuleCandidates(rootDir, testsDir, specModule);
754
+ const target = moduleCandidates.find((candidate) => (0, node_fs_1.existsSync)(candidate));
755
+ if (!target) {
756
+ return undefined;
757
+ }
758
+ try {
759
+ const requireFn = (0, node_module_1.createRequire)(__filename);
760
+ const loaded = requireFn(target);
761
+ const normalized = (loaded === null || loaded === void 0 ? void 0 : loaded.default) && (0, sqlCatalogDiscovery_1.isPlainObject)(loaded.default)
762
+ ? loaded.default
763
+ : loaded;
764
+ return normalized;
765
+ }
766
+ catch (error) {
767
+ throw new TestEvidenceRuntimeError(`Failed to load evidence module ${target}: ${error instanceof Error ? error.message : String(error)}`);
768
+ }
769
+ }
770
+ function resolveEvidenceModuleCandidates(rootDir, testsDir, specModule) {
771
+ if (specModule) {
772
+ const absolute = node_path_1.default.resolve(rootDir, specModule);
773
+ return [
774
+ absolute,
775
+ `${absolute}.js`,
776
+ `${absolute}.cjs`,
777
+ `${absolute}.ts`
778
+ ];
779
+ }
780
+ const base = node_path_1.default.resolve(testsDir, 'specs', 'index');
781
+ return [
782
+ `${base}.js`,
783
+ `${base}.cjs`,
784
+ `${base}.ts`
785
+ ];
786
+ }
787
+ function readTestCaseCatalogsFromModule(moduleValue) {
788
+ if (!Array.isArray(moduleValue.testCaseCatalogs)) {
789
+ return [];
790
+ }
791
+ return moduleValue.testCaseCatalogs.map((catalog, index) => {
792
+ if (!(0, sqlCatalogDiscovery_1.isPlainObject)(catalog)) {
793
+ throw new TestEvidenceRuntimeError(`testCaseCatalogs[${index}] must be an object in evidence module.`);
794
+ }
795
+ const id = typeof catalog.id === 'string' ? catalog.id.trim() : '';
796
+ const title = typeof catalog.title === 'string' ? catalog.title.trim() : '';
797
+ const cases = Array.isArray(catalog.cases) ? catalog.cases : [];
798
+ if (!id || !title) {
799
+ throw new TestEvidenceRuntimeError(`testCaseCatalogs[${index}] requires non-empty id/title.`);
800
+ }
801
+ const normalizedCases = cases.map((item, caseIndex) => {
802
+ if (!(0, sqlCatalogDiscovery_1.isPlainObject)(item)) {
803
+ throw new TestEvidenceRuntimeError(`testCaseCatalogs[${index}].cases[${caseIndex}] must be an object.`);
804
+ }
805
+ const caseId = typeof item.id === 'string' ? item.id.trim() : '';
806
+ const caseTitle = typeof item.title === 'string' ? item.title.trim() : '';
807
+ if (!caseId || !caseTitle) {
808
+ throw new TestEvidenceRuntimeError(`testCaseCatalogs[${index}].cases[${caseIndex}] requires non-empty id/title.`);
809
+ }
810
+ const description = typeof item.description === 'string' && item.description.trim().length > 0
811
+ ? item.description.trim()
812
+ : undefined;
813
+ const hasDirectInput = Object.prototype.hasOwnProperty.call(item, 'input');
814
+ const hasDirectOutput = Object.prototype.hasOwnProperty.call(item, 'output');
815
+ const hasDirectExpected = Object.prototype.hasOwnProperty.call(item, 'expected');
816
+ const hasDirectError = Object.prototype.hasOwnProperty.call(item, 'error');
817
+ const hasDirectTags = Object.prototype.hasOwnProperty.call(item, 'tags');
818
+ const hasDirectFocus = Object.prototype.hasOwnProperty.call(item, 'focus');
819
+ const hasDirectRefs = Object.prototype.hasOwnProperty.call(item, 'refs');
820
+ const evidenceValue = Object.prototype.hasOwnProperty.call(item, 'evidence') ? item.evidence : undefined;
821
+ const hasEvidence = (0, sqlCatalogDiscovery_1.isPlainObject)(evidenceValue);
822
+ // Accept both normalized evidence shape and raw test catalog shape (`evidence.*`).
823
+ const input = hasDirectInput
824
+ ? item.input
825
+ : hasEvidence && Object.prototype.hasOwnProperty.call(evidenceValue, 'input')
826
+ ? evidenceValue.input
827
+ : undefined;
828
+ const output = hasDirectOutput
829
+ ? item.output
830
+ : hasEvidence && Object.prototype.hasOwnProperty.call(evidenceValue, 'output')
831
+ ? evidenceValue.output
832
+ : undefined;
833
+ const expectedRaw = hasDirectExpected
834
+ ? item.expected
835
+ : hasEvidence && Object.prototype.hasOwnProperty.call(evidenceValue, 'expected')
836
+ ? evidenceValue.expected
837
+ : undefined;
838
+ const expected = expectedRaw === 'success' || expectedRaw === 'throws' || expectedRaw === 'errorResult'
839
+ ? expectedRaw
840
+ : output === undefined
841
+ ? undefined
842
+ : 'success';
843
+ const errorRaw = hasDirectError
844
+ ? item.error
845
+ : hasEvidence && Object.prototype.hasOwnProperty.call(evidenceValue, 'error')
846
+ ? evidenceValue.error
847
+ : undefined;
848
+ const tagsRaw = hasDirectTags
849
+ ? item.tags
850
+ : hasEvidence && Object.prototype.hasOwnProperty.call(evidenceValue, 'tags')
851
+ ? evidenceValue.tags
852
+ : undefined;
853
+ const focusRaw = hasDirectFocus
854
+ ? item.focus
855
+ : hasEvidence && Object.prototype.hasOwnProperty.call(evidenceValue, 'focus')
856
+ ? evidenceValue.focus
857
+ : undefined;
858
+ const refsRaw = hasDirectRefs
859
+ ? item.refs
860
+ : hasEvidence && Object.prototype.hasOwnProperty.call(evidenceValue, 'refs')
861
+ ? evidenceValue.refs
862
+ : undefined;
863
+ if (input === undefined || expected === undefined) {
864
+ throw new TestEvidenceRuntimeError(`testCaseCatalogs[${index}].cases[${caseIndex}] requires input/expected evidence fields.`);
865
+ }
866
+ if (expected !== 'throws' && output === undefined) {
867
+ throw new TestEvidenceRuntimeError(`testCaseCatalogs[${index}].cases[${caseIndex}] requires output when expected is not "throws".`);
868
+ }
869
+ if (expected === 'throws') {
870
+ if (!(0, sqlCatalogDiscovery_1.isPlainObject)(errorRaw)) {
871
+ throw new TestEvidenceRuntimeError(`testCaseCatalogs[${index}].cases[${caseIndex}] requires error when expected is "throws".`);
872
+ }
873
+ if (typeof errorRaw.name !== 'string' ||
874
+ typeof errorRaw.message !== 'string' ||
875
+ (errorRaw.match !== 'equals' && errorRaw.match !== 'contains')) {
876
+ throw new TestEvidenceRuntimeError(`testCaseCatalogs[${index}].cases[${caseIndex}].error must define name/message/match.`);
877
+ }
878
+ }
879
+ const normalizedTags = Array.isArray(tagsRaw) && tagsRaw.every((tag) => typeof tag === 'string' && tag.trim().length > 0)
880
+ ? normalizeCaseTags(tagsRaw.map((tag) => tag.trim()))
881
+ : undefined;
882
+ const normalizedFocus = typeof focusRaw === 'string' && focusRaw.trim().length > 0
883
+ ? focusRaw.trim()
884
+ : undefined;
885
+ const normalizedRefs = normalizeCatalogRefs(refsRaw);
886
+ if (!normalizedTags || normalizedTags.length !== 2) {
887
+ throw new TestEvidenceRuntimeError(`testCaseCatalogs[${index}].cases[${caseIndex}] requires tags with exactly 2 axes: [intent, technique].`);
888
+ }
889
+ if (!normalizedFocus) {
890
+ throw new TestEvidenceRuntimeError(`testCaseCatalogs[${index}].cases[${caseIndex}] requires focus sentence.`);
891
+ }
892
+ return {
893
+ id: caseId,
894
+ title: caseTitle,
895
+ ...(description ? { description } : {}),
896
+ input,
897
+ expected,
898
+ ...(expected === 'throws'
899
+ ? {
900
+ error: {
901
+ name: errorRaw.name,
902
+ message: errorRaw.message,
903
+ match: errorRaw.match
904
+ }
905
+ }
906
+ : { output }),
907
+ tags: normalizedTags,
908
+ focus: normalizedFocus,
909
+ ...(normalizedRefs.length > 0 ? { refs: normalizedRefs } : {})
910
+ };
911
+ });
912
+ const description = typeof catalog.description === 'string' && catalog.description.trim().length > 0
913
+ ? catalog.description.trim()
914
+ : undefined;
915
+ const definitionPath = typeof catalog.definitionPath === 'string' && catalog.definitionPath.trim().length > 0
916
+ ? catalog.definitionPath.trim()
917
+ : undefined;
918
+ const refs = normalizeCatalogRefs(catalog.refs);
919
+ return {
920
+ id,
921
+ title,
922
+ ...(description ? { description } : {}),
923
+ ...(definitionPath ? { definitionPath } : {}),
924
+ ...(refs.length > 0 ? { refs } : {}),
925
+ cases: normalizedCases
926
+ };
927
+ });
928
+ }
929
+ function flattenTestCaseCatalogs(catalogs) {
930
+ const rows = [];
931
+ for (const catalog of catalogs) {
932
+ for (const testCase of catalog.cases) {
933
+ rows.push({
934
+ kind: 'test-case',
935
+ id: `${catalog.id}.${testCase.id}`,
936
+ catalogId: catalog.id,
937
+ caseId: testCase.id,
938
+ filePath: 'tests/specs/index',
939
+ title: testCase.title,
940
+ ...(testCase.description ? { description: testCase.description } : {})
941
+ });
942
+ }
943
+ }
944
+ return rows;
945
+ }
946
+ function readSqlCaseCatalogsFromModule(moduleValue) {
947
+ if (!Array.isArray(moduleValue.sqlCatalogCases)) {
948
+ return [];
949
+ }
950
+ return moduleValue.sqlCatalogCases.map((catalog, index) => normalizeSqlCaseCatalog(catalog, index));
951
+ }
952
+ function normalizeSqlCaseCatalog(catalog, index) {
953
+ if (!(0, sqlCatalogDiscovery_1.isPlainObject)(catalog)) {
954
+ throw new TestEvidenceRuntimeError(`sqlCatalogCases[${index}] must be an object in evidence module.`);
955
+ }
956
+ const id = typeof catalog.id === 'string' ? catalog.id.trim() : '';
957
+ const title = typeof catalog.title === 'string' ? catalog.title.trim() : '';
958
+ if (!id || !title) {
959
+ throw new TestEvidenceRuntimeError(`sqlCatalogCases[${index}] requires non-empty id/title.`);
960
+ }
961
+ const details = (0, sqlCatalogDiscovery_1.isPlainObject)(catalog.catalog) ? catalog.catalog : {};
962
+ const params = (0, sqlCatalogDiscovery_1.isPlainObject)(details.params) ? details.params : {};
963
+ const output = (0, sqlCatalogDiscovery_1.isPlainObject)(details.output) ? details.output : {};
964
+ const mapping = (0, sqlCatalogDiscovery_1.isPlainObject)(output.mapping) ? output.mapping : {};
965
+ const columnMapRaw = (0, sqlCatalogDiscovery_1.isPlainObject)(mapping.columnMap) ? mapping.columnMap : {};
966
+ const columnMap = Object.fromEntries(Object.entries(columnMapRaw)
967
+ .filter((entry) => typeof entry[0] === 'string' && typeof entry[1] === 'string')
968
+ .sort((a, b) => a[0].localeCompare(b[0])));
969
+ const sql = typeof details.sql === 'string' ? details.sql : '';
970
+ const example = (0, sqlCatalogDiscovery_1.isPlainObject)(params.example) ? { ...params.example } : {};
971
+ const fixturesRaw = Array.isArray(catalog.fixtures) ? catalog.fixtures : [];
972
+ const fixtures = fixturesRaw
973
+ .filter((item) => (0, sqlCatalogDiscovery_1.isPlainObject)(item) && typeof item.tableName === 'string')
974
+ .map((item) => ({
975
+ tableName: item.tableName,
976
+ ...((0, sqlCatalogDiscovery_1.isPlainObject)(item.schema) && (0, sqlCatalogDiscovery_1.isPlainObject)(item.schema.columns)
977
+ ? {
978
+ schema: {
979
+ columns: Object.fromEntries(Object.entries(item.schema.columns)
980
+ .filter((entry) => typeof entry[0] === 'string' && typeof entry[1] === 'string')
981
+ .sort((a, b) => a[0].localeCompare(b[0])))
982
+ }
983
+ }
984
+ : {}),
985
+ rowsCount: Array.isArray(item.rows) ? item.rows.length : typeof item.rowsCount === 'number' ? item.rowsCount : 0
986
+ }))
987
+ .sort((a, b) => a.tableName.localeCompare(b.tableName));
988
+ const casesRaw = Array.isArray(catalog.cases) ? catalog.cases : [];
989
+ const baseParams = (0, sqlCatalogDiscovery_1.isPlainObject)(params.example) ? { ...params.example } : {};
990
+ const cases = casesRaw
991
+ .filter((item) => (0, sqlCatalogDiscovery_1.isPlainObject)(item) && typeof item.id === 'string' && typeof item.title === 'string')
992
+ .map((item, caseIndex) => {
993
+ const arrangedParams = resolveCaseArrangeParams(item, index, caseIndex);
994
+ const mergedParams = arrangedParams ? { ...baseParams, ...arrangedParams } : { ...baseParams };
995
+ const expected = Array.isArray(item.expected) ? [...item.expected] : [];
996
+ return {
997
+ id: item.id,
998
+ title: item.title,
999
+ params: Object.fromEntries(Object.entries(mergedParams)
1000
+ .filter((entry) => typeof entry[0] === 'string')
1001
+ .sort((a, b) => a[0].localeCompare(b[0]))),
1002
+ expected
1003
+ };
1004
+ })
1005
+ .sort((a, b) => a.id.localeCompare(b.id));
1006
+ const nestedDefinitionPath = typeof details.definitionPath === 'string' && details.definitionPath.trim().length > 0
1007
+ ? details.definitionPath.trim()
1008
+ : undefined;
1009
+ const description = typeof catalog.description === 'string' && catalog.description.trim().length > 0
1010
+ ? catalog.description.trim()
1011
+ : undefined;
1012
+ const definitionPath = typeof catalog.definitionPath === 'string' && catalog.definitionPath.trim().length > 0
1013
+ ? catalog.definitionPath.trim()
1014
+ : nestedDefinitionPath;
1015
+ return {
1016
+ id,
1017
+ title,
1018
+ ...(description ? { description } : {}),
1019
+ ...(definitionPath ? { definitionPath } : {}),
1020
+ params: {
1021
+ shape: 'named',
1022
+ example
1023
+ },
1024
+ output: {
1025
+ mapping: {
1026
+ columnMap
1027
+ }
1028
+ },
1029
+ // Keep SQL text unchanged so evidence remains a direct projection of test source.
1030
+ sql,
1031
+ fixtures,
1032
+ cases
1033
+ };
1034
+ }
1035
+ function resolveCaseArrangeParams(testCase, catalogIndex, caseIndex) {
1036
+ const arrange = testCase.arrange;
1037
+ if (arrange === undefined) {
1038
+ return undefined;
1039
+ }
1040
+ if (typeof arrange !== 'function') {
1041
+ throw new TestEvidenceRuntimeError(`sqlCatalogCases[${catalogIndex}].cases[${caseIndex}].arrange must be a function when provided.`);
1042
+ }
1043
+ const arranged = arrange();
1044
+ if (!(0, sqlCatalogDiscovery_1.isPlainObject)(arranged)) {
1045
+ throw new TestEvidenceRuntimeError(`sqlCatalogCases[${catalogIndex}].cases[${caseIndex}].arrange must return an object.`);
1046
+ }
1047
+ return arranged;
1048
+ }
1049
+ function loadTestCaseCatalogEvidence(rootDir, filePath) {
1050
+ let parsed;
1051
+ try {
1052
+ parsed = JSON.parse((0, node_fs_1.readFileSync)(filePath, 'utf8'));
1053
+ }
1054
+ catch (error) {
1055
+ throw new TestEvidenceRuntimeError(`Failed to parse test-case catalog file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
1056
+ }
1057
+ if (!(0, sqlCatalogDiscovery_1.isPlainObject)(parsed)) {
1058
+ throw new TestEvidenceRuntimeError(`Test-case catalog file must contain an object: ${filePath}`);
1059
+ }
1060
+ const document = parsed;
1061
+ if (!Array.isArray(document.catalogs)) {
1062
+ throw new TestEvidenceRuntimeError(`Test-case catalog file must define a catalogs array: ${filePath}`);
1063
+ }
1064
+ const relative = normalizePath(node_path_1.default.relative(rootDir, filePath));
1065
+ const rows = [];
1066
+ const catalogs = document.catalogs;
1067
+ for (const catalog of catalogs) {
1068
+ const catalogId = typeof catalog.id === 'string' ? catalog.id.trim() : '';
1069
+ if (!catalogId) {
1070
+ throw new TestEvidenceRuntimeError(`Test-case catalog id must be a non-empty string: ${filePath}`);
1071
+ }
1072
+ if (!Array.isArray(catalog.cases)) {
1073
+ throw new TestEvidenceRuntimeError(`Test-case catalog "${catalogId}" must define a cases array: ${filePath}`);
1074
+ }
1075
+ const cases = catalog.cases;
1076
+ for (const testCase of cases) {
1077
+ const caseId = typeof testCase.id === 'string' ? testCase.id.trim() : '';
1078
+ const title = typeof testCase.title === 'string' ? testCase.title.trim() : '';
1079
+ if (!caseId || !title) {
1080
+ throw new TestEvidenceRuntimeError(`Test-case catalog "${catalogId}" requires each case to define non-empty id/title: ${filePath}`);
1081
+ }
1082
+ const description = typeof testCase.description === 'string' && testCase.description.trim().length > 0
1083
+ ? testCase.description.trim()
1084
+ : undefined;
1085
+ rows.push({
1086
+ kind: 'test-case',
1087
+ id: `${catalogId}.${caseId}`,
1088
+ catalogId,
1089
+ caseId,
1090
+ filePath: relative,
1091
+ title,
1092
+ ...(description ? { description } : {})
1093
+ });
1094
+ }
1095
+ }
1096
+ return rows;
1097
+ }
1098
+ function walkFiles(rootDir, predicate) {
1099
+ const stack = [rootDir];
1100
+ const files = [];
1101
+ while (stack.length > 0) {
1102
+ const current = stack.pop();
1103
+ const entries = (0, node_fs_1.readdirSync)(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
1104
+ for (const entry of entries) {
1105
+ const absolute = node_path_1.default.join(current, entry.name);
1106
+ if (entry.isDirectory()) {
1107
+ stack.push(absolute);
1108
+ continue;
1109
+ }
1110
+ if (entry.isFile() && predicate(absolute)) {
1111
+ files.push(absolute);
1112
+ }
1113
+ }
1114
+ }
1115
+ return files.sort((a, b) => a.localeCompare(b));
1116
+ }
1117
+ function isTestCaseCatalogFile(filePath) {
1118
+ const lowered = filePath.toLowerCase();
1119
+ return lowered.endsWith('.test-case-catalog.json');
1120
+ }
1121
+ function normalizePath(input) {
1122
+ return input.split(node_path_1.default.sep).join('/');
1123
+ }
1124
+ function normalizeCatalogRefs(value) {
1125
+ if (!Array.isArray(value)) {
1126
+ return [];
1127
+ }
1128
+ return value
1129
+ .filter((item) => (0, sqlCatalogDiscovery_1.isPlainObject)(item))
1130
+ .map((item) => ({
1131
+ label: typeof item.label === 'string' ? item.label.trim() : '',
1132
+ url: typeof item.url === 'string' ? item.url.trim() : ''
1133
+ }))
1134
+ .filter((item) => item.label.length > 0 && item.url.length > 0)
1135
+ .sort((a, b) => a.label.localeCompare(b.label) || a.url.localeCompare(b.url));
1136
+ }
1137
+ const TAG_NORMALIZATION_MAP = {
1138
+ boundary: 'bva'
1139
+ };
1140
+ const INTENT_TAGS = ['normalization', 'validation', 'authorization', 'invariant'];
1141
+ const TECHNIQUE_TAGS = ['ep', 'bva', 'idempotence', 'state'];
1142
+ const INTENT_TAG_SET = new Set(INTENT_TAGS);
1143
+ const TECHNIQUE_TAG_SET = new Set(TECHNIQUE_TAGS);
1144
+ function normalizeCaseTags(tags) {
1145
+ var _a;
1146
+ const normalized = new Set();
1147
+ for (const rawTag of tags) {
1148
+ const lowered = rawTag.trim().toLowerCase();
1149
+ const mapped = (_a = TAG_NORMALIZATION_MAP[lowered]) !== null && _a !== void 0 ? _a : lowered;
1150
+ if (INTENT_TAG_SET.has(mapped) || TECHNIQUE_TAG_SET.has(mapped)) {
1151
+ normalized.add(mapped);
1152
+ }
1153
+ }
1154
+ const intent = INTENT_TAGS.find((tag) => normalized.has(tag));
1155
+ const technique = TECHNIQUE_TAGS.find((tag) => normalized.has(tag));
1156
+ const result = [];
1157
+ if (intent) {
1158
+ result.push(intent);
1159
+ }
1160
+ if (technique) {
1161
+ result.push(technique);
1162
+ }
1163
+ return result;
1164
+ }
1165
+ function formatCaseTags(tags) {
1166
+ const normalized = Array.isArray(tags) ? normalizeCaseTags(tags) : [];
1167
+ return `[${normalized.join(', ')}]`;
1168
+ }
1169
+ function formatCaseFocus(focus) {
1170
+ if (typeof focus === 'string' && focus.trim().length > 0) {
1171
+ return focus.trim();
1172
+ }
1173
+ return '(not specified)';
1174
+ }
1175
+ function stringifyStablePretty(value) {
1176
+ var _a;
1177
+ return (_a = JSON.stringify(sortDeep(value), null, 2)) !== null && _a !== void 0 ? _a : 'null';
1178
+ }
1179
+ function stringifyErrorPretty(value) {
1180
+ var _a;
1181
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
1182
+ return 'null';
1183
+ }
1184
+ const source = value;
1185
+ return (_a = JSON.stringify({
1186
+ name: source.name,
1187
+ message: source.message,
1188
+ match: source.match
1189
+ }, null, 2)) !== null && _a !== void 0 ? _a : 'null';
1190
+ }
1191
+ function sortDeep(value) {
1192
+ if (Array.isArray(value)) {
1193
+ return value.map((item) => sortDeep(item));
1194
+ }
1195
+ if (value !== null && typeof value === 'object') {
1196
+ const entries = Object.entries(value)
1197
+ .sort((a, b) => a[0].localeCompare(b[0]))
1198
+ .map(([key, nested]) => [key, sortDeep(nested)]);
1199
+ return Object.fromEntries(entries);
1200
+ }
1201
+ return value;
1202
+ }
1203
+ /**
1204
+ * Render deterministic Markdown focused on human-readable test intent.
1205
+ */
1206
+ function formatTestDocumentationOutput(report, context) {
1207
+ const model = (0, test_evidence_core_1.buildSpecificationModel)(report);
1208
+ return `${(0, test_evidence_renderer_md_1.renderTestDocumentationMarkdown)(model, {
1209
+ definitionLinks: resolveDefinitionLinkOptions(context)
1210
+ })}\n`;
1211
+ }
1212
+ function writeTestDocumentationArtifact(args) {
1213
+ (0, node_fs_1.mkdirSync)(node_path_1.default.dirname(args.outPath), { recursive: true });
1214
+ (0, node_fs_1.writeFileSync)(args.outPath, formatTestDocumentationOutput(args.report, {
1215
+ markdownPath: args.outPath,
1216
+ sourceRootDir: args.sourceRootDir
1217
+ }), 'utf8');
1218
+ console.log(`wrote: ${args.outPath}`);
1219
+ }
1220
+ //# sourceMappingURL=testEvidence.js.map