@lbroth/rothunter 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -0
  3. package/dist/adapters/llm.d.ts +68 -0
  4. package/dist/adapters/llm.d.ts.map +1 -0
  5. package/dist/adapters/llm.js +189 -0
  6. package/dist/adapters/llm.js.map +1 -0
  7. package/dist/config.d.ts +37 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +81 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/detector-registry.d.ts +32 -0
  12. package/dist/detector-registry.d.ts.map +1 -0
  13. package/dist/detector-registry.js +74 -0
  14. package/dist/detector-registry.js.map +1 -0
  15. package/dist/detectors/api-race.d.ts +6 -0
  16. package/dist/detectors/api-race.d.ts.map +1 -0
  17. package/dist/detectors/api-race.js +222 -0
  18. package/dist/detectors/api-race.js.map +1 -0
  19. package/dist/detectors/bad-config.d.ts +6 -0
  20. package/dist/detectors/bad-config.d.ts.map +1 -0
  21. package/dist/detectors/bad-config.js +529 -0
  22. package/dist/detectors/bad-config.js.map +1 -0
  23. package/dist/detectors/console-log-prod.d.ts +6 -0
  24. package/dist/detectors/console-log-prod.d.ts.map +1 -0
  25. package/dist/detectors/console-log-prod.js +72 -0
  26. package/dist/detectors/console-log-prod.js.map +1 -0
  27. package/dist/detectors/dead-api.d.ts +10 -0
  28. package/dist/detectors/dead-api.d.ts.map +1 -0
  29. package/dist/detectors/dead-api.js +115 -0
  30. package/dist/detectors/dead-api.js.map +1 -0
  31. package/dist/detectors/dead-export.d.ts +12 -0
  32. package/dist/detectors/dead-export.d.ts.map +1 -0
  33. package/dist/detectors/dead-export.js +140 -0
  34. package/dist/detectors/dead-export.js.map +1 -0
  35. package/dist/detectors/dead-handler.d.ts +12 -0
  36. package/dist/detectors/dead-handler.d.ts.map +1 -0
  37. package/dist/detectors/dead-handler.js +40 -0
  38. package/dist/detectors/dead-handler.js.map +1 -0
  39. package/dist/detectors/dead-module.d.ts +14 -0
  40. package/dist/detectors/dead-module.d.ts.map +1 -0
  41. package/dist/detectors/dead-module.js +50 -0
  42. package/dist/detectors/dead-module.js.map +1 -0
  43. package/dist/detectors/deep-nesting.d.ts +12 -0
  44. package/dist/detectors/deep-nesting.d.ts.map +1 -0
  45. package/dist/detectors/deep-nesting.js +133 -0
  46. package/dist/detectors/deep-nesting.js.map +1 -0
  47. package/dist/detectors/duplicate-function.d.ts +9 -0
  48. package/dist/detectors/duplicate-function.d.ts.map +1 -0
  49. package/dist/detectors/duplicate-function.js +199 -0
  50. package/dist/detectors/duplicate-function.js.map +1 -0
  51. package/dist/detectors/duplicate-type.d.ts +9 -0
  52. package/dist/detectors/duplicate-type.d.ts.map +1 -0
  53. package/dist/detectors/duplicate-type.js +166 -0
  54. package/dist/detectors/duplicate-type.js.map +1 -0
  55. package/dist/detectors/hot-hub-file.d.ts +11 -0
  56. package/dist/detectors/hot-hub-file.d.ts.map +1 -0
  57. package/dist/detectors/hot-hub-file.js +42 -0
  58. package/dist/detectors/hot-hub-file.js.map +1 -0
  59. package/dist/detectors/long-file.d.ts +12 -0
  60. package/dist/detectors/long-file.d.ts.map +1 -0
  61. package/dist/detectors/long-file.js +82 -0
  62. package/dist/detectors/long-file.js.map +1 -0
  63. package/dist/detectors/long-function.d.ts +12 -0
  64. package/dist/detectors/long-function.d.ts.map +1 -0
  65. package/dist/detectors/long-function.js +45 -0
  66. package/dist/detectors/long-function.js.map +1 -0
  67. package/dist/detectors/magic-numbers.d.ts +10 -0
  68. package/dist/detectors/magic-numbers.d.ts.map +1 -0
  69. package/dist/detectors/magic-numbers.js +332 -0
  70. package/dist/detectors/magic-numbers.js.map +1 -0
  71. package/dist/detectors/mutable-globals.d.ts +6 -0
  72. package/dist/detectors/mutable-globals.d.ts.map +1 -0
  73. package/dist/detectors/mutable-globals.js +95 -0
  74. package/dist/detectors/mutable-globals.js.map +1 -0
  75. package/dist/detectors/mutation.d.ts +11 -0
  76. package/dist/detectors/mutation.d.ts.map +1 -0
  77. package/dist/detectors/mutation.js +397 -0
  78. package/dist/detectors/mutation.js.map +1 -0
  79. package/dist/detectors/public-any.d.ts +6 -0
  80. package/dist/detectors/public-any.d.ts.map +1 -0
  81. package/dist/detectors/public-any.js +52 -0
  82. package/dist/detectors/public-any.js.map +1 -0
  83. package/dist/detectors/race-condition.d.ts +6 -0
  84. package/dist/detectors/race-condition.d.ts.map +1 -0
  85. package/dist/detectors/race-condition.js +608 -0
  86. package/dist/detectors/race-condition.js.map +1 -0
  87. package/dist/detectors/shared-db-write.d.ts +6 -0
  88. package/dist/detectors/shared-db-write.d.ts.map +1 -0
  89. package/dist/detectors/shared-db-write.js +656 -0
  90. package/dist/detectors/shared-db-write.js.map +1 -0
  91. package/dist/detectors/silent-catch.d.ts +6 -0
  92. package/dist/detectors/silent-catch.d.ts.map +1 -0
  93. package/dist/detectors/silent-catch.js +167 -0
  94. package/dist/detectors/silent-catch.js.map +1 -0
  95. package/dist/detectors/similar-functions.d.ts +15 -0
  96. package/dist/detectors/similar-functions.d.ts.map +1 -0
  97. package/dist/detectors/similar-functions.js +334 -0
  98. package/dist/detectors/similar-functions.js.map +1 -0
  99. package/dist/detectors/skip-tests.d.ts +6 -0
  100. package/dist/detectors/skip-tests.d.ts.map +1 -0
  101. package/dist/detectors/skip-tests.js +69 -0
  102. package/dist/detectors/skip-tests.js.map +1 -0
  103. package/dist/detectors/todo-comments.d.ts +29 -0
  104. package/dist/detectors/todo-comments.d.ts.map +1 -0
  105. package/dist/detectors/todo-comments.js +154 -0
  106. package/dist/detectors/todo-comments.js.map +1 -0
  107. package/dist/detectors/unused-deps.d.ts +8 -0
  108. package/dist/detectors/unused-deps.d.ts.map +1 -0
  109. package/dist/detectors/unused-deps.js +115 -0
  110. package/dist/detectors/unused-deps.js.map +1 -0
  111. package/dist/extraction/api-race-confirmer.d.ts +31 -0
  112. package/dist/extraction/api-race-confirmer.d.ts.map +1 -0
  113. package/dist/extraction/api-race-confirmer.js +110 -0
  114. package/dist/extraction/api-race-confirmer.js.map +1 -0
  115. package/dist/extraction/llm-confirmer.d.ts +25 -0
  116. package/dist/extraction/llm-confirmer.d.ts.map +1 -0
  117. package/dist/extraction/llm-confirmer.js +118 -0
  118. package/dist/extraction/llm-confirmer.js.map +1 -0
  119. package/dist/extraction/mutation-confirmer.d.ts +30 -0
  120. package/dist/extraction/mutation-confirmer.d.ts.map +1 -0
  121. package/dist/extraction/mutation-confirmer.js +73 -0
  122. package/dist/extraction/mutation-confirmer.js.map +1 -0
  123. package/dist/extraction/prompt-chunking.d.ts +37 -0
  124. package/dist/extraction/prompt-chunking.d.ts.map +1 -0
  125. package/dist/extraction/prompt-chunking.js +61 -0
  126. package/dist/extraction/prompt-chunking.js.map +1 -0
  127. package/dist/extraction/race-confirmer.d.ts +28 -0
  128. package/dist/extraction/race-confirmer.d.ts.map +1 -0
  129. package/dist/extraction/race-confirmer.js +68 -0
  130. package/dist/extraction/race-confirmer.js.map +1 -0
  131. package/dist/extraction/shared-db-write-confirmer.d.ts +31 -0
  132. package/dist/extraction/shared-db-write-confirmer.d.ts.map +1 -0
  133. package/dist/extraction/shared-db-write-confirmer.js +141 -0
  134. package/dist/extraction/shared-db-write-confirmer.js.map +1 -0
  135. package/dist/extraction/triage-confirmer.d.ts +59 -0
  136. package/dist/extraction/triage-confirmer.d.ts.map +1 -0
  137. package/dist/extraction/triage-confirmer.js +104 -0
  138. package/dist/extraction/triage-confirmer.js.map +1 -0
  139. package/dist/graph/cfg.d.ts +45 -0
  140. package/dist/graph/cfg.d.ts.map +1 -0
  141. package/dist/graph/cfg.js +198 -0
  142. package/dist/graph/cfg.js.map +1 -0
  143. package/dist/graph/decorator-entries.d.ts +2 -0
  144. package/dist/graph/decorator-entries.d.ts.map +1 -0
  145. package/dist/graph/decorator-entries.js +89 -0
  146. package/dist/graph/decorator-entries.js.map +1 -0
  147. package/dist/graph/entry-points.d.ts +12 -0
  148. package/dist/graph/entry-points.d.ts.map +1 -0
  149. package/dist/graph/entry-points.js +282 -0
  150. package/dist/graph/entry-points.js.map +1 -0
  151. package/dist/graph/handler-conventions.d.ts +2 -0
  152. package/dist/graph/handler-conventions.d.ts.map +1 -0
  153. package/dist/graph/handler-conventions.js +26 -0
  154. package/dist/graph/handler-conventions.js.map +1 -0
  155. package/dist/graph/iac-entries.d.ts +2 -0
  156. package/dist/graph/iac-entries.d.ts.map +1 -0
  157. package/dist/graph/iac-entries.js +123 -0
  158. package/dist/graph/iac-entries.js.map +1 -0
  159. package/dist/graph/import-graph.d.ts +48 -0
  160. package/dist/graph/import-graph.d.ts.map +1 -0
  161. package/dist/graph/import-graph.js +86 -0
  162. package/dist/graph/import-graph.js.map +1 -0
  163. package/dist/graph/monorepo-detect.d.ts +3 -0
  164. package/dist/graph/monorepo-detect.d.ts.map +1 -0
  165. package/dist/graph/monorepo-detect.js +166 -0
  166. package/dist/graph/monorepo-detect.js.map +1 -0
  167. package/dist/graph/tsconfig-paths.d.ts +23 -0
  168. package/dist/graph/tsconfig-paths.d.ts.map +1 -0
  169. package/dist/graph/tsconfig-paths.js +217 -0
  170. package/dist/graph/tsconfig-paths.js.map +1 -0
  171. package/dist/multi-workspace-scanner.d.ts +13 -0
  172. package/dist/multi-workspace-scanner.d.ts.map +1 -0
  173. package/dist/multi-workspace-scanner.js +130 -0
  174. package/dist/multi-workspace-scanner.js.map +1 -0
  175. package/dist/normalizers/type-normalizer.d.ts +16 -0
  176. package/dist/normalizers/type-normalizer.d.ts.map +1 -0
  177. package/dist/normalizers/type-normalizer.js +189 -0
  178. package/dist/normalizers/type-normalizer.js.map +1 -0
  179. package/dist/parsers/typescript-parser.d.ts +57 -0
  180. package/dist/parsers/typescript-parser.d.ts.map +1 -0
  181. package/dist/parsers/typescript-parser.js +502 -0
  182. package/dist/parsers/typescript-parser.js.map +1 -0
  183. package/dist/reporter/json-reporter.d.ts +12 -0
  184. package/dist/reporter/json-reporter.d.ts.map +1 -0
  185. package/dist/reporter/json-reporter.js +28 -0
  186. package/dist/reporter/json-reporter.js.map +1 -0
  187. package/dist/reporter/markdown-reporter.d.ts +11 -0
  188. package/dist/reporter/markdown-reporter.d.ts.map +1 -0
  189. package/dist/reporter/markdown-reporter.js +77 -0
  190. package/dist/reporter/markdown-reporter.js.map +1 -0
  191. package/dist/rothunter.d.ts +125 -0
  192. package/dist/rothunter.d.ts.map +1 -0
  193. package/dist/rothunter.js +1038 -0
  194. package/dist/rothunter.js.map +1 -0
  195. package/dist/server/false-positives.d.ts +34 -0
  196. package/dist/server/false-positives.d.ts.map +1 -0
  197. package/dist/server/false-positives.js +85 -0
  198. package/dist/server/false-positives.js.map +1 -0
  199. package/dist/server/index.d.ts +2 -0
  200. package/dist/server/index.d.ts.map +1 -0
  201. package/dist/server/index.js +1529 -0
  202. package/dist/server/index.js.map +1 -0
  203. package/dist/server/marked-to-fix.d.ts +16 -0
  204. package/dist/server/marked-to-fix.d.ts.map +1 -0
  205. package/dist/server/marked-to-fix.js +36 -0
  206. package/dist/server/marked-to-fix.js.map +1 -0
  207. package/dist/server/scan-store.d.ts +147 -0
  208. package/dist/server/scan-store.d.ts.map +1 -0
  209. package/dist/server/scan-store.js +291 -0
  210. package/dist/server/scan-store.js.map +1 -0
  211. package/dist/server/settings-store.d.ts +28 -0
  212. package/dist/server/settings-store.d.ts.map +1 -0
  213. package/dist/server/settings-store.js +46 -0
  214. package/dist/server/settings-store.js.map +1 -0
  215. package/dist/server/workspace-store.d.ts +39 -0
  216. package/dist/server/workspace-store.d.ts.map +1 -0
  217. package/dist/server/workspace-store.js +108 -0
  218. package/dist/server/workspace-store.js.map +1 -0
  219. package/dist/types/detector-input.d.ts +37 -0
  220. package/dist/types/detector-input.d.ts.map +1 -0
  221. package/dist/types/detector-input.js +2 -0
  222. package/dist/types/detector-input.js.map +1 -0
  223. package/dist/types.d.ts +110 -0
  224. package/dist/types.d.ts.map +1 -0
  225. package/dist/types.js +2 -0
  226. package/dist/types.js.map +1 -0
  227. package/dist/utils/clustering.d.ts +14 -0
  228. package/dist/utils/clustering.d.ts.map +1 -0
  229. package/dist/utils/clustering.js +56 -0
  230. package/dist/utils/clustering.js.map +1 -0
  231. package/dist/utils/gitignore.d.ts +32 -0
  232. package/dist/utils/gitignore.d.ts.map +1 -0
  233. package/dist/utils/gitignore.js +122 -0
  234. package/dist/utils/gitignore.js.map +1 -0
  235. package/dist/utils/hash.d.ts +11 -0
  236. package/dist/utils/hash.d.ts.map +1 -0
  237. package/dist/utils/hash.js +14 -0
  238. package/dist/utils/hash.js.map +1 -0
  239. package/dist/utils/ignore-annotation.d.ts +28 -0
  240. package/dist/utils/ignore-annotation.d.ts.map +1 -0
  241. package/dist/utils/ignore-annotation.js +46 -0
  242. package/dist/utils/ignore-annotation.js.map +1 -0
  243. package/dist/utils/llm-json.d.ts +2 -0
  244. package/dist/utils/llm-json.d.ts.map +1 -0
  245. package/dist/utils/llm-json.js +53 -0
  246. package/dist/utils/llm-json.js.map +1 -0
  247. package/dist/utils/logger.d.ts +3 -0
  248. package/dist/utils/logger.d.ts.map +1 -0
  249. package/dist/utils/logger.js +4 -0
  250. package/dist/utils/logger.js.map +1 -0
  251. package/dist/utils/project-conventions.d.ts +2 -0
  252. package/dist/utils/project-conventions.d.ts.map +1 -0
  253. package/dist/utils/project-conventions.js +108 -0
  254. package/dist/utils/project-conventions.js.map +1 -0
  255. package/dist/utils/regex.d.ts +9 -0
  256. package/dist/utils/regex.d.ts.map +1 -0
  257. package/dist/utils/regex.js +11 -0
  258. package/dist/utils/regex.js.map +1 -0
  259. package/dist/utils/snippet.d.ts +20 -0
  260. package/dist/utils/snippet.d.ts.map +1 -0
  261. package/dist/utils/snippet.js +28 -0
  262. package/dist/utils/snippet.js.map +1 -0
  263. package/dist/utils/source-reader.d.ts +19 -0
  264. package/dist/utils/source-reader.d.ts.map +1 -0
  265. package/dist/utils/source-reader.js +32 -0
  266. package/dist/utils/source-reader.js.map +1 -0
  267. package/logo.png +0 -0
  268. package/package.json +92 -0
  269. package/scripts/start-llm.mjs +161 -0
@@ -0,0 +1,69 @@
1
+ import { makeSourceReader } from '../utils/source-reader.js';
2
+ import { stableHash } from '../utils/hash.js';
3
+ // .skip / .only / xdescribe / fdescribe in tests. .only is HIGH (suite
4
+ // no-op on merge), .skip / x* are MED.
5
+ export function detectSkipTests(input) {
6
+ const read = makeSourceReader(input.workspaceRoot, input.project);
7
+ const findings = [];
8
+ for (const rel of input.files) {
9
+ if (!isTestFile(rel))
10
+ continue;
11
+ const raw = read(rel);
12
+ if (raw == null)
13
+ continue;
14
+ findings.push(...analyseFile(rel, raw));
15
+ }
16
+ return findings;
17
+ }
18
+ const SKIP_PATTERNS = [
19
+ { re: /\b(describe|it|test)\.skip\s*\(/g, kind: 'skip', label: '.skip(...)' },
20
+ { re: /\b(describe|it|test)\.only\s*\(/g, kind: 'only', label: '.only(...)' },
21
+ { re: /\b(xdescribe|xit|xtest)\s*\(/g, kind: 'skip', label: 'x-prefixed skip' },
22
+ { re: /\b(fdescribe|fit)\s*\(/g, kind: 'only', label: 'f-prefixed only' },
23
+ ];
24
+ function analyseFile(file, raw) {
25
+ const out = [];
26
+ for (const p of SKIP_PATTERNS) {
27
+ p.re.lastIndex = 0;
28
+ for (const match of raw.matchAll(p.re)) {
29
+ const line = lineOf(raw, match.index);
30
+ out.push({
31
+ detectorId: 'skip-tests',
32
+ severity: p.kind === 'only' ? 'high' : 'medium',
33
+ confidence: 0.97,
34
+ layer: 1,
35
+ title: `${p.kind === 'only' ? 'Test isolation' : 'Skipped test'}: ${p.label} in ${file}:${line}`,
36
+ description: p.kind === 'only'
37
+ ? `\`${match[0].replace(/\($/, '')}\` causes the entire test framework to run ONLY the marked block — every other test in the suite is silently skipped during CI. Merging this disables the whole test bed.`
38
+ : `\`${match[0].replace(/\($/, '')}\` is a skipped test. Skips are useful during triage but rot fast when left without a tracking ticket or a delete plan.`,
39
+ evidence: [
40
+ {
41
+ file,
42
+ range: { startLine: line, endLine: line },
43
+ snippet: snippetAround(raw, line),
44
+ },
45
+ ],
46
+ suggestion: p.kind === 'only'
47
+ ? 'Remove the `.only` modifier before merging. Add an ESLint rule (e.g. `mocha/no-exclusive-tests` or `jest/no-focused-tests`) to fail CI on `.only` callsites.'
48
+ : 'Either re-enable + fix the test, or delete it. If a skip is genuinely intentional, replace it with `it.todo(...)` and link the tracking ticket.',
49
+ fingerprint: `skip-tests:${stableHash(`${file}:${line}:${p.label}`)}`,
50
+ });
51
+ }
52
+ }
53
+ return out;
54
+ }
55
+ function isTestFile(file) {
56
+ const posix = file.replace(/\\/g, '/');
57
+ return /(?:^|\/)(__tests__|tests|test|spec)\//.test(posix)
58
+ || /\.(?:test|spec)\.(?:ts|tsx|js|jsx|mts|cts|mjs|cjs)$/.test(posix);
59
+ }
60
+ function lineOf(raw, idx) {
61
+ return raw.slice(0, idx).split('\n').length;
62
+ }
63
+ function snippetAround(raw, line) {
64
+ const lines = raw.split('\n');
65
+ const from = Math.max(0, line - 2);
66
+ const to = Math.min(lines.length, line + 2);
67
+ return lines.slice(from, to).join('\n');
68
+ }
69
+ //# sourceMappingURL=skip-tests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skip-tests.js","sourceRoot":"","sources":["../../src/detectors/skip-tests.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { Finding } from '../types.js';
2
+ /**
3
+ * todo-comments takes an OPTIONAL files list because the detector
4
+ * walks the workspace itself when omitted — that's how it picks up
5
+ * Python / Go / shell sources the TS parser ignores. The shared
6
+ * `FileWalkingDetectorInput` requires `files`, so this detector keeps
7
+ * its own shape rather than extending.
8
+ */
9
+ export interface TodoCommentsDetectorInput {
10
+ workspaceRoot: string;
11
+ /**
12
+ * Optional pre-parsed file list. When omitted, the detector walks the
13
+ * workspace itself — useful for picking up Python / Go / shell files
14
+ * the TS parser does not visit.
15
+ */
16
+ files?: ReadonlyArray<string>;
17
+ /**
18
+ * Recognised markers (case-insensitive). Default covers the common
19
+ * ones used across JS / TS / Go / Rust / Python style guides.
20
+ */
21
+ markers?: ReadonlyArray<string>;
22
+ /**
23
+ * Max findings to emit. Default 60 — these are LOW signal and would
24
+ * otherwise drown the dashboard on legacy repos.
25
+ */
26
+ maxFindings?: number;
27
+ }
28
+ export declare function detectTodoComments(input: TodoCommentsDetectorInput): Finding[];
29
+ //# sourceMappingURL=todo-comments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"todo-comments.d.ts","sourceRoot":"","sources":["../../src/detectors/todo-comments.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAK3C;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9B;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAChC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAID,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,yBAAyB,GAAG,OAAO,EAAE,CAuD9E"}
@@ -0,0 +1,154 @@
1
+ import * as path from 'node:path';
2
+ import { readFileSync, readdirSync, statSync } from 'node:fs';
3
+ import { stableHash } from '../utils/hash.js';
4
+ import { escapeForRegex } from '../utils/regex.js';
5
+ import { loadGitignore } from '../utils/gitignore.js';
6
+ // Inline TODO/FIXME/HACK/XXX/WTF/BUG/NOTE/REVIEW/DEPRECATED comments.
7
+ // HACK/XXX/WTF/FIXME/BUG → MED, rest LOW.
8
+ export function detectTodoComments(input) {
9
+ const markers = input.markers ?? DEFAULT_MARKERS;
10
+ const maxFindings = input.maxFindings ?? 60;
11
+ const findings = [];
12
+ // Build a single alternation regex so we make one pass per file.
13
+ const alt = markers.map((m) => escapeForRegex(m)).join('|');
14
+ const re = new RegExp(`(?:\\/\\/|\\/\\*+|\\#)\\s*(${alt})\\b[:!\\-]?\\s*(.*)`, 'gi');
15
+ // Use the caller-supplied file list when present; otherwise walk the
16
+ // workspace to pick up non-TS sources (Python / Go / shell) the
17
+ // TypeScript parser doesn't visit.
18
+ const files = input.files && input.files.length > 0 ? input.files : walkFiles(input.workspaceRoot);
19
+ for (const rel of files) {
20
+ if (findings.length >= maxFindings)
21
+ break;
22
+ if (!isAnalysable(rel))
23
+ continue;
24
+ const abs = path.resolve(input.workspaceRoot, rel);
25
+ let raw;
26
+ try {
27
+ raw = readFileSync(abs, 'utf-8');
28
+ }
29
+ catch {
30
+ continue;
31
+ }
32
+ const lines = raw.split('\n');
33
+ for (let i = 0; i < lines.length; i++) {
34
+ if (findings.length >= maxFindings)
35
+ break;
36
+ re.lastIndex = 0;
37
+ const m = re.exec(lines[i]);
38
+ if (!m)
39
+ continue;
40
+ const marker = m[1].toUpperCase();
41
+ const note = (m[2] ?? '').trim().slice(0, 160);
42
+ const line = i + 1;
43
+ const sev = severityFor(marker);
44
+ findings.push({
45
+ detectorId: 'todo-comments',
46
+ severity: sev,
47
+ confidence: 1,
48
+ layer: 1,
49
+ title: `${marker} comment in ${rel}:${line}${note ? ` — ${note}` : ''}`,
50
+ description: `Inline \`${marker}\` comment at \`${rel}:${line}\`. Tracking technical debt in source comments works for a sprint or two and then rots — these accumulate and nobody knows which ones are still relevant.`,
51
+ evidence: [
52
+ {
53
+ file: rel,
54
+ range: { startLine: line, endLine: line },
55
+ snippet: snippetAround(lines, line),
56
+ },
57
+ ],
58
+ suggestion: 'Move actionable items to your issue tracker and link the ticket from the comment, or delete the comment if it has become obsolete. Repo-wide grep for stale markers is a useful periodic chore.',
59
+ fingerprint: `todo-comments:${stableHash(`${rel}:${line}:${marker}:${note}`)}`,
60
+ });
61
+ }
62
+ }
63
+ return findings;
64
+ }
65
+ const DEFAULT_MARKERS = [
66
+ 'TODO',
67
+ 'FIXME',
68
+ 'HACK',
69
+ 'XXX',
70
+ 'BUG',
71
+ 'NOTE',
72
+ 'REVIEW',
73
+ 'WTF',
74
+ 'DEPRECATED',
75
+ 'DEPRECATE',
76
+ ];
77
+ function severityFor(marker) {
78
+ switch (marker) {
79
+ case 'HACK':
80
+ case 'XXX':
81
+ case 'WTF':
82
+ case 'FIXME':
83
+ case 'BUG':
84
+ return 'medium';
85
+ case 'TODO':
86
+ case 'NOTE':
87
+ case 'REVIEW':
88
+ case 'DEPRECATED':
89
+ case 'DEPRECATE':
90
+ default:
91
+ return 'low';
92
+ }
93
+ }
94
+ /**
95
+ * Recursive workspace walk that returns relative file paths matching
96
+ * `isAnalysable`. Stops at conventional skip-dirs (node_modules, dist,
97
+ * build, .git, …) so big legacy repos don't blow the file budget.
98
+ */
99
+ function walkFiles(root) {
100
+ const out = [];
101
+ // Path exclusions come from `.gitignore` + `.rothunterignore` only.
102
+ // The matcher always bakes in `node_modules` + `.git` so a workspace
103
+ // without ignore files still walks something sensible.
104
+ const gitignore = loadGitignore(root);
105
+ const stack = [''];
106
+ while (stack.length > 0) {
107
+ const rel = stack.pop();
108
+ const abs = rel ? path.join(root, rel) : root;
109
+ let entries;
110
+ try {
111
+ entries = readdirSync(abs);
112
+ }
113
+ catch {
114
+ continue;
115
+ }
116
+ for (const name of entries) {
117
+ const childRel = rel ? path.join(rel, name) : name;
118
+ const posixRel = childRel.replace(/\\/g, '/');
119
+ const childAbs = path.join(root, childRel);
120
+ let s;
121
+ try {
122
+ s = statSync(childAbs);
123
+ }
124
+ catch {
125
+ continue;
126
+ }
127
+ if (s.isDirectory()) {
128
+ if (gitignore.ignores(posixRel + '/'))
129
+ continue;
130
+ stack.push(childRel);
131
+ }
132
+ else if (s.isFile() && isAnalysable(childRel)) {
133
+ if (gitignore.ignores(posixRel))
134
+ continue;
135
+ out.push(posixRel);
136
+ }
137
+ }
138
+ }
139
+ return out;
140
+ }
141
+ function isAnalysable(file) {
142
+ const posix = file.replace(/\\/g, '/');
143
+ return /\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs|py|go|rs|java|kt|swift|rb|php|sql|sh|yaml|yml|toml)$/.test(posix)
144
+ && !/\.d\.ts$/.test(posix)
145
+ && !/(^|\/)node_modules\//.test(posix)
146
+ && !/(^|\/)dist\//.test(posix)
147
+ && !/(^|\/)build\//.test(posix);
148
+ }
149
+ function snippetAround(lines, line) {
150
+ const from = Math.max(0, line - 2);
151
+ const to = Math.min(lines.length, line + 1);
152
+ return lines.slice(from, to).join('\n');
153
+ }
154
+ //# sourceMappingURL=todo-comments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"todo-comments.js","sourceRoot":"","sources":["../../src/detectors/todo-comments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { ImportRecord } from '../graph/import-graph.js';
2
+ import type { Finding } from '../types.js';
3
+ export interface UnusedDepsDetectorInput {
4
+ workspaceRoot: string;
5
+ imports: ReadonlyArray<ImportRecord>;
6
+ }
7
+ export declare function detectUnusedDeps(input: UnusedDepsDetectorInput): Finding[];
8
+ //# sourceMappingURL=unused-deps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unused-deps.d.ts","sourceRoot":"","sources":["../../src/detectors/unused-deps.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAI3C,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CACtC;AAID,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,EAAE,CAqD1E"}
@@ -0,0 +1,115 @@
1
+ import * as path from 'node:path';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { stableHash } from '../utils/hash.js';
4
+ import { escapeForRegex } from '../utils/regex.js';
5
+ // dependencies + peerDependencies declared but never imported. LOW.
6
+ // Skips devDeps, workspace:* entries, known runtime loaders (tsx, …).
7
+ export function detectUnusedDeps(input) {
8
+ const pkgPath = path.join(input.workspaceRoot, 'package.json');
9
+ if (!existsSync(pkgPath))
10
+ return [];
11
+ let raw;
12
+ try {
13
+ raw = readFileSync(pkgPath, 'utf-8');
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ let pkg;
19
+ try {
20
+ pkg = JSON.parse(raw);
21
+ }
22
+ catch {
23
+ return [];
24
+ }
25
+ const declared = new Set();
26
+ for (const k of Object.keys(pkg.dependencies ?? {}))
27
+ declared.add(k);
28
+ for (const k of Object.keys(pkg.peerDependencies ?? {}))
29
+ declared.add(k);
30
+ const used = new Set();
31
+ for (const imp of input.imports) {
32
+ if (imp.target)
33
+ continue; // resolved to a workspace path → not an npm dep
34
+ const pkgName = parsePackageName(imp.specifier);
35
+ if (pkgName)
36
+ used.add(pkgName);
37
+ }
38
+ const out = [];
39
+ for (const dep of declared) {
40
+ if (used.has(dep))
41
+ continue;
42
+ if (RUNTIME_LOADERS.has(dep))
43
+ continue;
44
+ if ((pkg.dependencies?.[dep] ?? '').startsWith('workspace:'))
45
+ continue;
46
+ const line = findLineInJson(raw, dep);
47
+ out.push({
48
+ detectorId: 'unused-deps',
49
+ severity: 'low',
50
+ confidence: 0.85,
51
+ layer: 1,
52
+ title: `Unused dependency: \`${dep}\` in package.json`,
53
+ description: `\`${dep}\` is declared in \`dependencies\` (or \`peerDependencies\`) but never imported across the workspace. Unused deps inflate the lockfile + the install footprint and complicate audits.`,
54
+ evidence: [
55
+ {
56
+ file: 'package.json',
57
+ range: { startLine: line, endLine: line },
58
+ snippet: snippetAround(raw, line),
59
+ },
60
+ ],
61
+ suggestion: `If genuinely unused, run \`npm uninstall ${dep}\` (or your package manager's equivalent). If the dep is loaded by name at runtime (plugin/CLI/loader pattern), add it to the runtime-loader allow-list.`,
62
+ fingerprint: `unused-deps:${stableHash(dep)}`,
63
+ });
64
+ }
65
+ return out;
66
+ }
67
+ const RUNTIME_LOADERS = new Set([
68
+ 'tsx',
69
+ 'ts-node',
70
+ 'tsconfig-paths',
71
+ '@swc/register',
72
+ '@swc-node/register',
73
+ 'esbuild-register',
74
+ 'jiti',
75
+ 'dotenv-cli',
76
+ 'concurrently',
77
+ 'rimraf',
78
+ 'cross-env',
79
+ ]);
80
+ function parsePackageName(specifier) {
81
+ if (!specifier || specifier.startsWith('.') || specifier.startsWith('/'))
82
+ return null;
83
+ if (specifier.startsWith('node:'))
84
+ return null;
85
+ // node builtins without prefix:
86
+ if (BUILTINS.has(specifier.split('/')[0]))
87
+ return null;
88
+ if (specifier.startsWith('@')) {
89
+ const parts = specifier.split('/');
90
+ if (parts.length < 2)
91
+ return null;
92
+ return `${parts[0]}/${parts[1]}`;
93
+ }
94
+ return specifier.split('/')[0];
95
+ }
96
+ const BUILTINS = new Set([
97
+ 'fs', 'path', 'os', 'http', 'https', 'crypto', 'util', 'events', 'stream', 'buffer',
98
+ 'child_process', 'url', 'querystring', 'zlib', 'tty', 'net', 'tls', 'dns', 'cluster',
99
+ 'worker_threads', 'perf_hooks', 'assert', 'process', 'readline', 'string_decoder',
100
+ 'vm', 'v8', 'inspector', 'async_hooks', 'timers', 'dgram',
101
+ ]);
102
+ function findLineInJson(raw, key) {
103
+ const re = new RegExp(`"${escapeForRegex(key)}"\\s*:`);
104
+ const m = re.exec(raw);
105
+ if (!m)
106
+ return 1;
107
+ return raw.slice(0, m.index).split('\n').length;
108
+ }
109
+ function snippetAround(raw, line) {
110
+ const lines = raw.split('\n');
111
+ const from = Math.max(0, line - 1);
112
+ const to = Math.min(lines.length, line + 1);
113
+ return lines.slice(from, to).join('\n');
114
+ }
115
+ //# sourceMappingURL=unused-deps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unused-deps.js","sourceRoot":"","sources":["../../src/detectors/unused-deps.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ import { LlmClient } from '../adapters/llm.js';
3
+ declare const VerdictSchema: z.ZodObject<{
4
+ race: z.ZodBoolean;
5
+ confidence: z.ZodNumber;
6
+ reason: z.ZodString;
7
+ }, z.core.$strip>;
8
+ export type ApiRaceVerdict = z.infer<typeof VerdictSchema>;
9
+ export interface ApiRaceCheckInput {
10
+ method: string;
11
+ pathPattern: string;
12
+ /** Comma-separated client identifiers detected on the cluster. */
13
+ clients: string;
14
+ /** Up to 8 call sites with their enclosing function source slice. */
15
+ sites: ReadonlyArray<{
16
+ file: string;
17
+ line: number;
18
+ enclosingName?: string;
19
+ enclosingSource: string;
20
+ }>;
21
+ }
22
+ export declare class ApiRaceConfirmer {
23
+ private llm;
24
+ private cache;
25
+ constructor(llm?: LlmClient);
26
+ confirm(input: ApiRaceCheckInput): Promise<ApiRaceVerdict | null>;
27
+ private callOnce;
28
+ private callChunked;
29
+ }
30
+ export {};
31
+ //# sourceMappingURL=api-race-confirmer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-race-confirmer.d.ts","sourceRoot":"","sources":["../../src/extraction/api-race-confirmer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,SAAS,EAA0B,MAAM,oBAAoB,CAAC;AAYvE,QAAA,MAAM,aAAa;;;;iBAIjB,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,KAAK,EAAE,aAAa,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC,CAAC;CACJ;AAgDD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,KAAK,CAAqC;gBAEtC,GAAG,CAAC,EAAE,SAAS;IAIrB,OAAO,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;YAoBzD,QAAQ;YAwBR,WAAW;CAa1B"}
@@ -0,0 +1,110 @@
1
+ import { z } from 'zod';
2
+ import { createDefaultLlmClient } from '../adapters/llm.js';
3
+ import { parseLlmJsonResponse } from '../utils/llm-json.js';
4
+ import { logger } from '../utils/logger.js';
5
+ import { prepareSites, estimatePromptChars, splitIntoChunks, aggregateChunkVerdicts, PROMPT_BUDGET_CHARS, } from './prompt-chunking.js';
6
+ const VerdictSchema = z.object({
7
+ race: z.boolean(),
8
+ confidence: z.number().min(0).max(1),
9
+ reason: z.string(),
10
+ });
11
+ const PROMPT = `You are reviewing a static-analysis finding about a mutating HTTP call (PUT / PATCH / POST / DELETE) issued by multiple TypeScript functions from different files against the same URL pattern. Decide: is this a REAL cross-flow race (two independent flows can fire the write concurrently and the second overwrites the first) or SAFE (one of the callers is a test, all callers share a retry helper, the body is idempotent, or the writes use If-Match / ETag optimistic locking)?
12
+
13
+ Output ONE compact JSON object and STOP:
14
+ {"race": boolean, "confidence": <0..1>, "reason": "<max 22 words>"}
15
+
16
+ Decision rules (apply IN ORDER — first match wins):
17
+
18
+ 1. **Test-only counterpart** — STRICT trigger: one caller's FILE PATH literally contains \`__tests__/\`, \`tests/\`, \`.test.ts\`, or \`.spec.ts\`, OR the function name begins with \`test_\` / \`it_\` / \`describe_\`. Mere fixture-style file names like \`case_*\` / \`*_a.ts\` / \`*_b.ts\` are NOT test markers. Suffixes like \`_web\` / \`_worker\` / \`_api\` / \`_cron\` are NOT test markers. → safe, confidence ≥ 0.85.
19
+
20
+ 2. **Idempotent body** — every caller sends a CONSTANT or static value in the request body (\`{ status: 'active' }\`, \`{ enabled: true }\`, \`new Date()\`, \`{}\`). The value must NOT depend on a function parameter that callers fill in differently. → safe, confidence ≥ 0.85.
21
+
22
+ 3. **Optimistic locking** — every caller sets an \`If-Match\` / \`If-Unmodified-Since\` / \`X-Resource-Version\` header (literal token \`If-Match\` or \`etag\` visible in the snippets of EVERY caller). → safe, confidence ≥ 0.8.
23
+
24
+ 4. **Retry / single-flight wrapper** — STRICT trigger: one of the snippets contains a literal retry-helper definition (function whose name contains \`retry\`, \`backoff\`, \`withRetries\`, \`singleFlight\`) AND the other caller IMPORTS or CALLS that helper around the HTTP write. A mere caller name like \`*WithRetries\` is not enough on its own — the snippets must show the helper. → safe, confidence ≥ 0.75.
25
+
26
+ 5. **Variable host (opaque upstream)** — the cluster path begins with \`/:param/\` (because the host portion of every URL in the snippets is itself a template variable, not a literal). In the snippets, EVERY URL is built by concatenating a host variable (commonly named \`baseUrl\`, \`this.baseUrl\`, \`apiUrl\`, \`endpoint\`, \`host\`, \`server\`) with the path. The actual upstream service is opaque to static analysis — two snippets may point at the SAME service (race) or at DIFFERENT services at runtime (safe). Common real-world case: two modules implementing OpenAI-compatible / vLLM clients against \`/chat/completions\` or \`/v1/embed\`, each parameterising the host via env-var / constructor. Without proof of host equality, treat as low-confidence race. → race: true, confidence 0.55. THIS RULE TAKES PRIORITY OVER RULE 6.
27
+
28
+ 6. **Independent flows** — caller names or file basenames mention two unrelated owners. Common signal tokens: \`webhook\`, \`worker\`, \`cron\`, \`bot\`, \`consumer\`, \`browser\`, \`web\`, \`mobile\`, \`service\`, \`api\`, \`handler\`, \`scheduler\`, \`background\`. Body uses request-specific data that differs between flows, AND the path host is NOT a template variable. → race, confidence ≥ 0.85.
29
+
30
+ 7. **Default** — mutating HTTP call from two distinct files, no idempotent body, no etag header in the snippets, no explicit test path, no variable host. → race, confidence ≥ 0.8.
31
+
32
+ 8. Unclear → \`race: true, confidence: 0.6\`.
33
+
34
+ Strictness notes:
35
+ - Rule 1 ONLY fires when the file PATH contains a literal test marker (\`__tests__\`, \`tests/\`, \`.test.ts\`, \`.spec.ts\`). Fixture-style identifiers (\`case_01_*\`, \`_a.ts\`, \`_b.ts\`) and owner-style suffixes (\`_web\`, \`_worker\`, \`_api\`, \`_cron\`, \`_service\`) are NOT test markers — they ARE the independent-flow signal of rule 6.
36
+ - Rule 4 ONLY fires when a retry helper definition is actually visible in the supplied snippets. Do NOT assume a retry helper exists.
37
+ - For rule 2: the value is idempotent only if it is a constant or monotonic. Parameters passed into the caller (\`body\`, \`payload\`) make it NOT idempotent.
38
+ - Rule 5 catches a real-world FP class: two modules implementing OpenAI-compatible / vLLM-compatible / generic-HTTP API clients against \`/chat/completions\`, \`/v1/embed\`, etc., each parameterising the host via env-var or constructor. The path looks racy but the upstream service is opaque.
39
+
40
+ Cluster: \`{{METHOD}} {{PATH}}\` (clients: {{CLIENTS}})
41
+
42
+ Call sites:
43
+ \`\`\`
44
+ {{SITES}}
45
+ \`\`\`
46
+ `;
47
+ function renderSites(sites) {
48
+ return sites
49
+ .map((s, i) => {
50
+ const header = `[${i + 1}] ${s.file}:${s.line}${s.enclosingName ? ` — ${s.enclosingName}` : ''}`;
51
+ return `${header}\n${s.enclosingSource}`;
52
+ })
53
+ .join('\n---\n');
54
+ }
55
+ export class ApiRaceConfirmer {
56
+ llm;
57
+ cache = new Map();
58
+ constructor(llm) {
59
+ this.llm = llm ?? createDefaultLlmClient();
60
+ }
61
+ async confirm(input) {
62
+ const cacheKey = `${input.method} ${input.pathPattern}::${input.clients}::${input.sites
63
+ .map((s) => `${s.file}:${s.line}`)
64
+ .sort()
65
+ .join(',')}`;
66
+ const cached = this.cache.get(cacheKey);
67
+ if (cached)
68
+ return cached;
69
+ const sites = prepareSites(input.sites);
70
+ const singleCallChars = estimatePromptChars(PROMPT, sites);
71
+ let verdict;
72
+ if (singleCallChars <= PROMPT_BUDGET_CHARS) {
73
+ verdict = await this.callOnce(input, sites);
74
+ }
75
+ else {
76
+ verdict = await this.callChunked(input, sites);
77
+ }
78
+ if (verdict)
79
+ this.cache.set(cacheKey, verdict);
80
+ return verdict;
81
+ }
82
+ async callOnce(input, sites) {
83
+ const prompt = PROMPT.replace('{{METHOD}}', input.method)
84
+ .replace('{{PATH}}', input.pathPattern)
85
+ .replace('{{CLIENTS}}', input.clients)
86
+ .replace('{{SITES}}', renderSites(sites));
87
+ try {
88
+ const raw = await this.llm.chat([{ role: 'user', content: prompt }], { temperature: 0, maxTokens: 128 });
89
+ const parsed = parseLlmJsonResponse(raw);
90
+ return VerdictSchema.parse(parsed);
91
+ }
92
+ catch (err) {
93
+ logger.warn({ err: err.message, method: input.method, path: input.pathPattern }, 'ApiRaceConfirmer failed; returning null');
94
+ return null;
95
+ }
96
+ }
97
+ async callChunked(input, sites) {
98
+ const chunks = splitIntoChunks(sites);
99
+ const chunkVerdicts = [];
100
+ for (const chunk of chunks) {
101
+ const v = await this.callOnce(input, chunk);
102
+ if (v)
103
+ chunkVerdicts.push(v);
104
+ }
105
+ if (chunkVerdicts.length === 0)
106
+ return null;
107
+ return aggregateChunkVerdicts(chunkVerdicts);
108
+ }
109
+ }
110
+ //# sourceMappingURL=api-race-confirmer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-race-confirmer.js","sourceRoot":"","sources":["../../src/extraction/api-race-confirmer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod';
2
+ import { LlmClient } from '../adapters/llm.js';
3
+ import type { SymbolRecord } from '../types.js';
4
+ declare const ConfirmationSchema: z.ZodObject<{
5
+ same_concept: z.ZodBoolean;
6
+ confidence: z.ZodNumber;
7
+ reason: z.ZodString;
8
+ }, z.core.$strip>;
9
+ export type ConfirmationResult = z.infer<typeof ConfirmationSchema>;
10
+ export declare class LlmConfirmer {
11
+ private llm;
12
+ private cache;
13
+ constructor(llm?: LlmClient);
14
+ confirmSameConcept(a: SymbolRecord, b: SymbolRecord,
15
+ /**
16
+ * Project conventions text (CLAUDE.md / AGENTS.md / etc., already
17
+ * concatenated and truncated). When present, the verdict weighs
18
+ * project-stated rules — e.g. "three similar lines is better than
19
+ * premature abstraction" turns this into `same_concept: false` even
20
+ * on a tight skeleton match.
21
+ */
22
+ projectConventions?: string): Promise<ConfirmationResult | null>;
23
+ }
24
+ export {};
25
+ //# sourceMappingURL=llm-confirmer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-confirmer.d.ts","sourceRoot":"","sources":["../../src/extraction/llm-confirmer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,SAAS,EAA0B,MAAM,oBAAoB,CAAC;AAGvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,QAAA,MAAM,kBAAkB;;;;iBAItB,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAwDpE,qBAAa,YAAY;IACvB,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,KAAK,CAAyC;gBAE1C,GAAG,CAAC,EAAE,SAAS;IAIrB,kBAAkB,CACtB,CAAC,EAAE,YAAY,EACf,CAAC,EAAE,YAAY;IACf;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAC1B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;CA+CtC"}