@wrongstack/plugins 0.317.0 → 0.318.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 (72) hide show
  1. package/dist/accessibility-auditor.js +12 -10
  2. package/dist/agent-handoff.js +30 -10
  3. package/dist/auto-doc.js +22 -9
  4. package/dist/auto-escalate.js +17 -7
  5. package/dist/auto-i18n-extractor.js +19 -9
  6. package/dist/branch-guard.js +12 -9
  7. package/dist/changelog-writer/index.d.ts +3 -3
  8. package/dist/changelog-writer.js +59 -14
  9. package/dist/checkpoint.js +18 -7
  10. package/dist/code-metrics.js +17 -9
  11. package/dist/commit-validator.js +18 -11
  12. package/dist/config-validator.js +5 -3
  13. package/dist/context-pins.js +17 -6
  14. package/dist/cost-tracker.js +24 -16
  15. package/dist/cron.js +17 -13
  16. package/dist/dead-code-detector.js +5 -4
  17. package/dist/dep-guard.js +5 -4
  18. package/dist/dependency-vulnerability-gate.js +64 -24
  19. package/dist/diff-summary.js +6 -2
  20. package/dist/doc-sync-guard.js +12 -2
  21. package/dist/duplicate-code-detector.js +87 -15
  22. package/dist/error-lens.js +6 -5
  23. package/dist/feature-flag-tracker.js +43 -9
  24. package/dist/file-watcher.js +26 -3
  25. package/dist/format-on-save.js +9 -5
  26. package/dist/git-autocommit.js +20 -12
  27. package/dist/gitignore-guard.js +7 -4
  28. package/dist/import-organizer.js +9 -5
  29. package/dist/index.js +1675 -745
  30. package/dist/injection-shield/index.d.ts +1 -0
  31. package/dist/injection-shield.js +30 -5
  32. package/dist/interface-contract-guard.js +4 -2
  33. package/dist/knowledge-graph.js +40 -14
  34. package/dist/license-audit-gate.js +61 -8
  35. package/dist/lint-gate.js +13 -5
  36. package/dist/llm-cache.js +20 -6
  37. package/dist/loop-breaker.js +16 -8
  38. package/dist/migration-planner.js +80 -11
  39. package/dist/model-router.js +16 -9
  40. package/dist/notify-hub.js +14 -5
  41. package/dist/path-guard.js +7 -3
  42. package/dist/performance-regression-gate.js +12 -6
  43. package/dist/plugin-stack-observer.js +25 -3
  44. package/dist/pr-drafter.js +16 -7
  45. package/dist/process-guard.js +2 -1
  46. package/dist/prompt-firewall.js +9 -7
  47. package/dist/refactor-suggester.js +37 -8
  48. package/dist/release-notes-generator/index.d.ts +9 -0
  49. package/dist/release-notes-generator.js +50 -12
  50. package/dist/schema-evolution-guard.js +33 -6
  51. package/dist/secret-scanner.js +10 -7
  52. package/dist/security-hotspot-scanner.js +13 -8
  53. package/dist/semantic-search-indexer/index.d.ts +11 -0
  54. package/dist/semantic-search-indexer.js +67 -19
  55. package/dist/semver-bump/index.d.ts +3 -3
  56. package/dist/semver-bump.js +30 -15
  57. package/dist/session-recap/index.d.ts +16 -0
  58. package/dist/session-recap.js +18 -9
  59. package/dist/shell-check.js +27 -6
  60. package/dist/smart-rename.js +18 -9
  61. package/dist/spec-linker.js +70 -19
  62. package/dist/template-engine.js +24 -14
  63. package/dist/test-coverage-gate.js +1 -1
  64. package/dist/test-flake-detector.js +28 -5
  65. package/dist/test-generator/index.d.ts +10 -0
  66. package/dist/test-generator.js +35 -13
  67. package/dist/todo-listener.js +2 -2
  68. package/dist/todo-tracker.js +19 -11
  69. package/dist/token-budget.js +14 -9
  70. package/dist/token-throttle.js +8 -3
  71. package/dist/type-gate.js +9 -5
  72. package/package.json +5 -5
@@ -59,22 +59,23 @@ var DEFAULTS = {
59
59
  excludeDirs: ["node_modules", "dist", ".git", "coverage"],
60
60
  maxFindings: 5
61
61
  };
62
- var extensionsSet = new Set(DEFAULTS.extensions);
63
62
  function readConfig(raw) {
64
63
  if (!raw || typeof raw !== "object") {
65
- extensionsSet = new Set(DEFAULTS.extensions);
66
64
  return { ...DEFAULTS };
67
65
  }
68
66
  const r = raw;
69
- const extensions = Array.isArray(r["extensions"]) ? r["extensions"].filter((x) => typeof x === "string") : DEFAULTS.extensions;
70
- extensionsSet = new Set(extensions);
67
+ const rawExts = r["extensions"] ?? r["file_extensions"] ?? r["fileExtensions"];
68
+ const extensions = Array.isArray(rawExts) ? rawExts.filter((x) => typeof x === "string") : DEFAULTS.extensions;
69
+ const rawMin = r["minLines"] ?? r["min_lines"] ?? r["min"];
70
+ const rawExclude = r["excludeDirs"] ?? r["exclude_dirs"] ?? r["exclude"];
71
+ const rawMax = r["maxFindings"] ?? r["max_findings"] ?? r["limit"];
71
72
  return {
72
73
  enabled: r["enabled"] === true,
73
- minLines: typeof r["minLines"] === "number" && r["minLines"] >= 2 && r["minLines"] <= 100 ? r["minLines"] : DEFAULTS.minLines,
74
+ minLines: typeof rawMin === "number" && rawMin >= 2 && rawMin <= 100 ? rawMin : DEFAULTS.minLines,
74
75
  threshold: typeof r["threshold"] === "number" && r["threshold"] > 0 && r["threshold"] <= 1 ? r["threshold"] : DEFAULTS.threshold,
75
76
  extensions,
76
- excludeDirs: Array.isArray(r["excludeDirs"]) ? r["excludeDirs"].filter((x) => typeof x === "string") : DEFAULTS.excludeDirs,
77
- maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings
77
+ excludeDirs: Array.isArray(rawExclude) ? rawExclude.filter((x) => typeof x === "string") : DEFAULTS.excludeDirs,
78
+ maxFindings: typeof rawMax === "number" && rawMax >= 1 && rawMax <= 500 ? rawMax : DEFAULTS.maxFindings
78
79
  };
79
80
  }
80
81
  function isWithinRoot(projectRoot, candidate) {
@@ -135,19 +136,31 @@ function evictHookIndex() {
135
136
  }
136
137
  function extractWindows(filePath, content, minLines) {
137
138
  const rawLines = content.split(/\r?\n/);
139
+ const covered = [];
138
140
  const windows = [];
139
141
  for (let i = 0; i <= rawLines.length - minLines; i++) {
142
+ const startLine = i + 1;
143
+ const endLine = i + minLines;
144
+ let overlaps = false;
145
+ for (const [s, e] of covered) {
146
+ if (startLine <= e && s <= endLine) {
147
+ overlaps = true;
148
+ break;
149
+ }
150
+ }
151
+ if (overlaps) continue;
140
152
  const slice = rawLines.slice(i, i + minLines);
141
153
  const fingerprint = buildFingerprint(slice);
142
154
  if (fingerprint.length === 0) continue;
143
155
  const snippet = slice.join("\n");
144
156
  windows.push({
145
157
  file: filePath,
146
- startLine: i + 1,
147
- endLine: i + minLines,
158
+ startLine,
159
+ endLine,
148
160
  snippet,
149
161
  fingerprint
150
162
  });
163
+ covered.push([startLine, endLine]);
151
164
  }
152
165
  return windows;
153
166
  }
@@ -162,8 +175,17 @@ function findDuplicates(files, minLines, maxFindings) {
162
175
  }
163
176
  }
164
177
  const findings = [];
178
+ const coveredSpans = /* @__PURE__ */ new Set();
165
179
  for (const [fingerprint, windows] of byFingerprint.entries()) {
166
180
  if (windows.length < 2) continue;
181
+ const isOverlapping = windows.every((w) => {
182
+ const spanKey = `${w.file}:${Math.floor(w.startLine / minLines)}`;
183
+ return coveredSpans.has(spanKey);
184
+ });
185
+ if (isOverlapping && findings.length > 0) continue;
186
+ for (const w of windows) {
187
+ coveredSpans.add(`${w.file}:${Math.floor(w.startLine / minLines)}`);
188
+ }
167
189
  const locations = windows.map((w) => ({
168
190
  file: relativePath(w.file),
169
191
  startLine: w.startLine,
@@ -171,9 +193,51 @@ function findDuplicates(files, minLines, maxFindings) {
171
193
  snippet: w.snippet
172
194
  }));
173
195
  findings.push({ fingerprint, lineCount: fingerprint.split("\n").length, locations });
174
- if (findings.length >= maxFindings) break;
175
196
  }
176
- return findings;
197
+ function locationsOverlap(a, b) {
198
+ for (const la of a.locations) {
199
+ for (const lb of b.locations) {
200
+ if (la.file !== lb.file) continue;
201
+ if (la.startLine <= lb.endLine && lb.startLine <= la.endLine) return true;
202
+ }
203
+ }
204
+ return false;
205
+ }
206
+ let merged = true;
207
+ while (merged) {
208
+ merged = false;
209
+ for (let i = 0; i < findings.length; i++) {
210
+ for (let j = i + 1; j < findings.length; j++) {
211
+ if (!locationsOverlap(findings[i], findings[j])) continue;
212
+ const a = findings[i];
213
+ const b = findings[j];
214
+ const keep = b.fingerprint.split("\n").length > a.fingerprint.split("\n").length ? b : a;
215
+ const drop = keep === a ? b : a;
216
+ const seen = /* @__PURE__ */ new Set();
217
+ const mergedLocations = [];
218
+ for (const loc of [...keep.locations, ...drop.locations]) {
219
+ const k = `${loc.file}#${loc.startLine}#${loc.endLine}`;
220
+ if (seen.has(k)) continue;
221
+ seen.add(k);
222
+ mergedLocations.push(loc);
223
+ }
224
+ mergedLocations.sort(
225
+ (x, y) => x.file === y.file ? x.startLine - y.startLine : x.file.localeCompare(y.file)
226
+ );
227
+ findings[i] = {
228
+ fingerprint: keep.fingerprint,
229
+ lineCount: keep.fingerprint.split("\n").length,
230
+ locations: mergedLocations
231
+ };
232
+ findings.splice(j, 1);
233
+ merged = true;
234
+ break;
235
+ }
236
+ if (merged) break;
237
+ }
238
+ }
239
+ const capped = findings.slice(0, maxFindings);
240
+ return capped;
177
241
  }
178
242
  async function scanPath(rawPath, cfg) {
179
243
  const root = process.cwd();
@@ -189,7 +253,10 @@ async function scanPath(rawPath, cfg) {
189
253
  } catch {
190
254
  }
191
255
  }
192
- return { findings: findDuplicates(files, cfg.minLines, cfg.maxFindings), scannedFiles: files.size };
256
+ return {
257
+ findings: findDuplicates(files, cfg.minLines, cfg.maxFindings),
258
+ scannedFiles: files.size
259
+ };
193
260
  }
194
261
  var plugin = {
195
262
  name: "duplicate-code-detector",
@@ -256,6 +323,7 @@ var plugin = {
256
323
  state.hookUnregister = null;
257
324
  }
258
325
  const cfg = readConfig(api.config.extensions?.["duplicate-code-detector"]);
326
+ const extensionsSet = new Set(cfg.extensions);
259
327
  async function readCachedFingerprints(filePath, minLines) {
260
328
  let st;
261
329
  try {
@@ -303,7 +371,8 @@ var plugin = {
303
371
  if (!cfg.enabled) return;
304
372
  if (input.toolResult?.isError) return;
305
373
  const inp = input.toolInput ?? {};
306
- const sourcePath = inp["path"];
374
+ const rawSource = inp["path"] ?? inp["TargetFile"] ?? inp["filePath"] ?? inp["file_path"] ?? inp["targetFile"] ?? inp["file"];
375
+ const sourcePath = typeof rawSource === "string" ? rawSource : void 0;
307
376
  if (!sourcePath || typeof sourcePath !== "string") return;
308
377
  const projectRoot = resolve(process.cwd());
309
378
  const resolvedFile = isAbsolute(sourcePath) ? resolve(sourcePath) : resolve(projectRoot, sourcePath);
@@ -357,7 +426,9 @@ var plugin = {
357
426
  contextAs: "separate"
358
427
  };
359
428
  };
360
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
429
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
430
+ background: true
431
+ });
361
432
  api.tools.register({
362
433
  name: "detect_duplicate_code",
363
434
  description: "Scan source files for duplicated code blocks. Uses normalized-line fingerprinting to find identical multi-line blocks across files.",
@@ -372,7 +443,8 @@ var plugin = {
372
443
  mutating: false,
373
444
  async execute(input) {
374
445
  if (!cfg.enabled) return { ok: false, error: "duplicate-code-detector is disabled" };
375
- const rawPath = typeof input.path === "string" ? input.path : ".";
446
+ const raw = input;
447
+ const rawPath = (typeof raw["path"] === "string" ? raw["path"] : void 0) ?? (typeof raw["directory"] === "string" ? raw["directory"] : void 0) ?? (typeof raw["dir"] === "string" ? raw["dir"] : void 0) ?? (typeof raw["SearchDirectory"] === "string" ? raw["SearchDirectory"] : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["file_path"] === "string" ? raw["file_path"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? (typeof raw["file"] === "string" ? raw["file"] : void 0) ?? ".";
376
448
  if (!(0, runtime_exports.withinProject)(rawPath)) {
377
449
  return { ok: false, error: "scan path is outside the project root" };
378
450
  }
@@ -41,12 +41,12 @@ var ERROR_LINE_PATTERNS = [
41
41
  /^[ \t]*((?:FAIL|✗|×)[ \t]+[^\n]{5,})/m
42
42
  ];
43
43
  var FRAME_PATTERNS = [
44
- // Node/V8: "at fn (path/file.ts:12:5)" or "at path/file.ts:12:5"
45
- /\bat\s+(?:[^(\n]+\()?((?:[A-Za-z]:[\\/])?[^()\n:]+:\d+)(?::\d+)?\)?/g,
44
+ // Node/V8: "at fn (path/file.ts:12:5)" or "at file:///path/file.ts:12:5"
45
+ /\bat\s+(?:[^(\n]+\()?((?:file:\/\/\/?)?(?:[A-Za-z]:[\\/])?[^()\n:]+:\d+)(?::\d+)?\)?/g,
46
46
  // Python: File "path/file.py", line 12
47
47
  /File "([^"]+)", line (\d+)/g,
48
48
  // tsc/vitest/eslint bare: "src/foo.ts:12:5" (require a path-ish prefix)
49
- /(?:^|[ \t(])((?:[A-Za-z]:[\\/])?[A-Za-z0-9_./\\-]+\.[a-z]{1,4}:\d+)(?::\d+)?/gm,
49
+ /(?:^|[ \t(])((?:file:\/\/\/?)?(?:[A-Za-z]:[\\/])?[A-Za-z0-9_./\\-]+\.[a-z]{1,4}:\d+)(?::\d+)?/gm,
50
50
  // Rust: "--> src/main.rs:4:5"
51
51
  /-->\s+((?:[A-Za-z]:[\\/])?[^\s:]+:\d+)/g
52
52
  ];
@@ -66,7 +66,7 @@ function extractFrames(output, maxFrames) {
66
66
  let m = re.exec(output);
67
67
  while (m !== null) {
68
68
  const frame = m[2] !== void 0 ? `${m[1]}:${m[2]}` : m[1] ?? "";
69
- const cleaned = frame.replace(/\\/g, "/").trim();
69
+ const cleaned = frame.replace(/^file:\/\/\/?/i, "").replace(/\\/g, "/").trim();
70
70
  if (cleaned && !seen.has(cleaned)) {
71
71
  seen.add(cleaned);
72
72
  const isVendor = cleaned.includes("node_modules/") || cleaned.startsWith("node:") || cleaned.includes("internal/");
@@ -161,10 +161,11 @@ var plugin = {
161
161
  } else {
162
162
  isNewFailure = true;
163
163
  const ti = input.toolInput ?? {};
164
+ const rawCmd = ti["command"] ?? ti["CommandLine"] ?? ti["cmd"] ?? ti["script"];
164
165
  state.history.push({
165
166
  when: (/* @__PURE__ */ new Date()).toISOString(),
166
167
  tool: input.toolName ?? "unknown",
167
- command: typeof ti["command"] === "string" ? ti["command"].slice(0, 200) : null,
168
+ command: typeof rawCmd === "string" ? rawCmd.slice(0, 200) : null,
168
169
  errorLine,
169
170
  frames,
170
171
  repeats: 1
@@ -32,10 +32,10 @@ var state = {
32
32
  hookUnregister: null
33
33
  };
34
34
  var DEFAULT_PATTERNS = [
35
- String.raw`isFeatureEnabled\(['"]([^'"]+)['"]\)`,
36
- String.raw`featureFlags?\.([A-Za-z_$][A-Za-z0-9_$]*)`,
37
- String.raw`useFeatureFlag\(['"]([^'"]+)['"]\)`,
38
- String.raw`flags\.([A-Za-z_$][A-Za-z0-9_$]*)`
35
+ String.raw`isFeatureEnabled\(['"\`]([^'"\`]+)['"\`]\)`,
36
+ String.raw`featureFlags?(?:\.|\?\.)([A-Za-z_$][A-Za-z0-9_$]*)`,
37
+ String.raw`useFeatureFlag\(['"\`]([^'"\`]+)['"\`]\)`,
38
+ String.raw`flags(?:\.|\?\.)([A-Za-z_$][A-Za-z0-9_$]*)`
39
39
  ];
40
40
  var DEFAULTS = {
41
41
  enabled: false,
@@ -46,11 +46,14 @@ var DEFAULTS = {
46
46
  function readConfig(raw) {
47
47
  if (!raw || typeof raw !== "object") return { ...DEFAULTS };
48
48
  const r = raw;
49
+ const rawExts = r["extensions"] ?? r["file_extensions"] ?? r["fileExtensions"];
50
+ const rawPatterns = r["patterns"] ?? r["custom_patterns"] ?? r["customPatterns"];
51
+ const rawMax = r["maxFindings"] ?? r["max_findings"] ?? r["limit"];
49
52
  return {
50
53
  enabled: r["enabled"] !== false,
51
- extensions: Array.isArray(r["extensions"]) ? r["extensions"].filter((x) => typeof x === "string") : DEFAULTS.extensions,
52
- patterns: Array.isArray(r["patterns"]) ? r["patterns"].filter((x) => typeof x === "string") : DEFAULTS.patterns,
53
- maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings
54
+ extensions: Array.isArray(rawExts) ? rawExts.filter((x) => typeof x === "string") : DEFAULTS.extensions,
55
+ patterns: Array.isArray(rawPatterns) ? rawPatterns.filter((x) => typeof x === "string") : DEFAULTS.patterns,
56
+ maxFindings: typeof rawMax === "number" && rawMax >= 1 && rawMax <= 500 ? rawMax : DEFAULTS.maxFindings
54
57
  };
55
58
  }
56
59
  function normalizeExtensions(exts) {
@@ -72,6 +75,34 @@ function compilePatterns(patterns) {
72
75
  }
73
76
  return out;
74
77
  }
78
+ var RESERVED_FLAG_NAMES = /* @__PURE__ */ new Set([
79
+ "includes",
80
+ "length",
81
+ "has",
82
+ "get",
83
+ "set",
84
+ "size",
85
+ "slice",
86
+ "split",
87
+ "filter",
88
+ "map",
89
+ "forEach",
90
+ "join",
91
+ "push",
92
+ "pop",
93
+ "indexOf",
94
+ "values",
95
+ "keys",
96
+ "entries",
97
+ "toString",
98
+ "trim",
99
+ "toLowerCase",
100
+ "toUpperCase",
101
+ "match",
102
+ "replace",
103
+ "name",
104
+ "type"
105
+ ]);
75
106
  function scanFile(filePath, content, patterns, maxFindings) {
76
107
  const usages = [];
77
108
  const lines = content.split(/\r?\n/);
@@ -79,6 +110,7 @@ function scanFile(filePath, content, patterns, maxFindings) {
79
110
  re.lastIndex = 0;
80
111
  for (const m of content.matchAll(re)) {
81
112
  const flag = m[1] ?? m[0];
113
+ if (RESERVED_FLAG_NAMES.has(flag)) continue;
82
114
  const lineNo = content.slice(0, m.index).split(/\r?\n/).length;
83
115
  const context = (lines[lineNo - 1] ?? "").trim();
84
116
  usages.push({
@@ -172,7 +204,8 @@ var plugin = {
172
204
  if (!cfg.enabled) return;
173
205
  if (input.toolResult?.isError) return;
174
206
  const inp = input.toolInput ?? {};
175
- const sourcePath = inp["path"];
207
+ const rawPath = inp["path"] ?? inp["filePath"] ?? inp["file_path"] ?? inp["TargetFile"] ?? inp["targetFile"] ?? inp["file"];
208
+ const sourcePath = typeof rawPath === "string" && rawPath.trim() ? rawPath.trim() : void 0;
176
209
  if (!sourcePath || typeof sourcePath !== "string") return;
177
210
  if (!(0, runtime_exports.withinProject)(sourcePath)) return;
178
211
  const exts = normalizeExtensions(cfg.extensions);
@@ -212,7 +245,8 @@ Make sure flag behavior is intentional and consider updating flag inventory/docs
212
245
  mutating: false,
213
246
  async execute(input) {
214
247
  if (!cfg.enabled) return { ok: false, error: "feature-flag-tracker is disabled" };
215
- const rawPath = typeof input.path === "string" ? input.path : ".";
248
+ const raw = input ?? {};
249
+ const rawPath = (typeof input.path === "string" && input.path.trim().length > 0 ? input.path.trim() : void 0) ?? (typeof raw["directory"] === "string" ? raw["directory"] : void 0) ?? (typeof raw["SearchDirectory"] === "string" ? raw["SearchDirectory"] : void 0) ?? (typeof raw["dir"] === "string" ? raw["dir"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["file_path"] === "string" ? raw["file_path"] : void 0) ?? (typeof raw["file"] === "string" ? raw["file"] : void 0) ?? ".";
216
250
  if (!(0, runtime_exports.withinProject)(rawPath)) {
217
251
  return { ok: false, error: "path is outside the project root" };
218
252
  }
@@ -205,8 +205,22 @@ var plugin = {
205
205
  category: "Filesystem",
206
206
  mutating: false,
207
207
  async execute(input) {
208
- const rawPaths = input["paths"];
209
- if (!rawPaths || typeof rawPaths !== "object" || !Array.isArray(rawPaths)) {
208
+ const explicitPaths = input["paths"];
209
+ let rawPaths;
210
+ if (explicitPaths !== void 0) {
211
+ if (!Array.isArray(explicitPaths)) {
212
+ return {
213
+ ok: false,
214
+ error: "paths must be an array of file/directory paths",
215
+ watch_id: null
216
+ };
217
+ }
218
+ rawPaths = explicitPaths;
219
+ } else {
220
+ const fallback = input["path"] ?? input["file"] ?? input["directory"] ?? input["TargetFile"] ?? input["filePath"];
221
+ rawPaths = Array.isArray(fallback) ? fallback : typeof fallback === "string" && fallback.trim().length > 0 ? [fallback.trim()] : void 0;
222
+ }
223
+ if (!rawPaths || !Array.isArray(rawPaths)) {
210
224
  return {
211
225
  ok: false,
212
226
  error: "paths must be an array of file/directory paths",
@@ -294,9 +308,11 @@ var plugin = {
294
308
  required: ["watch_id"]
295
309
  },
296
310
  permission: "auto",
311
+ category: "Filesystem",
297
312
  mutating: false,
298
313
  async execute(input) {
299
- const watch_id = input["watch_id"];
314
+ const rawId = input["watch_id"] ?? input["watchId"] ?? input["id"];
315
+ const watch_id = typeof rawId === "string" ? rawId.trim() : "";
300
316
  const handle = watches.get(watch_id);
301
317
  if (!handle) {
302
318
  return { ok: false, error: `No active watch with ID: ${watch_id}` };
@@ -307,6 +323,13 @@ var plugin = {
307
323
  } catch {
308
324
  }
309
325
  }
326
+ const prefix = `${watch_id}:`;
327
+ for (const [key, timer] of debounceTimers.entries()) {
328
+ if (key.startsWith(prefix)) {
329
+ clearTimeout(timer);
330
+ debounceTimers.delete(key);
331
+ }
332
+ }
310
333
  watches.delete(watch_id);
311
334
  api.metrics.gauge("active_watches", watches.size);
312
335
  return {
@@ -51,11 +51,14 @@ var DEFAULTS = {
51
51
  function readConfig(raw) {
52
52
  if (!raw || typeof raw !== "object") return { ...DEFAULTS };
53
53
  const r = raw;
54
+ const rawTimeout = r["timeoutMs"] ?? r["timeout_ms"] ?? r["timeout"];
55
+ const rawCovered = r["skipWhenCoveredBy"] ?? r["skip_when_covered_by"] ?? r["skipCovered"] ?? r["skip_covered"];
56
+ const rawTtl = r["skipTtlMs"] ?? r["skip_ttl_ms"] ?? r["ttlMs"] ?? r["ttl_ms"] ?? r["ttl"];
54
57
  return {
55
58
  enabled: r["enabled"] !== false,
56
- timeoutMs: typeof r["timeoutMs"] === "number" && r["timeoutMs"] > 0 ? r["timeoutMs"] : DEFAULTS.timeoutMs,
57
- skipWhenCoveredBy: r["skipWhenCoveredBy"] !== false,
58
- skipTtlMs: typeof r["skipTtlMs"] === "number" && r["skipTtlMs"] >= 0 ? r["skipTtlMs"] : DEFAULTS.skipTtlMs
59
+ timeoutMs: typeof rawTimeout === "number" && rawTimeout > 0 ? rawTimeout : DEFAULTS.timeoutMs,
60
+ skipWhenCoveredBy: rawCovered !== false,
61
+ skipTtlMs: typeof rawTtl === "number" && rawTtl >= 0 ? rawTtl : DEFAULTS.skipTtlMs
59
62
  };
60
63
  }
61
64
  var recentlyCovered = new runtime_exports.BoundedMap({ max: 256 });
@@ -253,8 +256,9 @@ var plugin = {
253
256
  if (input.toolResult?.isError) return;
254
257
  const toolName = input.toolName ?? "";
255
258
  const inp = input.toolInput ?? {};
256
- const filePath = inp["path"];
257
- if (!filePath || typeof filePath !== "string") return;
259
+ const rawPath = inp["path"] ?? inp["filePath"] ?? inp["file_path"] ?? inp["TargetFile"] ?? inp["targetFile"] ?? inp["file"];
260
+ const filePath = typeof rawPath === "string" && rawPath.trim() ? rawPath.trim() : void 0;
261
+ if (!filePath) return;
258
262
  if (cfg.skipWhenCoveredBy && cfg.skipTtlMs > 0) {
259
263
  evictExpired(cfg.skipTtlMs);
260
264
  if (recentlyCovered.has(filePath)) {
@@ -357,22 +357,28 @@ var plugin = {
357
357
  try {
358
358
  let type = input["type"];
359
359
  let scope = input["scope"];
360
- let summary = input["message"] ?? "";
360
+ let summary = input["message"] ?? input["summary"] ?? input["msg"] ?? input["description"] ?? "";
361
361
  let body = input["body"];
362
- const dryRun = input["dry_run"] ?? false;
363
- const explicitAsk = input["generate"] === true;
362
+ const dryRun = input["dry_run"] ?? input["dryRun"] ?? false;
363
+ const explicitAsk = (input["generate"] ?? input["autoGenerate"] ?? input["auto_generate"]) === true;
364
364
  const autoAsk = opts.useLlm && !input["type"] && !input["message"];
365
365
  const wantGenerate = (explicitAsk || autoAsk) && Boolean(api.llm);
366
366
  let files;
367
- const rawFiles = input["files"];
367
+ const rawFiles = input["files"] ?? input["fileList"] ?? input["file_list"];
368
368
  if (rawFiles !== void 0) {
369
369
  if (!Array.isArray(rawFiles)) {
370
370
  return { ok: false, error: "files must be an array of file paths" };
371
371
  }
372
372
  files = rawFiles;
373
+ } else if (typeof (input["file"] ?? input["file_path"]) === "string" && String(input["file"] ?? input["file_path"]).trim().length > 0) {
374
+ files = [String(input["file"] ?? input["file_path"]).trim()];
375
+ } else if (typeof input["filePath"] === "string" && input["filePath"].trim().length > 0) {
376
+ files = [input["filePath"].trim()];
377
+ } else if (typeof (input["TargetFile"] ?? input["targetFile"]) === "string" && String(input["TargetFile"] ?? input["targetFile"]).trim().length > 0) {
378
+ files = [String(input["TargetFile"] ?? input["targetFile"]).trim()];
373
379
  }
374
380
  let pathspecs;
375
- const rawPaths = input["paths"];
381
+ const rawPaths = input["paths"] ?? input["pathList"] ?? input["path_list"];
376
382
  if (rawPaths !== void 0) {
377
383
  if (!Array.isArray(rawPaths)) {
378
384
  return { ok: false, error: "paths must be an array of pathspec patterns" };
@@ -381,12 +387,14 @@ var plugin = {
381
387
  if (pathspecs.length === 0) {
382
388
  return { ok: false, error: "paths must contain at least one non-empty pattern" };
383
389
  }
384
- if (files && files.length > 0) {
385
- return {
386
- ok: false,
387
- error: "Pass either files (exact paths) or paths (pathspec globs), not both \u2014 the other would be silently ignored."
388
- };
389
- }
390
+ } else if (typeof input["path"] === "string" && input["path"].trim().length > 0) {
391
+ pathspecs = [input["path"].trim()];
392
+ }
393
+ if (rawPaths !== void 0 && files && files.length > 0) {
394
+ return {
395
+ ok: false,
396
+ error: "Pass either files (exact paths) or paths (pathspec globs), not both \u2014 the other would be silently ignored."
397
+ };
390
398
  }
391
399
  let commitScope;
392
400
  let staged = [];
@@ -562,7 +570,7 @@ ${stagedDiff}
562
570
  lastCommit.hash = String(hash);
563
571
  lastCommit.at = (/* @__PURE__ */ new Date()).toISOString();
564
572
  try {
565
- await api.session.append({
573
+ await api.session?.append?.({
566
574
  type: "git-autocommit:commit",
567
575
  ts: (/* @__PURE__ */ new Date()).toISOString(),
568
576
  hash: String(hash),
@@ -257,9 +257,10 @@ var plugin = {
257
257
  if (!cfg.enabled) return;
258
258
  if (input.toolResult?.isError) return;
259
259
  const toolName = input.toolName ?? "";
260
- const inp = input.toolInput ?? {};
261
- const rawPath = inp["path"];
262
- if (typeof rawPath !== "string" || rawPath.trim().length === 0) return;
260
+ const toolInput = input.toolInput ?? {};
261
+ const raw = toolInput["path"] ?? toolInput["filePath"] ?? toolInput["file_path"] ?? toolInput["TargetFile"] ?? toolInput["targetFile"] ?? toolInput["file"];
262
+ const rawPath = typeof raw === "string" ? raw : "";
263
+ if (rawPath.trim().length === 0) return;
263
264
  if (basename(rawPath) === ".gitignore") return;
264
265
  const resolved = projectRelativePath(rawPath, input.cwd);
265
266
  if (!resolved) return;
@@ -365,7 +366,9 @@ var plugin = {
365
366
  category: "Code Quality",
366
367
  mutating: true,
367
368
  async execute(input) {
368
- const rawPath = typeof input?.path === "string" ? input.path : "";
369
+ const rawInp = input ?? {};
370
+ const raw = rawInp["path"] ?? rawInp["filePath"] ?? rawInp["file_path"] ?? rawInp["TargetFile"] ?? rawInp["targetFile"] ?? rawInp["file"];
371
+ const rawPath = typeof raw === "string" ? raw.trim() : "";
369
372
  if (rawPath.length === 0) return { ok: false, reason: "path is required" };
370
373
  const resolved = projectRelativePath(rawPath, process.cwd());
371
374
  if (!resolved) {
@@ -116,12 +116,15 @@ var DEFAULTS = {
116
116
  function readConfig(raw) {
117
117
  if (!raw || typeof raw !== "object") return { ...DEFAULTS };
118
118
  const r = raw;
119
+ const rawFallback = r["fallbackCommand"] ?? r["fallback_command"] ?? r["fallback"];
120
+ const rawTimeout = r["timeoutMs"] ?? r["timeout_ms"] ?? r["timeout"];
121
+ const rawNotify = r["notifyFormatOnSave"] ?? r["notify_format_on_save"] ?? r["notify"];
119
122
  return {
120
123
  enabled: r["enabled"] !== false,
121
124
  command: typeof r["command"] === "string" && r["command"].length > 0 ? r["command"] : DEFAULTS.command,
122
- fallbackCommand: typeof r["fallbackCommand"] === "string" && r["fallbackCommand"].length > 0 ? r["fallbackCommand"] : DEFAULTS.fallbackCommand,
123
- timeoutMs: typeof r["timeoutMs"] === "number" && r["timeoutMs"] > 0 ? r["timeoutMs"] : DEFAULTS.timeoutMs,
124
- notifyFormatOnSave: r["notifyFormatOnSave"] !== false
125
+ fallbackCommand: typeof rawFallback === "string" && rawFallback.length > 0 ? rawFallback : DEFAULTS.fallbackCommand,
126
+ timeoutMs: typeof rawTimeout === "number" && rawTimeout > 0 ? rawTimeout : DEFAULTS.timeoutMs,
127
+ notifyFormatOnSave: rawNotify !== false
125
128
  };
126
129
  }
127
130
  var MAX_CAPTURE_BYTES = 4 * 1024 * 1024;
@@ -287,8 +290,9 @@ var plugin = {
287
290
  if (input.toolResult?.isError) return;
288
291
  const toolName = input.toolName ?? "";
289
292
  const inp = input.toolInput ?? {};
290
- const filePath = inp["path"];
291
- if (!filePath || typeof filePath !== "string") return;
293
+ const rawPath = inp["path"] ?? inp["filePath"] ?? inp["file_path"] ?? inp["TargetFile"] ?? inp["targetFile"] ?? inp["file"];
294
+ const filePath = typeof rawPath === "string" ? rawPath : void 0;
295
+ if (!filePath) return;
292
296
  const ext = filePath.includes(".") ? filePath.slice(filePath.lastIndexOf(".")) : "";
293
297
  if (![".ts", ".tsx", ".js", ".jsx", ".mjs", ".mts", ".cjs", ".cts"].includes(ext)) return;
294
298
  state.invocationCount += 1;