@wrongstack/plugins 1.0.8 → 1.0.11

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 (73) 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 +1553 -1086
  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/loop-breaker.js +11 -4
  39. package/dist/migration-planner/index.d.ts +1 -1
  40. package/dist/migration-planner.js +6 -2
  41. package/dist/notify-hub/index.d.ts +1 -1
  42. package/dist/notify-hub.js +19 -12
  43. package/dist/performance-regression-gate/index.d.ts +1 -1
  44. package/dist/performance-regression-gate.js +33 -13
  45. package/dist/pr-drafter/index.d.ts +10 -1
  46. package/dist/pr-drafter.js +57 -26
  47. package/dist/refactor-suggester/index.d.ts +1 -1
  48. package/dist/refactor-suggester.js +17 -4
  49. package/dist/release-notes-generator.js +2 -2
  50. package/dist/secret-scanner/index.d.ts +1 -1
  51. package/dist/secret-scanner.js +11 -3
  52. package/dist/security-hotspot-scanner/index.d.ts +1 -1
  53. package/dist/security-hotspot-scanner.js +6 -2
  54. package/dist/semantic-search-indexer/index.d.ts +1 -1
  55. package/dist/semantic-search-indexer.js +19 -3
  56. package/dist/semver-bump/index.d.ts +1 -1
  57. package/dist/semver-bump.js +57 -37
  58. package/dist/session-recap.js +4 -2
  59. package/dist/shell-check/index.d.ts +1 -1
  60. package/dist/shell-check.js +30 -39
  61. package/dist/smart-rename/index.d.ts +1 -1
  62. package/dist/smart-rename.js +23 -10
  63. package/dist/template-engine/index.d.ts +1 -1
  64. package/dist/template-engine.js +51 -38
  65. package/dist/test-flake-detector/index.d.ts +1 -1
  66. package/dist/test-flake-detector.js +11 -5
  67. package/dist/test-generator/index.d.ts +1 -1
  68. package/dist/test-generator.js +12 -8
  69. package/dist/todo-tracker/index.d.ts +68 -1
  70. package/dist/todo-tracker.js +579 -395
  71. package/dist/token-budget.js +7 -4
  72. package/dist/token-throttle.js +6 -3
  73. package/package.json +7 -7
@@ -15,6 +15,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
15
15
  // src/template-engine/index.ts
16
16
  import { readFile, writeFile } from "node:fs/promises";
17
17
  import { isAbsolute } from "node:path";
18
+ import { ToolValidationError } from "@wrongstack/core/types";
18
19
 
19
20
  // src/runtime/index.ts
20
21
  var runtime_exports = {};
@@ -183,24 +184,26 @@ var plugin = {
183
184
  const output_path = typeof rawOutputPath === "string" && rawOutputPath.trim().length > 0 ? rawOutputPath.trim() : void 0;
184
185
  const raw = input["raw"] ?? false;
185
186
  if (!template || typeof template !== "string") {
186
- return { ok: false, error: "template is required and must be a string" };
187
+ throw new ToolValidationError({
188
+ message: "template is required and must be a string",
189
+ field: "template"
190
+ });
187
191
  }
188
192
  if (!variables || typeof variables !== "object") {
189
- return { ok: false, error: "variables is required and must be an object" };
190
- }
191
- let result;
192
- try {
193
- result = raw ? renderTemplateRaw(template, variables) : renderTemplate(template, variables, autoEscapeHtml);
194
- } catch (err) {
195
- return { ok: false, error: String(err) };
193
+ throw new ToolValidationError({
194
+ message: "variables is required and must be an object",
195
+ field: "variables"
196
+ });
196
197
  }
198
+ const result = raw ? renderTemplateRaw(template, variables) : renderTemplate(template, variables, autoEscapeHtml);
197
199
  if (output_path) {
198
200
  const pathError = validateWritableTemplateTarget("output_path", output_path);
199
- if (pathError) return { ok: false, error: pathError };
201
+ if (pathError)
202
+ throw new ToolValidationError({ message: pathError, field: "output_path" });
200
203
  try {
201
204
  await writeFile(output_path, result, "utf-8");
202
205
  } catch (err) {
203
- return { ok: false, error: `Could not write ${output_path}: ${String(err)}` };
206
+ throw new Error(`Could not write ${output_path}: ${String(err)}`, { cause: err });
204
207
  }
205
208
  return {
206
209
  ok: true,
@@ -251,32 +254,36 @@ var plugin = {
251
254
  const output_path = typeof rawOutputPath === "string" && rawOutputPath.trim().length > 0 ? rawOutputPath.trim() : void 0;
252
255
  const raw = input["raw"] ?? false;
253
256
  if (!template_path || typeof template_path !== "string") {
254
- return { ok: false, error: "template_path is required and must be a string" };
257
+ throw new ToolValidationError({
258
+ message: "template_path is required and must be a string",
259
+ field: "template_path"
260
+ });
255
261
  }
256
262
  const templatePathError = validateRelativeTemplatePath("template_path", template_path);
257
- if (templatePathError) return { ok: false, error: templatePathError };
263
+ if (templatePathError) {
264
+ throw new ToolValidationError({ message: templatePathError, field: "template_path" });
265
+ }
258
266
  if (!variables || typeof variables !== "object") {
259
- return { ok: false, error: "variables is required and must be an object" };
267
+ throw new ToolValidationError({
268
+ message: "variables is required and must be an object",
269
+ field: "variables"
270
+ });
260
271
  }
261
272
  let content;
262
273
  try {
263
274
  content = await readFile(template_path, "utf-8");
264
275
  } catch (err) {
265
- return { ok: false, error: `Could not read template file: ${err}` };
266
- }
267
- let result;
268
- try {
269
- result = raw ? renderTemplateRaw(content, variables) : renderTemplate(content, variables, autoEscapeHtml);
270
- } catch (err) {
271
- return { ok: false, error: `Template rendering failed: ${err}` };
276
+ throw new Error(`Could not read template file: ${String(err)}`, { cause: err });
272
277
  }
278
+ const result = raw ? renderTemplateRaw(content, variables) : renderTemplate(content, variables, autoEscapeHtml);
273
279
  if (output_path) {
274
280
  const pathError = validateWritableTemplateTarget("output_path", output_path);
275
- if (pathError) return { ok: false, error: pathError };
281
+ if (pathError)
282
+ throw new ToolValidationError({ message: pathError, field: "output_path" });
276
283
  try {
277
284
  await writeFile(output_path, result, "utf-8");
278
285
  } catch (err) {
279
- return { ok: false, error: `Could not write ${output_path}: ${String(err)}` };
286
+ throw new Error(`Could not write ${output_path}: ${String(err)}`, { cause: err });
280
287
  }
281
288
  return {
282
289
  ok: true,
@@ -327,30 +334,39 @@ var plugin = {
327
334
  const rawDesc = input["description"] ?? input["desc"] ?? input["summary"];
328
335
  const description = typeof rawDesc === "string" ? rawDesc : void 0;
329
336
  if (!name || typeof name !== "string" || name.trim() === "") {
330
- return { ok: false, error: "name is required and must be a non-empty string" };
337
+ throw new ToolValidationError({
338
+ message: "name is required and must be a non-empty string",
339
+ field: "name"
340
+ });
331
341
  }
332
342
  if (!content || typeof content !== "string") {
333
- return { ok: false, error: "content is required and must be a string" };
343
+ throw new ToolValidationError({
344
+ message: "content is required and must be a string",
345
+ field: "content"
346
+ });
334
347
  }
335
348
  if (name.length > MAX_TEMPLATE_NAME_CHARS) {
336
- return { ok: false, error: `name exceeds ${MAX_TEMPLATE_NAME_CHARS} characters` };
349
+ throw new ToolValidationError({
350
+ message: `name exceeds ${MAX_TEMPLATE_NAME_CHARS} characters`,
351
+ field: "name"
352
+ });
337
353
  }
338
354
  if (content.length > MAX_TEMPLATE_CONTENT_CHARS) {
339
- return {
340
- ok: false,
341
- error: `content exceeds ${MAX_TEMPLATE_CONTENT_CHARS} characters`
342
- };
355
+ throw new ToolValidationError({
356
+ message: `content exceeds ${MAX_TEMPLATE_CONTENT_CHARS} characters`,
357
+ field: "content"
358
+ });
343
359
  }
344
360
  if (description && description.length > MAX_TEMPLATE_DESCRIPTION_CHARS) {
345
- return {
346
- ok: false,
347
- error: `description exceeds ${MAX_TEMPLATE_DESCRIPTION_CHARS} characters`
348
- };
361
+ throw new ToolValidationError({
362
+ message: `description exceeds ${MAX_TEMPLATE_DESCRIPTION_CHARS} characters`,
363
+ field: "description"
364
+ });
349
365
  }
350
366
  const now = (/* @__PURE__ */ new Date()).toISOString();
351
367
  const existing = templates.get(name);
352
368
  if (!existing && templates.size >= MAX_TEMPLATES) {
353
- return { ok: false, error: `template limit reached (${MAX_TEMPLATES})` };
369
+ throw new Error(`template limit reached (${MAX_TEMPLATES})`);
354
370
  }
355
371
  const tmpl = {
356
372
  name,
@@ -363,10 +379,7 @@ var plugin = {
363
379
  for (const stored of templates.values()) retainedChars += templateChars(stored);
364
380
  const nextChars = retainedChars - (existing ? templateChars(existing) : 0) + templateChars(tmpl);
365
381
  if (nextChars > MAX_TOTAL_TEMPLATE_CHARS) {
366
- return {
367
- ok: false,
368
- error: `template store exceeds ${MAX_TOTAL_TEMPLATE_CHARS} retained characters`
369
- };
382
+ throw new Error(`template store exceeds ${MAX_TOTAL_TEMPLATE_CHARS} retained characters`);
370
383
  }
371
384
  templates.set(name, tmpl);
372
385
  api.metrics.gauge("template_count", templates.size);
@@ -19,7 +19,7 @@
19
19
  *
20
20
  * @public
21
21
  */
22
- import type { Plugin } from '@wrongstack/core/types';
22
+ import { type Plugin } from '@wrongstack/core/types';
23
23
  declare const plugin: Plugin;
24
24
  export default plugin;
25
25
  //# sourceMappingURL=index.d.ts.map
@@ -17,6 +17,7 @@ import { execFile } from "node:child_process";
17
17
  import { readFileSync } from "node:fs";
18
18
  import { createRequire } from "node:module";
19
19
  import { dirname, isAbsolute, relative, resolve } from "node:path";
20
+ import { ToolValidationError } from "@wrongstack/core/types";
20
21
 
21
22
  // src/runtime/index.ts
22
23
  var runtime_exports = {};
@@ -274,7 +275,7 @@ var plugin = {
274
275
  mutating: false,
275
276
  async execute(input) {
276
277
  if (!cfg.enabled) {
277
- return { ok: false, error: "test-flake-detector is disabled" };
278
+ throw new Error("test-flake-detector is disabled");
278
279
  }
279
280
  const raw = input;
280
281
  const rawPattern = input.testPattern ?? raw["pattern"] ?? raw["path"] ?? raw["file"] ?? raw["filePath"] ?? raw["TargetFile"] ?? raw["targetFile"];
@@ -285,10 +286,10 @@ var plugin = {
285
286
  const requestedRuns = typeof rawRuns === "number" && rawRuns >= 1 ? Math.min(Math.floor(rawRuns), cfg.maxRuns) : 5;
286
287
  const command = resolveTestCommand(commandString, testPattern);
287
288
  if (!command) {
288
- return {
289
- ok: false,
290
- error: "Unsupported test command or unsafe testPattern. Use vitest, jest, or mocha through a supported package runner, and keep patterns inside the project."
291
- };
289
+ throw new ToolValidationError({
290
+ message: "Unsupported test command or unsafe testPattern. Use vitest, jest, or mocha through a supported package runner, and keep patterns inside the project.",
291
+ field: "command"
292
+ });
292
293
  }
293
294
  state.invocationCount += 1;
294
295
  const start = Date.now();
@@ -316,6 +317,11 @@ var plugin = {
316
317
  }
317
318
  }
318
319
  const all = Array.from(records.values());
320
+ if (all.length === 0 && runErrors.length === requestedRuns) {
321
+ throw new Error(
322
+ `test command produced no test results in ${requestedRuns} run(s): ${runErrors.slice(0, 3).join("; ")}`
323
+ );
324
+ }
319
325
  const flakyTests = all.filter((r) => r.passCount > 0 && r.failCount > 0);
320
326
  const alwaysFailing = all.filter((r) => r.passCount === 0 && r.failCount > 0);
321
327
  const alwaysPassing = all.filter((r) => r.passCount > 0 && r.failCount === 0);
@@ -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
  interface TestGeneratorConfig {
27
27
  enabled: boolean;
28
28
  framework: TestFramework;
@@ -1,6 +1,7 @@
1
1
  // src/test-generator/index.ts
2
2
  import { readFileSync } from "node:fs";
3
3
  import { isAbsolute, relative, resolve } from "node:path";
4
+ import { ToolValidationError } from "@wrongstack/core/types";
4
5
 
5
6
  // src/runtime/llm.ts
6
7
  import {
@@ -299,7 +300,7 @@ var plugin = {
299
300
  category: "Development",
300
301
  mutating: false,
301
302
  async execute(input, _ctx, execOpts) {
302
- if (!cfg.enabled) return { ok: false, error: "test-generator is disabled" };
303
+ if (!cfg.enabled) throw new Error("test-generator is disabled");
303
304
  execOpts?.signal?.throwIfAborted();
304
305
  const inp = input ?? {};
305
306
  const rawFramework = inp["framework"];
@@ -310,16 +311,19 @@ var plugin = {
310
311
  };
311
312
  const rawPath = inp["path"] ?? inp["filePath"] ?? inp["file_path"] ?? inp["TargetFile"] ?? inp["targetFile"] ?? inp["file"];
312
313
  if (!rawPath || typeof rawPath !== "string") {
313
- return { ok: false, error: "path is required" };
314
+ throw new ToolValidationError({ message: "path is required", field: "path" });
314
315
  }
315
316
  if (!withinProject(rawPath)) {
316
- return { ok: false, error: "path is outside the project root" };
317
+ throw new ToolValidationError({
318
+ message: "path is outside the project root",
319
+ field: "path"
320
+ });
317
321
  }
318
322
  if (!SOURCE_EXTENSIONS.some((ext) => rawPath.toLowerCase().endsWith(ext))) {
319
- return {
320
- ok: false,
321
- error: `test generation only reads source files (${SOURCE_EXTENSIONS.join(", ")}); refusing "${rawPath}"`
322
- };
323
+ throw new ToolValidationError({
324
+ message: `test generation only reads source files (${SOURCE_EXTENSIONS.join(", ")}); refusing "${rawPath}"`,
325
+ field: "path"
326
+ });
323
327
  }
324
328
  const resolved = resolve(process.cwd(), rawPath);
325
329
  state.generateCount += 1;
@@ -328,7 +332,7 @@ var plugin = {
328
332
  result = generateForFile(resolved, effectiveCfg);
329
333
  } catch (err) {
330
334
  state.errorCount += 1;
331
- return { ok: false, error: String(err) };
335
+ throw new Error(`Could not read ${rawPath}: ${String(err)}`, { cause: err });
332
336
  }
333
337
  state.exportCount += result.exports.length;
334
338
  const raw = input ?? {};
@@ -26,8 +26,75 @@
26
26
  * into the session's ctx.todos via the
27
27
  * built-in `todo` tool
28
28
  * - todo_tracker_status : Counters + last update timestamp
29
+ *
30
+ * Durability contract (the file is the source of truth, memory is a cache):
31
+ * - A file that cannot be parsed or validated is QUARANTINED (renamed to
32
+ * `<file>.corrupt-<timestamp>`), never silently overwritten. If the
33
+ * rename fails the store goes read-only.
34
+ * - A file written by a newer format version is never written to.
35
+ * - Every mutation runs under the shared file lock, re-reads the file,
36
+ * applies the change to a clone, writes atomically, and only then
37
+ * updates memory. Two sessions on one project therefore merge instead of
38
+ * erasing each other, and a failed write leaves memory and disk unchanged.
39
+ * - Read tools re-read the file so another process's changes are visible.
40
+ */
41
+ import { type Plugin } from '@wrongstack/core/types';
42
+ type Status = 'pending' | 'in_progress' | 'completed' | 'dropped';
43
+ type Priority = 'low' | 'normal' | 'high';
44
+ interface TrackedItem {
45
+ id: string;
46
+ content: string;
47
+ status: Status;
48
+ priority: Priority;
49
+ tags: string[];
50
+ createdAt: string;
51
+ updatedAt: string;
52
+ completedAt: string | null;
53
+ /** Session that first created the item (if known). */
54
+ sourceSessionId: string | null;
55
+ /** Optional free-form note attached to the item. */
56
+ notes: string | null;
57
+ }
58
+ interface TodoTrackerFile {
59
+ version: 1;
60
+ projectSlug: string;
61
+ updatedAt: string;
62
+ items: TrackedItem[];
63
+ }
64
+ /**
65
+ * Filesystem/persistence seam. Production uses the real primitives; tests
66
+ * inject faults here instead of module-mocking `@wrongstack/core/utils`
67
+ * (which is flaky under parallel vitest — see context-pins-extra.test.ts).
29
68
  */
30
- import type { Plugin } from '@wrongstack/core/types';
69
+ export interface TodoTrackerDeps {
70
+ readFile(path: string): Promise<string>;
71
+ rename(from: string, to: string): Promise<void>;
72
+ atomicWrite(path: string, content: string, opts?: {
73
+ mode?: number;
74
+ }): Promise<void>;
75
+ withFileLock<T>(path: string, fn: () => Promise<T>): Promise<T>;
76
+ ensureDir(dir: string): Promise<void>;
77
+ }
78
+ /** Basename without its extension; dotfiles like `.todos` keep their name. */
79
+ export declare function deriveProjectSlug(filePath: string): string;
80
+ type LoadResult = {
81
+ kind: 'missing';
82
+ } | {
83
+ kind: 'ok';
84
+ file: TodoTrackerFile;
85
+ } | {
86
+ kind: 'corrupt';
87
+ reason: string;
88
+ } | {
89
+ kind: 'unsupportedVersion';
90
+ version: unknown;
91
+ } | {
92
+ kind: 'invalidItems';
93
+ reason: string;
94
+ };
95
+ /** Pure classification of raw file bytes. Exported for tests. */
96
+ export declare function parseTrackerFile(rawText: string): LoadResult;
97
+ export declare function createTodoTrackerPlugin(overrides?: Partial<TodoTrackerDeps>): Plugin;
31
98
  declare const plugin: Plugin;
32
99
  export default plugin;
33
100
  //# sourceMappingURL=index.d.ts.map