@wrongstack/plugins 1.0.8 → 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
@@ -64,6 +64,9 @@ function estimateCost(model, freshTokens, completionTokens, cachedTokens = 0) {
64
64
  const outputCost = completionTokens / 1e6 * pricing.output;
65
65
  return inputCost + outputCost;
66
66
  }
67
+ function toFiniteNumber(value) {
68
+ return Number.isFinite(value) ? value : 0;
69
+ }
67
70
  var plugin = {
68
71
  name: "cost-tracker",
69
72
  version: "0.1.0",
@@ -137,11 +140,12 @@ var plugin = {
137
140
  const input = v["input"];
138
141
  const output = v["output"];
139
142
  if (typeof input !== "number" || typeof output !== "number") continue;
143
+ if (!Number.isFinite(input) || !Number.isFinite(output)) continue;
140
144
  const cacheRead = v["cacheRead"];
141
145
  pricingOverrides[model.toLowerCase()] = {
142
146
  input,
143
147
  output,
144
- ...typeof cacheRead === "number" ? { cacheRead } : {}
148
+ ...typeof cacheRead === "number" && Number.isFinite(cacheRead) ? { cacheRead } : {}
145
149
  };
146
150
  }
147
151
  }
@@ -154,11 +158,11 @@ var plugin = {
154
158
  if (!providerModels) continue;
155
159
  for (const [modelId, model] of Object.entries(providerModels)) {
156
160
  const cost = model?.cost;
157
- if (cost && typeof cost.input === "number" && typeof cost.output === "number") {
161
+ if (cost && typeof cost.input === "number" && typeof cost.output === "number" && Number.isFinite(cost.input) && Number.isFinite(cost.output)) {
158
162
  bundledFromRegistry[modelId.toLowerCase()] = {
159
163
  input: cost.input,
160
164
  output: cost.output,
161
- ...typeof cost.cache_read === "number" ? { cacheRead: cost.cache_read } : {}
165
+ ...typeof cost.cache_read === "number" && Number.isFinite(cost.cache_read) ? { cacheRead: cost.cache_read } : {}
162
166
  };
163
167
  hydrated += 1;
164
168
  }
@@ -186,14 +190,22 @@ var plugin = {
186
190
  const usage = payload.usage;
187
191
  const model = payload.ctx?.model ?? "unknown";
188
192
  const u = usage ?? {};
189
- const cachedTokens = Number(u["cacheRead"] ?? u["cache_read_input_tokens"] ?? u["cached_prompt_tokens"] ?? 0) || 0;
190
- const rawInput = Number(u["input"] ?? u["prompt_tokens"] ?? u["inputTokens"] ?? u["promptTokens"] ?? 0) || 0;
191
- const rawCacheWrite = Number(u["cacheWrite"] ?? u["cache_creation_input_tokens"] ?? 0) || 0;
193
+ const cachedTokens = toFiniteNumber(
194
+ Number(u["cacheRead"] ?? u["cache_read_input_tokens"] ?? u["cached_prompt_tokens"] ?? 0)
195
+ );
196
+ const rawInput = toFiniteNumber(
197
+ Number(u["input"] ?? u["prompt_tokens"] ?? u["inputTokens"] ?? u["promptTokens"] ?? 0)
198
+ );
199
+ const rawCacheWrite = toFiniteNumber(
200
+ Number(u["cacheWrite"] ?? u["cache_creation_input_tokens"] ?? 0)
201
+ );
192
202
  const freshTokens = rawInput + rawCacheWrite;
193
203
  const promptTokens = freshTokens + cachedTokens;
194
- const completionTokens = Number(
195
- u["output"] ?? u["completion_tokens"] ?? u["outputTokens"] ?? u["completionTokens"] ?? 0
196
- ) || 0;
204
+ const completionTokens = toFiniteNumber(
205
+ Number(
206
+ u["output"] ?? u["completion_tokens"] ?? u["outputTokens"] ?? u["completionTokens"] ?? 0
207
+ )
208
+ );
197
209
  const totalTokens = promptTokens + completionTokens;
198
210
  const costUsd = estimateCost(model, freshTokens, completionTokens, cachedTokens);
199
211
  const record = {
@@ -6,7 +6,7 @@
6
6
  * - cron_list: List all scheduled jobs
7
7
  * - cron_cancel: Cancel a scheduled job
8
8
  */
9
- import type { Plugin } from '@wrongstack/core/types';
9
+ import { type Plugin } from '@wrongstack/core/types';
10
10
  declare const plugin: Plugin;
11
11
  export default plugin;
12
12
  //# sourceMappingURL=index.d.ts.map
package/dist/cron.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/cron/index.ts
2
+ import { ToolValidationError } from "@wrongstack/core/types";
2
3
  var COORDINATION_CRON_CAPABILITY = "coordination.cron";
3
4
  var API_VERSION = "^0.1.10";
4
5
  var state = {
@@ -167,16 +168,28 @@ var plugin = {
167
168
  const action = input["action"] ?? input["task"] ?? input["command"] ?? input["run"];
168
169
  const enabled = input["enabled"] ?? true;
169
170
  if (!name || typeof name !== "string" || name.trim() === "") {
170
- return { ok: false, error: "name is required and must be a non-empty string" };
171
+ throw new ToolValidationError({
172
+ message: "name is required and must be a non-empty string",
173
+ field: "name"
174
+ });
171
175
  }
172
176
  if (Number.isNaN(intervalMs) || rawInterval === void 0 || rawInterval === null) {
173
- return { ok: false, error: "intervalMs must be a number >= 1000" };
177
+ throw new ToolValidationError({
178
+ message: "intervalMs must be a number >= 1000",
179
+ field: "intervalMs"
180
+ });
181
+ }
182
+ if (typeof action !== "string" || action.trim() === "") {
183
+ throw new ToolValidationError({
184
+ message: "action is required and must be a non-empty string",
185
+ field: "action"
186
+ });
174
187
  }
175
188
  if (state.jobs.has(name)) {
176
- return { ok: false, error: `Cron job '${name}' already exists. Use cron_cancel first.` };
189
+ throw new Error(`Cron job '${name}' already exists. Use cron_cancel first.`);
177
190
  }
178
191
  if (state.jobs.size >= maxConcurrent) {
179
- return { ok: false, error: `Maximum concurrent jobs (${maxConcurrent}) reached.` };
192
+ throw new Error(`Maximum concurrent jobs (${maxConcurrent}) reached.`);
180
193
  }
181
194
  const job = {
182
195
  name,
@@ -242,7 +255,7 @@ var plugin = {
242
255
  async execute(input) {
243
256
  const name = input["name"] ?? input["jobName"] ?? input["job_name"] ?? input["job"] ?? input["id"];
244
257
  if (!name || typeof name !== "string" || !state.jobs.has(name)) {
245
- return { ok: false, error: `No cron job named '${name}'` };
258
+ throw new Error(`No cron job named '${name}'`);
246
259
  }
247
260
  cancelJob(name);
248
261
  api.metrics.gauge("cron_active_jobs", state.jobs.size);
@@ -26,7 +26,7 @@
26
26
  *
27
27
  * @public
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
@@ -15,6 +15,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
15
15
  // src/dead-code-detector/index.ts
16
16
  import { readdir, readFile, stat } from "node:fs/promises";
17
17
  import { extname, join, 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 = {};
@@ -168,14 +169,8 @@ async function scan(root, depth, cfg) {
168
169
  }
169
170
  async function resolveScanRoot(rawPath) {
170
171
  const resolved = resolve(process.cwd(), rawPath);
171
- try {
172
- const stats = await stat(resolved);
173
- if (!stats.isDirectory()) {
174
- return resolve(resolved, "..");
175
- }
176
- } catch {
177
- }
178
- return resolved;
172
+ const stats = await stat(resolved);
173
+ return stats.isDirectory() ? resolved : resolve(resolved, "..");
179
174
  }
180
175
  function toPosix(p) {
181
176
  return p.replace(/\\/g, "/");
@@ -294,22 +289,29 @@ Consider removing the export if it is not part of the public API.`;
294
289
  category: "Diagnostics",
295
290
  mutating: false,
296
291
  async execute(input) {
297
- if (!cfg.enabled) return { ok: false, error: "dead-code-detector is disabled" };
292
+ if (!cfg.enabled) throw new Error("dead-code-detector is disabled");
298
293
  const raw = input ?? {};
299
294
  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["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? (typeof raw["file_path"] === "string" ? raw["file_path"] : void 0) ?? (typeof raw["file"] === "string" ? raw["file"] : void 0) ?? ".";
300
295
  const rawDepth = typeof input.depth === "number" ? input.depth : cfg.defaultDepth;
301
296
  const depth = Math.max(0, Math.min(Math.floor(rawDepth), cfg.maxDepth));
302
297
  if (!(0, runtime_exports.withinProject)(rawPath)) {
303
- return { ok: false, error: "scan path is outside the project root" };
298
+ throw new ToolValidationError({
299
+ message: "scan path is outside the project root",
300
+ field: "path"
301
+ });
304
302
  }
305
303
  state.scanCount += 1;
306
- const scanRoot = await resolveScanRoot(rawPath);
307
304
  let result;
305
+ let scanRoot;
308
306
  try {
307
+ scanRoot = await resolveScanRoot(rawPath);
309
308
  result = await scan(scanRoot, depth, cfg);
310
309
  } catch (err) {
311
310
  state.errorCount += 1;
312
- return { ok: false, error: String(err) };
311
+ throw new Error(
312
+ `dead_code_scan failed for ${rawPath}: ${err instanceof Error ? err.message : String(err)}`,
313
+ { cause: err }
314
+ );
313
315
  }
314
316
  return {
315
317
  ok: true,
@@ -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
  /**
30
30
  * Tunable budgets that bound the hook fingerprint index. Keeping the defaults
31
31
  * together makes the file, fingerprint, and byte limits explicit.
@@ -15,6 +15,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
15
15
  // src/duplicate-code-detector/index.ts
16
16
  import { readFile, realpath, stat } from "node:fs/promises";
17
17
  import { extname, isAbsolute, relative, resolve, sep } from "node:path";
18
+ import { ToolValidationError } from "@wrongstack/core/types";
18
19
 
19
20
  // src/runtime/index.ts
20
21
  var runtime_exports = {};
@@ -442,19 +443,26 @@ var plugin = {
442
443
  category: "Diagnostics",
443
444
  mutating: false,
444
445
  async execute(input) {
445
- if (!cfg.enabled) return { ok: false, error: "duplicate-code-detector is disabled" };
446
+ if (!cfg.enabled) throw new Error("duplicate-code-detector is disabled");
446
447
  const raw = input;
447
448
  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) ?? ".";
448
449
  if (!(0, runtime_exports.withinProject)(rawPath)) {
449
- return { ok: false, error: "scan path is outside the project root" };
450
+ throw new ToolValidationError({
451
+ message: "scan path is outside the project root",
452
+ field: "path"
453
+ });
450
454
  }
451
455
  state.scanCount += 1;
452
456
  let result;
453
457
  try {
458
+ await stat(resolve(process.cwd(), rawPath));
454
459
  result = await scanPath(rawPath, cfg);
455
460
  } catch (err) {
456
461
  state.errorCount += 1;
457
- return { ok: false, error: String(err) };
462
+ throw new Error(
463
+ `detect_duplicate_code failed for ${rawPath}: ${err instanceof Error ? err.message : String(err)}`,
464
+ { cause: err }
465
+ );
458
466
  }
459
467
  state.findingCount += result.findings.length;
460
468
  return {
@@ -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 FeatureFlagUsage {
28
28
  flag: 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/feature-flag-tracker/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 = {};
@@ -246,19 +247,26 @@ Make sure flag behavior is intentional and consider updating flag inventory/docs
246
247
  category: "Diagnostics",
247
248
  mutating: false,
248
249
  async execute(input) {
249
- if (!cfg.enabled) return { ok: false, error: "feature-flag-tracker is disabled" };
250
+ if (!cfg.enabled) throw new Error("feature-flag-tracker is disabled");
250
251
  const raw = input ?? {};
251
252
  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) ?? ".";
252
253
  if (!(0, runtime_exports.withinProject)(rawPath)) {
253
- return { ok: false, error: "path is outside the project root" };
254
+ throw new ToolValidationError({
255
+ message: "path is outside the project root",
256
+ field: "path"
257
+ });
254
258
  }
255
259
  state.scanCount += 1;
256
260
  let result;
257
261
  try {
262
+ await stat(resolve(process.cwd(), rawPath));
258
263
  result = await scanPath(rawPath, cfg);
259
264
  } catch (err) {
260
265
  state.errorCount += 1;
261
- return { ok: false, error: String(err) };
266
+ throw new Error(
267
+ `scan_feature_flags failed for ${rawPath}: ${err instanceof Error ? err.message : String(err)}`,
268
+ { cause: err }
269
+ );
262
270
  }
263
271
  state.flagCount += result.usages.length;
264
272
  return {
@@ -6,7 +6,7 @@
6
6
  * - watch_stop: Stop a watch by ID
7
7
  * - watch_list: List all active watches
8
8
  */
9
- import type { Plugin } from '@wrongstack/core/types';
9
+ import { type Plugin } from '@wrongstack/core/types';
10
10
  declare const plugin: Plugin;
11
11
  export default plugin;
12
12
  //# sourceMappingURL=index.d.ts.map
@@ -15,6 +15,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
15
15
  // src/file-watcher/index.ts
16
16
  import { watch as fsWatch } from "node:fs";
17
17
  import { join } from "node:path";
18
+ import { ToolValidationError } from "@wrongstack/core/types";
18
19
 
19
20
  // src/runtime/index.ts
20
21
  var runtime_exports = {};
@@ -209,11 +210,10 @@ var plugin = {
209
210
  let rawPaths;
210
211
  if (explicitPaths !== void 0) {
211
212
  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
- };
213
+ throw new ToolValidationError({
214
+ message: "paths must be an array of file/directory paths",
215
+ field: "paths"
216
+ });
217
217
  }
218
218
  rawPaths = explicitPaths;
219
219
  } else {
@@ -221,55 +221,46 @@ var plugin = {
221
221
  rawPaths = Array.isArray(fallback) ? fallback : typeof fallback === "string" && fallback.trim().length > 0 ? [fallback.trim()] : void 0;
222
222
  }
223
223
  if (!rawPaths || !Array.isArray(rawPaths)) {
224
- return {
225
- ok: false,
226
- error: "paths must be an array of file/directory paths",
227
- watch_id: null
228
- };
224
+ throw new ToolValidationError({
225
+ message: "paths must be an array of file/directory paths",
226
+ field: "paths"
227
+ });
229
228
  }
230
229
  const paths = [...new Set(rawPaths)];
231
230
  if (paths.length === 0) {
232
- return {
233
- ok: false,
234
- error: "paths array is empty \u2014 provide at least one path",
235
- watch_id: null
236
- };
231
+ throw new ToolValidationError({
232
+ message: "paths array is empty \u2014 provide at least one path",
233
+ field: "paths"
234
+ });
237
235
  }
238
236
  if (paths.length > MAX_PATHS_PER_WATCH) {
239
- return {
240
- ok: false,
241
- error: `a watch may contain at most ${MAX_PATHS_PER_WATCH} unique paths`,
242
- watch_id: null
243
- };
237
+ throw new ToolValidationError({
238
+ message: `a watch may contain at most ${MAX_PATHS_PER_WATCH} unique paths`,
239
+ field: "paths"
240
+ });
244
241
  }
245
242
  if (watches.size >= MAX_WATCH_GROUPS) {
246
- return {
247
- ok: false,
248
- error: `active watch group limit reached (${MAX_WATCH_GROUPS})`,
249
- watch_id: null
250
- };
243
+ throw new Error(
244
+ `active watch group limit reached (${MAX_WATCH_GROUPS}); stop a watch with watch_stop first`
245
+ );
251
246
  }
252
247
  const activeFilesystemWatchers = [...watches.values()].reduce(
253
248
  (total, handle2) => total + handle2.watchers.length,
254
249
  0
255
250
  );
256
251
  if (activeFilesystemWatchers + paths.length > MAX_FILESYSTEM_WATCHERS) {
257
- return {
258
- ok: false,
259
- error: `filesystem watcher limit reached (${MAX_FILESYSTEM_WATCHERS})`,
260
- watch_id: null
261
- };
252
+ throw new Error(
253
+ `filesystem watcher limit reached (${MAX_FILESYSTEM_WATCHERS}); stop a watch with watch_stop first`
254
+ );
262
255
  }
263
256
  const events = input["events"] ?? ["change", "add", "delete"];
264
257
  const recursive = input["recursive"] ?? true;
265
258
  const bad = paths.find((p) => !(0, runtime_exports.withinProject)(p));
266
259
  if (bad !== void 0) {
267
- return {
268
- ok: false,
269
- error: `path is outside the project root: ${bad}`,
270
- watch_id: null,
271
- rejectedOutsideProject: true
272
- };
260
+ throw new ToolValidationError({
261
+ message: `path is outside the project root: ${bad}`,
262
+ field: "paths"
263
+ });
273
264
  }
274
265
  const id = nextId();
275
266
  const handle = {
@@ -281,8 +272,15 @@ var plugin = {
281
272
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
282
273
  };
283
274
  const watchedPaths = [];
275
+ const failedPaths = [];
284
276
  for (const p of paths) {
285
277
  if (safeWatchDir(p, recursive, handle)) watchedPaths.push(p);
278
+ else failedPaths.push(p);
279
+ }
280
+ if (watchedPaths.length === 0) {
281
+ throw new Error(
282
+ `could not watch any of the requested paths: ${failedPaths.join(", ")} (missing path or OS watcher limit)`
283
+ );
286
284
  }
287
285
  handle.paths = watchedPaths;
288
286
  watches.set(id, handle);
@@ -291,9 +289,10 @@ var plugin = {
291
289
  ok: true,
292
290
  watch_id: id,
293
291
  paths: watchedPaths,
292
+ ...failedPaths.length > 0 ? { failedPaths } : {},
294
293
  events,
295
294
  recursive,
296
- message: `Started watching ${watchedPaths.length} path(s). Use watch_stop to cancel.`
295
+ message: `Started watching ${watchedPaths.length} path(s). Use watch_stop to cancel.` + (failedPaths.length > 0 ? ` Could not watch: ${failedPaths.join(", ")}.` : "")
297
296
  };
298
297
  }
299
298
  });
@@ -315,7 +314,7 @@ var plugin = {
315
314
  const watch_id = typeof rawId === "string" ? rawId.trim() : "";
316
315
  const handle = watches.get(watch_id);
317
316
  if (!handle) {
318
- return { ok: false, error: `No active watch with ID: ${watch_id}` };
317
+ throw new Error(`No active watch with ID: ${watch_id}`);
319
318
  }
320
319
  for (const w of handle.watchers) {
321
320
  try {
@@ -24,7 +24,7 @@
24
24
  * - For staging: use `git_autocommit` with `files` or `paths` (it stages automatically), or `bash` with `git add`.
25
25
  * - For status: use the built-in `git` tool with `command: "status"` or `command: "diff"`.
26
26
  */
27
- import type { Plugin } from '@wrongstack/core/types';
27
+ import { type Plugin } from '@wrongstack/core/types';
28
28
  /**
29
29
  * Parse one `git status --porcelain` line into the path to stage.
30
30
  *
@@ -2,6 +2,7 @@
2
2
  import { execFile } from "node:child_process";
3
3
  import { existsSync } from "node:fs";
4
4
  import { resolve } from "node:path";
5
+ import { ToolValidationError } from "@wrongstack/core/types";
5
6
  var API_VERSION = "^0.1.10";
6
7
  var commitCount = { value: 0 };
7
8
  var lastCommit = { hash: null, at: null };
@@ -382,7 +383,10 @@ var plugin = {
382
383
  const rawFiles = input["files"] ?? input["fileList"] ?? input["file_list"];
383
384
  if (rawFiles !== void 0) {
384
385
  if (!Array.isArray(rawFiles)) {
385
- return { ok: false, error: "files must be an array of file paths" };
386
+ throw new ToolValidationError({
387
+ message: "files must be an array of file paths",
388
+ field: "files"
389
+ });
386
390
  }
387
391
  files = rawFiles;
388
392
  } else if (typeof (input["file"] ?? input["file_path"]) === "string" && String(input["file"] ?? input["file_path"]).trim().length > 0) {
@@ -396,20 +400,26 @@ var plugin = {
396
400
  const rawPaths = input["paths"] ?? input["pathList"] ?? input["path_list"];
397
401
  if (rawPaths !== void 0) {
398
402
  if (!Array.isArray(rawPaths)) {
399
- return { ok: false, error: "paths must be an array of pathspec patterns" };
403
+ throw new ToolValidationError({
404
+ message: "paths must be an array of pathspec patterns",
405
+ field: "paths"
406
+ });
400
407
  }
401
408
  pathspecs = rawPaths.filter((p) => typeof p === "string" && p.length > 0);
402
409
  if (pathspecs.length === 0) {
403
- return { ok: false, error: "paths must contain at least one non-empty pattern" };
410
+ throw new ToolValidationError({
411
+ message: "paths must contain at least one non-empty pattern",
412
+ field: "paths"
413
+ });
404
414
  }
405
415
  } else if (typeof input["path"] === "string" && input["path"].trim().length > 0) {
406
416
  pathspecs = [input["path"].trim()];
407
417
  }
408
418
  if (rawPaths !== void 0 && files && files.length > 0) {
409
- return {
410
- ok: false,
411
- error: "Pass either files (exact paths) or paths (pathspec globs), not both \u2014 the other would be silently ignored."
412
- };
419
+ throw new ToolValidationError({
420
+ message: "Pass either files (exact paths) or paths (pathspec globs), not both \u2014 the other would be silently ignored.",
421
+ field: "paths"
422
+ });
413
423
  }
414
424
  let commitScope;
415
425
  let staged = [];
@@ -417,10 +427,10 @@ var plugin = {
417
427
  try {
418
428
  await stageFiles(pathspecs);
419
429
  } catch (err) {
420
- return {
421
- ok: false,
422
- error: `Failed to stage files matching paths: ${err instanceof Error ? err.message : String(err)}`
423
- };
430
+ throw new Error(
431
+ `Failed to stage files matching paths: ${err instanceof Error ? err.message : String(err)}`,
432
+ { cause: err }
433
+ );
424
434
  }
425
435
  try {
426
436
  staged = await getScopedStagedFiles(pathspecs);
@@ -428,10 +438,9 @@ var plugin = {
428
438
  staged = [];
429
439
  }
430
440
  if (staged.length === 0) {
431
- return {
432
- ok: false,
433
- error: "No changed files match the given paths \u2014 refusing to commit anything else."
434
- };
441
+ throw new Error(
442
+ "No changed files match the given paths \u2014 refusing to commit anything else."
443
+ );
435
444
  }
436
445
  commitScope = staged;
437
446
  try {
@@ -443,10 +452,10 @@ var plugin = {
443
452
  try {
444
453
  commitScope = await stageFiles(files);
445
454
  } catch (err) {
446
- return {
447
- ok: false,
448
- error: `Failed to stage files: ${err instanceof Error ? err.message : String(err)}`
449
- };
455
+ throw new Error(
456
+ `Failed to stage files: ${err instanceof Error ? err.message : String(err)}`,
457
+ { cause: err }
458
+ );
450
459
  }
451
460
  try {
452
461
  staged = await getStagedFiles();
@@ -511,17 +520,16 @@ var plugin = {
511
520
  message: `Would create: ${summary || "update code"}`
512
521
  };
513
522
  }
514
- return {
515
- ok: false,
516
- error: "type is required and must be a valid conventional commit type"
517
- };
523
+ throw new ToolValidationError({
524
+ message: "type is required and must be a valid conventional commit type",
525
+ field: "type"
526
+ });
518
527
  }
519
528
  const msg = generateCommitMessage(type, scope, summary || "update code", body);
520
529
  if (staged.length === 0) {
521
- return {
522
- ok: false,
523
- error: 'Nothing staged. Pass files (exact paths) or paths (pathspec globs) to scope this commit, stage with git add beforehand, or set extensions["git-autocommit"].autoStage=true to allow staging every changed file (legacy whole-tree behavior).'
524
- };
530
+ throw new Error(
531
+ 'Nothing staged. Pass files (exact paths) or paths (pathspec globs) to scope this commit, stage with git add beforehand, or set extensions["git-autocommit"].autoStage=true to allow staging every changed file (legacy whole-tree behavior).'
532
+ );
525
533
  }
526
534
  let scopeWarning = null;
527
535
  if (commitScope) {
@@ -563,20 +571,19 @@ ${stagedDiff}
563
571
  if (drifted.length > 0) {
564
572
  const preview = drifted.slice(0, 10).join(", ");
565
573
  const suffix = drifted.length > 10 ? ` and ${drifted.length - 10} more` : "";
566
- return {
567
- ok: false,
568
- error: `Working tree changed after staging for: ${preview}${suffix}. A scoped commit takes working-tree content, so committing now could include changes that were never staged or previewed. Re-run the tool to re-stage the current content.`
569
- };
574
+ throw new Error(
575
+ `Working tree changed after staging for: ${preview}${suffix}. A scoped commit takes working-tree content, so committing now could include changes that were never staged or previewed. Re-run the tool to re-stage the current content.`
576
+ );
570
577
  }
571
578
  }
572
579
  let hash = "";
573
580
  try {
574
581
  hash = await commitWithMessage(msg, void 0, commitScope);
575
582
  } catch (err) {
576
- return {
577
- ok: false,
578
- error: `Failed to commit: ${err instanceof Error ? err.message : String(err)}`
579
- };
583
+ throw new Error(
584
+ `Failed to commit: ${err instanceof Error ? err.message : String(err)}`,
585
+ { cause: err }
586
+ );
580
587
  }
581
588
  api.log.info("git-autocommit: created commit", { hash, type, scope });
582
589
  commitCount.value += 1;
@@ -615,10 +622,8 @@ ${stagedDiff}
615
622
  \`\`\``
616
623
  };
617
624
  } catch (err) {
618
- return {
619
- ok: false,
620
- error: `Uncaught error in git_autocommit: ${err instanceof Error ? err.message : String(err)}`
621
- };
625
+ if (err instanceof Error) throw err;
626
+ throw new Error(`Uncaught error in git_autocommit: ${String(err)}`, { cause: err });
622
627
  }
623
628
  }
624
629
  });
@@ -52,7 +52,7 @@
52
52
  *
53
53
  * @public
54
54
  */
55
- import type { Plugin } from '@wrongstack/core/types';
55
+ import { type Plugin } from '@wrongstack/core/types';
56
56
  /**
57
57
  * Curated default list of build-artifact-looking names. High precision on
58
58
  * purpose: this hook fires on every write/edit, so the defaults avoid