@ipation/specbridge 2.3.0 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -464,12 +464,7 @@ import fg from "fast-glob";
464
464
  import { minimatch } from "minimatch";
465
465
  import { relative, isAbsolute } from "path";
466
466
  async function glob(patterns, options = {}) {
467
- const {
468
- cwd = process.cwd(),
469
- ignore = [],
470
- absolute = false,
471
- onlyFiles = true
472
- } = options;
467
+ const { cwd = process.cwd(), ignore = [], absolute = false, onlyFiles = true } = options;
473
468
  return fg(patterns, {
474
469
  cwd,
475
470
  ignore,
@@ -612,17 +607,13 @@ var Registry = class {
612
607
  * Get decisions by tag
613
608
  */
614
609
  getByTag(tag) {
615
- return this.getAll().filter(
616
- (d) => d.metadata.tags?.includes(tag)
617
- );
610
+ return this.getAll().filter((d) => d.metadata.tags?.includes(tag));
618
611
  }
619
612
  /**
620
613
  * Get decisions by owner
621
614
  */
622
615
  getByOwner(owner) {
623
- return this.getAll().filter(
624
- (d) => d.metadata.owners.includes(owner)
625
- );
616
+ return this.getAll().filter((d) => d.metadata.owners.includes(owner));
626
617
  }
627
618
  /**
628
619
  * Apply filter to decisions
@@ -633,21 +624,15 @@ var Registry = class {
633
624
  return false;
634
625
  }
635
626
  if (filter.tags) {
636
- const hasTags = filter.tags.some(
637
- (tag) => decision.metadata.tags?.includes(tag)
638
- );
627
+ const hasTags = filter.tags.some((tag) => decision.metadata.tags?.includes(tag));
639
628
  if (!hasTags) return false;
640
629
  }
641
630
  if (filter.constraintType) {
642
- const hasType = decision.constraints.some(
643
- (c) => filter.constraintType?.includes(c.type)
644
- );
631
+ const hasType = decision.constraints.some((c) => filter.constraintType?.includes(c.type));
645
632
  if (!hasType) return false;
646
633
  }
647
634
  if (filter.severity) {
648
- const hasSeverity = decision.constraints.some(
649
- (c) => filter.severity?.includes(c.severity)
650
- );
635
+ const hasSeverity = decision.constraints.some((c) => filter.severity?.includes(c.severity));
651
636
  if (!hasSeverity) return false;
652
637
  }
653
638
  return true;
@@ -905,12 +890,24 @@ var FUNCTION_PATTERNS = [
905
890
  { convention: "snake_case", regex: /^[a-z][a-z0-9_]*$/, description: "Functions use snake_case" }
906
891
  ];
907
892
  var INTERFACE_PATTERNS = [
908
- { convention: "PascalCase", regex: /^[A-Z][a-zA-Z0-9]*$/, description: "Interfaces use PascalCase" },
909
- { convention: "IPrefixed", regex: /^I[A-Z][a-zA-Z0-9]*$/, description: "Interfaces are prefixed with I" }
893
+ {
894
+ convention: "PascalCase",
895
+ regex: /^[A-Z][a-zA-Z0-9]*$/,
896
+ description: "Interfaces use PascalCase"
897
+ },
898
+ {
899
+ convention: "IPrefixed",
900
+ regex: /^I[A-Z][a-zA-Z0-9]*$/,
901
+ description: "Interfaces are prefixed with I"
902
+ }
910
903
  ];
911
904
  var TYPE_PATTERNS = [
912
905
  { convention: "PascalCase", regex: /^[A-Z][a-zA-Z0-9]*$/, description: "Types use PascalCase" },
913
- { convention: "TSuffixed", regex: /^[A-Z][a-zA-Z0-9]*Type$/, description: "Types are suffixed with Type" }
906
+ {
907
+ convention: "TSuffixed",
908
+ regex: /^[A-Z][a-zA-Z0-9]*Type$/,
909
+ description: "Types are suffixed with Type"
910
+ }
914
911
  ];
915
912
  var NamingAnalyzer = class {
916
913
  id = "naming";
@@ -931,7 +928,10 @@ var NamingAnalyzer = class {
931
928
  analyzeClassNaming(scanner) {
932
929
  const classes = scanner.findClasses();
933
930
  if (classes.length < 3) return null;
934
- const matches = this.findBestMatch(classes.map((c) => c.name), CLASS_PATTERNS);
931
+ const matches = this.findBestMatch(
932
+ classes.map((c) => c.name),
933
+ CLASS_PATTERNS
934
+ );
935
935
  if (!matches) return null;
936
936
  return createPattern(this.id, {
937
937
  id: "naming-classes",
@@ -955,7 +955,10 @@ var NamingAnalyzer = class {
955
955
  analyzeFunctionNaming(scanner) {
956
956
  const functions = scanner.findFunctions();
957
957
  if (functions.length < 3) return null;
958
- const matches = this.findBestMatch(functions.map((f) => f.name), FUNCTION_PATTERNS);
958
+ const matches = this.findBestMatch(
959
+ functions.map((f) => f.name),
960
+ FUNCTION_PATTERNS
961
+ );
959
962
  if (!matches) return null;
960
963
  return createPattern(this.id, {
961
964
  id: "naming-functions",
@@ -979,7 +982,10 @@ var NamingAnalyzer = class {
979
982
  analyzeInterfaceNaming(scanner) {
980
983
  const interfaces = scanner.findInterfaces();
981
984
  if (interfaces.length < 3) return null;
982
- const matches = this.findBestMatch(interfaces.map((i) => i.name), INTERFACE_PATTERNS);
985
+ const matches = this.findBestMatch(
986
+ interfaces.map((i) => i.name),
987
+ INTERFACE_PATTERNS
988
+ );
983
989
  if (!matches) return null;
984
990
  return createPattern(this.id, {
985
991
  id: "naming-interfaces",
@@ -1003,7 +1009,10 @@ var NamingAnalyzer = class {
1003
1009
  analyzeTypeNaming(scanner) {
1004
1010
  const types = scanner.findTypeAliases();
1005
1011
  if (types.length < 3) return null;
1006
- const matches = this.findBestMatch(types.map((t) => t.name), TYPE_PATTERNS);
1012
+ const matches = this.findBestMatch(
1013
+ types.map((t) => t.name),
1014
+ TYPE_PATTERNS
1015
+ );
1007
1016
  if (!matches) return null;
1008
1017
  return createPattern(this.id, {
1009
1018
  id: "naming-types",
@@ -1092,8 +1101,12 @@ var ImportsAnalyzer = class {
1092
1101
  analyzeRelativeImports(scanner) {
1093
1102
  const imports = scanner.findImports();
1094
1103
  const relativeImports = imports.filter((i) => i.module.startsWith("."));
1095
- const absoluteImports = imports.filter((i) => !i.module.startsWith(".") && !i.module.startsWith("@"));
1096
- const aliasImports = imports.filter((i) => i.module.startsWith("@/") || i.module.startsWith("~"));
1104
+ const absoluteImports = imports.filter(
1105
+ (i) => !i.module.startsWith(".") && !i.module.startsWith("@")
1106
+ );
1107
+ const aliasImports = imports.filter(
1108
+ (i) => i.module.startsWith("@/") || i.module.startsWith("~")
1109
+ );
1097
1110
  const total = relativeImports.length + absoluteImports.length + aliasImports.length;
1098
1111
  if (total < 10) return null;
1099
1112
  if (aliasImports.length > relativeImports.length && aliasImports.length >= 5) {
@@ -1160,18 +1173,20 @@ var ImportsAnalyzer = class {
1160
1173
  for (const [packageName, data] of moduleCounts) {
1161
1174
  if (data.count >= 5) {
1162
1175
  const confidence = Math.min(100, 50 + data.count * 2);
1163
- patterns.push(createPattern(this.id, {
1164
- id: `imports-module-${packageName.replace(/[/@]/g, "-")}`,
1165
- name: `${packageName} Usage`,
1166
- description: `${packageName} is used across ${data.count} files`,
1167
- confidence,
1168
- occurrences: data.count,
1169
- examples: data.examples.slice(0, 3).map((i) => ({
1170
- file: i.file,
1171
- line: i.line,
1172
- snippet: `import { ${i.named.slice(0, 2).join(", ") || "..."} } from '${i.module}'`
1173
- }))
1174
- }));
1176
+ patterns.push(
1177
+ createPattern(this.id, {
1178
+ id: `imports-module-${packageName.replace(/[/@]/g, "-")}`,
1179
+ name: `${packageName} Usage`,
1180
+ description: `${packageName} is used across ${data.count} files`,
1181
+ confidence,
1182
+ occurrences: data.count,
1183
+ examples: data.examples.slice(0, 3).map((i) => ({
1184
+ file: i.file,
1185
+ line: i.line,
1186
+ snippet: `import { ${i.named.slice(0, 2).join(", ") || "..."} } from '${i.module}'`
1187
+ }))
1188
+ })
1189
+ );
1175
1190
  }
1176
1191
  }
1177
1192
  return patterns;
@@ -1216,24 +1231,26 @@ var StructureAnalyzer = class {
1216
1231
  const count = dirCounts.get(name);
1217
1232
  if (count && count >= 3) {
1218
1233
  const exampleFiles = files.filter((f) => basename(dirname2(f.path)) === name).slice(0, 3);
1219
- patterns.push(createPattern(this.id, {
1220
- id: `structure-dir-${name}`,
1221
- name: `${name}/ Directory Convention`,
1222
- description,
1223
- confidence: Math.min(100, 60 + count * 5),
1224
- occurrences: count,
1225
- examples: exampleFiles.map((f) => ({
1226
- file: f.path,
1227
- line: 1,
1228
- snippet: basename(f.path)
1229
- })),
1230
- suggestedConstraint: {
1231
- type: "convention",
1232
- rule: `${name.charAt(0).toUpperCase() + name.slice(1)} should be placed in the ${name}/ directory`,
1233
- severity: "low",
1234
- scope: `src/**/${name}/**/*.ts`
1235
- }
1236
- }));
1234
+ patterns.push(
1235
+ createPattern(this.id, {
1236
+ id: `structure-dir-${name}`,
1237
+ name: `${name}/ Directory Convention`,
1238
+ description,
1239
+ confidence: Math.min(100, 60 + count * 5),
1240
+ occurrences: count,
1241
+ examples: exampleFiles.map((f) => ({
1242
+ file: f.path,
1243
+ line: 1,
1244
+ snippet: basename(f.path)
1245
+ })),
1246
+ suggestedConstraint: {
1247
+ type: "convention",
1248
+ rule: `${name.charAt(0).toUpperCase() + name.slice(1)} should be placed in the ${name}/ directory`,
1249
+ severity: "low",
1250
+ scope: `src/**/${name}/**/*.ts`
1251
+ }
1252
+ })
1253
+ );
1237
1254
  }
1238
1255
  }
1239
1256
  return patterns;
@@ -1243,29 +1260,55 @@ var StructureAnalyzer = class {
1243
1260
  const suffixPatterns = [
1244
1261
  { suffix: ".test.ts", pattern: /\.test\.ts$/, description: "Test files use .test.ts suffix" },
1245
1262
  { suffix: ".spec.ts", pattern: /\.spec\.ts$/, description: "Test files use .spec.ts suffix" },
1246
- { suffix: ".types.ts", pattern: /\.types\.ts$/, description: "Type definition files use .types.ts suffix" },
1247
- { suffix: ".utils.ts", pattern: /\.utils\.ts$/, description: "Utility files use .utils.ts suffix" },
1248
- { suffix: ".service.ts", pattern: /\.service\.ts$/, description: "Service files use .service.ts suffix" },
1249
- { suffix: ".controller.ts", pattern: /\.controller\.ts$/, description: "Controller files use .controller.ts suffix" },
1250
- { suffix: ".model.ts", pattern: /\.model\.ts$/, description: "Model files use .model.ts suffix" },
1251
- { suffix: ".schema.ts", pattern: /\.schema\.ts$/, description: "Schema files use .schema.ts suffix" }
1263
+ {
1264
+ suffix: ".types.ts",
1265
+ pattern: /\.types\.ts$/,
1266
+ description: "Type definition files use .types.ts suffix"
1267
+ },
1268
+ {
1269
+ suffix: ".utils.ts",
1270
+ pattern: /\.utils\.ts$/,
1271
+ description: "Utility files use .utils.ts suffix"
1272
+ },
1273
+ {
1274
+ suffix: ".service.ts",
1275
+ pattern: /\.service\.ts$/,
1276
+ description: "Service files use .service.ts suffix"
1277
+ },
1278
+ {
1279
+ suffix: ".controller.ts",
1280
+ pattern: /\.controller\.ts$/,
1281
+ description: "Controller files use .controller.ts suffix"
1282
+ },
1283
+ {
1284
+ suffix: ".model.ts",
1285
+ pattern: /\.model\.ts$/,
1286
+ description: "Model files use .model.ts suffix"
1287
+ },
1288
+ {
1289
+ suffix: ".schema.ts",
1290
+ pattern: /\.schema\.ts$/,
1291
+ description: "Schema files use .schema.ts suffix"
1292
+ }
1252
1293
  ];
1253
1294
  for (const { suffix, pattern, description } of suffixPatterns) {
1254
1295
  const matchingFiles = files.filter((f) => pattern.test(f.path));
1255
1296
  if (matchingFiles.length >= 3) {
1256
1297
  const confidence = Math.min(100, 60 + matchingFiles.length * 3);
1257
- patterns.push(createPattern(this.id, {
1258
- id: `structure-suffix-${suffix.replace(/\./g, "-")}`,
1259
- name: `${suffix} File Naming`,
1260
- description,
1261
- confidence,
1262
- occurrences: matchingFiles.length,
1263
- examples: matchingFiles.slice(0, 3).map((f) => ({
1264
- file: f.path,
1265
- line: 1,
1266
- snippet: basename(f.path)
1267
- }))
1268
- }));
1298
+ patterns.push(
1299
+ createPattern(this.id, {
1300
+ id: `structure-suffix-${suffix.replace(/\./g, "-")}`,
1301
+ name: `${suffix} File Naming`,
1302
+ description,
1303
+ confidence,
1304
+ occurrences: matchingFiles.length,
1305
+ examples: matchingFiles.slice(0, 3).map((f) => ({
1306
+ file: f.path,
1307
+ line: 1,
1308
+ snippet: basename(f.path)
1309
+ }))
1310
+ })
1311
+ );
1269
1312
  }
1270
1313
  }
1271
1314
  return patterns;
@@ -1582,7 +1625,6 @@ async function runInference(config, options) {
1582
1625
 
1583
1626
  // src/verification/engine.ts
1584
1627
  import { Project as Project2 } from "ts-morph";
1585
- import chalk from "chalk";
1586
1628
 
1587
1629
  // src/verification/verifiers/base.ts
1588
1630
  function defineVerifierPlugin(plugin) {
@@ -1644,17 +1686,19 @@ var NamingVerifier = class {
1644
1686
  for (const classDecl of sourceFile.getClasses()) {
1645
1687
  const name = classDecl.getName();
1646
1688
  if (name && !pattern.regex.test(name)) {
1647
- violations.push(createViolation({
1648
- decisionId,
1649
- constraintId: constraint.id,
1650
- type: constraint.type,
1651
- severity: constraint.severity,
1652
- message: `Class "${name}" does not follow ${pattern.description} naming convention`,
1653
- file: filePath,
1654
- line: classDecl.getStartLineNumber(),
1655
- column: classDecl.getStart() - classDecl.getStartLinePos(),
1656
- suggestion: `Rename to follow ${pattern.description}`
1657
- }));
1689
+ violations.push(
1690
+ createViolation({
1691
+ decisionId,
1692
+ constraintId: constraint.id,
1693
+ type: constraint.type,
1694
+ severity: constraint.severity,
1695
+ message: `Class "${name}" does not follow ${pattern.description} naming convention`,
1696
+ file: filePath,
1697
+ line: classDecl.getStartLineNumber(),
1698
+ column: classDecl.getStart() - classDecl.getStartLinePos(),
1699
+ suggestion: `Rename to follow ${pattern.description}`
1700
+ })
1701
+ );
1658
1702
  }
1659
1703
  }
1660
1704
  }
@@ -1662,16 +1706,18 @@ var NamingVerifier = class {
1662
1706
  for (const funcDecl of sourceFile.getFunctions()) {
1663
1707
  const name = funcDecl.getName();
1664
1708
  if (name && !pattern.regex.test(name)) {
1665
- violations.push(createViolation({
1666
- decisionId,
1667
- constraintId: constraint.id,
1668
- type: constraint.type,
1669
- severity: constraint.severity,
1670
- message: `Function "${name}" does not follow ${pattern.description} naming convention`,
1671
- file: filePath,
1672
- line: funcDecl.getStartLineNumber(),
1673
- suggestion: `Rename to follow ${pattern.description}`
1674
- }));
1709
+ violations.push(
1710
+ createViolation({
1711
+ decisionId,
1712
+ constraintId: constraint.id,
1713
+ type: constraint.type,
1714
+ severity: constraint.severity,
1715
+ message: `Function "${name}" does not follow ${pattern.description} naming convention`,
1716
+ file: filePath,
1717
+ line: funcDecl.getStartLineNumber(),
1718
+ suggestion: `Rename to follow ${pattern.description}`
1719
+ })
1720
+ );
1675
1721
  }
1676
1722
  }
1677
1723
  }
@@ -1679,16 +1725,18 @@ var NamingVerifier = class {
1679
1725
  for (const interfaceDecl of sourceFile.getInterfaces()) {
1680
1726
  const name = interfaceDecl.getName();
1681
1727
  if (!pattern.regex.test(name)) {
1682
- violations.push(createViolation({
1683
- decisionId,
1684
- constraintId: constraint.id,
1685
- type: constraint.type,
1686
- severity: constraint.severity,
1687
- message: `Interface "${name}" does not follow ${pattern.description} naming convention`,
1688
- file: filePath,
1689
- line: interfaceDecl.getStartLineNumber(),
1690
- suggestion: `Rename to follow ${pattern.description}`
1691
- }));
1728
+ violations.push(
1729
+ createViolation({
1730
+ decisionId,
1731
+ constraintId: constraint.id,
1732
+ type: constraint.type,
1733
+ severity: constraint.severity,
1734
+ message: `Interface "${name}" does not follow ${pattern.description} naming convention`,
1735
+ file: filePath,
1736
+ line: interfaceDecl.getStartLineNumber(),
1737
+ suggestion: `Rename to follow ${pattern.description}`
1738
+ })
1739
+ );
1692
1740
  }
1693
1741
  }
1694
1742
  }
@@ -1696,16 +1744,18 @@ var NamingVerifier = class {
1696
1744
  for (const typeAlias of sourceFile.getTypeAliases()) {
1697
1745
  const name = typeAlias.getName();
1698
1746
  if (!pattern.regex.test(name)) {
1699
- violations.push(createViolation({
1700
- decisionId,
1701
- constraintId: constraint.id,
1702
- type: constraint.type,
1703
- severity: constraint.severity,
1704
- message: `Type "${name}" does not follow ${pattern.description} naming convention`,
1705
- file: filePath,
1706
- line: typeAlias.getStartLineNumber(),
1707
- suggestion: `Rename to follow ${pattern.description}`
1708
- }));
1747
+ violations.push(
1748
+ createViolation({
1749
+ decisionId,
1750
+ constraintId: constraint.id,
1751
+ type: constraint.type,
1752
+ severity: constraint.severity,
1753
+ message: `Type "${name}" does not follow ${pattern.description} naming convention`,
1754
+ file: filePath,
1755
+ line: typeAlias.getStartLineNumber(),
1756
+ suggestion: `Rename to follow ${pattern.description}`
1757
+ })
1758
+ );
1709
1759
  }
1710
1760
  }
1711
1761
  }
@@ -1740,20 +1790,22 @@ var ImportsVerifier = class {
1740
1790
  const ms = importDecl.getModuleSpecifier();
1741
1791
  const start = ms.getStart() + 1;
1742
1792
  const end = ms.getEnd() - 1;
1743
- violations.push(createViolation({
1744
- decisionId,
1745
- constraintId: constraint.id,
1746
- type: constraint.type,
1747
- severity: constraint.severity,
1748
- message: `Relative import "${moduleSpec}" should include a .js extension`,
1749
- file: filePath,
1750
- line: importDecl.getStartLineNumber(),
1751
- suggestion: `Update to "${suggested}"`,
1752
- autofix: {
1753
- description: "Add/normalize .js extension in import specifier",
1754
- edits: [{ start, end, text: suggested }]
1755
- }
1756
- }));
1793
+ violations.push(
1794
+ createViolation({
1795
+ decisionId,
1796
+ constraintId: constraint.id,
1797
+ type: constraint.type,
1798
+ severity: constraint.severity,
1799
+ message: `Relative import "${moduleSpec}" should include a .js extension`,
1800
+ file: filePath,
1801
+ line: importDecl.getStartLineNumber(),
1802
+ suggestion: `Update to "${suggested}"`,
1803
+ autofix: {
1804
+ description: "Add/normalize .js extension in import specifier",
1805
+ edits: [{ start, end, text: suggested }]
1806
+ }
1807
+ })
1808
+ );
1757
1809
  }
1758
1810
  }
1759
1811
  if (rule.includes("barrel") || rule.includes("index")) {
@@ -1762,16 +1814,18 @@ var ImportsVerifier = class {
1762
1814
  if (!moduleSpec.startsWith(".")) continue;
1763
1815
  if (moduleSpec.match(/\.(ts|js|tsx|jsx)$/) || moduleSpec.match(/\/[^/]+$/)) {
1764
1816
  if (!moduleSpec.endsWith("/index") && !moduleSpec.endsWith("index")) {
1765
- violations.push(createViolation({
1766
- decisionId,
1767
- constraintId: constraint.id,
1768
- type: constraint.type,
1769
- severity: constraint.severity,
1770
- message: `Import from "${moduleSpec}" should use barrel (index) import`,
1771
- file: filePath,
1772
- line: importDecl.getStartLineNumber(),
1773
- suggestion: "Import from the parent directory index file instead"
1774
- }));
1817
+ violations.push(
1818
+ createViolation({
1819
+ decisionId,
1820
+ constraintId: constraint.id,
1821
+ type: constraint.type,
1822
+ severity: constraint.severity,
1823
+ message: `Import from "${moduleSpec}" should use barrel (index) import`,
1824
+ file: filePath,
1825
+ line: importDecl.getStartLineNumber(),
1826
+ suggestion: "Import from the parent directory index file instead"
1827
+ })
1828
+ );
1775
1829
  }
1776
1830
  }
1777
1831
  }
@@ -1780,16 +1834,18 @@ var ImportsVerifier = class {
1780
1834
  for (const importDecl of sourceFile.getImportDeclarations()) {
1781
1835
  const moduleSpec = importDecl.getModuleSpecifierValue();
1782
1836
  if (moduleSpec.match(/^\.\.\/\.\.\/\.\.\//)) {
1783
- violations.push(createViolation({
1784
- decisionId,
1785
- constraintId: constraint.id,
1786
- type: constraint.type,
1787
- severity: constraint.severity,
1788
- message: `Deep relative import "${moduleSpec}" should use path alias`,
1789
- file: filePath,
1790
- line: importDecl.getStartLineNumber(),
1791
- suggestion: "Use path alias (e.g., @/module) for deep imports"
1792
- }));
1837
+ violations.push(
1838
+ createViolation({
1839
+ decisionId,
1840
+ constraintId: constraint.id,
1841
+ type: constraint.type,
1842
+ severity: constraint.severity,
1843
+ message: `Deep relative import "${moduleSpec}" should use path alias`,
1844
+ file: filePath,
1845
+ line: importDecl.getStartLineNumber(),
1846
+ suggestion: "Use path alias (e.g., @/module) for deep imports"
1847
+ })
1848
+ );
1793
1849
  }
1794
1850
  }
1795
1851
  }
@@ -1798,16 +1854,18 @@ var ImportsVerifier = class {
1798
1854
  for (const importDecl of sourceFile.getImportDeclarations()) {
1799
1855
  const moduleSpec = importDecl.getModuleSpecifierValue();
1800
1856
  if (moduleSpec.includes(currentFilename.split("/").pop() || "")) {
1801
- violations.push(createViolation({
1802
- decisionId,
1803
- constraintId: constraint.id,
1804
- type: constraint.type,
1805
- severity: constraint.severity,
1806
- message: `Possible circular import detected: "${moduleSpec}"`,
1807
- file: filePath,
1808
- line: importDecl.getStartLineNumber(),
1809
- suggestion: "Review import structure for circular dependencies"
1810
- }));
1857
+ violations.push(
1858
+ createViolation({
1859
+ decisionId,
1860
+ constraintId: constraint.id,
1861
+ type: constraint.type,
1862
+ severity: constraint.severity,
1863
+ message: `Possible circular import detected: "${moduleSpec}"`,
1864
+ file: filePath,
1865
+ line: importDecl.getStartLineNumber(),
1866
+ suggestion: "Review import structure for circular dependencies"
1867
+ })
1868
+ );
1811
1869
  }
1812
1870
  }
1813
1871
  }
@@ -1815,16 +1873,18 @@ var ImportsVerifier = class {
1815
1873
  for (const importDecl of sourceFile.getImportDeclarations()) {
1816
1874
  const namespaceImport = importDecl.getNamespaceImport();
1817
1875
  if (namespaceImport) {
1818
- violations.push(createViolation({
1819
- decisionId,
1820
- constraintId: constraint.id,
1821
- type: constraint.type,
1822
- severity: constraint.severity,
1823
- message: `Namespace import "* as ${namespaceImport.getText()}" should use named imports`,
1824
- file: filePath,
1825
- line: importDecl.getStartLineNumber(),
1826
- suggestion: "Use specific named imports instead of namespace import"
1827
- }));
1876
+ violations.push(
1877
+ createViolation({
1878
+ decisionId,
1879
+ constraintId: constraint.id,
1880
+ type: constraint.type,
1881
+ severity: constraint.severity,
1882
+ message: `Namespace import "* as ${namespaceImport.getText()}" should use named imports`,
1883
+ file: filePath,
1884
+ line: importDecl.getStartLineNumber(),
1885
+ suggestion: "Use specific named imports instead of namespace import"
1886
+ })
1887
+ );
1828
1888
  }
1829
1889
  }
1830
1890
  }
@@ -1850,16 +1910,18 @@ var ErrorsVerifier = class {
1850
1910
  if (!className?.endsWith("Error") && !className?.endsWith("Exception")) continue;
1851
1911
  const extendsClause = classDecl.getExtends();
1852
1912
  if (!extendsClause) {
1853
- violations.push(createViolation({
1854
- decisionId,
1855
- constraintId: constraint.id,
1856
- type: constraint.type,
1857
- severity: constraint.severity,
1858
- message: `Error class "${className}" does not extend any base class`,
1859
- file: filePath,
1860
- line: classDecl.getStartLineNumber(),
1861
- suggestion: requiredBase ? `Extend ${requiredBase}` : "Extend a base error class for consistent error handling"
1862
- }));
1913
+ violations.push(
1914
+ createViolation({
1915
+ decisionId,
1916
+ constraintId: constraint.id,
1917
+ type: constraint.type,
1918
+ severity: constraint.severity,
1919
+ message: `Error class "${className}" does not extend any base class`,
1920
+ file: filePath,
1921
+ line: classDecl.getStartLineNumber(),
1922
+ suggestion: requiredBase ? `Extend ${requiredBase}` : "Extend a base error class for consistent error handling"
1923
+ })
1924
+ );
1863
1925
  } else if (requiredBase) {
1864
1926
  const baseName = extendsClause.getText();
1865
1927
  if (baseName !== requiredBase && baseName !== "Error") {
@@ -1874,16 +1936,18 @@ var ErrorsVerifier = class {
1874
1936
  if (expression) {
1875
1937
  const text = expression.getText();
1876
1938
  if (text.startsWith("new Error(")) {
1877
- violations.push(createViolation({
1878
- decisionId,
1879
- constraintId: constraint.id,
1880
- type: constraint.type,
1881
- severity: constraint.severity,
1882
- message: "Throwing generic Error instead of custom error class",
1883
- file: filePath,
1884
- line: node.getStartLineNumber(),
1885
- suggestion: "Use a custom error class for better error handling"
1886
- }));
1939
+ violations.push(
1940
+ createViolation({
1941
+ decisionId,
1942
+ constraintId: constraint.id,
1943
+ type: constraint.type,
1944
+ severity: constraint.severity,
1945
+ message: "Throwing generic Error instead of custom error class",
1946
+ file: filePath,
1947
+ line: node.getStartLineNumber(),
1948
+ suggestion: "Use a custom error class for better error handling"
1949
+ })
1950
+ );
1887
1951
  }
1888
1952
  }
1889
1953
  }
@@ -1897,16 +1961,18 @@ var ErrorsVerifier = class {
1897
1961
  const block = catchClause.getBlock();
1898
1962
  const statements = block.getStatements();
1899
1963
  if (statements.length === 0) {
1900
- violations.push(createViolation({
1901
- decisionId,
1902
- constraintId: constraint.id,
1903
- type: constraint.type,
1904
- severity: constraint.severity,
1905
- message: "Empty catch block swallows error without handling",
1906
- file: filePath,
1907
- line: catchClause.getStartLineNumber(),
1908
- suggestion: "Add error handling, logging, or rethrow the error"
1909
- }));
1964
+ violations.push(
1965
+ createViolation({
1966
+ decisionId,
1967
+ constraintId: constraint.id,
1968
+ type: constraint.type,
1969
+ severity: constraint.severity,
1970
+ message: "Empty catch block swallows error without handling",
1971
+ file: filePath,
1972
+ line: catchClause.getStartLineNumber(),
1973
+ suggestion: "Add error handling, logging, or rethrow the error"
1974
+ })
1975
+ );
1910
1976
  }
1911
1977
  }
1912
1978
  }
@@ -1918,16 +1984,18 @@ var ErrorsVerifier = class {
1918
1984
  const expression = node.getExpression();
1919
1985
  const text = expression.getText();
1920
1986
  if (text === "console.error" || text === "console.log") {
1921
- violations.push(createViolation({
1922
- decisionId,
1923
- constraintId: constraint.id,
1924
- type: constraint.type,
1925
- severity: constraint.severity,
1926
- message: `Using ${text} instead of proper logging`,
1927
- file: filePath,
1928
- line: node.getStartLineNumber(),
1929
- suggestion: "Use a proper logging library"
1930
- }));
1987
+ violations.push(
1988
+ createViolation({
1989
+ decisionId,
1990
+ constraintId: constraint.id,
1991
+ type: constraint.type,
1992
+ severity: constraint.severity,
1993
+ message: `Using ${text} instead of proper logging`,
1994
+ file: filePath,
1995
+ line: node.getStartLineNumber(),
1996
+ suggestion: "Use a proper logging library"
1997
+ })
1998
+ );
1931
1999
  }
1932
2000
  }
1933
2001
  });
@@ -1958,16 +2026,18 @@ var RegexVerifier = class {
1958
2026
  while ((match = regex.exec(fileText)) !== null) {
1959
2027
  const beforeMatch = fileText.substring(0, match.index);
1960
2028
  const lineNumber = beforeMatch.split("\n").length;
1961
- violations.push(createViolation({
1962
- decisionId,
1963
- constraintId: constraint.id,
1964
- type: constraint.type,
1965
- severity: constraint.severity,
1966
- message: `Found forbidden pattern: "${match[0]}"`,
1967
- file: filePath,
1968
- line: lineNumber,
1969
- suggestion: `Remove or replace the pattern matching /${patternToForbid}/`
1970
- }));
2029
+ violations.push(
2030
+ createViolation({
2031
+ decisionId,
2032
+ constraintId: constraint.id,
2033
+ type: constraint.type,
2034
+ severity: constraint.severity,
2035
+ message: `Found forbidden pattern: "${match[0]}"`,
2036
+ file: filePath,
2037
+ line: lineNumber,
2038
+ suggestion: `Remove or replace the pattern matching /${patternToForbid}/`
2039
+ })
2040
+ );
1971
2041
  }
1972
2042
  } catch {
1973
2043
  }
@@ -1977,15 +2047,17 @@ var RegexVerifier = class {
1977
2047
  try {
1978
2048
  const regex = new RegExp(patternToRequire);
1979
2049
  if (!regex.test(fileText)) {
1980
- violations.push(createViolation({
1981
- decisionId,
1982
- constraintId: constraint.id,
1983
- type: constraint.type,
1984
- severity: constraint.severity,
1985
- message: `File does not contain required pattern: /${patternToRequire}/`,
1986
- file: filePath,
1987
- suggestion: `Add code matching /${patternToRequire}/`
1988
- }));
2050
+ violations.push(
2051
+ createViolation({
2052
+ decisionId,
2053
+ constraintId: constraint.id,
2054
+ type: constraint.type,
2055
+ severity: constraint.severity,
2056
+ message: `File does not contain required pattern: /${patternToRequire}/`,
2057
+ file: filePath,
2058
+ suggestion: `Add code matching /${patternToRequire}/`
2059
+ })
2060
+ );
1989
2061
  }
1990
2062
  } catch {
1991
2063
  }
@@ -2124,7 +2196,9 @@ function parseBannedDependency(rule) {
2124
2196
  return value.length > 0 ? value : null;
2125
2197
  }
2126
2198
  function parseLayerRule(rule) {
2127
- const m = rule.match(/(\w+)\s{1,5}layer\s{1,5}cannot\s{1,5}depend\s{1,5}on\s{1,5}(\w+)\s{1,5}layer/i);
2199
+ const m = rule.match(
2200
+ /(\w+)\s{1,5}layer\s{1,5}cannot\s{1,5}depend\s{1,5}on\s{1,5}(\w+)\s{1,5}layer/i
2201
+ );
2128
2202
  const fromLayer = m?.[1]?.toLowerCase();
2129
2203
  const toLayer = m?.[2]?.toLowerCase();
2130
2204
  if (!fromLayer || !toLayer) return null;
@@ -2150,22 +2224,25 @@ var DependencyVerifier = class {
2150
2224
  const sccs = tarjanScc(graph);
2151
2225
  const current = projectFilePath;
2152
2226
  for (const scc of sccs) {
2153
- const hasSelfLoop = scc.length === 1 && (graph.get(scc[0])?.has(scc[0]) ?? false);
2227
+ const first = scc[0];
2228
+ const hasSelfLoop = first !== void 0 && scc.length === 1 && (graph.get(first)?.has(first) ?? false);
2154
2229
  const isCycle = scc.length > 1 || hasSelfLoop;
2155
2230
  if (!isCycle) continue;
2156
2231
  if (!scc.includes(current)) continue;
2157
2232
  const sorted = [...scc].sort();
2158
2233
  if (sorted[0] !== current) continue;
2159
- violations.push(createViolation({
2160
- decisionId,
2161
- constraintId: constraint.id,
2162
- type: constraint.type,
2163
- severity: constraint.severity,
2164
- message: `Circular dependency detected across: ${sorted.join(" -> ")}`,
2165
- file: filePath,
2166
- line: 1,
2167
- suggestion: "Break the cycle by extracting shared abstractions or reversing the dependency"
2168
- }));
2234
+ violations.push(
2235
+ createViolation({
2236
+ decisionId,
2237
+ constraintId: constraint.id,
2238
+ type: constraint.type,
2239
+ severity: constraint.severity,
2240
+ message: `Circular dependency detected across: ${sorted.join(" -> ")}`,
2241
+ file: filePath,
2242
+ line: 1,
2243
+ suggestion: "Break the cycle by extracting shared abstractions or reversing the dependency"
2244
+ })
2245
+ );
2169
2246
  }
2170
2247
  }
2171
2248
  const layerRule = parseLayerRule(rule);
@@ -2175,16 +2252,18 @@ var DependencyVerifier = class {
2175
2252
  const resolved = resolveToSourceFilePath(project, projectFilePath, moduleSpec);
2176
2253
  if (!resolved) continue;
2177
2254
  if (fileInLayer(resolved, layerRule.toLayer)) {
2178
- violations.push(createViolation({
2179
- decisionId,
2180
- constraintId: constraint.id,
2181
- type: constraint.type,
2182
- severity: constraint.severity,
2183
- message: `Layer violation: ${layerRule.fromLayer} depends on ${layerRule.toLayer} via import "${moduleSpec}"`,
2184
- file: filePath,
2185
- line: importDecl.getStartLineNumber(),
2186
- suggestion: `Refactor to remove dependency from ${layerRule.fromLayer} to ${layerRule.toLayer}`
2187
- }));
2255
+ violations.push(
2256
+ createViolation({
2257
+ decisionId,
2258
+ constraintId: constraint.id,
2259
+ type: constraint.type,
2260
+ severity: constraint.severity,
2261
+ message: `Layer violation: ${layerRule.fromLayer} depends on ${layerRule.toLayer} via import "${moduleSpec}"`,
2262
+ file: filePath,
2263
+ line: importDecl.getStartLineNumber(),
2264
+ suggestion: `Refactor to remove dependency from ${layerRule.fromLayer} to ${layerRule.toLayer}`
2265
+ })
2266
+ );
2188
2267
  }
2189
2268
  }
2190
2269
  }
@@ -2194,16 +2273,18 @@ var DependencyVerifier = class {
2194
2273
  for (const importDecl of sourceFile.getImportDeclarations()) {
2195
2274
  const moduleSpec = importDecl.getModuleSpecifierValue();
2196
2275
  if (moduleSpec.toLowerCase().includes(bannedLower)) {
2197
- violations.push(createViolation({
2198
- decisionId,
2199
- constraintId: constraint.id,
2200
- type: constraint.type,
2201
- severity: constraint.severity,
2202
- message: `Banned dependency import detected: "${moduleSpec}"`,
2203
- file: filePath,
2204
- line: importDecl.getStartLineNumber(),
2205
- suggestion: `Remove or replace dependency "${banned}"`
2206
- }));
2276
+ violations.push(
2277
+ createViolation({
2278
+ decisionId,
2279
+ constraintId: constraint.id,
2280
+ type: constraint.type,
2281
+ severity: constraint.severity,
2282
+ message: `Banned dependency import detected: "${moduleSpec}"`,
2283
+ file: filePath,
2284
+ line: importDecl.getStartLineNumber(),
2285
+ suggestion: `Remove or replace dependency "${banned}"`
2286
+ })
2287
+ );
2207
2288
  }
2208
2289
  }
2209
2290
  }
@@ -2214,16 +2295,18 @@ var DependencyVerifier = class {
2214
2295
  if (!moduleSpec.startsWith(".")) continue;
2215
2296
  const depth = (moduleSpec.match(/\.\.\//g) || []).length;
2216
2297
  if (depth > maxDepth) {
2217
- violations.push(createViolation({
2218
- decisionId,
2219
- constraintId: constraint.id,
2220
- type: constraint.type,
2221
- severity: constraint.severity,
2222
- message: `Import depth ${depth} exceeds maximum ${maxDepth}: "${moduleSpec}"`,
2223
- file: filePath,
2224
- line: importDecl.getStartLineNumber(),
2225
- suggestion: "Use a shallower module boundary (or introduce a public entrypoint for this dependency)"
2226
- }));
2298
+ violations.push(
2299
+ createViolation({
2300
+ decisionId,
2301
+ constraintId: constraint.id,
2302
+ type: constraint.type,
2303
+ severity: constraint.severity,
2304
+ message: `Import depth ${depth} exceeds maximum ${maxDepth}: "${moduleSpec}"`,
2305
+ file: filePath,
2306
+ line: importDecl.getStartLineNumber(),
2307
+ suggestion: "Use a shallower module boundary (or introduce a public entrypoint for this dependency)"
2308
+ })
2309
+ );
2227
2310
  }
2228
2311
  }
2229
2312
  }
@@ -2232,7 +2315,9 @@ var DependencyVerifier = class {
2232
2315
  };
2233
2316
 
2234
2317
  // src/verification/verifiers/complexity.ts
2235
- import { Node as Node4 } from "ts-morph";
2318
+ import {
2319
+ Node as Node4
2320
+ } from "ts-morph";
2236
2321
  import { SyntaxKind as SyntaxKind2 } from "ts-morph";
2237
2322
  function parseLimit(rule, pattern) {
2238
2323
  const m = rule.match(pattern);
@@ -2316,16 +2401,18 @@ var ComplexityVerifier = class {
2316
2401
  if (maxLines !== null) {
2317
2402
  const lineCount = getFileLineCount(sourceFile.getFullText());
2318
2403
  if (lineCount > maxLines) {
2319
- violations.push(createViolation({
2320
- decisionId,
2321
- constraintId: constraint.id,
2322
- type: constraint.type,
2323
- severity: constraint.severity,
2324
- message: `File has ${lineCount} lines which exceeds maximum ${maxLines}`,
2325
- file: filePath,
2326
- line: 1,
2327
- suggestion: "Split the file into smaller modules"
2328
- }));
2404
+ violations.push(
2405
+ createViolation({
2406
+ decisionId,
2407
+ constraintId: constraint.id,
2408
+ type: constraint.type,
2409
+ severity: constraint.severity,
2410
+ message: `File has ${lineCount} lines which exceeds maximum ${maxLines}`,
2411
+ file: filePath,
2412
+ line: 1,
2413
+ suggestion: "Split the file into smaller modules"
2414
+ })
2415
+ );
2329
2416
  }
2330
2417
  }
2331
2418
  const functionLikes = [
@@ -2339,46 +2426,52 @@ var ComplexityVerifier = class {
2339
2426
  if (maxComplexity !== null) {
2340
2427
  const complexity = calculateCyclomaticComplexity(fn);
2341
2428
  if (complexity > maxComplexity) {
2342
- violations.push(createViolation({
2343
- decisionId,
2344
- constraintId: constraint.id,
2345
- type: constraint.type,
2346
- severity: constraint.severity,
2347
- message: `Function ${fnName} has cyclomatic complexity ${complexity} which exceeds maximum ${maxComplexity}`,
2348
- file: filePath,
2349
- line: fn.getStartLineNumber(),
2350
- suggestion: "Refactor to reduce branching or extract smaller functions"
2351
- }));
2429
+ violations.push(
2430
+ createViolation({
2431
+ decisionId,
2432
+ constraintId: constraint.id,
2433
+ type: constraint.type,
2434
+ severity: constraint.severity,
2435
+ message: `Function ${fnName} has cyclomatic complexity ${complexity} which exceeds maximum ${maxComplexity}`,
2436
+ file: filePath,
2437
+ line: fn.getStartLineNumber(),
2438
+ suggestion: "Refactor to reduce branching or extract smaller functions"
2439
+ })
2440
+ );
2352
2441
  }
2353
2442
  }
2354
2443
  if (maxParams !== null) {
2355
2444
  const paramCount = fn.getParameters().length;
2356
2445
  if (paramCount > maxParams) {
2357
- violations.push(createViolation({
2358
- decisionId,
2359
- constraintId: constraint.id,
2360
- type: constraint.type,
2361
- severity: constraint.severity,
2362
- message: `Function ${fnName} has ${paramCount} parameters which exceeds maximum ${maxParams}`,
2363
- file: filePath,
2364
- line: fn.getStartLineNumber(),
2365
- suggestion: "Consider grouping parameters into an options object"
2366
- }));
2446
+ violations.push(
2447
+ createViolation({
2448
+ decisionId,
2449
+ constraintId: constraint.id,
2450
+ type: constraint.type,
2451
+ severity: constraint.severity,
2452
+ message: `Function ${fnName} has ${paramCount} parameters which exceeds maximum ${maxParams}`,
2453
+ file: filePath,
2454
+ line: fn.getStartLineNumber(),
2455
+ suggestion: "Consider grouping parameters into an options object"
2456
+ })
2457
+ );
2367
2458
  }
2368
2459
  }
2369
2460
  if (maxNesting !== null) {
2370
2461
  const depth = maxNestingDepth(fn);
2371
2462
  if (depth > maxNesting) {
2372
- violations.push(createViolation({
2373
- decisionId,
2374
- constraintId: constraint.id,
2375
- type: constraint.type,
2376
- severity: constraint.severity,
2377
- message: `Function ${fnName} has nesting depth ${depth} which exceeds maximum ${maxNesting}`,
2378
- file: filePath,
2379
- line: fn.getStartLineNumber(),
2380
- suggestion: "Reduce nesting by using early returns or extracting functions"
2381
- }));
2463
+ violations.push(
2464
+ createViolation({
2465
+ decisionId,
2466
+ constraintId: constraint.id,
2467
+ type: constraint.type,
2468
+ severity: constraint.severity,
2469
+ message: `Function ${fnName} has nesting depth ${depth} which exceeds maximum ${maxNesting}`,
2470
+ file: filePath,
2471
+ line: fn.getStartLineNumber(),
2472
+ suggestion: "Reduce nesting by using early returns or extracting functions"
2473
+ })
2474
+ );
2382
2475
  }
2383
2476
  }
2384
2477
  }
@@ -2414,48 +2507,54 @@ var SecurityVerifier = class {
2414
2507
  if (!init || !isStringLiteralLike(init)) continue;
2415
2508
  const value = init.getText().slice(1, -1);
2416
2509
  if (value.length === 0) continue;
2417
- violations.push(createViolation({
2418
- decisionId,
2419
- constraintId: constraint.id,
2420
- type: constraint.type,
2421
- severity: constraint.severity,
2422
- message: `Possible hardcoded secret in variable "${name}"`,
2423
- file: filePath,
2424
- line: vd.getStartLineNumber(),
2425
- suggestion: "Move secrets to environment variables or a secret manager"
2426
- }));
2510
+ violations.push(
2511
+ createViolation({
2512
+ decisionId,
2513
+ constraintId: constraint.id,
2514
+ type: constraint.type,
2515
+ severity: constraint.severity,
2516
+ message: `Possible hardcoded secret in variable "${name}"`,
2517
+ file: filePath,
2518
+ line: vd.getStartLineNumber(),
2519
+ suggestion: "Move secrets to environment variables or a secret manager"
2520
+ })
2521
+ );
2427
2522
  }
2428
2523
  for (const pa of sourceFile.getDescendantsOfKind(SyntaxKind3.PropertyAssignment)) {
2429
2524
  const propName = pa.getNameNode().getText();
2430
2525
  if (!SECRET_NAME_RE.test(propName)) continue;
2431
2526
  const init = pa.getInitializer();
2432
2527
  if (!init || !isStringLiteralLike(init)) continue;
2433
- violations.push(createViolation({
2434
- decisionId,
2435
- constraintId: constraint.id,
2436
- type: constraint.type,
2437
- severity: constraint.severity,
2438
- message: `Possible hardcoded secret in object property ${propName}`,
2439
- file: filePath,
2440
- line: pa.getStartLineNumber(),
2441
- suggestion: "Move secrets to environment variables or a secret manager"
2442
- }));
2528
+ violations.push(
2529
+ createViolation({
2530
+ decisionId,
2531
+ constraintId: constraint.id,
2532
+ type: constraint.type,
2533
+ severity: constraint.severity,
2534
+ message: `Possible hardcoded secret in object property ${propName}`,
2535
+ file: filePath,
2536
+ line: pa.getStartLineNumber(),
2537
+ suggestion: "Move secrets to environment variables or a secret manager"
2538
+ })
2539
+ );
2443
2540
  }
2444
2541
  }
2445
2542
  if (checkEval) {
2446
2543
  for (const call of sourceFile.getDescendantsOfKind(SyntaxKind3.CallExpression)) {
2447
2544
  const exprText = call.getExpression().getText();
2448
2545
  if (exprText === "eval" || exprText === "Function") {
2449
- violations.push(createViolation({
2450
- decisionId,
2451
- constraintId: constraint.id,
2452
- type: constraint.type,
2453
- severity: constraint.severity,
2454
- message: `Unsafe dynamic code execution via ${exprText}()`,
2455
- file: filePath,
2456
- line: call.getStartLineNumber(),
2457
- suggestion: "Avoid eval/Function; use safer alternatives"
2458
- }));
2546
+ violations.push(
2547
+ createViolation({
2548
+ decisionId,
2549
+ constraintId: constraint.id,
2550
+ type: constraint.type,
2551
+ severity: constraint.severity,
2552
+ message: `Unsafe dynamic code execution via ${exprText}()`,
2553
+ file: filePath,
2554
+ line: call.getStartLineNumber(),
2555
+ suggestion: "Avoid eval/Function; use safer alternatives"
2556
+ })
2557
+ );
2459
2558
  }
2460
2559
  }
2461
2560
  }
@@ -2465,29 +2564,33 @@ var SecurityVerifier = class {
2465
2564
  const propertyAccess = left.asKind(SyntaxKind3.PropertyAccessExpression);
2466
2565
  if (!propertyAccess) continue;
2467
2566
  if (propertyAccess.getName() === "innerHTML") {
2468
- violations.push(createViolation({
2567
+ violations.push(
2568
+ createViolation({
2569
+ decisionId,
2570
+ constraintId: constraint.id,
2571
+ type: constraint.type,
2572
+ severity: constraint.severity,
2573
+ message: "Potential XSS: assignment to innerHTML",
2574
+ file: filePath,
2575
+ line: bin.getStartLineNumber(),
2576
+ suggestion: "Prefer textContent or a safe templating/escaping strategy"
2577
+ })
2578
+ );
2579
+ }
2580
+ }
2581
+ if (sourceFile.getFullText().includes("dangerouslySetInnerHTML")) {
2582
+ violations.push(
2583
+ createViolation({
2469
2584
  decisionId,
2470
2585
  constraintId: constraint.id,
2471
2586
  type: constraint.type,
2472
2587
  severity: constraint.severity,
2473
- message: "Potential XSS: assignment to innerHTML",
2588
+ message: "Potential XSS: usage of dangerouslySetInnerHTML",
2474
2589
  file: filePath,
2475
- line: bin.getStartLineNumber(),
2476
- suggestion: "Prefer textContent or a safe templating/escaping strategy"
2477
- }));
2478
- }
2479
- }
2480
- if (sourceFile.getFullText().includes("dangerouslySetInnerHTML")) {
2481
- violations.push(createViolation({
2482
- decisionId,
2483
- constraintId: constraint.id,
2484
- type: constraint.type,
2485
- severity: constraint.severity,
2486
- message: "Potential XSS: usage of dangerouslySetInnerHTML",
2487
- file: filePath,
2488
- line: 1,
2489
- suggestion: "Avoid dangerouslySetInnerHTML or ensure content is sanitized"
2490
- }));
2590
+ line: 1,
2591
+ suggestion: "Avoid dangerouslySetInnerHTML or ensure content is sanitized"
2592
+ })
2593
+ );
2491
2594
  }
2492
2595
  }
2493
2596
  if (checkSql) {
@@ -2506,31 +2609,35 @@ var SecurityVerifier = class {
2506
2609
  if (!text.includes("select") && !text.includes("insert") && !text.includes("update") && !text.includes("delete")) {
2507
2610
  continue;
2508
2611
  }
2509
- violations.push(createViolation({
2510
- decisionId,
2511
- constraintId: constraint.id,
2512
- type: constraint.type,
2513
- severity: constraint.severity,
2514
- message: "Potential SQL injection: dynamically constructed SQL query",
2515
- file: filePath,
2516
- line: call.getStartLineNumber(),
2517
- suggestion: "Use parameterized queries / prepared statements"
2518
- }));
2612
+ violations.push(
2613
+ createViolation({
2614
+ decisionId,
2615
+ constraintId: constraint.id,
2616
+ type: constraint.type,
2617
+ severity: constraint.severity,
2618
+ message: "Potential SQL injection: dynamically constructed SQL query",
2619
+ file: filePath,
2620
+ line: call.getStartLineNumber(),
2621
+ suggestion: "Use parameterized queries / prepared statements"
2622
+ })
2623
+ );
2519
2624
  }
2520
2625
  }
2521
2626
  if (checkProto) {
2522
2627
  const text = sourceFile.getFullText();
2523
2628
  if (text.includes("__proto__") || text.includes("constructor.prototype")) {
2524
- violations.push(createViolation({
2525
- decisionId,
2526
- constraintId: constraint.id,
2527
- type: constraint.type,
2528
- severity: constraint.severity,
2529
- message: "Potential prototype pollution pattern detected",
2530
- file: filePath,
2531
- line: 1,
2532
- suggestion: "Avoid writing to __proto__/prototype; validate object keys"
2533
- }));
2629
+ violations.push(
2630
+ createViolation({
2631
+ decisionId,
2632
+ constraintId: constraint.id,
2633
+ type: constraint.type,
2634
+ severity: constraint.severity,
2635
+ message: "Potential prototype pollution pattern detected",
2636
+ file: filePath,
2637
+ line: 1,
2638
+ suggestion: "Avoid writing to __proto__/prototype; validate object keys"
2639
+ })
2640
+ );
2534
2641
  }
2535
2642
  }
2536
2643
  return violations;
@@ -2570,16 +2677,18 @@ var ApiVerifier = class {
2570
2677
  const pathValue = stringLiteral.getLiteralValue();
2571
2678
  if (typeof pathValue !== "string") continue;
2572
2679
  if (!isKebabPath(pathValue)) {
2573
- violations.push(createViolation({
2574
- decisionId,
2575
- constraintId: constraint.id,
2576
- type: constraint.type,
2577
- severity: constraint.severity,
2578
- message: `Endpoint path "${pathValue}" is not kebab-case`,
2579
- file: filePath,
2580
- line: call.getStartLineNumber(),
2581
- suggestion: "Use lowercase and hyphens in static path segments (e.g., /user-settings)"
2582
- }));
2680
+ violations.push(
2681
+ createViolation({
2682
+ decisionId,
2683
+ constraintId: constraint.id,
2684
+ type: constraint.type,
2685
+ severity: constraint.severity,
2686
+ message: `Endpoint path "${pathValue}" is not kebab-case`,
2687
+ file: filePath,
2688
+ line: call.getStartLineNumber(),
2689
+ suggestion: "Use lowercase and hyphens in static path segments (e.g., /user-settings)"
2690
+ })
2691
+ );
2583
2692
  }
2584
2693
  }
2585
2694
  return violations;
@@ -2591,10 +2700,36 @@ import { existsSync } from "fs";
2591
2700
  import { join as join3 } from "path";
2592
2701
  import { pathToFileURL } from "url";
2593
2702
  import fg2 from "fast-glob";
2703
+
2704
+ // src/utils/logger.ts
2705
+ import pino from "pino";
2706
+ var defaultOptions = {
2707
+ level: process.env.SPECBRIDGE_LOG_LEVEL || "info",
2708
+ timestamp: pino.stdTimeFunctions.isoTime,
2709
+ base: {
2710
+ service: "specbridge"
2711
+ }
2712
+ };
2713
+ var destination = pino.destination({
2714
+ fd: 2,
2715
+ // stderr
2716
+ sync: false
2717
+ });
2718
+ var rootLogger = pino(defaultOptions, destination);
2719
+ function getLogger(bindings) {
2720
+ if (!bindings) {
2721
+ return rootLogger;
2722
+ }
2723
+ return rootLogger.child(bindings);
2724
+ }
2725
+ var logger = getLogger();
2726
+
2727
+ // src/verification/plugins/loader.ts
2594
2728
  var PluginLoader = class {
2595
2729
  plugins = /* @__PURE__ */ new Map();
2596
2730
  loaded = false;
2597
2731
  loadErrors = [];
2732
+ logger = getLogger({ module: "verification.plugins.loader" });
2598
2733
  /**
2599
2734
  * Load all plugins from the specified base path
2600
2735
  *
@@ -2617,15 +2752,15 @@ var PluginLoader = class {
2617
2752
  } catch (error) {
2618
2753
  const message = error instanceof Error ? error.message : String(error);
2619
2754
  this.loadErrors.push({ file, error: message });
2620
- console.warn(`Failed to load plugin from ${file}: ${message}`);
2755
+ this.logger.warn({ file, error: message }, "Failed to load plugin");
2621
2756
  }
2622
2757
  }
2623
2758
  this.loaded = true;
2624
2759
  if (this.plugins.size > 0) {
2625
- console.error(`Loaded ${this.plugins.size} custom verifier(s)`);
2760
+ this.logger.info({ count: this.plugins.size }, "Loaded custom verifier plugins");
2626
2761
  }
2627
2762
  if (this.loadErrors.length > 0) {
2628
- console.warn(`Failed to load ${this.loadErrors.length} plugin(s)`);
2763
+ this.logger.warn({ count: this.loadErrors.length }, "Plugin load failures");
2629
2764
  }
2630
2765
  }
2631
2766
  /**
@@ -2734,7 +2869,10 @@ var PluginLoader = class {
2734
2869
  return { success: false, error: `Plugin ${id} requires params but none were provided` };
2735
2870
  }
2736
2871
  if (typeof plugin.paramsSchema !== "object" || !plugin.paramsSchema || !("parse" in plugin.paramsSchema)) {
2737
- return { success: false, error: `Plugin ${id} has invalid paramsSchema (must be a Zod schema)` };
2872
+ return {
2873
+ success: false,
2874
+ error: `Plugin ${id} has invalid paramsSchema (must be a Zod schema)`
2875
+ };
2738
2876
  }
2739
2877
  const schema = plugin.paramsSchema;
2740
2878
  if (schema.safeParse) {
@@ -2801,8 +2939,9 @@ var builtinVerifiers = {
2801
2939
  };
2802
2940
  var verifierInstances = /* @__PURE__ */ new Map();
2803
2941
  function getVerifier(id) {
2804
- if (verifierInstances.has(id)) {
2805
- return verifierInstances.get(id);
2942
+ const pooled = verifierInstances.get(id);
2943
+ if (pooled) {
2944
+ return pooled;
2806
2945
  }
2807
2946
  const pluginLoader2 = getPluginLoader();
2808
2947
  const customVerifier = pluginLoader2.getVerifier(id);
@@ -3007,6 +3146,7 @@ var VerificationEngine = class {
3007
3146
  astCache;
3008
3147
  resultsCache;
3009
3148
  pluginsLoaded = false;
3149
+ logger = getLogger({ module: "verification.engine" });
3010
3150
  constructor(registry) {
3011
3151
  this.registry = registry || createRegistry();
3012
3152
  this.project = new Project2({
@@ -3180,12 +3320,14 @@ var VerificationEngine = class {
3180
3320
  );
3181
3321
  if (!verifier) {
3182
3322
  const requestedVerifier = constraint.check?.verifier || constraint.verifier || "auto-detected";
3183
- console.warn(
3184
- chalk.yellow(
3185
- `Warning: No verifier found for ${decision.metadata.id}/${constraint.id}
3186
- Requested: ${requestedVerifier}
3187
- Available: ${getVerifierIds().join(", ")}`
3188
- )
3323
+ this.logger.warn(
3324
+ {
3325
+ decisionId: decision.metadata.id,
3326
+ constraintId: constraint.id,
3327
+ requestedVerifier,
3328
+ availableVerifiers: getVerifierIds()
3329
+ },
3330
+ "No verifier found for constraint"
3189
3331
  );
3190
3332
  warnings.push({
3191
3333
  type: "missing_verifier",
@@ -3232,7 +3374,10 @@ var VerificationEngine = class {
3232
3374
  }
3233
3375
  if (constraint.check?.verifier && constraint.check?.params) {
3234
3376
  const pluginLoader2 = getPluginLoader();
3235
- const validationResult = pluginLoader2.validateParams(constraint.check.verifier, constraint.check.params);
3377
+ const validationResult = pluginLoader2.validateParams(
3378
+ constraint.check.verifier,
3379
+ constraint.check.params
3380
+ );
3236
3381
  if (!validationResult.success) {
3237
3382
  warnings.push({
3238
3383
  type: "invalid_params",
@@ -3292,17 +3437,17 @@ var VerificationEngine = class {
3292
3437
  } catch (error) {
3293
3438
  const errorMessage = error instanceof Error ? error.message : String(error);
3294
3439
  const errorStack = error instanceof Error ? error.stack : void 0;
3295
- console.error(
3296
- chalk.red(
3297
- `Error: Verifier '${verifier.id}' failed
3298
- File: ${filePath}
3299
- Decision: ${decision.metadata.id}/${constraint.id}
3300
- Error: ${errorMessage}`
3301
- )
3440
+ this.logger.error(
3441
+ {
3442
+ verifierId: verifier.id,
3443
+ filePath,
3444
+ decisionId: decision.metadata.id,
3445
+ constraintId: constraint.id,
3446
+ error: errorMessage,
3447
+ stack: errorStack
3448
+ },
3449
+ "Verifier execution failed"
3302
3450
  );
3303
- if (errorStack) {
3304
- console.error(chalk.dim(errorStack));
3305
- }
3306
3451
  errors.push({
3307
3452
  type: "verifier_exception",
3308
3453
  message: `Verifier '${verifier.id}' failed: ${errorMessage}`,
@@ -3367,7 +3512,11 @@ import { resolve } from "path";
3367
3512
  var execFileAsync = promisify(execFile);
3368
3513
  async function getChangedFiles(cwd) {
3369
3514
  try {
3370
- const { stdout: stdout2 } = await execFileAsync("git", ["diff", "--name-only", "--diff-filter=AM", "HEAD"], { cwd });
3515
+ const { stdout: stdout2 } = await execFileAsync(
3516
+ "git",
3517
+ ["diff", "--name-only", "--diff-filter=AM", "HEAD"],
3518
+ { cwd }
3519
+ );
3371
3520
  const rel = stdout2.trim().split("\n").map((s) => s.trim()).filter(Boolean);
3372
3521
  const abs = [];
3373
3522
  for (const file of rel) {
@@ -3438,8 +3587,14 @@ var AutofixEngine = class {
3438
3587
  const edits = [];
3439
3588
  for (const violation of fileViolations) {
3440
3589
  const fix = violation.autofix;
3590
+ if (!fix) {
3591
+ skippedViolations++;
3592
+ continue;
3593
+ }
3441
3594
  if (options.interactive) {
3442
- const ok = await confirmFix(`Apply fix: ${fix.description} (${filePath}:${violation.line ?? 1})?`);
3595
+ const ok = await confirmFix(
3596
+ `Apply fix: ${fix.description} (${filePath}:${violation.line ?? 1})?`
3597
+ );
3443
3598
  if (!ok) {
3444
3599
  skippedViolations++;
3445
3600
  continue;
@@ -3566,7 +3721,24 @@ var PropagationEngine = class {
3566
3721
  if (!this.graph) {
3567
3722
  await this.initialize(config, options);
3568
3723
  }
3569
- const affectedFilePaths = getAffectedFiles(this.graph, decisionId);
3724
+ const graph = this.graph;
3725
+ if (!graph) {
3726
+ return {
3727
+ decision: decisionId,
3728
+ change,
3729
+ affectedFiles: [],
3730
+ estimatedEffort: "low",
3731
+ migrationSteps: [
3732
+ {
3733
+ order: 1,
3734
+ description: "Run verification to confirm all violations resolved",
3735
+ files: [],
3736
+ automated: true
3737
+ }
3738
+ ]
3739
+ };
3740
+ }
3741
+ const affectedFilePaths = getAffectedFiles(graph, decisionId);
3570
3742
  const verificationEngine = createVerificationEngine(this.registry);
3571
3743
  const result = await verificationEngine.verify(config, {
3572
3744
  files: affectedFilePaths,
@@ -3599,10 +3771,7 @@ var PropagationEngine = class {
3599
3771
  } else {
3600
3772
  estimatedEffort = "high";
3601
3773
  }
3602
- const migrationSteps = this.generateMigrationSteps(
3603
- affectedFiles,
3604
- totalAutoFixable > 0
3605
- );
3774
+ const migrationSteps = this.generateMigrationSteps(affectedFiles, totalAutoFixable > 0);
3606
3775
  return {
3607
3776
  decision: decisionId,
3608
3777
  change,
@@ -3625,9 +3794,7 @@ var PropagationEngine = class {
3625
3794
  automated: true
3626
3795
  });
3627
3796
  }
3628
- const filesWithManualFixes = affectedFiles.filter(
3629
- (f) => f.violations > f.autoFixable
3630
- );
3797
+ const filesWithManualFixes = affectedFiles.filter((f) => f.violations > f.autoFixable);
3631
3798
  if (filesWithManualFixes.length > 0) {
3632
3799
  const highPriority = filesWithManualFixes.filter((f) => f.violations > 5);
3633
3800
  const mediumPriority = filesWithManualFixes.filter(
@@ -3707,10 +3874,7 @@ async function generateReport(config, options = {}) {
3707
3874
  medium: decisionViolations.filter((v) => v.severity === "medium").length,
3708
3875
  low: decisionViolations.filter((v) => v.severity === "low").length
3709
3876
  };
3710
- weightedScore = decisionViolations.reduce(
3711
- (score, v) => score + weights[v.severity],
3712
- 0
3713
- );
3877
+ weightedScore = decisionViolations.reduce((score, v) => score + weights[v.severity], 0);
3714
3878
  compliance = Math.max(0, 100 - weightedScore);
3715
3879
  if (decisionViolations.length > 0 && constraintCount > 0) {
3716
3880
  const violationRate = decisionViolations.length / constraintCount;
@@ -3830,7 +3994,9 @@ var Reporter = class {
3830
3994
  lines.push("Summary:");
3831
3995
  lines.push(` Decisions Checked: ${result.summary.decisionsChecked || 0}`);
3832
3996
  lines.push(` Files Checked: ${result.summary.filesChecked || 0}`);
3833
- lines.push(` Total Violations: ${result.summary.totalViolations || result.violations?.length || 0}`);
3997
+ lines.push(
3998
+ ` Total Violations: ${result.summary.totalViolations || result.violations?.length || 0}`
3999
+ );
3834
4000
  lines.push(` Critical: ${result.summary.critical || 0}`);
3835
4001
  lines.push(` High: ${result.summary.high || 0}`);
3836
4002
  lines.push(` Medium: ${result.summary.medium || 0}`);
@@ -3844,7 +4010,9 @@ var Reporter = class {
3844
4010
  lines.push("-".repeat(50));
3845
4011
  result.violations.forEach((v) => {
3846
4012
  const severity = v.severity.toLowerCase();
3847
- lines.push(` [${v.severity.toUpperCase()}] ${v.decisionId} - ${v.constraintId} (${severity})`);
4013
+ lines.push(
4014
+ ` [${v.severity.toUpperCase()}] ${v.decisionId} - ${v.constraintId} (${severity})`
4015
+ );
3848
4016
  lines.push(` ${v.message}`);
3849
4017
  const file = v.location?.file || v.file;
3850
4018
  const line = v.location?.line || v.line || 0;
@@ -3865,7 +4033,9 @@ var Reporter = class {
3865
4033
  lines.push("");
3866
4034
  if (result.summary) {
3867
4035
  lines.push("Summary:");
3868
- lines.push(` Total Violations: ${result.summary.totalViolations || result.violations?.length || 0}`);
4036
+ lines.push(
4037
+ ` Total Violations: ${result.summary.totalViolations || result.violations?.length || 0}`
4038
+ );
3869
4039
  lines.push("");
3870
4040
  }
3871
4041
  if (result.violations && result.violations.length > 0) {
@@ -3919,7 +4089,9 @@ var Reporter = class {
3919
4089
  lines.push("### Summary\n");
3920
4090
  lines.push(`- **Decisions Checked:** ${result.summary.decisionsChecked || 0}`);
3921
4091
  lines.push(`- **Files Checked:** ${result.summary.filesChecked || 0}`);
3922
- lines.push(`- **Total Violations:** ${result.summary.totalViolations || result.violations?.length || 0}`);
4092
+ lines.push(
4093
+ `- **Total Violations:** ${result.summary.totalViolations || result.violations?.length || 0}`
4094
+ );
3923
4095
  lines.push(`- **Critical:** ${result.summary.critical || 0}`);
3924
4096
  lines.push(`- **High:** ${result.summary.high || 0}`);
3925
4097
  lines.push(`- **Medium:** ${result.summary.medium || 0}`);
@@ -3944,54 +4116,58 @@ var Reporter = class {
3944
4116
  };
3945
4117
 
3946
4118
  // src/reporting/formats/console.ts
3947
- import chalk2 from "chalk";
4119
+ import chalk from "chalk";
3948
4120
  import { table } from "table";
3949
4121
  function formatConsoleReport(report) {
3950
4122
  const lines = [];
3951
4123
  lines.push("");
3952
- lines.push(chalk2.bold.blue("SpecBridge Compliance Report"));
3953
- lines.push(chalk2.dim(`Generated: ${new Date(report.timestamp).toLocaleString()}`));
3954
- lines.push(chalk2.dim(`Project: ${report.project}`));
4124
+ lines.push(chalk.bold.blue("SpecBridge Compliance Report"));
4125
+ lines.push(chalk.dim(`Generated: ${new Date(report.timestamp).toLocaleString()}`));
4126
+ lines.push(chalk.dim(`Project: ${report.project}`));
3955
4127
  lines.push("");
3956
4128
  const complianceColor = getComplianceColor(report.summary.compliance);
3957
- lines.push(chalk2.bold("Overall Compliance"));
3958
- lines.push(` ${complianceColor(formatComplianceBar(report.summary.compliance))} ${complianceColor(`${report.summary.compliance}%`)}`);
4129
+ lines.push(chalk.bold("Overall Compliance"));
4130
+ lines.push(
4131
+ ` ${complianceColor(formatComplianceBar(report.summary.compliance))} ${complianceColor(`${report.summary.compliance}%`)}`
4132
+ );
3959
4133
  lines.push("");
3960
- lines.push(chalk2.bold("Summary"));
3961
- lines.push(` Decisions: ${report.summary.activeDecisions} active / ${report.summary.totalDecisions} total`);
4134
+ lines.push(chalk.bold("Summary"));
4135
+ lines.push(
4136
+ ` Decisions: ${report.summary.activeDecisions} active / ${report.summary.totalDecisions} total`
4137
+ );
3962
4138
  lines.push(` Constraints: ${report.summary.totalConstraints}`);
3963
4139
  lines.push("");
3964
- lines.push(chalk2.bold("Violations"));
4140
+ lines.push(chalk.bold("Violations"));
3965
4141
  const { violations } = report.summary;
3966
4142
  const violationParts = [];
3967
4143
  if (violations.critical > 0) {
3968
- violationParts.push(chalk2.red(`${violations.critical} critical`));
4144
+ violationParts.push(chalk.red(`${violations.critical} critical`));
3969
4145
  }
3970
4146
  if (violations.high > 0) {
3971
- violationParts.push(chalk2.yellow(`${violations.high} high`));
4147
+ violationParts.push(chalk.yellow(`${violations.high} high`));
3972
4148
  }
3973
4149
  if (violations.medium > 0) {
3974
- violationParts.push(chalk2.cyan(`${violations.medium} medium`));
4150
+ violationParts.push(chalk.cyan(`${violations.medium} medium`));
3975
4151
  }
3976
4152
  if (violations.low > 0) {
3977
- violationParts.push(chalk2.dim(`${violations.low} low`));
4153
+ violationParts.push(chalk.dim(`${violations.low} low`));
3978
4154
  }
3979
4155
  if (violationParts.length > 0) {
3980
4156
  lines.push(` ${violationParts.join(" | ")}`);
3981
4157
  } else {
3982
- lines.push(chalk2.green(" No violations"));
4158
+ lines.push(chalk.green(" No violations"));
3983
4159
  }
3984
4160
  lines.push("");
3985
4161
  if (report.byDecision.length > 0) {
3986
- lines.push(chalk2.bold("By Decision"));
4162
+ lines.push(chalk.bold("By Decision"));
3987
4163
  lines.push("");
3988
4164
  const tableData = [
3989
4165
  [
3990
- chalk2.bold("Decision"),
3991
- chalk2.bold("Status"),
3992
- chalk2.bold("Constraints"),
3993
- chalk2.bold("Violations"),
3994
- chalk2.bold("Compliance")
4166
+ chalk.bold("Decision"),
4167
+ chalk.bold("Status"),
4168
+ chalk.bold("Constraints"),
4169
+ chalk.bold("Violations"),
4170
+ chalk.bold("Compliance")
3995
4171
  ]
3996
4172
  ];
3997
4173
  for (const dec of report.byDecision) {
@@ -4001,7 +4177,7 @@ function formatConsoleReport(report) {
4001
4177
  truncate(dec.title, 40),
4002
4178
  statusColor(dec.status),
4003
4179
  String(dec.constraints),
4004
- dec.violations > 0 ? chalk2.red(String(dec.violations)) : chalk2.green("0"),
4180
+ dec.violations > 0 ? chalk.red(String(dec.violations)) : chalk.green("0"),
4005
4181
  compColor(`${dec.compliance}%`)
4006
4182
  ]);
4007
4183
  }
@@ -4035,23 +4211,23 @@ function formatComplianceBar(compliance) {
4035
4211
  return "\u2588".repeat(filled) + "\u2591".repeat(empty);
4036
4212
  }
4037
4213
  function getComplianceColor(compliance) {
4038
- if (compliance >= 90) return chalk2.green;
4039
- if (compliance >= 70) return chalk2.yellow;
4040
- if (compliance >= 50) return chalk2.hex("#FFA500");
4041
- return chalk2.red;
4214
+ if (compliance >= 90) return chalk.green;
4215
+ if (compliance >= 70) return chalk.yellow;
4216
+ if (compliance >= 50) return chalk.hex("#FFA500");
4217
+ return chalk.red;
4042
4218
  }
4043
4219
  function getStatusColor(status) {
4044
4220
  switch (status) {
4045
4221
  case "active":
4046
- return chalk2.green;
4222
+ return chalk.green;
4047
4223
  case "draft":
4048
- return chalk2.yellow;
4224
+ return chalk.yellow;
4049
4225
  case "deprecated":
4050
- return chalk2.gray;
4226
+ return chalk.gray;
4051
4227
  case "superseded":
4052
- return chalk2.blue;
4228
+ return chalk.blue;
4053
4229
  default:
4054
- return chalk2.white;
4230
+ return chalk.white;
4055
4231
  }
4056
4232
  }
4057
4233
  function truncate(str, length) {
@@ -4075,7 +4251,9 @@ function formatMarkdownReport(report) {
4075
4251
  lines.push("");
4076
4252
  lines.push("## Summary");
4077
4253
  lines.push("");
4078
- lines.push(`- **Active Decisions:** ${report.summary.activeDecisions} / ${report.summary.totalDecisions}`);
4254
+ lines.push(
4255
+ `- **Active Decisions:** ${report.summary.activeDecisions} / ${report.summary.totalDecisions}`
4256
+ );
4079
4257
  lines.push(`- **Total Constraints:** ${report.summary.totalConstraints}`);
4080
4258
  lines.push("");
4081
4259
  lines.push("### Violations");
@@ -4125,6 +4303,7 @@ function formatProgressBar(percentage) {
4125
4303
  import { join as join4 } from "path";
4126
4304
  var ReportStorage = class {
4127
4305
  storageDir;
4306
+ logger = getLogger({ module: "reporting.storage" });
4128
4307
  constructor(basePath) {
4129
4308
  this.storageDir = join4(getSpecBridgeDir(basePath), "reports", "history");
4130
4309
  }
@@ -4183,7 +4362,7 @@ var ReportStorage = class {
4183
4362
  const timestamp = file.replace("report-", "").replace(".json", "");
4184
4363
  return { timestamp, report };
4185
4364
  } catch (error) {
4186
- console.warn(`Warning: Failed to load report ${file}:`, error);
4365
+ this.logger.warn({ file, error }, "Failed to load report file");
4187
4366
  return null;
4188
4367
  }
4189
4368
  });
@@ -4227,7 +4406,7 @@ var ReportStorage = class {
4227
4406
  const fs = await import("fs/promises");
4228
4407
  await fs.unlink(filepath);
4229
4408
  } catch (error) {
4230
- console.warn(`Warning: Failed to delete old report ${file}:`, error);
4409
+ this.logger.warn({ file, error }, "Failed to delete old report file");
4231
4410
  }
4232
4411
  }
4233
4412
  return filesToDelete.length;
@@ -4238,9 +4417,7 @@ var ReportStorage = class {
4238
4417
  async function detectDrift(current, previous) {
4239
4418
  const byDecision = [];
4240
4419
  for (const currDecision of current.byDecision) {
4241
- const prevDecision = previous.byDecision.find(
4242
- (d) => d.decisionId === currDecision.decisionId
4243
- );
4420
+ const prevDecision = previous.byDecision.find((d) => d.decisionId === currDecision.decisionId);
4244
4421
  if (!prevDecision) {
4245
4422
  byDecision.push({
4246
4423
  decisionId: currDecision.decisionId,
@@ -5021,9 +5198,7 @@ var AnalyticsEngine = class {
5021
5198
  overallTrend = "down";
5022
5199
  }
5023
5200
  }
5024
- const sortedByCompliance = [...latest.byDecision].sort(
5025
- (a, b) => b.compliance - a.compliance
5026
- );
5201
+ const sortedByCompliance = [...latest.byDecision].sort((a, b) => b.compliance - a.compliance);
5027
5202
  const topDecisions = sortedByCompliance.slice(0, 5).map((d) => ({
5028
5203
  decisionId: d.decisionId,
5029
5204
  title: d.title,
@@ -5064,6 +5239,7 @@ var DashboardServer = class {
5064
5239
  CACHE_TTL = 6e4;
5065
5240
  // 1 minute
5066
5241
  refreshInterval = null;
5242
+ logger = getLogger({ module: "dashboard.server" });
5067
5243
  constructor(options) {
5068
5244
  this.cwd = options.cwd;
5069
5245
  this.config = options.config;
@@ -5079,10 +5255,11 @@ var DashboardServer = class {
5079
5255
  async start() {
5080
5256
  await this.registry.load();
5081
5257
  await this.refreshCache();
5082
- this.refreshInterval = setInterval(
5083
- () => this.refreshCache().catch(console.error),
5084
- this.CACHE_TTL
5085
- );
5258
+ this.refreshInterval = setInterval(() => {
5259
+ void this.refreshCache().catch((error) => {
5260
+ this.logger.error({ error }, "Background cache refresh failed");
5261
+ });
5262
+ }, this.CACHE_TTL);
5086
5263
  }
5087
5264
  /**
5088
5265
  * Stop the server and clear intervals
@@ -5103,7 +5280,7 @@ var DashboardServer = class {
5103
5280
  this.cacheTimestamp = Date.now();
5104
5281
  await this.reportStorage.save(report);
5105
5282
  } catch (error) {
5106
- console.error("Cache refresh failed:", error);
5283
+ this.logger.error({ error }, "Cache refresh failed");
5107
5284
  if (!this.cachedReport) {
5108
5285
  try {
5109
5286
  const stored = await this.reportStorage.loadLatest();
@@ -5111,7 +5288,7 @@ var DashboardServer = class {
5111
5288
  this.cachedReport = stored.report;
5112
5289
  }
5113
5290
  } catch (fallbackError) {
5114
- console.error("Failed to load fallback report:", fallbackError);
5291
+ this.logger.error({ error: fallbackError }, "Failed to load fallback report");
5115
5292
  }
5116
5293
  }
5117
5294
  }
@@ -5354,11 +5531,13 @@ var DashboardServer = class {
5354
5531
  */
5355
5532
  setupStaticFiles() {
5356
5533
  const publicDir = join5(__dirname, "public");
5357
- this.app.use(express.static(publicDir, {
5358
- maxAge: "1h",
5359
- // Cache static assets
5360
- etag: true
5361
- }));
5534
+ this.app.use(
5535
+ express.static(publicDir, {
5536
+ maxAge: "1h",
5537
+ // Cache static assets
5538
+ etag: true
5539
+ })
5540
+ );
5362
5541
  this.app.get("/{*path}", (_req, res) => {
5363
5542
  res.sendFile(join5(publicDir, "index.html"));
5364
5543
  });
@@ -5369,12 +5548,19 @@ function createDashboardServer(options) {
5369
5548
  }
5370
5549
 
5371
5550
  // src/lsp/server.ts
5372
- import { createConnection, ProposedFeatures, TextDocuments, TextDocumentSyncKind, DiagnosticSeverity, CodeActionKind } from "vscode-languageserver/node.js";
5551
+ import {
5552
+ createConnection,
5553
+ ProposedFeatures,
5554
+ TextDocuments,
5555
+ TextDocumentSyncKind,
5556
+ DiagnosticSeverity,
5557
+ CodeActionKind
5558
+ } from "vscode-languageserver/node.js";
5373
5559
  import { TextDocument } from "vscode-languageserver-textdocument";
5374
5560
  import { fileURLToPath as fileURLToPath2 } from "url";
5375
5561
  import path3 from "path";
5376
5562
  import { Project as Project3 } from "ts-morph";
5377
- import chalk3 from "chalk";
5563
+ import chalk2 from "chalk";
5378
5564
  function severityToDiagnostic(severity) {
5379
5565
  switch (severity) {
5380
5566
  case "critical":
@@ -5485,7 +5671,7 @@ var SpecBridgeLspServer = class {
5485
5671
  if (!await pathExists(getSpecBridgeDir(this.cwd))) {
5486
5672
  const err = new NotInitializedError();
5487
5673
  this.initError = err.message;
5488
- if (this.options.verbose) this.connection.console.error(chalk3.red(this.initError));
5674
+ if (this.options.verbose) this.connection.console.error(chalk2.red(this.initError));
5489
5675
  return;
5490
5676
  }
5491
5677
  try {
@@ -5494,7 +5680,8 @@ var SpecBridgeLspServer = class {
5494
5680
  await getPluginLoader().loadPlugins(this.cwd);
5495
5681
  } catch (error) {
5496
5682
  const msg = error instanceof Error ? error.message : String(error);
5497
- if (this.options.verbose) this.connection.console.error(chalk3.red(`Plugin load failed: ${msg}`));
5683
+ if (this.options.verbose)
5684
+ this.connection.console.error(chalk2.red(`Plugin load failed: ${msg}`));
5498
5685
  }
5499
5686
  this.registry = createRegistry({ basePath: this.cwd });
5500
5687
  await this.registry.load();
@@ -5507,11 +5694,13 @@ var SpecBridgeLspServer = class {
5507
5694
  }
5508
5695
  }
5509
5696
  if (this.options.verbose) {
5510
- this.connection.console.log(chalk3.dim(`Loaded ${this.decisions.length} active decision(s)`));
5697
+ this.connection.console.log(
5698
+ chalk2.dim(`Loaded ${this.decisions.length} active decision(s)`)
5699
+ );
5511
5700
  }
5512
5701
  } catch (error) {
5513
5702
  this.initError = error instanceof Error ? error.message : String(error);
5514
- if (this.options.verbose) this.connection.console.error(chalk3.red(this.initError));
5703
+ if (this.options.verbose) this.connection.console.error(chalk2.red(this.initError));
5515
5704
  }
5516
5705
  }
5517
5706
  async verifyTextDocument(doc) {
@@ -5525,7 +5714,11 @@ var SpecBridgeLspServer = class {
5525
5714
  for (const decision of this.decisions) {
5526
5715
  for (const constraint of decision.constraints) {
5527
5716
  if (!shouldApplyConstraintToFile({ filePath, constraint, cwd: this.cwd })) continue;
5528
- const verifier = selectVerifierForConstraint(constraint.rule, constraint.verifier, constraint.check);
5717
+ const verifier = selectVerifierForConstraint(
5718
+ constraint.rule,
5719
+ constraint.verifier,
5720
+ constraint.check
5721
+ );
5529
5722
  if (!verifier) continue;
5530
5723
  const ctx = {
5531
5724
  filePath,
@@ -5579,6 +5772,9 @@ async function startLspServer(options) {
5579
5772
  // src/integrations/github.ts
5580
5773
  function toMdTable(rows) {
5581
5774
  const header = rows[0];
5775
+ if (!header) {
5776
+ return "";
5777
+ }
5582
5778
  const body = rows.slice(1);
5583
5779
  const sep = header.map(() => "---");
5584
5780
  const lines = [
@@ -5592,9 +5788,7 @@ function formatViolationsForGitHub(violations, limit = 50) {
5592
5788
  if (violations.length === 0) {
5593
5789
  return "## SpecBridge\n\n\u2705 No violations found.";
5594
5790
  }
5595
- const rows = [
5596
- ["Severity", "Type", "File", "Decision/Constraint", "Message"]
5597
- ];
5791
+ const rows = [["Severity", "Type", "File", "Decision/Constraint", "Message"]];
5598
5792
  for (const v of violations.slice(0, limit)) {
5599
5793
  const loc = v.line ? `:${v.line}${v.column ? `:${v.column}` : ""}` : "";
5600
5794
  rows.push([
@@ -5616,19 +5810,24 @@ ${toMdTable(rows)}${extra}`;
5616
5810
  }
5617
5811
  async function postPrComment(violations, options) {
5618
5812
  const body = formatViolationsForGitHub(violations);
5619
- const res = await fetch(`https://api.github.com/repos/${options.repo}/issues/${options.pr}/comments`, {
5620
- method: "POST",
5621
- headers: {
5622
- Authorization: `Bearer ${options.token}`,
5623
- Accept: "application/vnd.github+json",
5624
- "Content-Type": "application/json",
5625
- "User-Agent": "specbridge"
5626
- },
5627
- body: JSON.stringify({ body })
5628
- });
5813
+ const res = await fetch(
5814
+ `https://api.github.com/repos/${options.repo}/issues/${options.pr}/comments`,
5815
+ {
5816
+ method: "POST",
5817
+ headers: {
5818
+ Authorization: `Bearer ${options.token}`,
5819
+ Accept: "application/vnd.github+json",
5820
+ "Content-Type": "application/json",
5821
+ "User-Agent": "specbridge"
5822
+ },
5823
+ body: JSON.stringify({ body })
5824
+ }
5825
+ );
5629
5826
  if (!res.ok) {
5630
5827
  const text = await res.text().catch(() => "");
5631
- throw new Error(`GitHub comment failed: ${res.status} ${res.statusText}${text ? ` - ${text}` : ""}`);
5828
+ throw new Error(
5829
+ `GitHub comment failed: ${res.status} ${res.statusText}${text ? ` - ${text}` : ""}`
5830
+ );
5632
5831
  }
5633
5832
  }
5634
5833
  export {
@@ -5722,6 +5921,7 @@ export {
5722
5921
  getConfigPath,
5723
5922
  getDecisionsDir,
5724
5923
  getInferredDir,
5924
+ getLogger,
5725
5925
  getReportsDir,
5726
5926
  getSpecBridgeDir,
5727
5927
  getTransitiveDependencies,
@@ -5734,6 +5934,7 @@ export {
5734
5934
  loadConfig,
5735
5935
  loadDecisionFile,
5736
5936
  loadDecisionsFromDir,
5937
+ logger,
5737
5938
  matchesAnyPattern,
5738
5939
  matchesPattern,
5739
5940
  mergeWithDefaults,