@wrongstack/plugins 0.286.0 → 0.289.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 (74) hide show
  1. package/README.md +20 -11
  2. package/dist/accessibility-auditor/index.d.ts.map +1 -1
  3. package/dist/accessibility-auditor.js +67 -55
  4. package/dist/api-compatibility-gate.js +1 -1
  5. package/dist/auto-doc.js +1 -1
  6. package/dist/auto-i18n-extractor/index.d.ts.map +1 -1
  7. package/dist/auto-i18n-extractor.js +20 -12
  8. package/dist/changelog-writer.js +1 -1
  9. package/dist/code-metrics/index.d.ts.map +1 -1
  10. package/dist/code-metrics.js +63 -50
  11. package/dist/commit-validator.js +1 -1
  12. package/dist/config-validator.js +1 -1
  13. package/dist/cost-tracker/index.d.ts.map +1 -1
  14. package/dist/cost-tracker.js +4 -2
  15. package/dist/cron/index.d.ts.map +1 -1
  16. package/dist/cron.js +29 -0
  17. package/dist/dead-code-detector/index.d.ts.map +1 -1
  18. package/dist/dead-code-detector.js +28 -19
  19. package/dist/dep-guard.js +1 -1
  20. package/dist/diff-summary/index.d.ts.map +1 -1
  21. package/dist/diff-summary.js +19 -11
  22. package/dist/doc-sync-guard/index.d.ts.map +1 -1
  23. package/dist/doc-sync-guard.js +25 -16
  24. package/dist/duplicate-code-detector/index.d.ts.map +1 -1
  25. package/dist/duplicate-code-detector.js +70 -57
  26. package/dist/error-lens.js +1 -1
  27. package/dist/feature-flag-tracker/index.d.ts.map +1 -1
  28. package/dist/feature-flag-tracker.js +69 -56
  29. package/dist/file-watcher/index.d.ts.map +1 -1
  30. package/dist/file-watcher.js +21 -4
  31. package/dist/format-on-save/index.d.ts.map +1 -1
  32. package/dist/format-on-save.js +19 -11
  33. package/dist/git-autocommit.js +1 -1
  34. package/dist/import-organizer/index.d.ts.map +1 -1
  35. package/dist/import-organizer.js +23 -14
  36. package/dist/index.d.ts +4 -4
  37. package/dist/index.js +1167 -1048
  38. package/dist/interface-contract-guard/index.d.ts.map +1 -1
  39. package/dist/interface-contract-guard.js +64 -51
  40. package/dist/lint-gate/index.d.ts.map +1 -1
  41. package/dist/lint-gate.js +61 -30
  42. package/dist/migration-planner/index.d.ts +12 -2
  43. package/dist/migration-planner/index.d.ts.map +1 -1
  44. package/dist/migration-planner.js +210 -28
  45. package/dist/pr-drafter.js +1 -1
  46. package/dist/prompt-firewall/index.d.ts +14 -6
  47. package/dist/prompt-firewall/index.d.ts.map +1 -1
  48. package/dist/prompt-firewall.js +7 -6
  49. package/dist/refactor-suggester/index.d.ts.map +1 -1
  50. package/dist/refactor-suggester.js +69 -56
  51. package/dist/release-notes-generator/index.d.ts +3 -1
  52. package/dist/release-notes-generator/index.d.ts.map +1 -1
  53. package/dist/release-notes-generator.js +152 -12
  54. package/dist/runtime/index.d.ts +36 -0
  55. package/dist/runtime/index.d.ts.map +1 -1
  56. package/dist/runtime/llm.d.ts +33 -0
  57. package/dist/runtime/llm.d.ts.map +1 -0
  58. package/dist/runtime.js +119 -25
  59. package/dist/schema-evolution-guard/index.d.ts.map +1 -1
  60. package/dist/schema-evolution-guard.js +22 -13
  61. package/dist/secret-scanner/index.d.ts.map +1 -1
  62. package/dist/secret-scanner.js +17 -6
  63. package/dist/security-hotspot-scanner/index.d.ts.map +1 -1
  64. package/dist/security-hotspot-scanner.js +24 -15
  65. package/dist/session-recap.js +1 -1
  66. package/dist/spec-linker.js +1167 -1048
  67. package/dist/test-generator/index.d.ts +5 -3
  68. package/dist/test-generator/index.d.ts.map +1 -1
  69. package/dist/test-generator.js +169 -26
  70. package/dist/test-runner-gate/index.d.ts.map +1 -1
  71. package/dist/test-runner-gate.js +65 -43
  72. package/dist/type-gate/index.d.ts.map +1 -1
  73. package/dist/type-gate.js +23 -35
  74. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dead-code-detector/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAyQ/C,QAAA,MAAM,MAAM,EAAE,MAkOb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dead-code-detector/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAiQ/C,QAAA,MAAM,MAAM,EAAE,MAkOb,CAAC;eAEa,MAAM"}
@@ -1,6 +1,25 @@
1
1
  // src/dead-code-detector/index.ts
2
2
  import { readdirSync, readFileSync, statSync } from "node:fs";
3
- import { extname, isAbsolute, join, relative, resolve } from "node:path";
3
+ import { extname as extname2, join, relative as relative2, resolve as resolve2 } from "node:path";
4
+
5
+ // src/runtime/index.ts
6
+ import { basename, extname, isAbsolute, relative, resolve } from "node:path";
7
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
8
+ function hasLeadingDash(arg) {
9
+ return arg.length > 0 && arg.startsWith("-");
10
+ }
11
+ function withinProjectPath(projectRoot, candidate) {
12
+ if (candidate.length === 0 || candidate.length > 4096) return false;
13
+ if (hasLeadingDash(candidate)) return false;
14
+ const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
15
+ const rel = relative(projectRoot, resolved);
16
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
17
+ }
18
+ function withinProject(p) {
19
+ return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
20
+ }
21
+
22
+ // src/dead-code-detector/index.ts
4
23
  var API_VERSION = "^0.1.10";
5
24
  var state = {
6
25
  scanCount: 0,
@@ -44,14 +63,14 @@ function gatherFiles(root, depth, cfg) {
44
63
  walk(join(dir, entry.name), remaining - 1);
45
64
  }
46
65
  } else if (entry.isFile()) {
47
- const ext = extname(entry.name).toLowerCase();
66
+ const ext = extname2(entry.name).toLowerCase();
48
67
  if (cfg.extensions.includes(ext)) {
49
68
  files.push(join(dir, entry.name));
50
69
  }
51
70
  }
52
71
  }
53
72
  }
54
- walk(resolve(root), depth);
73
+ walk(resolve2(root), depth);
55
74
  return files;
56
75
  }
57
76
  function stripNoise(content) {
@@ -134,22 +153,12 @@ function scan(root, depth, cfg) {
134
153
  }
135
154
  return { findings: findUnusedSymbols(files), scannedFiles: files.length };
136
155
  }
137
- function withinProject(p) {
138
- if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
139
- const root = process.cwd();
140
- const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
141
- const rel = relative(root, resolved);
142
- if (rel === "" || rel === ".") return true;
143
- if (rel.startsWith("..")) return false;
144
- if (isAbsolute(rel)) return false;
145
- return true;
146
- }
147
156
  function resolveScanRoot(rawPath) {
148
- const resolved = resolve(process.cwd(), rawPath);
157
+ const resolved = resolve2(process.cwd(), rawPath);
149
158
  try {
150
159
  const stats = statSync(resolved);
151
160
  if (!stats.isDirectory()) {
152
- return resolve(resolved, "..");
161
+ return resolve2(resolved, "..");
153
162
  }
154
163
  } catch {
155
164
  }
@@ -159,7 +168,7 @@ function toPosix(p) {
159
168
  return p.replace(/\\/g, "/");
160
169
  }
161
170
  function relativePath(p) {
162
- return toPosix(relative(process.cwd(), p));
171
+ return toPosix(relative2(process.cwd(), p));
163
172
  }
164
173
  var plugin = {
165
174
  name: "dead-code-detector",
@@ -230,8 +239,8 @@ var plugin = {
230
239
  state.errorCount += 1;
231
240
  return;
232
241
  }
233
- const changedFile = resolve(process.cwd(), sourcePath);
234
- const relevant = result.findings.filter((f) => resolve(f.file) === changedFile);
242
+ const changedFile = resolve2(process.cwd(), sourcePath);
243
+ const relevant = result.findings.filter((f) => resolve2(f.file) === changedFile);
235
244
  if (relevant.length === 0) {
236
245
  state.missCount += 1;
237
246
  return;
@@ -247,7 +256,7 @@ ${list}
247
256
  Consider removing the export if it is not part of the public API.`;
248
257
  return { additionalContext: message };
249
258
  };
250
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
259
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
251
260
  api.tools.register({
252
261
  name: "dead_code_scan",
253
262
  description: "Scan TypeScript/JavaScript source files for exported symbols that appear unused anywhere in the project.",
package/dist/dep-guard.js CHANGED
@@ -138,7 +138,7 @@ var plugin = {
138
138
  version: "0.1.0",
139
139
  description: "Supervises dependency installs: blocks deny-listed packages, flags typosquat lookalikes, and optionally warns on unpinned versions",
140
140
  apiVersion: "^0.1.10",
141
- capabilities: { tools: true, hooks: true },
141
+ capabilities: { tools: true, hooks: true, llm: true },
142
142
  defaultConfig: { ...DEFAULTS },
143
143
  configSchema: {
144
144
  type: "object",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diff-summary/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAgQ/C,QAAA,MAAM,MAAM,EAAE,MA4Pb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diff-summary/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAwP/C,QAAA,MAAM,MAAM,EAAE,MA4Pb,CAAC;eAEa,MAAM"}
@@ -1,17 +1,25 @@
1
1
  // src/diff-summary/index.ts
2
2
  import { execFileSync } from "node:child_process";
3
- import { isAbsolute, relative, resolve } from "node:path";
4
- var API_VERSION = "^0.1.10";
3
+
4
+ // src/runtime/index.ts
5
+ import { basename, extname, isAbsolute, relative, resolve } from "node:path";
6
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
7
+ function hasLeadingDash(arg) {
8
+ return arg.length > 0 && arg.startsWith("-");
9
+ }
10
+ function withinProjectPath(projectRoot, candidate) {
11
+ if (candidate.length === 0 || candidate.length > 4096) return false;
12
+ if (hasLeadingDash(candidate)) return false;
13
+ const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
14
+ const rel = relative(projectRoot, resolved);
15
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
16
+ }
5
17
  function withinProject(p) {
6
- if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
7
- const root = process.cwd();
8
- const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
9
- const rel = relative(root, resolved);
10
- if (rel === "" || rel === ".") return true;
11
- if (rel.startsWith("..")) return false;
12
- if (isAbsolute(rel)) return false;
13
- return true;
18
+ return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
14
19
  }
20
+
21
+ // src/diff-summary/index.ts
22
+ var API_VERSION = "^0.1.10";
15
23
  var state = {
16
24
  invocationCount: 0,
17
25
  /** Times a diff was successfully generated and injected. */
@@ -243,7 +251,7 @@ var plugin = {
243
251
  additionalContext: header + summary
244
252
  };
245
253
  };
246
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
254
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
247
255
  api.tools.register({
248
256
  name: "diff_summary_status",
249
257
  description: "Reports diff-summary state: mode, maxLines, and per-session invocation/injected/fallback counters.",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/doc-sync-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAkJ/C,QAAA,MAAM,MAAM,EAAE,MAuJb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/doc-sync-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA0I/C,QAAA,MAAM,MAAM,EAAE,MAuJb,CAAC;eAEa,MAAM"}
@@ -1,5 +1,24 @@
1
1
  // src/doc-sync-guard/index.ts
2
- import { isAbsolute, relative, resolve, basename, extname } from "node:path";
2
+ import { basename as basename2, extname as extname2 } from "node:path";
3
+
4
+ // src/runtime/index.ts
5
+ import { basename, extname, isAbsolute, relative, resolve } from "node:path";
6
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
7
+ function hasLeadingDash(arg) {
8
+ return arg.length > 0 && arg.startsWith("-");
9
+ }
10
+ function withinProjectPath(projectRoot, candidate) {
11
+ if (candidate.length === 0 || candidate.length > 4096) return false;
12
+ if (hasLeadingDash(candidate)) return false;
13
+ const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
14
+ const rel = relative(projectRoot, resolved);
15
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
16
+ }
17
+ function withinProject(p) {
18
+ return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
19
+ }
20
+
21
+ // src/doc-sync-guard/index.ts
3
22
  var API_VERSION = "^0.1.10";
4
23
  var state = {
5
24
  changedFiles: [],
@@ -24,35 +43,25 @@ function readConfig(raw) {
24
43
  maxTrackedFiles: typeof r["maxTrackedFiles"] === "number" && r["maxTrackedFiles"] >= 1 && r["maxTrackedFiles"] <= 200 ? r["maxTrackedFiles"] : DEFAULTS.maxTrackedFiles
25
44
  };
26
45
  }
27
- function withinProject(p) {
28
- if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
29
- const root = process.cwd();
30
- const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
31
- const rel = relative(root, resolved);
32
- if (rel === "" || rel === ".") return true;
33
- if (rel.startsWith("..")) return false;
34
- if (isAbsolute(rel)) return false;
35
- return true;
36
- }
37
46
  function normalizeSlashes(p) {
38
47
  return p.replace(/\\/g, "/");
39
48
  }
40
49
  function isSourceFile(p, extensions) {
41
- const ext = extname(p).toLowerCase();
50
+ const ext = extname2(p).toLowerCase();
42
51
  return extensions.includes(ext);
43
52
  }
44
53
  function isPublicSource(p, extensions) {
45
54
  if (!isSourceFile(p, extensions)) return false;
46
55
  const norm = normalizeSlashes(p);
47
56
  if (norm.includes("/node_modules/") || norm.startsWith("node_modules/")) return false;
48
- const base = basename(norm).toLowerCase();
57
+ const base = basename2(norm).toLowerCase();
49
58
  if (/\.(test|spec)\./.test(base)) return false;
50
59
  if (base.startsWith("_")) return false;
51
60
  return true;
52
61
  }
53
62
  function isDocFile(p, docNames) {
54
63
  const norm = normalizeSlashes(p);
55
- const base = basename(norm);
64
+ const base = basename2(norm);
56
65
  const lowerBase = base.toLowerCase();
57
66
  if (docNames.some((name) => lowerBase === name.toLowerCase())) return true;
58
67
  if (norm.includes("/docs/") || norm.startsWith("docs/")) return true;
@@ -70,7 +79,7 @@ function extractDocContent(toolInput) {
70
79
  return void 0;
71
80
  }
72
81
  function referenceTokens(p) {
73
- const base = basename(p);
82
+ const base = basename2(p);
74
83
  const withoutExt = base.replace(/\.[^.]+$/, "");
75
84
  const tokens = [base];
76
85
  if (withoutExt && withoutExt !== base) tokens.push(withoutExt);
@@ -155,7 +164,7 @@ Consider mentioning these changes in the documentation.`;
155
164
  return { additionalContext: message };
156
165
  }
157
166
  };
158
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
167
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
159
168
  api.tools.register({
160
169
  name: "doc_sync_status",
161
170
  description: "Reports doc-sync-guard state: tracked changed files, doc-write count, and warning count.",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/duplicate-code-detector/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAsP/C,QAAA,MAAM,MAAM,EAAE,MA8Qb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/duplicate-code-detector/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAoM/C,QAAA,MAAM,MAAM,EAAE,MA8Qb,CAAC;eAEa,MAAM"}
@@ -1,60 +1,37 @@
1
1
  // src/duplicate-code-detector/index.ts
2
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
- import { extname, isAbsolute, relative, resolve } from "node:path";
4
- var API_VERSION = "^0.1.10";
5
- var state = {
6
- scanCount: 0,
7
- findingCount: 0,
8
- hookInvocationCount: 0,
9
- warningCount: 0,
10
- errorCount: 0,
11
- hookUnregister: null,
12
- lastHookWarning: /* @__PURE__ */ new Map()
13
- };
14
- var DEFAULTS = {
15
- enabled: false,
16
- minLines: 8,
17
- threshold: 0.8,
18
- extensions: [".ts", ".tsx", ".js", ".jsx"],
19
- excludeDirs: ["node_modules", "dist", ".git", "coverage"],
20
- maxFindings: 5
21
- };
22
- function readConfig(raw) {
23
- if (!raw || typeof raw !== "object") return { ...DEFAULTS };
24
- const r = raw;
25
- return {
26
- enabled: r["enabled"] === true,
27
- minLines: typeof r["minLines"] === "number" && r["minLines"] >= 2 && r["minLines"] <= 100 ? r["minLines"] : DEFAULTS.minLines,
28
- threshold: typeof r["threshold"] === "number" && r["threshold"] > 0 && r["threshold"] <= 1 ? r["threshold"] : DEFAULTS.threshold,
29
- extensions: Array.isArray(r["extensions"]) ? r["extensions"].filter((x) => typeof x === "string") : DEFAULTS.extensions,
30
- excludeDirs: Array.isArray(r["excludeDirs"]) ? r["excludeDirs"].filter((x) => typeof x === "string") : DEFAULTS.excludeDirs,
31
- maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings
32
- };
2
+ import { readFileSync } from "node:fs";
3
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
4
+
5
+ // src/runtime/index.ts
6
+ import { existsSync, readdirSync, statSync } from "node:fs";
7
+ import { basename, extname, isAbsolute, relative, resolve } from "node:path";
8
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
9
+ function hasLeadingDash(arg) {
10
+ return arg.length > 0 && arg.startsWith("-");
33
11
  }
34
- function withinProject(p) {
35
- if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
36
- const root = process.cwd();
37
- const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
38
- const rel = relative(root, resolved);
39
- if (rel === "" || rel === ".") return true;
40
- if (rel.startsWith("..")) return false;
41
- if (isAbsolute(rel)) return false;
42
- return true;
12
+ function withinProjectPath(projectRoot, candidate) {
13
+ if (candidate.length === 0 || candidate.length > 4096) return false;
14
+ if (hasLeadingDash(candidate)) return false;
15
+ const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
16
+ const rel = relative(projectRoot, resolved);
17
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
43
18
  }
44
- function matchesExtension(p, exts) {
45
- const ext = extname(p).toLowerCase();
46
- return exts.includes(ext);
19
+ function withinProject(p) {
20
+ return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
47
21
  }
48
- function collectSourceFiles(root, cfg) {
22
+ var DEFAULT_EXCLUDE_DIRS = ["node_modules", "dist", ".git", "coverage"];
23
+ function collectSourceFiles(root, opts) {
49
24
  const files = [];
50
25
  if (!existsSync(root)) return files;
51
26
  const s = statSync(root);
52
27
  if (s.isFile()) {
53
- if (matchesExtension(root, cfg.extensions)) files.push(root);
28
+ if (matchesExtension(root, opts.extensions)) files.push(root);
54
29
  return files;
55
30
  }
56
31
  if (!s.isDirectory()) return files;
57
- function walk(dir) {
32
+ const exclude = opts.excludeDirs ?? DEFAULT_EXCLUDE_DIRS;
33
+ function walk(dir, depth) {
34
+ if (opts.maxDepth !== void 0 && depth > opts.maxDepth) return;
58
35
  let entries;
59
36
  try {
60
37
  entries = readdirSync(dir);
@@ -62,6 +39,7 @@ function collectSourceFiles(root, cfg) {
62
39
  return;
63
40
  }
64
41
  for (const entry of entries) {
42
+ if (exclude.includes(entry)) continue;
65
43
  const full = resolve(dir, entry);
66
44
  let st;
67
45
  try {
@@ -70,20 +48,55 @@ function collectSourceFiles(root, cfg) {
70
48
  continue;
71
49
  }
72
50
  if (st.isDirectory()) {
73
- if (!cfg.excludeDirs.includes(entry)) walk(full);
74
- } else if (st.isFile() && matchesExtension(full, cfg.extensions)) {
51
+ walk(full, depth + 1);
52
+ } else if (st.isFile() && matchesExtension(full, opts.extensions)) {
75
53
  files.push(full);
76
54
  }
77
55
  }
78
56
  }
79
- walk(root);
57
+ walk(root, 0);
80
58
  return files;
81
59
  }
60
+ function matchesExtension(p, exts) {
61
+ return exts.includes(extname(p).toLowerCase());
62
+ }
63
+
64
+ // src/duplicate-code-detector/index.ts
65
+ var API_VERSION = "^0.1.10";
66
+ var state = {
67
+ scanCount: 0,
68
+ findingCount: 0,
69
+ hookInvocationCount: 0,
70
+ warningCount: 0,
71
+ errorCount: 0,
72
+ hookUnregister: null,
73
+ lastHookWarning: /* @__PURE__ */ new Map()
74
+ };
75
+ var DEFAULTS = {
76
+ enabled: false,
77
+ minLines: 8,
78
+ threshold: 0.8,
79
+ extensions: [".ts", ".tsx", ".js", ".jsx"],
80
+ excludeDirs: ["node_modules", "dist", ".git", "coverage"],
81
+ maxFindings: 5
82
+ };
83
+ function readConfig(raw) {
84
+ if (!raw || typeof raw !== "object") return { ...DEFAULTS };
85
+ const r = raw;
86
+ return {
87
+ enabled: r["enabled"] === true,
88
+ minLines: typeof r["minLines"] === "number" && r["minLines"] >= 2 && r["minLines"] <= 100 ? r["minLines"] : DEFAULTS.minLines,
89
+ threshold: typeof r["threshold"] === "number" && r["threshold"] > 0 && r["threshold"] <= 1 ? r["threshold"] : DEFAULTS.threshold,
90
+ extensions: Array.isArray(r["extensions"]) ? r["extensions"].filter((x) => typeof x === "string") : DEFAULTS.extensions,
91
+ excludeDirs: Array.isArray(r["excludeDirs"]) ? r["excludeDirs"].filter((x) => typeof x === "string") : DEFAULTS.excludeDirs,
92
+ maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings
93
+ };
94
+ }
82
95
  function toPosix(p) {
83
96
  return p.replace(/\\/g, "/");
84
97
  }
85
98
  function relativePath(p) {
86
- return toPosix(relative(process.cwd(), p));
99
+ return toPosix(relative2(process.cwd(), p));
87
100
  }
88
101
  function removeInlineComments(line) {
89
102
  return line.replace(/\/\/.*$/, "").replace(/\/\*[\s\S]*?\*\//, "");
@@ -141,8 +154,8 @@ function findDuplicates(files, minLines, maxFindings) {
141
154
  }
142
155
  function scanPath(rawPath, cfg) {
143
156
  const root = process.cwd();
144
- const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
145
- const filePaths = collectSourceFiles(resolved, cfg);
157
+ const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
158
+ const filePaths = collectSourceFiles(resolved, { extensions: cfg.extensions, excludeDirs: cfg.excludeDirs });
146
159
  const files = /* @__PURE__ */ new Map();
147
160
  for (const p of filePaths) {
148
161
  try {
@@ -226,7 +239,7 @@ var plugin = {
226
239
  const now = Date.now();
227
240
  const lastWarning = state.lastHookWarning.get(sourcePath);
228
241
  if (lastWarning !== void 0 && now - lastWarning < 6e4) return;
229
- const changedFile = resolve(process.cwd(), sourcePath);
242
+ const changedFile = resolve2(process.cwd(), sourcePath);
230
243
  let content;
231
244
  try {
232
245
  content = readFileSync(changedFile, "utf-8");
@@ -236,10 +249,10 @@ var plugin = {
236
249
  }
237
250
  const changedWindows = extractWindows(changedFile, content, cfg.minLines);
238
251
  if (changedWindows.length === 0) return;
239
- const projectRoot = resolve(process.cwd());
252
+ const projectRoot = resolve2(process.cwd());
240
253
  let otherFiles;
241
254
  try {
242
- const filePaths = collectSourceFiles(projectRoot, cfg).filter((p) => p !== changedFile);
255
+ const filePaths = collectSourceFiles(projectRoot, { extensions: cfg.extensions, excludeDirs: cfg.excludeDirs }).filter((p) => p !== changedFile);
243
256
  otherFiles = /* @__PURE__ */ new Map();
244
257
  for (const p of filePaths) {
245
258
  try {
@@ -272,7 +285,7 @@ var plugin = {
272
285
  contextAs: "separate"
273
286
  };
274
287
  };
275
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
288
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
276
289
  api.tools.register({
277
290
  name: "detect_duplicate_code",
278
291
  description: "Scan source files for duplicated code blocks. Uses normalized-line fingerprinting to find identical multi-line blocks across files.",
@@ -302,7 +315,7 @@ var plugin = {
302
315
  state.findingCount += result.findings.length;
303
316
  return {
304
317
  ok: true,
305
- path: relativePath(resolve(process.cwd(), rawPath)),
318
+ path: relativePath(resolve2(process.cwd(), rawPath)),
306
319
  scannedFiles: result.scannedFiles,
307
320
  minLines: cfg.minLines,
308
321
  findings: result.findings
@@ -85,7 +85,7 @@ var plugin = {
85
85
  version: "0.1.0",
86
86
  description: "Distills failed command output into a compact digest (error line + project stack frames) and flags repeated failures",
87
87
  apiVersion: "^0.1.10",
88
- capabilities: { tools: true, hooks: true },
88
+ capabilities: { tools: true, hooks: true, llm: true },
89
89
  defaultConfig: { ...DEFAULTS },
90
90
  configSchema: {
91
91
  type: "object",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/feature-flag-tracker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAQ/C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AA6LD,QAAA,MAAM,MAAM,EAAE,MA+Mb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/feature-flag-tracker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAS/C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AA0ID,QAAA,MAAM,MAAM,EAAE,MA+Mb,CAAC;eAEa,MAAM"}
@@ -1,6 +1,67 @@
1
1
  // src/feature-flag-tracker/index.ts
2
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
- import { extname, isAbsolute, relative, resolve } from "node:path";
2
+ import { readFileSync } from "node:fs";
3
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
4
+
5
+ // src/runtime/index.ts
6
+ import { existsSync, readdirSync, statSync } from "node:fs";
7
+ import { basename, extname, isAbsolute, relative, resolve } from "node:path";
8
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
9
+ function hasLeadingDash(arg) {
10
+ return arg.length > 0 && arg.startsWith("-");
11
+ }
12
+ function withinProjectPath(projectRoot, candidate) {
13
+ if (candidate.length === 0 || candidate.length > 4096) return false;
14
+ if (hasLeadingDash(candidate)) return false;
15
+ const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
16
+ const rel = relative(projectRoot, resolved);
17
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
18
+ }
19
+ function withinProject(p) {
20
+ return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
21
+ }
22
+ var DEFAULT_EXCLUDE_DIRS = ["node_modules", "dist", ".git", "coverage"];
23
+ function collectSourceFiles(root, opts) {
24
+ 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);
29
+ return files;
30
+ }
31
+ if (!s.isDirectory()) return files;
32
+ const exclude = opts.excludeDirs ?? DEFAULT_EXCLUDE_DIRS;
33
+ function walk(dir, depth) {
34
+ if (opts.maxDepth !== void 0 && depth > opts.maxDepth) return;
35
+ let entries;
36
+ try {
37
+ entries = readdirSync(dir);
38
+ } catch {
39
+ return;
40
+ }
41
+ 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
+ files.push(full);
54
+ }
55
+ }
56
+ }
57
+ walk(root, 0);
58
+ return files;
59
+ }
60
+ function matchesExtension(p, exts) {
61
+ return exts.includes(extname(p).toLowerCase());
62
+ }
63
+
64
+ // src/feature-flag-tracker/index.ts
4
65
  var API_VERSION = "^0.1.10";
5
66
  var state = {
6
67
  scanCount: 0,
@@ -32,62 +93,14 @@ function readConfig(raw) {
32
93
  maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings
33
94
  };
34
95
  }
35
- function withinProject(p) {
36
- if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
37
- const root = process.cwd();
38
- const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
39
- const rel = relative(root, resolved);
40
- if (rel === "" || rel === ".") return true;
41
- if (rel.startsWith("..")) return false;
42
- if (isAbsolute(rel)) return false;
43
- return true;
44
- }
45
96
  function normalizeExtensions(exts) {
46
97
  return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
47
98
  }
48
- function matchesExtension(p, exts) {
49
- return exts.includes(extname(p).toLowerCase());
50
- }
51
- function collectSourceFiles(root, exts) {
52
- const files = [];
53
- if (!existsSync(root)) return files;
54
- const s = statSync(root);
55
- if (s.isFile()) {
56
- if (matchesExtension(root, exts)) files.push(root);
57
- return files;
58
- }
59
- if (!s.isDirectory()) return files;
60
- function walk(dir) {
61
- let entries;
62
- try {
63
- entries = readdirSync(dir);
64
- } catch {
65
- return;
66
- }
67
- for (const entry of entries) {
68
- if (entry === "node_modules" || entry === "dist" || entry === ".git" || entry === "coverage") continue;
69
- const full = resolve(dir, entry);
70
- let st;
71
- try {
72
- st = statSync(full);
73
- } catch {
74
- continue;
75
- }
76
- if (st.isDirectory()) {
77
- walk(full);
78
- } else if (st.isFile() && matchesExtension(full, exts)) {
79
- files.push(full);
80
- }
81
- }
82
- }
83
- walk(root);
84
- return files;
85
- }
86
99
  function toPosix(p) {
87
100
  return p.replace(/\\/g, "/");
88
101
  }
89
102
  function relativePath(p) {
90
- return toPosix(relative(process.cwd(), p));
103
+ return toPosix(relative2(process.cwd(), p));
91
104
  }
92
105
  function compilePatterns(patterns) {
93
106
  const sources = [...DEFAULT_PATTERNS, ...patterns];
@@ -117,9 +130,9 @@ function scanFile(filePath, content, patterns, maxFindings) {
117
130
  }
118
131
  function scanPath(rawPath, cfg) {
119
132
  const root = process.cwd();
120
- const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
133
+ const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
121
134
  const exts = normalizeExtensions(cfg.extensions);
122
- const files = collectSourceFiles(resolved, exts);
135
+ const files = collectSourceFiles(resolved, { extensions: exts });
123
136
  const patterns = compilePatterns(cfg.patterns);
124
137
  const usages = [];
125
138
  for (const p of files) {
@@ -188,7 +201,7 @@ var plugin = {
188
201
  const exts = normalizeExtensions(cfg.extensions);
189
202
  if (!matchesExtension(sourcePath, exts)) return;
190
203
  state.hookInvocationCount += 1;
191
- const resolved = resolve(process.cwd(), sourcePath);
204
+ const resolved = resolve2(process.cwd(), sourcePath);
192
205
  let content;
193
206
  try {
194
207
  content = readFileSync(resolved, "utf-8");
@@ -207,7 +220,7 @@ var plugin = {
207
220
  Make sure flag behavior is intentional and consider updating flag inventory/docs.`
208
221
  };
209
222
  };
210
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
223
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
211
224
  api.tools.register({
212
225
  name: "scan_feature_flags",
213
226
  description: "Scan source files for feature-flag-like expressions (isFeatureEnabled, featureFlags.*, useFeatureFlag, flags.*, plus custom patterns).",
@@ -237,7 +250,7 @@ Make sure flag behavior is intentional and consider updating flag inventory/docs
237
250
  state.flagCount += result.usages.length;
238
251
  return {
239
252
  ok: true,
240
- path: relativePath(resolve(process.cwd(), rawPath)),
253
+ path: relativePath(resolve2(process.cwd(), rawPath)),
241
254
  scannedFiles: result.scannedFiles,
242
255
  usages: result.usages
243
256
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/file-watcher/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA2D/C,QAAA,MAAM,MAAM,EAAE,MAsWb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/file-watcher/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA2D/C,QAAA,MAAM,MAAM,EAAE,MAsYb,CAAC;eAEa,MAAM"}