@toolbaux/guardian 0.1.0

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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +366 -0
  3. package/dist/adapters/csharp-adapter.js +149 -0
  4. package/dist/adapters/go-adapter.js +96 -0
  5. package/dist/adapters/index.js +16 -0
  6. package/dist/adapters/java-adapter.js +122 -0
  7. package/dist/adapters/python-adapter.js +183 -0
  8. package/dist/adapters/runner.js +69 -0
  9. package/dist/adapters/types.js +1 -0
  10. package/dist/adapters/typescript-adapter.js +179 -0
  11. package/dist/benchmarking/framework.js +91 -0
  12. package/dist/cli.js +343 -0
  13. package/dist/commands/analyze-depth.js +43 -0
  14. package/dist/commands/api-spec-extractor.js +52 -0
  15. package/dist/commands/breaking-change-analyzer.js +334 -0
  16. package/dist/commands/config-compliance.js +219 -0
  17. package/dist/commands/constraints.js +221 -0
  18. package/dist/commands/context.js +101 -0
  19. package/dist/commands/data-flow-tracer.js +291 -0
  20. package/dist/commands/dependency-impact-analyzer.js +27 -0
  21. package/dist/commands/diff.js +146 -0
  22. package/dist/commands/discrepancy.js +71 -0
  23. package/dist/commands/doc-generate.js +163 -0
  24. package/dist/commands/doc-html.js +120 -0
  25. package/dist/commands/drift.js +88 -0
  26. package/dist/commands/extract.js +16 -0
  27. package/dist/commands/feature-context.js +116 -0
  28. package/dist/commands/generate.js +339 -0
  29. package/dist/commands/guard.js +182 -0
  30. package/dist/commands/init.js +209 -0
  31. package/dist/commands/intel.js +20 -0
  32. package/dist/commands/license-dependency-auditor.js +33 -0
  33. package/dist/commands/performance-hotspot-profiler.js +42 -0
  34. package/dist/commands/search.js +314 -0
  35. package/dist/commands/security-boundary-auditor.js +359 -0
  36. package/dist/commands/simulate.js +294 -0
  37. package/dist/commands/summary.js +27 -0
  38. package/dist/commands/test-coverage-mapper.js +264 -0
  39. package/dist/commands/verify-drift.js +62 -0
  40. package/dist/config.js +441 -0
  41. package/dist/extract/ai-context-hints.js +107 -0
  42. package/dist/extract/analyzers/backend.js +1704 -0
  43. package/dist/extract/analyzers/depth.js +264 -0
  44. package/dist/extract/analyzers/frontend.js +2221 -0
  45. package/dist/extract/api-usage-tracker.js +19 -0
  46. package/dist/extract/cache.js +53 -0
  47. package/dist/extract/codebase-intel.js +190 -0
  48. package/dist/extract/compress.js +452 -0
  49. package/dist/extract/context-block.js +356 -0
  50. package/dist/extract/contracts.js +183 -0
  51. package/dist/extract/discrepancies.js +233 -0
  52. package/dist/extract/docs-loader.js +110 -0
  53. package/dist/extract/docs.js +2379 -0
  54. package/dist/extract/drift.js +1578 -0
  55. package/dist/extract/duplicates.js +435 -0
  56. package/dist/extract/feature-arcs.js +138 -0
  57. package/dist/extract/graph.js +76 -0
  58. package/dist/extract/html-doc.js +1409 -0
  59. package/dist/extract/ignore.js +45 -0
  60. package/dist/extract/index.js +455 -0
  61. package/dist/extract/llm-client.js +159 -0
  62. package/dist/extract/pattern-registry.js +141 -0
  63. package/dist/extract/product-doc.js +497 -0
  64. package/dist/extract/python.js +1202 -0
  65. package/dist/extract/runtime.js +193 -0
  66. package/dist/extract/schema-evolution-validator.js +35 -0
  67. package/dist/extract/test-gap-analyzer.js +20 -0
  68. package/dist/extract/tests.js +74 -0
  69. package/dist/extract/types.js +1 -0
  70. package/dist/extract/validate-backend.js +30 -0
  71. package/dist/extract/writer.js +11 -0
  72. package/dist/output-layout.js +37 -0
  73. package/dist/project-discovery.js +309 -0
  74. package/dist/schema/architecture.js +350 -0
  75. package/dist/schema/feature-spec.js +89 -0
  76. package/dist/schema/index.js +8 -0
  77. package/dist/schema/ux.js +46 -0
  78. package/package.json +75 -0
package/dist/cli.js ADDED
@@ -0,0 +1,343 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { runExtract } from "./commands/extract.js";
4
+ import { runDrift } from "./commands/drift.js";
5
+ import { runConstraints } from "./commands/constraints.js";
6
+ import { runSimulate } from "./commands/simulate.js";
7
+ import { runGuard } from "./commands/guard.js";
8
+ import { runDiff } from "./commands/diff.js";
9
+ import { runSummary } from "./commands/summary.js";
10
+ import { runSearch } from "./commands/search.js";
11
+ import { runContext } from "./commands/context.js";
12
+ import { runGenerate } from "./commands/generate.js";
13
+ import { runVerifyDrift } from "./commands/verify-drift.js";
14
+ import { runAnalyzeDepth } from "./commands/analyze-depth.js";
15
+ import { runIntel } from "./commands/intel.js";
16
+ import { runFeatureContext } from "./commands/feature-context.js";
17
+ import { runDocGenerate } from "./commands/doc-generate.js";
18
+ import { runDiscrepancy } from "./commands/discrepancy.js";
19
+ import { runDocHtml } from "./commands/doc-html.js";
20
+ import { runInit } from "./commands/init.js";
21
+ const program = new Command();
22
+ program
23
+ .name("guardian")
24
+ .description("Guardian — Architectural intelligence for codebases (by Toolbaux)")
25
+ .version("0.1.0");
26
+ program
27
+ .command("generate")
28
+ .description("Generate compact AI-ready architecture context")
29
+ .argument("[projectRoot]", "Repo or project root", process.cwd())
30
+ .option("--backend-root <path>", "Path to backend root")
31
+ .option("--frontend-root <path>", "Path to frontend root")
32
+ .option("--config <path>", "Path to specguard.config.json")
33
+ .option("--output <path>", "Output directory", "specs-out")
34
+ .option("--focus <text>", "Focus the generated AI context on a feature area")
35
+ .option("--max-lines <count>", "Maximum lines for the generated context")
36
+ .option("--ai-context", "Generate architecture-context.md for AI tools", false)
37
+ .action(async (projectRoot, options) => {
38
+ await runGenerate({
39
+ projectRoot,
40
+ backendRoot: options.backendRoot,
41
+ frontendRoot: options.frontendRoot,
42
+ configPath: options.config,
43
+ output: options.output,
44
+ focus: options.focus,
45
+ maxLines: options.maxLines,
46
+ aiContext: options.aiContext ?? false
47
+ });
48
+ });
49
+ program
50
+ .command("extract")
51
+ .description("Generate architecture and UX snapshots")
52
+ .argument("[projectRoot]", "Repo or project root", process.cwd())
53
+ .option("--backend-root <path>", "Path to backend root")
54
+ .option("--frontend-root <path>", "Path to frontend root")
55
+ .option("--output <path>", "Output directory", "specs-out")
56
+ .option("--include-file-graph", "Include file-level dependency graph", false)
57
+ .option("--config <path>", "Path to specguard.config.json")
58
+ .option("--docs-mode <mode>", "Docs mode (lean|full)")
59
+ .action(async (projectRoot, options) => {
60
+ await runExtract({
61
+ projectRoot,
62
+ backendRoot: options.backendRoot,
63
+ frontendRoot: options.frontendRoot,
64
+ output: options.output ?? "specs-out",
65
+ includeFileGraph: options.includeFileGraph ?? false,
66
+ configPath: options.config,
67
+ docsMode: options.docsMode
68
+ });
69
+ });
70
+ program
71
+ .command("diff")
72
+ .description("Generate changelog diff between a baseline snapshot and current snapshot")
73
+ .requiredOption("--baseline <path>", "Path to baseline architecture.snapshot.yaml")
74
+ .requiredOption("--current <path>", "Path to current architecture.snapshot.yaml")
75
+ .option("--output <path>", "Output diff path", "specs-out/machine/docs/diff.md")
76
+ .action(async (options) => {
77
+ await runDiff({
78
+ baselinePath: options.baseline,
79
+ currentPath: options.current,
80
+ output: options.output ?? "specs-out/docs/diff.md"
81
+ });
82
+ });
83
+ program
84
+ .command("drift")
85
+ .description("Compute architectural drift metrics")
86
+ .argument("[projectRoot]", "Repo or project root", process.cwd())
87
+ .option("--backend-root <path>", "Path to backend root")
88
+ .option("--frontend-root <path>", "Path to frontend root")
89
+ .option("--output <path>", "Output report path", "specs-out/machine/drift.report.json")
90
+ .option("--baseline [path]", "Write baseline drift file")
91
+ .option("--history [path]", "Append drift history entry")
92
+ .option("--config <path>", "Path to specguard.config.json")
93
+ .action(async (projectRoot, options) => {
94
+ await runDrift({
95
+ projectRoot,
96
+ backendRoot: options.backendRoot,
97
+ frontendRoot: options.frontendRoot,
98
+ output: options.output ?? "specs-out/drift.report.json",
99
+ configPath: options.config,
100
+ baseline: options.baseline,
101
+ history: options.history
102
+ });
103
+ });
104
+ program
105
+ .command("verify-drift")
106
+ .description("Verify architectural drift against a baseline and strict thresholds for CI/CD")
107
+ .argument("[projectRoot]", "Repo or project root", process.cwd())
108
+ .option("--backend-root <path>", "Path to backend root")
109
+ .option("--frontend-root <path>", "Path to frontend root")
110
+ .option("--config <path>", "Path to specguard.config.json")
111
+ .option("--baseline <path>", "Path to baseline payload")
112
+ .option("--strict-threshold <val>", "Maximum allowed delta shift (default 0.15)")
113
+ .action(async (projectRoot, options) => {
114
+ await runVerifyDrift({
115
+ projectRoot,
116
+ backendRoot: options.backendRoot,
117
+ frontendRoot: options.frontendRoot,
118
+ configPath: options.config,
119
+ baseline: options.baseline,
120
+ strictThreshold: options.strictThreshold
121
+ });
122
+ });
123
+ program
124
+ .command("constraints")
125
+ .description("Generate LLM constraint summary")
126
+ .argument("[projectRoot]", "Repo or project root", process.cwd())
127
+ .option("--backend-root <path>", "Path to backend root")
128
+ .option("--frontend-root <path>", "Path to frontend root")
129
+ .option("--output <path>", "Output constraints path", "specs-out/machine/constraints.json")
130
+ .option("--config <path>", "Path to specguard.config.json")
131
+ .action(async (projectRoot, options) => {
132
+ await runConstraints({
133
+ projectRoot,
134
+ backendRoot: options.backendRoot,
135
+ frontendRoot: options.frontendRoot,
136
+ output: options.output ?? "specs-out/constraints.json",
137
+ configPath: options.config
138
+ });
139
+ });
140
+ program
141
+ .command("simulate")
142
+ .description("Simulate drift for a candidate workspace")
143
+ .argument("[projectRoot]", "Repo or project root", process.cwd())
144
+ .option("--backend-root <path>", "Path to backend root")
145
+ .option("--frontend-root <path>", "Path to frontend root")
146
+ .option("--output <path>", "Output simulation report", "specs-out/machine/drift.simulation.json")
147
+ .option("--baseline <path>", "Baseline drift/baseline file")
148
+ .option("--baseline-summary <path>", "Baseline architecture summary path")
149
+ .option("--patch <path>", "Patch file to apply for simulation")
150
+ .option("--mode <mode>", "Simulation mode (soft|hard)")
151
+ .option("--config <path>", "Path to specguard.config.json")
152
+ .action(async (projectRoot, options) => {
153
+ await runSimulate({
154
+ projectRoot,
155
+ backendRoot: options.backendRoot,
156
+ frontendRoot: options.frontendRoot,
157
+ output: options.output ?? "specs-out/drift.simulation.json",
158
+ baseline: options.baseline,
159
+ baselineSummary: options.baselineSummary,
160
+ configPath: options.config,
161
+ patch: options.patch,
162
+ mode: options.mode
163
+ });
164
+ });
165
+ program
166
+ .command("guard")
167
+ .description("Generate LLM prompt, optional patch, and simulate drift")
168
+ .argument("[projectRoot]", "Repo or project root", process.cwd())
169
+ .option("--backend-root <path>", "Path to backend root")
170
+ .option("--frontend-root <path>", "Path to frontend root")
171
+ .requiredOption("--task <text>", "Task description for the LLM")
172
+ .option("--prompt-out <path>", "Prompt output path", "specs-out/machine/guard.prompt.txt")
173
+ .option("--patch-out <path>", "Patch output path", "specs-out/machine/guard.patch")
174
+ .option("--simulation-out <path>", "Simulation report output", "specs-out/machine/drift.simulation.json")
175
+ .option("--mode <mode>", "Simulation mode (soft|hard)")
176
+ .option("--llm-command <cmd>", "Override LLM command from config")
177
+ .option("--print-context", "Print an IDE-ready context block instead of calling an LLM", false)
178
+ .option("--config <path>", "Path to specguard.config.json")
179
+ .action(async (projectRoot, options) => {
180
+ await runGuard({
181
+ projectRoot,
182
+ backendRoot: options.backendRoot,
183
+ frontendRoot: options.frontendRoot,
184
+ task: options.task,
185
+ promptOutput: options.promptOut,
186
+ patchOutput: options.patchOut,
187
+ simulationOutput: options.simulationOut,
188
+ mode: options.mode,
189
+ llmCommand: options.llmCommand,
190
+ printContext: options.printContext ?? false,
191
+ configPath: options.config
192
+ });
193
+ });
194
+ program
195
+ .command("summary")
196
+ .description("Generate a plain-language project summary from existing snapshots")
197
+ .option("--input <path>", "Snapshot output directory", "specs-out")
198
+ .option("--output <path>", "Summary output path")
199
+ .action(async (options) => {
200
+ await runSummary({
201
+ input: options.input ?? "specs-out",
202
+ output: options.output
203
+ });
204
+ });
205
+ program
206
+ .command("search")
207
+ .description("Search existing snapshots for models, endpoints, components, modules, and tasks")
208
+ .option("--input <path>", "Snapshot output directory", "specs-out")
209
+ .requiredOption("--query <text>", "Search query")
210
+ .option("--output <path>", "Write search results to a file")
211
+ .option("--types <items>", "Comma-separated filters: models,endpoints,components,modules,tasks")
212
+ .action(async (options) => {
213
+ await runSearch({
214
+ input: options.input ?? "specs-out",
215
+ query: options.query,
216
+ output: options.output,
217
+ types: options.types ? [options.types] : undefined
218
+ });
219
+ });
220
+ program
221
+ .command("context")
222
+ .description("Render an AI-ready context block from existing snapshots")
223
+ .option("--input <path>", "Snapshot output directory", "specs-out")
224
+ .option("--output <path>", "Append the context block to a file")
225
+ .option("--focus <text>", "Focus the context on a feature area")
226
+ .option("--max-lines <count>", "Maximum number of lines to include")
227
+ .action(async (options) => {
228
+ await runContext({
229
+ input: options.input ?? "specs-out",
230
+ output: options.output,
231
+ focus: options.focus,
232
+ maxLines: options.maxLines
233
+ });
234
+ });
235
+ program
236
+ .command("analyze-depth")
237
+ .description("Compute the Structural Intelligence profile for a feature or query")
238
+ .argument("[projectRoot]", "Repo or project root", process.cwd())
239
+ .requiredOption("--query <text>", "Feature or area to analyze (e.g. 'stripe', 'auth')")
240
+ .option("--backend-root <path>", "Path to backend root")
241
+ .option("--frontend-root <path>", "Path to frontend root")
242
+ .option("--config <path>", "Path to specguard.config.json")
243
+ .option("--output <path>", "Write report to a file instead of stdout")
244
+ .option("--format <fmt>", "Output format: yaml or json (default: yaml)")
245
+ .option("--ci", "Exit with code 1 when HIGH complexity is detected with strong confidence", false)
246
+ .action(async (projectRoot, options) => {
247
+ await runAnalyzeDepth({
248
+ projectRoot,
249
+ backendRoot: options.backendRoot,
250
+ frontendRoot: options.frontendRoot,
251
+ configPath: options.config,
252
+ output: options.output,
253
+ format: options.format ?? "yaml",
254
+ ci: options.ci ?? false,
255
+ query: options.query
256
+ });
257
+ });
258
+ program
259
+ .command("intel")
260
+ .description("Build codebase-intelligence.json from existing snapshots")
261
+ .option("--specs <dir>", "Snapshot output directory", "specs-out")
262
+ .option("--output <path>", "Output path for codebase-intelligence.json")
263
+ .action(async (options) => {
264
+ await runIntel({
265
+ specs: options.specs,
266
+ output: options.output
267
+ });
268
+ });
269
+ program
270
+ .command("feature-context")
271
+ .description("Generate a filtered context packet for implementing a single feature")
272
+ .requiredOption("--spec <file>", "Path to feature spec YAML")
273
+ .option("--specs <dir>", "Snapshot output directory", "specs-out")
274
+ .option("--output <path>", "Output path for feature context JSON")
275
+ .action(async (options) => {
276
+ await runFeatureContext({
277
+ spec: options.spec,
278
+ specs: options.specs,
279
+ output: options.output
280
+ });
281
+ });
282
+ program
283
+ .command("doc-generate")
284
+ .description("Generate a human-readable product document from codebase intelligence")
285
+ .option("--specs <dir>", "Snapshot output directory", "specs-out")
286
+ .option("--feature-specs <dir>", "Directory of feature spec YAML files")
287
+ .option("--output <path>", "Output path for product-document.md")
288
+ .option("--update-baseline", "Freeze current state as new baseline for discrepancy tracking", false)
289
+ .action(async (options) => {
290
+ await runDocGenerate({
291
+ specs: options.specs,
292
+ featureSpecs: options.featureSpecs,
293
+ output: options.output,
294
+ updateBaseline: options.updateBaseline ?? false
295
+ });
296
+ });
297
+ program
298
+ .command("discrepancy")
299
+ .description("Diff current codebase intelligence against a committed baseline")
300
+ .option("--specs <dir>", "Snapshot output directory", "specs-out")
301
+ .option("--feature-specs <dir>", "Directory of feature spec YAML files")
302
+ .option("--output <path>", "Output path (used when --format is json or md)")
303
+ .option("--format <fmt>", "Output format: json, md, or both (default: both)", "both")
304
+ .action(async (options) => {
305
+ await runDiscrepancy({
306
+ specs: options.specs,
307
+ featureSpecs: options.featureSpecs,
308
+ output: options.output,
309
+ format: options.format ?? "both"
310
+ });
311
+ });
312
+ program
313
+ .command("doc-html")
314
+ .description("Generate a self-contained Javadoc-style HTML viewer from codebase intelligence")
315
+ .option("--specs <dir>", "Snapshot output directory", "specs-out")
316
+ .option("--output <path>", "Output path for index.html")
317
+ .action(async (options) => {
318
+ await runDocHtml({
319
+ specs: options.specs ?? "specs-out",
320
+ output: options.output,
321
+ });
322
+ });
323
+ program
324
+ .command("init")
325
+ .description("Initialize specguard for a project (config, .specs dir, pre-commit hook, CLAUDE.md)")
326
+ .argument("[projectRoot]", "Repo or project root", process.cwd())
327
+ .option("--backend-root <path>", "Path to backend root")
328
+ .option("--frontend-root <path>", "Path to frontend root")
329
+ .option("--output <path>", "Output directory", ".specs")
330
+ .option("--skip-hook", "Skip pre-commit hook installation", false)
331
+ .action(async (projectRoot, options) => {
332
+ await runInit({
333
+ projectRoot,
334
+ backendRoot: options.backendRoot,
335
+ frontendRoot: options.frontendRoot,
336
+ output: options.output,
337
+ skipHook: options.skipHook ?? false,
338
+ });
339
+ });
340
+ program.parseAsync().catch((error) => {
341
+ console.error(error);
342
+ process.exitCode = 1;
343
+ });
@@ -0,0 +1,43 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs/promises";
3
+ import yaml from "js-yaml";
4
+ import { buildSnapshots } from "../extract/index.js";
5
+ import { analyzeDepth } from "../extract/analyzers/depth.js";
6
+ export async function runAnalyzeDepth(options) {
7
+ const { architecture } = await buildSnapshots({
8
+ projectRoot: options.projectRoot,
9
+ backendRoot: options.backendRoot,
10
+ frontendRoot: options.frontendRoot,
11
+ output: options.output ?? "specs-out",
12
+ includeFileGraph: true,
13
+ configPath: options.configPath
14
+ });
15
+ const report = analyzeDepth({
16
+ query: options.query,
17
+ modules: architecture.modules,
18
+ moduleGraph: architecture.dependencies.module_graph,
19
+ fileGraph: architecture.dependencies.file_graph,
20
+ circularDependencies: architecture.analysis.circular_dependencies
21
+ });
22
+ const formatted = options.format === "json"
23
+ ? JSON.stringify(report, null, 2)
24
+ : (yaml.dump(report, { lineWidth: 100 }));
25
+ if (options.output) {
26
+ const target = path.resolve(options.output);
27
+ await fs.mkdir(path.dirname(target), { recursive: true });
28
+ await fs.writeFile(target, formatted, "utf8");
29
+ console.log(`Wrote ${target}`);
30
+ }
31
+ else {
32
+ console.log(formatted);
33
+ }
34
+ // CI enforcement: exit 1 when confident and non-compressible
35
+ if (options.ci &&
36
+ report.classification.compressible === "NON_COMPRESSIBLE" &&
37
+ report.confidence.value >= report.guardrails.enforce_if_confidence_above) {
38
+ console.error(`\n[SpecGuard] CI FAIL: "${options.query}" classified as HIGH complexity (confidence ${report.confidence.value.toFixed(2)}).\n` +
39
+ `Recommended pattern: ${report.recommendation.primary.pattern}\n` +
40
+ `Avoid: ${report.recommendation.avoid.join(", ")}`);
41
+ process.exit(1);
42
+ }
43
+ }
@@ -0,0 +1,52 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ async function readDirRecursive(dir) {
4
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
5
+ const files = [];
6
+ for (const e of entries) {
7
+ const res = path.join(dir, e.name);
8
+ if (e.isDirectory())
9
+ files.push(...await readDirRecursive(res));
10
+ else
11
+ files.push(res);
12
+ }
13
+ return files;
14
+ }
15
+ export async function run() {
16
+ const root = process.cwd();
17
+ const srcDir = path.join(root, 'src');
18
+ let routes = [];
19
+ try {
20
+ const files = await readDirRecursive(srcDir);
21
+ const jsTs = files.filter(f => f.endsWith('.ts') || f.endsWith('.js'));
22
+ for (const f of jsTs) {
23
+ const text = await fs.promises.readFile(f, 'utf8');
24
+ const matches = Array.from(text.matchAll(/(?:get|post|put|delete|patch)\s*\(['"`]([^'"`]+)/ig));
25
+ if (matches.length) {
26
+ routes.push({ file: path.relative(root, f), samplePaths: matches.map(m => m[1]) });
27
+ }
28
+ }
29
+ }
30
+ catch (err) {
31
+ // no-op: project may not have src; return empty
32
+ }
33
+ const openapi = {
34
+ openapi: '3.0.0',
35
+ info: { title: 'Inferred API', version: '0.0.0' },
36
+ paths: {}
37
+ };
38
+ for (const r of routes) {
39
+ for (const p of r.samplePaths) {
40
+ openapi.paths[p] = { get: { description: `Discovered in ${r.file}` } };
41
+ }
42
+ }
43
+ const out = path.join(process.cwd(), 'out', 'inferred-openapi.json');
44
+ await fs.promises.mkdir(path.dirname(out), { recursive: true });
45
+ await fs.promises.writeFile(out, JSON.stringify(openapi, null, 2), 'utf8');
46
+ console.log('Wrote inferred OpenAPI to', out);
47
+ return openapi;
48
+ }
49
+ const _isMain = (typeof require !== 'undefined' && (require.main === module)) || (process.argv[1] && process.argv[1].endsWith('api-spec-extractor.ts'));
50
+ if (_isMain) {
51
+ run().catch(err => { console.error(err); process.exit(1); });
52
+ }