@ipation/specbridge 2.4.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/cli.js CHANGED
@@ -343,12 +343,7 @@ import fg from "fast-glob";
343
343
  import { minimatch } from "minimatch";
344
344
  import { relative, isAbsolute } from "path";
345
345
  async function glob(patterns, options = {}) {
346
- const {
347
- cwd = process.cwd(),
348
- ignore = [],
349
- absolute = false,
350
- onlyFiles = true
351
- } = options;
346
+ const { cwd = process.cwd(), ignore = [], absolute = false, onlyFiles = true } = options;
352
347
  return fg(patterns, {
353
348
  cwd,
354
349
  ignore,
@@ -582,12 +577,24 @@ var FUNCTION_PATTERNS = [
582
577
  { convention: "snake_case", regex: /^[a-z][a-z0-9_]*$/, description: "Functions use snake_case" }
583
578
  ];
584
579
  var INTERFACE_PATTERNS = [
585
- { convention: "PascalCase", regex: /^[A-Z][a-zA-Z0-9]*$/, description: "Interfaces use PascalCase" },
586
- { convention: "IPrefixed", regex: /^I[A-Z][a-zA-Z0-9]*$/, description: "Interfaces are prefixed with I" }
580
+ {
581
+ convention: "PascalCase",
582
+ regex: /^[A-Z][a-zA-Z0-9]*$/,
583
+ description: "Interfaces use PascalCase"
584
+ },
585
+ {
586
+ convention: "IPrefixed",
587
+ regex: /^I[A-Z][a-zA-Z0-9]*$/,
588
+ description: "Interfaces are prefixed with I"
589
+ }
587
590
  ];
588
591
  var TYPE_PATTERNS = [
589
592
  { convention: "PascalCase", regex: /^[A-Z][a-zA-Z0-9]*$/, description: "Types use PascalCase" },
590
- { convention: "TSuffixed", regex: /^[A-Z][a-zA-Z0-9]*Type$/, description: "Types are suffixed with Type" }
593
+ {
594
+ convention: "TSuffixed",
595
+ regex: /^[A-Z][a-zA-Z0-9]*Type$/,
596
+ description: "Types are suffixed with Type"
597
+ }
591
598
  ];
592
599
  var NamingAnalyzer = class {
593
600
  id = "naming";
@@ -608,7 +615,10 @@ var NamingAnalyzer = class {
608
615
  analyzeClassNaming(scanner) {
609
616
  const classes = scanner.findClasses();
610
617
  if (classes.length < 3) return null;
611
- const matches = this.findBestMatch(classes.map((c) => c.name), CLASS_PATTERNS);
618
+ const matches = this.findBestMatch(
619
+ classes.map((c) => c.name),
620
+ CLASS_PATTERNS
621
+ );
612
622
  if (!matches) return null;
613
623
  return createPattern(this.id, {
614
624
  id: "naming-classes",
@@ -632,7 +642,10 @@ var NamingAnalyzer = class {
632
642
  analyzeFunctionNaming(scanner) {
633
643
  const functions = scanner.findFunctions();
634
644
  if (functions.length < 3) return null;
635
- const matches = this.findBestMatch(functions.map((f) => f.name), FUNCTION_PATTERNS);
645
+ const matches = this.findBestMatch(
646
+ functions.map((f) => f.name),
647
+ FUNCTION_PATTERNS
648
+ );
636
649
  if (!matches) return null;
637
650
  return createPattern(this.id, {
638
651
  id: "naming-functions",
@@ -656,7 +669,10 @@ var NamingAnalyzer = class {
656
669
  analyzeInterfaceNaming(scanner) {
657
670
  const interfaces = scanner.findInterfaces();
658
671
  if (interfaces.length < 3) return null;
659
- const matches = this.findBestMatch(interfaces.map((i) => i.name), INTERFACE_PATTERNS);
672
+ const matches = this.findBestMatch(
673
+ interfaces.map((i) => i.name),
674
+ INTERFACE_PATTERNS
675
+ );
660
676
  if (!matches) return null;
661
677
  return createPattern(this.id, {
662
678
  id: "naming-interfaces",
@@ -680,7 +696,10 @@ var NamingAnalyzer = class {
680
696
  analyzeTypeNaming(scanner) {
681
697
  const types = scanner.findTypeAliases();
682
698
  if (types.length < 3) return null;
683
- const matches = this.findBestMatch(types.map((t) => t.name), TYPE_PATTERNS);
699
+ const matches = this.findBestMatch(
700
+ types.map((t) => t.name),
701
+ TYPE_PATTERNS
702
+ );
684
703
  if (!matches) return null;
685
704
  return createPattern(this.id, {
686
705
  id: "naming-types",
@@ -769,8 +788,12 @@ var ImportsAnalyzer = class {
769
788
  analyzeRelativeImports(scanner) {
770
789
  const imports = scanner.findImports();
771
790
  const relativeImports = imports.filter((i) => i.module.startsWith("."));
772
- const absoluteImports = imports.filter((i) => !i.module.startsWith(".") && !i.module.startsWith("@"));
773
- const aliasImports = imports.filter((i) => i.module.startsWith("@/") || i.module.startsWith("~"));
791
+ const absoluteImports = imports.filter(
792
+ (i) => !i.module.startsWith(".") && !i.module.startsWith("@")
793
+ );
794
+ const aliasImports = imports.filter(
795
+ (i) => i.module.startsWith("@/") || i.module.startsWith("~")
796
+ );
774
797
  const total = relativeImports.length + absoluteImports.length + aliasImports.length;
775
798
  if (total < 10) return null;
776
799
  if (aliasImports.length > relativeImports.length && aliasImports.length >= 5) {
@@ -837,18 +860,20 @@ var ImportsAnalyzer = class {
837
860
  for (const [packageName, data] of moduleCounts) {
838
861
  if (data.count >= 5) {
839
862
  const confidence = Math.min(100, 50 + data.count * 2);
840
- patterns.push(createPattern(this.id, {
841
- id: `imports-module-${packageName.replace(/[/@]/g, "-")}`,
842
- name: `${packageName} Usage`,
843
- description: `${packageName} is used across ${data.count} files`,
844
- confidence,
845
- occurrences: data.count,
846
- examples: data.examples.slice(0, 3).map((i) => ({
847
- file: i.file,
848
- line: i.line,
849
- snippet: `import { ${i.named.slice(0, 2).join(", ") || "..."} } from '${i.module}'`
850
- }))
851
- }));
863
+ patterns.push(
864
+ createPattern(this.id, {
865
+ id: `imports-module-${packageName.replace(/[/@]/g, "-")}`,
866
+ name: `${packageName} Usage`,
867
+ description: `${packageName} is used across ${data.count} files`,
868
+ confidence,
869
+ occurrences: data.count,
870
+ examples: data.examples.slice(0, 3).map((i) => ({
871
+ file: i.file,
872
+ line: i.line,
873
+ snippet: `import { ${i.named.slice(0, 2).join(", ") || "..."} } from '${i.module}'`
874
+ }))
875
+ })
876
+ );
852
877
  }
853
878
  }
854
879
  return patterns;
@@ -893,24 +918,26 @@ var StructureAnalyzer = class {
893
918
  const count = dirCounts.get(name);
894
919
  if (count && count >= 3) {
895
920
  const exampleFiles = files.filter((f) => basename(dirname2(f.path)) === name).slice(0, 3);
896
- patterns.push(createPattern(this.id, {
897
- id: `structure-dir-${name}`,
898
- name: `${name}/ Directory Convention`,
899
- description,
900
- confidence: Math.min(100, 60 + count * 5),
901
- occurrences: count,
902
- examples: exampleFiles.map((f) => ({
903
- file: f.path,
904
- line: 1,
905
- snippet: basename(f.path)
906
- })),
907
- suggestedConstraint: {
908
- type: "convention",
909
- rule: `${name.charAt(0).toUpperCase() + name.slice(1)} should be placed in the ${name}/ directory`,
910
- severity: "low",
911
- scope: `src/**/${name}/**/*.ts`
912
- }
913
- }));
921
+ patterns.push(
922
+ createPattern(this.id, {
923
+ id: `structure-dir-${name}`,
924
+ name: `${name}/ Directory Convention`,
925
+ description,
926
+ confidence: Math.min(100, 60 + count * 5),
927
+ occurrences: count,
928
+ examples: exampleFiles.map((f) => ({
929
+ file: f.path,
930
+ line: 1,
931
+ snippet: basename(f.path)
932
+ })),
933
+ suggestedConstraint: {
934
+ type: "convention",
935
+ rule: `${name.charAt(0).toUpperCase() + name.slice(1)} should be placed in the ${name}/ directory`,
936
+ severity: "low",
937
+ scope: `src/**/${name}/**/*.ts`
938
+ }
939
+ })
940
+ );
914
941
  }
915
942
  }
916
943
  return patterns;
@@ -920,29 +947,55 @@ var StructureAnalyzer = class {
920
947
  const suffixPatterns = [
921
948
  { suffix: ".test.ts", pattern: /\.test\.ts$/, description: "Test files use .test.ts suffix" },
922
949
  { suffix: ".spec.ts", pattern: /\.spec\.ts$/, description: "Test files use .spec.ts suffix" },
923
- { suffix: ".types.ts", pattern: /\.types\.ts$/, description: "Type definition files use .types.ts suffix" },
924
- { suffix: ".utils.ts", pattern: /\.utils\.ts$/, description: "Utility files use .utils.ts suffix" },
925
- { suffix: ".service.ts", pattern: /\.service\.ts$/, description: "Service files use .service.ts suffix" },
926
- { suffix: ".controller.ts", pattern: /\.controller\.ts$/, description: "Controller files use .controller.ts suffix" },
927
- { suffix: ".model.ts", pattern: /\.model\.ts$/, description: "Model files use .model.ts suffix" },
928
- { suffix: ".schema.ts", pattern: /\.schema\.ts$/, description: "Schema files use .schema.ts suffix" }
950
+ {
951
+ suffix: ".types.ts",
952
+ pattern: /\.types\.ts$/,
953
+ description: "Type definition files use .types.ts suffix"
954
+ },
955
+ {
956
+ suffix: ".utils.ts",
957
+ pattern: /\.utils\.ts$/,
958
+ description: "Utility files use .utils.ts suffix"
959
+ },
960
+ {
961
+ suffix: ".service.ts",
962
+ pattern: /\.service\.ts$/,
963
+ description: "Service files use .service.ts suffix"
964
+ },
965
+ {
966
+ suffix: ".controller.ts",
967
+ pattern: /\.controller\.ts$/,
968
+ description: "Controller files use .controller.ts suffix"
969
+ },
970
+ {
971
+ suffix: ".model.ts",
972
+ pattern: /\.model\.ts$/,
973
+ description: "Model files use .model.ts suffix"
974
+ },
975
+ {
976
+ suffix: ".schema.ts",
977
+ pattern: /\.schema\.ts$/,
978
+ description: "Schema files use .schema.ts suffix"
979
+ }
929
980
  ];
930
981
  for (const { suffix, pattern, description } of suffixPatterns) {
931
982
  const matchingFiles = files.filter((f) => pattern.test(f.path));
932
983
  if (matchingFiles.length >= 3) {
933
984
  const confidence = Math.min(100, 60 + matchingFiles.length * 3);
934
- patterns.push(createPattern(this.id, {
935
- id: `structure-suffix-${suffix.replace(/\./g, "-")}`,
936
- name: `${suffix} File Naming`,
937
- description,
938
- confidence,
939
- occurrences: matchingFiles.length,
940
- examples: matchingFiles.slice(0, 3).map((f) => ({
941
- file: f.path,
942
- line: 1,
943
- snippet: basename(f.path)
944
- }))
945
- }));
985
+ patterns.push(
986
+ createPattern(this.id, {
987
+ id: `structure-suffix-${suffix.replace(/\./g, "-")}`,
988
+ name: `${suffix} File Naming`,
989
+ description,
990
+ confidence,
991
+ occurrences: matchingFiles.length,
992
+ examples: matchingFiles.slice(0, 3).map((f) => ({
993
+ file: f.path,
994
+ line: 1,
995
+ snippet: basename(f.path)
996
+ }))
997
+ })
998
+ );
946
999
  }
947
1000
  }
948
1001
  return patterns;
@@ -1308,7 +1361,9 @@ Results saved to: ${outputPath}`));
1308
1361
  console.log("");
1309
1362
  console.log(chalk2.cyan("Next steps:"));
1310
1363
  console.log(" Review detected patterns and create decisions for important ones.");
1311
- console.log(` Use ${chalk2.bold("specbridge decision create <id>")} to create a new decision.`);
1364
+ console.log(
1365
+ ` Use ${chalk2.bold("specbridge decision create <id>")} to create a new decision.`
1366
+ );
1312
1367
  }
1313
1368
  } catch (error) {
1314
1369
  spinner.fail("Inference failed");
@@ -1324,7 +1379,9 @@ Detected ${patterns.length} pattern(s):
1324
1379
  console.log(chalk2.bold(`${pattern.name}`));
1325
1380
  console.log(chalk2.dim(` ID: ${pattern.id}`));
1326
1381
  console.log(` ${pattern.description}`);
1327
- console.log(` Confidence: ${confidenceColor(`${pattern.confidence}%`)} (${pattern.occurrences} occurrences)`);
1382
+ console.log(
1383
+ ` Confidence: ${confidenceColor(`${pattern.confidence}%`)} (${pattern.occurrences} occurrences)`
1384
+ );
1328
1385
  console.log(chalk2.dim(` Analyzer: ${pattern.analyzer}`));
1329
1386
  if (pattern.examples.length > 0) {
1330
1387
  console.log(chalk2.dim(" Examples:"));
@@ -1607,17 +1664,13 @@ var Registry = class {
1607
1664
  * Get decisions by tag
1608
1665
  */
1609
1666
  getByTag(tag) {
1610
- return this.getAll().filter(
1611
- (d) => d.metadata.tags?.includes(tag)
1612
- );
1667
+ return this.getAll().filter((d) => d.metadata.tags?.includes(tag));
1613
1668
  }
1614
1669
  /**
1615
1670
  * Get decisions by owner
1616
1671
  */
1617
1672
  getByOwner(owner) {
1618
- return this.getAll().filter(
1619
- (d) => d.metadata.owners.includes(owner)
1620
- );
1673
+ return this.getAll().filter((d) => d.metadata.owners.includes(owner));
1621
1674
  }
1622
1675
  /**
1623
1676
  * Apply filter to decisions
@@ -1628,21 +1681,15 @@ var Registry = class {
1628
1681
  return false;
1629
1682
  }
1630
1683
  if (filter.tags) {
1631
- const hasTags = filter.tags.some(
1632
- (tag) => decision.metadata.tags?.includes(tag)
1633
- );
1684
+ const hasTags = filter.tags.some((tag) => decision.metadata.tags?.includes(tag));
1634
1685
  if (!hasTags) return false;
1635
1686
  }
1636
1687
  if (filter.constraintType) {
1637
- const hasType = decision.constraints.some(
1638
- (c) => filter.constraintType?.includes(c.type)
1639
- );
1688
+ const hasType = decision.constraints.some((c) => filter.constraintType?.includes(c.type));
1640
1689
  if (!hasType) return false;
1641
1690
  }
1642
1691
  if (filter.severity) {
1643
- const hasSeverity = decision.constraints.some(
1644
- (c) => filter.severity?.includes(c.severity)
1645
- );
1692
+ const hasSeverity = decision.constraints.some((c) => filter.severity?.includes(c.severity));
1646
1693
  if (!hasSeverity) return false;
1647
1694
  }
1648
1695
  return true;
@@ -1737,17 +1784,19 @@ var NamingVerifier = class {
1737
1784
  for (const classDecl of sourceFile.getClasses()) {
1738
1785
  const name = classDecl.getName();
1739
1786
  if (name && !pattern.regex.test(name)) {
1740
- violations.push(createViolation({
1741
- decisionId,
1742
- constraintId: constraint.id,
1743
- type: constraint.type,
1744
- severity: constraint.severity,
1745
- message: `Class "${name}" does not follow ${pattern.description} naming convention`,
1746
- file: filePath,
1747
- line: classDecl.getStartLineNumber(),
1748
- column: classDecl.getStart() - classDecl.getStartLinePos(),
1749
- suggestion: `Rename to follow ${pattern.description}`
1750
- }));
1787
+ violations.push(
1788
+ createViolation({
1789
+ decisionId,
1790
+ constraintId: constraint.id,
1791
+ type: constraint.type,
1792
+ severity: constraint.severity,
1793
+ message: `Class "${name}" does not follow ${pattern.description} naming convention`,
1794
+ file: filePath,
1795
+ line: classDecl.getStartLineNumber(),
1796
+ column: classDecl.getStart() - classDecl.getStartLinePos(),
1797
+ suggestion: `Rename to follow ${pattern.description}`
1798
+ })
1799
+ );
1751
1800
  }
1752
1801
  }
1753
1802
  }
@@ -1755,16 +1804,18 @@ var NamingVerifier = class {
1755
1804
  for (const funcDecl of sourceFile.getFunctions()) {
1756
1805
  const name = funcDecl.getName();
1757
1806
  if (name && !pattern.regex.test(name)) {
1758
- violations.push(createViolation({
1759
- decisionId,
1760
- constraintId: constraint.id,
1761
- type: constraint.type,
1762
- severity: constraint.severity,
1763
- message: `Function "${name}" does not follow ${pattern.description} naming convention`,
1764
- file: filePath,
1765
- line: funcDecl.getStartLineNumber(),
1766
- suggestion: `Rename to follow ${pattern.description}`
1767
- }));
1807
+ violations.push(
1808
+ createViolation({
1809
+ decisionId,
1810
+ constraintId: constraint.id,
1811
+ type: constraint.type,
1812
+ severity: constraint.severity,
1813
+ message: `Function "${name}" does not follow ${pattern.description} naming convention`,
1814
+ file: filePath,
1815
+ line: funcDecl.getStartLineNumber(),
1816
+ suggestion: `Rename to follow ${pattern.description}`
1817
+ })
1818
+ );
1768
1819
  }
1769
1820
  }
1770
1821
  }
@@ -1772,16 +1823,18 @@ var NamingVerifier = class {
1772
1823
  for (const interfaceDecl of sourceFile.getInterfaces()) {
1773
1824
  const name = interfaceDecl.getName();
1774
1825
  if (!pattern.regex.test(name)) {
1775
- violations.push(createViolation({
1776
- decisionId,
1777
- constraintId: constraint.id,
1778
- type: constraint.type,
1779
- severity: constraint.severity,
1780
- message: `Interface "${name}" does not follow ${pattern.description} naming convention`,
1781
- file: filePath,
1782
- line: interfaceDecl.getStartLineNumber(),
1783
- suggestion: `Rename to follow ${pattern.description}`
1784
- }));
1826
+ violations.push(
1827
+ createViolation({
1828
+ decisionId,
1829
+ constraintId: constraint.id,
1830
+ type: constraint.type,
1831
+ severity: constraint.severity,
1832
+ message: `Interface "${name}" does not follow ${pattern.description} naming convention`,
1833
+ file: filePath,
1834
+ line: interfaceDecl.getStartLineNumber(),
1835
+ suggestion: `Rename to follow ${pattern.description}`
1836
+ })
1837
+ );
1785
1838
  }
1786
1839
  }
1787
1840
  }
@@ -1789,16 +1842,18 @@ var NamingVerifier = class {
1789
1842
  for (const typeAlias of sourceFile.getTypeAliases()) {
1790
1843
  const name = typeAlias.getName();
1791
1844
  if (!pattern.regex.test(name)) {
1792
- violations.push(createViolation({
1793
- decisionId,
1794
- constraintId: constraint.id,
1795
- type: constraint.type,
1796
- severity: constraint.severity,
1797
- message: `Type "${name}" does not follow ${pattern.description} naming convention`,
1798
- file: filePath,
1799
- line: typeAlias.getStartLineNumber(),
1800
- suggestion: `Rename to follow ${pattern.description}`
1801
- }));
1845
+ violations.push(
1846
+ createViolation({
1847
+ decisionId,
1848
+ constraintId: constraint.id,
1849
+ type: constraint.type,
1850
+ severity: constraint.severity,
1851
+ message: `Type "${name}" does not follow ${pattern.description} naming convention`,
1852
+ file: filePath,
1853
+ line: typeAlias.getStartLineNumber(),
1854
+ suggestion: `Rename to follow ${pattern.description}`
1855
+ })
1856
+ );
1802
1857
  }
1803
1858
  }
1804
1859
  }
@@ -1833,20 +1888,22 @@ var ImportsVerifier = class {
1833
1888
  const ms = importDecl.getModuleSpecifier();
1834
1889
  const start = ms.getStart() + 1;
1835
1890
  const end = ms.getEnd() - 1;
1836
- violations.push(createViolation({
1837
- decisionId,
1838
- constraintId: constraint.id,
1839
- type: constraint.type,
1840
- severity: constraint.severity,
1841
- message: `Relative import "${moduleSpec}" should include a .js extension`,
1842
- file: filePath,
1843
- line: importDecl.getStartLineNumber(),
1844
- suggestion: `Update to "${suggested}"`,
1845
- autofix: {
1846
- description: "Add/normalize .js extension in import specifier",
1847
- edits: [{ start, end, text: suggested }]
1848
- }
1849
- }));
1891
+ violations.push(
1892
+ createViolation({
1893
+ decisionId,
1894
+ constraintId: constraint.id,
1895
+ type: constraint.type,
1896
+ severity: constraint.severity,
1897
+ message: `Relative import "${moduleSpec}" should include a .js extension`,
1898
+ file: filePath,
1899
+ line: importDecl.getStartLineNumber(),
1900
+ suggestion: `Update to "${suggested}"`,
1901
+ autofix: {
1902
+ description: "Add/normalize .js extension in import specifier",
1903
+ edits: [{ start, end, text: suggested }]
1904
+ }
1905
+ })
1906
+ );
1850
1907
  }
1851
1908
  }
1852
1909
  if (rule.includes("barrel") || rule.includes("index")) {
@@ -1855,16 +1912,18 @@ var ImportsVerifier = class {
1855
1912
  if (!moduleSpec.startsWith(".")) continue;
1856
1913
  if (moduleSpec.match(/\.(ts|js|tsx|jsx)$/) || moduleSpec.match(/\/[^/]+$/)) {
1857
1914
  if (!moduleSpec.endsWith("/index") && !moduleSpec.endsWith("index")) {
1858
- violations.push(createViolation({
1859
- decisionId,
1860
- constraintId: constraint.id,
1861
- type: constraint.type,
1862
- severity: constraint.severity,
1863
- message: `Import from "${moduleSpec}" should use barrel (index) import`,
1864
- file: filePath,
1865
- line: importDecl.getStartLineNumber(),
1866
- suggestion: "Import from the parent directory index file instead"
1867
- }));
1915
+ violations.push(
1916
+ createViolation({
1917
+ decisionId,
1918
+ constraintId: constraint.id,
1919
+ type: constraint.type,
1920
+ severity: constraint.severity,
1921
+ message: `Import from "${moduleSpec}" should use barrel (index) import`,
1922
+ file: filePath,
1923
+ line: importDecl.getStartLineNumber(),
1924
+ suggestion: "Import from the parent directory index file instead"
1925
+ })
1926
+ );
1868
1927
  }
1869
1928
  }
1870
1929
  }
@@ -1873,16 +1932,18 @@ var ImportsVerifier = class {
1873
1932
  for (const importDecl of sourceFile.getImportDeclarations()) {
1874
1933
  const moduleSpec = importDecl.getModuleSpecifierValue();
1875
1934
  if (moduleSpec.match(/^\.\.\/\.\.\/\.\.\//)) {
1876
- violations.push(createViolation({
1877
- decisionId,
1878
- constraintId: constraint.id,
1879
- type: constraint.type,
1880
- severity: constraint.severity,
1881
- message: `Deep relative import "${moduleSpec}" should use path alias`,
1882
- file: filePath,
1883
- line: importDecl.getStartLineNumber(),
1884
- suggestion: "Use path alias (e.g., @/module) for deep imports"
1885
- }));
1935
+ violations.push(
1936
+ createViolation({
1937
+ decisionId,
1938
+ constraintId: constraint.id,
1939
+ type: constraint.type,
1940
+ severity: constraint.severity,
1941
+ message: `Deep relative import "${moduleSpec}" should use path alias`,
1942
+ file: filePath,
1943
+ line: importDecl.getStartLineNumber(),
1944
+ suggestion: "Use path alias (e.g., @/module) for deep imports"
1945
+ })
1946
+ );
1886
1947
  }
1887
1948
  }
1888
1949
  }
@@ -1891,16 +1952,18 @@ var ImportsVerifier = class {
1891
1952
  for (const importDecl of sourceFile.getImportDeclarations()) {
1892
1953
  const moduleSpec = importDecl.getModuleSpecifierValue();
1893
1954
  if (moduleSpec.includes(currentFilename.split("/").pop() || "")) {
1894
- violations.push(createViolation({
1895
- decisionId,
1896
- constraintId: constraint.id,
1897
- type: constraint.type,
1898
- severity: constraint.severity,
1899
- message: `Possible circular import detected: "${moduleSpec}"`,
1900
- file: filePath,
1901
- line: importDecl.getStartLineNumber(),
1902
- suggestion: "Review import structure for circular dependencies"
1903
- }));
1955
+ violations.push(
1956
+ createViolation({
1957
+ decisionId,
1958
+ constraintId: constraint.id,
1959
+ type: constraint.type,
1960
+ severity: constraint.severity,
1961
+ message: `Possible circular import detected: "${moduleSpec}"`,
1962
+ file: filePath,
1963
+ line: importDecl.getStartLineNumber(),
1964
+ suggestion: "Review import structure for circular dependencies"
1965
+ })
1966
+ );
1904
1967
  }
1905
1968
  }
1906
1969
  }
@@ -1908,16 +1971,18 @@ var ImportsVerifier = class {
1908
1971
  for (const importDecl of sourceFile.getImportDeclarations()) {
1909
1972
  const namespaceImport = importDecl.getNamespaceImport();
1910
1973
  if (namespaceImport) {
1911
- violations.push(createViolation({
1912
- decisionId,
1913
- constraintId: constraint.id,
1914
- type: constraint.type,
1915
- severity: constraint.severity,
1916
- message: `Namespace import "* as ${namespaceImport.getText()}" should use named imports`,
1917
- file: filePath,
1918
- line: importDecl.getStartLineNumber(),
1919
- suggestion: "Use specific named imports instead of namespace import"
1920
- }));
1974
+ violations.push(
1975
+ createViolation({
1976
+ decisionId,
1977
+ constraintId: constraint.id,
1978
+ type: constraint.type,
1979
+ severity: constraint.severity,
1980
+ message: `Namespace import "* as ${namespaceImport.getText()}" should use named imports`,
1981
+ file: filePath,
1982
+ line: importDecl.getStartLineNumber(),
1983
+ suggestion: "Use specific named imports instead of namespace import"
1984
+ })
1985
+ );
1921
1986
  }
1922
1987
  }
1923
1988
  }
@@ -1943,16 +2008,18 @@ var ErrorsVerifier = class {
1943
2008
  if (!className?.endsWith("Error") && !className?.endsWith("Exception")) continue;
1944
2009
  const extendsClause = classDecl.getExtends();
1945
2010
  if (!extendsClause) {
1946
- violations.push(createViolation({
1947
- decisionId,
1948
- constraintId: constraint.id,
1949
- type: constraint.type,
1950
- severity: constraint.severity,
1951
- message: `Error class "${className}" does not extend any base class`,
1952
- file: filePath,
1953
- line: classDecl.getStartLineNumber(),
1954
- suggestion: requiredBase ? `Extend ${requiredBase}` : "Extend a base error class for consistent error handling"
1955
- }));
2011
+ violations.push(
2012
+ createViolation({
2013
+ decisionId,
2014
+ constraintId: constraint.id,
2015
+ type: constraint.type,
2016
+ severity: constraint.severity,
2017
+ message: `Error class "${className}" does not extend any base class`,
2018
+ file: filePath,
2019
+ line: classDecl.getStartLineNumber(),
2020
+ suggestion: requiredBase ? `Extend ${requiredBase}` : "Extend a base error class for consistent error handling"
2021
+ })
2022
+ );
1956
2023
  } else if (requiredBase) {
1957
2024
  const baseName = extendsClause.getText();
1958
2025
  if (baseName !== requiredBase && baseName !== "Error") {
@@ -1967,16 +2034,18 @@ var ErrorsVerifier = class {
1967
2034
  if (expression) {
1968
2035
  const text = expression.getText();
1969
2036
  if (text.startsWith("new Error(")) {
1970
- violations.push(createViolation({
1971
- decisionId,
1972
- constraintId: constraint.id,
1973
- type: constraint.type,
1974
- severity: constraint.severity,
1975
- message: "Throwing generic Error instead of custom error class",
1976
- file: filePath,
1977
- line: node.getStartLineNumber(),
1978
- suggestion: "Use a custom error class for better error handling"
1979
- }));
2037
+ violations.push(
2038
+ createViolation({
2039
+ decisionId,
2040
+ constraintId: constraint.id,
2041
+ type: constraint.type,
2042
+ severity: constraint.severity,
2043
+ message: "Throwing generic Error instead of custom error class",
2044
+ file: filePath,
2045
+ line: node.getStartLineNumber(),
2046
+ suggestion: "Use a custom error class for better error handling"
2047
+ })
2048
+ );
1980
2049
  }
1981
2050
  }
1982
2051
  }
@@ -1990,16 +2059,18 @@ var ErrorsVerifier = class {
1990
2059
  const block = catchClause.getBlock();
1991
2060
  const statements = block.getStatements();
1992
2061
  if (statements.length === 0) {
1993
- violations.push(createViolation({
1994
- decisionId,
1995
- constraintId: constraint.id,
1996
- type: constraint.type,
1997
- severity: constraint.severity,
1998
- message: "Empty catch block swallows error without handling",
1999
- file: filePath,
2000
- line: catchClause.getStartLineNumber(),
2001
- suggestion: "Add error handling, logging, or rethrow the error"
2002
- }));
2062
+ violations.push(
2063
+ createViolation({
2064
+ decisionId,
2065
+ constraintId: constraint.id,
2066
+ type: constraint.type,
2067
+ severity: constraint.severity,
2068
+ message: "Empty catch block swallows error without handling",
2069
+ file: filePath,
2070
+ line: catchClause.getStartLineNumber(),
2071
+ suggestion: "Add error handling, logging, or rethrow the error"
2072
+ })
2073
+ );
2003
2074
  }
2004
2075
  }
2005
2076
  }
@@ -2011,16 +2082,18 @@ var ErrorsVerifier = class {
2011
2082
  const expression = node.getExpression();
2012
2083
  const text = expression.getText();
2013
2084
  if (text === "console.error" || text === "console.log") {
2014
- violations.push(createViolation({
2015
- decisionId,
2016
- constraintId: constraint.id,
2017
- type: constraint.type,
2018
- severity: constraint.severity,
2019
- message: `Using ${text} instead of proper logging`,
2020
- file: filePath,
2021
- line: node.getStartLineNumber(),
2022
- suggestion: "Use a proper logging library"
2023
- }));
2085
+ violations.push(
2086
+ createViolation({
2087
+ decisionId,
2088
+ constraintId: constraint.id,
2089
+ type: constraint.type,
2090
+ severity: constraint.severity,
2091
+ message: `Using ${text} instead of proper logging`,
2092
+ file: filePath,
2093
+ line: node.getStartLineNumber(),
2094
+ suggestion: "Use a proper logging library"
2095
+ })
2096
+ );
2024
2097
  }
2025
2098
  }
2026
2099
  });
@@ -2051,16 +2124,18 @@ var RegexVerifier = class {
2051
2124
  while ((match = regex.exec(fileText)) !== null) {
2052
2125
  const beforeMatch = fileText.substring(0, match.index);
2053
2126
  const lineNumber = beforeMatch.split("\n").length;
2054
- violations.push(createViolation({
2055
- decisionId,
2056
- constraintId: constraint.id,
2057
- type: constraint.type,
2058
- severity: constraint.severity,
2059
- message: `Found forbidden pattern: "${match[0]}"`,
2060
- file: filePath,
2061
- line: lineNumber,
2062
- suggestion: `Remove or replace the pattern matching /${patternToForbid}/`
2063
- }));
2127
+ violations.push(
2128
+ createViolation({
2129
+ decisionId,
2130
+ constraintId: constraint.id,
2131
+ type: constraint.type,
2132
+ severity: constraint.severity,
2133
+ message: `Found forbidden pattern: "${match[0]}"`,
2134
+ file: filePath,
2135
+ line: lineNumber,
2136
+ suggestion: `Remove or replace the pattern matching /${patternToForbid}/`
2137
+ })
2138
+ );
2064
2139
  }
2065
2140
  } catch {
2066
2141
  }
@@ -2070,15 +2145,17 @@ var RegexVerifier = class {
2070
2145
  try {
2071
2146
  const regex = new RegExp(patternToRequire);
2072
2147
  if (!regex.test(fileText)) {
2073
- violations.push(createViolation({
2074
- decisionId,
2075
- constraintId: constraint.id,
2076
- type: constraint.type,
2077
- severity: constraint.severity,
2078
- message: `File does not contain required pattern: /${patternToRequire}/`,
2079
- file: filePath,
2080
- suggestion: `Add code matching /${patternToRequire}/`
2081
- }));
2148
+ violations.push(
2149
+ createViolation({
2150
+ decisionId,
2151
+ constraintId: constraint.id,
2152
+ type: constraint.type,
2153
+ severity: constraint.severity,
2154
+ message: `File does not contain required pattern: /${patternToRequire}/`,
2155
+ file: filePath,
2156
+ suggestion: `Add code matching /${patternToRequire}/`
2157
+ })
2158
+ );
2082
2159
  }
2083
2160
  } catch {
2084
2161
  }
@@ -2217,7 +2294,9 @@ function parseBannedDependency(rule) {
2217
2294
  return value.length > 0 ? value : null;
2218
2295
  }
2219
2296
  function parseLayerRule(rule) {
2220
- 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);
2297
+ const m = rule.match(
2298
+ /(\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
2299
+ );
2221
2300
  const fromLayer = m?.[1]?.toLowerCase();
2222
2301
  const toLayer = m?.[2]?.toLowerCase();
2223
2302
  if (!fromLayer || !toLayer) return null;
@@ -2250,16 +2329,18 @@ var DependencyVerifier = class {
2250
2329
  if (!scc.includes(current)) continue;
2251
2330
  const sorted = [...scc].sort();
2252
2331
  if (sorted[0] !== current) continue;
2253
- violations.push(createViolation({
2254
- decisionId,
2255
- constraintId: constraint.id,
2256
- type: constraint.type,
2257
- severity: constraint.severity,
2258
- message: `Circular dependency detected across: ${sorted.join(" -> ")}`,
2259
- file: filePath,
2260
- line: 1,
2261
- suggestion: "Break the cycle by extracting shared abstractions or reversing the dependency"
2262
- }));
2332
+ violations.push(
2333
+ createViolation({
2334
+ decisionId,
2335
+ constraintId: constraint.id,
2336
+ type: constraint.type,
2337
+ severity: constraint.severity,
2338
+ message: `Circular dependency detected across: ${sorted.join(" -> ")}`,
2339
+ file: filePath,
2340
+ line: 1,
2341
+ suggestion: "Break the cycle by extracting shared abstractions or reversing the dependency"
2342
+ })
2343
+ );
2263
2344
  }
2264
2345
  }
2265
2346
  const layerRule = parseLayerRule(rule);
@@ -2269,16 +2350,18 @@ var DependencyVerifier = class {
2269
2350
  const resolved = resolveToSourceFilePath(project, projectFilePath, moduleSpec);
2270
2351
  if (!resolved) continue;
2271
2352
  if (fileInLayer(resolved, layerRule.toLayer)) {
2272
- violations.push(createViolation({
2273
- decisionId,
2274
- constraintId: constraint.id,
2275
- type: constraint.type,
2276
- severity: constraint.severity,
2277
- message: `Layer violation: ${layerRule.fromLayer} depends on ${layerRule.toLayer} via import "${moduleSpec}"`,
2278
- file: filePath,
2279
- line: importDecl.getStartLineNumber(),
2280
- suggestion: `Refactor to remove dependency from ${layerRule.fromLayer} to ${layerRule.toLayer}`
2281
- }));
2353
+ violations.push(
2354
+ createViolation({
2355
+ decisionId,
2356
+ constraintId: constraint.id,
2357
+ type: constraint.type,
2358
+ severity: constraint.severity,
2359
+ message: `Layer violation: ${layerRule.fromLayer} depends on ${layerRule.toLayer} via import "${moduleSpec}"`,
2360
+ file: filePath,
2361
+ line: importDecl.getStartLineNumber(),
2362
+ suggestion: `Refactor to remove dependency from ${layerRule.fromLayer} to ${layerRule.toLayer}`
2363
+ })
2364
+ );
2282
2365
  }
2283
2366
  }
2284
2367
  }
@@ -2288,16 +2371,18 @@ var DependencyVerifier = class {
2288
2371
  for (const importDecl of sourceFile.getImportDeclarations()) {
2289
2372
  const moduleSpec = importDecl.getModuleSpecifierValue();
2290
2373
  if (moduleSpec.toLowerCase().includes(bannedLower)) {
2291
- violations.push(createViolation({
2292
- decisionId,
2293
- constraintId: constraint.id,
2294
- type: constraint.type,
2295
- severity: constraint.severity,
2296
- message: `Banned dependency import detected: "${moduleSpec}"`,
2297
- file: filePath,
2298
- line: importDecl.getStartLineNumber(),
2299
- suggestion: `Remove or replace dependency "${banned}"`
2300
- }));
2374
+ violations.push(
2375
+ createViolation({
2376
+ decisionId,
2377
+ constraintId: constraint.id,
2378
+ type: constraint.type,
2379
+ severity: constraint.severity,
2380
+ message: `Banned dependency import detected: "${moduleSpec}"`,
2381
+ file: filePath,
2382
+ line: importDecl.getStartLineNumber(),
2383
+ suggestion: `Remove or replace dependency "${banned}"`
2384
+ })
2385
+ );
2301
2386
  }
2302
2387
  }
2303
2388
  }
@@ -2308,16 +2393,18 @@ var DependencyVerifier = class {
2308
2393
  if (!moduleSpec.startsWith(".")) continue;
2309
2394
  const depth = (moduleSpec.match(/\.\.\//g) || []).length;
2310
2395
  if (depth > maxDepth) {
2311
- violations.push(createViolation({
2312
- decisionId,
2313
- constraintId: constraint.id,
2314
- type: constraint.type,
2315
- severity: constraint.severity,
2316
- message: `Import depth ${depth} exceeds maximum ${maxDepth}: "${moduleSpec}"`,
2317
- file: filePath,
2318
- line: importDecl.getStartLineNumber(),
2319
- suggestion: "Use a shallower module boundary (or introduce a public entrypoint for this dependency)"
2320
- }));
2396
+ violations.push(
2397
+ createViolation({
2398
+ decisionId,
2399
+ constraintId: constraint.id,
2400
+ type: constraint.type,
2401
+ severity: constraint.severity,
2402
+ message: `Import depth ${depth} exceeds maximum ${maxDepth}: "${moduleSpec}"`,
2403
+ file: filePath,
2404
+ line: importDecl.getStartLineNumber(),
2405
+ suggestion: "Use a shallower module boundary (or introduce a public entrypoint for this dependency)"
2406
+ })
2407
+ );
2321
2408
  }
2322
2409
  }
2323
2410
  }
@@ -2326,7 +2413,9 @@ var DependencyVerifier = class {
2326
2413
  };
2327
2414
 
2328
2415
  // src/verification/verifiers/complexity.ts
2329
- import { Node as Node4 } from "ts-morph";
2416
+ import {
2417
+ Node as Node4
2418
+ } from "ts-morph";
2330
2419
  import { SyntaxKind as SyntaxKind2 } from "ts-morph";
2331
2420
  function parseLimit(rule, pattern) {
2332
2421
  const m = rule.match(pattern);
@@ -2410,16 +2499,18 @@ var ComplexityVerifier = class {
2410
2499
  if (maxLines !== null) {
2411
2500
  const lineCount = getFileLineCount(sourceFile.getFullText());
2412
2501
  if (lineCount > maxLines) {
2413
- violations.push(createViolation({
2414
- decisionId,
2415
- constraintId: constraint.id,
2416
- type: constraint.type,
2417
- severity: constraint.severity,
2418
- message: `File has ${lineCount} lines which exceeds maximum ${maxLines}`,
2419
- file: filePath,
2420
- line: 1,
2421
- suggestion: "Split the file into smaller modules"
2422
- }));
2502
+ violations.push(
2503
+ createViolation({
2504
+ decisionId,
2505
+ constraintId: constraint.id,
2506
+ type: constraint.type,
2507
+ severity: constraint.severity,
2508
+ message: `File has ${lineCount} lines which exceeds maximum ${maxLines}`,
2509
+ file: filePath,
2510
+ line: 1,
2511
+ suggestion: "Split the file into smaller modules"
2512
+ })
2513
+ );
2423
2514
  }
2424
2515
  }
2425
2516
  const functionLikes = [
@@ -2433,46 +2524,52 @@ var ComplexityVerifier = class {
2433
2524
  if (maxComplexity !== null) {
2434
2525
  const complexity = calculateCyclomaticComplexity(fn);
2435
2526
  if (complexity > maxComplexity) {
2436
- violations.push(createViolation({
2437
- decisionId,
2438
- constraintId: constraint.id,
2439
- type: constraint.type,
2440
- severity: constraint.severity,
2441
- message: `Function ${fnName} has cyclomatic complexity ${complexity} which exceeds maximum ${maxComplexity}`,
2442
- file: filePath,
2443
- line: fn.getStartLineNumber(),
2444
- suggestion: "Refactor to reduce branching or extract smaller functions"
2445
- }));
2527
+ violations.push(
2528
+ createViolation({
2529
+ decisionId,
2530
+ constraintId: constraint.id,
2531
+ type: constraint.type,
2532
+ severity: constraint.severity,
2533
+ message: `Function ${fnName} has cyclomatic complexity ${complexity} which exceeds maximum ${maxComplexity}`,
2534
+ file: filePath,
2535
+ line: fn.getStartLineNumber(),
2536
+ suggestion: "Refactor to reduce branching or extract smaller functions"
2537
+ })
2538
+ );
2446
2539
  }
2447
2540
  }
2448
2541
  if (maxParams !== null) {
2449
2542
  const paramCount = fn.getParameters().length;
2450
2543
  if (paramCount > maxParams) {
2451
- violations.push(createViolation({
2452
- decisionId,
2453
- constraintId: constraint.id,
2454
- type: constraint.type,
2455
- severity: constraint.severity,
2456
- message: `Function ${fnName} has ${paramCount} parameters which exceeds maximum ${maxParams}`,
2457
- file: filePath,
2458
- line: fn.getStartLineNumber(),
2459
- suggestion: "Consider grouping parameters into an options object"
2460
- }));
2544
+ violations.push(
2545
+ createViolation({
2546
+ decisionId,
2547
+ constraintId: constraint.id,
2548
+ type: constraint.type,
2549
+ severity: constraint.severity,
2550
+ message: `Function ${fnName} has ${paramCount} parameters which exceeds maximum ${maxParams}`,
2551
+ file: filePath,
2552
+ line: fn.getStartLineNumber(),
2553
+ suggestion: "Consider grouping parameters into an options object"
2554
+ })
2555
+ );
2461
2556
  }
2462
2557
  }
2463
2558
  if (maxNesting !== null) {
2464
2559
  const depth = maxNestingDepth(fn);
2465
2560
  if (depth > maxNesting) {
2466
- violations.push(createViolation({
2467
- decisionId,
2468
- constraintId: constraint.id,
2469
- type: constraint.type,
2470
- severity: constraint.severity,
2471
- message: `Function ${fnName} has nesting depth ${depth} which exceeds maximum ${maxNesting}`,
2472
- file: filePath,
2473
- line: fn.getStartLineNumber(),
2474
- suggestion: "Reduce nesting by using early returns or extracting functions"
2475
- }));
2561
+ violations.push(
2562
+ createViolation({
2563
+ decisionId,
2564
+ constraintId: constraint.id,
2565
+ type: constraint.type,
2566
+ severity: constraint.severity,
2567
+ message: `Function ${fnName} has nesting depth ${depth} which exceeds maximum ${maxNesting}`,
2568
+ file: filePath,
2569
+ line: fn.getStartLineNumber(),
2570
+ suggestion: "Reduce nesting by using early returns or extracting functions"
2571
+ })
2572
+ );
2476
2573
  }
2477
2574
  }
2478
2575
  }
@@ -2508,48 +2605,54 @@ var SecurityVerifier = class {
2508
2605
  if (!init || !isStringLiteralLike(init)) continue;
2509
2606
  const value = init.getText().slice(1, -1);
2510
2607
  if (value.length === 0) continue;
2511
- violations.push(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
- }));
2608
+ violations.push(
2609
+ createViolation({
2610
+ decisionId,
2611
+ constraintId: constraint.id,
2612
+ type: constraint.type,
2613
+ severity: constraint.severity,
2614
+ message: `Possible hardcoded secret in variable "${name}"`,
2615
+ file: filePath,
2616
+ line: vd.getStartLineNumber(),
2617
+ suggestion: "Move secrets to environment variables or a secret manager"
2618
+ })
2619
+ );
2521
2620
  }
2522
2621
  for (const pa of sourceFile.getDescendantsOfKind(SyntaxKind3.PropertyAssignment)) {
2523
2622
  const propName = pa.getNameNode().getText();
2524
2623
  if (!SECRET_NAME_RE.test(propName)) continue;
2525
2624
  const init = pa.getInitializer();
2526
2625
  if (!init || !isStringLiteralLike(init)) continue;
2527
- violations.push(createViolation({
2528
- decisionId,
2529
- constraintId: constraint.id,
2530
- type: constraint.type,
2531
- severity: constraint.severity,
2532
- message: `Possible hardcoded secret in object property ${propName}`,
2533
- file: filePath,
2534
- line: pa.getStartLineNumber(),
2535
- suggestion: "Move secrets to environment variables or a secret manager"
2536
- }));
2626
+ violations.push(
2627
+ createViolation({
2628
+ decisionId,
2629
+ constraintId: constraint.id,
2630
+ type: constraint.type,
2631
+ severity: constraint.severity,
2632
+ message: `Possible hardcoded secret in object property ${propName}`,
2633
+ file: filePath,
2634
+ line: pa.getStartLineNumber(),
2635
+ suggestion: "Move secrets to environment variables or a secret manager"
2636
+ })
2637
+ );
2537
2638
  }
2538
2639
  }
2539
2640
  if (checkEval) {
2540
2641
  for (const call of sourceFile.getDescendantsOfKind(SyntaxKind3.CallExpression)) {
2541
2642
  const exprText = call.getExpression().getText();
2542
2643
  if (exprText === "eval" || exprText === "Function") {
2543
- violations.push(createViolation({
2544
- decisionId,
2545
- constraintId: constraint.id,
2546
- type: constraint.type,
2547
- severity: constraint.severity,
2548
- message: `Unsafe dynamic code execution via ${exprText}()`,
2549
- file: filePath,
2550
- line: call.getStartLineNumber(),
2551
- suggestion: "Avoid eval/Function; use safer alternatives"
2552
- }));
2644
+ violations.push(
2645
+ createViolation({
2646
+ decisionId,
2647
+ constraintId: constraint.id,
2648
+ type: constraint.type,
2649
+ severity: constraint.severity,
2650
+ message: `Unsafe dynamic code execution via ${exprText}()`,
2651
+ file: filePath,
2652
+ line: call.getStartLineNumber(),
2653
+ suggestion: "Avoid eval/Function; use safer alternatives"
2654
+ })
2655
+ );
2553
2656
  }
2554
2657
  }
2555
2658
  }
@@ -2559,29 +2662,33 @@ var SecurityVerifier = class {
2559
2662
  const propertyAccess = left.asKind(SyntaxKind3.PropertyAccessExpression);
2560
2663
  if (!propertyAccess) continue;
2561
2664
  if (propertyAccess.getName() === "innerHTML") {
2562
- violations.push(createViolation({
2665
+ violations.push(
2666
+ createViolation({
2667
+ decisionId,
2668
+ constraintId: constraint.id,
2669
+ type: constraint.type,
2670
+ severity: constraint.severity,
2671
+ message: "Potential XSS: assignment to innerHTML",
2672
+ file: filePath,
2673
+ line: bin.getStartLineNumber(),
2674
+ suggestion: "Prefer textContent or a safe templating/escaping strategy"
2675
+ })
2676
+ );
2677
+ }
2678
+ }
2679
+ if (sourceFile.getFullText().includes("dangerouslySetInnerHTML")) {
2680
+ violations.push(
2681
+ createViolation({
2563
2682
  decisionId,
2564
2683
  constraintId: constraint.id,
2565
2684
  type: constraint.type,
2566
2685
  severity: constraint.severity,
2567
- message: "Potential XSS: assignment to innerHTML",
2686
+ message: "Potential XSS: usage of dangerouslySetInnerHTML",
2568
2687
  file: filePath,
2569
- line: bin.getStartLineNumber(),
2570
- suggestion: "Prefer textContent or a safe templating/escaping strategy"
2571
- }));
2572
- }
2573
- }
2574
- if (sourceFile.getFullText().includes("dangerouslySetInnerHTML")) {
2575
- violations.push(createViolation({
2576
- decisionId,
2577
- constraintId: constraint.id,
2578
- type: constraint.type,
2579
- severity: constraint.severity,
2580
- message: "Potential XSS: usage of dangerouslySetInnerHTML",
2581
- file: filePath,
2582
- line: 1,
2583
- suggestion: "Avoid dangerouslySetInnerHTML or ensure content is sanitized"
2584
- }));
2688
+ line: 1,
2689
+ suggestion: "Avoid dangerouslySetInnerHTML or ensure content is sanitized"
2690
+ })
2691
+ );
2585
2692
  }
2586
2693
  }
2587
2694
  if (checkSql) {
@@ -2600,31 +2707,35 @@ var SecurityVerifier = class {
2600
2707
  if (!text.includes("select") && !text.includes("insert") && !text.includes("update") && !text.includes("delete")) {
2601
2708
  continue;
2602
2709
  }
2603
- violations.push(createViolation({
2604
- decisionId,
2605
- constraintId: constraint.id,
2606
- type: constraint.type,
2607
- severity: constraint.severity,
2608
- message: "Potential SQL injection: dynamically constructed SQL query",
2609
- file: filePath,
2610
- line: call.getStartLineNumber(),
2611
- suggestion: "Use parameterized queries / prepared statements"
2612
- }));
2710
+ violations.push(
2711
+ createViolation({
2712
+ decisionId,
2713
+ constraintId: constraint.id,
2714
+ type: constraint.type,
2715
+ severity: constraint.severity,
2716
+ message: "Potential SQL injection: dynamically constructed SQL query",
2717
+ file: filePath,
2718
+ line: call.getStartLineNumber(),
2719
+ suggestion: "Use parameterized queries / prepared statements"
2720
+ })
2721
+ );
2613
2722
  }
2614
2723
  }
2615
2724
  if (checkProto) {
2616
2725
  const text = sourceFile.getFullText();
2617
2726
  if (text.includes("__proto__") || text.includes("constructor.prototype")) {
2618
- violations.push(createViolation({
2619
- decisionId,
2620
- constraintId: constraint.id,
2621
- type: constraint.type,
2622
- severity: constraint.severity,
2623
- message: "Potential prototype pollution pattern detected",
2624
- file: filePath,
2625
- line: 1,
2626
- suggestion: "Avoid writing to __proto__/prototype; validate object keys"
2627
- }));
2727
+ violations.push(
2728
+ createViolation({
2729
+ decisionId,
2730
+ constraintId: constraint.id,
2731
+ type: constraint.type,
2732
+ severity: constraint.severity,
2733
+ message: "Potential prototype pollution pattern detected",
2734
+ file: filePath,
2735
+ line: 1,
2736
+ suggestion: "Avoid writing to __proto__/prototype; validate object keys"
2737
+ })
2738
+ );
2628
2739
  }
2629
2740
  }
2630
2741
  return violations;
@@ -2664,16 +2775,18 @@ var ApiVerifier = class {
2664
2775
  const pathValue = stringLiteral.getLiteralValue();
2665
2776
  if (typeof pathValue !== "string") continue;
2666
2777
  if (!isKebabPath(pathValue)) {
2667
- violations.push(createViolation({
2668
- decisionId,
2669
- constraintId: constraint.id,
2670
- type: constraint.type,
2671
- severity: constraint.severity,
2672
- message: `Endpoint path "${pathValue}" is not kebab-case`,
2673
- file: filePath,
2674
- line: call.getStartLineNumber(),
2675
- suggestion: "Use lowercase and hyphens in static path segments (e.g., /user-settings)"
2676
- }));
2778
+ violations.push(
2779
+ createViolation({
2780
+ decisionId,
2781
+ constraintId: constraint.id,
2782
+ type: constraint.type,
2783
+ severity: constraint.severity,
2784
+ message: `Endpoint path "${pathValue}" is not kebab-case`,
2785
+ file: filePath,
2786
+ line: call.getStartLineNumber(),
2787
+ suggestion: "Use lowercase and hyphens in static path segments (e.g., /user-settings)"
2788
+ })
2789
+ );
2677
2790
  }
2678
2791
  }
2679
2792
  return violations;
@@ -2854,7 +2967,10 @@ var PluginLoader = class {
2854
2967
  return { success: false, error: `Plugin ${id} requires params but none were provided` };
2855
2968
  }
2856
2969
  if (typeof plugin.paramsSchema !== "object" || !plugin.paramsSchema || !("parse" in plugin.paramsSchema)) {
2857
- return { success: false, error: `Plugin ${id} has invalid paramsSchema (must be a Zod schema)` };
2970
+ return {
2971
+ success: false,
2972
+ error: `Plugin ${id} has invalid paramsSchema (must be a Zod schema)`
2973
+ };
2858
2974
  }
2859
2975
  const schema = plugin.paramsSchema;
2860
2976
  if (schema.safeParse) {
@@ -3299,12 +3415,15 @@ var VerificationEngine = class {
3299
3415
  );
3300
3416
  if (!verifier) {
3301
3417
  const requestedVerifier = constraint.check?.verifier || constraint.verifier || "auto-detected";
3302
- this.logger.warn({
3303
- decisionId: decision.metadata.id,
3304
- constraintId: constraint.id,
3305
- requestedVerifier,
3306
- availableVerifiers: getVerifierIds()
3307
- }, "No verifier found for constraint");
3418
+ this.logger.warn(
3419
+ {
3420
+ decisionId: decision.metadata.id,
3421
+ constraintId: constraint.id,
3422
+ requestedVerifier,
3423
+ availableVerifiers: getVerifierIds()
3424
+ },
3425
+ "No verifier found for constraint"
3426
+ );
3308
3427
  warnings.push({
3309
3428
  type: "missing_verifier",
3310
3429
  message: `No verifier found for constraint (requested: ${requestedVerifier})`,
@@ -3350,7 +3469,10 @@ var VerificationEngine = class {
3350
3469
  }
3351
3470
  if (constraint.check?.verifier && constraint.check?.params) {
3352
3471
  const pluginLoader2 = getPluginLoader();
3353
- const validationResult = pluginLoader2.validateParams(constraint.check.verifier, constraint.check.params);
3472
+ const validationResult = pluginLoader2.validateParams(
3473
+ constraint.check.verifier,
3474
+ constraint.check.params
3475
+ );
3354
3476
  if (!validationResult.success) {
3355
3477
  warnings.push({
3356
3478
  type: "invalid_params",
@@ -3410,14 +3532,17 @@ var VerificationEngine = class {
3410
3532
  } catch (error) {
3411
3533
  const errorMessage = error instanceof Error ? error.message : String(error);
3412
3534
  const errorStack = error instanceof Error ? error.stack : void 0;
3413
- this.logger.error({
3414
- verifierId: verifier.id,
3415
- filePath,
3416
- decisionId: decision.metadata.id,
3417
- constraintId: constraint.id,
3418
- error: errorMessage,
3419
- stack: errorStack
3420
- }, "Verifier execution failed");
3535
+ this.logger.error(
3536
+ {
3537
+ verifierId: verifier.id,
3538
+ filePath,
3539
+ decisionId: decision.metadata.id,
3540
+ constraintId: constraint.id,
3541
+ error: errorMessage,
3542
+ stack: errorStack
3543
+ },
3544
+ "Verifier execution failed"
3545
+ );
3421
3546
  errors.push({
3422
3547
  type: "verifier_exception",
3423
3548
  message: `Verifier '${verifier.id}' failed: ${errorMessage}`,
@@ -3538,7 +3663,9 @@ var AutofixEngine = class {
3538
3663
  continue;
3539
3664
  }
3540
3665
  if (options.interactive) {
3541
- const ok = await confirmFix(`Apply fix: ${fix.description} (${filePath}:${violation.line ?? 1})?`);
3666
+ const ok = await confirmFix(
3667
+ `Apply fix: ${fix.description} (${filePath}:${violation.line ?? 1})?`
3668
+ );
3542
3669
  if (!ok) {
3543
3670
  skippedViolations++;
3544
3671
  continue;
@@ -3569,7 +3696,11 @@ import { resolve } from "path";
3569
3696
  var execFileAsync = promisify(execFile);
3570
3697
  async function getChangedFiles(cwd) {
3571
3698
  try {
3572
- const { stdout: stdout2 } = await execFileAsync("git", ["diff", "--name-only", "--diff-filter=AM", "HEAD"], { cwd });
3699
+ const { stdout: stdout2 } = await execFileAsync(
3700
+ "git",
3701
+ ["diff", "--name-only", "--diff-filter=AM", "HEAD"],
3702
+ { cwd }
3703
+ );
3573
3704
  const rel = stdout2.trim().split("\n").map((s) => s.trim()).filter(Boolean);
3574
3705
  const abs = [];
3575
3706
  for (const file of rel) {
@@ -3652,7 +3783,10 @@ var ExplainReporter = class {
3652
3783
  };
3653
3784
 
3654
3785
  // src/cli/commands/verify.ts
3655
- var verifyCommand = new Command3("verify").description("Verify code compliance against decisions").option("-l, --level <level>", "Verification level (commit, pr, full)", "full").option("-f, --files <patterns>", "Comma-separated file patterns to check").option("-d, --decisions <ids>", "Comma-separated decision IDs to check").option("-s, --severity <levels>", "Comma-separated severity levels (critical, high, medium, low)").option("--json", "Output as JSON").option("--incremental", "Only verify changed files (git diff --name-only --diff-filter=AM HEAD)").option("--explain", "Show detailed explanation of verification process").option("--fix", "Apply auto-fixes for supported violations").option("--dry-run", "Show what would be fixed without applying (requires --fix)").option("--interactive", "Confirm each fix interactively (requires --fix)").action(async (options) => {
3786
+ var verifyCommand = new Command3("verify").description("Verify code compliance against decisions").option("-l, --level <level>", "Verification level (commit, pr, full)", "full").option("-f, --files <patterns>", "Comma-separated file patterns to check").option("-d, --decisions <ids>", "Comma-separated decision IDs to check").option(
3787
+ "-s, --severity <levels>",
3788
+ "Comma-separated severity levels (critical, high, medium, low)"
3789
+ ).option("--json", "Output as JSON").option("--incremental", "Only verify changed files (git diff --name-only --diff-filter=AM HEAD)").option("--explain", "Show detailed explanation of verification process").option("--fix", "Apply auto-fixes for supported violations").option("--dry-run", "Show what would be fixed without applying (requires --fix)").option("--interactive", "Confirm each fix interactively (requires --fix)").action(async (options) => {
3656
3790
  const cwd = process.cwd();
3657
3791
  if (!await pathExists(getSpecBridgeDir(cwd))) {
3658
3792
  throw new NotInitializedError();
@@ -3772,9 +3906,7 @@ function printResult(result, level) {
3772
3906
  const typeIcon = getTypeIcon(v.type);
3773
3907
  const severityColor = getSeverityColor(v.severity);
3774
3908
  const location = v.line ? `:${v.line}${v.column ? `:${v.column}` : ""}` : "";
3775
- console.log(
3776
- ` ${typeIcon} ${severityColor(`[${v.severity}]`)} ${v.message}`
3777
- );
3909
+ console.log(` ${typeIcon} ${severityColor(`[${v.severity}]`)} ${v.message}`);
3778
3910
  console.log(chalk4.dim(` ${v.decisionId}/${v.constraintId}${location}`));
3779
3911
  if (v.suggestion) {
3780
3912
  console.log(chalk4.cyan(` Suggestion: ${v.suggestion}`));
@@ -3787,7 +3919,9 @@ function printResult(result, level) {
3787
3919
  const mediumCount = result.violations.filter((v) => v.severity === "medium").length;
3788
3920
  const lowCount = result.violations.filter((v) => v.severity === "low").length;
3789
3921
  console.log(chalk4.bold("Summary:"));
3790
- console.log(` Files: ${result.checked} checked, ${result.passed} passed, ${result.failed} failed`);
3922
+ console.log(
3923
+ ` Files: ${result.checked} checked, ${result.passed} passed, ${result.failed} failed`
3924
+ );
3791
3925
  const violationParts = [];
3792
3926
  if (criticalCount > 0) violationParts.push(chalk4.red(`${criticalCount} critical`));
3793
3927
  if (highCount > 0) violationParts.push(chalk4.yellow(`${highCount} high`));
@@ -3881,26 +4015,28 @@ var listDecisions = new Command4("list").description("List all architectural dec
3881
4015
  (decision.metadata.tags || []).join(", ") || "-"
3882
4016
  ]);
3883
4017
  }
3884
- console.log(table(data, {
3885
- border: {
3886
- topBody: "",
3887
- topJoin: "",
3888
- topLeft: "",
3889
- topRight: "",
3890
- bottomBody: "",
3891
- bottomJoin: "",
3892
- bottomLeft: "",
3893
- bottomRight: "",
3894
- bodyLeft: "",
3895
- bodyRight: "",
3896
- bodyJoin: " ",
3897
- joinBody: "",
3898
- joinLeft: "",
3899
- joinRight: "",
3900
- joinJoin: ""
3901
- },
3902
- drawHorizontalLine: (index) => index === 1
3903
- }));
4018
+ console.log(
4019
+ table(data, {
4020
+ border: {
4021
+ topBody: "",
4022
+ topJoin: "",
4023
+ topLeft: "",
4024
+ topRight: "",
4025
+ bottomBody: "",
4026
+ bottomJoin: "",
4027
+ bottomLeft: "",
4028
+ bottomRight: "",
4029
+ bodyLeft: "",
4030
+ bodyRight: "",
4031
+ bodyJoin: " ",
4032
+ joinBody: "",
4033
+ joinLeft: "",
4034
+ joinRight: "",
4035
+ joinJoin: ""
4036
+ },
4037
+ drawHorizontalLine: (index) => index === 1
4038
+ })
4039
+ );
3904
4040
  console.log(chalk5.dim(`Total: ${decisions.length} decision(s)`));
3905
4041
  });
3906
4042
  function getStatusColor(status) {
@@ -4131,13 +4267,23 @@ var validateDecisions = new Command6("validate").description("Validate decision
4131
4267
  import { Command as Command7 } from "commander";
4132
4268
  import chalk8 from "chalk";
4133
4269
  import { join as join7 } from "path";
4134
- var createDecision = new Command7("create").description("Create a new decision file").argument("<id>", "Decision ID (e.g., auth-001)").requiredOption("-t, --title <title>", "Decision title").requiredOption("-s, --summary <summary>", "One-sentence summary").option("--type <type>", "Default constraint type (invariant, convention, guideline)", "convention").option("--severity <severity>", "Default constraint severity (critical, high, medium, low)", "medium").option("--scope <scope>", "Default constraint scope (glob pattern)", "src/**/*.ts").option("-o, --owner <owner>", "Owner name", "team").action(async (id, options) => {
4270
+ var createDecision = new Command7("create").description("Create a new decision file").argument("<id>", "Decision ID (e.g., auth-001)").requiredOption("-t, --title <title>", "Decision title").requiredOption("-s, --summary <summary>", "One-sentence summary").option(
4271
+ "--type <type>",
4272
+ "Default constraint type (invariant, convention, guideline)",
4273
+ "convention"
4274
+ ).option(
4275
+ "--severity <severity>",
4276
+ "Default constraint severity (critical, high, medium, low)",
4277
+ "medium"
4278
+ ).option("--scope <scope>", "Default constraint scope (glob pattern)", "src/**/*.ts").option("-o, --owner <owner>", "Owner name", "team").action(async (id, options) => {
4135
4279
  const cwd = process.cwd();
4136
4280
  if (!await pathExists(getSpecBridgeDir(cwd))) {
4137
4281
  throw new NotInitializedError();
4138
4282
  }
4139
4283
  if (!/^[a-z0-9-]+$/.test(id)) {
4140
- console.error(chalk8.red("Error: Decision ID must be lowercase alphanumeric with hyphens only."));
4284
+ console.error(
4285
+ chalk8.red("Error: Decision ID must be lowercase alphanumeric with hyphens only.")
4286
+ );
4141
4287
  process.exit(1);
4142
4288
  }
4143
4289
  const decisionsDir = getDecisionsDir(cwd);
@@ -4185,7 +4331,9 @@ var createDecision = new Command7("create").description("Create a new decision f
4185
4331
  console.log(` 1. Edit the file to add rationale, context, and consequences`);
4186
4332
  console.log(` 2. Define constraints with appropriate scopes`);
4187
4333
  console.log(` 3. Run ${chalk8.bold("specbridge decision validate")} to check syntax`);
4188
- console.log(` 4. Change status from ${chalk8.yellow("draft")} to ${chalk8.green("active")} when ready`);
4334
+ console.log(
4335
+ ` 4. Change status from ${chalk8.yellow("draft")} to ${chalk8.green("active")} when ready`
4336
+ );
4189
4337
  });
4190
4338
 
4191
4339
  // src/cli/commands/decision/index.ts
@@ -4227,12 +4375,14 @@ function createHookCommand() {
4227
4375
  console.log("");
4228
4376
  console.log(chalk9.cyan("Add this to your lefthook.yml:"));
4229
4377
  console.log("");
4230
- console.log(chalk9.dim(`pre-commit:
4378
+ console.log(
4379
+ chalk9.dim(`pre-commit:
4231
4380
  commands:
4232
4381
  specbridge:
4233
4382
  glob: "*.{ts,tsx}"
4234
4383
  run: npx specbridge hook run --level commit --files {staged_files}
4235
- `));
4384
+ `)
4385
+ );
4236
4386
  return;
4237
4387
  } else {
4238
4388
  if (await pathExists(join8(cwd, ".husky"))) {
@@ -4244,12 +4394,14 @@ function createHookCommand() {
4244
4394
  console.log("");
4245
4395
  console.log(chalk9.cyan("Add this to your lefthook.yml:"));
4246
4396
  console.log("");
4247
- console.log(chalk9.dim(`pre-commit:
4397
+ console.log(
4398
+ chalk9.dim(`pre-commit:
4248
4399
  commands:
4249
4400
  specbridge:
4250
4401
  glob: "*.{ts,tsx}"
4251
4402
  run: npx specbridge hook run --level commit --files {staged_files}
4252
- `));
4403
+ `)
4404
+ );
4253
4405
  return;
4254
4406
  } else {
4255
4407
  hookPath = join8(cwd, ".git", "hooks", "pre-commit");
@@ -4291,7 +4443,11 @@ function createHookCommand() {
4291
4443
  const { promisify: promisify2 } = await import("util");
4292
4444
  const execFileAsync2 = promisify2(execFile2);
4293
4445
  try {
4294
- const { stdout: stdout2 } = await execFileAsync2("git", ["diff", "--cached", "--name-only", "--diff-filter=AM"], { cwd });
4446
+ const { stdout: stdout2 } = await execFileAsync2(
4447
+ "git",
4448
+ ["diff", "--cached", "--name-only", "--diff-filter=AM"],
4449
+ { cwd }
4450
+ );
4295
4451
  files = stdout2.trim().split("\n").map((s) => s.trim()).filter(Boolean).filter((f) => /\.(ts|tsx|js|jsx)$/.test(f));
4296
4452
  } catch {
4297
4453
  files = [];
@@ -4393,10 +4549,7 @@ async function generateReport(config, options = {}) {
4393
4549
  medium: decisionViolations.filter((v) => v.severity === "medium").length,
4394
4550
  low: decisionViolations.filter((v) => v.severity === "low").length
4395
4551
  };
4396
- weightedScore = decisionViolations.reduce(
4397
- (score, v) => score + weights[v.severity],
4398
- 0
4399
- );
4552
+ weightedScore = decisionViolations.reduce((score, v) => score + weights[v.severity], 0);
4400
4553
  compliance = Math.max(0, 100 - weightedScore);
4401
4554
  if (decisionViolations.length > 0 && constraintCount > 0) {
4402
4555
  const violationRate = decisionViolations.length / constraintCount;
@@ -4455,10 +4608,14 @@ function formatConsoleReport(report) {
4455
4608
  lines.push("");
4456
4609
  const complianceColor = getComplianceColor(report.summary.compliance);
4457
4610
  lines.push(chalk10.bold("Overall Compliance"));
4458
- lines.push(` ${complianceColor(formatComplianceBar(report.summary.compliance))} ${complianceColor(`${report.summary.compliance}%`)}`);
4611
+ lines.push(
4612
+ ` ${complianceColor(formatComplianceBar(report.summary.compliance))} ${complianceColor(`${report.summary.compliance}%`)}`
4613
+ );
4459
4614
  lines.push("");
4460
4615
  lines.push(chalk10.bold("Summary"));
4461
- lines.push(` Decisions: ${report.summary.activeDecisions} active / ${report.summary.totalDecisions} total`);
4616
+ lines.push(
4617
+ ` Decisions: ${report.summary.activeDecisions} active / ${report.summary.totalDecisions} total`
4618
+ );
4462
4619
  lines.push(` Constraints: ${report.summary.totalConstraints}`);
4463
4620
  lines.push("");
4464
4621
  lines.push(chalk10.bold("Violations"));
@@ -4575,7 +4732,9 @@ function formatMarkdownReport(report) {
4575
4732
  lines.push("");
4576
4733
  lines.push("## Summary");
4577
4734
  lines.push("");
4578
- lines.push(`- **Active Decisions:** ${report.summary.activeDecisions} / ${report.summary.totalDecisions}`);
4735
+ lines.push(
4736
+ `- **Active Decisions:** ${report.summary.activeDecisions} / ${report.summary.totalDecisions}`
4737
+ );
4579
4738
  lines.push(`- **Total Constraints:** ${report.summary.totalConstraints}`);
4580
4739
  lines.push("");
4581
4740
  lines.push("### Violations");
@@ -4739,9 +4898,7 @@ var ReportStorage = class {
4739
4898
  async function detectDrift(current, previous) {
4740
4899
  const byDecision = [];
4741
4900
  for (const currDecision of current.byDecision) {
4742
- const prevDecision = previous.byDecision.find(
4743
- (d) => d.decisionId === currDecision.decisionId
4744
- );
4901
+ const prevDecision = previous.byDecision.find((d) => d.decisionId === currDecision.decisionId);
4745
4902
  if (!prevDecision) {
4746
4903
  byDecision.push({
4747
4904
  decisionId: currDecision.decisionId,
@@ -4934,12 +5091,22 @@ var reportCommand = new Command10("report").description("Generate compliance rep
4934
5091
  const days = parseInt(options.days || "30", 10);
4935
5092
  const history = await storage.loadHistory(days);
4936
5093
  if (history.length < 2) {
4937
- console.log(chalk11.yellow(`Not enough data for trend analysis. Found ${history.length} report(s), need at least 2.`));
5094
+ console.log(
5095
+ chalk11.yellow(
5096
+ `Not enough data for trend analysis. Found ${history.length} report(s), need at least 2.`
5097
+ )
5098
+ );
4938
5099
  } else {
4939
5100
  const trend = await analyzeTrend(history);
4940
- console.log(chalk11.bold(`Period: ${trend.period.start} to ${trend.period.end} (${trend.period.days} days)`));
4941
- console.log(`
4942
- Overall Compliance: ${trend.overall.startCompliance}% \u2192 ${trend.overall.endCompliance}% (${trend.overall.change > 0 ? "+" : ""}${trend.overall.change.toFixed(1)}%)`);
5101
+ console.log(
5102
+ chalk11.bold(
5103
+ `Period: ${trend.period.start} to ${trend.period.end} (${trend.period.days} days)`
5104
+ )
5105
+ );
5106
+ console.log(
5107
+ `
5108
+ Overall Compliance: ${trend.overall.startCompliance}% \u2192 ${trend.overall.endCompliance}% (${trend.overall.change > 0 ? "+" : ""}${trend.overall.change.toFixed(1)}%)`
5109
+ );
4943
5110
  const trendEmoji = trend.overall.trend === "improving" ? "\u{1F4C8}" : trend.overall.trend === "degrading" ? "\u{1F4C9}" : "\u27A1\uFE0F";
4944
5111
  const trendColor = trend.overall.trend === "improving" ? chalk11.green : trend.overall.trend === "degrading" ? chalk11.red : chalk11.yellow;
4945
5112
  console.log(trendColor(`${trendEmoji} Trend: ${trend.overall.trend.toUpperCase()}`));
@@ -4947,14 +5114,18 @@ Overall Compliance: ${trend.overall.startCompliance}% \u2192 ${trend.overall.end
4947
5114
  if (degrading.length > 0) {
4948
5115
  console.log(chalk11.red("\n\u26A0\uFE0F Most Degraded Decisions:"));
4949
5116
  degrading.forEach((d) => {
4950
- console.log(` \u2022 ${d.title}: ${d.startCompliance}% \u2192 ${d.endCompliance}% (${d.change.toFixed(1)}%)`);
5117
+ console.log(
5118
+ ` \u2022 ${d.title}: ${d.startCompliance}% \u2192 ${d.endCompliance}% (${d.change.toFixed(1)}%)`
5119
+ );
4951
5120
  });
4952
5121
  }
4953
5122
  const improving = trend.decisions.filter((d) => d.trend === "improving").slice(0, 3);
4954
5123
  if (improving.length > 0) {
4955
5124
  console.log(chalk11.green("\n\u2705 Most Improved Decisions:"));
4956
5125
  improving.forEach((d) => {
4957
- console.log(` \u2022 ${d.title}: ${d.startCompliance}% \u2192 ${d.endCompliance}% (+${d.change.toFixed(1)}%)`);
5126
+ console.log(
5127
+ ` \u2022 ${d.title}: ${d.startCompliance}% \u2192 ${d.endCompliance}% (+${d.change.toFixed(1)}%)`
5128
+ );
4958
5129
  });
4959
5130
  }
4960
5131
  }
@@ -4973,9 +5144,13 @@ Overall Compliance: ${trend.overall.startCompliance}% \u2192 ${trend.overall.end
4973
5144
  return;
4974
5145
  }
4975
5146
  const drift = await detectDrift(currentEntry.report, previousEntry.report);
4976
- console.log(chalk11.bold(`Comparing: ${previousEntry.timestamp} vs ${currentEntry.timestamp}`));
4977
- console.log(`
4978
- Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceChange.toFixed(1)}%`);
5147
+ console.log(
5148
+ chalk11.bold(`Comparing: ${previousEntry.timestamp} vs ${currentEntry.timestamp}`)
5149
+ );
5150
+ console.log(
5151
+ `
5152
+ Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceChange.toFixed(1)}%`
5153
+ );
4979
5154
  const driftEmoji = drift.trend === "improving" ? "\u{1F4C8}" : drift.trend === "degrading" ? "\u{1F4C9}" : "\u27A1\uFE0F";
4980
5155
  const driftColor = drift.trend === "improving" ? chalk11.green : drift.trend === "degrading" ? chalk11.red : chalk11.yellow;
4981
5156
  console.log(driftColor(`${driftEmoji} Overall Trend: ${drift.trend.toUpperCase()}`));
@@ -4996,8 +5171,10 @@ Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceCha
4996
5171
  }
4997
5172
  }
4998
5173
  if (drift.summary.fixedViolations.total > 0) {
4999
- console.log(chalk11.green(`
5000
- \u2705 Fixed Violations: ${drift.summary.fixedViolations.total}`));
5174
+ console.log(
5175
+ chalk11.green(`
5176
+ \u2705 Fixed Violations: ${drift.summary.fixedViolations.total}`)
5177
+ );
5001
5178
  if (drift.summary.fixedViolations.critical > 0) {
5002
5179
  console.log(` \u2022 Critical: ${drift.summary.fixedViolations.critical}`);
5003
5180
  }
@@ -5014,7 +5191,9 @@ Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceCha
5014
5191
  if (drift.mostDegraded.length > 0) {
5015
5192
  console.log(chalk11.red("\n\u{1F4C9} Most Degraded:"));
5016
5193
  drift.mostDegraded.forEach((d) => {
5017
- console.log(` \u2022 ${d.title}: ${d.previousCompliance}% \u2192 ${d.currentCompliance}% (${d.complianceChange.toFixed(1)}%)`);
5194
+ console.log(
5195
+ ` \u2022 ${d.title}: ${d.previousCompliance}% \u2192 ${d.currentCompliance}% (${d.complianceChange.toFixed(1)}%)`
5196
+ );
5018
5197
  if (d.newViolations > 0) {
5019
5198
  console.log(` +${d.newViolations} new violation(s)`);
5020
5199
  }
@@ -5023,7 +5202,9 @@ Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceCha
5023
5202
  if (drift.mostImproved.length > 0) {
5024
5203
  console.log(chalk11.green("\n\u{1F4C8} Most Improved:"));
5025
5204
  drift.mostImproved.forEach((d) => {
5026
- console.log(` \u2022 ${d.title}: ${d.previousCompliance}% \u2192 ${d.currentCompliance}% (+${d.complianceChange.toFixed(1)}%)`);
5205
+ console.log(
5206
+ ` \u2022 ${d.title}: ${d.previousCompliance}% \u2192 ${d.currentCompliance}% (+${d.complianceChange.toFixed(1)}%)`
5207
+ );
5027
5208
  if (d.fixedViolations > 0) {
5028
5209
  console.log(` -${d.fixedViolations} fixed violation(s)`);
5029
5210
  }
@@ -5056,10 +5237,7 @@ Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceCha
5056
5237
  }
5057
5238
  }
5058
5239
  if (options.output || options.save) {
5059
- const outputPath = options.output || join10(
5060
- getReportsDir(cwd),
5061
- `health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.${extension}`
5062
- );
5240
+ const outputPath = options.output || join10(getReportsDir(cwd), `health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.${extension}`);
5063
5241
  await writeTextFile(outputPath, output);
5064
5242
  console.log(chalk11.green(`
5065
5243
  Report saved to: ${outputPath}`));
@@ -5210,7 +5388,14 @@ var contextCommand = new Command11("context").description("Generate architectura
5210
5388
  import { Command as Command12 } from "commander";
5211
5389
 
5212
5390
  // src/lsp/server.ts
5213
- import { createConnection, ProposedFeatures, TextDocuments, TextDocumentSyncKind, DiagnosticSeverity, CodeActionKind } from "vscode-languageserver/node.js";
5391
+ import {
5392
+ createConnection,
5393
+ ProposedFeatures,
5394
+ TextDocuments,
5395
+ TextDocumentSyncKind,
5396
+ DiagnosticSeverity,
5397
+ CodeActionKind
5398
+ } from "vscode-languageserver/node.js";
5214
5399
  import { TextDocument } from "vscode-languageserver-textdocument";
5215
5400
  import { fileURLToPath } from "url";
5216
5401
  import path3 from "path";
@@ -5335,7 +5520,8 @@ var SpecBridgeLspServer = class {
5335
5520
  await getPluginLoader().loadPlugins(this.cwd);
5336
5521
  } catch (error) {
5337
5522
  const msg = error instanceof Error ? error.message : String(error);
5338
- if (this.options.verbose) this.connection.console.error(chalk13.red(`Plugin load failed: ${msg}`));
5523
+ if (this.options.verbose)
5524
+ this.connection.console.error(chalk13.red(`Plugin load failed: ${msg}`));
5339
5525
  }
5340
5526
  this.registry = createRegistry({ basePath: this.cwd });
5341
5527
  await this.registry.load();
@@ -5348,7 +5534,9 @@ var SpecBridgeLspServer = class {
5348
5534
  }
5349
5535
  }
5350
5536
  if (this.options.verbose) {
5351
- this.connection.console.log(chalk13.dim(`Loaded ${this.decisions.length} active decision(s)`));
5537
+ this.connection.console.log(
5538
+ chalk13.dim(`Loaded ${this.decisions.length} active decision(s)`)
5539
+ );
5352
5540
  }
5353
5541
  } catch (error) {
5354
5542
  this.initError = error instanceof Error ? error.message : String(error);
@@ -5366,7 +5554,11 @@ var SpecBridgeLspServer = class {
5366
5554
  for (const decision of this.decisions) {
5367
5555
  for (const constraint of decision.constraints) {
5368
5556
  if (!shouldApplyConstraintToFile({ filePath, constraint, cwd: this.cwd })) continue;
5369
- const verifier = selectVerifierForConstraint(constraint.rule, constraint.verifier, constraint.check);
5557
+ const verifier = selectVerifierForConstraint(
5558
+ constraint.rule,
5559
+ constraint.verifier,
5560
+ constraint.check
5561
+ );
5370
5562
  if (!verifier) continue;
5371
5563
  const ctx = {
5372
5564
  filePath,
@@ -5944,9 +6136,7 @@ var AnalyticsEngine = class {
5944
6136
  overallTrend = "down";
5945
6137
  }
5946
6138
  }
5947
- const sortedByCompliance = [...latest.byDecision].sort(
5948
- (a, b) => b.compliance - a.compliance
5949
- );
6139
+ const sortedByCompliance = [...latest.byDecision].sort((a, b) => b.compliance - a.compliance);
5950
6140
  const topDecisions = sortedByCompliance.slice(0, 5).map((d) => ({
5951
6141
  decisionId: d.decisionId,
5952
6142
  title: d.title,
@@ -6008,7 +6198,9 @@ var analyticsCommand = new Command16("analytics").description("Analyze complianc
6008
6198
  console.log(` Average Compliance: ${metrics.averageComplianceScore.toFixed(1)}%`);
6009
6199
  const trendEmoji = metrics.trendDirection === "up" ? "\u{1F4C8}" : metrics.trendDirection === "down" ? "\u{1F4C9}" : "\u27A1\uFE0F";
6010
6200
  const trendColor = metrics.trendDirection === "up" ? chalk15.green : metrics.trendDirection === "down" ? chalk15.red : chalk15.yellow;
6011
- console.log(` ${trendColor(`${trendEmoji} Trend: ${metrics.trendDirection.toUpperCase()}`)}`);
6201
+ console.log(
6202
+ ` ${trendColor(`${trendEmoji} Trend: ${metrics.trendDirection.toUpperCase()}`)}`
6203
+ );
6012
6204
  if (metrics.history.length > 0) {
6013
6205
  console.log(chalk15.bold("\nCompliance History:"));
6014
6206
  const recentHistory = metrics.history.slice(-10);
@@ -6026,7 +6218,9 @@ var analyticsCommand = new Command16("analytics").description("Analyze complianc
6026
6218
  console.log(` Critical Issues: ${summary.criticalIssues}`);
6027
6219
  const trendEmoji = summary.overallTrend === "up" ? "\u{1F4C8}" : summary.overallTrend === "down" ? "\u{1F4C9}" : "\u27A1\uFE0F";
6028
6220
  const trendColor = summary.overallTrend === "up" ? chalk15.green : summary.overallTrend === "down" ? chalk15.red : chalk15.yellow;
6029
- console.log(` ${trendColor(`${trendEmoji} Overall Trend: ${summary.overallTrend.toUpperCase()}`)}`);
6221
+ console.log(
6222
+ ` ${trendColor(`${trendEmoji} Overall Trend: ${summary.overallTrend.toUpperCase()}`)}`
6223
+ );
6030
6224
  if (summary.topDecisions.length > 0) {
6031
6225
  console.log(chalk15.green("\n\u2705 Top Performing Decisions:"));
6032
6226
  summary.topDecisions.forEach((d, i) => {
@@ -6080,8 +6274,10 @@ var analyticsCommand = new Command16("analytics").description("Analyze complianc
6080
6274
  const latestEntry = history[history.length - 1];
6081
6275
  const oldestEntry = history[0];
6082
6276
  if (latestEntry && oldestEntry) {
6083
- console.log(chalk15.gray(`
6084
- Data range: ${latestEntry.timestamp} to ${oldestEntry.timestamp}`));
6277
+ console.log(
6278
+ chalk15.gray(`
6279
+ Data range: ${latestEntry.timestamp} to ${oldestEntry.timestamp}`)
6280
+ );
6085
6281
  }
6086
6282
  console.log(chalk15.gray(`Analyzing ${history.length} report(s) over ${days} days
6087
6283
  `));
@@ -6128,14 +6324,11 @@ var DashboardServer = class {
6128
6324
  async start() {
6129
6325
  await this.registry.load();
6130
6326
  await this.refreshCache();
6131
- this.refreshInterval = setInterval(
6132
- () => {
6133
- void this.refreshCache().catch((error) => {
6134
- this.logger.error({ error }, "Background cache refresh failed");
6135
- });
6136
- },
6137
- this.CACHE_TTL
6138
- );
6327
+ this.refreshInterval = setInterval(() => {
6328
+ void this.refreshCache().catch((error) => {
6329
+ this.logger.error({ error }, "Background cache refresh failed");
6330
+ });
6331
+ }, this.CACHE_TTL);
6139
6332
  }
6140
6333
  /**
6141
6334
  * Stop the server and clear intervals
@@ -6407,11 +6600,13 @@ var DashboardServer = class {
6407
6600
  */
6408
6601
  setupStaticFiles() {
6409
6602
  const publicDir = join12(__dirname, "public");
6410
- this.app.use(express.static(publicDir, {
6411
- maxAge: "1h",
6412
- // Cache static assets
6413
- etag: true
6414
- }));
6603
+ this.app.use(
6604
+ express.static(publicDir, {
6605
+ maxAge: "1h",
6606
+ // Cache static assets
6607
+ etag: true
6608
+ })
6609
+ );
6415
6610
  this.app.get("/{*path}", (_req, res) => {
6416
6611
  res.sendFile(join12(publicDir, "index.html"));
6417
6612
  });
@@ -6440,7 +6635,9 @@ var dashboardCommand = new Command17("dashboard").description("Start compliance
6440
6635
  console.log(chalk16.gray(" Press Ctrl+C to stop\n"));
6441
6636
  console.log(chalk16.bold("API Endpoints:"));
6442
6637
  console.log(` ${chalk16.cyan(`http://${host}:${port}/api/health`)} - Health check`);
6443
- console.log(` ${chalk16.cyan(`http://${host}:${port}/api/report/latest`)} - Latest report (cached)`);
6638
+ console.log(
6639
+ ` ${chalk16.cyan(`http://${host}:${port}/api/report/latest`)} - Latest report (cached)`
6640
+ );
6444
6641
  console.log(` ${chalk16.cyan(`http://${host}:${port}/api/decisions`)} - All decisions`);
6445
6642
  console.log(` ${chalk16.cyan(`http://${host}:${port}/api/analytics/summary`)} - Analytics`);
6446
6643
  console.log("");
@@ -6597,10 +6794,7 @@ var PropagationEngine = class {
6597
6794
  } else {
6598
6795
  estimatedEffort = "high";
6599
6796
  }
6600
- const migrationSteps = this.generateMigrationSteps(
6601
- affectedFiles,
6602
- totalAutoFixable > 0
6603
- );
6797
+ const migrationSteps = this.generateMigrationSteps(affectedFiles, totalAutoFixable > 0);
6604
6798
  return {
6605
6799
  decision: decisionId,
6606
6800
  change,
@@ -6623,9 +6817,7 @@ var PropagationEngine = class {
6623
6817
  automated: true
6624
6818
  });
6625
6819
  }
6626
- const filesWithManualFixes = affectedFiles.filter(
6627
- (f) => f.violations > f.autoFixable
6628
- );
6820
+ const filesWithManualFixes = affectedFiles.filter((f) => f.violations > f.autoFixable);
6629
6821
  if (filesWithManualFixes.length > 0) {
6630
6822
  const highPriority = filesWithManualFixes.filter((f) => f.violations > 5);
6631
6823
  const mediumPriority = filesWithManualFixes.filter(
@@ -6756,7 +6948,9 @@ function printImpactAnalysis(analysis, showSteps) {
6756
6948
  console.log(chalk17.bold("Summary:"));
6757
6949
  console.log(` Total Violations: ${totalViolations}`);
6758
6950
  console.log(` Auto-fixable: ${chalk17.green(totalAutoFixable)}`);
6759
- console.log(` Manual Fixes Required: ${manualFixes > 0 ? chalk17.yellow(manualFixes) : chalk17.green(0)}`);
6951
+ console.log(
6952
+ ` Manual Fixes Required: ${manualFixes > 0 ? chalk17.yellow(manualFixes) : chalk17.green(0)}`
6953
+ );
6760
6954
  }
6761
6955
 
6762
6956
  // src/cli/commands/migrate.ts
@@ -6851,7 +7045,11 @@ var migrateCommand = new Command19("migrate").description("Migrate SpecBridge co
6851
7045
  console.log(` Difference: ${diffColor(`${diff > 0 ? "+" : ""}${diff.toFixed(1)}%`)}`);
6852
7046
  console.log("");
6853
7047
  if (Math.abs(diff) > 10) {
6854
- console.log(chalk18.yellow("\u26A0\uFE0F Note: Compliance score changed significantly due to severity weighting."));
7048
+ console.log(
7049
+ chalk18.yellow(
7050
+ "\u26A0\uFE0F Note: Compliance score changed significantly due to severity weighting."
7051
+ )
7052
+ );
6855
7053
  console.log(chalk18.gray(" Consider adjusting CI thresholds if needed.\n"));
6856
7054
  }
6857
7055
  }
@@ -6860,9 +7058,13 @@ var migrateCommand = new Command19("migrate").description("Migrate SpecBridge co
6860
7058
  console.log(chalk18.gray("Run without --dry-run to apply changes.\n"));
6861
7059
  } else {
6862
7060
  console.log(chalk18.green("\u2713 Migration successful!"));
6863
- console.log(chalk18.gray(`
7061
+ console.log(
7062
+ chalk18.gray(
7063
+ `
6864
7064
  Rollback: Copy files from ${report.backupPath} back to .specbridge/decisions/
6865
- `));
7065
+ `
7066
+ )
7067
+ );
6866
7068
  }
6867
7069
  } catch (error) {
6868
7070
  spinner.fail("Migration failed");
@@ -6883,10 +7085,7 @@ async function createBackup(cwd, dryRun) {
6883
7085
  const files = await readdir2(decisionsDir);
6884
7086
  for (const file of files) {
6885
7087
  if (file.endsWith(".decision.yaml") || file.endsWith(".decision.yml")) {
6886
- await copyFile(
6887
- join13(decisionsDir, file),
6888
- join13(backupDir, file)
6889
- );
7088
+ await copyFile(join13(decisionsDir, file), join13(backupDir, file));
6890
7089
  }
6891
7090
  }
6892
7091
  }
@@ -6929,7 +7128,9 @@ var __dirname2 = dirname5(fileURLToPath4(import.meta.url));
6929
7128
  var packageJsonPath = join14(__dirname2, "../package.json");
6930
7129
  var packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
6931
7130
  var program = new Command20();
6932
- program.name("specbridge").description("Architecture Decision Runtime - Transform architectural decisions into executable, verifiable constraints").version(packageJson.version);
7131
+ program.name("specbridge").description(
7132
+ "Architecture Decision Runtime - Transform architectural decisions into executable, verifiable constraints"
7133
+ ).version(packageJson.version);
6933
7134
  program.addCommand(initCommand);
6934
7135
  program.addCommand(inferCommand);
6935
7136
  program.addCommand(verifyCommand);