@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
@@ -24,7 +24,7 @@
24
24
  *
25
25
  * @public
26
26
  */
27
- import type { Plugin } from '@wrongstack/core/types';
27
+ import { type Plugin } from '@wrongstack/core/types';
28
28
  export type A11yRule = 'missing-alt' | 'missing-input-label' | 'low-contrast-placeholder' | 'missing-button-text' | 'duplicate-id';
29
29
  export interface A11yFinding {
30
30
  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/accessibility-auditor/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 = {};
@@ -53,6 +54,13 @@ function readConfig(raw) {
53
54
  onWriteEdit: (r["onWriteEdit"] ?? r["on_write_edit"] ?? r["onSave"]) !== false
54
55
  };
55
56
  }
57
+ async function assertPathExists(rawPath) {
58
+ try {
59
+ await stat(resolve(process.cwd(), rawPath));
60
+ } catch (err) {
61
+ throw new Error(`path not found: ${rawPath}`, { cause: err });
62
+ }
63
+ }
56
64
  function normalizeExtensions(exts) {
57
65
  return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
58
66
  }
@@ -381,12 +389,16 @@ var plugin = {
381
389
  category: "Diagnostics",
382
390
  mutating: false,
383
391
  async execute(input) {
384
- if (!cfg.enabled) return { ok: false, error: "accessibility-auditor is disabled" };
392
+ if (!cfg.enabled) throw new Error("accessibility-auditor is disabled");
385
393
  const raw = input;
386
394
  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["file_path"] === "string" ? raw["file_path"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? (typeof raw["file"] === "string" ? raw["file"] : void 0) ?? ".";
387
395
  if (!(0, runtime_exports.withinProject)(rawPath)) {
388
- return { ok: false, error: "path must be inside the project" };
396
+ throw new ToolValidationError({
397
+ message: "path must be inside the project",
398
+ field: "path"
399
+ });
389
400
  }
401
+ await assertPathExists(rawPath);
390
402
  state.auditCount += 1;
391
403
  const result = await auditPath(rawPath, cfg);
392
404
  state.fileCount += result.fileCount;
@@ -238,8 +238,8 @@ var plugin = {
238
238
  category: "Coordination",
239
239
  mutating: false,
240
240
  async execute(input) {
241
- if (!cfg.enabled) return { ok: false, error: "agent-handoff is disabled" };
242
- if (!mailbox) return { ok: false, error: "mailbox not available" };
241
+ if (!cfg.enabled) throw new Error("agent-handoff is disabled");
242
+ if (!mailbox) throw new Error("mailbox not available");
243
243
  const raw = input;
244
244
  const summary = (typeof raw["summary"] === "string" ? raw["summary"] : void 0) ?? (typeof raw["note"] === "string" ? raw["note"] : void 0) ?? (typeof raw["message"] === "string" ? raw["message"] : void 0) ?? (typeof raw["content"] === "string" ? raw["content"] : void 0) ?? (typeof raw["text"] === "string" ? raw["text"] : void 0) ?? (typeof raw["body"] === "string" ? raw["body"] : void 0);
245
245
  const task = (typeof raw["task"] === "string" ? raw["task"] : void 0) ?? (typeof raw["title"] === "string" ? raw["title"] : void 0) ?? (typeof raw["subject"] === "string" ? raw["subject"] : void 0);
@@ -266,10 +266,10 @@ var plugin = {
266
266
  return { ok: true, messageId: result.id, to: recipient };
267
267
  } catch (err) {
268
268
  state.errorCount += 1;
269
- return {
270
- ok: false,
271
- error: err instanceof Error ? err.message : String(err)
272
- };
269
+ throw new Error(
270
+ `handoff_note: mailbox send to "${recipient}" failed: ${err instanceof Error ? err.message : String(err)}`,
271
+ { cause: err }
272
+ );
273
273
  }
274
274
  }
275
275
  });
@@ -14,7 +14,7 @@
14
14
  * the session default. Best-effort: any LLM failure (or a bad response)
15
15
  * falls back to the template for that entity — a doc comment always lands.
16
16
  */
17
- import type { Plugin } from '@wrongstack/core/types';
17
+ import { type Plugin } from '@wrongstack/core/types';
18
18
  declare const plugin: Plugin;
19
19
  export default plugin;
20
20
  //# sourceMappingURL=index.d.ts.map
package/dist/auto-doc.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/auto-doc/index.ts
2
2
  import { isAbsolute, relative, resolve } from "node:path";
3
+ import { ToolValidationError } from "@wrongstack/core/types";
3
4
  var AUTO_DOC_API_VERSION = "^0.1.10";
4
5
  function resolveProjectPath(rawPath, cwd = process.cwd()) {
5
6
  if (typeof rawPath !== "string" || rawPath.length === 0) return null;
@@ -163,41 +164,37 @@ function injectDocComment(content, entity, doc) {
163
164
  async function runAutoDoc(input, api) {
164
165
  const rawInput = input;
165
166
  if (rawInput["files"] !== void 0 && !Array.isArray(rawInput["files"])) {
166
- return {
167
- ok: false,
168
- error: "input.files must be an array of file paths",
169
- filesProcessed: 0,
170
- changes: []
171
- };
167
+ throw new ToolValidationError({
168
+ message: "input.files must be an array of file paths",
169
+ field: "files"
170
+ });
172
171
  }
173
172
  const rawFiles = rawInput["files"] ?? rawInput["file"] ?? rawInput["path"] ?? rawInput["filePath"] ?? rawInput["TargetFile"] ?? rawInput["targetFile"];
174
173
  const files = Array.isArray(rawFiles) ? rawFiles.filter((f) => typeof f === "string" && f.trim().length > 0).map((f) => f.trim()) : typeof rawFiles === "string" && rawFiles.trim().length > 0 ? [rawFiles.trim()] : void 0;
175
174
  if (!files || !Array.isArray(files)) {
176
- return {
177
- ok: false,
178
- error: "input.files must be an array of file paths",
179
- filesProcessed: 0,
180
- changes: []
181
- };
175
+ throw new ToolValidationError({
176
+ message: "input.files must be an array of file paths",
177
+ field: "files"
178
+ });
182
179
  }
183
180
  if (files.length === 0) {
184
- return {
185
- ok: false,
186
- error: "input.files is empty \u2014 provide at least one file path",
187
- filesProcessed: 0,
188
- changes: []
189
- };
181
+ throw new ToolValidationError({
182
+ message: "input.files is empty \u2014 provide at least one file path",
183
+ field: "files"
184
+ });
190
185
  }
191
186
  const extConfig = api.config.extensions?.["auto-doc"] ?? {};
192
187
  const includeTypes = extConfig["includeTypes"] ?? false;
193
188
  const useLlm = (input.use_llm ?? rawInput["useLlm"] ?? extConfig["useLlm"] ?? false) === true && Boolean(api.llm);
194
189
  const maxLlmEntities = typeof extConfig["maxLlmEntities"] === "number" && extConfig["maxLlmEntities"] >= 0 ? extConfig["maxLlmEntities"] : 25;
195
190
  const results = [];
191
+ const skipped = [];
196
192
  let llmBudget = maxLlmEntities;
197
193
  for (const rawFile of files) {
198
194
  const safeFile = resolveProjectPath(rawFile);
199
195
  if (!safeFile) {
200
196
  api.log.warn(`auto-doc: skipped file outside project directory: ${rawFile}`);
197
+ skipped.push({ file: rawFile, reason: "outside project directory" });
201
198
  continue;
202
199
  }
203
200
  try {
@@ -205,8 +202,12 @@ async function runAutoDoc(input, api) {
205
202
  let content;
206
203
  try {
207
204
  content = readFileSync(safeFile, "utf-8");
208
- } catch {
205
+ } catch (err) {
209
206
  api.log.warn(`auto-doc: could not read file ${safeFile}`);
207
+ skipped.push({
208
+ file: safeFile,
209
+ reason: `could not read: ${err instanceof Error ? err.message : String(err)}`
210
+ });
210
211
  continue;
211
212
  }
212
213
  const entities = parseSource(content);
@@ -246,11 +247,21 @@ async function runAutoDoc(input, api) {
246
247
  }
247
248
  } catch (err) {
248
249
  api.log.error(`auto-doc: error processing ${safeFile}: ${err}`);
250
+ skipped.push({
251
+ file: safeFile,
252
+ reason: err instanceof Error ? err.message : String(err)
253
+ });
249
254
  }
250
255
  }
256
+ if (skipped.length === files.length) {
257
+ throw new Error(
258
+ `auto_doc processed no files: ${skipped.map((s) => `${s.file} (${s.reason})`).join("; ")}`
259
+ );
260
+ }
251
261
  return {
252
262
  ok: true,
253
- filesProcessed: files.length,
263
+ filesProcessed: files.length - skipped.length,
264
+ ...skipped.length > 0 ? { skipped } : {},
254
265
  changes: results,
255
266
  llm: useLlm ? { docs: state.llmDocs, fallbacks: state.llmFallbacks } : void 0
256
267
  };
@@ -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
  declare const plugin: Plugin;
33
33
  export default plugin;
34
34
  //# sourceMappingURL=index.d.ts.map
@@ -14,6 +14,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
14
14
 
15
15
  // src/auto-i18n-extractor/index.ts
16
16
  import { readFileSync } from "node:fs";
17
+ import { ToolValidationError } from "@wrongstack/core/types";
17
18
 
18
19
  // src/runtime/index.ts
19
20
  var runtime_exports = {};
@@ -244,28 +245,31 @@ ${lines.join("\n")}${more}`;
244
245
  category: "Diagnostics",
245
246
  mutating: false,
246
247
  async execute(input) {
247
- if (!cfg.enabled) return { ok: false, error: "auto-i18n-extractor is disabled" };
248
+ if (!cfg.enabled) throw new Error("auto-i18n-extractor is disabled");
248
249
  const raw = input;
249
250
  const rawPath = (typeof input.path === "string" && input.path.trim().length > 0 ? input.path.trim() : void 0) ?? (typeof raw["TargetFile"] === "string" && raw["TargetFile"].trim().length > 0 ? raw["TargetFile"].trim() : void 0) ?? (typeof raw["targetFile"] === "string" && raw["targetFile"].trim().length > 0 ? raw["targetFile"].trim() : void 0) ?? (typeof raw["filePath"] === "string" && raw["filePath"].trim().length > 0 ? raw["filePath"].trim() : void 0) ?? (typeof raw["file_path"] === "string" && raw["file_path"].trim().length > 0 ? raw["file_path"].trim() : void 0) ?? (typeof raw["file"] === "string" && raw["file"].trim().length > 0 ? raw["file"].trim() : void 0);
250
251
  const filePath = typeof rawPath === "string" ? rawPath.trim() : "";
251
252
  if (!filePath) {
252
- return { ok: false, error: "path is required" };
253
+ throw new ToolValidationError({ message: "path is required", field: "path" });
253
254
  }
254
255
  if (!(0, runtime_exports.withinProject)(filePath)) {
255
- return { ok: false, error: "path must be inside the project" };
256
+ throw new ToolValidationError({
257
+ message: "path must be inside the project",
258
+ field: "path"
259
+ });
256
260
  }
257
261
  const ext = fileExtension(filePath);
258
262
  if (!cfg.fileExtensions.includes(ext)) {
259
- return {
260
- ok: false,
261
- error: `unsupported extension "${ext}"; allowed: ${cfg.fileExtensions.join(", ")}`
262
- };
263
+ throw new ToolValidationError({
264
+ message: `unsupported extension "${ext}"; allowed: ${cfg.fileExtensions.join(", ")}`,
265
+ field: "path"
266
+ });
263
267
  }
264
268
  state.filesScanned += 1;
265
269
  const content = readSourceFile(filePath);
266
270
  if (content === null) {
267
271
  state.readErrors += 1;
268
- return { ok: false, error: `could not read ${filePath}` };
272
+ throw new Error(`could not read ${filePath}`);
269
273
  }
270
274
  const extracted = extractStrings(content, cfg);
271
275
  state.stringsFound += extracted.length;
@@ -114,13 +114,13 @@ async function detectUncommittedChanges(cwd, signal) {
114
114
  }
115
115
  function detectGitCommand(command) {
116
116
  const cmd = command.trim();
117
- if (/\bgit\s+commit\b/.test(cmd)) {
117
+ if (/\bgit\s+commit(?![a-zA-Z0-9_-])/.test(cmd)) {
118
118
  return { type: "commit", snippet: cmd.slice(0, 120) };
119
119
  }
120
- if (/\bgit\s+push\b/.test(cmd)) {
120
+ if (/\bgit\s+push(?![a-zA-Z0-9_-])/.test(cmd)) {
121
121
  return { type: "push", snippet: cmd.slice(0, 120) };
122
122
  }
123
- if (/\bgit\s+merge\s/.test(cmd)) {
123
+ if (/\bgit\s+merge(?![a-zA-Z0-9_-])/.test(cmd)) {
124
124
  return { type: "merge", snippet: cmd.slice(0, 120) };
125
125
  }
126
126
  return null;
@@ -43,7 +43,7 @@
43
43
  *
44
44
  * @public
45
45
  */
46
- import type { Plugin } from '@wrongstack/core/types';
46
+ import { type Plugin } from '@wrongstack/core/types';
47
47
  type Section = 'Added' | 'Changed' | 'Fixed' | 'Removed' | 'Security' | 'Documentation' | 'Maintenance';
48
48
  export interface ChangelogEntry {
49
49
  section: Section;
@@ -15,6 +15,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
15
15
  // src/changelog-writer/index.ts
16
16
  import { readFileSync, writeFileSync } from "node:fs";
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 = {};
@@ -318,11 +319,13 @@ var plugin = {
318
319
  category: "Docs",
319
320
  mutating: false,
320
321
  async execute(input) {
321
- if (!cfg.enabled) return { ok: false, error: "changelog-writer is disabled" };
322
+ if (!cfg.enabled) throw new Error("changelog-writer is disabled");
322
323
  const raw = input ?? {};
323
324
  const rawText = input.text ?? raw["message"] ?? raw["entry"] ?? raw["content"] ?? raw["description"] ?? raw["desc"] ?? raw["summary"] ?? raw["body"] ?? raw["note"] ?? raw["item"];
324
325
  const text = String(rawText ?? "").trim();
325
- if (!text) return { ok: false, error: "entry text must not be empty" };
326
+ if (!text) {
327
+ throw new ToolValidationError({ message: "entry text must not be empty", field: "text" });
328
+ }
326
329
  const section = SECTION_ORDER.includes(input.section) ? input.section : "Changed";
327
330
  addEntry({ section, text, origin: "manual", when: (/* @__PURE__ */ new Date()).toISOString() });
328
331
  return { ok: true, pendingEntries: state.entries.length };
@@ -377,12 +380,12 @@ var plugin = {
377
380
  category: "Docs",
378
381
  mutating: true,
379
382
  async execute(input) {
380
- if (!cfg.enabled) return { ok: false, error: "changelog-writer is disabled" };
383
+ if (!cfg.enabled) throw new Error("changelog-writer is disabled");
381
384
  if (!cfg.filePath) {
382
- return { ok: false, error: "filePath must stay within the current project directory" };
385
+ throw new Error("filePath must stay within the current project directory");
383
386
  }
384
387
  if (state.entries.length === 0) {
385
- return { ok: false, error: "no pending entries \u2014 add some with changelog_add first" };
388
+ throw new Error("no pending entries \u2014 add some with changelog_add first");
386
389
  }
387
390
  const rawInput = input ?? {};
388
391
  const shouldPolish = input.polish === true || rawInput["use_llm"] === true || rawInput["useLlm"] === true || rawInput["ai"] === true || rawInput["use_ai"] === true;
@@ -390,16 +393,22 @@ var plugin = {
390
393
  let existing = null;
391
394
  try {
392
395
  existing = readFileSync(cfg.filePath, "utf-8");
393
- } catch {
396
+ } catch (err) {
397
+ if (err.code !== "ENOENT") {
398
+ throw new Error(
399
+ `failed to read ${cfg.filePath}: ${err instanceof Error ? err.message : String(err)}`,
400
+ { cause: err }
401
+ );
402
+ }
394
403
  existing = null;
395
404
  }
396
405
  try {
397
406
  writeFileSync(cfg.filePath, mergeIntoChangelog(existing, block));
398
407
  } catch (err) {
399
- return {
400
- ok: false,
401
- error: `failed to write ${cfg.filePath}: ${err instanceof Error ? err.message : String(err)}`
402
- };
408
+ throw new Error(
409
+ `failed to write ${cfg.filePath}: ${err instanceof Error ? err.message : String(err)}`,
410
+ { cause: err }
411
+ );
403
412
  }
404
413
  const written = state.entries.length;
405
414
  state.entries = [];
@@ -35,7 +35,7 @@
35
35
  *
36
36
  * @public
37
37
  */
38
- import type { Plugin } from '@wrongstack/core/types';
38
+ import { type Plugin } from '@wrongstack/core/types';
39
39
  export interface Snapshot {
40
40
  id: string;
41
41
  createdAt: string;
@@ -1,6 +1,7 @@
1
1
  // src/checkpoint/index.ts
2
2
  import { mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, relative, resolve } from "node:path";
4
+ import { ToolValidationError } from "@wrongstack/core/types";
4
5
  var state = {
5
6
  snapshots: [],
6
7
  nextId: 1,
@@ -83,7 +84,13 @@ async function captureFile(path, maxBytes) {
83
84
  if (st.size > maxBytes) return "too-large";
84
85
  const content = await readFile(path, "utf-8");
85
86
  return { path, content, bytes: st.size };
86
- } catch {
87
+ } catch (err) {
88
+ if (err.code !== "ENOENT") {
89
+ throw new Error(
90
+ `could not snapshot ${path}: ${err instanceof Error ? err.message : String(err)}`,
91
+ { cause: err }
92
+ );
93
+ }
87
94
  return { path, content: null, bytes: 0 };
88
95
  }
89
96
  }
@@ -225,7 +232,7 @@ var plugin = {
225
232
  category: "Safety",
226
233
  mutating: false,
227
234
  async execute(input) {
228
- if (!cfg.enabled) return { ok: false, error: "checkpoint is disabled" };
235
+ if (!cfg.enabled) throw new Error("checkpoint is disabled");
229
236
  let paths = [];
230
237
  const rawInput = input;
231
238
  const raw = rawInput["paths"] ?? rawInput["path"] ?? rawInput["files"] ?? rawInput["file"] ?? rawInput["filePath"] ?? rawInput["file_path"] ?? rawInput["TargetFile"] ?? rawInput["targetFile"];
@@ -234,16 +241,25 @@ var plugin = {
234
241
  } else if (Array.isArray(raw)) {
235
242
  paths = raw.filter((p) => typeof p === "string" && p.trim().length > 0);
236
243
  }
237
- if (paths.length === 0) return { ok: false, error: "paths must not be empty" };
238
- const files = [];
244
+ if (paths.length === 0) {
245
+ throw new ToolValidationError({ message: "paths must not be empty", field: "paths" });
246
+ }
239
247
  const rejectedOutsideProject = [];
240
- let skipped = 0;
248
+ const safePaths = [];
241
249
  for (const p of paths) {
242
250
  const safePath = await resolveProjectPath(p);
243
- if (!safePath) {
244
- rejectedOutsideProject.push(p);
245
- continue;
246
- }
251
+ if (safePath) safePaths.push(safePath);
252
+ else rejectedOutsideProject.push(p);
253
+ }
254
+ if (rejectedOutsideProject.length > 0) {
255
+ throw new ToolValidationError({
256
+ message: `paths must stay within the current project directory: ${rejectedOutsideProject.join(", ")}`,
257
+ field: "paths"
258
+ });
259
+ }
260
+ const files = [];
261
+ let skipped = 0;
262
+ for (const safePath of safePaths) {
247
263
  const captured = await captureFile(safePath, cfg.maxFileBytes);
248
264
  if (captured === "too-large") {
249
265
  skipped += 1;
@@ -252,15 +268,10 @@ var plugin = {
252
268
  }
253
269
  files.push(captured);
254
270
  }
255
- if (rejectedOutsideProject.length > 0) {
256
- return {
257
- ok: false,
258
- error: "paths must stay within the current project directory",
259
- rejectedOutsideProject
260
- };
261
- }
262
271
  if (files.length === 0) {
263
- return { ok: false, error: "all files were skipped (too large)" };
272
+ throw new Error(
273
+ `all files were skipped (larger than maxFileBytes=${cfg.maxFileBytes}); nothing was snapshotted`
274
+ );
264
275
  }
265
276
  const snapshot = {
266
277
  id: `cp-${state.nextId++}`,
@@ -335,21 +346,18 @@ var plugin = {
335
346
  category: "Safety",
336
347
  mutating: true,
337
348
  async execute(input) {
338
- if (!cfg.enabled) return { ok: false, error: "checkpoint is disabled" };
349
+ if (!cfg.enabled) throw new Error("checkpoint is disabled");
339
350
  const raw = input ?? {};
340
351
  const rawId = (typeof input.id === "string" && input.id.trim().length > 0 ? input.id.trim() : void 0) ?? (typeof raw["snapshotId"] === "string" ? raw["snapshotId"] : void 0) ?? (typeof raw["snapshot_id"] === "string" ? raw["snapshot_id"] : void 0);
341
352
  const rawPath = (typeof input.path === "string" && input.path.trim().length > 0 ? input.path.trim() : 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);
342
353
  const snapshot = rawId ? state.snapshots.find((s) => s.id === rawId) : state.snapshots[state.snapshots.length - 1];
343
354
  if (!snapshot) {
344
- return {
345
- ok: false,
346
- error: rawId ? `no snapshot with id "${rawId}"` : "no snapshots captured yet"
347
- };
355
+ throw new Error(rawId ? `no snapshot with id "${rawId}"` : "no snapshots captured yet");
348
356
  }
349
357
  const targetPath = rawPath ? await resolveProjectPath(rawPath) ?? rawPath : null;
350
358
  const targets = targetPath ? snapshot.files.filter((f) => f.path === targetPath || f.path === rawPath) : snapshot.files;
351
359
  if (targets.length === 0) {
352
- return { ok: false, error: `snapshot ${snapshot.id} has no entry for "${rawPath}"` };
360
+ throw new Error(`snapshot ${snapshot.id} has no entry for "${rawPath}"`);
353
361
  }
354
362
  const restored = [];
355
363
  const createdByTool = [];
@@ -371,8 +379,14 @@ var plugin = {
371
379
  state.restores += 1;
372
380
  api.metrics.counter("restores");
373
381
  }
382
+ if (errors.length > 0) {
383
+ const failed = errors.map((e) => `${e.path} (${e.error})`).join("; ");
384
+ throw new Error(
385
+ `checkpoint_restore ${snapshot.id}: ${errors.length} file(s) failed to restore: ${failed}` + (restored.length > 0 ? `. Restored: ${restored.join(", ")}` : "")
386
+ );
387
+ }
374
388
  return {
375
- ok: errors.length === 0,
389
+ ok: true,
376
390
  snapshotId: snapshot.id,
377
391
  restored,
378
392
  notRestoredFileDidNotExist: createdByTool,
@@ -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
  /**
28
28
  * Heuristic cyclomatic-complexity formula (per file, not per function):
29
29
  * control keywords (if | else if | for | while | switch | catch)
@@ -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/code-metrics/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 = {};
@@ -257,19 +258,26 @@ var plugin = {
257
258
  category: "Diagnostics",
258
259
  mutating: false,
259
260
  async execute(input) {
260
- if (!cfg.enabled) return { ok: false, error: "code-metrics is disabled" };
261
+ if (!cfg.enabled) throw new Error("code-metrics is disabled");
261
262
  const raw = input ?? {};
262
263
  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) ?? ".";
263
264
  if (!(0, runtime_exports.withinProject)(rawPath)) {
264
- return { ok: false, error: "path is outside the project root" };
265
+ throw new ToolValidationError({
266
+ message: "path is outside the project root",
267
+ field: "path"
268
+ });
265
269
  }
266
270
  state.measureCount += 1;
267
271
  let result;
268
272
  try {
273
+ await stat(resolve(process.cwd(), rawPath));
269
274
  result = await measurePath(rawPath, cfg);
270
275
  } catch (err) {
271
276
  state.errorCount += 1;
272
- return { ok: false, error: String(err) };
277
+ throw new Error(
278
+ `measure_code_metrics failed for ${rawPath}: ${err instanceof Error ? err.message : String(err)}`,
279
+ { cause: err }
280
+ );
273
281
  }
274
282
  state.fileCount += result.files.length;
275
283
  return {
@@ -144,11 +144,11 @@ function parseCommitMessage(message, cfg) {
144
144
  }
145
145
  var GIT_MESSAGE_FLAG_RE = new RegExp(
146
146
  [
147
- // -m "…" | -m '…' | -m=… ; --message "…" | --message='…' | --message=…
148
- String.raw`(?:^|\s)(?:-m|--message)(?:\s+|=)"([^"]*)"`,
149
- String.raw`(?:^|\s)(?:-m|--message)(?:\s+|=)'([^']*)'`,
147
+ // -m "…" | -am "…" | -m '…' | -m=… ; --message "…" | --message='…' | --message=…
148
+ String.raw`(?:^|\s)(?:-[a-zA-Z]*m|--message)(?:\s+|=)"([^"]*)"`,
149
+ String.raw`(?:^|\s)(?:-[a-zA-Z]*m|--message)(?:\s+|=)'([^']*)'`,
150
150
  // Bare value: stops at whitespace or a shell separator.
151
- String.raw`(?:^|\s)(?:-m|--message)(?:\s+|=)([^\s;&|"']+)`
151
+ String.raw`(?:^|\s)(?:-[a-zA-Z]*m|--message)(?:\s+|=)([^\s;&|"']+)`
152
152
  ].join("|"),
153
153
  "g"
154
154
  );
@@ -258,7 +258,7 @@ var plugin = {
258
258
  } else if (toolName === "bash") {
259
259
  const command = inp["command"] ?? inp["CommandLine"] ?? inp["cmd"] ?? inp["script"] ?? inp["input"];
260
260
  if (typeof command !== "string") return;
261
- if (!/\bgit\s+commit\b/.test(command)) return;
261
+ if (!/\bgit\s+commit(?![a-zA-Z0-9_-])/.test(command)) return;
262
262
  message = extractMessageFromBash(command);
263
263
  if (!message) return;
264
264
  } else {
@@ -31,7 +31,7 @@
31
31
  *
32
32
  * @public
33
33
  */
34
- import type { Plugin } from '@wrongstack/core/types';
34
+ import { type Plugin } from '@wrongstack/core/types';
35
35
  export interface Pin {
36
36
  id: string;
37
37
  label: string | null;
@@ -1,6 +1,7 @@
1
1
  // src/context-pins/index.ts
2
2
  import * as fs 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 state = {
6
7
  pins: [],
@@ -141,16 +142,17 @@ var plugin = {
141
142
  category: "Memory",
142
143
  mutating: true,
143
144
  async execute(input) {
144
- if (!cfg.enabled) return { ok: false, error: "context-pins is disabled" };
145
+ if (!cfg.enabled) throw new Error("context-pins is disabled");
145
146
  const raw = input ?? {};
146
147
  const rawText = input.text ?? raw["pin"] ?? raw["content"] ?? raw["message"] ?? raw["note"] ?? raw["fact"] ?? raw["data"];
147
148
  const text = String(rawText ?? "").trim();
148
- if (!text) return { ok: false, error: "pin text must not be empty" };
149
+ if (!text) {
150
+ throw new ToolValidationError({ message: "pin text must not be empty", field: "text" });
151
+ }
149
152
  if (state.pins.length >= cfg.maxPins) {
150
- return {
151
- ok: false,
152
- error: `pin limit reached (${cfg.maxPins}). Remove a pin first with pin_remove.`
153
- };
153
+ throw new Error(
154
+ `pin limit reached (${cfg.maxPins}). Remove a pin first with pin_remove.`
155
+ );
154
156
  }
155
157
  const pin = {
156
158
  id: `pin-${state.nextId++}`,
@@ -179,16 +181,17 @@ var plugin = {
179
181
  category: "Memory",
180
182
  mutating: true,
181
183
  async execute(input) {
182
- if (!cfg.enabled) return { ok: false, error: "context-pins is disabled" };
184
+ if (!cfg.enabled) throw new Error("context-pins is disabled");
183
185
  const raw = input ?? {};
184
186
  const key = String(
185
187
  input.id ?? input.label ?? raw["pinId"] ?? raw["pin_id"] ?? raw["name"] ?? raw["key"] ?? ""
186
188
  ).trim();
187
- if (!key) return { ok: false, error: "id or label is required" };
189
+ if (!key)
190
+ throw new ToolValidationError({ message: "id or label is required", field: "id" });
188
191
  const before = state.pins.length;
189
192
  state.pins = state.pins.filter((p) => p.id !== key && p.label !== key);
190
193
  const removed = before - state.pins.length;
191
- if (removed === 0) return { ok: false, error: `no pin matches "${key}"` };
194
+ if (removed === 0) throw new Error(`no pin matches "${key}"`);
192
195
  state.removals += removed;
193
196
  api.metrics.counter("removals", removed);
194
197
  const persisted = await persistPins(cfg.filePath);