@wrongstack/plugins 0.293.0 → 0.295.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 (211) hide show
  1. package/README.md +7 -71
  2. package/dist/accessibility-auditor/index.d.ts +1 -1
  3. package/dist/accessibility-auditor/index.d.ts.map +1 -1
  4. package/dist/accessibility-auditor.js +50 -33
  5. package/dist/agent-handoff/index.d.ts +1 -1
  6. package/dist/agent-handoff/index.d.ts.map +1 -1
  7. package/dist/agent-handoff.js +30 -1
  8. package/dist/api-compatibility-gate/index.d.ts +1 -1
  9. package/dist/api-compatibility-gate/index.d.ts.map +1 -1
  10. package/dist/api-compatibility-gate.js +21 -16
  11. package/dist/audit/index.d.ts +386 -0
  12. package/dist/audit/index.d.ts.map +1 -0
  13. package/dist/audit.d.ts +1 -0
  14. package/dist/audit.js +454 -0
  15. package/dist/auto-doc/index.d.ts +1 -1
  16. package/dist/auto-doc/index.d.ts.map +1 -1
  17. package/dist/auto-doc.js +3 -1
  18. package/dist/auto-escalate/index.d.ts +1 -1
  19. package/dist/auto-escalate/index.d.ts.map +1 -1
  20. package/dist/auto-i18n-extractor/index.d.ts +1 -1
  21. package/dist/auto-i18n-extractor/index.d.ts.map +1 -1
  22. package/dist/auto-i18n-extractor.js +7 -1
  23. package/dist/branch-guard/index.d.ts +1 -1
  24. package/dist/branch-guard/index.d.ts.map +1 -1
  25. package/dist/branch-guard.js +111 -1
  26. package/dist/catalog.d.ts +5 -30
  27. package/dist/catalog.d.ts.map +1 -1
  28. package/dist/changelog-writer/index.d.ts +1 -1
  29. package/dist/changelog-writer/index.d.ts.map +1 -1
  30. package/dist/changelog-writer.js +137 -3
  31. package/dist/checkpoint/index.d.ts +3 -1
  32. package/dist/checkpoint/index.d.ts.map +1 -1
  33. package/dist/checkpoint.js +41 -16
  34. package/dist/code-metrics/index.d.ts +1 -1
  35. package/dist/code-metrics/index.d.ts.map +1 -1
  36. package/dist/code-metrics.js +34 -29
  37. package/dist/commit-validator/index.d.ts +1 -1
  38. package/dist/commit-validator/index.d.ts.map +1 -1
  39. package/dist/commit-validator.js +17 -7
  40. package/dist/config-validator/index.d.ts +1 -1
  41. package/dist/config-validator/index.d.ts.map +1 -1
  42. package/dist/context-pins/index.d.ts +1 -1
  43. package/dist/context-pins/index.d.ts.map +1 -1
  44. package/dist/context-pins.js +6 -5
  45. package/dist/cost-tracker/index.d.ts +1 -1
  46. package/dist/cost-tracker/index.d.ts.map +1 -1
  47. package/dist/cost-tracker.js +109 -2
  48. package/dist/cron/index.d.ts +1 -1
  49. package/dist/cron/index.d.ts.map +1 -1
  50. package/dist/cron.js +1 -0
  51. package/dist/dead-code-detector/index.d.ts +1 -1
  52. package/dist/dead-code-detector/index.d.ts.map +1 -1
  53. package/dist/dead-code-detector.js +31 -18
  54. package/dist/dep-guard/index.d.ts +1 -1
  55. package/dist/dep-guard/index.d.ts.map +1 -1
  56. package/dist/dependency-vulnerability-gate/index.d.ts +1 -1
  57. package/dist/dependency-vulnerability-gate/index.d.ts.map +1 -1
  58. package/dist/dependency-vulnerability-gate.js +67 -20
  59. package/dist/diff-summary/index.d.ts +1 -1
  60. package/dist/diff-summary/index.d.ts.map +1 -1
  61. package/dist/diff-summary.js +128 -30
  62. package/dist/doc-sync-guard/index.d.ts +1 -1
  63. package/dist/doc-sync-guard/index.d.ts.map +1 -1
  64. package/dist/doc-sync-guard.js +7 -1
  65. package/dist/duplicate-code-detector/index.d.ts +26 -1
  66. package/dist/duplicate-code-detector/index.d.ts.map +1 -1
  67. package/dist/duplicate-code-detector.js +280 -69
  68. package/dist/error-lens/index.d.ts +1 -1
  69. package/dist/error-lens/index.d.ts.map +1 -1
  70. package/dist/factories/index.d.ts +4 -0
  71. package/dist/factories/index.d.ts.map +1 -0
  72. package/dist/factories.d.ts +1 -0
  73. package/dist/factories.js +74 -0
  74. package/dist/feature-flag-tracker/index.d.ts +1 -1
  75. package/dist/feature-flag-tracker/index.d.ts.map +1 -1
  76. package/dist/feature-flag-tracker.js +59 -33
  77. package/dist/file-watcher/index.d.ts +1 -1
  78. package/dist/file-watcher/index.d.ts.map +1 -1
  79. package/dist/file-watcher.js +6 -7
  80. package/dist/format-on-save/index.d.ts +3 -1
  81. package/dist/format-on-save/index.d.ts.map +1 -1
  82. package/dist/format-on-save.js +289 -59
  83. package/dist/generated-plugin-exports.d.ts +65 -0
  84. package/dist/generated-plugin-exports.d.ts.map +1 -0
  85. package/dist/git-autocommit/index.d.ts +1 -1
  86. package/dist/git-autocommit/index.d.ts.map +1 -1
  87. package/dist/import-organizer/index.d.ts +1 -1
  88. package/dist/import-organizer/index.d.ts.map +1 -1
  89. package/dist/import-organizer.js +141 -20
  90. package/dist/index.d.ts +4 -183
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +18298 -17279
  93. package/dist/injection-shield/index.d.ts +1 -1
  94. package/dist/injection-shield/index.d.ts.map +1 -1
  95. package/dist/interface-contract-guard/index.d.ts +1 -1
  96. package/dist/interface-contract-guard/index.d.ts.map +1 -1
  97. package/dist/interface-contract-guard.js +87 -53
  98. package/dist/knowledge-graph/index.d.ts +1 -1
  99. package/dist/knowledge-graph/index.d.ts.map +1 -1
  100. package/dist/knowledge-graph.js +7 -6
  101. package/dist/license-audit-gate/index.d.ts +1 -1
  102. package/dist/license-audit-gate/index.d.ts.map +1 -1
  103. package/dist/lint-gate/index.d.ts +1 -1
  104. package/dist/lint-gate/index.d.ts.map +1 -1
  105. package/dist/lint-gate.js +112 -1
  106. package/dist/llm-cache/index.d.ts +5 -1
  107. package/dist/llm-cache/index.d.ts.map +1 -1
  108. package/dist/llm-cache.js +16 -8
  109. package/dist/loop-breaker/index.d.ts +1 -1
  110. package/dist/loop-breaker/index.d.ts.map +1 -1
  111. package/dist/loop-breaker.js +25 -9
  112. package/dist/manifest/index.d.ts +16 -0
  113. package/dist/manifest/index.d.ts.map +1 -0
  114. package/dist/manifest.d.ts +1 -0
  115. package/dist/manifest.js +152 -0
  116. package/dist/migration-planner/index.d.ts +1 -1
  117. package/dist/migration-planner/index.d.ts.map +1 -1
  118. package/dist/migration-planner.js +5 -1
  119. package/dist/model-router/index.d.ts +1 -1
  120. package/dist/model-router/index.d.ts.map +1 -1
  121. package/dist/notify-hub/index.d.ts +1 -1
  122. package/dist/notify-hub/index.d.ts.map +1 -1
  123. package/dist/notify-hub/webhook-channel.d.ts +1 -1
  124. package/dist/notify-hub/webhook-channel.d.ts.map +1 -1
  125. package/dist/notify-hub.js +41 -4
  126. package/dist/path-guard/index.d.ts +1 -1
  127. package/dist/path-guard/index.d.ts.map +1 -1
  128. package/dist/performance-regression-gate/index.d.ts +1 -1
  129. package/dist/performance-regression-gate/index.d.ts.map +1 -1
  130. package/dist/plugin-stack-observer/index.d.ts +1 -1
  131. package/dist/plugin-stack-observer/index.d.ts.map +1 -1
  132. package/dist/pr-drafter/index.d.ts +1 -1
  133. package/dist/pr-drafter/index.d.ts.map +1 -1
  134. package/dist/pr-drafter.js +28 -29
  135. package/dist/process-guard/index.d.ts +1 -1
  136. package/dist/process-guard/index.d.ts.map +1 -1
  137. package/dist/prompt-firewall/index.d.ts +1 -1
  138. package/dist/prompt-firewall/index.d.ts.map +1 -1
  139. package/dist/prompt-firewall.js +140 -11
  140. package/dist/refactor-suggester/index.d.ts +1 -1
  141. package/dist/refactor-suggester/index.d.ts.map +1 -1
  142. package/dist/refactor-suggester.js +148 -33
  143. package/dist/release-notes-generator/index.d.ts +1 -1
  144. package/dist/release-notes-generator/index.d.ts.map +1 -1
  145. package/dist/release-notes-generator.js +29 -22
  146. package/dist/runtime/bounded-map.d.ts +86 -0
  147. package/dist/runtime/bounded-map.d.ts.map +1 -0
  148. package/dist/runtime/credential-patterns.d.ts +42 -0
  149. package/dist/runtime/credential-patterns.d.ts.map +1 -0
  150. package/dist/runtime/index.d.ts +18 -0
  151. package/dist/runtime/index.d.ts.map +1 -1
  152. package/dist/runtime/llm.d.ts +1 -1
  153. package/dist/runtime/llm.d.ts.map +1 -1
  154. package/dist/runtime/local-bin.d.ts +120 -0
  155. package/dist/runtime/local-bin.d.ts.map +1 -0
  156. package/dist/runtime/safe-json.d.ts +25 -0
  157. package/dist/runtime/safe-json.d.ts.map +1 -0
  158. package/dist/runtime.js +332 -22
  159. package/dist/schema-evolution-guard/index.d.ts +1 -1
  160. package/dist/schema-evolution-guard/index.d.ts.map +1 -1
  161. package/dist/schema-evolution-guard.js +7 -1
  162. package/dist/secret-scanner/index.d.ts +1 -1
  163. package/dist/secret-scanner/index.d.ts.map +1 -1
  164. package/dist/secret-scanner.js +181 -48
  165. package/dist/security-hotspot-scanner/index.d.ts +1 -1
  166. package/dist/security-hotspot-scanner/index.d.ts.map +1 -1
  167. package/dist/security-hotspot-scanner.js +158 -20
  168. package/dist/semantic-search-indexer/index.d.ts +1 -1
  169. package/dist/semantic-search-indexer/index.d.ts.map +1 -1
  170. package/dist/semantic-search-indexer.js +7 -1
  171. package/dist/semver-bump/index.d.ts +1 -1
  172. package/dist/semver-bump/index.d.ts.map +1 -1
  173. package/dist/semver-bump.js +97 -43
  174. package/dist/session-recap/index.d.ts +1 -1
  175. package/dist/session-recap/index.d.ts.map +1 -1
  176. package/dist/shell-check/index.d.ts +1 -1
  177. package/dist/shell-check/index.d.ts.map +1 -1
  178. package/dist/smart-rename/index.d.ts +1 -1
  179. package/dist/smart-rename/index.d.ts.map +1 -1
  180. package/dist/spec-linker/index.d.ts +1 -1
  181. package/dist/spec-linker/index.d.ts.map +1 -1
  182. package/dist/spec-linker.js +222 -20772
  183. package/dist/template-engine/index.d.ts +1 -1
  184. package/dist/template-engine/index.d.ts.map +1 -1
  185. package/dist/template-engine.js +34 -8
  186. package/dist/test-coverage-gate/index.d.ts +1 -1
  187. package/dist/test-coverage-gate/index.d.ts.map +1 -1
  188. package/dist/test-coverage-gate.js +34 -14
  189. package/dist/test-flake-detector/index.d.ts +1 -1
  190. package/dist/test-flake-detector/index.d.ts.map +1 -1
  191. package/dist/test-flake-detector.js +31 -20
  192. package/dist/test-generator/index.d.ts +1 -1
  193. package/dist/test-generator/index.d.ts.map +1 -1
  194. package/dist/test-runner-gate/index.d.ts +1 -1
  195. package/dist/test-runner-gate/index.d.ts.map +1 -1
  196. package/dist/test-runner-gate.js +132 -9
  197. package/dist/todo-listener/index.d.ts +1 -32
  198. package/dist/todo-listener/index.d.ts.map +1 -1
  199. package/dist/todo-listener.js +30 -1
  200. package/dist/todo-tracker/index.d.ts +1 -1
  201. package/dist/todo-tracker/index.d.ts.map +1 -1
  202. package/dist/todo-tracker.js +3 -4
  203. package/dist/token-budget/index.d.ts +1 -1
  204. package/dist/token-budget/index.d.ts.map +1 -1
  205. package/dist/token-throttle/index.d.ts +1 -1
  206. package/dist/token-throttle/index.d.ts.map +1 -1
  207. package/dist/token-throttle.js +17 -2
  208. package/dist/type-gate/index.d.ts +1 -1
  209. package/dist/type-gate/index.d.ts.map +1 -1
  210. package/dist/type-gate.js +106 -19
  211. package/package.json +80 -68
@@ -40,7 +40,7 @@
40
40
  *
41
41
  * @public
42
42
  */
43
- import type { Plugin } from '@wrongstack/core';
43
+ import type { Plugin } from '@wrongstack/core/types';
44
44
  export declare function scanForInjection(text: string): string[];
45
45
  declare const plugin: Plugin;
46
46
  export default plugin;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/injection-shield/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA6G/C,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAMD,QAAA,MAAM,MAAM,EAAE,MAwJb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/injection-shield/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AA6GrD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAMD,QAAA,MAAM,MAAM,EAAE,MAwJb,CAAC;eAEa,MAAM"}
@@ -23,7 +23,7 @@
23
23
  *
24
24
  * @public
25
25
  */
26
- import type { Plugin } from '@wrongstack/core';
26
+ import type { Plugin } from '@wrongstack/core/types';
27
27
  export interface InterfaceContractFinding {
28
28
  interfaceName: string;
29
29
  file: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/interface-contract-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAS/C,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAqID,QAAA,MAAM,MAAM,EAAE,MAuMb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/interface-contract-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AASrD,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAqLD,QAAA,MAAM,MAAM,EAAE,MAyNb,CAAC;eAEa,MAAM"}
@@ -1,10 +1,14 @@
1
1
  // src/interface-contract-guard/index.ts
2
- import { readFileSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
3
  import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
4
4
 
5
5
  // src/runtime/index.ts
6
- import { existsSync, readdirSync, statSync } from "node:fs";
7
6
  import { basename, extname, isAbsolute, relative, resolve } from "node:path";
7
+
8
+ // src/runtime/local-bin.ts
9
+ import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
10
+
11
+ // src/runtime/index.ts
8
12
  var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
9
13
  function hasLeadingDash(arg) {
10
14
  return arg.length > 0 && arg.startsWith("-");
@@ -17,44 +21,45 @@ function withinProjectPath(projectRoot, candidate) {
17
21
  return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
18
22
  }
19
23
  function withinProject(p) {
20
- return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
24
+ const cwd = process.cwd();
25
+ return withinProjectPath(cwd, p) || relative(cwd, p) === ".";
21
26
  }
22
27
  var DEFAULT_EXCLUDE_DIRS = ["node_modules", "dist", ".git", "coverage"];
23
- function collectSourceFiles(root, opts) {
28
+ async function collectSourceFilesAsync(root, opts) {
29
+ const { readdir, stat } = await import("node:fs/promises");
24
30
  const files = [];
25
- if (!existsSync(root)) return files;
26
- const s = statSync(root);
27
- if (s.isFile()) {
28
- if (matchesExtension(root, opts.extensions)) files.push(root);
31
+ try {
32
+ const s = await stat(root);
33
+ if (s.isFile()) {
34
+ if (matchesExtension(root, opts.extensions)) files.push(root);
35
+ return files;
36
+ }
37
+ if (!s.isDirectory()) return files;
38
+ } catch {
29
39
  return files;
30
40
  }
31
- if (!s.isDirectory()) return files;
32
41
  const exclude = opts.excludeDirs ?? DEFAULT_EXCLUDE_DIRS;
33
- function walk(dir, depth) {
42
+ const excludeSet = new Set(exclude);
43
+ async function walk(dir, depth) {
34
44
  if (opts.maxDepth !== void 0 && depth > opts.maxDepth) return;
35
45
  let entries;
36
46
  try {
37
- entries = readdirSync(dir);
47
+ entries = await readdir(dir, { withFileTypes: true });
38
48
  } catch {
39
49
  return;
40
50
  }
51
+ entries.sort((a, b) => a.name.localeCompare(b.name));
41
52
  for (const entry of entries) {
42
- if (exclude.includes(entry)) continue;
43
- const full = resolve(dir, entry);
44
- let st;
45
- try {
46
- st = statSync(full);
47
- } catch {
48
- continue;
49
- }
50
- if (st.isDirectory()) {
51
- walk(full, depth + 1);
52
- } else if (st.isFile() && matchesExtension(full, opts.extensions)) {
53
+ if (excludeSet.has(entry.name)) continue;
54
+ const full = resolve(dir, entry.name);
55
+ if (entry.isDirectory()) {
56
+ await walk(full, depth + 1);
57
+ } else if (entry.isFile() && matchesExtension(full, opts.extensions)) {
53
58
  files.push(full);
54
59
  }
55
60
  }
56
61
  }
57
- walk(root, 0);
62
+ await walk(root, 0);
58
63
  return files;
59
64
  }
60
65
  function matchesExtension(p, exts) {
@@ -74,7 +79,8 @@ var state = {
74
79
  var DEFAULTS = {
75
80
  enabled: false,
76
81
  extensions: [".ts", ".tsx"],
77
- maxFindings: 50
82
+ maxFindings: 50,
83
+ maxFiles: 2e3
78
84
  };
79
85
  function readConfig(raw) {
80
86
  if (!raw || typeof raw !== "object") return { ...DEFAULTS };
@@ -82,7 +88,8 @@ function readConfig(raw) {
82
88
  return {
83
89
  enabled: r["enabled"] !== false,
84
90
  extensions: Array.isArray(r["extensions"]) ? r["extensions"].filter((x) => typeof x === "string") : DEFAULTS.extensions,
85
- maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings
91
+ maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings,
92
+ maxFiles: typeof r["maxFiles"] === "number" && r["maxFiles"] >= 1 && r["maxFiles"] <= 5e4 ? Math.floor(r["maxFiles"]) : DEFAULTS.maxFiles
86
93
  };
87
94
  }
88
95
  function normalizeExtensions(exts) {
@@ -98,47 +105,60 @@ var INTERFACE_RE = /(?:export\s+)?interface\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
98
105
  function extractInterfaceNames(content) {
99
106
  const names = [];
100
107
  INTERFACE_RE.lastIndex = 0;
108
+ let searchedUpTo = 0;
109
+ let line = 1;
101
110
  for (const m of content.matchAll(INTERFACE_RE)) {
102
- names.push({ name: m[1], line: content.slice(0, m.index).split(/\r?\n/).length });
111
+ const index = m.index ?? 0;
112
+ for (let i = searchedUpTo; i < index; i++) {
113
+ if (content.charCodeAt(i) === 10) line += 1;
114
+ }
115
+ searchedUpTo = index;
116
+ names.push({ name: m[1], line });
103
117
  }
104
118
  return names;
105
119
  }
106
- function hasImplementer(name, contents) {
107
- const implRe = new RegExp(`(?:implements|satisfies|\\bas)\\s+${name}\\b`, "g");
108
- for (const content of contents) {
109
- if (implRe.test(content)) return true;
120
+ var IMPLEMENTER_RE = /(?:implements|satisfies|\bas)\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
121
+ function collectImplementedNames(content, into) {
122
+ IMPLEMENTER_RE.lastIndex = 0;
123
+ for (const m of content.matchAll(IMPLEMENTER_RE)) {
124
+ const name = m[1];
125
+ if (name) into.add(name);
110
126
  }
111
- return false;
112
127
  }
113
- function scanPath(rawPath, cfg) {
128
+ async function scanPath(rawPath, cfg) {
114
129
  const root = process.cwd();
115
130
  const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
116
131
  const exts = normalizeExtensions(cfg.extensions);
117
- const files = collectSourceFiles(resolved, { extensions: exts });
118
- const contents = [];
132
+ const allFiles = await collectSourceFilesAsync(resolved, { extensions: exts });
133
+ const files = allFiles.slice(0, cfg.maxFiles);
134
+ const implemented = /* @__PURE__ */ new Set();
135
+ const declarations = [];
136
+ let scannedFiles = 0;
119
137
  for (const p of files) {
138
+ let content;
120
139
  try {
121
- contents.push({ path: p, content: readFileSync(p, "utf-8") });
140
+ content = await readFile(p, "utf-8");
122
141
  } catch {
142
+ continue;
123
143
  }
124
- }
125
- const allContentStrings = contents.map((c) => c.content);
126
- const findings = [];
127
- for (const { path, content } of contents) {
144
+ scannedFiles += 1;
145
+ collectImplementedNames(content, implemented);
128
146
  for (const { name, line } of extractInterfaceNames(content)) {
129
- if (!hasImplementer(name, allContentStrings)) {
130
- findings.push({
131
- interfaceName: name,
132
- file: relativePath(path),
133
- line,
134
- message: `interface "${name}" is declared but has no visible implementer`
135
- });
136
- if (findings.length >= cfg.maxFindings) break;
137
- }
147
+ declarations.push({ name, line, file: p });
138
148
  }
149
+ }
150
+ const findings = [];
151
+ for (const { name, line, file } of declarations) {
152
+ if (implemented.has(name)) continue;
153
+ findings.push({
154
+ interfaceName: name,
155
+ file: relativePath(file),
156
+ line,
157
+ message: `interface "${name}" is declared but has no visible implementer`
158
+ });
139
159
  if (findings.length >= cfg.maxFindings) break;
140
160
  }
141
- return { findings, scannedFiles: contents.length };
161
+ return { findings, scannedFiles, truncated: allFiles.length > files.length };
142
162
  }
143
163
  var plugin = {
144
164
  name: "interface-contract-guard",
@@ -157,6 +177,13 @@ var plugin = {
157
177
  default: [".ts", ".tsx"],
158
178
  description: "File extensions to scan."
159
179
  },
180
+ maxFiles: {
181
+ type: "number",
182
+ minimum: 1,
183
+ maximum: 5e4,
184
+ default: 2e3,
185
+ description: "Upper bound on files read per scan."
186
+ },
160
187
  maxFindings: {
161
188
  type: "number",
162
189
  minimum: 1,
@@ -180,7 +207,7 @@ var plugin = {
180
207
  state.hookUnregister = null;
181
208
  }
182
209
  const cfg = readConfig(api.config.extensions?.["interface-contract-guard"]);
183
- const hook = (input) => {
210
+ const hook = async (input) => {
184
211
  if (!cfg.enabled) return;
185
212
  if (input.toolResult?.isError) return;
186
213
  const inp = input.toolInput ?? {};
@@ -193,7 +220,7 @@ var plugin = {
193
220
  const resolved = resolve2(process.cwd(), sourcePath);
194
221
  let content;
195
222
  try {
196
- content = readFileSync(resolved, "utf-8");
223
+ content = await readFile(resolved, "utf-8");
197
224
  } catch {
198
225
  state.errorCount += 1;
199
226
  return;
@@ -230,7 +257,7 @@ If you changed member shapes, search the project for implementers/\`satisfies\`/
230
257
  state.scanCount += 1;
231
258
  let result;
232
259
  try {
233
- result = scanPath(rawPath, cfg);
260
+ result = await scanPath(rawPath, cfg);
234
261
  } catch (err) {
235
262
  state.errorCount += 1;
236
263
  return { ok: false, error: String(err) };
@@ -240,7 +267,14 @@ If you changed member shapes, search the project for implementers/\`satisfies\`/
240
267
  ok: true,
241
268
  path: relativePath(resolve2(process.cwd(), rawPath)),
242
269
  scannedFiles: result.scannedFiles,
243
- findings: result.findings
270
+ findings: result.findings,
271
+ // Say so when the corpus was cut short. A partial scan that
272
+ // reports "no findings" is not the same as a clean result, and
273
+ // the caller has no other way to tell the difference.
274
+ truncated: result.truncated,
275
+ ...result.truncated ? {
276
+ note: `Scan stopped at the maxFiles limit (${cfg.maxFiles}); results are partial. Raise \`maxFiles\` or scan a narrower path for full coverage.`
277
+ } : {}
244
278
  };
245
279
  }
246
280
  });
@@ -28,7 +28,7 @@
28
28
  *
29
29
  * @public
30
30
  */
31
- import type { Plugin } from '@wrongstack/core';
31
+ import type { Plugin } from '@wrongstack/core/types';
32
32
  export interface Fact {
33
33
  id: string;
34
34
  subject: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/knowledge-graph/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAQ/C,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;CACnB;AA2HD,QAAA,MAAM,MAAM,EAAE,MA2Sb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/knowledge-graph/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AASrD,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;CACnB;AAiID,QAAA,MAAM,MAAM,EAAE,MA2Sb,CAAC;eAEa,MAAM"}
@@ -1,6 +1,7 @@
1
1
  // src/knowledge-graph/index.ts
2
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { readFileSync } from "node:fs";
3
3
  import { dirname, isAbsolute, relative, resolve } from "node:path";
4
+ import { atomicWrite, ensureDir } from "@wrongstack/core/utils";
4
5
  var API_VERSION = "^0.1.10";
5
6
  var state = {
6
7
  facts: [],
@@ -52,11 +53,11 @@ function loadFacts(filePath) {
52
53
  return { facts: [], nextId: 1 };
53
54
  }
54
55
  }
55
- function persistFacts(filePath) {
56
+ async function persistFacts(filePath) {
56
57
  if (!filePath) return true;
57
58
  try {
58
- mkdirSync(dirname(filePath), { recursive: true });
59
- writeFileSync(
59
+ await ensureDir(dirname(filePath));
60
+ await atomicWrite(
60
61
  filePath,
61
62
  JSON.stringify({ facts: state.facts, nextId: state.nextId }, null, 2)
62
63
  );
@@ -187,7 +188,7 @@ var plugin = {
187
188
  state.facts.push(fact);
188
189
  state.adds += 1;
189
190
  api.metrics.counter("adds");
190
- const persisted = persistFacts(resolved);
191
+ const persisted = await persistFacts(resolved);
191
192
  return { ok: true, fact, persisted, totalFacts: state.facts.length };
192
193
  }
193
194
  });
@@ -247,7 +248,7 @@ var plugin = {
247
248
  if (removed === 0) return { ok: false, error: `no fact matches "${input.id}"` };
248
249
  state.removals += removed;
249
250
  api.metrics.counter("removals", removed);
250
- const persisted = persistFacts(resolved);
251
+ const persisted = await persistFacts(resolved);
251
252
  return { ok: true, removed, persisted, totalFacts: state.facts.length };
252
253
  }
253
254
  });
@@ -26,7 +26,7 @@
26
26
  *
27
27
  * @public
28
28
  */
29
- import type { Plugin } from '@wrongstack/core';
29
+ import type { Plugin } from '@wrongstack/core/types';
30
30
  declare const plugin: Plugin;
31
31
  export default plugin;
32
32
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/license-audit-gate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAkK/C,QAAA,MAAM,MAAM,EAAE,MAqMb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/license-audit-gate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAkKrD,QAAA,MAAM,MAAM,EAAE,MAqMb,CAAC;eAEa,MAAM"}
@@ -25,7 +25,7 @@
25
25
  *
26
26
  * @public
27
27
  */
28
- import type { Plugin } from '@wrongstack/core';
28
+ import type { Plugin } from '@wrongstack/core/types';
29
29
  declare const plugin: Plugin;
30
30
  export default plugin;
31
31
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lint-gate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAQH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAwU/C,QAAA,MAAM,MAAM,EAAE,MAwWb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lint-gate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAQH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AA+VrD,QAAA,MAAM,MAAM,EAAE,MA2Wb,CAAC;eAEa,MAAM"}
package/dist/lint-gate.js CHANGED
@@ -5,6 +5,108 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
5
5
  import { createRequire } from "node:module";
6
6
  import { tmpdir } from "node:os";
7
7
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
8
+
9
+ // src/runtime/local-bin.ts
10
+ import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
11
+
12
+ // src/runtime/bounded-map.ts
13
+ var BoundedMap = class {
14
+ map = /* @__PURE__ */ new Map();
15
+ max;
16
+ ttlMs;
17
+ now;
18
+ /** Entries dropped to stay under `max`. Surfaced by plugin health(). */
19
+ evictions = 0;
20
+ constructor(options) {
21
+ const normalizedMax = Math.floor(options.max);
22
+ this.max = Number.isSafeInteger(normalizedMax) ? Math.max(1, normalizedMax) : 1;
23
+ this.ttlMs = options.ttlMs;
24
+ this.now = options.now ?? Date.now;
25
+ }
26
+ expired(entry) {
27
+ return this.ttlMs !== void 0 && this.now() - entry.storedAt > this.ttlMs;
28
+ }
29
+ get(key) {
30
+ const entry = this.map.get(key);
31
+ if (entry === void 0) return void 0;
32
+ if (this.expired(entry)) {
33
+ this.map.delete(key);
34
+ return void 0;
35
+ }
36
+ this.map.delete(key);
37
+ this.map.set(key, entry);
38
+ return entry.value;
39
+ }
40
+ /**
41
+ * Read without promoting the key to most-recently-used. Use for
42
+ * diagnostics that must not perturb the eviction order.
43
+ */
44
+ peek(key) {
45
+ const entry = this.map.get(key);
46
+ if (entry === void 0 || this.expired(entry)) return void 0;
47
+ return entry.value;
48
+ }
49
+ has(key) {
50
+ const entry = this.map.get(key);
51
+ if (entry === void 0) return false;
52
+ if (this.expired(entry)) {
53
+ this.map.delete(key);
54
+ return false;
55
+ }
56
+ return true;
57
+ }
58
+ set(key, value) {
59
+ this.map.delete(key);
60
+ this.map.set(key, { value, storedAt: this.now() });
61
+ while (this.map.size > this.max) {
62
+ const coldest = this.map.keys().next().value;
63
+ if (coldest === void 0) break;
64
+ this.map.delete(coldest);
65
+ this.evictions += 1;
66
+ }
67
+ return this;
68
+ }
69
+ delete(key) {
70
+ return this.map.delete(key);
71
+ }
72
+ clear() {
73
+ this.map.clear();
74
+ this.evictions = 0;
75
+ }
76
+ get size() {
77
+ return this.map.size;
78
+ }
79
+ /** How many entries have been dropped to respect `max`, since the last clear. */
80
+ get evictionCount() {
81
+ return this.evictions;
82
+ }
83
+ /** Drop every expired entry. Cheap enough to call from a status tool. */
84
+ prune() {
85
+ if (this.ttlMs === void 0) return 0;
86
+ let removed = 0;
87
+ for (const [key, entry] of this.map) {
88
+ if (this.expired(entry)) {
89
+ this.map.delete(key);
90
+ removed += 1;
91
+ }
92
+ }
93
+ return removed;
94
+ }
95
+ /** Live (non-expired) entries, coldest first. */
96
+ *entries() {
97
+ for (const [key, entry] of this.map) {
98
+ if (!this.expired(entry)) yield [key, entry.value];
99
+ }
100
+ }
101
+ [Symbol.iterator]() {
102
+ return this.entries();
103
+ }
104
+ };
105
+
106
+ // src/runtime/index.ts
107
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
108
+
109
+ // src/lint-gate/index.ts
8
110
  var API_VERSION = "^0.1.10";
9
111
  var state = {
10
112
  /** Total PreToolUse invocations. */
@@ -42,6 +144,7 @@ var LINTER_PACKAGES = {
42
144
  biome: "@biomejs/biome",
43
145
  eslint: "eslint"
44
146
  };
147
+ var linterCache = new BoundedMap({ max: 32, ttlMs: 3e5 });
45
148
  function isInside(parent, candidate) {
46
149
  const rel = relative(parent, candidate);
47
150
  return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel);
@@ -89,13 +192,20 @@ function runCommand(command, args, timeoutMs, cwd, signal) {
89
192
  });
90
193
  }
91
194
  async function detectLinter(requested, cwd) {
195
+ const cacheKey = `${requested}:${cwd}`;
196
+ const cached = linterCache.get(cacheKey);
197
+ if (cached !== void 0) return cached;
92
198
  const candidates = requested === "auto" ? ["biome", "eslint"] : [requested];
93
199
  for (const name of candidates) {
94
200
  const linter = resolveLocalLinter(name, cwd);
95
201
  if (!linter) continue;
96
202
  const probe = await runCommand(linter.cmd, [linter.args[0], "--version"], 5e3, cwd);
97
- if (!probe.error) return linter;
203
+ if (!probe.error) {
204
+ linterCache.set(cacheKey, linter);
205
+ return linter;
206
+ }
98
207
  }
208
+ linterCache.set(cacheKey, null);
99
209
  return null;
100
210
  }
101
211
  async function lintContent(content, filePath, linter, timeoutMs, cwd, signal) {
@@ -223,6 +333,7 @@ var plugin = {
223
333
  state.linterErrorCount = 0;
224
334
  state.hookUnregister = null;
225
335
  state.lastResult = null;
336
+ linterCache.clear();
226
337
  const cfg = readConfig(api.config.extensions?.["lint-gate"]);
227
338
  const cwd = resolve(api.config.cwd ?? process.cwd());
228
339
  const linterReady = detectLinter(cfg.linter, cwd).then((linter) => {
@@ -37,7 +37,7 @@
37
37
  *
38
38
  * @public
39
39
  */
40
- import type { Plugin } from '@wrongstack/core';
40
+ import type { Plugin } from '@wrongstack/core/types';
41
41
  /**
42
42
  * Whether a request is safe to cache under `onlyDeterministic`. Sampled
43
43
  * requests (temperature > 0) return different content each call, so
@@ -47,6 +47,10 @@ export declare function isDeterministic(request: Record<string, unknown>): boole
47
47
  /**
48
48
  * Stable fingerprint of the request fields that affect the response.
49
49
  * Excludes anything cosmetic (user id, etc.). Same inputs → same key.
50
+ *
51
+ * Determinism: uses canonical JSON key ordering (alphabetical) so
52
+ * identical semantic requests produce identical fingerprints regardless
53
+ * of property insertion order.
50
54
  */
51
55
  export declare function fingerprintRequest(request: Record<string, unknown>): string;
52
56
  declare const plugin: Plugin;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/llm-cache/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAa,MAAM,kBAAkB,CAAC;AAwF1D;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAGzE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAiB3E;AAkCD,QAAA,MAAM,MAAM,EAAE,MAkNb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/llm-cache/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAa,MAAM,wBAAwB,CAAC;AAwFhE;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAGzE;AAeD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAsB3E;AA6CD,QAAA,MAAM,MAAM,EAAE,MAkNb,CAAC;eAEa,MAAM"}
package/dist/llm-cache.js CHANGED
@@ -32,21 +32,26 @@ function isDeterministic(request) {
32
32
  const t = request["temperature"];
33
33
  return t === void 0 || t === 0;
34
34
  }
35
+ var fingerprintCache = /* @__PURE__ */ new WeakMap();
35
36
  function fingerprintRequest(request) {
37
+ const cached = fingerprintCache.get(request);
38
+ if (cached) return cached;
36
39
  const subset = {
40
+ maxTokens: request["maxTokens"] ?? null,
41
+ messages: request["messages"] ?? null,
37
42
  model: request["model"] ?? null,
43
+ reasoning: request["reasoning"] ?? null,
44
+ responseFormat: request["responseFormat"] ?? null,
45
+ stopSequences: request["stopSequences"] ?? null,
38
46
  system: request["system"] ?? null,
39
- messages: request["messages"] ?? null,
40
- tools: Array.isArray(request["tools"]) ? request["tools"].map((t) => t?.name ?? t) : null,
41
47
  temperature: request["temperature"] ?? null,
42
- topP: request["topP"] ?? null,
48
+ tools: Array.isArray(request["tools"]) ? request["tools"].map((t) => t?.name ?? t) : null,
43
49
  topK: request["topK"] ?? null,
44
- maxTokens: request["maxTokens"] ?? null,
45
- stopSequences: request["stopSequences"] ?? null,
46
- responseFormat: request["responseFormat"] ?? null,
47
- reasoning: request["reasoning"] ?? null
50
+ topP: request["topP"] ?? null
48
51
  };
49
- return createHash("sha256").update(JSON.stringify(subset)).digest("hex");
52
+ const fingerprint = createHash("sha256").update(JSON.stringify(subset)).digest("hex");
53
+ fingerprintCache.set(request, fingerprint);
54
+ return fingerprint;
50
55
  }
51
56
  function lruGet(key, ttlMs) {
52
57
  const entry = state.cache.get(key);
@@ -60,6 +65,9 @@ function lruGet(key, ttlMs) {
60
65
  return entry;
61
66
  }
62
67
  function lruSet(key, response, maxEntries) {
68
+ if (state.cache.has(key)) {
69
+ state.cache.delete(key);
70
+ }
63
71
  state.cache.set(key, { response, storedAt: Date.now(), hits: 0 });
64
72
  while (state.cache.size > maxEntries) {
65
73
  const oldest = state.cache.keys().next().value;
@@ -1,4 +1,4 @@
1
- import type { Plugin } from '@wrongstack/core';
1
+ import type { Plugin } from '@wrongstack/core/types';
2
2
  declare const plugin: Plugin;
3
3
  export default plugin;
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/loop-breaker/index.ts"],"names":[],"mappings":"AA2CA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAqP/C,QAAA,MAAM,MAAM,EAAE,MA6Yb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/loop-breaker/index.ts"],"names":[],"mappings":"AA2CA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAmSrD,QAAA,MAAM,MAAM,EAAE,MA+Yb,CAAC;eAEa,MAAM"}