@ipation/specbridge 2.4.0 → 2.4.3

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;
@@ -1643,17 +1686,19 @@ var NamingVerifier = class {
1643
1686
  for (const classDecl of sourceFile.getClasses()) {
1644
1687
  const name = classDecl.getName();
1645
1688
  if (name && !pattern.regex.test(name)) {
1646
- violations.push(createViolation({
1647
- decisionId,
1648
- constraintId: constraint.id,
1649
- type: constraint.type,
1650
- severity: constraint.severity,
1651
- message: `Class "${name}" does not follow ${pattern.description} naming convention`,
1652
- file: filePath,
1653
- line: classDecl.getStartLineNumber(),
1654
- column: classDecl.getStart() - classDecl.getStartLinePos(),
1655
- suggestion: `Rename to follow ${pattern.description}`
1656
- }));
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
+ );
1657
1702
  }
1658
1703
  }
1659
1704
  }
@@ -1661,16 +1706,18 @@ var NamingVerifier = class {
1661
1706
  for (const funcDecl of sourceFile.getFunctions()) {
1662
1707
  const name = funcDecl.getName();
1663
1708
  if (name && !pattern.regex.test(name)) {
1664
- violations.push(createViolation({
1665
- decisionId,
1666
- constraintId: constraint.id,
1667
- type: constraint.type,
1668
- severity: constraint.severity,
1669
- message: `Function "${name}" does not follow ${pattern.description} naming convention`,
1670
- file: filePath,
1671
- line: funcDecl.getStartLineNumber(),
1672
- suggestion: `Rename to follow ${pattern.description}`
1673
- }));
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
+ );
1674
1721
  }
1675
1722
  }
1676
1723
  }
@@ -1678,16 +1725,18 @@ var NamingVerifier = class {
1678
1725
  for (const interfaceDecl of sourceFile.getInterfaces()) {
1679
1726
  const name = interfaceDecl.getName();
1680
1727
  if (!pattern.regex.test(name)) {
1681
- violations.push(createViolation({
1682
- decisionId,
1683
- constraintId: constraint.id,
1684
- type: constraint.type,
1685
- severity: constraint.severity,
1686
- message: `Interface "${name}" does not follow ${pattern.description} naming convention`,
1687
- file: filePath,
1688
- line: interfaceDecl.getStartLineNumber(),
1689
- suggestion: `Rename to follow ${pattern.description}`
1690
- }));
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
+ );
1691
1740
  }
1692
1741
  }
1693
1742
  }
@@ -1695,16 +1744,18 @@ var NamingVerifier = class {
1695
1744
  for (const typeAlias of sourceFile.getTypeAliases()) {
1696
1745
  const name = typeAlias.getName();
1697
1746
  if (!pattern.regex.test(name)) {
1698
- violations.push(createViolation({
1699
- decisionId,
1700
- constraintId: constraint.id,
1701
- type: constraint.type,
1702
- severity: constraint.severity,
1703
- message: `Type "${name}" does not follow ${pattern.description} naming convention`,
1704
- file: filePath,
1705
- line: typeAlias.getStartLineNumber(),
1706
- suggestion: `Rename to follow ${pattern.description}`
1707
- }));
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
+ );
1708
1759
  }
1709
1760
  }
1710
1761
  }
@@ -1739,20 +1790,22 @@ var ImportsVerifier = class {
1739
1790
  const ms = importDecl.getModuleSpecifier();
1740
1791
  const start = ms.getStart() + 1;
1741
1792
  const end = ms.getEnd() - 1;
1742
- violations.push(createViolation({
1743
- decisionId,
1744
- constraintId: constraint.id,
1745
- type: constraint.type,
1746
- severity: constraint.severity,
1747
- message: `Relative import "${moduleSpec}" should include a .js extension`,
1748
- file: filePath,
1749
- line: importDecl.getStartLineNumber(),
1750
- suggestion: `Update to "${suggested}"`,
1751
- autofix: {
1752
- description: "Add/normalize .js extension in import specifier",
1753
- edits: [{ start, end, text: suggested }]
1754
- }
1755
- }));
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
+ );
1756
1809
  }
1757
1810
  }
1758
1811
  if (rule.includes("barrel") || rule.includes("index")) {
@@ -1761,16 +1814,18 @@ var ImportsVerifier = class {
1761
1814
  if (!moduleSpec.startsWith(".")) continue;
1762
1815
  if (moduleSpec.match(/\.(ts|js|tsx|jsx)$/) || moduleSpec.match(/\/[^/]+$/)) {
1763
1816
  if (!moduleSpec.endsWith("/index") && !moduleSpec.endsWith("index")) {
1764
- violations.push(createViolation({
1765
- decisionId,
1766
- constraintId: constraint.id,
1767
- type: constraint.type,
1768
- severity: constraint.severity,
1769
- message: `Import from "${moduleSpec}" should use barrel (index) import`,
1770
- file: filePath,
1771
- line: importDecl.getStartLineNumber(),
1772
- suggestion: "Import from the parent directory index file instead"
1773
- }));
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
+ );
1774
1829
  }
1775
1830
  }
1776
1831
  }
@@ -1779,16 +1834,18 @@ var ImportsVerifier = class {
1779
1834
  for (const importDecl of sourceFile.getImportDeclarations()) {
1780
1835
  const moduleSpec = importDecl.getModuleSpecifierValue();
1781
1836
  if (moduleSpec.match(/^\.\.\/\.\.\/\.\.\//)) {
1782
- violations.push(createViolation({
1783
- decisionId,
1784
- constraintId: constraint.id,
1785
- type: constraint.type,
1786
- severity: constraint.severity,
1787
- message: `Deep relative import "${moduleSpec}" should use path alias`,
1788
- file: filePath,
1789
- line: importDecl.getStartLineNumber(),
1790
- suggestion: "Use path alias (e.g., @/module) for deep imports"
1791
- }));
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
+ );
1792
1849
  }
1793
1850
  }
1794
1851
  }
@@ -1797,16 +1854,18 @@ var ImportsVerifier = class {
1797
1854
  for (const importDecl of sourceFile.getImportDeclarations()) {
1798
1855
  const moduleSpec = importDecl.getModuleSpecifierValue();
1799
1856
  if (moduleSpec.includes(currentFilename.split("/").pop() || "")) {
1800
- violations.push(createViolation({
1801
- decisionId,
1802
- constraintId: constraint.id,
1803
- type: constraint.type,
1804
- severity: constraint.severity,
1805
- message: `Possible circular import detected: "${moduleSpec}"`,
1806
- file: filePath,
1807
- line: importDecl.getStartLineNumber(),
1808
- suggestion: "Review import structure for circular dependencies"
1809
- }));
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
+ );
1810
1869
  }
1811
1870
  }
1812
1871
  }
@@ -1814,16 +1873,18 @@ var ImportsVerifier = class {
1814
1873
  for (const importDecl of sourceFile.getImportDeclarations()) {
1815
1874
  const namespaceImport = importDecl.getNamespaceImport();
1816
1875
  if (namespaceImport) {
1817
- violations.push(createViolation({
1818
- decisionId,
1819
- constraintId: constraint.id,
1820
- type: constraint.type,
1821
- severity: constraint.severity,
1822
- message: `Namespace import "* as ${namespaceImport.getText()}" should use named imports`,
1823
- file: filePath,
1824
- line: importDecl.getStartLineNumber(),
1825
- suggestion: "Use specific named imports instead of namespace import"
1826
- }));
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
+ );
1827
1888
  }
1828
1889
  }
1829
1890
  }
@@ -1849,16 +1910,18 @@ var ErrorsVerifier = class {
1849
1910
  if (!className?.endsWith("Error") && !className?.endsWith("Exception")) continue;
1850
1911
  const extendsClause = classDecl.getExtends();
1851
1912
  if (!extendsClause) {
1852
- violations.push(createViolation({
1853
- decisionId,
1854
- constraintId: constraint.id,
1855
- type: constraint.type,
1856
- severity: constraint.severity,
1857
- message: `Error class "${className}" does not extend any base class`,
1858
- file: filePath,
1859
- line: classDecl.getStartLineNumber(),
1860
- suggestion: requiredBase ? `Extend ${requiredBase}` : "Extend a base error class for consistent error handling"
1861
- }));
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
+ );
1862
1925
  } else if (requiredBase) {
1863
1926
  const baseName = extendsClause.getText();
1864
1927
  if (baseName !== requiredBase && baseName !== "Error") {
@@ -1873,16 +1936,18 @@ var ErrorsVerifier = class {
1873
1936
  if (expression) {
1874
1937
  const text = expression.getText();
1875
1938
  if (text.startsWith("new Error(")) {
1876
- violations.push(createViolation({
1877
- decisionId,
1878
- constraintId: constraint.id,
1879
- type: constraint.type,
1880
- severity: constraint.severity,
1881
- message: "Throwing generic Error instead of custom error class",
1882
- file: filePath,
1883
- line: node.getStartLineNumber(),
1884
- suggestion: "Use a custom error class for better error handling"
1885
- }));
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
+ );
1886
1951
  }
1887
1952
  }
1888
1953
  }
@@ -1896,16 +1961,18 @@ var ErrorsVerifier = class {
1896
1961
  const block = catchClause.getBlock();
1897
1962
  const statements = block.getStatements();
1898
1963
  if (statements.length === 0) {
1899
- violations.push(createViolation({
1900
- decisionId,
1901
- constraintId: constraint.id,
1902
- type: constraint.type,
1903
- severity: constraint.severity,
1904
- message: "Empty catch block swallows error without handling",
1905
- file: filePath,
1906
- line: catchClause.getStartLineNumber(),
1907
- suggestion: "Add error handling, logging, or rethrow the error"
1908
- }));
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
+ );
1909
1976
  }
1910
1977
  }
1911
1978
  }
@@ -1917,16 +1984,18 @@ var ErrorsVerifier = class {
1917
1984
  const expression = node.getExpression();
1918
1985
  const text = expression.getText();
1919
1986
  if (text === "console.error" || text === "console.log") {
1920
- violations.push(createViolation({
1921
- decisionId,
1922
- constraintId: constraint.id,
1923
- type: constraint.type,
1924
- severity: constraint.severity,
1925
- message: `Using ${text} instead of proper logging`,
1926
- file: filePath,
1927
- line: node.getStartLineNumber(),
1928
- suggestion: "Use a proper logging library"
1929
- }));
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
+ );
1930
1999
  }
1931
2000
  }
1932
2001
  });
@@ -1957,16 +2026,18 @@ var RegexVerifier = class {
1957
2026
  while ((match = regex.exec(fileText)) !== null) {
1958
2027
  const beforeMatch = fileText.substring(0, match.index);
1959
2028
  const lineNumber = beforeMatch.split("\n").length;
1960
- violations.push(createViolation({
1961
- decisionId,
1962
- constraintId: constraint.id,
1963
- type: constraint.type,
1964
- severity: constraint.severity,
1965
- message: `Found forbidden pattern: "${match[0]}"`,
1966
- file: filePath,
1967
- line: lineNumber,
1968
- suggestion: `Remove or replace the pattern matching /${patternToForbid}/`
1969
- }));
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
+ );
1970
2041
  }
1971
2042
  } catch {
1972
2043
  }
@@ -1976,15 +2047,17 @@ var RegexVerifier = class {
1976
2047
  try {
1977
2048
  const regex = new RegExp(patternToRequire);
1978
2049
  if (!regex.test(fileText)) {
1979
- violations.push(createViolation({
1980
- decisionId,
1981
- constraintId: constraint.id,
1982
- type: constraint.type,
1983
- severity: constraint.severity,
1984
- message: `File does not contain required pattern: /${patternToRequire}/`,
1985
- file: filePath,
1986
- suggestion: `Add code matching /${patternToRequire}/`
1987
- }));
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
+ );
1988
2061
  }
1989
2062
  } catch {
1990
2063
  }
@@ -2123,7 +2196,9 @@ function parseBannedDependency(rule) {
2123
2196
  return value.length > 0 ? value : null;
2124
2197
  }
2125
2198
  function parseLayerRule(rule) {
2126
- 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
+ );
2127
2202
  const fromLayer = m?.[1]?.toLowerCase();
2128
2203
  const toLayer = m?.[2]?.toLowerCase();
2129
2204
  if (!fromLayer || !toLayer) return null;
@@ -2156,16 +2231,18 @@ var DependencyVerifier = class {
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;
@@ -2760,7 +2869,10 @@ var PluginLoader = class {
2760
2869
  return { success: false, error: `Plugin ${id} requires params but none were provided` };
2761
2870
  }
2762
2871
  if (typeof plugin.paramsSchema !== "object" || !plugin.paramsSchema || !("parse" in plugin.paramsSchema)) {
2763
- 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
+ };
2764
2876
  }
2765
2877
  const schema = plugin.paramsSchema;
2766
2878
  if (schema.safeParse) {
@@ -3208,12 +3320,15 @@ var VerificationEngine = class {
3208
3320
  );
3209
3321
  if (!verifier) {
3210
3322
  const requestedVerifier = constraint.check?.verifier || constraint.verifier || "auto-detected";
3211
- this.logger.warn({
3212
- decisionId: decision.metadata.id,
3213
- constraintId: constraint.id,
3214
- requestedVerifier,
3215
- availableVerifiers: getVerifierIds()
3216
- }, "No verifier found for constraint");
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"
3331
+ );
3217
3332
  warnings.push({
3218
3333
  type: "missing_verifier",
3219
3334
  message: `No verifier found for constraint (requested: ${requestedVerifier})`,
@@ -3259,7 +3374,10 @@ var VerificationEngine = class {
3259
3374
  }
3260
3375
  if (constraint.check?.verifier && constraint.check?.params) {
3261
3376
  const pluginLoader2 = getPluginLoader();
3262
- const validationResult = pluginLoader2.validateParams(constraint.check.verifier, constraint.check.params);
3377
+ const validationResult = pluginLoader2.validateParams(
3378
+ constraint.check.verifier,
3379
+ constraint.check.params
3380
+ );
3263
3381
  if (!validationResult.success) {
3264
3382
  warnings.push({
3265
3383
  type: "invalid_params",
@@ -3319,14 +3437,17 @@ var VerificationEngine = class {
3319
3437
  } catch (error) {
3320
3438
  const errorMessage = error instanceof Error ? error.message : String(error);
3321
3439
  const errorStack = error instanceof Error ? error.stack : void 0;
3322
- this.logger.error({
3323
- verifierId: verifier.id,
3324
- filePath,
3325
- decisionId: decision.metadata.id,
3326
- constraintId: constraint.id,
3327
- error: errorMessage,
3328
- stack: errorStack
3329
- }, "Verifier execution failed");
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"
3450
+ );
3330
3451
  errors.push({
3331
3452
  type: "verifier_exception",
3332
3453
  message: `Verifier '${verifier.id}' failed: ${errorMessage}`,
@@ -3391,7 +3512,11 @@ import { resolve } from "path";
3391
3512
  var execFileAsync = promisify(execFile);
3392
3513
  async function getChangedFiles(cwd) {
3393
3514
  try {
3394
- 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
+ );
3395
3520
  const rel = stdout2.trim().split("\n").map((s) => s.trim()).filter(Boolean);
3396
3521
  const abs = [];
3397
3522
  for (const file of rel) {
@@ -3467,7 +3592,9 @@ var AutofixEngine = class {
3467
3592
  continue;
3468
3593
  }
3469
3594
  if (options.interactive) {
3470
- 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
+ );
3471
3598
  if (!ok) {
3472
3599
  skippedViolations++;
3473
3600
  continue;
@@ -3644,10 +3771,7 @@ var PropagationEngine = class {
3644
3771
  } else {
3645
3772
  estimatedEffort = "high";
3646
3773
  }
3647
- const migrationSteps = this.generateMigrationSteps(
3648
- affectedFiles,
3649
- totalAutoFixable > 0
3650
- );
3774
+ const migrationSteps = this.generateMigrationSteps(affectedFiles, totalAutoFixable > 0);
3651
3775
  return {
3652
3776
  decision: decisionId,
3653
3777
  change,
@@ -3670,9 +3794,7 @@ var PropagationEngine = class {
3670
3794
  automated: true
3671
3795
  });
3672
3796
  }
3673
- const filesWithManualFixes = affectedFiles.filter(
3674
- (f) => f.violations > f.autoFixable
3675
- );
3797
+ const filesWithManualFixes = affectedFiles.filter((f) => f.violations > f.autoFixable);
3676
3798
  if (filesWithManualFixes.length > 0) {
3677
3799
  const highPriority = filesWithManualFixes.filter((f) => f.violations > 5);
3678
3800
  const mediumPriority = filesWithManualFixes.filter(
@@ -3752,10 +3874,7 @@ async function generateReport(config, options = {}) {
3752
3874
  medium: decisionViolations.filter((v) => v.severity === "medium").length,
3753
3875
  low: decisionViolations.filter((v) => v.severity === "low").length
3754
3876
  };
3755
- weightedScore = decisionViolations.reduce(
3756
- (score, v) => score + weights[v.severity],
3757
- 0
3758
- );
3877
+ weightedScore = decisionViolations.reduce((score, v) => score + weights[v.severity], 0);
3759
3878
  compliance = Math.max(0, 100 - weightedScore);
3760
3879
  if (decisionViolations.length > 0 && constraintCount > 0) {
3761
3880
  const violationRate = decisionViolations.length / constraintCount;
@@ -3875,7 +3994,9 @@ var Reporter = class {
3875
3994
  lines.push("Summary:");
3876
3995
  lines.push(` Decisions Checked: ${result.summary.decisionsChecked || 0}`);
3877
3996
  lines.push(` Files Checked: ${result.summary.filesChecked || 0}`);
3878
- 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
+ );
3879
4000
  lines.push(` Critical: ${result.summary.critical || 0}`);
3880
4001
  lines.push(` High: ${result.summary.high || 0}`);
3881
4002
  lines.push(` Medium: ${result.summary.medium || 0}`);
@@ -3889,7 +4010,9 @@ var Reporter = class {
3889
4010
  lines.push("-".repeat(50));
3890
4011
  result.violations.forEach((v) => {
3891
4012
  const severity = v.severity.toLowerCase();
3892
- lines.push(` [${v.severity.toUpperCase()}] ${v.decisionId} - ${v.constraintId} (${severity})`);
4013
+ lines.push(
4014
+ ` [${v.severity.toUpperCase()}] ${v.decisionId} - ${v.constraintId} (${severity})`
4015
+ );
3893
4016
  lines.push(` ${v.message}`);
3894
4017
  const file = v.location?.file || v.file;
3895
4018
  const line = v.location?.line || v.line || 0;
@@ -3910,7 +4033,9 @@ var Reporter = class {
3910
4033
  lines.push("");
3911
4034
  if (result.summary) {
3912
4035
  lines.push("Summary:");
3913
- 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
+ );
3914
4039
  lines.push("");
3915
4040
  }
3916
4041
  if (result.violations && result.violations.length > 0) {
@@ -3964,7 +4089,9 @@ var Reporter = class {
3964
4089
  lines.push("### Summary\n");
3965
4090
  lines.push(`- **Decisions Checked:** ${result.summary.decisionsChecked || 0}`);
3966
4091
  lines.push(`- **Files Checked:** ${result.summary.filesChecked || 0}`);
3967
- 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
+ );
3968
4095
  lines.push(`- **Critical:** ${result.summary.critical || 0}`);
3969
4096
  lines.push(`- **High:** ${result.summary.high || 0}`);
3970
4097
  lines.push(`- **Medium:** ${result.summary.medium || 0}`);
@@ -4000,10 +4127,14 @@ function formatConsoleReport(report) {
4000
4127
  lines.push("");
4001
4128
  const complianceColor = getComplianceColor(report.summary.compliance);
4002
4129
  lines.push(chalk.bold("Overall Compliance"));
4003
- lines.push(` ${complianceColor(formatComplianceBar(report.summary.compliance))} ${complianceColor(`${report.summary.compliance}%`)}`);
4130
+ lines.push(
4131
+ ` ${complianceColor(formatComplianceBar(report.summary.compliance))} ${complianceColor(`${report.summary.compliance}%`)}`
4132
+ );
4004
4133
  lines.push("");
4005
4134
  lines.push(chalk.bold("Summary"));
4006
- lines.push(` Decisions: ${report.summary.activeDecisions} active / ${report.summary.totalDecisions} total`);
4135
+ lines.push(
4136
+ ` Decisions: ${report.summary.activeDecisions} active / ${report.summary.totalDecisions} total`
4137
+ );
4007
4138
  lines.push(` Constraints: ${report.summary.totalConstraints}`);
4008
4139
  lines.push("");
4009
4140
  lines.push(chalk.bold("Violations"));
@@ -4120,7 +4251,9 @@ function formatMarkdownReport(report) {
4120
4251
  lines.push("");
4121
4252
  lines.push("## Summary");
4122
4253
  lines.push("");
4123
- lines.push(`- **Active Decisions:** ${report.summary.activeDecisions} / ${report.summary.totalDecisions}`);
4254
+ lines.push(
4255
+ `- **Active Decisions:** ${report.summary.activeDecisions} / ${report.summary.totalDecisions}`
4256
+ );
4124
4257
  lines.push(`- **Total Constraints:** ${report.summary.totalConstraints}`);
4125
4258
  lines.push("");
4126
4259
  lines.push("### Violations");
@@ -4284,9 +4417,7 @@ var ReportStorage = class {
4284
4417
  async function detectDrift(current, previous) {
4285
4418
  const byDecision = [];
4286
4419
  for (const currDecision of current.byDecision) {
4287
- const prevDecision = previous.byDecision.find(
4288
- (d) => d.decisionId === currDecision.decisionId
4289
- );
4420
+ const prevDecision = previous.byDecision.find((d) => d.decisionId === currDecision.decisionId);
4290
4421
  if (!prevDecision) {
4291
4422
  byDecision.push({
4292
4423
  decisionId: currDecision.decisionId,
@@ -5067,9 +5198,7 @@ var AnalyticsEngine = class {
5067
5198
  overallTrend = "down";
5068
5199
  }
5069
5200
  }
5070
- const sortedByCompliance = [...latest.byDecision].sort(
5071
- (a, b) => b.compliance - a.compliance
5072
- );
5201
+ const sortedByCompliance = [...latest.byDecision].sort((a, b) => b.compliance - a.compliance);
5073
5202
  const topDecisions = sortedByCompliance.slice(0, 5).map((d) => ({
5074
5203
  decisionId: d.decisionId,
5075
5204
  title: d.title,
@@ -5126,14 +5255,11 @@ var DashboardServer = class {
5126
5255
  async start() {
5127
5256
  await this.registry.load();
5128
5257
  await this.refreshCache();
5129
- this.refreshInterval = setInterval(
5130
- () => {
5131
- void this.refreshCache().catch((error) => {
5132
- this.logger.error({ error }, "Background cache refresh failed");
5133
- });
5134
- },
5135
- this.CACHE_TTL
5136
- );
5258
+ this.refreshInterval = setInterval(() => {
5259
+ void this.refreshCache().catch((error) => {
5260
+ this.logger.error({ error }, "Background cache refresh failed");
5261
+ });
5262
+ }, this.CACHE_TTL);
5137
5263
  }
5138
5264
  /**
5139
5265
  * Stop the server and clear intervals
@@ -5405,11 +5531,13 @@ var DashboardServer = class {
5405
5531
  */
5406
5532
  setupStaticFiles() {
5407
5533
  const publicDir = join5(__dirname, "public");
5408
- this.app.use(express.static(publicDir, {
5409
- maxAge: "1h",
5410
- // Cache static assets
5411
- etag: true
5412
- }));
5534
+ this.app.use(
5535
+ express.static(publicDir, {
5536
+ maxAge: "1h",
5537
+ // Cache static assets
5538
+ etag: true
5539
+ })
5540
+ );
5413
5541
  this.app.get("/{*path}", (_req, res) => {
5414
5542
  res.sendFile(join5(publicDir, "index.html"));
5415
5543
  });
@@ -5420,7 +5548,14 @@ function createDashboardServer(options) {
5420
5548
  }
5421
5549
 
5422
5550
  // src/lsp/server.ts
5423
- 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";
5424
5559
  import { TextDocument } from "vscode-languageserver-textdocument";
5425
5560
  import { fileURLToPath as fileURLToPath2 } from "url";
5426
5561
  import path3 from "path";
@@ -5545,7 +5680,8 @@ var SpecBridgeLspServer = class {
5545
5680
  await getPluginLoader().loadPlugins(this.cwd);
5546
5681
  } catch (error) {
5547
5682
  const msg = error instanceof Error ? error.message : String(error);
5548
- if (this.options.verbose) this.connection.console.error(chalk2.red(`Plugin load failed: ${msg}`));
5683
+ if (this.options.verbose)
5684
+ this.connection.console.error(chalk2.red(`Plugin load failed: ${msg}`));
5549
5685
  }
5550
5686
  this.registry = createRegistry({ basePath: this.cwd });
5551
5687
  await this.registry.load();
@@ -5558,7 +5694,9 @@ var SpecBridgeLspServer = class {
5558
5694
  }
5559
5695
  }
5560
5696
  if (this.options.verbose) {
5561
- this.connection.console.log(chalk2.dim(`Loaded ${this.decisions.length} active decision(s)`));
5697
+ this.connection.console.log(
5698
+ chalk2.dim(`Loaded ${this.decisions.length} active decision(s)`)
5699
+ );
5562
5700
  }
5563
5701
  } catch (error) {
5564
5702
  this.initError = error instanceof Error ? error.message : String(error);
@@ -5576,7 +5714,11 @@ var SpecBridgeLspServer = class {
5576
5714
  for (const decision of this.decisions) {
5577
5715
  for (const constraint of decision.constraints) {
5578
5716
  if (!shouldApplyConstraintToFile({ filePath, constraint, cwd: this.cwd })) continue;
5579
- const verifier = selectVerifierForConstraint(constraint.rule, constraint.verifier, constraint.check);
5717
+ const verifier = selectVerifierForConstraint(
5718
+ constraint.rule,
5719
+ constraint.verifier,
5720
+ constraint.check
5721
+ );
5580
5722
  if (!verifier) continue;
5581
5723
  const ctx = {
5582
5724
  filePath,
@@ -5646,9 +5788,7 @@ function formatViolationsForGitHub(violations, limit = 50) {
5646
5788
  if (violations.length === 0) {
5647
5789
  return "## SpecBridge\n\n\u2705 No violations found.";
5648
5790
  }
5649
- const rows = [
5650
- ["Severity", "Type", "File", "Decision/Constraint", "Message"]
5651
- ];
5791
+ const rows = [["Severity", "Type", "File", "Decision/Constraint", "Message"]];
5652
5792
  for (const v of violations.slice(0, limit)) {
5653
5793
  const loc = v.line ? `:${v.line}${v.column ? `:${v.column}` : ""}` : "";
5654
5794
  rows.push([
@@ -5670,19 +5810,24 @@ ${toMdTable(rows)}${extra}`;
5670
5810
  }
5671
5811
  async function postPrComment(violations, options) {
5672
5812
  const body = formatViolationsForGitHub(violations);
5673
- const res = await fetch(`https://api.github.com/repos/${options.repo}/issues/${options.pr}/comments`, {
5674
- method: "POST",
5675
- headers: {
5676
- Authorization: `Bearer ${options.token}`,
5677
- Accept: "application/vnd.github+json",
5678
- "Content-Type": "application/json",
5679
- "User-Agent": "specbridge"
5680
- },
5681
- body: JSON.stringify({ body })
5682
- });
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
+ );
5683
5826
  if (!res.ok) {
5684
5827
  const text = await res.text().catch(() => "");
5685
- 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
+ );
5686
5831
  }
5687
5832
  }
5688
5833
  export {