debtlens 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE +21 -0
  3. package/README.md +244 -0
  4. package/dist/cli/index.d.ts +2 -0
  5. package/dist/cli/index.js +153 -0
  6. package/dist/cli/index.js.map +1 -0
  7. package/dist/cli/init.d.ts +10 -0
  8. package/dist/cli/init.js +18 -0
  9. package/dist/cli/init.js.map +1 -0
  10. package/dist/cli/parseList.d.ts +2 -0
  11. package/dist/cli/parseList.js +29 -0
  12. package/dist/cli/parseList.js.map +1 -0
  13. package/dist/config/defaults.d.ts +2 -0
  14. package/dist/config/defaults.js +40 -0
  15. package/dist/config/defaults.js.map +1 -0
  16. package/dist/config/loadConfig.d.ts +2 -0
  17. package/dist/config/loadConfig.js +23 -0
  18. package/dist/config/loadConfig.js.map +1 -0
  19. package/dist/config/mergeConfig.d.ts +2 -0
  20. package/dist/config/mergeConfig.js +26 -0
  21. package/dist/config/mergeConfig.js.map +1 -0
  22. package/dist/config/schema.d.ts +7 -0
  23. package/dist/config/schema.js +63 -0
  24. package/dist/config/schema.js.map +1 -0
  25. package/dist/config/template.d.ts +10 -0
  26. package/dist/config/template.js +32 -0
  27. package/dist/config/template.js.map +1 -0
  28. package/dist/core/baseline.d.ts +23 -0
  29. package/dist/core/baseline.js +118 -0
  30. package/dist/core/baseline.js.map +1 -0
  31. package/dist/core/scan.d.ts +2 -0
  32. package/dist/core/scan.js +129 -0
  33. package/dist/core/scan.js.map +1 -0
  34. package/dist/core/severity.d.ts +7 -0
  35. package/dist/core/severity.js +26 -0
  36. package/dist/core/severity.js.map +1 -0
  37. package/dist/core/types.d.ts +96 -0
  38. package/dist/core/types.js +2 -0
  39. package/dist/core/types.js.map +1 -0
  40. package/dist/detectors/deadAbstraction.d.ts +2 -0
  41. package/dist/detectors/deadAbstraction.js +115 -0
  42. package/dist/detectors/deadAbstraction.js.map +1 -0
  43. package/dist/detectors/duplicateLogic.d.ts +2 -0
  44. package/dist/detectors/duplicateLogic.js +81 -0
  45. package/dist/detectors/duplicateLogic.js.map +1 -0
  46. package/dist/detectors/effectComplexity.d.ts +2 -0
  47. package/dist/detectors/effectComplexity.js +64 -0
  48. package/dist/detectors/effectComplexity.js.map +1 -0
  49. package/dist/detectors/index.d.ts +3 -0
  50. package/dist/detectors/index.js +20 -0
  51. package/dist/detectors/index.js.map +1 -0
  52. package/dist/detectors/largeComponent.d.ts +2 -0
  53. package/dist/detectors/largeComponent.js +46 -0
  54. package/dist/detectors/largeComponent.js.map +1 -0
  55. package/dist/detectors/namingDrift.d.ts +10 -0
  56. package/dist/detectors/namingDrift.js +82 -0
  57. package/dist/detectors/namingDrift.js.map +1 -0
  58. package/dist/detectors/propDrilling.d.ts +2 -0
  59. package/dist/detectors/propDrilling.js +97 -0
  60. package/dist/detectors/propDrilling.js.map +1 -0
  61. package/dist/detectors/stateSprawl.d.ts +2 -0
  62. package/dist/detectors/stateSprawl.js +47 -0
  63. package/dist/detectors/stateSprawl.js.map +1 -0
  64. package/dist/detectors/todoComment.d.ts +2 -0
  65. package/dist/detectors/todoComment.js +45 -0
  66. package/dist/detectors/todoComment.js.map +1 -0
  67. package/dist/reporters/index.d.ts +4 -0
  68. package/dist/reporters/index.js +14 -0
  69. package/dist/reporters/index.js.map +1 -0
  70. package/dist/reporters/jsonReporter.d.ts +2 -0
  71. package/dist/reporters/jsonReporter.js +4 -0
  72. package/dist/reporters/jsonReporter.js.map +1 -0
  73. package/dist/reporters/markdownReporter.d.ts +2 -0
  74. package/dist/reporters/markdownReporter.js +52 -0
  75. package/dist/reporters/markdownReporter.js.map +1 -0
  76. package/dist/reporters/sarifReporter.d.ts +7 -0
  77. package/dist/reporters/sarifReporter.js +77 -0
  78. package/dist/reporters/sarifReporter.js.map +1 -0
  79. package/dist/reporters/terminalReporter.d.ts +4 -0
  80. package/dist/reporters/terminalReporter.js +39 -0
  81. package/dist/reporters/terminalReporter.js.map +1 -0
  82. package/dist/utils/ast.d.ts +23 -0
  83. package/dist/utils/ast.js +132 -0
  84. package/dist/utils/ast.js.map +1 -0
  85. package/dist/utils/color.d.ts +8 -0
  86. package/dist/utils/color.js +27 -0
  87. package/dist/utils/color.js.map +1 -0
  88. package/dist/utils/createIssue.d.ts +13 -0
  89. package/dist/utils/createIssue.js +28 -0
  90. package/dist/utils/createIssue.js.map +1 -0
  91. package/dist/utils/git.d.ts +15 -0
  92. package/dist/utils/git.js +68 -0
  93. package/dist/utils/git.js.map +1 -0
  94. package/dist/utils/hostComponents.d.ts +12 -0
  95. package/dist/utils/hostComponents.js +57 -0
  96. package/dist/utils/hostComponents.js.map +1 -0
  97. package/dist/utils/identifiers.d.ts +3 -0
  98. package/dist/utils/identifiers.js +20 -0
  99. package/dist/utils/identifiers.js.map +1 -0
  100. package/dist/utils/lines.d.ts +8 -0
  101. package/dist/utils/lines.js +19 -0
  102. package/dist/utils/lines.js.map +1 -0
  103. package/dist/utils/similarity.d.ts +8 -0
  104. package/dist/utils/similarity.js +63 -0
  105. package/dist/utils/similarity.js.map +1 -0
  106. package/docs/architecture.md +68 -0
  107. package/docs/example-report.md +141 -0
  108. package/docs/good-first-issues.md +63 -0
  109. package/docs/rules.md +139 -0
  110. package/docs/showcase-expensify-app.md +119 -0
  111. package/package.json +60 -0
  112. package/schema/debtlens.config.schema.json +116 -0
@@ -0,0 +1,47 @@
1
+ import { Node, SyntaxKind } from "ts-morph";
2
+ import { collectFunctionLikes, getFunctionBody } from "../utils/ast.js";
3
+ import { createIssue } from "../utils/createIssue.js";
4
+ import { nodeLineSpan } from "../utils/lines.js";
5
+ const statefulHooks = new Set(["useState", "useReducer", "useRef", "useSyncExternalStore", "useTransition", "useOptimistic"]);
6
+ export const stateSprawlDetector = {
7
+ id: "state-sprawl",
8
+ name: "State sprawl",
9
+ description: "Flags components/hooks that manage many independent pieces of local state.",
10
+ defaultSeverity: "medium",
11
+ tags: ["react", "state", "complexity"],
12
+ detect(context) {
13
+ const issues = [];
14
+ const maxStatefulHooks = context.getThreshold("state-sprawl.maxStatefulHooks", 6);
15
+ for (const file of context.files) {
16
+ for (const fn of collectFunctionLikes(file)) {
17
+ if (fn.classification === "function")
18
+ continue;
19
+ const body = getFunctionBody(fn.node) ?? fn.node;
20
+ const calls = body.getDescendantsOfKind(SyntaxKind.CallExpression).filter((call) => {
21
+ const expression = call.getExpression();
22
+ if (Node.isIdentifier(expression))
23
+ return statefulHooks.has(expression.getText());
24
+ if (Node.isPropertyAccessExpression(expression))
25
+ return statefulHooks.has(expression.getName());
26
+ return false;
27
+ });
28
+ if (calls.length < maxStatefulHooks)
29
+ continue;
30
+ const span = nodeLineSpan(body);
31
+ const names = calls.map((call) => call.getExpression().getText());
32
+ issues.push(createIssue({
33
+ detector: stateSprawlDetector,
34
+ severity: calls.length >= maxStatefulHooks + 4 ? "high" : "medium",
35
+ confidence: 0.82,
36
+ file: file.relativePath,
37
+ location: { startLine: span.startLine, endLine: span.endLine },
38
+ message: `${fn.name} manages ${calls.length} stateful hook calls. This often means one component is coordinating several unrelated workflows.`,
39
+ evidence: [`Stateful hooks: ${names.join(", ")}`],
40
+ suggestion: "Group related state in a reducer, extract state machines into a hook, or move server/cache state out of component state.",
41
+ }));
42
+ }
43
+ }
44
+ return issues;
45
+ },
46
+ };
47
+ //# sourceMappingURL=stateSprawl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stateSprawl.js","sourceRoot":"","sources":["../../src/detectors/stateSprawl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE5C,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,sBAAsB,EAAE,eAAe,EAAE,eAAe,CAAC,CAAC,CAAC;AAE9H,MAAM,CAAC,MAAM,mBAAmB,GAAa;IAC3C,EAAE,EAAE,cAAc;IAClB,IAAI,EAAE,cAAc;IACpB,WAAW,EAAE,4EAA4E;IACzF,eAAe,EAAE,QAAQ;IACzB,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC;IACtC,MAAM,CAAC,OAAwB;QAC7B,MAAM,MAAM,GAAgB,EAAE,CAAC;QAC/B,MAAM,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,+BAA+B,EAAE,CAAC,CAAC,CAAC;QAElF,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YACjC,KAAK,MAAM,EAAE,IAAI,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5C,IAAI,EAAE,CAAC,cAAc,KAAK,UAAU;oBAAE,SAAS;gBAC/C,MAAM,IAAI,GAAG,eAAe,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC;gBACjD,MAAM,KAAK,GAAG,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;oBACjF,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;oBACxC,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;wBAAE,OAAO,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;oBAClF,IAAI,IAAI,CAAC,0BAA0B,CAAC,UAAU,CAAC;wBAAE,OAAO,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;oBAChG,OAAO,KAAK,CAAC;gBACf,CAAC,CAAC,CAAC;gBAEH,IAAI,KAAK,CAAC,MAAM,GAAG,gBAAgB;oBAAE,SAAS;gBAE9C,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;gBAChC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;gBAClE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;oBACtB,QAAQ,EAAE,mBAAmB;oBAC7B,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,gBAAgB,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ;oBAClE,UAAU,EAAE,IAAI;oBAChB,IAAI,EAAE,IAAI,CAAC,YAAY;oBACvB,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;oBAC9D,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,YAAY,KAAK,CAAC,MAAM,mGAAmG;oBAC9I,QAAQ,EAAE,CAAC,mBAAmB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACjD,UAAU,EAAE,0HAA0H;iBACvI,CAAC,CAAC,CAAC;YACN,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Detector } from "../core/types.js";
2
+ export declare const todoCommentDetector: Detector;
@@ -0,0 +1,45 @@
1
+ import { createIssue } from "../utils/createIssue.js";
2
+ const patterns = [
3
+ { regex: /\b(FIXME|BUG|BROKEN)\b/i, severity: "medium", label: "fixme/bug marker" },
4
+ { regex: /\b(HACK|WORKAROUND|KLUDGE)\b/i, severity: "medium", label: "hack/workaround marker" },
5
+ { regex: /\b(TODO|XXX)\b/i, severity: "low", label: "todo marker" },
6
+ { regex: /\b(for now|temporary|temp|quick fix|placeholder|stub)\b/i, severity: "low", label: "temporary implementation marker" },
7
+ { regex: /\b(generated by|ai generated|copilot|cursor|codex)\b/i, severity: "info", label: "assistant-generation marker" },
8
+ ];
9
+ export const todoCommentDetector = {
10
+ id: "todo-comment",
11
+ name: "Debt marker comment",
12
+ description: "Finds comment markers that often identify debt accepted during fast implementation.",
13
+ defaultSeverity: "low",
14
+ tags: ["comments", "review", "cleanup"],
15
+ detect(context) {
16
+ const issues = [];
17
+ for (const file of context.files) {
18
+ const lines = file.content.split(/\r?\n/);
19
+ let countForFile = 0;
20
+ for (let index = 0; index < lines.length; index += 1) {
21
+ const line = lines[index] ?? "";
22
+ if (!line.includes("//") && !line.includes("/*") && !line.includes("*"))
23
+ continue;
24
+ const match = patterns.find((pattern) => pattern.regex.test(line));
25
+ if (!match)
26
+ continue;
27
+ issues.push(createIssue({
28
+ detector: todoCommentDetector,
29
+ severity: match.severity,
30
+ confidence: 0.9,
31
+ file: file.relativePath,
32
+ location: { startLine: index + 1 },
33
+ message: `Comment contains a ${match.label}.`,
34
+ evidence: [line.trim().slice(0, 220)],
35
+ suggestion: "Convert the marker into a tracked issue, add a removal condition, or fix it before more code depends on it.",
36
+ }));
37
+ countForFile += 1;
38
+ if (countForFile >= 12)
39
+ break;
40
+ }
41
+ }
42
+ return issues;
43
+ },
44
+ };
45
+ //# sourceMappingURL=todoComment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"todoComment.js","sourceRoot":"","sources":["../../src/detectors/todoComment.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,MAAM,QAAQ,GAAgE;IAC5E,EAAE,KAAK,EAAE,yBAAyB,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,kBAAkB,EAAE;IACnF,EAAE,KAAK,EAAE,+BAA+B,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,wBAAwB,EAAE;IAC/F,EAAE,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE;IACnE,EAAE,KAAK,EAAE,0DAA0D,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,iCAAiC,EAAE;IAChI,EAAE,KAAK,EAAE,uDAAuD,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,6BAA6B,EAAE;CAC3H,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAa;IAC3C,EAAE,EAAE,cAAc;IAClB,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EAAE,qFAAqF;IAClG,eAAe,EAAE,KAAK;IACtB,IAAI,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,SAAS,CAAC;IACvC,MAAM,CAAC,OAAwB;QAC7B,MAAM,MAAM,GAAgB,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC1C,IAAI,YAAY,GAAG,CAAC,CAAC;YACrB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;gBACrD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;gBAChC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAClF,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBACnE,IAAI,CAAC,KAAK;oBAAE,SAAS;gBAErB,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;oBACtB,QAAQ,EAAE,mBAAmB;oBAC7B,QAAQ,EAAE,KAAK,CAAC,QAAQ;oBACxB,UAAU,EAAE,GAAG;oBACf,IAAI,EAAE,IAAI,CAAC,YAAY;oBACvB,QAAQ,EAAE,EAAE,SAAS,EAAE,KAAK,GAAG,CAAC,EAAE;oBAClC,OAAO,EAAE,sBAAsB,KAAK,CAAC,KAAK,GAAG;oBAC7C,QAAQ,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;oBACrC,UAAU,EAAE,6GAA6G;iBAC1H,CAAC,CAAC,CAAC;gBAEJ,YAAY,IAAI,CAAC,CAAC;gBAClB,IAAI,YAAY,IAAI,EAAE;oBAAE,MAAM;YAChC,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { OutputFormat, ScanResult } from "../core/types.js";
2
+ export declare function renderReport(result: ScanResult, format: OutputFormat, options?: {
3
+ color?: boolean;
4
+ }): string;
@@ -0,0 +1,14 @@
1
+ import { renderJson } from "./jsonReporter.js";
2
+ import { renderMarkdown } from "./markdownReporter.js";
3
+ import { renderSarif } from "./sarifReporter.js";
4
+ import { renderTerminal } from "./terminalReporter.js";
5
+ export function renderReport(result, format, options = {}) {
6
+ if (format === "json")
7
+ return renderJson(result);
8
+ if (format === "markdown")
9
+ return renderMarkdown(result);
10
+ if (format === "sarif")
11
+ return renderSarif(result);
12
+ return renderTerminal(result, { color: options.color ?? true });
13
+ }
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/reporters/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,MAAM,UAAU,YAAY,CAAC,MAAkB,EAAE,MAAoB,EAAE,UAA+B,EAAE;IACtG,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;IACjD,IAAI,MAAM,KAAK,UAAU;QAAE,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;IACzD,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC;IACnD,OAAO,cAAc,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC,CAAC;AAClE,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { ScanResult } from "../core/types.js";
2
+ export declare function renderJson(result: ScanResult): string;
@@ -0,0 +1,4 @@
1
+ export function renderJson(result) {
2
+ return `${JSON.stringify(result, null, 2)}\n`;
3
+ }
4
+ //# sourceMappingURL=jsonReporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonReporter.js","sourceRoot":"","sources":["../../src/reporters/jsonReporter.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,UAAU,CAAC,MAAkB;IAC3C,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { ScanResult } from "../core/types.js";
2
+ export declare function renderMarkdown(result: ScanResult): string;
@@ -0,0 +1,52 @@
1
+ const severityOrder = ["high", "medium", "low", "info"];
2
+ export function renderMarkdown(result) {
3
+ const lines = [];
4
+ lines.push("# DebtLens Report");
5
+ lines.push("");
6
+ lines.push(`Scanned **${result.summary.filesScanned}** files with **${result.summary.rulesRun}** rules in **${result.summary.elapsedMs}ms**.`);
7
+ lines.push("");
8
+ lines.push("## Summary");
9
+ lines.push("");
10
+ lines.push(`- Total issues: **${result.summary.totalIssues}**`);
11
+ for (const severity of severityOrder) {
12
+ lines.push(`- ${capitalize(severity)}: **${result.summary.bySeverity[severity]}**`);
13
+ }
14
+ if (result.issues.length === 0) {
15
+ lines.push("");
16
+ lines.push("No maintainability debt found at the configured severity level.");
17
+ return `${lines.join("\n")}\n`;
18
+ }
19
+ for (const severity of severityOrder) {
20
+ const issues = result.issues.filter((issue) => issue.severity === severity);
21
+ if (issues.length === 0)
22
+ continue;
23
+ lines.push("");
24
+ lines.push(`## ${capitalize(severity)} severity`);
25
+ lines.push("");
26
+ for (const issue of issues) {
27
+ const location = issue.location ? `:${issue.location.startLine}` : "";
28
+ lines.push(`### ${issue.ruleName} — \`${issue.file}${location}\``);
29
+ lines.push("");
30
+ lines.push(issue.message);
31
+ lines.push("");
32
+ lines.push(`Confidence: **${Math.round(issue.confidence * 100)}%**`);
33
+ if (issue.evidence?.length) {
34
+ lines.push("");
35
+ lines.push("Evidence:");
36
+ for (const evidence of issue.evidence) {
37
+ lines.push(`- ${evidence}`);
38
+ }
39
+ }
40
+ if (issue.suggestion) {
41
+ lines.push("");
42
+ lines.push(`Suggestion: ${issue.suggestion}`);
43
+ }
44
+ lines.push("");
45
+ }
46
+ }
47
+ return `${lines.join("\n")}\n`;
48
+ }
49
+ function capitalize(value) {
50
+ return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
51
+ }
52
+ //# sourceMappingURL=markdownReporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdownReporter.js","sourceRoot":"","sources":["../../src/reporters/markdownReporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { ScanResult } from "../core/types.js";
2
+ /**
3
+ * Render a scan result as SARIF 2.1.0 for GitHub code scanning and other tools.
4
+ * The full rule catalog (all detectors) is always emitted under `tool.driver.rules`
5
+ * so rule indices stay stable regardless of which rules produced results.
6
+ */
7
+ export declare function renderSarif(result: ScanResult): string;
@@ -0,0 +1,77 @@
1
+ import { allDetectors } from "../detectors/index.js";
2
+ const TOOL_VERSION = "0.1.0";
3
+ const INFORMATION_URI = "https://github.com/ColumbusLabs/debtlens";
4
+ function toSarifLevel(severity) {
5
+ switch (severity) {
6
+ case "high":
7
+ return "error";
8
+ case "medium":
9
+ return "warning";
10
+ case "low":
11
+ case "info":
12
+ return "note";
13
+ default:
14
+ return "none";
15
+ }
16
+ }
17
+ /**
18
+ * Render a scan result as SARIF 2.1.0 for GitHub code scanning and other tools.
19
+ * The full rule catalog (all detectors) is always emitted under `tool.driver.rules`
20
+ * so rule indices stay stable regardless of which rules produced results.
21
+ */
22
+ export function renderSarif(result) {
23
+ const ruleIndex = new Map();
24
+ const rules = allDetectors.map((detector, index) => {
25
+ ruleIndex.set(detector.id, index);
26
+ return {
27
+ id: detector.id,
28
+ name: detector.name,
29
+ shortDescription: { text: detector.description },
30
+ defaultConfiguration: { level: toSarifLevel(detector.defaultSeverity) },
31
+ properties: { tags: detector.tags },
32
+ };
33
+ });
34
+ const results = result.issues.map((issue) => ({
35
+ ruleId: issue.ruleId,
36
+ ruleIndex: ruleIndex.get(issue.ruleId) ?? -1,
37
+ level: toSarifLevel(issue.severity),
38
+ message: { text: issue.message },
39
+ locations: [
40
+ {
41
+ physicalLocation: {
42
+ artifactLocation: { uri: issue.file },
43
+ region: {
44
+ startLine: issue.location?.startLine ?? 1,
45
+ ...(issue.location?.endLine ? { endLine: issue.location.endLine } : {}),
46
+ ...(issue.location?.startColumn ? { startColumn: issue.location.startColumn } : {}),
47
+ },
48
+ },
49
+ },
50
+ ],
51
+ properties: {
52
+ confidence: issue.confidence,
53
+ severity: issue.severity,
54
+ ...(issue.evidence?.length ? { evidence: issue.evidence } : {}),
55
+ ...(issue.suggestion ? { suggestion: issue.suggestion } : {}),
56
+ },
57
+ }));
58
+ const sarif = {
59
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
60
+ version: "2.1.0",
61
+ runs: [
62
+ {
63
+ tool: {
64
+ driver: {
65
+ name: "DebtLens",
66
+ informationUri: INFORMATION_URI,
67
+ version: TOOL_VERSION,
68
+ rules,
69
+ },
70
+ },
71
+ results,
72
+ },
73
+ ],
74
+ };
75
+ return `${JSON.stringify(sarif, null, 2)}\n`;
76
+ }
77
+ //# sourceMappingURL=sarifReporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sarifReporter.js","sourceRoot":"","sources":["../../src/reporters/sarifReporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { ScanResult } from "../core/types.js";
2
+ export declare function renderTerminal(result: ScanResult, options?: {
3
+ color: boolean;
4
+ }): string;
@@ -0,0 +1,39 @@
1
+ import { createColorizer } from "../utils/color.js";
2
+ const severityOrder = ["high", "medium", "low", "info"];
3
+ export function renderTerminal(result, options = { color: true }) {
4
+ const color = createColorizer(options.color);
5
+ const lines = [];
6
+ lines.push(color.bold("DebtLens Report"));
7
+ lines.push(`Scanned ${result.summary.filesScanned} files with ${result.summary.rulesRun} rules in ${result.summary.elapsedMs}ms.`);
8
+ lines.push(`Issues: ${result.summary.totalIssues} | high ${result.summary.bySeverity.high} | medium ${result.summary.bySeverity.medium} | low ${result.summary.bySeverity.low} | info ${result.summary.bySeverity.info}`);
9
+ if (result.issues.length === 0) {
10
+ lines.push("");
11
+ lines.push("No maintainability debt found at the configured severity level.");
12
+ return `${lines.join("\n")}\n`;
13
+ }
14
+ for (const severity of severityOrder) {
15
+ const issues = result.issues.filter((issue) => issue.severity === severity);
16
+ if (issues.length === 0)
17
+ continue;
18
+ lines.push("");
19
+ lines.push(color.severity(severity, color.bold(`${severity.toUpperCase()} (${issues.length})`)));
20
+ for (const issue of issues) {
21
+ const location = issue.location ? `:${issue.location.startLine}` : "";
22
+ lines.push(` ${color.severity(issue.severity, issue.ruleName)} ${color.gray(`[${issue.ruleId}]`)}`);
23
+ lines.push(` ${issue.file}${location}`);
24
+ lines.push(` ${issue.message}`);
25
+ lines.push(` confidence ${Math.round(issue.confidence * 100)}%`);
26
+ if (issue.evidence?.length) {
27
+ for (const evidence of issue.evidence.slice(0, 3)) {
28
+ lines.push(` - ${evidence}`);
29
+ }
30
+ }
31
+ if (issue.suggestion) {
32
+ lines.push(` suggestion: ${issue.suggestion}`);
33
+ }
34
+ lines.push("");
35
+ }
36
+ }
37
+ return `${lines.join("\n")}\n`;
38
+ }
39
+ //# sourceMappingURL=terminalReporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminalReporter.js","sourceRoot":"","sources":["../../src/reporters/terminalReporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { ArrowFunction, FunctionDeclaration, FunctionExpression, Node as MorphNode, SourceFile, VariableDeclaration } from "ts-morph";
2
+ import type { SourceFileInfo } from "../core/types.js";
3
+ export type FunctionNode = FunctionDeclaration | ArrowFunction | FunctionExpression;
4
+ export interface FunctionLikeInfo {
5
+ name: string;
6
+ node: FunctionNode;
7
+ declaration: FunctionDeclaration | VariableDeclaration;
8
+ file: SourceFileInfo;
9
+ classification: "component" | "hook" | "function";
10
+ }
11
+ export declare function collectFunctionLikes(file: SourceFileInfo): FunctionLikeInfo[];
12
+ export declare function classifyFunctionName(name: string): FunctionLikeInfo["classification"];
13
+ export declare function getFunctionBody(node: FunctionNode): MorphNode | undefined;
14
+ export declare function countHookCalls(node: MorphNode): number;
15
+ export declare function countBranches(node: MorphNode): number;
16
+ export declare function getCallName(callExpressionText: string): string | undefined;
17
+ export declare function getSourceFileLine(sourceFile: SourceFile, lineNumber: number): string;
18
+ /**
19
+ * Build a structural fingerprint of a function body: a multiset of node-shape tokens
20
+ * that ignores identifier and literal values. Two functions that share control-flow
21
+ * and call shape produce similar fingerprints even when every name differs.
22
+ */
23
+ export declare function structuralFingerprint(node: MorphNode): Map<string, number>;
@@ -0,0 +1,132 @@
1
+ import { Node, SyntaxKind } from "ts-morph";
2
+ import { isHookName, isPascalCase } from "./identifiers.js";
3
+ export function collectFunctionLikes(file) {
4
+ const results = [];
5
+ for (const declaration of file.sourceFile.getFunctions()) {
6
+ const name = declaration.getName();
7
+ if (!name)
8
+ continue;
9
+ results.push({
10
+ name,
11
+ node: declaration,
12
+ declaration,
13
+ file,
14
+ classification: classifyFunctionName(name),
15
+ });
16
+ }
17
+ for (const declaration of file.sourceFile.getVariableDeclarations()) {
18
+ const name = declaration.getName();
19
+ const initializer = declaration.getInitializer();
20
+ if (!initializer)
21
+ continue;
22
+ if (Node.isArrowFunction(initializer) || Node.isFunctionExpression(initializer)) {
23
+ results.push({
24
+ name,
25
+ node: initializer,
26
+ declaration,
27
+ file,
28
+ classification: classifyFunctionName(name),
29
+ });
30
+ }
31
+ }
32
+ return results;
33
+ }
34
+ export function classifyFunctionName(name) {
35
+ if (isHookName(name))
36
+ return "hook";
37
+ if (isPascalCase(name))
38
+ return "component";
39
+ return "function";
40
+ }
41
+ export function getFunctionBody(node) {
42
+ if (Node.isFunctionDeclaration(node) || Node.isFunctionExpression(node) || Node.isArrowFunction(node)) {
43
+ return node.getBody();
44
+ }
45
+ return undefined;
46
+ }
47
+ export function countHookCalls(node) {
48
+ return node.getDescendantsOfKind(SyntaxKind.CallExpression).filter((call) => {
49
+ const expression = call.getExpression();
50
+ return Node.isIdentifier(expression) && isHookName(expression.getText());
51
+ }).length;
52
+ }
53
+ export function countBranches(node) {
54
+ return node.getDescendants().filter((descendant) => {
55
+ const kind = descendant.getKind();
56
+ return kind === SyntaxKind.IfStatement
57
+ || kind === SyntaxKind.ConditionalExpression
58
+ || kind === SyntaxKind.SwitchStatement
59
+ || kind === SyntaxKind.ForStatement
60
+ || kind === SyntaxKind.ForOfStatement
61
+ || kind === SyntaxKind.ForInStatement
62
+ || kind === SyntaxKind.WhileStatement
63
+ || kind === SyntaxKind.DoStatement
64
+ || kind === SyntaxKind.CatchClause;
65
+ }).length;
66
+ }
67
+ export function getCallName(callExpressionText) {
68
+ const match = callExpressionText.match(/^([A-Za-z_$][\w$]*)\s*\(/);
69
+ return match?.[1];
70
+ }
71
+ export function getSourceFileLine(sourceFile, lineNumber) {
72
+ return sourceFile.getFullText().split(/\r?\n/)[lineNumber - 1] ?? "";
73
+ }
74
+ /**
75
+ * Build a structural fingerprint of a function body: a multiset of node-shape tokens
76
+ * that ignores identifier and literal values. Two functions that share control-flow
77
+ * and call shape produce similar fingerprints even when every name differs.
78
+ */
79
+ export function structuralFingerprint(node) {
80
+ const counts = new Map();
81
+ const bump = (token) => counts.set(token, (counts.get(token) ?? 0) + 1);
82
+ for (const descendant of node.getDescendants()) {
83
+ switch (descendant.getKind()) {
84
+ case SyntaxKind.IfStatement:
85
+ bump("if");
86
+ break;
87
+ case SyntaxKind.ConditionalExpression:
88
+ bump("ternary");
89
+ break;
90
+ case SyntaxKind.ForStatement:
91
+ case SyntaxKind.ForOfStatement:
92
+ case SyntaxKind.ForInStatement:
93
+ bump("for");
94
+ break;
95
+ case SyntaxKind.WhileStatement:
96
+ case SyntaxKind.DoStatement:
97
+ bump("while");
98
+ break;
99
+ case SyntaxKind.SwitchStatement:
100
+ bump("switch");
101
+ break;
102
+ case SyntaxKind.ReturnStatement:
103
+ bump("return");
104
+ break;
105
+ case SyntaxKind.AwaitExpression:
106
+ bump("await");
107
+ break;
108
+ case SyntaxKind.TryStatement:
109
+ bump("try");
110
+ break;
111
+ case SyntaxKind.VariableDeclaration:
112
+ bump("var");
113
+ break;
114
+ case SyntaxKind.BinaryExpression:
115
+ bump("binop");
116
+ break;
117
+ case SyntaxKind.CallExpression: {
118
+ const expression = descendant.getExpression();
119
+ bump(Node.isPropertyAccessExpression(expression) ? "call.prop" : "call.id");
120
+ break;
121
+ }
122
+ case SyntaxKind.JsxElement:
123
+ case SyntaxKind.JsxSelfClosingElement:
124
+ bump("jsx");
125
+ break;
126
+ default:
127
+ break;
128
+ }
129
+ }
130
+ return counts;
131
+ }
132
+ //# sourceMappingURL=ast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.js","sourceRoot":"","sources":["../../src/utils/ast.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Severity } from "../core/types.js";
2
+ export interface Colorizer {
3
+ bold: (value: string) => string;
4
+ dim: (value: string) => string;
5
+ severity: (severity: Severity, value: string) => string;
6
+ gray: (value: string) => string;
7
+ }
8
+ export declare function createColorizer(enabled: boolean): Colorizer;
@@ -0,0 +1,27 @@
1
+ const codes = {
2
+ reset: "\u001b[0m",
3
+ dim: "\u001b[2m",
4
+ bold: "\u001b[1m",
5
+ red: "\u001b[31m",
6
+ yellow: "\u001b[33m",
7
+ cyan: "\u001b[36m",
8
+ gray: "\u001b[90m",
9
+ };
10
+ export function createColorizer(enabled) {
11
+ const wrap = (open, value) => enabled ? `${open}${value}${codes.reset}` : value;
12
+ return {
13
+ bold: (value) => wrap(codes.bold, value),
14
+ dim: (value) => wrap(codes.dim, value),
15
+ gray: (value) => wrap(codes.gray, value),
16
+ severity: (severity, value) => {
17
+ if (severity === "high")
18
+ return wrap(codes.red, value);
19
+ if (severity === "medium")
20
+ return wrap(codes.yellow, value);
21
+ if (severity === "info")
22
+ return wrap(codes.cyan, value);
23
+ return value;
24
+ },
25
+ };
26
+ }
27
+ //# sourceMappingURL=color.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.js","sourceRoot":"","sources":["../../src/utils/color.ts"],"names":[],"mappings":"AAEA,MAAM,KAAK,GAAG;IACZ,KAAK,EAAE,WAAW;IAClB,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,WAAW;IACjB,GAAG,EAAE,YAAY;IACjB,MAAM,EAAE,YAAY;IACpB,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,YAAY;CACnB,CAAC;AASF,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,KAAa,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IAChG,OAAO;QACL,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC;QACxC,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC;QACtC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC;QACxC,QAAQ,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE;YAC5B,IAAI,QAAQ,KAAK,MAAM;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACvD,IAAI,QAAQ,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAC5D,IAAI,QAAQ,KAAK,MAAM;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACxD,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { DebtIssue, Detector, IssueLocation, Severity } from "../core/types.js";
2
+ export interface CreateIssueInput {
3
+ detector: Detector;
4
+ severity?: Severity;
5
+ confidence?: number;
6
+ message: string;
7
+ file: string;
8
+ location?: IssueLocation;
9
+ evidence?: string[];
10
+ suggestion?: string;
11
+ tags?: string[];
12
+ }
13
+ export declare function createIssue(input: CreateIssueInput): DebtIssue;
@@ -0,0 +1,28 @@
1
+ export function createIssue(input) {
2
+ const stable = `${input.detector.id}:${input.file}:${input.location?.startLine ?? 0}:${input.message}`;
3
+ return {
4
+ id: hashString(stable),
5
+ ruleId: input.detector.id,
6
+ ruleName: input.detector.name,
7
+ severity: input.severity ?? input.detector.defaultSeverity,
8
+ confidence: clamp(input.confidence ?? 0.75, 0, 1),
9
+ message: input.message,
10
+ file: input.file,
11
+ location: input.location,
12
+ evidence: input.evidence,
13
+ suggestion: input.suggestion,
14
+ tags: Array.from(new Set([...(input.detector.tags ?? []), ...(input.tags ?? [])])),
15
+ };
16
+ }
17
+ function clamp(value, min, max) {
18
+ return Math.max(min, Math.min(max, value));
19
+ }
20
+ function hashString(value) {
21
+ let hash = 2166136261;
22
+ for (let i = 0; i < value.length; i += 1) {
23
+ hash ^= value.charCodeAt(i);
24
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
25
+ }
26
+ return `dl_${(hash >>> 0).toString(16)}`;
27
+ }
28
+ //# sourceMappingURL=createIssue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createIssue.js","sourceRoot":"","sources":["../../src/utils/createIssue.ts"],"names":[],"mappings":"AAcA,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,MAAM,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,QAAQ,EAAE,SAAS,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;IAEvG,OAAO;QACL,EAAE,EAAE,UAAU,CAAC,MAAM,CAAC;QACtB,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,EAAE;QACzB,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,IAAI;QAC7B,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,eAAe;QAC1D,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACjD,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;KACnF,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,KAAa,EAAE,GAAW,EAAE,GAAW;IACpD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,IAAI,IAAI,GAAG,UAAU,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,15 @@
1
+ export declare function isGitRepo(cwd: string): boolean;
2
+ export interface ChangedFiles {
3
+ root: string;
4
+ files: string[];
5
+ }
6
+ /**
7
+ * Collect changed files as absolute paths. Returns `null` when `cwd` is not inside a
8
+ * git work tree, so the caller can degrade gracefully.
9
+ *
10
+ * - With `base`, includes the committed diff `base...HEAD` (merge-base — the PR's own
11
+ * changes). An unknown `base` throws a clear error.
12
+ * - Always unions in uncommitted working-tree changes vs HEAD and untracked files.
13
+ * - Deletions are excluded (`--diff-filter=d`) since deleted files cannot be scanned.
14
+ */
15
+ export declare function getChangedFiles(cwd: string, base?: string): ChangedFiles | null;