@wrongstack/plugins 1.0.7 → 1.0.10

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/index.d.ts +1 -1
  2. package/dist/accessibility-auditor.js +15 -3
  3. package/dist/agent-handoff.js +6 -6
  4. package/dist/auto-doc/index.d.ts +1 -1
  5. package/dist/auto-doc.js +31 -20
  6. package/dist/auto-i18n-extractor/index.d.ts +1 -1
  7. package/dist/auto-i18n-extractor.js +12 -8
  8. package/dist/branch-guard.js +3 -3
  9. package/dist/changelog-writer/index.d.ts +1 -1
  10. package/dist/changelog-writer.js +19 -10
  11. package/dist/checkpoint/index.d.ts +1 -1
  12. package/dist/checkpoint.js +38 -24
  13. package/dist/code-metrics/index.d.ts +1 -1
  14. package/dist/code-metrics.js +12 -4
  15. package/dist/commit-validator.js +5 -5
  16. package/dist/context-pins/index.d.ts +1 -1
  17. package/dist/context-pins.js +12 -9
  18. package/dist/cost-tracker.js +21 -9
  19. package/dist/cron/index.d.ts +1 -1
  20. package/dist/cron.js +18 -5
  21. package/dist/dead-code-detector/index.d.ts +1 -1
  22. package/dist/dead-code-detector.js +14 -12
  23. package/dist/duplicate-code-detector/index.d.ts +1 -1
  24. package/dist/duplicate-code-detector.js +11 -3
  25. package/dist/feature-flag-tracker/index.d.ts +1 -1
  26. package/dist/feature-flag-tracker.js +12 -4
  27. package/dist/file-watcher/index.d.ts +1 -1
  28. package/dist/file-watcher.js +37 -38
  29. package/dist/git-autocommit/index.d.ts +1 -1
  30. package/dist/git-autocommit.js +44 -39
  31. package/dist/gitignore-guard/index.d.ts +1 -1
  32. package/dist/gitignore-guard.js +12 -6
  33. package/dist/index.js +1534 -1080
  34. package/dist/interface-contract-guard/index.d.ts +1 -1
  35. package/dist/interface-contract-guard.js +12 -4
  36. package/dist/knowledge-graph/index.d.ts +1 -1
  37. package/dist/knowledge-graph.js +11 -9
  38. package/dist/migration-planner/index.d.ts +1 -1
  39. package/dist/migration-planner.js +6 -2
  40. package/dist/notify-hub/index.d.ts +1 -1
  41. package/dist/notify-hub.js +12 -11
  42. package/dist/performance-regression-gate/index.d.ts +1 -1
  43. package/dist/performance-regression-gate.js +33 -13
  44. package/dist/pr-drafter/index.d.ts +10 -1
  45. package/dist/pr-drafter.js +57 -26
  46. package/dist/refactor-suggester/index.d.ts +1 -1
  47. package/dist/refactor-suggester.js +17 -4
  48. package/dist/release-notes-generator.js +2 -2
  49. package/dist/secret-scanner/index.d.ts +1 -1
  50. package/dist/secret-scanner.js +11 -3
  51. package/dist/security-hotspot-scanner/index.d.ts +1 -1
  52. package/dist/security-hotspot-scanner.js +6 -2
  53. package/dist/semantic-search-indexer/index.d.ts +1 -1
  54. package/dist/semantic-search-indexer.js +19 -3
  55. package/dist/semver-bump/index.d.ts +1 -1
  56. package/dist/semver-bump.js +57 -37
  57. package/dist/session-recap.js +4 -2
  58. package/dist/shell-check/index.d.ts +1 -1
  59. package/dist/shell-check.js +30 -39
  60. package/dist/smart-rename/index.d.ts +1 -1
  61. package/dist/smart-rename.js +23 -10
  62. package/dist/template-engine/index.d.ts +1 -1
  63. package/dist/template-engine.js +51 -38
  64. package/dist/test-flake-detector/index.d.ts +1 -1
  65. package/dist/test-flake-detector.js +11 -5
  66. package/dist/test-generator/index.d.ts +1 -1
  67. package/dist/test-generator.js +12 -8
  68. package/dist/todo-tracker/index.d.ts +68 -1
  69. package/dist/todo-tracker.js +579 -395
  70. package/dist/token-budget.js +7 -4
  71. package/dist/token-throttle.js +6 -3
  72. package/package.json +7 -7
@@ -23,7 +23,7 @@
23
23
  *
24
24
  * @public
25
25
  */
26
- import type { Plugin } from '@wrongstack/core/types';
26
+ import { type Plugin } from '@wrongstack/core/types';
27
27
  export interface InterfaceContractFinding {
28
28
  interfaceName: string;
29
29
  file: string;
@@ -13,8 +13,9 @@ var __copyProps = (to, from, except, desc) => {
13
13
  var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
14
14
 
15
15
  // src/interface-contract-guard/index.ts
16
- import { readFile } from "node:fs/promises";
16
+ import { readFile, stat } from "node:fs/promises";
17
17
  import { isAbsolute, relative, resolve } from "node:path";
18
+ import { ToolValidationError } from "@wrongstack/core/types";
18
19
 
19
20
  // src/runtime/index.ts
20
21
  var runtime_exports = {};
@@ -216,19 +217,26 @@ If you changed member shapes, search the project for implementers/\`satisfies\`/
216
217
  category: "Diagnostics",
217
218
  mutating: false,
218
219
  async execute(input) {
219
- if (!cfg.enabled) return { ok: false, error: "interface-contract-guard is disabled" };
220
+ if (!cfg.enabled) throw new Error("interface-contract-guard is disabled");
220
221
  const raw = input ?? {};
221
222
  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["dir"] === "string" ? raw["dir"] : void 0) ?? (typeof raw["SearchDirectory"] === "string" ? raw["SearchDirectory"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? (typeof raw["file"] === "string" ? raw["file"] : void 0) ?? ".";
222
223
  if (!(0, runtime_exports.withinProject)(rawPath)) {
223
- return { ok: false, error: "path is outside the project root" };
224
+ throw new ToolValidationError({
225
+ message: "path is outside the project root",
226
+ field: "path"
227
+ });
224
228
  }
225
229
  state.scanCount += 1;
226
230
  let result;
227
231
  try {
232
+ await stat(resolve(process.cwd(), rawPath));
228
233
  result = await scanPath(rawPath, cfg);
229
234
  } catch (err) {
230
235
  state.errorCount += 1;
231
- return { ok: false, error: String(err) };
236
+ throw new Error(
237
+ `check_interface_contracts failed for ${rawPath}: ${err instanceof Error ? err.message : String(err)}`,
238
+ { cause: err }
239
+ );
232
240
  }
233
241
  state.findingCount += result.findings.length;
234
242
  return {
@@ -28,7 +28,7 @@
28
28
  *
29
29
  * @public
30
30
  */
31
- import type { Plugin } from '@wrongstack/core/types';
31
+ import { type Plugin } from '@wrongstack/core/types';
32
32
  export interface Fact {
33
33
  id: string;
34
34
  subject: string;
@@ -1,6 +1,7 @@
1
1
  // src/knowledge-graph/index.ts
2
2
  import { readFileSync } from "node:fs";
3
3
  import { dirname, isAbsolute, relative, resolve } from "node:path";
4
+ import { ToolValidationError } from "@wrongstack/core/types";
4
5
  import { atomicWrite, ensureDir } from "@wrongstack/core/utils";
5
6
  var API_VERSION = "^0.1.10";
6
7
  var state = {
@@ -171,12 +172,9 @@ var plugin = {
171
172
  category: "Memory",
172
173
  mutating: true,
173
174
  async execute(input) {
174
- if (!cfg.enabled) return { ok: false, error: "knowledge-graph is disabled" };
175
+ if (!cfg.enabled) throw new Error("knowledge-graph is disabled");
175
176
  if (state.facts.length >= cfg.maxFacts) {
176
- return {
177
- ok: false,
178
- error: `fact limit reached (${cfg.maxFacts}). Remove old facts first.`
179
- };
177
+ throw new Error(`fact limit reached (${cfg.maxFacts}). Remove old facts first.`);
180
178
  }
181
179
  const trim = (s) => String(s ?? "").trim().slice(0, cfg.maxFactChars);
182
180
  const raw = input;
@@ -190,7 +188,10 @@ var plugin = {
190
188
  input.object ?? raw["target"] ?? raw["val"] ?? raw["value"] ?? raw["obj"] ?? raw["targetEntity"]
191
189
  );
192
190
  if (!subject || !relation || !object) {
193
- return { ok: false, error: "subject, relation, and object are required" };
191
+ throw new ToolValidationError({
192
+ message: "subject, relation, and object are required",
193
+ field: !subject ? "subject" : !relation ? "relation" : "object"
194
+ });
194
195
  }
195
196
  const rawConf = typeof input.confidence === "string" ? input.confidence.trim().toLowerCase() : "";
196
197
  const confidence = rawConf === "low" || rawConf === "high" || rawConf === "medium" ? rawConf : "medium";
@@ -227,7 +228,7 @@ var plugin = {
227
228
  category: "Memory",
228
229
  mutating: false,
229
230
  async execute(input) {
230
- if (!cfg.enabled) return { ok: false, error: "knowledge-graph is disabled" };
231
+ if (!cfg.enabled) throw new Error("knowledge-graph is disabled");
231
232
  state.queries += 1;
232
233
  const raw = input;
233
234
  const rawQ = (typeof raw["query"] === "string" ? raw["query"] : void 0) ?? (typeof raw["q"] === "string" ? raw["q"] : void 0);
@@ -272,16 +273,17 @@ var plugin = {
272
273
  category: "Memory",
273
274
  mutating: true,
274
275
  async execute(input) {
275
- if (!cfg.enabled) return { ok: false, error: "knowledge-graph is disabled" };
276
+ if (!cfg.enabled) throw new Error("knowledge-graph is disabled");
276
277
  const before = state.facts.length;
277
278
  const raw = input ?? {};
278
279
  const rawId = String(input.id ?? raw["factId"] ?? raw["fact_id"] ?? "").trim();
280
+ if (!rawId) throw new ToolValidationError({ message: "id is required", field: "id" });
279
281
  const normalized = rawId.toLowerCase().startsWith("kg-") ? rawId.toLowerCase() : `kg-${rawId.toLowerCase()}`;
280
282
  state.facts = state.facts.filter(
281
283
  (f) => f.id.toLowerCase() !== normalized && f.id !== rawId
282
284
  );
283
285
  const removed = before - state.facts.length;
284
- if (removed === 0) return { ok: false, error: `no fact matches "${input.id ?? rawId}"` };
286
+ if (removed === 0) throw new Error(`no fact matches "${input.id ?? rawId}"`);
285
287
  state.removals += removed;
286
288
  api.metrics.counter("removals", removed);
287
289
  const persisted = await persistFacts(resolved);
@@ -25,7 +25,7 @@
25
25
  *
26
26
  * @public
27
27
  */
28
- import type { Plugin } from '@wrongstack/core/types';
28
+ import { type Plugin } from '@wrongstack/core/types';
29
29
  export interface MigrationAiAnalysis {
30
30
  summary: string;
31
31
  riskLevel: 'low' | 'medium' | 'high' | 'unknown';
@@ -14,6 +14,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
14
14
 
15
15
  // src/migration-planner/index.ts
16
16
  import { existsSync, readFileSync } from "node:fs";
17
+ import { ToolValidationError } from "@wrongstack/core/types";
17
18
 
18
19
  // src/runtime/index.ts
19
20
  var runtime_exports = {};
@@ -392,7 +393,7 @@ var plugin = {
392
393
  category: "Planning",
393
394
  mutating: false,
394
395
  async execute(input, _ctx, execOpts) {
395
- if (!cfg.enabled) return { ok: false, error: "migration-planner is disabled" };
396
+ if (!cfg.enabled) throw new Error("migration-planner is disabled");
396
397
  execOpts?.signal?.throwIfAborted();
397
398
  const raw = input ?? {};
398
399
  const rawPackage = input.packageName || raw["package"] || raw["pkg"] || raw["name"] || raw["package_name"] || raw["packageName"] || raw["dependency"] || raw["dep"] || raw["module"];
@@ -403,7 +404,10 @@ var plugin = {
403
404
  const fromVersion = String(rawFrom ?? "").trim();
404
405
  const toVersion = String(rawTo ?? "").trim();
405
406
  if (!packageName || !fromVersion || !toVersion) {
406
- return { ok: false, error: "packageName, fromVersion, and toVersion are required" };
407
+ throw new ToolValidationError({
408
+ message: "packageName, fromVersion, and toVersion are required",
409
+ field: !packageName ? "packageName" : !fromVersion ? "fromVersion" : "toVersion"
410
+ });
407
411
  }
408
412
  const changelog = readChangelog(packageName, cfg);
409
413
  let breakingChanges;
@@ -1,4 +1,4 @@
1
- import type { Plugin } from '@wrongstack/core/types';
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
@@ -14,6 +14,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
14
14
 
15
15
  // src/notify-hub/index.ts
16
16
  import { lookup } from "node:dns/promises";
17
+ import { ToolValidationError } from "@wrongstack/core/types";
17
18
 
18
19
  // src/runtime/index.ts
19
20
  var runtime_exports = {};
@@ -421,18 +422,19 @@ var plugin = {
421
422
  category: "Notifications",
422
423
  mutating: true,
423
424
  async execute(input) {
424
- if (!cfg.enabled) return { ok: false, error: "notify-hub is disabled" };
425
+ if (!cfg.enabled) throw new Error("notify-hub is disabled");
425
426
  const ch = state.channel;
426
427
  if (!ch) {
427
- return {
428
- ok: false,
429
- error: 'no webhookUrl configured \u2014 set config.extensions["notify-hub"].webhookUrl to enable deliveries'
430
- };
428
+ throw new Error(
429
+ 'no webhookUrl configured \u2014 set config.extensions["notify-hub"].webhookUrl to enable deliveries'
430
+ );
431
431
  }
432
432
  const inp = input ?? {};
433
433
  const rawMsg = inp["message"] ?? inp["body"] ?? inp["text"] ?? inp["content"] ?? inp["msg"];
434
434
  const message = typeof rawMsg === "string" && rawMsg.trim().length > 0 ? rawMsg.trim() : "";
435
- if (!message) return { ok: false, error: "message is required" };
435
+ if (!message) {
436
+ throw new ToolValidationError({ message: "message is required", field: "message" });
437
+ }
436
438
  const rawTitle = inp["title"] ?? inp["subject"] ?? inp["header"];
437
439
  const title = typeof rawTitle === "string" && rawTitle.trim() ? rawTitle.trim() : "WrongStack notification";
438
440
  const result = await deliverViaChannel(ch, "manual", {
@@ -441,11 +443,10 @@ var plugin = {
441
443
  level: input.level === "warning" || input.level === "critical" ? input.level : "info",
442
444
  source: "manual"
443
445
  });
444
- return {
445
- ok: result.ok,
446
- circuitOpen: ch.circuitStatus().open,
447
- ...result.ok ? {} : { error: result.error ?? "delivery failed" }
448
- };
446
+ if (!result.ok) {
447
+ throw new Error(`notification delivery failed: ${result.error ?? "unknown error"}`);
448
+ }
449
+ return { ok: true, circuitOpen: ch.circuitStatus().open };
449
450
  }
450
451
  });
451
452
  api.tools.register({
@@ -25,7 +25,7 @@
25
25
  *
26
26
  * @public
27
27
  */
28
- import type { Plugin } from '@wrongstack/core/types';
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,6 +1,7 @@
1
1
  // src/performance-regression-gate/index.ts
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { isAbsolute, relative, resolve } from "node:path";
4
+ import { ToolValidationError } from "@wrongstack/core/types";
4
5
  var API_VERSION = "^0.1.10";
5
6
  var state = {
6
7
  invocationCount: 0,
@@ -67,10 +68,11 @@ function flattenResults(results) {
67
68
  function loadResults(path) {
68
69
  if (!path || !existsSync(path)) return null;
69
70
  try {
70
- const raw = JSON.parse(readFileSync(path, "utf-8"));
71
- return raw;
72
- } catch {
73
- return null;
71
+ return JSON.parse(readFileSync(path, "utf-8"));
72
+ } catch (err) {
73
+ throw new Error(`Could not read benchmark results at ${path}: ${String(err)}`, {
74
+ cause: err
75
+ });
74
76
  }
75
77
  }
76
78
  function stripVariantSuffix(name) {
@@ -197,7 +199,7 @@ var plugin = {
197
199
  mutating: false,
198
200
  async execute(input) {
199
201
  if (!cfg.enabled) {
200
- return { ok: false, error: "performance-regression-gate is disabled" };
202
+ throw new Error("performance-regression-gate is disabled");
201
203
  }
202
204
  state.invocationCount += 1;
203
205
  const raw = input ?? {};
@@ -208,11 +210,23 @@ var plugin = {
208
210
  const resultsPath = resolveProjectPath(resultsPathStr) ?? "";
209
211
  if (!resultsPath) {
210
212
  state.errorCount += 1;
211
- return { ok: false, error: "invalid results path (must be inside project)" };
213
+ throw new ToolValidationError({
214
+ message: "invalid results path (must be inside project)",
215
+ field: "resultsPath"
216
+ });
217
+ }
218
+ let results;
219
+ try {
220
+ results = loadResults(resultsPath);
221
+ } catch (err) {
222
+ state.errorCount += 1;
223
+ throw err;
212
224
  }
213
- const results = loadResults(resultsPath);
214
225
  if (!results) {
215
226
  state.missingResultsCount += 1;
227
+ if (resultsPathStr !== "bench-results.json" || rawResultsPath !== "bench-results.json") {
228
+ throw new Error(`No benchmark results found at ${resultsPathStr}.`);
229
+ }
216
230
  return {
217
231
  ok: true,
218
232
  hasResults: false,
@@ -241,15 +255,21 @@ var plugin = {
241
255
  const baselineResolved = resolveProjectPath(baselinePathStr) ?? "";
242
256
  if (!baselineResolved) {
243
257
  state.errorCount += 1;
244
- return { ok: false, error: "invalid baseline path (must be inside project)" };
258
+ throw new ToolValidationError({
259
+ message: "invalid baseline path (must be inside project)",
260
+ field: "baselinePath"
261
+ });
262
+ }
263
+ let baselineResults;
264
+ try {
265
+ baselineResults = loadResults(baselineResolved);
266
+ } catch (err) {
267
+ state.errorCount += 1;
268
+ throw err;
245
269
  }
246
- const baselineResults = loadResults(baselineResolved);
247
270
  if (!baselineResults) {
248
271
  state.errorCount += 1;
249
- return {
250
- ok: false,
251
- error: `Could not read baseline results at ${baselinePathStr}.`
252
- };
272
+ throw new Error(`Could not read baseline results at ${baselinePathStr}.`);
253
273
  }
254
274
  const baseline = flattenResults(baselineResults);
255
275
  pairs = pairCrossFile(baseline, current);
@@ -27,7 +27,16 @@
27
27
  *
28
28
  * @public
29
29
  */
30
- import type { Plugin } from '@wrongstack/core/types';
30
+ import { type Plugin } from '@wrongstack/core/types';
31
+ /**
32
+ * Extract the commit from git_autocommit's serialized result (`{ok, hash, message, …, diff}`).
33
+ * Regex, not JSON.parse: the trailing diff can push the text past the output budget and
34
+ * truncate it. A dry run carries no `hash`, so it records nothing.
35
+ */
36
+ export declare function parseAutocommitResult(content: string): {
37
+ hash: string;
38
+ message: string;
39
+ } | null;
31
40
  declare const plugin: Plugin;
32
41
  export default plugin;
33
42
  //# sourceMappingURL=index.d.ts.map
@@ -16,6 +16,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
16
16
  import { execFile } from "node:child_process";
17
17
  import { mkdir, writeFile } from "node:fs/promises";
18
18
  import { dirname, isAbsolute, relative, resolve } from "node:path";
19
+ import { ToolValidationError } from "@wrongstack/core/types";
19
20
 
20
21
  // src/runtime/index.ts
21
22
  var runtime_exports = {};
@@ -35,8 +36,23 @@ var state = {
35
36
  draftErrors: 0,
36
37
  stopInvocations: 0,
37
38
  stopHookUnregister: null,
39
+ postHookUnregister: null,
38
40
  eventUnsubscribers: []
39
41
  };
42
+ var TRACKED_FILE_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "write_to_file", "replace_file_content"]);
43
+ function parseAutocommitResult(content) {
44
+ const hash = /"hash":\s*"([0-9a-f]{7,64})"/.exec(content)?.[1];
45
+ if (!hash) return null;
46
+ const rawMessage = /"message":\s*("(?:[^"\\]|\\.)*")/.exec(content)?.[1];
47
+ let message = "commit";
48
+ if (rawMessage) {
49
+ try {
50
+ message = JSON.parse(rawMessage);
51
+ } catch {
52
+ }
53
+ }
54
+ return { hash, message };
55
+ }
40
56
  var DEFAULTS = {
41
57
  enabled: true,
42
58
  outputPath: ".wrongstack/PR_DRAFT.md",
@@ -219,6 +235,7 @@ var plugin = {
219
235
  state.draftErrors = 0;
220
236
  state.stopInvocations = 0;
221
237
  state.stopHookUnregister = (0, runtime_exports.releaseHandle)(state.stopHookUnregister);
238
+ state.postHookUnregister = (0, runtime_exports.releaseHandle)(state.postHookUnregister);
222
239
  for (const off of state.eventUnsubscribers) {
223
240
  try {
224
241
  off();
@@ -228,22 +245,24 @@ var plugin = {
228
245
  state.eventUnsubscribers = [];
229
246
  const cfg = readConfig(api.config.extensions?.["pr-drafter"]);
230
247
  if (api.onPattern) {
231
- const offTool = api.onPattern("tool.completed", (_event, payload) => {
232
- const p = payload;
233
- const toolName = p?.tool;
248
+ const offTool = api.onPattern("tool.completed", () => {
234
249
  state.toolCalls += 1;
235
- if (toolName === "git_autocommit" && p?.result?.committed) {
236
- const msg = p.result.commitMessage ?? p.input?.message ?? "commit";
237
- state.commits.push(msg);
238
- }
239
- const rawInput = p?.input ?? {};
240
- const filePath = typeof rawInput["path"] === "string" && rawInput["path"] || typeof rawInput["filePath"] === "string" && rawInput["filePath"] || typeof rawInput["file_path"] === "string" && rawInput["file_path"] || typeof rawInput["TargetFile"] === "string" && rawInput["TargetFile"] || typeof rawInput["targetFile"] === "string" && rawInput["targetFile"] || typeof rawInput["file"] === "string" && rawInput["file"];
241
- if ((toolName === "write" || toolName === "edit" || toolName === "write_to_file" || toolName === "replace_file_content") && filePath) {
242
- state.files.add(filePath);
243
- }
244
250
  });
245
251
  state.eventUnsubscribers.push(offTool);
246
252
  }
253
+ const postHook = (input) => {
254
+ if (!input.toolResult || input.toolResult.isError) return;
255
+ const toolName = input.toolName;
256
+ if (toolName === "git_autocommit") {
257
+ const commit = parseAutocommitResult(String(input.toolResult.content ?? ""));
258
+ if (commit) state.commits.push(commit.message);
259
+ return;
260
+ }
261
+ if (!toolName || !TRACKED_FILE_TOOLS.has(toolName)) return;
262
+ const rawInput = input.toolInput ?? {};
263
+ const filePath = ["path", "filePath", "file_path", "TargetFile", "targetFile", "file"].map((key) => rawInput[key]).find((value) => typeof value === "string" && value.length > 0);
264
+ if (filePath) state.files.add(filePath);
265
+ };
247
266
  if (api.onEvent) {
248
267
  const offUsage = api.onEvent("provider.response", (payload) => {
249
268
  const p = payload;
@@ -262,6 +281,11 @@ var plugin = {
262
281
  await writeDraft(cfg, api.llm);
263
282
  };
264
283
  state.stopHookUnregister = api.registerHook("Stop", void 0, stopHook);
284
+ state.postHookUnregister = api.registerHook(
285
+ "PostToolUse",
286
+ "git_autocommit|write|edit|write_to_file|replace_file_content",
287
+ postHook
288
+ );
265
289
  api.tools.register({
266
290
  name: "pr_draft",
267
291
  description: "Generate or refresh the pull-request draft for the current session. Writes the markdown file and returns its path + title.",
@@ -284,7 +308,7 @@ var plugin = {
284
308
  mutating: true,
285
309
  capabilities: ["fs.write"],
286
310
  async execute(input = {}) {
287
- if (!cfg.enabled) return { ok: false, error: "pr-drafter is disabled" };
311
+ if (!cfg.enabled) throw new Error("pr-drafter is disabled");
288
312
  const raw = input ?? {};
289
313
  const preview = Boolean(
290
314
  input?.preview ?? raw["dryRun"] ?? raw["dry_run"] ?? raw["dry"] ?? raw["previewOnly"]
@@ -296,24 +320,29 @@ var plugin = {
296
320
  return { ok: true, preview: true, title: draft.title, body: draft.body };
297
321
  }
298
322
  const resolved = resolveProjectPath(outputPathStr);
299
- if (!resolved) return { ok: false, error: "outputPath resolves outside project" };
323
+ if (!resolved) {
324
+ throw new ToolValidationError({
325
+ message: "outputPath resolves outside project",
326
+ field: "outputPath"
327
+ });
328
+ }
300
329
  try {
301
330
  await mkdir(dirname(resolved), { recursive: true });
302
331
  await writeFile(resolved, draft.body);
303
- state.draftsWritten += 1;
304
- return {
305
- ok: true,
306
- path: outputPathStr,
307
- resolvedPath: resolved,
308
- title: draft.title
309
- };
310
332
  } catch (err) {
311
333
  state.draftErrors += 1;
312
- return {
313
- ok: false,
314
- error: err instanceof Error ? err.message : String(err)
315
- };
334
+ throw new Error(
335
+ `Could not write PR draft to ${outputPathStr}: ${err instanceof Error ? err.message : String(err)}`,
336
+ { cause: err }
337
+ );
316
338
  }
339
+ state.draftsWritten += 1;
340
+ return {
341
+ ok: true,
342
+ path: outputPathStr,
343
+ resolvedPath: resolved,
344
+ title: draft.title
345
+ };
317
346
  }
318
347
  });
319
348
  api.log.info("pr-drafter plugin loaded", {
@@ -330,6 +359,7 @@ var plugin = {
330
359
  }
331
360
  state.stopHookUnregister = null;
332
361
  }
362
+ state.postHookUnregister = (0, runtime_exports.releaseHandle)(state.postHookUnregister);
333
363
  for (const off of state.eventUnsubscribers) {
334
364
  try {
335
365
  off();
@@ -371,5 +401,6 @@ var plugin = {
371
401
  };
372
402
  var pr_drafter_default = plugin;
373
403
  export {
374
- pr_drafter_default as default
404
+ pr_drafter_default as default,
405
+ parseAutocommitResult
375
406
  };
@@ -23,7 +23,7 @@
23
23
  *
24
24
  * @public
25
25
  */
26
- import type { Plugin } from '@wrongstack/core/types';
26
+ import { type Plugin } from '@wrongstack/core/types';
27
27
  export type SmellType = 'long-function' | 'deep-nesting' | 'many-parameters' | 'magic-number' | 'console-log';
28
28
  export interface RefactorSuggestion {
29
29
  file: string;
@@ -13,8 +13,9 @@ var __copyProps = (to, from, except, desc) => {
13
13
  var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
14
14
 
15
15
  // src/refactor-suggester/index.ts
16
- import { readFile } from "node:fs/promises";
16
+ import { readFile, stat } from "node:fs/promises";
17
17
  import { isAbsolute, relative, resolve } from "node:path";
18
+ import { ToolValidationError } from "@wrongstack/core/types";
18
19
 
19
20
  // src/runtime/index.ts
20
21
  var runtime_exports = {};
@@ -311,11 +312,23 @@ var plugin = {
311
312
  category: "Diagnostics",
312
313
  mutating: false,
313
314
  async execute(input) {
314
- if (!cfg.enabled) return { ok: false, error: "refactor-suggester is disabled" };
315
+ if (!cfg.enabled) throw new Error("refactor-suggester is disabled");
315
316
  const raw = input ?? {};
316
317
  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["dir"] === "string" ? raw["dir"] : void 0) ?? (typeof raw["SearchDirectory"] === "string" ? raw["SearchDirectory"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? ".";
317
318
  if (!(0, runtime_exports.withinProject)(rawPath)) {
318
- return { ok: false, error: "path is outside the project root" };
319
+ throw new ToolValidationError({
320
+ message: "path is outside the project root",
321
+ field: "path"
322
+ });
323
+ }
324
+ try {
325
+ await stat(resolve(process.cwd(), rawPath));
326
+ } catch (err) {
327
+ throw new ToolValidationError({
328
+ message: `path does not exist or cannot be read: ${rawPath}`,
329
+ field: "path",
330
+ cause: err
331
+ });
319
332
  }
320
333
  state.scanCount += 1;
321
334
  let result;
@@ -323,7 +336,7 @@ var plugin = {
323
336
  result = await scanPath(rawPath, cfg);
324
337
  } catch (err) {
325
338
  state.errorCount += 1;
326
- return { ok: false, error: String(err) };
339
+ throw new Error(`refactor scan failed: ${String(err)}`, { cause: err });
327
340
  }
328
341
  state.suggestionCount += result.suggestions.length;
329
342
  return {
@@ -284,7 +284,7 @@ var plugin = {
284
284
  category: "Development",
285
285
  mutating: false,
286
286
  async execute(input, _ctx, execOpts) {
287
- if (!cfg.enabled) return { ok: false, error: "release-notes-generator is disabled" };
287
+ if (!cfg.enabled) throw new Error("release-notes-generator is disabled");
288
288
  execOpts?.signal?.throwIfAborted();
289
289
  const raw = input ?? {};
290
290
  const rawTo = input.to ?? raw["to_ref"] ?? raw["toRef"] ?? raw["until"] ?? raw["end"];
@@ -299,7 +299,7 @@ var plugin = {
299
299
  commits = await getCommits(fromRef, toRef, execOpts?.signal);
300
300
  } catch (err) {
301
301
  state.errorCount += 1;
302
- return { ok: false, error: String(err) };
302
+ throw new Error(`Could not read git history: ${String(err)}`, { cause: err });
303
303
  }
304
304
  state.commitCount += commits.length;
305
305
  execOpts?.signal?.throwIfAborted();
@@ -26,7 +26,7 @@
26
26
  * They share the same threat model but act at different points in the
27
27
  * pipeline.
28
28
  */
29
- import type { Plugin } from '@wrongstack/core/types';
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
@@ -12,6 +12,9 @@ var __copyProps = (to, from, except, desc) => {
12
12
  };
13
13
  var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
14
14
 
15
+ // src/secret-scanner/index.ts
16
+ import { ToolValidationError } from "@wrongstack/core/types";
17
+
15
18
  // src/runtime/credential-patterns.ts
16
19
  import {
17
20
  cloneCredentialPatterns,
@@ -447,9 +450,14 @@ var plugin = {
447
450
  mutating: false,
448
451
  async execute(input) {
449
452
  activateRuntime(runtime);
450
- const rawText = input["text"] ?? input["content"] ?? input["string"] ?? input["input"] ?? input["code"] ?? input["value"] ?? "";
451
- const text = typeof rawText === "string" ? rawText : "";
452
- const matched = findMatches(text);
453
+ const rawText = input["text"] ?? input["content"] ?? input["string"] ?? input["input"] ?? input["code"] ?? input["value"];
454
+ if (typeof rawText !== "string") {
455
+ throw new ToolValidationError({
456
+ message: "text is required and must be a string",
457
+ field: "text"
458
+ });
459
+ }
460
+ const matched = findMatches(rawText);
453
461
  return {
454
462
  ok: true,
455
463
  matched,
@@ -22,7 +22,7 @@
22
22
  *
23
23
  * @public
24
24
  */
25
- import type { Plugin } from '@wrongstack/core/types';
25
+ import { type Plugin } from '@wrongstack/core/types';
26
26
  declare const plugin: Plugin;
27
27
  export default plugin;
28
28
  //# sourceMappingURL=index.d.ts.map
@@ -15,6 +15,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
15
15
  // src/security-hotspot-scanner/index.ts
16
16
  import { readdir, readFile, stat } from "node:fs/promises";
17
17
  import { extname, isAbsolute, relative, resolve } from "node:path";
18
+ import { ToolValidationError } from "@wrongstack/core/types";
18
19
 
19
20
  // src/runtime/index.ts
20
21
  var runtime_exports = {};
@@ -335,7 +336,7 @@ Review or remove the risky pattern(s).`;
335
336
  category: "Security",
336
337
  mutating: false,
337
338
  async execute(input) {
338
- if (!cfg.enabled) return { ok: false, error: "security-hotspot-scanner is disabled" };
339
+ if (!cfg.enabled) throw new Error("security-hotspot-scanner is disabled");
339
340
  const raw = input;
340
341
  const targetPath = (typeof input.path === "string" ? input.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["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? ".";
341
342
  const result = await scanPath(targetPath, cfg);
@@ -343,7 +344,10 @@ Review or remove the risky pattern(s).`;
343
344
  state.fileScanCount += result.filesScanned;
344
345
  state.findingCount += result.findings.length;
345
346
  if (!result.scanned) {
346
- return { ok: false, error: result.error, path: targetPath };
347
+ throw new ToolValidationError({
348
+ message: `cannot scan ${targetPath}: ${result.error ?? "unknown error"}`,
349
+ field: "path"
350
+ });
347
351
  }
348
352
  state.lastResult = {
349
353
  path: result.path,