@ipation/specbridge 0.2.1

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/index.js ADDED
@@ -0,0 +1,3059 @@
1
+ // src/core/schemas/decision.schema.ts
2
+ import { z } from "zod";
3
+ var DecisionStatusSchema = z.enum(["draft", "active", "deprecated", "superseded"]);
4
+ var ConstraintTypeSchema = z.enum(["invariant", "convention", "guideline"]);
5
+ var SeveritySchema = z.enum(["critical", "high", "medium", "low"]);
6
+ var VerificationFrequencySchema = z.enum(["commit", "pr", "daily", "weekly"]);
7
+ var DecisionMetadataSchema = z.object({
8
+ id: z.string().min(1).regex(/^[a-z0-9-]+$/, "ID must be lowercase alphanumeric with hyphens"),
9
+ title: z.string().min(1).max(200),
10
+ status: DecisionStatusSchema,
11
+ owners: z.array(z.string().min(1)).min(1),
12
+ createdAt: z.string().datetime().optional(),
13
+ updatedAt: z.string().datetime().optional(),
14
+ supersededBy: z.string().optional(),
15
+ tags: z.array(z.string()).optional()
16
+ });
17
+ var DecisionContentSchema = z.object({
18
+ summary: z.string().min(1).max(500),
19
+ rationale: z.string().min(1),
20
+ context: z.string().optional(),
21
+ consequences: z.array(z.string()).optional()
22
+ });
23
+ var ConstraintExceptionSchema = z.object({
24
+ pattern: z.string().min(1),
25
+ reason: z.string().min(1),
26
+ approvedBy: z.string().optional(),
27
+ expiresAt: z.string().datetime().optional()
28
+ });
29
+ var ConstraintSchema = z.object({
30
+ id: z.string().min(1).regex(/^[a-z0-9-]+$/, "Constraint ID must be lowercase alphanumeric with hyphens"),
31
+ type: ConstraintTypeSchema,
32
+ rule: z.string().min(1),
33
+ severity: SeveritySchema,
34
+ scope: z.string().min(1),
35
+ verifier: z.string().optional(),
36
+ autofix: z.boolean().optional(),
37
+ exceptions: z.array(ConstraintExceptionSchema).optional()
38
+ });
39
+ var VerificationConfigSchema = z.object({
40
+ check: z.string().min(1),
41
+ target: z.string().min(1),
42
+ frequency: VerificationFrequencySchema,
43
+ timeout: z.number().positive().optional()
44
+ });
45
+ var LinksSchema = z.object({
46
+ related: z.array(z.string()).optional(),
47
+ supersedes: z.array(z.string()).optional(),
48
+ references: z.array(z.string().url()).optional()
49
+ });
50
+ var DecisionSchema = z.object({
51
+ kind: z.literal("Decision"),
52
+ metadata: DecisionMetadataSchema,
53
+ decision: DecisionContentSchema,
54
+ constraints: z.array(ConstraintSchema).min(1),
55
+ verification: z.object({
56
+ automated: z.array(VerificationConfigSchema).optional()
57
+ }).optional(),
58
+ links: LinksSchema.optional()
59
+ });
60
+ function validateDecision(data) {
61
+ const result = DecisionSchema.safeParse(data);
62
+ if (result.success) {
63
+ return { success: true, data: result.data };
64
+ }
65
+ return { success: false, errors: result.error };
66
+ }
67
+ function formatValidationErrors(errors) {
68
+ return errors.errors.map((err) => {
69
+ const path = err.path.join(".");
70
+ return `${path}: ${err.message}`;
71
+ });
72
+ }
73
+
74
+ // src/core/schemas/config.schema.ts
75
+ import { z as z2 } from "zod";
76
+ var SeveritySchema2 = z2.enum(["critical", "high", "medium", "low"]);
77
+ var LevelConfigSchema = z2.object({
78
+ timeout: z2.number().positive().optional(),
79
+ severity: z2.array(SeveritySchema2).optional()
80
+ });
81
+ var ProjectConfigSchema = z2.object({
82
+ name: z2.string().min(1),
83
+ sourceRoots: z2.array(z2.string().min(1)).min(1),
84
+ exclude: z2.array(z2.string()).optional()
85
+ });
86
+ var InferenceConfigSchema = z2.object({
87
+ minConfidence: z2.number().min(0).max(100).optional(),
88
+ analyzers: z2.array(z2.string()).optional()
89
+ });
90
+ var VerificationConfigSchema2 = z2.object({
91
+ levels: z2.object({
92
+ commit: LevelConfigSchema.optional(),
93
+ pr: LevelConfigSchema.optional(),
94
+ full: LevelConfigSchema.optional()
95
+ }).optional()
96
+ });
97
+ var AgentConfigSchema = z2.object({
98
+ format: z2.enum(["markdown", "json", "mcp"]).optional(),
99
+ includeRationale: z2.boolean().optional()
100
+ });
101
+ var SpecBridgeConfigSchema = z2.object({
102
+ version: z2.string().regex(/^\d+\.\d+$/, "Version must be in format X.Y"),
103
+ project: ProjectConfigSchema,
104
+ inference: InferenceConfigSchema.optional(),
105
+ verification: VerificationConfigSchema2.optional(),
106
+ agent: AgentConfigSchema.optional()
107
+ });
108
+ function validateConfig(data) {
109
+ const result = SpecBridgeConfigSchema.safeParse(data);
110
+ if (result.success) {
111
+ return { success: true, data: result.data };
112
+ }
113
+ return { success: false, errors: result.error };
114
+ }
115
+ var defaultConfig = {
116
+ version: "1.0",
117
+ project: {
118
+ name: "my-project",
119
+ sourceRoots: ["src/**/*.ts", "src/**/*.tsx"],
120
+ exclude: ["**/*.test.ts", "**/*.spec.ts", "**/node_modules/**", "**/dist/**"]
121
+ },
122
+ inference: {
123
+ minConfidence: 70,
124
+ analyzers: ["naming", "structure", "imports", "errors"]
125
+ },
126
+ verification: {
127
+ levels: {
128
+ commit: {
129
+ timeout: 5e3,
130
+ severity: ["critical"]
131
+ },
132
+ pr: {
133
+ timeout: 6e4,
134
+ severity: ["critical", "high"]
135
+ },
136
+ full: {
137
+ timeout: 3e5,
138
+ severity: ["critical", "high", "medium", "low"]
139
+ }
140
+ }
141
+ },
142
+ agent: {
143
+ format: "markdown",
144
+ includeRationale: true
145
+ }
146
+ };
147
+
148
+ // src/core/errors/index.ts
149
+ var SpecBridgeError = class extends Error {
150
+ constructor(message, code, details) {
151
+ super(message);
152
+ this.code = code;
153
+ this.details = details;
154
+ this.name = "SpecBridgeError";
155
+ Error.captureStackTrace(this, this.constructor);
156
+ }
157
+ };
158
+ var ConfigError = class extends SpecBridgeError {
159
+ constructor(message, details) {
160
+ super(message, "CONFIG_ERROR", details);
161
+ this.name = "ConfigError";
162
+ }
163
+ };
164
+ var DecisionValidationError = class extends SpecBridgeError {
165
+ constructor(message, decisionId, validationErrors) {
166
+ super(message, "DECISION_VALIDATION_ERROR", { decisionId, validationErrors });
167
+ this.decisionId = decisionId;
168
+ this.validationErrors = validationErrors;
169
+ this.name = "DecisionValidationError";
170
+ }
171
+ };
172
+ var DecisionNotFoundError = class extends SpecBridgeError {
173
+ constructor(decisionId) {
174
+ super(`Decision not found: ${decisionId}`, "DECISION_NOT_FOUND", { decisionId });
175
+ this.name = "DecisionNotFoundError";
176
+ }
177
+ };
178
+ var RegistryError = class extends SpecBridgeError {
179
+ constructor(message, details) {
180
+ super(message, "REGISTRY_ERROR", details);
181
+ this.name = "RegistryError";
182
+ }
183
+ };
184
+ var VerificationError = class extends SpecBridgeError {
185
+ constructor(message, details) {
186
+ super(message, "VERIFICATION_ERROR", details);
187
+ this.name = "VerificationError";
188
+ }
189
+ };
190
+ var InferenceError = class extends SpecBridgeError {
191
+ constructor(message, details) {
192
+ super(message, "INFERENCE_ERROR", details);
193
+ this.name = "InferenceError";
194
+ }
195
+ };
196
+ var FileSystemError = class extends SpecBridgeError {
197
+ constructor(message, path) {
198
+ super(message, "FILE_SYSTEM_ERROR", { path });
199
+ this.path = path;
200
+ this.name = "FileSystemError";
201
+ }
202
+ };
203
+ var AlreadyInitializedError = class extends SpecBridgeError {
204
+ constructor(path) {
205
+ super(`SpecBridge is already initialized at ${path}`, "ALREADY_INITIALIZED", { path });
206
+ this.name = "AlreadyInitializedError";
207
+ }
208
+ };
209
+ var NotInitializedError = class extends SpecBridgeError {
210
+ constructor() {
211
+ super(
212
+ 'SpecBridge is not initialized. Run "specbridge init" first.',
213
+ "NOT_INITIALIZED"
214
+ );
215
+ this.name = "NotInitializedError";
216
+ }
217
+ };
218
+ var VerifierNotFoundError = class extends SpecBridgeError {
219
+ constructor(verifierId) {
220
+ super(`Verifier not found: ${verifierId}`, "VERIFIER_NOT_FOUND", { verifierId });
221
+ this.name = "VerifierNotFoundError";
222
+ }
223
+ };
224
+ var AnalyzerNotFoundError = class extends SpecBridgeError {
225
+ constructor(analyzerId) {
226
+ super(`Analyzer not found: ${analyzerId}`, "ANALYZER_NOT_FOUND", { analyzerId });
227
+ this.name = "AnalyzerNotFoundError";
228
+ }
229
+ };
230
+ var HookError = class extends SpecBridgeError {
231
+ constructor(message, details) {
232
+ super(message, "HOOK_ERROR", details);
233
+ this.name = "HookError";
234
+ }
235
+ };
236
+ function formatError(error) {
237
+ if (error instanceof SpecBridgeError) {
238
+ let message = `Error [${error.code}]: ${error.message}`;
239
+ if (error.details) {
240
+ const detailsStr = Object.entries(error.details).filter(([key]) => key !== "validationErrors").map(([key, value]) => ` ${key}: ${value}`).join("\n");
241
+ if (detailsStr) {
242
+ message += `
243
+ ${detailsStr}`;
244
+ }
245
+ }
246
+ if (error instanceof DecisionValidationError && error.validationErrors.length > 0) {
247
+ message += "\nValidation errors:\n" + error.validationErrors.map((e) => ` - ${e}`).join("\n");
248
+ }
249
+ return message;
250
+ }
251
+ return `Error: ${error.message}`;
252
+ }
253
+
254
+ // src/utils/fs.ts
255
+ import { readFile, writeFile, mkdir, access, readdir, stat } from "fs/promises";
256
+ import { join, dirname } from "path";
257
+ import { constants } from "fs";
258
+ async function pathExists(path) {
259
+ try {
260
+ await access(path, constants.F_OK);
261
+ return true;
262
+ } catch {
263
+ return false;
264
+ }
265
+ }
266
+ async function isDirectory(path) {
267
+ try {
268
+ const stats = await stat(path);
269
+ return stats.isDirectory();
270
+ } catch {
271
+ return false;
272
+ }
273
+ }
274
+ async function ensureDir(path) {
275
+ await mkdir(path, { recursive: true });
276
+ }
277
+ async function readTextFile(path) {
278
+ return readFile(path, "utf-8");
279
+ }
280
+ async function writeTextFile(path, content) {
281
+ await ensureDir(dirname(path));
282
+ await writeFile(path, content, "utf-8");
283
+ }
284
+ async function readFilesInDir(dirPath, filter) {
285
+ try {
286
+ const entries = await readdir(dirPath, { withFileTypes: true });
287
+ const files = entries.filter((entry) => entry.isFile()).map((entry) => entry.name);
288
+ if (filter) {
289
+ return files.filter(filter);
290
+ }
291
+ return files;
292
+ } catch {
293
+ return [];
294
+ }
295
+ }
296
+ function getSpecBridgeDir(basePath = process.cwd()) {
297
+ return join(basePath, ".specbridge");
298
+ }
299
+ function getDecisionsDir(basePath = process.cwd()) {
300
+ return join(getSpecBridgeDir(basePath), "decisions");
301
+ }
302
+ function getVerifiersDir(basePath = process.cwd()) {
303
+ return join(getSpecBridgeDir(basePath), "verifiers");
304
+ }
305
+ function getInferredDir(basePath = process.cwd()) {
306
+ return join(getSpecBridgeDir(basePath), "inferred");
307
+ }
308
+ function getReportsDir(basePath = process.cwd()) {
309
+ return join(getSpecBridgeDir(basePath), "reports");
310
+ }
311
+ function getConfigPath(basePath = process.cwd()) {
312
+ return join(getSpecBridgeDir(basePath), "config.yaml");
313
+ }
314
+
315
+ // src/utils/yaml.ts
316
+ import { parse, stringify, parseDocument } from "yaml";
317
+ function parseYaml(content) {
318
+ return parse(content);
319
+ }
320
+ function stringifyYaml(data, options) {
321
+ return stringify(data, {
322
+ indent: options?.indent ?? 2,
323
+ lineWidth: 100,
324
+ minContentWidth: 20
325
+ });
326
+ }
327
+ function parseYamlDocument(content) {
328
+ return parseDocument(content);
329
+ }
330
+ function updateYamlDocument(doc, path, value) {
331
+ let current = doc.contents;
332
+ for (let i = 0; i < path.length - 1; i++) {
333
+ const key = path[i];
334
+ if (key && current && typeof current === "object" && "get" in current) {
335
+ current = current.get(key);
336
+ }
337
+ }
338
+ const lastKey = path[path.length - 1];
339
+ if (lastKey && current && typeof current === "object" && "set" in current) {
340
+ current.set(lastKey, value);
341
+ }
342
+ }
343
+
344
+ // src/config/loader.ts
345
+ async function loadConfig(basePath = process.cwd()) {
346
+ const specbridgeDir = getSpecBridgeDir(basePath);
347
+ const configPath = getConfigPath(basePath);
348
+ if (!await pathExists(specbridgeDir)) {
349
+ throw new NotInitializedError();
350
+ }
351
+ if (!await pathExists(configPath)) {
352
+ return defaultConfig;
353
+ }
354
+ const content = await readTextFile(configPath);
355
+ const parsed = parseYaml(content);
356
+ const result = validateConfig(parsed);
357
+ if (!result.success) {
358
+ const errors = result.errors.errors.map((e) => `${e.path.join(".")}: ${e.message}`);
359
+ throw new ConfigError(`Invalid configuration in ${configPath}`, { errors });
360
+ }
361
+ return result.data;
362
+ }
363
+ function mergeWithDefaults(partial) {
364
+ return {
365
+ ...defaultConfig,
366
+ ...partial,
367
+ project: {
368
+ ...defaultConfig.project,
369
+ ...partial.project
370
+ },
371
+ inference: {
372
+ ...defaultConfig.inference,
373
+ ...partial.inference
374
+ },
375
+ verification: {
376
+ ...defaultConfig.verification,
377
+ ...partial.verification,
378
+ levels: {
379
+ ...defaultConfig.verification?.levels,
380
+ ...partial.verification?.levels
381
+ }
382
+ },
383
+ agent: {
384
+ ...defaultConfig.agent,
385
+ ...partial.agent
386
+ }
387
+ };
388
+ }
389
+
390
+ // src/registry/loader.ts
391
+ import { join as join2 } from "path";
392
+ async function loadDecisionFile(filePath) {
393
+ if (!await pathExists(filePath)) {
394
+ throw new FileSystemError(`Decision file not found: ${filePath}`, filePath);
395
+ }
396
+ const content = await readTextFile(filePath);
397
+ const parsed = parseYaml(content);
398
+ const result = validateDecision(parsed);
399
+ if (!result.success) {
400
+ const errors = formatValidationErrors(result.errors);
401
+ throw new DecisionValidationError(
402
+ `Invalid decision file: ${filePath}`,
403
+ typeof parsed === "object" && parsed !== null && "metadata" in parsed ? parsed.metadata?.id || "unknown" : "unknown",
404
+ errors
405
+ );
406
+ }
407
+ return result.data;
408
+ }
409
+ async function loadDecisionsFromDir(dirPath) {
410
+ const decisions = [];
411
+ const errors = [];
412
+ if (!await pathExists(dirPath)) {
413
+ return { decisions, errors };
414
+ }
415
+ const files = await readFilesInDir(dirPath, (f) => f.endsWith(".decision.yaml"));
416
+ for (const file of files) {
417
+ const filePath = join2(dirPath, file);
418
+ try {
419
+ const decision = await loadDecisionFile(filePath);
420
+ decisions.push({ decision, filePath });
421
+ } catch (error) {
422
+ errors.push({
423
+ filePath,
424
+ error: error instanceof Error ? error.message : String(error)
425
+ });
426
+ }
427
+ }
428
+ return { decisions, errors };
429
+ }
430
+ async function validateDecisionFile(filePath) {
431
+ try {
432
+ if (!await pathExists(filePath)) {
433
+ return { valid: false, errors: [`File not found: ${filePath}`] };
434
+ }
435
+ const content = await readTextFile(filePath);
436
+ const parsed = parseYaml(content);
437
+ const result = validateDecision(parsed);
438
+ if (!result.success) {
439
+ return { valid: false, errors: formatValidationErrors(result.errors) };
440
+ }
441
+ return { valid: true, errors: [] };
442
+ } catch (error) {
443
+ return {
444
+ valid: false,
445
+ errors: [error instanceof Error ? error.message : String(error)]
446
+ };
447
+ }
448
+ }
449
+
450
+ // src/utils/glob.ts
451
+ import fg from "fast-glob";
452
+ import { minimatch } from "minimatch";
453
+ async function glob(patterns, options = {}) {
454
+ const {
455
+ cwd = process.cwd(),
456
+ ignore = [],
457
+ absolute = false,
458
+ onlyFiles = true
459
+ } = options;
460
+ return fg(patterns, {
461
+ cwd,
462
+ ignore,
463
+ absolute,
464
+ onlyFiles,
465
+ dot: false
466
+ });
467
+ }
468
+ function matchesPattern(filePath, pattern) {
469
+ return minimatch(filePath, pattern, { matchBase: true });
470
+ }
471
+ function matchesAnyPattern(filePath, patterns) {
472
+ return patterns.some((pattern) => matchesPattern(filePath, pattern));
473
+ }
474
+
475
+ // src/registry/registry.ts
476
+ var Registry = class {
477
+ decisions = /* @__PURE__ */ new Map();
478
+ basePath;
479
+ loaded = false;
480
+ constructor(options = {}) {
481
+ this.basePath = options.basePath || process.cwd();
482
+ }
483
+ /**
484
+ * Load all decisions from the decisions directory
485
+ */
486
+ async load() {
487
+ if (!await pathExists(getSpecBridgeDir(this.basePath))) {
488
+ throw new NotInitializedError();
489
+ }
490
+ const decisionsDir = getDecisionsDir(this.basePath);
491
+ const result = await loadDecisionsFromDir(decisionsDir);
492
+ this.decisions.clear();
493
+ for (const loaded of result.decisions) {
494
+ this.decisions.set(loaded.decision.metadata.id, loaded);
495
+ }
496
+ this.loaded = true;
497
+ return result;
498
+ }
499
+ /**
500
+ * Ensure registry is loaded
501
+ */
502
+ ensureLoaded() {
503
+ if (!this.loaded) {
504
+ throw new Error("Registry not loaded. Call load() first.");
505
+ }
506
+ }
507
+ /**
508
+ * Get all decisions
509
+ */
510
+ getAll(filter) {
511
+ this.ensureLoaded();
512
+ let decisions = Array.from(this.decisions.values()).map((d) => d.decision);
513
+ if (filter) {
514
+ decisions = this.applyFilter(decisions, filter);
515
+ }
516
+ return decisions;
517
+ }
518
+ /**
519
+ * Get active decisions only
520
+ */
521
+ getActive() {
522
+ return this.getAll({ status: ["active"] });
523
+ }
524
+ /**
525
+ * Get a decision by ID
526
+ */
527
+ get(id) {
528
+ this.ensureLoaded();
529
+ const loaded = this.decisions.get(id);
530
+ if (!loaded) {
531
+ throw new DecisionNotFoundError(id);
532
+ }
533
+ return loaded.decision;
534
+ }
535
+ /**
536
+ * Get a decision with its file path
537
+ */
538
+ getWithPath(id) {
539
+ this.ensureLoaded();
540
+ const loaded = this.decisions.get(id);
541
+ if (!loaded) {
542
+ throw new DecisionNotFoundError(id);
543
+ }
544
+ return loaded;
545
+ }
546
+ /**
547
+ * Check if a decision exists
548
+ */
549
+ has(id) {
550
+ this.ensureLoaded();
551
+ return this.decisions.has(id);
552
+ }
553
+ /**
554
+ * Get all decision IDs
555
+ */
556
+ getIds() {
557
+ this.ensureLoaded();
558
+ return Array.from(this.decisions.keys());
559
+ }
560
+ /**
561
+ * Get constraints applicable to a specific file
562
+ */
563
+ getConstraintsForFile(filePath, filter) {
564
+ this.ensureLoaded();
565
+ const applicable = [];
566
+ let decisions = this.getActive();
567
+ if (filter) {
568
+ decisions = this.applyFilter(decisions, filter);
569
+ }
570
+ for (const decision of decisions) {
571
+ for (const constraint of decision.constraints) {
572
+ if (matchesPattern(filePath, constraint.scope)) {
573
+ applicable.push({
574
+ decisionId: decision.metadata.id,
575
+ decisionTitle: decision.metadata.title,
576
+ constraintId: constraint.id,
577
+ type: constraint.type,
578
+ rule: constraint.rule,
579
+ severity: constraint.severity,
580
+ scope: constraint.scope
581
+ });
582
+ }
583
+ }
584
+ }
585
+ return applicable;
586
+ }
587
+ /**
588
+ * Get decisions by tag
589
+ */
590
+ getByTag(tag) {
591
+ return this.getAll().filter(
592
+ (d) => d.metadata.tags?.includes(tag)
593
+ );
594
+ }
595
+ /**
596
+ * Get decisions by owner
597
+ */
598
+ getByOwner(owner) {
599
+ return this.getAll().filter(
600
+ (d) => d.metadata.owners.includes(owner)
601
+ );
602
+ }
603
+ /**
604
+ * Apply filter to decisions
605
+ */
606
+ applyFilter(decisions, filter) {
607
+ return decisions.filter((decision) => {
608
+ if (filter.status && !filter.status.includes(decision.metadata.status)) {
609
+ return false;
610
+ }
611
+ if (filter.tags) {
612
+ const hasTags = filter.tags.some(
613
+ (tag) => decision.metadata.tags?.includes(tag)
614
+ );
615
+ if (!hasTags) return false;
616
+ }
617
+ if (filter.constraintType) {
618
+ const hasType = decision.constraints.some(
619
+ (c) => filter.constraintType?.includes(c.type)
620
+ );
621
+ if (!hasType) return false;
622
+ }
623
+ if (filter.severity) {
624
+ const hasSeverity = decision.constraints.some(
625
+ (c) => filter.severity?.includes(c.severity)
626
+ );
627
+ if (!hasSeverity) return false;
628
+ }
629
+ return true;
630
+ });
631
+ }
632
+ /**
633
+ * Get count of decisions by status
634
+ */
635
+ getStatusCounts() {
636
+ this.ensureLoaded();
637
+ const counts = {
638
+ draft: 0,
639
+ active: 0,
640
+ deprecated: 0,
641
+ superseded: 0
642
+ };
643
+ for (const loaded of this.decisions.values()) {
644
+ counts[loaded.decision.metadata.status]++;
645
+ }
646
+ return counts;
647
+ }
648
+ /**
649
+ * Get total constraint count
650
+ */
651
+ getConstraintCount() {
652
+ this.ensureLoaded();
653
+ let count = 0;
654
+ for (const loaded of this.decisions.values()) {
655
+ count += loaded.decision.constraints.length;
656
+ }
657
+ return count;
658
+ }
659
+ };
660
+ function createRegistry(options) {
661
+ return new Registry(options);
662
+ }
663
+
664
+ // src/inference/scanner.ts
665
+ import { Project, Node, SyntaxKind } from "ts-morph";
666
+ var CodeScanner = class {
667
+ project;
668
+ scannedFiles = /* @__PURE__ */ new Map();
669
+ constructor() {
670
+ this.project = new Project({
671
+ compilerOptions: {
672
+ allowJs: true,
673
+ checkJs: false,
674
+ noEmit: true,
675
+ skipLibCheck: true
676
+ },
677
+ skipAddingFilesFromTsConfig: true
678
+ });
679
+ }
680
+ /**
681
+ * Scan files matching the given patterns
682
+ */
683
+ async scan(options) {
684
+ const { sourceRoots, exclude = [], cwd = process.cwd() } = options;
685
+ const files = await glob(sourceRoots, {
686
+ cwd,
687
+ ignore: exclude,
688
+ absolute: true
689
+ });
690
+ for (const filePath of files) {
691
+ try {
692
+ const sourceFile = this.project.addSourceFileAtPath(filePath);
693
+ const lines = sourceFile.getEndLineNumber();
694
+ this.scannedFiles.set(filePath, {
695
+ path: filePath,
696
+ sourceFile,
697
+ lines
698
+ });
699
+ } catch {
700
+ }
701
+ }
702
+ const scannedArray = Array.from(this.scannedFiles.values());
703
+ const totalLines = scannedArray.reduce((sum, f) => sum + f.lines, 0);
704
+ return {
705
+ files: scannedArray,
706
+ totalFiles: scannedArray.length,
707
+ totalLines
708
+ };
709
+ }
710
+ /**
711
+ * Get all scanned files
712
+ */
713
+ getFiles() {
714
+ return Array.from(this.scannedFiles.values());
715
+ }
716
+ /**
717
+ * Get a specific file
718
+ */
719
+ getFile(path) {
720
+ return this.scannedFiles.get(path);
721
+ }
722
+ /**
723
+ * Get project instance for advanced analysis
724
+ */
725
+ getProject() {
726
+ return this.project;
727
+ }
728
+ /**
729
+ * Find all classes in scanned files
730
+ */
731
+ findClasses() {
732
+ const classes = [];
733
+ for (const { path, sourceFile } of this.scannedFiles.values()) {
734
+ for (const classDecl of sourceFile.getClasses()) {
735
+ const name = classDecl.getName();
736
+ if (name) {
737
+ classes.push({
738
+ file: path,
739
+ name,
740
+ line: classDecl.getStartLineNumber()
741
+ });
742
+ }
743
+ }
744
+ }
745
+ return classes;
746
+ }
747
+ /**
748
+ * Find all functions in scanned files
749
+ */
750
+ findFunctions() {
751
+ const functions = [];
752
+ for (const { path, sourceFile } of this.scannedFiles.values()) {
753
+ for (const funcDecl of sourceFile.getFunctions()) {
754
+ const name = funcDecl.getName();
755
+ if (name) {
756
+ functions.push({
757
+ file: path,
758
+ name,
759
+ line: funcDecl.getStartLineNumber(),
760
+ isExported: funcDecl.isExported()
761
+ });
762
+ }
763
+ }
764
+ for (const varDecl of sourceFile.getVariableDeclarations()) {
765
+ const init = varDecl.getInitializer();
766
+ if (init && Node.isArrowFunction(init)) {
767
+ functions.push({
768
+ file: path,
769
+ name: varDecl.getName(),
770
+ line: varDecl.getStartLineNumber(),
771
+ isExported: varDecl.isExported()
772
+ });
773
+ }
774
+ }
775
+ }
776
+ return functions;
777
+ }
778
+ /**
779
+ * Find all imports in scanned files
780
+ */
781
+ findImports() {
782
+ const imports = [];
783
+ for (const { path, sourceFile } of this.scannedFiles.values()) {
784
+ for (const importDecl of sourceFile.getImportDeclarations()) {
785
+ const module = importDecl.getModuleSpecifierValue();
786
+ const namedImports = importDecl.getNamedImports().map((n) => n.getName());
787
+ imports.push({
788
+ file: path,
789
+ module,
790
+ named: namedImports,
791
+ line: importDecl.getStartLineNumber()
792
+ });
793
+ }
794
+ }
795
+ return imports;
796
+ }
797
+ /**
798
+ * Find all interfaces in scanned files
799
+ */
800
+ findInterfaces() {
801
+ const interfaces = [];
802
+ for (const { path, sourceFile } of this.scannedFiles.values()) {
803
+ for (const interfaceDecl of sourceFile.getInterfaces()) {
804
+ interfaces.push({
805
+ file: path,
806
+ name: interfaceDecl.getName(),
807
+ line: interfaceDecl.getStartLineNumber()
808
+ });
809
+ }
810
+ }
811
+ return interfaces;
812
+ }
813
+ /**
814
+ * Find all type aliases in scanned files
815
+ */
816
+ findTypeAliases() {
817
+ const types = [];
818
+ for (const { path, sourceFile } of this.scannedFiles.values()) {
819
+ for (const typeAlias of sourceFile.getTypeAliases()) {
820
+ types.push({
821
+ file: path,
822
+ name: typeAlias.getName(),
823
+ line: typeAlias.getStartLineNumber()
824
+ });
825
+ }
826
+ }
827
+ return types;
828
+ }
829
+ /**
830
+ * Find try-catch blocks in scanned files
831
+ */
832
+ findTryCatchBlocks() {
833
+ const blocks = [];
834
+ for (const { path, sourceFile } of this.scannedFiles.values()) {
835
+ sourceFile.forEachDescendant((node) => {
836
+ if (Node.isTryStatement(node)) {
837
+ const catchClause = node.getCatchClause();
838
+ const hasThrow = catchClause ? catchClause.getDescendantsOfKind(SyntaxKind.ThrowStatement).length > 0 : false;
839
+ blocks.push({
840
+ file: path,
841
+ line: node.getStartLineNumber(),
842
+ hasThrow
843
+ });
844
+ }
845
+ });
846
+ }
847
+ return blocks;
848
+ }
849
+ };
850
+ function createScannerFromConfig(_config) {
851
+ return new CodeScanner();
852
+ }
853
+
854
+ // src/inference/analyzers/base.ts
855
+ function createPattern(analyzer, params) {
856
+ return {
857
+ analyzer,
858
+ ...params
859
+ };
860
+ }
861
+ function calculateConfidence(occurrences, total, minOccurrences = 3) {
862
+ if (occurrences < minOccurrences) {
863
+ return 0;
864
+ }
865
+ const ratio = occurrences / total;
866
+ return Math.min(100, Math.round(50 + ratio * 50));
867
+ }
868
+ function extractSnippet(content, line, contextLines = 1) {
869
+ const lines = content.split("\n");
870
+ const start = Math.max(0, line - 1 - contextLines);
871
+ const end = Math.min(lines.length, line + contextLines);
872
+ return lines.slice(start, end).join("\n");
873
+ }
874
+
875
+ // src/inference/analyzers/naming.ts
876
+ var CLASS_PATTERNS = [
877
+ { convention: "PascalCase", regex: /^[A-Z][a-zA-Z0-9]*$/, description: "Classes use PascalCase" }
878
+ ];
879
+ var FUNCTION_PATTERNS = [
880
+ { convention: "camelCase", regex: /^[a-z][a-zA-Z0-9]*$/, description: "Functions use camelCase" },
881
+ { convention: "snake_case", regex: /^[a-z][a-z0-9_]*$/, description: "Functions use snake_case" }
882
+ ];
883
+ var INTERFACE_PATTERNS = [
884
+ { convention: "PascalCase", regex: /^[A-Z][a-zA-Z0-9]*$/, description: "Interfaces use PascalCase" },
885
+ { convention: "IPrefixed", regex: /^I[A-Z][a-zA-Z0-9]*$/, description: "Interfaces are prefixed with I" }
886
+ ];
887
+ var TYPE_PATTERNS = [
888
+ { convention: "PascalCase", regex: /^[A-Z][a-zA-Z0-9]*$/, description: "Types use PascalCase" },
889
+ { convention: "TSuffixed", regex: /^[A-Z][a-zA-Z0-9]*Type$/, description: "Types are suffixed with Type" }
890
+ ];
891
+ var NamingAnalyzer = class {
892
+ id = "naming";
893
+ name = "Naming Convention Analyzer";
894
+ description = "Detects naming conventions for classes, functions, interfaces, and types";
895
+ async analyze(scanner) {
896
+ const patterns = [];
897
+ const classPattern = this.analyzeClassNaming(scanner);
898
+ if (classPattern) patterns.push(classPattern);
899
+ const functionPattern = this.analyzeFunctionNaming(scanner);
900
+ if (functionPattern) patterns.push(functionPattern);
901
+ const interfacePattern = this.analyzeInterfaceNaming(scanner);
902
+ if (interfacePattern) patterns.push(interfacePattern);
903
+ const typePattern = this.analyzeTypeNaming(scanner);
904
+ if (typePattern) patterns.push(typePattern);
905
+ return patterns;
906
+ }
907
+ analyzeClassNaming(scanner) {
908
+ const classes = scanner.findClasses();
909
+ if (classes.length < 3) return null;
910
+ const matches = this.findBestMatch(classes.map((c) => c.name), CLASS_PATTERNS);
911
+ if (!matches) return null;
912
+ return createPattern(this.id, {
913
+ id: "naming-classes",
914
+ name: "Class Naming Convention",
915
+ description: `Classes follow ${matches.convention} naming convention`,
916
+ confidence: matches.confidence,
917
+ occurrences: matches.matchCount,
918
+ examples: classes.slice(0, 3).map((c) => ({
919
+ file: c.file,
920
+ line: c.line,
921
+ snippet: `class ${c.name}`
922
+ })),
923
+ suggestedConstraint: {
924
+ type: "convention",
925
+ rule: `Classes should use ${matches.convention} naming convention`,
926
+ severity: "medium",
927
+ scope: "src/**/*.ts"
928
+ }
929
+ });
930
+ }
931
+ analyzeFunctionNaming(scanner) {
932
+ const functions = scanner.findFunctions();
933
+ if (functions.length < 3) return null;
934
+ const matches = this.findBestMatch(functions.map((f) => f.name), FUNCTION_PATTERNS);
935
+ if (!matches) return null;
936
+ return createPattern(this.id, {
937
+ id: "naming-functions",
938
+ name: "Function Naming Convention",
939
+ description: `Functions follow ${matches.convention} naming convention`,
940
+ confidence: matches.confidence,
941
+ occurrences: matches.matchCount,
942
+ examples: functions.slice(0, 3).map((f) => ({
943
+ file: f.file,
944
+ line: f.line,
945
+ snippet: `function ${f.name}`
946
+ })),
947
+ suggestedConstraint: {
948
+ type: "convention",
949
+ rule: `Functions should use ${matches.convention} naming convention`,
950
+ severity: "low",
951
+ scope: "src/**/*.ts"
952
+ }
953
+ });
954
+ }
955
+ analyzeInterfaceNaming(scanner) {
956
+ const interfaces = scanner.findInterfaces();
957
+ if (interfaces.length < 3) return null;
958
+ const matches = this.findBestMatch(interfaces.map((i) => i.name), INTERFACE_PATTERNS);
959
+ if (!matches) return null;
960
+ return createPattern(this.id, {
961
+ id: "naming-interfaces",
962
+ name: "Interface Naming Convention",
963
+ description: `Interfaces follow ${matches.convention} naming convention`,
964
+ confidence: matches.confidence,
965
+ occurrences: matches.matchCount,
966
+ examples: interfaces.slice(0, 3).map((i) => ({
967
+ file: i.file,
968
+ line: i.line,
969
+ snippet: `interface ${i.name}`
970
+ })),
971
+ suggestedConstraint: {
972
+ type: "convention",
973
+ rule: `Interfaces should use ${matches.convention} naming convention`,
974
+ severity: "low",
975
+ scope: "src/**/*.ts"
976
+ }
977
+ });
978
+ }
979
+ analyzeTypeNaming(scanner) {
980
+ const types = scanner.findTypeAliases();
981
+ if (types.length < 3) return null;
982
+ const matches = this.findBestMatch(types.map((t) => t.name), TYPE_PATTERNS);
983
+ if (!matches) return null;
984
+ return createPattern(this.id, {
985
+ id: "naming-types",
986
+ name: "Type Alias Naming Convention",
987
+ description: `Type aliases follow ${matches.convention} naming convention`,
988
+ confidence: matches.confidence,
989
+ occurrences: matches.matchCount,
990
+ examples: types.slice(0, 3).map((t) => ({
991
+ file: t.file,
992
+ line: t.line,
993
+ snippet: `type ${t.name}`
994
+ })),
995
+ suggestedConstraint: {
996
+ type: "guideline",
997
+ rule: `Type aliases should use ${matches.convention} naming convention`,
998
+ severity: "low",
999
+ scope: "src/**/*.ts"
1000
+ }
1001
+ });
1002
+ }
1003
+ findBestMatch(names, patterns) {
1004
+ let bestMatch = null;
1005
+ for (const pattern of patterns) {
1006
+ const matchCount = names.filter((name) => pattern.regex.test(name)).length;
1007
+ if (!bestMatch || matchCount > bestMatch.matchCount) {
1008
+ bestMatch = { convention: pattern.convention, matchCount };
1009
+ }
1010
+ }
1011
+ if (!bestMatch || bestMatch.matchCount < 3) return null;
1012
+ const confidence = calculateConfidence(bestMatch.matchCount, names.length);
1013
+ if (confidence < 50) return null;
1014
+ return {
1015
+ convention: bestMatch.convention,
1016
+ confidence,
1017
+ matchCount: bestMatch.matchCount
1018
+ };
1019
+ }
1020
+ };
1021
+
1022
+ // src/inference/analyzers/imports.ts
1023
+ var ImportsAnalyzer = class {
1024
+ id = "imports";
1025
+ name = "Import Pattern Analyzer";
1026
+ description = "Detects import organization patterns and module usage conventions";
1027
+ async analyze(scanner) {
1028
+ const patterns = [];
1029
+ const barrelPattern = this.analyzeBarrelImports(scanner);
1030
+ if (barrelPattern) patterns.push(barrelPattern);
1031
+ const relativePattern = this.analyzeRelativeImports(scanner);
1032
+ if (relativePattern) patterns.push(relativePattern);
1033
+ const modulePatterns = this.analyzeCommonModules(scanner);
1034
+ patterns.push(...modulePatterns);
1035
+ return patterns;
1036
+ }
1037
+ analyzeBarrelImports(scanner) {
1038
+ const imports = scanner.findImports();
1039
+ const barrelImports = imports.filter((i) => {
1040
+ const modulePath = i.module;
1041
+ return modulePath.startsWith(".") && !modulePath.includes(".js") && !modulePath.includes(".ts");
1042
+ });
1043
+ const indexImports = barrelImports.filter((i) => {
1044
+ return i.module.endsWith("/index") || !i.module.includes("/");
1045
+ });
1046
+ if (indexImports.length < 3) return null;
1047
+ const confidence = calculateConfidence(indexImports.length, barrelImports.length);
1048
+ if (confidence < 50) return null;
1049
+ return createPattern(this.id, {
1050
+ id: "imports-barrel",
1051
+ name: "Barrel Import Pattern",
1052
+ description: "Modules are imported through barrel (index) files",
1053
+ confidence,
1054
+ occurrences: indexImports.length,
1055
+ examples: indexImports.slice(0, 3).map((i) => ({
1056
+ file: i.file,
1057
+ line: i.line,
1058
+ snippet: `import { ${i.named.slice(0, 3).join(", ")} } from '${i.module}'`
1059
+ })),
1060
+ suggestedConstraint: {
1061
+ type: "convention",
1062
+ rule: "Import from barrel (index) files rather than individual modules",
1063
+ severity: "low",
1064
+ scope: "src/**/*.ts"
1065
+ }
1066
+ });
1067
+ }
1068
+ analyzeRelativeImports(scanner) {
1069
+ const imports = scanner.findImports();
1070
+ const relativeImports = imports.filter((i) => i.module.startsWith("."));
1071
+ const absoluteImports = imports.filter((i) => !i.module.startsWith(".") && !i.module.startsWith("@"));
1072
+ const aliasImports = imports.filter((i) => i.module.startsWith("@/") || i.module.startsWith("~"));
1073
+ const total = relativeImports.length + absoluteImports.length + aliasImports.length;
1074
+ if (total < 10) return null;
1075
+ if (aliasImports.length > relativeImports.length && aliasImports.length >= 5) {
1076
+ const confidence = calculateConfidence(aliasImports.length, total);
1077
+ if (confidence < 50) return null;
1078
+ return createPattern(this.id, {
1079
+ id: "imports-alias",
1080
+ name: "Path Alias Import Pattern",
1081
+ description: "Imports use path aliases (@ or ~) instead of relative paths",
1082
+ confidence,
1083
+ occurrences: aliasImports.length,
1084
+ examples: aliasImports.slice(0, 3).map((i) => ({
1085
+ file: i.file,
1086
+ line: i.line,
1087
+ snippet: `import { ${i.named.slice(0, 2).join(", ")} } from '${i.module}'`
1088
+ })),
1089
+ suggestedConstraint: {
1090
+ type: "convention",
1091
+ rule: "Use path aliases instead of relative imports",
1092
+ severity: "low",
1093
+ scope: "src/**/*.ts"
1094
+ }
1095
+ });
1096
+ }
1097
+ if (relativeImports.length > aliasImports.length * 2 && relativeImports.length >= 5) {
1098
+ const confidence = calculateConfidence(relativeImports.length, total);
1099
+ if (confidence < 50) return null;
1100
+ return createPattern(this.id, {
1101
+ id: "imports-relative",
1102
+ name: "Relative Import Pattern",
1103
+ description: "Imports use relative paths",
1104
+ confidence,
1105
+ occurrences: relativeImports.length,
1106
+ examples: relativeImports.slice(0, 3).map((i) => ({
1107
+ file: i.file,
1108
+ line: i.line,
1109
+ snippet: `import { ${i.named.slice(0, 2).join(", ")} } from '${i.module}'`
1110
+ })),
1111
+ suggestedConstraint: {
1112
+ type: "guideline",
1113
+ rule: "Use relative imports for local modules",
1114
+ severity: "low",
1115
+ scope: "src/**/*.ts"
1116
+ }
1117
+ });
1118
+ }
1119
+ return null;
1120
+ }
1121
+ analyzeCommonModules(scanner) {
1122
+ const patterns = [];
1123
+ const imports = scanner.findImports();
1124
+ const moduleCounts = /* @__PURE__ */ new Map();
1125
+ for (const imp of imports) {
1126
+ if (imp.module.startsWith(".")) continue;
1127
+ const parts = imp.module.split("/");
1128
+ const packageName = imp.module.startsWith("@") && parts.length > 1 ? `${parts[0]}/${parts[1]}` : parts[0];
1129
+ if (packageName) {
1130
+ const existing = moduleCounts.get(packageName) || { count: 0, examples: [] };
1131
+ existing.count++;
1132
+ existing.examples.push(imp);
1133
+ moduleCounts.set(packageName, existing);
1134
+ }
1135
+ }
1136
+ for (const [packageName, data] of moduleCounts) {
1137
+ if (data.count >= 5) {
1138
+ const confidence = Math.min(100, 50 + data.count * 2);
1139
+ patterns.push(createPattern(this.id, {
1140
+ id: `imports-module-${packageName.replace(/[/@]/g, "-")}`,
1141
+ name: `${packageName} Usage`,
1142
+ description: `${packageName} is used across ${data.count} files`,
1143
+ confidence,
1144
+ occurrences: data.count,
1145
+ examples: data.examples.slice(0, 3).map((i) => ({
1146
+ file: i.file,
1147
+ line: i.line,
1148
+ snippet: `import { ${i.named.slice(0, 2).join(", ") || "..."} } from '${i.module}'`
1149
+ }))
1150
+ }));
1151
+ }
1152
+ }
1153
+ return patterns;
1154
+ }
1155
+ };
1156
+
1157
+ // src/inference/analyzers/structure.ts
1158
+ import { basename, dirname as dirname2 } from "path";
1159
+ var StructureAnalyzer = class {
1160
+ id = "structure";
1161
+ name = "Code Structure Analyzer";
1162
+ description = "Detects file organization and directory structure patterns";
1163
+ async analyze(scanner) {
1164
+ const patterns = [];
1165
+ const files = scanner.getFiles();
1166
+ const dirPatterns = this.analyzeDirectoryConventions(files);
1167
+ patterns.push(...dirPatterns);
1168
+ const filePatterns = this.analyzeFileNaming(files);
1169
+ patterns.push(...filePatterns);
1170
+ const colocationPattern = this.analyzeColocation(files);
1171
+ if (colocationPattern) patterns.push(colocationPattern);
1172
+ return patterns;
1173
+ }
1174
+ analyzeDirectoryConventions(files) {
1175
+ const patterns = [];
1176
+ const dirCounts = /* @__PURE__ */ new Map();
1177
+ for (const file of files) {
1178
+ const dir = basename(dirname2(file.path));
1179
+ dirCounts.set(dir, (dirCounts.get(dir) || 0) + 1);
1180
+ }
1181
+ const commonDirs = [
1182
+ { name: "components", description: "UI components are organized in a components directory" },
1183
+ { name: "hooks", description: "Custom hooks are organized in a hooks directory" },
1184
+ { name: "utils", description: "Utility functions are organized in a utils directory" },
1185
+ { name: "services", description: "Service modules are organized in a services directory" },
1186
+ { name: "types", description: "Type definitions are organized in a types directory" },
1187
+ { name: "api", description: "API modules are organized in an api directory" },
1188
+ { name: "lib", description: "Library code is organized in a lib directory" },
1189
+ { name: "core", description: "Core modules are organized in a core directory" }
1190
+ ];
1191
+ for (const { name, description } of commonDirs) {
1192
+ const count = dirCounts.get(name);
1193
+ if (count && count >= 3) {
1194
+ const exampleFiles = files.filter((f) => basename(dirname2(f.path)) === name).slice(0, 3);
1195
+ patterns.push(createPattern(this.id, {
1196
+ id: `structure-dir-${name}`,
1197
+ name: `${name}/ Directory Convention`,
1198
+ description,
1199
+ confidence: Math.min(100, 60 + count * 5),
1200
+ occurrences: count,
1201
+ examples: exampleFiles.map((f) => ({
1202
+ file: f.path,
1203
+ line: 1,
1204
+ snippet: basename(f.path)
1205
+ })),
1206
+ suggestedConstraint: {
1207
+ type: "convention",
1208
+ rule: `${name.charAt(0).toUpperCase() + name.slice(1)} should be placed in the ${name}/ directory`,
1209
+ severity: "low",
1210
+ scope: `src/**/${name}/**/*.ts`
1211
+ }
1212
+ }));
1213
+ }
1214
+ }
1215
+ return patterns;
1216
+ }
1217
+ analyzeFileNaming(files) {
1218
+ const patterns = [];
1219
+ const suffixPatterns = [
1220
+ { suffix: ".test.ts", pattern: /\.test\.ts$/, description: "Test files use .test.ts suffix" },
1221
+ { suffix: ".spec.ts", pattern: /\.spec\.ts$/, description: "Test files use .spec.ts suffix" },
1222
+ { suffix: ".types.ts", pattern: /\.types\.ts$/, description: "Type definition files use .types.ts suffix" },
1223
+ { suffix: ".utils.ts", pattern: /\.utils\.ts$/, description: "Utility files use .utils.ts suffix" },
1224
+ { suffix: ".service.ts", pattern: /\.service\.ts$/, description: "Service files use .service.ts suffix" },
1225
+ { suffix: ".controller.ts", pattern: /\.controller\.ts$/, description: "Controller files use .controller.ts suffix" },
1226
+ { suffix: ".model.ts", pattern: /\.model\.ts$/, description: "Model files use .model.ts suffix" },
1227
+ { suffix: ".schema.ts", pattern: /\.schema\.ts$/, description: "Schema files use .schema.ts suffix" }
1228
+ ];
1229
+ for (const { suffix, pattern, description } of suffixPatterns) {
1230
+ const matchingFiles = files.filter((f) => pattern.test(f.path));
1231
+ if (matchingFiles.length >= 3) {
1232
+ const confidence = Math.min(100, 60 + matchingFiles.length * 3);
1233
+ patterns.push(createPattern(this.id, {
1234
+ id: `structure-suffix-${suffix.replace(/\./g, "-")}`,
1235
+ name: `${suffix} File Naming`,
1236
+ description,
1237
+ confidence,
1238
+ occurrences: matchingFiles.length,
1239
+ examples: matchingFiles.slice(0, 3).map((f) => ({
1240
+ file: f.path,
1241
+ line: 1,
1242
+ snippet: basename(f.path)
1243
+ }))
1244
+ }));
1245
+ }
1246
+ }
1247
+ return patterns;
1248
+ }
1249
+ analyzeColocation(files) {
1250
+ const testFiles = files.filter((f) => /\.(test|spec)\.tsx?$/.test(f.path));
1251
+ const sourceFiles = files.filter((f) => !/\.(test|spec)\.tsx?$/.test(f.path));
1252
+ if (testFiles.length < 3) return null;
1253
+ let colocatedCount = 0;
1254
+ const colocatedExamples = [];
1255
+ for (const testFile of testFiles) {
1256
+ const testDir = dirname2(testFile.path);
1257
+ const testName = basename(testFile.path).replace(/\.(test|spec)\.tsx?$/, "");
1258
+ const hasColocatedSource = sourceFiles.some(
1259
+ (s) => dirname2(s.path) === testDir && basename(s.path).startsWith(testName)
1260
+ );
1261
+ if (hasColocatedSource) {
1262
+ colocatedCount++;
1263
+ if (colocatedExamples.length < 3) {
1264
+ colocatedExamples.push({
1265
+ file: testFile.path,
1266
+ line: 1,
1267
+ snippet: basename(testFile.path)
1268
+ });
1269
+ }
1270
+ }
1271
+ }
1272
+ const confidence = calculateConfidence(colocatedCount, testFiles.length);
1273
+ if (confidence < 60) return null;
1274
+ return createPattern(this.id, {
1275
+ id: "structure-colocation",
1276
+ name: "Test Colocation Pattern",
1277
+ description: "Test files are colocated with their source files",
1278
+ confidence,
1279
+ occurrences: colocatedCount,
1280
+ examples: colocatedExamples,
1281
+ suggestedConstraint: {
1282
+ type: "guideline",
1283
+ rule: "Test files should be colocated with their source files",
1284
+ severity: "low",
1285
+ scope: "src/**/*.test.ts"
1286
+ }
1287
+ });
1288
+ }
1289
+ };
1290
+
1291
+ // src/inference/analyzers/errors.ts
1292
+ import { Node as Node2 } from "ts-morph";
1293
+ var ErrorsAnalyzer = class {
1294
+ id = "errors";
1295
+ name = "Error Handling Analyzer";
1296
+ description = "Detects error handling patterns and custom error class usage";
1297
+ async analyze(scanner) {
1298
+ const patterns = [];
1299
+ const errorClassPattern = this.analyzeCustomErrorClasses(scanner);
1300
+ if (errorClassPattern) patterns.push(errorClassPattern);
1301
+ const tryCatchPattern = this.analyzeTryCatchPatterns(scanner);
1302
+ if (tryCatchPattern) patterns.push(tryCatchPattern);
1303
+ const throwPattern = this.analyzeThrowPatterns(scanner);
1304
+ if (throwPattern) patterns.push(throwPattern);
1305
+ return patterns;
1306
+ }
1307
+ analyzeCustomErrorClasses(scanner) {
1308
+ const classes = scanner.findClasses();
1309
+ const errorClasses = classes.filter(
1310
+ (c) => c.name.endsWith("Error") || c.name.endsWith("Exception")
1311
+ );
1312
+ if (errorClasses.length < 2) return null;
1313
+ const files = scanner.getFiles();
1314
+ let extendsError = 0;
1315
+ let extendsCustomBase = 0;
1316
+ let customBaseName = null;
1317
+ for (const errorClass of errorClasses) {
1318
+ const file = files.find((f) => f.path === errorClass.file);
1319
+ if (!file) continue;
1320
+ const classDecl = file.sourceFile.getClass(errorClass.name);
1321
+ if (!classDecl) continue;
1322
+ const extendClause = classDecl.getExtends();
1323
+ if (extendClause) {
1324
+ const baseName = extendClause.getText();
1325
+ if (baseName === "Error") {
1326
+ extendsError++;
1327
+ } else if (baseName.endsWith("Error")) {
1328
+ extendsCustomBase++;
1329
+ customBaseName = customBaseName || baseName;
1330
+ }
1331
+ }
1332
+ }
1333
+ if (extendsCustomBase >= 2 && customBaseName) {
1334
+ const confidence = calculateConfidence(extendsCustomBase, errorClasses.length);
1335
+ return createPattern(this.id, {
1336
+ id: "errors-custom-base",
1337
+ name: "Custom Error Base Class",
1338
+ description: `Custom errors extend a common base class (${customBaseName})`,
1339
+ confidence,
1340
+ occurrences: extendsCustomBase,
1341
+ examples: errorClasses.slice(0, 3).map((c) => ({
1342
+ file: c.file,
1343
+ line: c.line,
1344
+ snippet: `class ${c.name} extends ${customBaseName}`
1345
+ })),
1346
+ suggestedConstraint: {
1347
+ type: "convention",
1348
+ rule: `Custom error classes should extend ${customBaseName}`,
1349
+ severity: "medium",
1350
+ scope: "src/**/*.ts",
1351
+ verifier: "error-hierarchy"
1352
+ }
1353
+ });
1354
+ }
1355
+ if (errorClasses.length >= 3) {
1356
+ const confidence = Math.min(100, 50 + errorClasses.length * 5);
1357
+ return createPattern(this.id, {
1358
+ id: "errors-custom-classes",
1359
+ name: "Custom Error Classes",
1360
+ description: "Custom error classes are used for domain-specific errors",
1361
+ confidence,
1362
+ occurrences: errorClasses.length,
1363
+ examples: errorClasses.slice(0, 3).map((c) => ({
1364
+ file: c.file,
1365
+ line: c.line,
1366
+ snippet: `class ${c.name}`
1367
+ })),
1368
+ suggestedConstraint: {
1369
+ type: "guideline",
1370
+ rule: "Use custom error classes for domain-specific errors",
1371
+ severity: "low",
1372
+ scope: "src/**/*.ts"
1373
+ }
1374
+ });
1375
+ }
1376
+ return null;
1377
+ }
1378
+ analyzeTryCatchPatterns(scanner) {
1379
+ const tryCatchBlocks = scanner.findTryCatchBlocks();
1380
+ if (tryCatchBlocks.length < 3) return null;
1381
+ const rethrowCount = tryCatchBlocks.filter((b) => b.hasThrow).length;
1382
+ const swallowCount = tryCatchBlocks.length - rethrowCount;
1383
+ if (rethrowCount >= 3 && rethrowCount > swallowCount) {
1384
+ const confidence = calculateConfidence(rethrowCount, tryCatchBlocks.length);
1385
+ return createPattern(this.id, {
1386
+ id: "errors-rethrow",
1387
+ name: "Error Rethrow Pattern",
1388
+ description: "Caught errors are typically rethrown after handling",
1389
+ confidence,
1390
+ occurrences: rethrowCount,
1391
+ examples: tryCatchBlocks.filter((b) => b.hasThrow).slice(0, 3).map((b) => ({
1392
+ file: b.file,
1393
+ line: b.line,
1394
+ snippet: "try { ... } catch (e) { ... throw ... }"
1395
+ })),
1396
+ suggestedConstraint: {
1397
+ type: "guideline",
1398
+ rule: "Caught errors should be rethrown or wrapped after handling",
1399
+ severity: "low",
1400
+ scope: "src/**/*.ts"
1401
+ }
1402
+ });
1403
+ }
1404
+ return null;
1405
+ }
1406
+ analyzeThrowPatterns(scanner) {
1407
+ const files = scanner.getFiles();
1408
+ let throwNewError = 0;
1409
+ let throwCustom = 0;
1410
+ const examples = [];
1411
+ for (const { path, sourceFile } of files) {
1412
+ sourceFile.forEachDescendant((node) => {
1413
+ if (Node2.isThrowStatement(node)) {
1414
+ const expression = node.getExpression();
1415
+ if (expression) {
1416
+ const text = expression.getText();
1417
+ if (text.startsWith("new Error(")) {
1418
+ throwNewError++;
1419
+ } else if (text.startsWith("new ") && text.includes("Error")) {
1420
+ throwCustom++;
1421
+ if (examples.length < 3) {
1422
+ examples.push({
1423
+ file: path,
1424
+ line: node.getStartLineNumber(),
1425
+ snippet: `throw ${text.slice(0, 50)}${text.length > 50 ? "..." : ""}`
1426
+ });
1427
+ }
1428
+ }
1429
+ }
1430
+ }
1431
+ });
1432
+ }
1433
+ if (throwCustom >= 3 && throwCustom > throwNewError) {
1434
+ const confidence = calculateConfidence(throwCustom, throwCustom + throwNewError);
1435
+ return createPattern(this.id, {
1436
+ id: "errors-throw-custom",
1437
+ name: "Custom Error Throwing",
1438
+ description: "Custom error classes are thrown instead of generic Error",
1439
+ confidence,
1440
+ occurrences: throwCustom,
1441
+ examples,
1442
+ suggestedConstraint: {
1443
+ type: "convention",
1444
+ rule: "Throw custom error classes instead of generic Error",
1445
+ severity: "medium",
1446
+ scope: "src/**/*.ts",
1447
+ verifier: "custom-errors-only"
1448
+ }
1449
+ });
1450
+ }
1451
+ return null;
1452
+ }
1453
+ };
1454
+
1455
+ // src/inference/analyzers/index.ts
1456
+ var builtinAnalyzers = {
1457
+ naming: () => new NamingAnalyzer(),
1458
+ imports: () => new ImportsAnalyzer(),
1459
+ structure: () => new StructureAnalyzer(),
1460
+ errors: () => new ErrorsAnalyzer()
1461
+ };
1462
+ function getAnalyzer(id) {
1463
+ const factory = builtinAnalyzers[id];
1464
+ return factory ? factory() : null;
1465
+ }
1466
+ function getAnalyzerIds() {
1467
+ return Object.keys(builtinAnalyzers);
1468
+ }
1469
+
1470
+ // src/inference/engine.ts
1471
+ var InferenceEngine = class {
1472
+ scanner;
1473
+ analyzers = [];
1474
+ constructor() {
1475
+ this.scanner = new CodeScanner();
1476
+ }
1477
+ /**
1478
+ * Configure analyzers to use
1479
+ */
1480
+ configureAnalyzers(analyzerIds) {
1481
+ this.analyzers = [];
1482
+ for (const id of analyzerIds) {
1483
+ const analyzer = getAnalyzer(id);
1484
+ if (!analyzer) {
1485
+ throw new AnalyzerNotFoundError(id);
1486
+ }
1487
+ this.analyzers.push(analyzer);
1488
+ }
1489
+ }
1490
+ /**
1491
+ * Run inference on the codebase
1492
+ */
1493
+ async infer(options) {
1494
+ const startTime = Date.now();
1495
+ const {
1496
+ analyzers: analyzerIds = getAnalyzerIds(),
1497
+ minConfidence = 50,
1498
+ sourceRoots = ["src/**/*.ts", "src/**/*.tsx"],
1499
+ exclude = ["**/*.test.ts", "**/*.spec.ts", "**/node_modules/**", "**/dist/**"],
1500
+ cwd = process.cwd()
1501
+ } = options;
1502
+ this.configureAnalyzers(analyzerIds);
1503
+ const scanResult = await this.scanner.scan({
1504
+ sourceRoots,
1505
+ exclude,
1506
+ cwd
1507
+ });
1508
+ if (scanResult.totalFiles === 0) {
1509
+ return {
1510
+ patterns: [],
1511
+ analyzersRun: analyzerIds,
1512
+ filesScanned: 0,
1513
+ duration: Date.now() - startTime
1514
+ };
1515
+ }
1516
+ const allPatterns = [];
1517
+ for (const analyzer of this.analyzers) {
1518
+ try {
1519
+ const patterns = await analyzer.analyze(this.scanner);
1520
+ allPatterns.push(...patterns);
1521
+ } catch (error) {
1522
+ console.warn(`Analyzer ${analyzer.id} failed:`, error);
1523
+ }
1524
+ }
1525
+ const filteredPatterns = allPatterns.filter((p) => p.confidence >= minConfidence);
1526
+ filteredPatterns.sort((a, b) => b.confidence - a.confidence);
1527
+ return {
1528
+ patterns: filteredPatterns,
1529
+ analyzersRun: analyzerIds,
1530
+ filesScanned: scanResult.totalFiles,
1531
+ duration: Date.now() - startTime
1532
+ };
1533
+ }
1534
+ /**
1535
+ * Get scanner for direct access
1536
+ */
1537
+ getScanner() {
1538
+ return this.scanner;
1539
+ }
1540
+ };
1541
+ function createInferenceEngine() {
1542
+ return new InferenceEngine();
1543
+ }
1544
+ async function runInference(config, options) {
1545
+ const engine = createInferenceEngine();
1546
+ return engine.infer({
1547
+ analyzers: config.inference?.analyzers,
1548
+ minConfidence: config.inference?.minConfidence,
1549
+ sourceRoots: config.project.sourceRoots,
1550
+ exclude: config.project.exclude,
1551
+ ...options
1552
+ });
1553
+ }
1554
+
1555
+ // src/verification/engine.ts
1556
+ import { Project as Project2 } from "ts-morph";
1557
+
1558
+ // src/verification/verifiers/base.ts
1559
+ function createViolation(params) {
1560
+ return params;
1561
+ }
1562
+
1563
+ // src/verification/verifiers/naming.ts
1564
+ var NAMING_PATTERNS = {
1565
+ PascalCase: {
1566
+ regex: /^[A-Z][a-zA-Z0-9]*$/,
1567
+ description: "PascalCase (e.g., MyClass)"
1568
+ },
1569
+ camelCase: {
1570
+ regex: /^[a-z][a-zA-Z0-9]*$/,
1571
+ description: "camelCase (e.g., myFunction)"
1572
+ },
1573
+ UPPER_SNAKE_CASE: {
1574
+ regex: /^[A-Z][A-Z0-9_]*$/,
1575
+ description: "UPPER_SNAKE_CASE (e.g., MAX_VALUE)"
1576
+ },
1577
+ snake_case: {
1578
+ regex: /^[a-z][a-z0-9_]*$/,
1579
+ description: "snake_case (e.g., my_variable)"
1580
+ },
1581
+ "kebab-case": {
1582
+ regex: /^[a-z][a-z0-9-]*$/,
1583
+ description: "kebab-case (e.g., my-component)"
1584
+ }
1585
+ };
1586
+ var NamingVerifier = class {
1587
+ id = "naming";
1588
+ name = "Naming Convention Verifier";
1589
+ description = "Verifies naming conventions for classes, functions, and variables";
1590
+ async verify(ctx) {
1591
+ const violations = [];
1592
+ const { sourceFile, constraint, decisionId, filePath } = ctx;
1593
+ const rule = constraint.rule.toLowerCase();
1594
+ let convention = null;
1595
+ let targetType = null;
1596
+ for (const [name] of Object.entries(NAMING_PATTERNS)) {
1597
+ if (rule.includes(name.toLowerCase())) {
1598
+ convention = name;
1599
+ break;
1600
+ }
1601
+ }
1602
+ if (rule.includes("class")) targetType = "class";
1603
+ else if (rule.includes("function")) targetType = "function";
1604
+ else if (rule.includes("interface")) targetType = "interface";
1605
+ else if (rule.includes("type")) targetType = "type";
1606
+ if (!convention || !targetType) {
1607
+ return violations;
1608
+ }
1609
+ const pattern = NAMING_PATTERNS[convention];
1610
+ if (!pattern) return violations;
1611
+ if (targetType === "class") {
1612
+ for (const classDecl of sourceFile.getClasses()) {
1613
+ const name = classDecl.getName();
1614
+ if (name && !pattern.regex.test(name)) {
1615
+ violations.push(createViolation({
1616
+ decisionId,
1617
+ constraintId: constraint.id,
1618
+ type: constraint.type,
1619
+ severity: constraint.severity,
1620
+ message: `Class "${name}" does not follow ${pattern.description} naming convention`,
1621
+ file: filePath,
1622
+ line: classDecl.getStartLineNumber(),
1623
+ column: classDecl.getStart() - classDecl.getStartLinePos(),
1624
+ suggestion: `Rename to follow ${pattern.description}`
1625
+ }));
1626
+ }
1627
+ }
1628
+ }
1629
+ if (targetType === "function") {
1630
+ for (const funcDecl of sourceFile.getFunctions()) {
1631
+ const name = funcDecl.getName();
1632
+ if (name && !pattern.regex.test(name)) {
1633
+ violations.push(createViolation({
1634
+ decisionId,
1635
+ constraintId: constraint.id,
1636
+ type: constraint.type,
1637
+ severity: constraint.severity,
1638
+ message: `Function "${name}" does not follow ${pattern.description} naming convention`,
1639
+ file: filePath,
1640
+ line: funcDecl.getStartLineNumber(),
1641
+ suggestion: `Rename to follow ${pattern.description}`
1642
+ }));
1643
+ }
1644
+ }
1645
+ }
1646
+ if (targetType === "interface") {
1647
+ for (const interfaceDecl of sourceFile.getInterfaces()) {
1648
+ const name = interfaceDecl.getName();
1649
+ if (!pattern.regex.test(name)) {
1650
+ violations.push(createViolation({
1651
+ decisionId,
1652
+ constraintId: constraint.id,
1653
+ type: constraint.type,
1654
+ severity: constraint.severity,
1655
+ message: `Interface "${name}" does not follow ${pattern.description} naming convention`,
1656
+ file: filePath,
1657
+ line: interfaceDecl.getStartLineNumber(),
1658
+ suggestion: `Rename to follow ${pattern.description}`
1659
+ }));
1660
+ }
1661
+ }
1662
+ }
1663
+ if (targetType === "type") {
1664
+ for (const typeAlias of sourceFile.getTypeAliases()) {
1665
+ const name = typeAlias.getName();
1666
+ if (!pattern.regex.test(name)) {
1667
+ violations.push(createViolation({
1668
+ decisionId,
1669
+ constraintId: constraint.id,
1670
+ type: constraint.type,
1671
+ severity: constraint.severity,
1672
+ message: `Type "${name}" does not follow ${pattern.description} naming convention`,
1673
+ file: filePath,
1674
+ line: typeAlias.getStartLineNumber(),
1675
+ suggestion: `Rename to follow ${pattern.description}`
1676
+ }));
1677
+ }
1678
+ }
1679
+ }
1680
+ return violations;
1681
+ }
1682
+ };
1683
+
1684
+ // src/verification/verifiers/imports.ts
1685
+ var ImportsVerifier = class {
1686
+ id = "imports";
1687
+ name = "Import Pattern Verifier";
1688
+ description = "Verifies import patterns like barrel imports, path aliases, etc.";
1689
+ async verify(ctx) {
1690
+ const violations = [];
1691
+ const { sourceFile, constraint, decisionId, filePath } = ctx;
1692
+ const rule = constraint.rule.toLowerCase();
1693
+ if (rule.includes("barrel") || rule.includes("index")) {
1694
+ for (const importDecl of sourceFile.getImportDeclarations()) {
1695
+ const moduleSpec = importDecl.getModuleSpecifierValue();
1696
+ if (!moduleSpec.startsWith(".")) continue;
1697
+ if (moduleSpec.match(/\.(ts|js|tsx|jsx)$/) || moduleSpec.match(/\/[^/]+$/)) {
1698
+ if (!moduleSpec.endsWith("/index") && !moduleSpec.endsWith("index")) {
1699
+ violations.push(createViolation({
1700
+ decisionId,
1701
+ constraintId: constraint.id,
1702
+ type: constraint.type,
1703
+ severity: constraint.severity,
1704
+ message: `Import from "${moduleSpec}" should use barrel (index) import`,
1705
+ file: filePath,
1706
+ line: importDecl.getStartLineNumber(),
1707
+ suggestion: "Import from the parent directory index file instead"
1708
+ }));
1709
+ }
1710
+ }
1711
+ }
1712
+ }
1713
+ if (rule.includes("alias") || rule.includes("@/") || rule.includes("path alias")) {
1714
+ for (const importDecl of sourceFile.getImportDeclarations()) {
1715
+ const moduleSpec = importDecl.getModuleSpecifierValue();
1716
+ if (moduleSpec.match(/^\.\.\/\.\.\/\.\.\//)) {
1717
+ violations.push(createViolation({
1718
+ decisionId,
1719
+ constraintId: constraint.id,
1720
+ type: constraint.type,
1721
+ severity: constraint.severity,
1722
+ message: `Deep relative import "${moduleSpec}" should use path alias`,
1723
+ file: filePath,
1724
+ line: importDecl.getStartLineNumber(),
1725
+ suggestion: "Use path alias (e.g., @/module) for deep imports"
1726
+ }));
1727
+ }
1728
+ }
1729
+ }
1730
+ if (rule.includes("circular") || rule.includes("cycle")) {
1731
+ const currentFilename = filePath.replace(/\.[jt]sx?$/, "");
1732
+ for (const importDecl of sourceFile.getImportDeclarations()) {
1733
+ const moduleSpec = importDecl.getModuleSpecifierValue();
1734
+ if (moduleSpec.includes(currentFilename.split("/").pop() || "")) {
1735
+ violations.push(createViolation({
1736
+ decisionId,
1737
+ constraintId: constraint.id,
1738
+ type: constraint.type,
1739
+ severity: constraint.severity,
1740
+ message: `Possible circular import detected: "${moduleSpec}"`,
1741
+ file: filePath,
1742
+ line: importDecl.getStartLineNumber(),
1743
+ suggestion: "Review import structure for circular dependencies"
1744
+ }));
1745
+ }
1746
+ }
1747
+ }
1748
+ if (rule.includes("wildcard") || rule.includes("* as") || rule.includes("no namespace")) {
1749
+ for (const importDecl of sourceFile.getImportDeclarations()) {
1750
+ const namespaceImport = importDecl.getNamespaceImport();
1751
+ if (namespaceImport) {
1752
+ violations.push(createViolation({
1753
+ decisionId,
1754
+ constraintId: constraint.id,
1755
+ type: constraint.type,
1756
+ severity: constraint.severity,
1757
+ message: `Namespace import "* as ${namespaceImport.getText()}" should use named imports`,
1758
+ file: filePath,
1759
+ line: importDecl.getStartLineNumber(),
1760
+ suggestion: "Use specific named imports instead of namespace import"
1761
+ }));
1762
+ }
1763
+ }
1764
+ }
1765
+ return violations;
1766
+ }
1767
+ };
1768
+
1769
+ // src/verification/verifiers/errors.ts
1770
+ import { Node as Node3 } from "ts-morph";
1771
+ var ErrorsVerifier = class {
1772
+ id = "errors";
1773
+ name = "Error Handling Verifier";
1774
+ description = "Verifies error handling patterns";
1775
+ async verify(ctx) {
1776
+ const violations = [];
1777
+ const { sourceFile, constraint, decisionId, filePath } = ctx;
1778
+ const rule = constraint.rule.toLowerCase();
1779
+ if (rule.includes("extend") || rule.includes("base") || rule.includes("hierarchy")) {
1780
+ const baseClassMatch = rule.match(/extend\s+(\w+)/i) || rule.match(/(\w+Error)\s+class/i);
1781
+ const requiredBase = baseClassMatch ? baseClassMatch[1] : null;
1782
+ for (const classDecl of sourceFile.getClasses()) {
1783
+ const className = classDecl.getName();
1784
+ if (!className?.endsWith("Error") && !className?.endsWith("Exception")) continue;
1785
+ const extendsClause = classDecl.getExtends();
1786
+ if (!extendsClause) {
1787
+ violations.push(createViolation({
1788
+ decisionId,
1789
+ constraintId: constraint.id,
1790
+ type: constraint.type,
1791
+ severity: constraint.severity,
1792
+ message: `Error class "${className}" does not extend any base class`,
1793
+ file: filePath,
1794
+ line: classDecl.getStartLineNumber(),
1795
+ suggestion: requiredBase ? `Extend ${requiredBase}` : "Extend a base error class for consistent error handling"
1796
+ }));
1797
+ } else if (requiredBase) {
1798
+ const baseName = extendsClause.getText();
1799
+ if (baseName !== requiredBase && baseName !== "Error") {
1800
+ }
1801
+ }
1802
+ }
1803
+ }
1804
+ if (rule.includes("custom error") || rule.includes("throw custom")) {
1805
+ sourceFile.forEachDescendant((node) => {
1806
+ if (Node3.isThrowStatement(node)) {
1807
+ const expression = node.getExpression();
1808
+ if (expression) {
1809
+ const text = expression.getText();
1810
+ if (text.startsWith("new Error(")) {
1811
+ violations.push(createViolation({
1812
+ decisionId,
1813
+ constraintId: constraint.id,
1814
+ type: constraint.type,
1815
+ severity: constraint.severity,
1816
+ message: "Throwing generic Error instead of custom error class",
1817
+ file: filePath,
1818
+ line: node.getStartLineNumber(),
1819
+ suggestion: "Use a custom error class for better error handling"
1820
+ }));
1821
+ }
1822
+ }
1823
+ }
1824
+ });
1825
+ }
1826
+ if (rule.includes("empty catch") || rule.includes("swallow") || rule.includes("handle")) {
1827
+ sourceFile.forEachDescendant((node) => {
1828
+ if (Node3.isTryStatement(node)) {
1829
+ const catchClause = node.getCatchClause();
1830
+ if (catchClause) {
1831
+ const block = catchClause.getBlock();
1832
+ const statements = block.getStatements();
1833
+ if (statements.length === 0) {
1834
+ violations.push(createViolation({
1835
+ decisionId,
1836
+ constraintId: constraint.id,
1837
+ type: constraint.type,
1838
+ severity: constraint.severity,
1839
+ message: "Empty catch block swallows error without handling",
1840
+ file: filePath,
1841
+ line: catchClause.getStartLineNumber(),
1842
+ suggestion: "Add error handling, logging, or rethrow the error"
1843
+ }));
1844
+ }
1845
+ }
1846
+ }
1847
+ });
1848
+ }
1849
+ if (rule.includes("logging") || rule.includes("logger") || rule.includes("no console")) {
1850
+ sourceFile.forEachDescendant((node) => {
1851
+ if (Node3.isCallExpression(node)) {
1852
+ const expression = node.getExpression();
1853
+ const text = expression.getText();
1854
+ if (text === "console.error" || text === "console.log") {
1855
+ violations.push(createViolation({
1856
+ decisionId,
1857
+ constraintId: constraint.id,
1858
+ type: constraint.type,
1859
+ severity: constraint.severity,
1860
+ message: `Using ${text} instead of proper logging`,
1861
+ file: filePath,
1862
+ line: node.getStartLineNumber(),
1863
+ suggestion: "Use a proper logging library"
1864
+ }));
1865
+ }
1866
+ }
1867
+ });
1868
+ }
1869
+ return violations;
1870
+ }
1871
+ };
1872
+
1873
+ // src/verification/verifiers/regex.ts
1874
+ var RegexVerifier = class {
1875
+ id = "regex";
1876
+ name = "Regex Pattern Verifier";
1877
+ description = "Verifies code against regex patterns specified in constraints";
1878
+ async verify(ctx) {
1879
+ const violations = [];
1880
+ const { sourceFile, constraint, decisionId, filePath } = ctx;
1881
+ const rule = constraint.rule;
1882
+ const mustNotMatch = rule.match(/must\s+not\s+(?:contain|match|use)\s+\/(.+?)\//i);
1883
+ const shouldMatch = rule.match(/(?:should|must)\s+(?:contain|match|use)\s+\/(.+?)\//i);
1884
+ const forbiddenPattern = rule.match(/forbidden:\s*\/(.+?)\//i);
1885
+ const requiredPattern = rule.match(/required:\s*\/(.+?)\//i);
1886
+ const fileText = sourceFile.getFullText();
1887
+ const patternToForbid = mustNotMatch?.[1] || forbiddenPattern?.[1];
1888
+ if (patternToForbid) {
1889
+ try {
1890
+ const regex = new RegExp(patternToForbid, "g");
1891
+ let match;
1892
+ while ((match = regex.exec(fileText)) !== null) {
1893
+ const beforeMatch = fileText.substring(0, match.index);
1894
+ const lineNumber = beforeMatch.split("\n").length;
1895
+ violations.push(createViolation({
1896
+ decisionId,
1897
+ constraintId: constraint.id,
1898
+ type: constraint.type,
1899
+ severity: constraint.severity,
1900
+ message: `Found forbidden pattern: "${match[0]}"`,
1901
+ file: filePath,
1902
+ line: lineNumber,
1903
+ suggestion: `Remove or replace the pattern matching /${patternToForbid}/`
1904
+ }));
1905
+ }
1906
+ } catch {
1907
+ }
1908
+ }
1909
+ const patternToRequire = shouldMatch?.[1] || requiredPattern?.[1];
1910
+ if (patternToRequire && !mustNotMatch) {
1911
+ try {
1912
+ const regex = new RegExp(patternToRequire);
1913
+ if (!regex.test(fileText)) {
1914
+ violations.push(createViolation({
1915
+ decisionId,
1916
+ constraintId: constraint.id,
1917
+ type: constraint.type,
1918
+ severity: constraint.severity,
1919
+ message: `File does not contain required pattern: /${patternToRequire}/`,
1920
+ file: filePath,
1921
+ suggestion: `Add code matching /${patternToRequire}/`
1922
+ }));
1923
+ }
1924
+ } catch {
1925
+ }
1926
+ }
1927
+ return violations;
1928
+ }
1929
+ };
1930
+
1931
+ // src/verification/verifiers/index.ts
1932
+ var builtinVerifiers = {
1933
+ naming: () => new NamingVerifier(),
1934
+ imports: () => new ImportsVerifier(),
1935
+ errors: () => new ErrorsVerifier(),
1936
+ regex: () => new RegexVerifier()
1937
+ };
1938
+ function getVerifier(id) {
1939
+ const factory = builtinVerifiers[id];
1940
+ return factory ? factory() : null;
1941
+ }
1942
+ function getVerifierIds() {
1943
+ return Object.keys(builtinVerifiers);
1944
+ }
1945
+ function selectVerifierForConstraint(rule, specifiedVerifier) {
1946
+ if (specifiedVerifier) {
1947
+ return getVerifier(specifiedVerifier);
1948
+ }
1949
+ const lowerRule = rule.toLowerCase();
1950
+ if (lowerRule.includes("naming") || lowerRule.includes("case")) {
1951
+ return getVerifier("naming");
1952
+ }
1953
+ if (lowerRule.includes("import") || lowerRule.includes("barrel") || lowerRule.includes("alias")) {
1954
+ return getVerifier("imports");
1955
+ }
1956
+ if (lowerRule.includes("error") || lowerRule.includes("throw") || lowerRule.includes("catch")) {
1957
+ return getVerifier("errors");
1958
+ }
1959
+ if (lowerRule.includes("/") || lowerRule.includes("pattern") || lowerRule.includes("regex")) {
1960
+ return getVerifier("regex");
1961
+ }
1962
+ return getVerifier("regex");
1963
+ }
1964
+
1965
+ // src/verification/engine.ts
1966
+ var VerificationEngine = class {
1967
+ registry;
1968
+ project;
1969
+ constructor(registry) {
1970
+ this.registry = registry || createRegistry();
1971
+ this.project = new Project2({
1972
+ compilerOptions: {
1973
+ allowJs: true,
1974
+ checkJs: false,
1975
+ noEmit: true,
1976
+ skipLibCheck: true
1977
+ },
1978
+ skipAddingFilesFromTsConfig: true
1979
+ });
1980
+ }
1981
+ /**
1982
+ * Run verification
1983
+ */
1984
+ async verify(config, options = {}) {
1985
+ const startTime = Date.now();
1986
+ const {
1987
+ level = "full",
1988
+ files: specificFiles,
1989
+ decisions: decisionIds,
1990
+ cwd = process.cwd()
1991
+ } = options;
1992
+ const levelConfig = config.verification?.levels?.[level];
1993
+ const severityFilter = options.severity || levelConfig?.severity;
1994
+ const timeout = options.timeout || levelConfig?.timeout || 6e4;
1995
+ await this.registry.load();
1996
+ let decisions = this.registry.getActive();
1997
+ if (decisionIds && decisionIds.length > 0) {
1998
+ decisions = decisions.filter((d) => decisionIds.includes(d.metadata.id));
1999
+ }
2000
+ const filesToVerify = specificFiles ? specificFiles : await glob(config.project.sourceRoots, {
2001
+ cwd,
2002
+ ignore: config.project.exclude,
2003
+ absolute: true
2004
+ });
2005
+ if (filesToVerify.length === 0) {
2006
+ return {
2007
+ success: true,
2008
+ violations: [],
2009
+ checked: 0,
2010
+ passed: 0,
2011
+ failed: 0,
2012
+ skipped: 0,
2013
+ duration: Date.now() - startTime
2014
+ };
2015
+ }
2016
+ const allViolations = [];
2017
+ let checked = 0;
2018
+ let passed = 0;
2019
+ let failed = 0;
2020
+ let skipped = 0;
2021
+ const timeoutPromise = new Promise(
2022
+ (resolve) => setTimeout(() => resolve("timeout"), timeout)
2023
+ );
2024
+ const verificationPromise = this.verifyFiles(
2025
+ filesToVerify,
2026
+ decisions,
2027
+ severityFilter,
2028
+ (violations) => {
2029
+ allViolations.push(...violations);
2030
+ checked++;
2031
+ if (violations.length > 0) {
2032
+ failed++;
2033
+ } else {
2034
+ passed++;
2035
+ }
2036
+ }
2037
+ );
2038
+ const result = await Promise.race([verificationPromise, timeoutPromise]);
2039
+ if (result === "timeout") {
2040
+ return {
2041
+ success: false,
2042
+ violations: allViolations,
2043
+ checked,
2044
+ passed,
2045
+ failed,
2046
+ skipped: filesToVerify.length - checked,
2047
+ duration: timeout
2048
+ };
2049
+ }
2050
+ const hasBlockingViolations = allViolations.some((v) => {
2051
+ if (level === "commit") {
2052
+ return v.type === "invariant" || v.severity === "critical";
2053
+ }
2054
+ if (level === "pr") {
2055
+ return v.type === "invariant" || v.severity === "critical" || v.severity === "high";
2056
+ }
2057
+ return v.type === "invariant";
2058
+ });
2059
+ return {
2060
+ success: !hasBlockingViolations,
2061
+ violations: allViolations,
2062
+ checked,
2063
+ passed,
2064
+ failed,
2065
+ skipped,
2066
+ duration: Date.now() - startTime
2067
+ };
2068
+ }
2069
+ /**
2070
+ * Verify a single file
2071
+ */
2072
+ async verifyFile(filePath, decisions, severityFilter) {
2073
+ const violations = [];
2074
+ let sourceFile = this.project.getSourceFile(filePath);
2075
+ if (!sourceFile) {
2076
+ try {
2077
+ sourceFile = this.project.addSourceFileAtPath(filePath);
2078
+ } catch {
2079
+ return violations;
2080
+ }
2081
+ }
2082
+ for (const decision of decisions) {
2083
+ for (const constraint of decision.constraints) {
2084
+ if (!matchesPattern(filePath, constraint.scope)) {
2085
+ continue;
2086
+ }
2087
+ if (severityFilter && !severityFilter.includes(constraint.severity)) {
2088
+ continue;
2089
+ }
2090
+ if (this.isExcepted(filePath, constraint)) {
2091
+ continue;
2092
+ }
2093
+ const verifier = selectVerifierForConstraint(constraint.rule, constraint.verifier);
2094
+ if (!verifier) {
2095
+ continue;
2096
+ }
2097
+ const ctx = {
2098
+ filePath,
2099
+ sourceFile,
2100
+ constraint,
2101
+ decisionId: decision.metadata.id
2102
+ };
2103
+ try {
2104
+ const constraintViolations = await verifier.verify(ctx);
2105
+ violations.push(...constraintViolations);
2106
+ } catch {
2107
+ }
2108
+ }
2109
+ }
2110
+ return violations;
2111
+ }
2112
+ /**
2113
+ * Verify multiple files
2114
+ */
2115
+ async verifyFiles(files, decisions, severityFilter, onFileVerified) {
2116
+ for (const file of files) {
2117
+ const violations = await this.verifyFile(file, decisions, severityFilter);
2118
+ onFileVerified(violations);
2119
+ }
2120
+ }
2121
+ /**
2122
+ * Check if file is excepted from constraint
2123
+ */
2124
+ isExcepted(filePath, constraint) {
2125
+ if (!constraint.exceptions) return false;
2126
+ return constraint.exceptions.some((exception) => {
2127
+ if (exception.expiresAt) {
2128
+ const expiryDate = new Date(exception.expiresAt);
2129
+ if (expiryDate < /* @__PURE__ */ new Date()) {
2130
+ return false;
2131
+ }
2132
+ }
2133
+ return matchesPattern(filePath, exception.pattern);
2134
+ });
2135
+ }
2136
+ /**
2137
+ * Get registry
2138
+ */
2139
+ getRegistry() {
2140
+ return this.registry;
2141
+ }
2142
+ };
2143
+ function createVerificationEngine(registry) {
2144
+ return new VerificationEngine(registry);
2145
+ }
2146
+
2147
+ // src/propagation/graph.ts
2148
+ async function buildDependencyGraph(decisions, files) {
2149
+ const nodes = /* @__PURE__ */ new Map();
2150
+ const decisionToFiles = /* @__PURE__ */ new Map();
2151
+ const fileToDecisions = /* @__PURE__ */ new Map();
2152
+ for (const decision of decisions) {
2153
+ const decisionId = `decision:${decision.metadata.id}`;
2154
+ nodes.set(decisionId, {
2155
+ type: "decision",
2156
+ id: decision.metadata.id,
2157
+ edges: decision.constraints.map((c) => `constraint:${decision.metadata.id}/${c.id}`)
2158
+ });
2159
+ for (const constraint of decision.constraints) {
2160
+ const constraintId = `constraint:${decision.metadata.id}/${constraint.id}`;
2161
+ const matchingFiles = [];
2162
+ for (const file of files) {
2163
+ if (matchesPattern(file, constraint.scope)) {
2164
+ matchingFiles.push(`file:${file}`);
2165
+ const fileDecisions = fileToDecisions.get(file) || /* @__PURE__ */ new Set();
2166
+ fileDecisions.add(decision.metadata.id);
2167
+ fileToDecisions.set(file, fileDecisions);
2168
+ const decFiles = decisionToFiles.get(decision.metadata.id) || /* @__PURE__ */ new Set();
2169
+ decFiles.add(file);
2170
+ decisionToFiles.set(decision.metadata.id, decFiles);
2171
+ }
2172
+ }
2173
+ nodes.set(constraintId, {
2174
+ type: "constraint",
2175
+ id: `${decision.metadata.id}/${constraint.id}`,
2176
+ edges: matchingFiles
2177
+ });
2178
+ }
2179
+ }
2180
+ for (const file of files) {
2181
+ const fileId = `file:${file}`;
2182
+ if (!nodes.has(fileId)) {
2183
+ nodes.set(fileId, {
2184
+ type: "file",
2185
+ id: file,
2186
+ edges: []
2187
+ });
2188
+ }
2189
+ }
2190
+ return {
2191
+ nodes,
2192
+ decisionToFiles,
2193
+ fileToDecisions
2194
+ };
2195
+ }
2196
+ function getAffectedFiles(graph, decisionId) {
2197
+ const files = graph.decisionToFiles.get(decisionId);
2198
+ return files ? Array.from(files) : [];
2199
+ }
2200
+ function getAffectingDecisions(graph, filePath) {
2201
+ const decisions = graph.fileToDecisions.get(filePath);
2202
+ return decisions ? Array.from(decisions) : [];
2203
+ }
2204
+ function getTransitiveDependencies(graph, nodeId, visited = /* @__PURE__ */ new Set()) {
2205
+ if (visited.has(nodeId)) {
2206
+ return [];
2207
+ }
2208
+ visited.add(nodeId);
2209
+ const node = graph.nodes.get(nodeId);
2210
+ if (!node) {
2211
+ return [];
2212
+ }
2213
+ const deps = [nodeId];
2214
+ for (const edge of node.edges) {
2215
+ deps.push(...getTransitiveDependencies(graph, edge, visited));
2216
+ }
2217
+ return deps;
2218
+ }
2219
+
2220
+ // src/propagation/engine.ts
2221
+ var PropagationEngine = class {
2222
+ registry;
2223
+ graph = null;
2224
+ constructor(registry) {
2225
+ this.registry = registry || createRegistry();
2226
+ }
2227
+ /**
2228
+ * Initialize the engine with current state
2229
+ */
2230
+ async initialize(config, options = {}) {
2231
+ const { cwd = process.cwd() } = options;
2232
+ await this.registry.load();
2233
+ const files = await glob(config.project.sourceRoots, {
2234
+ cwd,
2235
+ ignore: config.project.exclude,
2236
+ absolute: true
2237
+ });
2238
+ const decisions = this.registry.getActive();
2239
+ this.graph = await buildDependencyGraph(decisions, files);
2240
+ }
2241
+ /**
2242
+ * Analyze impact of changing a decision
2243
+ */
2244
+ async analyzeImpact(decisionId, change, config, options = {}) {
2245
+ const { cwd = process.cwd() } = options;
2246
+ if (!this.graph) {
2247
+ await this.initialize(config, options);
2248
+ }
2249
+ const affectedFilePaths = getAffectedFiles(this.graph, decisionId);
2250
+ const verificationEngine = createVerificationEngine(this.registry);
2251
+ const result = await verificationEngine.verify(config, {
2252
+ files: affectedFilePaths,
2253
+ decisions: [decisionId],
2254
+ cwd
2255
+ });
2256
+ const fileViolations = /* @__PURE__ */ new Map();
2257
+ for (const violation of result.violations) {
2258
+ const existing = fileViolations.get(violation.file) || { total: 0, autoFixable: 0 };
2259
+ existing.total++;
2260
+ if (violation.autofix) {
2261
+ existing.autoFixable++;
2262
+ }
2263
+ fileViolations.set(violation.file, existing);
2264
+ }
2265
+ const affectedFiles = affectedFilePaths.map((path) => ({
2266
+ path,
2267
+ violations: fileViolations.get(path)?.total || 0,
2268
+ autoFixable: fileViolations.get(path)?.autoFixable || 0
2269
+ }));
2270
+ affectedFiles.sort((a, b) => b.violations - a.violations);
2271
+ const totalViolations = result.violations.length;
2272
+ const totalAutoFixable = result.violations.filter((v) => v.autofix).length;
2273
+ const manualFixes = totalViolations - totalAutoFixable;
2274
+ let estimatedEffort;
2275
+ if (manualFixes === 0) {
2276
+ estimatedEffort = "low";
2277
+ } else if (manualFixes <= 10) {
2278
+ estimatedEffort = "medium";
2279
+ } else {
2280
+ estimatedEffort = "high";
2281
+ }
2282
+ const migrationSteps = this.generateMigrationSteps(
2283
+ affectedFiles,
2284
+ totalAutoFixable > 0
2285
+ );
2286
+ return {
2287
+ decision: decisionId,
2288
+ change,
2289
+ affectedFiles,
2290
+ estimatedEffort,
2291
+ migrationSteps
2292
+ };
2293
+ }
2294
+ /**
2295
+ * Generate migration steps
2296
+ */
2297
+ generateMigrationSteps(affectedFiles, hasAutoFixes) {
2298
+ const steps = [];
2299
+ let order = 1;
2300
+ if (hasAutoFixes) {
2301
+ steps.push({
2302
+ order: order++,
2303
+ description: "Run auto-fix for mechanical violations",
2304
+ files: affectedFiles.filter((f) => f.autoFixable > 0).map((f) => f.path),
2305
+ automated: true
2306
+ });
2307
+ }
2308
+ const filesWithManualFixes = affectedFiles.filter(
2309
+ (f) => f.violations > f.autoFixable
2310
+ );
2311
+ if (filesWithManualFixes.length > 0) {
2312
+ const highPriority = filesWithManualFixes.filter((f) => f.violations > 5);
2313
+ const mediumPriority = filesWithManualFixes.filter(
2314
+ (f) => f.violations <= 5 && f.violations > 1
2315
+ );
2316
+ const lowPriority = filesWithManualFixes.filter((f) => f.violations === 1);
2317
+ if (highPriority.length > 0) {
2318
+ steps.push({
2319
+ order: order++,
2320
+ description: "Fix high-violation files first",
2321
+ files: highPriority.map((f) => f.path),
2322
+ automated: false
2323
+ });
2324
+ }
2325
+ if (mediumPriority.length > 0) {
2326
+ steps.push({
2327
+ order: order++,
2328
+ description: "Fix medium-violation files",
2329
+ files: mediumPriority.map((f) => f.path),
2330
+ automated: false
2331
+ });
2332
+ }
2333
+ if (lowPriority.length > 0) {
2334
+ steps.push({
2335
+ order: order++,
2336
+ description: "Fix remaining files",
2337
+ files: lowPriority.map((f) => f.path),
2338
+ automated: false
2339
+ });
2340
+ }
2341
+ }
2342
+ steps.push({
2343
+ order: order++,
2344
+ description: "Run verification to confirm all violations resolved",
2345
+ files: [],
2346
+ automated: true
2347
+ });
2348
+ return steps;
2349
+ }
2350
+ /**
2351
+ * Get dependency graph
2352
+ */
2353
+ getGraph() {
2354
+ return this.graph;
2355
+ }
2356
+ };
2357
+ function createPropagationEngine(registry) {
2358
+ return new PropagationEngine(registry);
2359
+ }
2360
+
2361
+ // src/reporting/reporter.ts
2362
+ async function generateReport(config, options = {}) {
2363
+ const { includeAll = false, cwd = process.cwd() } = options;
2364
+ const registry = createRegistry({ basePath: cwd });
2365
+ await registry.load();
2366
+ const engine = createVerificationEngine(registry);
2367
+ const result = await engine.verify(config, { level: "full", cwd });
2368
+ const decisions = includeAll ? registry.getAll() : registry.getActive();
2369
+ const byDecision = [];
2370
+ for (const decision of decisions) {
2371
+ const decisionViolations = result.violations.filter(
2372
+ (v) => v.decisionId === decision.metadata.id
2373
+ );
2374
+ const constraintCount = decision.constraints.length;
2375
+ const violationCount = decisionViolations.length;
2376
+ const compliance = violationCount === 0 ? 100 : Math.max(0, 100 - Math.min(violationCount * 10, 100));
2377
+ byDecision.push({
2378
+ decisionId: decision.metadata.id,
2379
+ title: decision.metadata.title,
2380
+ status: decision.metadata.status,
2381
+ constraints: constraintCount,
2382
+ violations: violationCount,
2383
+ compliance
2384
+ });
2385
+ }
2386
+ byDecision.sort((a, b) => a.compliance - b.compliance);
2387
+ const totalDecisions = decisions.length;
2388
+ const activeDecisions = decisions.filter((d) => d.metadata.status === "active").length;
2389
+ const totalConstraints = decisions.reduce((sum, d) => sum + d.constraints.length, 0);
2390
+ const violationsBySeverity = {
2391
+ critical: result.violations.filter((v) => v.severity === "critical").length,
2392
+ high: result.violations.filter((v) => v.severity === "high").length,
2393
+ medium: result.violations.filter((v) => v.severity === "medium").length,
2394
+ low: result.violations.filter((v) => v.severity === "low").length
2395
+ };
2396
+ const overallCompliance = byDecision.length > 0 ? Math.round(byDecision.reduce((sum, d) => sum + d.compliance, 0) / byDecision.length) : 100;
2397
+ return {
2398
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2399
+ project: config.project.name,
2400
+ summary: {
2401
+ totalDecisions,
2402
+ activeDecisions,
2403
+ totalConstraints,
2404
+ violations: violationsBySeverity,
2405
+ compliance: overallCompliance
2406
+ },
2407
+ byDecision
2408
+ };
2409
+ }
2410
+ function checkDegradation(current, previous) {
2411
+ if (!previous) {
2412
+ return { degraded: false, details: [] };
2413
+ }
2414
+ const details = [];
2415
+ let degraded = false;
2416
+ if (current.summary.compliance < previous.summary.compliance) {
2417
+ degraded = true;
2418
+ details.push(
2419
+ `Overall compliance dropped from ${previous.summary.compliance}% to ${current.summary.compliance}%`
2420
+ );
2421
+ }
2422
+ const newCritical = current.summary.violations.critical - previous.summary.violations.critical;
2423
+ const newHigh = current.summary.violations.high - previous.summary.violations.high;
2424
+ if (newCritical > 0) {
2425
+ degraded = true;
2426
+ details.push(`${newCritical} new critical violation(s)`);
2427
+ }
2428
+ if (newHigh > 0) {
2429
+ degraded = true;
2430
+ details.push(`${newHigh} new high severity violation(s)`);
2431
+ }
2432
+ return { degraded, details };
2433
+ }
2434
+ var Reporter = class {
2435
+ /**
2436
+ * Generate formatted report from verification result
2437
+ */
2438
+ generate(result, options = {}) {
2439
+ const { format = "table", groupBy } = options;
2440
+ switch (format) {
2441
+ case "json":
2442
+ return JSON.stringify(result, null, 2);
2443
+ case "markdown":
2444
+ return this.formatAsMarkdown(result);
2445
+ case "table":
2446
+ default:
2447
+ return groupBy ? this.formatAsTableGrouped(result, groupBy) : this.formatAsTable(result);
2448
+ }
2449
+ }
2450
+ /**
2451
+ * Generate compliance overview from multiple results
2452
+ */
2453
+ generateComplianceReport(results) {
2454
+ const lines = [];
2455
+ lines.push("# Compliance Report\n");
2456
+ if (results.length === 0) {
2457
+ lines.push("No results to report.\n");
2458
+ return lines.join("\n");
2459
+ }
2460
+ const totalViolations = results.reduce(
2461
+ (sum, r) => sum + (r.summary?.totalViolations || r.violations?.length || 0),
2462
+ 0
2463
+ );
2464
+ const avgViolations = totalViolations / results.length;
2465
+ lines.push(`## Overall Statistics
2466
+ `);
2467
+ lines.push(`- Total Results: ${results.length}`);
2468
+ lines.push(`- Total Violations: ${totalViolations}`);
2469
+ lines.push(`- Average Violations per Result: ${avgViolations.toFixed(1)}`);
2470
+ const complianceRate = results.length > 0 ? results.filter((r) => (r.violations?.length || 0) === 0).length / results.length * 100 : 100;
2471
+ lines.push(`- Compliance Rate: ${complianceRate.toFixed(1)}%
2472
+ `);
2473
+ return lines.join("\n");
2474
+ }
2475
+ formatAsTable(result) {
2476
+ const lines = [];
2477
+ lines.push("Verification Report");
2478
+ lines.push("=".repeat(50));
2479
+ lines.push("");
2480
+ if (result.summary) {
2481
+ lines.push("Summary:");
2482
+ lines.push(` Decisions Checked: ${result.summary.decisionsChecked || 0}`);
2483
+ lines.push(` Files Checked: ${result.summary.filesChecked || 0}`);
2484
+ lines.push(` Total Violations: ${result.summary.totalViolations || result.violations?.length || 0}`);
2485
+ lines.push(` Critical: ${result.summary.critical || 0}`);
2486
+ lines.push(` High: ${result.summary.high || 0}`);
2487
+ lines.push(` Medium: ${result.summary.medium || 0}`);
2488
+ lines.push(` Low: ${result.summary.low || 0}`);
2489
+ lines.push(` Duration: ${result.summary.duration || 0}ms`);
2490
+ lines.push("");
2491
+ }
2492
+ const totalViolations = result.summary?.totalViolations ?? result.violations?.length ?? 0;
2493
+ if (totalViolations > 0 && result.violations && result.violations.length > 0) {
2494
+ lines.push("Violations:");
2495
+ lines.push("-".repeat(50));
2496
+ result.violations.forEach((v) => {
2497
+ const severity = v.severity.toLowerCase();
2498
+ lines.push(` [${v.severity.toUpperCase()}] ${v.decisionId} - ${v.constraintId} (${severity})`);
2499
+ lines.push(` ${v.message}`);
2500
+ lines.push(` Location: ${v.location?.file || v.file}:${v.location?.line || 0}:${v.location?.column || 0}`);
2501
+ lines.push("");
2502
+ });
2503
+ } else {
2504
+ lines.push("No violations found.");
2505
+ lines.push("");
2506
+ }
2507
+ return lines.join("\n");
2508
+ }
2509
+ formatAsTableGrouped(result, groupBy) {
2510
+ const lines = [];
2511
+ lines.push("Verification Report");
2512
+ lines.push("=".repeat(50));
2513
+ lines.push("");
2514
+ if (result.summary) {
2515
+ lines.push("Summary:");
2516
+ lines.push(` Total Violations: ${result.summary.totalViolations || result.violations?.length || 0}`);
2517
+ lines.push("");
2518
+ }
2519
+ if (result.violations && result.violations.length > 0) {
2520
+ if (groupBy === "severity") {
2521
+ const grouped = /* @__PURE__ */ new Map();
2522
+ result.violations.forEach((v) => {
2523
+ const key = v.severity;
2524
+ if (!grouped.has(key)) grouped.set(key, []);
2525
+ grouped.get(key).push(v);
2526
+ });
2527
+ for (const [severity, violations] of grouped.entries()) {
2528
+ lines.push(`Severity: ${severity}`);
2529
+ lines.push("-".repeat(30));
2530
+ violations.forEach((v) => {
2531
+ lines.push(` ${v.decisionId} - ${v.message}`);
2532
+ });
2533
+ lines.push("");
2534
+ }
2535
+ } else if (groupBy === "file") {
2536
+ const grouped = /* @__PURE__ */ new Map();
2537
+ result.violations.forEach((v) => {
2538
+ const key = v.location?.file || v.file || "unknown";
2539
+ if (!grouped.has(key)) grouped.set(key, []);
2540
+ grouped.get(key).push(v);
2541
+ });
2542
+ for (const [file, violations] of grouped.entries()) {
2543
+ lines.push(`File: ${file}`);
2544
+ lines.push("-".repeat(30));
2545
+ violations.forEach((v) => {
2546
+ lines.push(` [${v.severity}] ${v.message}`);
2547
+ });
2548
+ lines.push("");
2549
+ }
2550
+ }
2551
+ } else {
2552
+ lines.push("No violations found.");
2553
+ lines.push("");
2554
+ }
2555
+ return lines.join("\n");
2556
+ }
2557
+ formatAsMarkdown(result) {
2558
+ const lines = [];
2559
+ lines.push("## Verification Report\n");
2560
+ if (result.summary) {
2561
+ lines.push("### Summary\n");
2562
+ lines.push(`- **Decisions Checked:** ${result.summary.decisionsChecked || 0}`);
2563
+ lines.push(`- **Files Checked:** ${result.summary.filesChecked || 0}`);
2564
+ lines.push(`- **Total Violations:** ${result.summary.totalViolations || result.violations?.length || 0}`);
2565
+ lines.push(`- **Critical:** ${result.summary.critical || 0}`);
2566
+ lines.push(`- **High:** ${result.summary.high || 0}`);
2567
+ lines.push(`- **Medium:** ${result.summary.medium || 0}`);
2568
+ lines.push(`- **Low:** ${result.summary.low || 0}
2569
+ `);
2570
+ }
2571
+ if (result.violations && result.violations.length > 0) {
2572
+ lines.push("### Violations\n");
2573
+ result.violations.forEach((v) => {
2574
+ lines.push(`#### [${v.severity.toUpperCase()}] ${v.decisionId}`);
2575
+ lines.push(`**Message:** ${v.message}`);
2576
+ lines.push(`**Location:** \`${v.location?.file || v.file}:${v.location?.line || 0}\`
2577
+ `);
2578
+ });
2579
+ } else {
2580
+ lines.push("No violations found.\n");
2581
+ }
2582
+ return lines.join("\n");
2583
+ }
2584
+ };
2585
+
2586
+ // src/reporting/formats/console.ts
2587
+ import chalk from "chalk";
2588
+ import { table } from "table";
2589
+ function formatConsoleReport(report) {
2590
+ const lines = [];
2591
+ lines.push("");
2592
+ lines.push(chalk.bold.blue("SpecBridge Compliance Report"));
2593
+ lines.push(chalk.dim(`Generated: ${new Date(report.timestamp).toLocaleString()}`));
2594
+ lines.push(chalk.dim(`Project: ${report.project}`));
2595
+ lines.push("");
2596
+ const complianceColor = getComplianceColor(report.summary.compliance);
2597
+ lines.push(chalk.bold("Overall Compliance"));
2598
+ lines.push(` ${complianceColor(formatComplianceBar(report.summary.compliance))} ${complianceColor(`${report.summary.compliance}%`)}`);
2599
+ lines.push("");
2600
+ lines.push(chalk.bold("Summary"));
2601
+ lines.push(` Decisions: ${report.summary.activeDecisions} active / ${report.summary.totalDecisions} total`);
2602
+ lines.push(` Constraints: ${report.summary.totalConstraints}`);
2603
+ lines.push("");
2604
+ lines.push(chalk.bold("Violations"));
2605
+ const { violations } = report.summary;
2606
+ const violationParts = [];
2607
+ if (violations.critical > 0) {
2608
+ violationParts.push(chalk.red(`${violations.critical} critical`));
2609
+ }
2610
+ if (violations.high > 0) {
2611
+ violationParts.push(chalk.yellow(`${violations.high} high`));
2612
+ }
2613
+ if (violations.medium > 0) {
2614
+ violationParts.push(chalk.cyan(`${violations.medium} medium`));
2615
+ }
2616
+ if (violations.low > 0) {
2617
+ violationParts.push(chalk.dim(`${violations.low} low`));
2618
+ }
2619
+ if (violationParts.length > 0) {
2620
+ lines.push(` ${violationParts.join(" | ")}`);
2621
+ } else {
2622
+ lines.push(chalk.green(" No violations"));
2623
+ }
2624
+ lines.push("");
2625
+ if (report.byDecision.length > 0) {
2626
+ lines.push(chalk.bold("By Decision"));
2627
+ lines.push("");
2628
+ const tableData = [
2629
+ [
2630
+ chalk.bold("Decision"),
2631
+ chalk.bold("Status"),
2632
+ chalk.bold("Constraints"),
2633
+ chalk.bold("Violations"),
2634
+ chalk.bold("Compliance")
2635
+ ]
2636
+ ];
2637
+ for (const dec of report.byDecision) {
2638
+ const compColor = getComplianceColor(dec.compliance);
2639
+ const statusColor = getStatusColor(dec.status);
2640
+ tableData.push([
2641
+ truncate(dec.title, 40),
2642
+ statusColor(dec.status),
2643
+ String(dec.constraints),
2644
+ dec.violations > 0 ? chalk.red(String(dec.violations)) : chalk.green("0"),
2645
+ compColor(`${dec.compliance}%`)
2646
+ ]);
2647
+ }
2648
+ const tableOutput = table(tableData, {
2649
+ border: {
2650
+ topBody: "",
2651
+ topJoin: "",
2652
+ topLeft: "",
2653
+ topRight: "",
2654
+ bottomBody: "",
2655
+ bottomJoin: "",
2656
+ bottomLeft: "",
2657
+ bottomRight: "",
2658
+ bodyLeft: " ",
2659
+ bodyRight: "",
2660
+ bodyJoin: " ",
2661
+ joinBody: "",
2662
+ joinLeft: "",
2663
+ joinRight: "",
2664
+ joinJoin: ""
2665
+ },
2666
+ drawHorizontalLine: (index) => index === 1
2667
+ });
2668
+ lines.push(tableOutput);
2669
+ }
2670
+ return lines.join("\n");
2671
+ }
2672
+ function formatComplianceBar(compliance) {
2673
+ const filled = Math.round(compliance / 10);
2674
+ const empty = 10 - filled;
2675
+ return "\u2588".repeat(filled) + "\u2591".repeat(empty);
2676
+ }
2677
+ function getComplianceColor(compliance) {
2678
+ if (compliance >= 90) return chalk.green;
2679
+ if (compliance >= 70) return chalk.yellow;
2680
+ if (compliance >= 50) return chalk.hex("#FFA500");
2681
+ return chalk.red;
2682
+ }
2683
+ function getStatusColor(status) {
2684
+ switch (status) {
2685
+ case "active":
2686
+ return chalk.green;
2687
+ case "draft":
2688
+ return chalk.yellow;
2689
+ case "deprecated":
2690
+ return chalk.gray;
2691
+ case "superseded":
2692
+ return chalk.blue;
2693
+ default:
2694
+ return chalk.white;
2695
+ }
2696
+ }
2697
+ function truncate(str, length) {
2698
+ if (str.length <= length) return str;
2699
+ return str.slice(0, length - 3) + "...";
2700
+ }
2701
+
2702
+ // src/reporting/formats/markdown.ts
2703
+ function formatMarkdownReport(report) {
2704
+ const lines = [];
2705
+ lines.push("# SpecBridge Compliance Report");
2706
+ lines.push("");
2707
+ lines.push(`**Project:** ${report.project}`);
2708
+ lines.push(`**Generated:** ${new Date(report.timestamp).toLocaleString()}`);
2709
+ lines.push("");
2710
+ lines.push("## Overall Compliance");
2711
+ lines.push("");
2712
+ lines.push(`**Score:** ${report.summary.compliance}%`);
2713
+ lines.push("");
2714
+ lines.push(formatProgressBar(report.summary.compliance));
2715
+ lines.push("");
2716
+ lines.push("## Summary");
2717
+ lines.push("");
2718
+ lines.push(`- **Active Decisions:** ${report.summary.activeDecisions} / ${report.summary.totalDecisions}`);
2719
+ lines.push(`- **Total Constraints:** ${report.summary.totalConstraints}`);
2720
+ lines.push("");
2721
+ lines.push("### Violations");
2722
+ lines.push("");
2723
+ const { violations } = report.summary;
2724
+ const totalViolations = violations.critical + violations.high + violations.medium + violations.low;
2725
+ if (totalViolations === 0) {
2726
+ lines.push("No violations found.");
2727
+ } else {
2728
+ lines.push(`| Severity | Count |`);
2729
+ lines.push(`|----------|-------|`);
2730
+ lines.push(`| Critical | ${violations.critical} |`);
2731
+ lines.push(`| High | ${violations.high} |`);
2732
+ lines.push(`| Medium | ${violations.medium} |`);
2733
+ lines.push(`| Low | ${violations.low} |`);
2734
+ lines.push(`| **Total** | **${totalViolations}** |`);
2735
+ }
2736
+ lines.push("");
2737
+ if (report.byDecision.length > 0) {
2738
+ lines.push("## By Decision");
2739
+ lines.push("");
2740
+ lines.push("| Decision | Status | Constraints | Violations | Compliance |");
2741
+ lines.push("|----------|--------|-------------|------------|------------|");
2742
+ for (const dec of report.byDecision) {
2743
+ const complianceEmoji = dec.compliance >= 90 ? "" : dec.compliance >= 70 ? "" : "";
2744
+ lines.push(
2745
+ `| ${dec.title} | ${dec.status} | ${dec.constraints} | ${dec.violations} | ${complianceEmoji} ${dec.compliance}% |`
2746
+ );
2747
+ }
2748
+ lines.push("");
2749
+ }
2750
+ lines.push("---");
2751
+ lines.push("");
2752
+ lines.push("*Generated by [SpecBridge](https://github.com/specbridge/specbridge)*");
2753
+ return lines.join("\n");
2754
+ }
2755
+ function formatProgressBar(percentage) {
2756
+ const width = 20;
2757
+ const filled = Math.round(percentage / 100 * width);
2758
+ const empty = width - filled;
2759
+ const filledChar = "";
2760
+ const emptyChar = "";
2761
+ return `\`${filledChar.repeat(filled)}${emptyChar.repeat(empty)}\` ${percentage}%`;
2762
+ }
2763
+
2764
+ // src/agent/context.generator.ts
2765
+ async function generateContext(filePath, config, options = {}) {
2766
+ const { includeRationale = config.agent?.includeRationale ?? true, cwd = process.cwd() } = options;
2767
+ const registry = createRegistry({ basePath: cwd });
2768
+ await registry.load();
2769
+ const decisions = registry.getActive();
2770
+ const applicableDecisions = [];
2771
+ for (const decision of decisions) {
2772
+ const applicableConstraints = [];
2773
+ for (const constraint of decision.constraints) {
2774
+ if (matchesPattern(filePath, constraint.scope)) {
2775
+ applicableConstraints.push({
2776
+ id: constraint.id,
2777
+ type: constraint.type,
2778
+ rule: constraint.rule,
2779
+ severity: constraint.severity
2780
+ });
2781
+ }
2782
+ }
2783
+ if (applicableConstraints.length > 0) {
2784
+ applicableDecisions.push({
2785
+ id: decision.metadata.id,
2786
+ title: decision.metadata.title,
2787
+ summary: includeRationale ? decision.decision.summary : "",
2788
+ constraints: applicableConstraints
2789
+ });
2790
+ }
2791
+ }
2792
+ return {
2793
+ file: filePath,
2794
+ applicableDecisions,
2795
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2796
+ };
2797
+ }
2798
+ function formatContextAsMarkdown(context) {
2799
+ const lines = [];
2800
+ lines.push("# Architectural Constraints");
2801
+ lines.push("");
2802
+ lines.push(`File: \`${context.file}\``);
2803
+ lines.push("");
2804
+ if (context.applicableDecisions.length === 0) {
2805
+ lines.push("No specific architectural constraints apply to this file.");
2806
+ return lines.join("\n");
2807
+ }
2808
+ lines.push("The following architectural decisions apply to this file:");
2809
+ lines.push("");
2810
+ for (const decision of context.applicableDecisions) {
2811
+ lines.push(`## ${decision.title}`);
2812
+ lines.push("");
2813
+ if (decision.summary) {
2814
+ lines.push(decision.summary);
2815
+ lines.push("");
2816
+ }
2817
+ lines.push("### Constraints");
2818
+ lines.push("");
2819
+ for (const constraint of decision.constraints) {
2820
+ const typeEmoji = constraint.type === "invariant" ? "" : constraint.type === "convention" ? "" : "";
2821
+ const severityBadge = `[${constraint.severity.toUpperCase()}]`;
2822
+ lines.push(`- ${typeEmoji} **${severityBadge}** ${constraint.rule}`);
2823
+ }
2824
+ lines.push("");
2825
+ }
2826
+ lines.push("---");
2827
+ lines.push("");
2828
+ lines.push("Please ensure your code complies with these constraints.");
2829
+ return lines.join("\n");
2830
+ }
2831
+ function formatContextAsJson(context) {
2832
+ return JSON.stringify(context, null, 2);
2833
+ }
2834
+ function formatContextAsMcp(context) {
2835
+ return {
2836
+ type: "architectural_context",
2837
+ version: "1.0",
2838
+ file: context.file,
2839
+ timestamp: context.generatedAt,
2840
+ decisions: context.applicableDecisions.map((d) => ({
2841
+ id: d.id,
2842
+ title: d.title,
2843
+ summary: d.summary,
2844
+ constraints: d.constraints.map((c) => ({
2845
+ id: c.id,
2846
+ type: c.type,
2847
+ severity: c.severity,
2848
+ rule: c.rule
2849
+ }))
2850
+ }))
2851
+ };
2852
+ }
2853
+ async function generateFormattedContext(filePath, config, options = {}) {
2854
+ const context = await generateContext(filePath, config, options);
2855
+ const format = options.format || config.agent?.format || "markdown";
2856
+ switch (format) {
2857
+ case "json":
2858
+ return formatContextAsJson(context);
2859
+ case "mcp":
2860
+ return JSON.stringify(formatContextAsMcp(context), null, 2);
2861
+ case "markdown":
2862
+ default:
2863
+ return formatContextAsMarkdown(context);
2864
+ }
2865
+ }
2866
+ var AgentContextGenerator = class {
2867
+ /**
2868
+ * Generate context from decisions
2869
+ */
2870
+ generateContext(options) {
2871
+ const { decisions, filePattern, format = "markdown", concise = false, minSeverity } = options;
2872
+ const activeDecisions = decisions.filter((d) => d.metadata.status !== "deprecated");
2873
+ let filteredDecisions = activeDecisions;
2874
+ if (filePattern) {
2875
+ filteredDecisions = activeDecisions.filter(
2876
+ (d) => d.constraints.some((c) => matchesPattern(filePattern, c.scope))
2877
+ );
2878
+ }
2879
+ if (minSeverity) {
2880
+ const severityOrder = { low: 0, medium: 1, high: 2, critical: 3 };
2881
+ const minLevel = severityOrder[minSeverity] || 0;
2882
+ filteredDecisions = filteredDecisions.map((d) => ({
2883
+ ...d,
2884
+ constraints: d.constraints.filter((c) => {
2885
+ const level = severityOrder[c.severity] || 0;
2886
+ return level >= minLevel;
2887
+ })
2888
+ })).filter((d) => d.constraints.length > 0);
2889
+ }
2890
+ if (filteredDecisions.length === 0) {
2891
+ return "No architectural decisions apply.";
2892
+ }
2893
+ if (format === "json") {
2894
+ return JSON.stringify({ decisions: filteredDecisions }, null, 2);
2895
+ }
2896
+ const lines = [];
2897
+ if (format === "markdown") {
2898
+ lines.push("# Architectural Decisions\n");
2899
+ for (const decision of filteredDecisions) {
2900
+ lines.push(`## ${decision.metadata.title}`);
2901
+ if (!concise && decision.decision.summary) {
2902
+ lines.push(`
2903
+ ${decision.decision.summary}
2904
+ `);
2905
+ }
2906
+ lines.push("### Constraints\n");
2907
+ for (const constraint of decision.constraints) {
2908
+ lines.push(`- **[${constraint.severity.toUpperCase()}]** ${constraint.rule}`);
2909
+ }
2910
+ lines.push("");
2911
+ }
2912
+ } else {
2913
+ for (const decision of filteredDecisions) {
2914
+ lines.push(`${decision.metadata.title}`);
2915
+ if (!concise && decision.decision.summary) {
2916
+ lines.push(`${decision.decision.summary}`);
2917
+ }
2918
+ for (const constraint of decision.constraints) {
2919
+ lines.push(` - ${constraint.rule}`);
2920
+ }
2921
+ lines.push("");
2922
+ }
2923
+ }
2924
+ return lines.join("\n");
2925
+ }
2926
+ /**
2927
+ * Generate prompt suffix for AI agents
2928
+ */
2929
+ generatePromptSuffix(options) {
2930
+ const { decisions } = options;
2931
+ if (decisions.length === 0) {
2932
+ return "No architectural decisions to follow.";
2933
+ }
2934
+ const lines = [];
2935
+ lines.push("Please follow these architectural decisions and constraints:");
2936
+ lines.push("");
2937
+ for (const decision of decisions) {
2938
+ lines.push(`- ${decision.metadata.title}`);
2939
+ for (const constraint of decision.constraints) {
2940
+ lines.push(` - ${constraint.rule}`);
2941
+ }
2942
+ }
2943
+ lines.push("");
2944
+ lines.push("Ensure your code complies with all constraints listed above.");
2945
+ return lines.join("\n");
2946
+ }
2947
+ /**
2948
+ * Extract relevant decisions for a specific file
2949
+ */
2950
+ extractRelevantDecisions(options) {
2951
+ const { decisions, filePath } = options;
2952
+ return decisions.filter(
2953
+ (d) => d.constraints.some((c) => matchesPattern(filePath, c.scope))
2954
+ );
2955
+ }
2956
+ };
2957
+ export {
2958
+ AgentContextGenerator,
2959
+ AlreadyInitializedError,
2960
+ AnalyzerNotFoundError,
2961
+ CodeScanner,
2962
+ ConfigError,
2963
+ ConstraintExceptionSchema,
2964
+ ConstraintSchema,
2965
+ ConstraintTypeSchema,
2966
+ DecisionContentSchema,
2967
+ DecisionMetadataSchema,
2968
+ DecisionNotFoundError,
2969
+ DecisionSchema,
2970
+ DecisionStatusSchema,
2971
+ DecisionValidationError,
2972
+ ErrorsAnalyzer,
2973
+ ErrorsVerifier,
2974
+ FileSystemError,
2975
+ HookError,
2976
+ ImportsAnalyzer,
2977
+ ImportsVerifier,
2978
+ InferenceEngine,
2979
+ InferenceError,
2980
+ LinksSchema,
2981
+ NamingAnalyzer,
2982
+ NamingVerifier,
2983
+ NotInitializedError,
2984
+ PropagationEngine,
2985
+ RegexVerifier,
2986
+ Registry,
2987
+ RegistryError,
2988
+ Reporter,
2989
+ SeveritySchema,
2990
+ SpecBridgeConfigSchema,
2991
+ SpecBridgeError,
2992
+ StructureAnalyzer,
2993
+ VerificationConfigSchema,
2994
+ VerificationEngine,
2995
+ VerificationError,
2996
+ VerificationFrequencySchema,
2997
+ VerifierNotFoundError,
2998
+ buildDependencyGraph,
2999
+ builtinAnalyzers,
3000
+ builtinVerifiers,
3001
+ calculateConfidence,
3002
+ checkDegradation,
3003
+ createInferenceEngine,
3004
+ createPattern,
3005
+ createPropagationEngine,
3006
+ createRegistry,
3007
+ createScannerFromConfig,
3008
+ createVerificationEngine,
3009
+ createViolation,
3010
+ defaultConfig,
3011
+ ensureDir,
3012
+ extractSnippet,
3013
+ formatConsoleReport,
3014
+ formatContextAsJson,
3015
+ formatContextAsMarkdown,
3016
+ formatContextAsMcp,
3017
+ formatError,
3018
+ formatMarkdownReport,
3019
+ formatValidationErrors,
3020
+ generateContext,
3021
+ generateFormattedContext,
3022
+ generateReport,
3023
+ getAffectedFiles,
3024
+ getAffectingDecisions,
3025
+ getAnalyzer,
3026
+ getAnalyzerIds,
3027
+ getConfigPath,
3028
+ getDecisionsDir,
3029
+ getInferredDir,
3030
+ getReportsDir,
3031
+ getSpecBridgeDir,
3032
+ getTransitiveDependencies,
3033
+ getVerifier,
3034
+ getVerifierIds,
3035
+ getVerifiersDir,
3036
+ glob,
3037
+ isDirectory,
3038
+ loadConfig,
3039
+ loadDecisionFile,
3040
+ loadDecisionsFromDir,
3041
+ matchesAnyPattern,
3042
+ matchesPattern,
3043
+ mergeWithDefaults,
3044
+ minimatch,
3045
+ parseYaml,
3046
+ parseYamlDocument,
3047
+ pathExists,
3048
+ readFilesInDir,
3049
+ readTextFile,
3050
+ runInference,
3051
+ selectVerifierForConstraint,
3052
+ stringifyYaml,
3053
+ updateYamlDocument,
3054
+ validateConfig,
3055
+ validateDecision,
3056
+ validateDecisionFile,
3057
+ writeTextFile
3058
+ };
3059
+ //# sourceMappingURL=index.js.map