@wrongstack/plugins 0.313.0 → 0.316.1

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 (48) hide show
  1. package/dist/accessibility-auditor.js +3 -2
  2. package/dist/agent-handoff.js +13 -3
  3. package/dist/auto-doc.js +5 -2
  4. package/dist/auto-i18n-extractor.js +2 -1
  5. package/dist/branch-guard.js +1 -1
  6. package/dist/changelog-writer.js +16 -4
  7. package/dist/checkpoint.js +2 -1
  8. package/dist/commit-validator.js +2 -2
  9. package/dist/config-validator.js +26 -1
  10. package/dist/cost-tracker.js +2 -1
  11. package/dist/cron.js +1 -1
  12. package/dist/dependency-vulnerability-gate.js +12 -0
  13. package/dist/diff-summary.js +1 -1
  14. package/dist/doc-sync-guard.js +8 -1
  15. package/dist/duplicate-code-detector.js +4 -3
  16. package/dist/error-lens.js +3 -3
  17. package/dist/git-autocommit.js +5 -2
  18. package/dist/gitignore-guard.js +6 -2
  19. package/dist/import-organizer.js +10 -4
  20. package/dist/index.js +304 -186
  21. package/dist/interface-contract-guard.js +12 -3
  22. package/dist/knowledge-graph.js +5 -1
  23. package/dist/llm-cache.js +3 -2
  24. package/dist/loop-breaker.js +1 -1
  25. package/dist/migration-planner.js +0 -2
  26. package/dist/model-router.js +2 -0
  27. package/dist/notify-hub.js +5 -1
  28. package/dist/path-guard.js +2 -1
  29. package/dist/performance-regression-gate.js +2 -0
  30. package/dist/plugin-stack-observer.js +1 -0
  31. package/dist/pr-drafter.js +2 -2
  32. package/dist/process-guard.js +1 -2
  33. package/dist/prompt-firewall.js +3 -2
  34. package/dist/refactor-suggester.js +11 -0
  35. package/dist/security-hotspot-scanner.js +5 -5
  36. package/dist/semver-bump.js +3 -1
  37. package/dist/smart-rename.js +2 -2
  38. package/dist/spec-linker.js +1 -1
  39. package/dist/template-engine.js +4 -4
  40. package/dist/test-coverage-gate.js +5 -4
  41. package/dist/test-flake-detector.js +22 -8
  42. package/dist/test-generator.js +2 -2
  43. package/dist/todo-listener.js +3 -2
  44. package/dist/todo-tracker.js +2 -1
  45. package/package.json +5 -5
  46. package/dist/runtime/bounded-map.d.ts +0 -3
  47. package/dist/runtime/local-bin.d.ts +0 -3
  48. package/dist/runtime/safe-json.d.ts +0 -3
@@ -72,10 +72,19 @@ function extractInterfaceNames(content) {
72
72
  }
73
73
  return names;
74
74
  }
75
- var IMPLEMENTER_RE = /(?:implements|satisfies|\bas)\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
75
+ var IMPLEMENTS_EXTENDS_RE = /(?:\bimplements\b|\bextends\b)\s+([^{;=]+)/g;
76
+ var SATISFIES_AS_RE = /(?:\bsatisfies\b|\bas\b)\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
77
+ var IDENTIFIER_RE = /[A-Za-z_$][A-Za-z0-9_$]*/g;
76
78
  function collectImplementedNames(content, into) {
77
- IMPLEMENTER_RE.lastIndex = 0;
78
- for (const m of content.matchAll(IMPLEMENTER_RE)) {
79
+ for (const m of content.matchAll(IMPLEMENTS_EXTENDS_RE)) {
80
+ const clause = m[1];
81
+ if (clause) {
82
+ for (const id of clause.matchAll(IDENTIFIER_RE)) {
83
+ if (id[0]) into.add(id[0]);
84
+ }
85
+ }
86
+ }
87
+ for (const m of content.matchAll(SATISFIES_AS_RE)) {
79
88
  const name = m[1];
80
89
  if (name) into.add(name);
81
90
  }
@@ -47,7 +47,11 @@ function loadFacts(filePath) {
47
47
  const facts = Array.isArray(raw.facts) ? raw.facts.filter(
48
48
  (f) => !!f && typeof f === "object" && typeof f.id === "string" && typeof f.subject === "string" && typeof f.relation === "string" && typeof f.object === "string"
49
49
  ) : [];
50
- const nextId = typeof raw.nextId === "number" && raw.nextId >= 1 ? raw.nextId : facts.length + 1;
50
+ const maxExistingId = facts.reduce((max, f) => {
51
+ const n = parseInt(f.id.replace(/^\D+/, ""), 10);
52
+ return Number.isFinite(n) && n > max ? n : max;
53
+ }, 0);
54
+ const nextId = typeof raw.nextId === "number" && raw.nextId >= 1 ? raw.nextId : maxExistingId + 1;
51
55
  return { facts, nextId };
52
56
  } catch {
53
57
  return { facts: [], nextId: 1 };
package/dist/llm-cache.js CHANGED
@@ -30,7 +30,7 @@ function readConfig(raw) {
30
30
  }
31
31
  function isDeterministic(request) {
32
32
  const t = request["temperature"];
33
- return t === void 0 || t === 0;
33
+ return t === void 0 || t === null || t === 0;
34
34
  }
35
35
  var fingerprintCache = /* @__PURE__ */ new WeakMap();
36
36
  function fingerprintRequest(request) {
@@ -172,7 +172,8 @@ var plugin = {
172
172
  state.misses += 1;
173
173
  api.metrics.counter("misses");
174
174
  const response = await inner(_ctx, request);
175
- if (response && typeof response === "object" && response.stopReason === "end_turn") {
175
+ const sr = response && typeof response === "object" ? response.stopReason : void 0;
176
+ if (response && typeof response === "object" && (sr === "end_turn" || sr === "stop" || sr === "tool_use")) {
176
177
  lruSet(key, response, cfg.maxEntries);
177
178
  }
178
179
  return response;
@@ -112,7 +112,7 @@ function hashString(value) {
112
112
  }
113
113
  async function gitDiffFingerprint(cwd, targetPath, signal) {
114
114
  const pathspec = isAbsolute(targetPath) ? relative(cwd, targetPath) : targetPath;
115
- if (!pathspec || pathspec === ".." || pathspec.startsWith("../") || pathspec.startsWith("..\\")) {
115
+ if (!pathspec || isAbsolute(pathspec) || pathspec === ".." || pathspec.startsWith("../") || pathspec.startsWith("..\\")) {
116
116
  return null;
117
117
  }
118
118
  try {
@@ -113,7 +113,6 @@ function extractBreakingChanges(sectionText) {
113
113
  for (const rawLine of sectionText.split(/\r?\n/)) {
114
114
  const line = rawLine.trim();
115
115
  if (!line) {
116
- inBreakingSection = false;
117
116
  continue;
118
117
  }
119
118
  if (/^#{3,4}\s+(?:BREAKING\s+CHANGES?|Breaking\s+Changes?|Breaking)/i.test(line)) {
@@ -139,7 +138,6 @@ function extractRecommendedSteps(sectionText) {
139
138
  for (const rawLine of sectionText.split(/\r?\n/)) {
140
139
  const line = rawLine.trim();
141
140
  if (!line) {
142
- inMigrationSection = false;
143
141
  continue;
144
142
  }
145
143
  if (/^#{3,4}\s+(?:Migration|Upgrade|How to|Steps|Recommended)/i.test(line)) {
@@ -39,6 +39,8 @@ function requestCharSize(request) {
39
39
  const o = v;
40
40
  if (typeof o["text"] === "string") size += o["text"].length;
41
41
  if (o["content"] !== void 0) walk(o["content"]);
42
+ if (o["input"] !== void 0) walk(o["input"]);
43
+ if (o["arguments"] !== void 0) walk(o["arguments"]);
42
44
  }
43
45
  };
44
46
  walk(request["system"]);
@@ -56,7 +56,7 @@ var WebhookNotificationChannel = class {
56
56
  // -----------------------------------------------------------------------
57
57
  async deliver(msg) {
58
58
  const deliveredAt = (/* @__PURE__ */ new Date()).toISOString();
59
- const inCooldown = this.#resetMs > 0 && Date.now() - this.#openedAt < this.#resetMs;
59
+ const inCooldown = this.#resetMs === 0 || Date.now() - this.#openedAt < this.#resetMs;
60
60
  if (this.#circuit.open && this.#maxFailures > 0 && inCooldown) {
61
61
  this.#totalSuppressed += 1;
62
62
  return {
@@ -86,6 +86,10 @@ var WebhookNotificationChannel = class {
86
86
  body,
87
87
  signal: controller.signal
88
88
  });
89
+ if (typeof res.arrayBuffer === "function") {
90
+ await res.arrayBuffer().catch(() => {
91
+ });
92
+ }
89
93
  if (!res.ok) throw new Error(`webhook responded ${res.status}`);
90
94
  } finally {
91
95
  clearTimeout(timer);
@@ -89,6 +89,7 @@ function normalizePath(p) {
89
89
  }
90
90
  const joined = segments.join("/");
91
91
  if (drive) return `${drive}/${joined}`.replace(/\/$/, "");
92
+ if (slashNormalized.startsWith("//")) return `//${joined}`.replace(/\/$/, "") || "//";
92
93
  if (slashNormalized.startsWith("/")) return `/${joined}`.replace(/\/$/, "") || "/";
93
94
  return joined;
94
95
  }
@@ -1007,7 +1008,7 @@ function destructiveTargetsAtDepth(command, depth) {
1007
1008
  targets.push(...roots.length > 0 ? roots : ["."]);
1008
1009
  f = findDelete.exec(normalizedCommand);
1009
1010
  }
1010
- const shellWrapper = /\b(?:ba|z|k)?sh\s+-c\s+(['"])(.*?)\1/gi;
1011
+ const shellWrapper = /\b(?:(?:ba|z|k)?sh|pwsh|powershell)\s+(?:-c|-Command)\s+(['"])(.*?)\1/gi;
1011
1012
  let w = shellWrapper.exec(normalizedCommand);
1012
1013
  while (w !== null) {
1013
1014
  if (w[2] && !tokenIsQuoted(w, w[0].split(/\s/)[0] ?? "")) {
@@ -108,6 +108,8 @@ function pairCrossFile(baseline, current) {
108
108
  const byKey = /* @__PURE__ */ new Map();
109
109
  for (const bench of baseline) {
110
110
  byKey.set(bench.key, bench);
111
+ const base = stripVariantSuffix(bench.name).base;
112
+ byKey.set(`${bench.group} > ${base}`, bench);
111
113
  }
112
114
  const pairs = [];
113
115
  for (const bench of current) {
@@ -55,6 +55,7 @@ var PLUGIN = {
55
55
  if (typeof p.plugin !== "string" || p.plugin.length === 0) return;
56
56
  const wraps = Array.isArray(p.wraps) ? p.wraps.filter((w) => typeof w === "string") : [];
57
57
  const kind = typeof p.kind === "string" ? p.kind : "unknown";
58
+ state.wraps = state.wraps.filter((w) => w.plugin !== p.plugin);
58
59
  state.wraps.push({
59
60
  plugin: p.plugin,
60
61
  kind,
@@ -278,10 +278,10 @@ var plugin = {
278
278
  category: "Workflow",
279
279
  mutating: true,
280
280
  capabilities: ["fs.write"],
281
- async execute(input) {
281
+ async execute(input = {}) {
282
282
  if (!cfg.enabled) return { ok: false, error: "pr-drafter is disabled" };
283
283
  const draft = await buildDraft(cfg, api.llm);
284
- if (input.preview) {
284
+ if (input?.preview) {
285
285
  return { ok: true, preview: true, title: draft.title, body: draft.body };
286
286
  }
287
287
  const resolved = resolveProjectPath(cfg.outputPath);
@@ -63,8 +63,7 @@ var plugin = {
63
63
  const ti = input.toolInput ?? {};
64
64
  const command = typeof ti["command"] === "string" ? ti["command"] : "";
65
65
  if (!command) return;
66
- const cmdLower = command.toLowerCase();
67
- const isKillRelated = cmdLower.includes("kill") || cmdLower.includes("taskkill") || cmdLower.includes("stop-process") || cmdLower.includes("tskill") || cmdLower.includes("pkill") || cmdLower.includes("killall") || cmdLower.includes("wmic");
66
+ const isKillRelated = /\b(?:kill|taskkill|stop-process|tskill|pkill|killall|wmic)\b/i.test(command);
68
67
  if (!isKillRelated) return;
69
68
  state.detections += 1;
70
69
  state.lastDetection = {
@@ -74,7 +74,7 @@ function growMatch(re, p, text, absStart, deadline) {
74
74
  const ext = text.slice(absStart, extEnd);
75
75
  re.lastIndex = 0;
76
76
  const em = re.exec(ext);
77
- if (!em || em.index !== 0 || em[0].length === 0) return null;
77
+ if (em?.index !== 0 || em[0].length === 0) return null;
78
78
  const end = absStart + em[0].length;
79
79
  if (end < extEnd || extEnd === text.length) {
80
80
  return { start: absStart, end, matched: em[0] };
@@ -84,7 +84,8 @@ function growMatch(re, p, text, absStart, deadline) {
84
84
  return null;
85
85
  }
86
86
  function* execWindowed(p, text, deadline) {
87
- const re = new RegExp(p.re.source, p.re.flags);
87
+ const flags = p.re.flags.includes("g") ? p.re.flags : `${p.re.flags}g`;
88
+ const re = new RegExp(p.re.source, flags);
88
89
  let acceptLo = 0;
89
90
  let highWater = 0;
90
91
  for (let window = 0; acceptLo < text.length; window++) {
@@ -77,10 +77,21 @@ function detectSmells(filePath, content, rules) {
77
77
  const suggestions = [];
78
78
  const lines = content.split(/\r?\n/);
79
79
  const stripped = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, " ");
80
+ const CONTROL_KEYWORDS = /* @__PURE__ */ new Set([
81
+ "if",
82
+ "for",
83
+ "while",
84
+ "switch",
85
+ "catch",
86
+ "with",
87
+ "typeof",
88
+ "instanceof"
89
+ ]);
80
90
  const functionLikeRe = /(?:export\s+)?(?:async\s+)?(?:function\s+)?([A-Za-z_$][A-Za-z0-9_$]*)\s*\(([^)]*)\)\s*\{/g;
81
91
  functionLikeRe.lastIndex = 0;
82
92
  for (const match of stripped.matchAll(functionLikeRe)) {
83
93
  const name = match[1];
94
+ if (CONTROL_KEYWORDS.has(name)) continue;
84
95
  const paramsRaw = match[2];
85
96
  const params = paramsRaw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
86
97
  if (params.length > rules.maxParams) {
@@ -14,7 +14,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
14
14
 
15
15
  // src/security-hotspot-scanner/index.ts
16
16
  import { readdir, readFile, stat } from "node:fs/promises";
17
- import { isAbsolute, relative, resolve } from "node:path";
17
+ import { extname, isAbsolute, relative, resolve } from "node:path";
18
18
 
19
19
  // src/runtime/index.ts
20
20
  var runtime_exports = {};
@@ -250,10 +250,10 @@ var plugin = {
250
250
  if (!cfg.enabled) return;
251
251
  if (input.toolResult?.isError) return;
252
252
  const inp = input.toolInput ?? {};
253
- const sourcePath = inp["path"];
254
- if (!sourcePath || typeof sourcePath !== "string") return;
255
- if (!(0, runtime_exports.withinProject)(sourcePath)) return;
256
- const ext = sourcePath.includes(".") ? sourcePath.slice(sourcePath.lastIndexOf(".")).toLowerCase() : "";
253
+ const rawPath = inp["path"] ?? inp["filePath"] ?? inp["file_path"];
254
+ const sourcePath = typeof rawPath === "string" ? rawPath : void 0;
255
+ if (!sourcePath || !(0, runtime_exports.withinProject)(sourcePath)) return;
256
+ const ext = extname(sourcePath).toLowerCase();
257
257
  if (!scanOnChangeSet.has(ext)) {
258
258
  state.skippedCount += 1;
259
259
  return;
@@ -217,6 +217,7 @@ var plugin = {
217
217
  state.lastBump = null;
218
218
  const tagPrefix = api.config.extensions?.["semver-bump"]?.["tagPrefix"] ?? "v";
219
219
  const autoTag = api.config.extensions?.["semver-bump"]?.["autoTag"] ?? true;
220
+ const tagMessage = api.config.extensions?.["semver-bump"]?.["tagMessage"] ?? "Release {{version}}";
220
221
  const VALID_PARTS = ["major", "minor", "patch", "auto"];
221
222
  function readDefaultPart(cfg) {
222
223
  const raw = cfg.extensions?.["semver-bump"]?.["defaultPart"];
@@ -325,7 +326,8 @@ var plugin = {
325
326
  }
326
327
  if (autoTag) {
327
328
  try {
328
- await runGit(["tag", "-a", `${tagPrefix}${newVersion}`, "-m", `Release ${newVersion}`], cwd);
329
+ const msg = tagMessage.replace("{{version}}", newVersion);
330
+ await runGit(["tag", "-a", `${tagPrefix}${newVersion}`, "-m", msg], cwd);
329
331
  } catch {
330
332
  }
331
333
  }
@@ -171,8 +171,8 @@ var plugin = {
171
171
  },
172
172
  async health() {
173
173
  return {
174
- ok: state.errorCount === 0,
175
- message: state.errorCount ? `smart-rename: ${state.errorCount} error(s)` : `smart-rename: ${state.renameCount} rename(s), ${state.replacementCount} replacement(s)`,
174
+ ok: true,
175
+ message: `smart-rename: ${state.renameCount} rename(s), ${state.replacementCount} replacement(s)${state.errorCount ? ` (${state.errorCount} error(s))` : ""}`,
176
176
  counters: {
177
177
  renames: state.renameCount,
178
178
  replacements: state.replacementCount,
@@ -227,7 +227,7 @@ function isWrappedAsLinkOrCode(line, name) {
227
227
  const target = name.toLowerCase();
228
228
  if (lower.includes(`[${target}](`)) return true;
229
229
  if (lower.includes(`\`${target}\``)) return true;
230
- if (lower.includes(`[\``) && lower.includes(`\`](`)) return true;
230
+ if (new RegExp(`\\[[^\\]]*\`${escapeRegExp(target)}\`[^\\]]*\\]\\(`, "i").test(line)) return true;
231
231
  return false;
232
232
  }
233
233
  function escapeRegExp(s) {
@@ -35,7 +35,7 @@ function templateChars(template) {
35
35
  var contributorUnregister = null;
36
36
  function expandTemplate(template, variables) {
37
37
  let result = template;
38
- result = result.replace(/\{\{(\w+)\}\}/g, (match, key) => {
38
+ result = result.replace(/\{\{([\w.-]+)\}\}/g, (match, key) => {
39
39
  const value = variables[key];
40
40
  if (value !== void 0) return value;
41
41
  return match;
@@ -43,18 +43,18 @@ function expandTemplate(template, variables) {
43
43
  return result;
44
44
  }
45
45
  function expandConditionals(template, variables) {
46
- return template.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_, key, content) => {
46
+ return template.replace(/\{\{#if\s+([\w.-]+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_, key, content) => {
47
47
  const val = variables[key];
48
48
  return val !== void 0 && val !== "" && val !== "false" && val !== "0" ? content : "";
49
49
  });
50
50
  }
51
51
  function expandLoops(template, variables) {
52
- return template.replace(/\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g, (_, key, content) => {
52
+ return template.replace(/\{\{#each\s+([\w.-]+)\}\}([\s\S]*?)\{\{\/each\}\}/g, (_, key, content) => {
53
53
  const val = variables[key];
54
54
  if (!val) return "";
55
55
  if (typeof val === "string" && val.includes(",")) {
56
56
  const items = val.split(",").map((s) => s.trim());
57
- return items.map((item) => expandTemplate(content, { ...variables, [key]: item })).join("\n");
57
+ return items.map((item) => expandTemplate(content, { ...variables, [key]: item, item })).join("\n");
58
58
  }
59
59
  return expandTemplate(content, variables);
60
60
  });
@@ -14,6 +14,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
14
14
 
15
15
  // src/test-coverage-gate/index.ts
16
16
  import { readFileSync } from "node:fs";
17
+ import { extname } from "node:path";
17
18
 
18
19
  // src/runtime/index.ts
19
20
  var runtime_exports = {};
@@ -137,10 +138,10 @@ var plugin = {
137
138
  if (!cfg.enabled) return;
138
139
  if (input.toolResult?.isError) return;
139
140
  const inp = input.toolInput ?? {};
140
- const sourcePath = inp["path"];
141
- if (!sourcePath || typeof sourcePath !== "string") return;
142
- if (!(0, runtime_exports.withinProject)(sourcePath)) return;
143
- const ext = sourcePath.includes(".") ? sourcePath.slice(sourcePath.lastIndexOf(".")).toLowerCase() : "";
141
+ const rawPath = inp["path"] ?? inp["filePath"] ?? inp["file_path"];
142
+ const sourcePath = typeof rawPath === "string" ? rawPath : void 0;
143
+ if (!sourcePath || !(0, runtime_exports.withinProject)(sourcePath)) return;
144
+ const ext = extname(sourcePath).toLowerCase();
144
145
  if (!runOnChangeSet.has(ext)) {
145
146
  state.skippedCount += 1;
146
147
  return;
@@ -1,8 +1,29 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __copyProps = (to, from, except, desc) => {
6
+ if (from && typeof from === "object" || typeof from === "function") {
7
+ for (let key of __getOwnPropNames(from))
8
+ if (!__hasOwnProp.call(to, key) && key !== except)
9
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
10
+ }
11
+ return to;
12
+ };
13
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
14
+
1
15
  // src/test-flake-detector/index.ts
2
16
  import { execFile } from "node:child_process";
3
17
  import { readFileSync } from "node:fs";
4
18
  import { createRequire } from "node:module";
5
19
  import { dirname, isAbsolute, relative, resolve } from "node:path";
20
+
21
+ // src/runtime/index.ts
22
+ var runtime_exports = {};
23
+ __reExport(runtime_exports, runtime_star);
24
+ import * as runtime_star from "@wrongstack/plugin-sdk/runtime";
25
+
26
+ // src/test-flake-detector/index.ts
6
27
  var API_VERSION = "^0.1.10";
7
28
  var state = {
8
29
  invocationCount: 0,
@@ -71,13 +92,6 @@ var ALLOWED_RUNNER_FLAGS = /* @__PURE__ */ new Set([
71
92
  "--reporter=verbose",
72
93
  "--reporter=default"
73
94
  ]);
74
- function withinProject(p) {
75
- if (p.length === 0 || p.length > 4096 || p.startsWith("-")) return false;
76
- const root = resolve(process.cwd());
77
- const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
78
- const rel = relative(root, resolved);
79
- return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
80
- }
81
95
  function isInside(parent, child) {
82
96
  if (parent === child) return true;
83
97
  const rel = relative(parent, child);
@@ -134,7 +148,7 @@ function resolveTestCommand(baseCommand, testPattern) {
134
148
  }
135
149
  const args = [resolvedEntry, ...runnerArgs];
136
150
  if (testPattern) {
137
- if (!withinProject(testPattern)) return null;
151
+ if (!(0, runtime_exports.withinProject)(testPattern)) return null;
138
152
  args.push(testPattern);
139
153
  }
140
154
  return {
@@ -369,8 +369,8 @@ var plugin = {
369
369
  },
370
370
  async health() {
371
371
  return {
372
- ok: state.errorCount === 0,
373
- message: state.errorCount ? `test-generator: ${state.errorCount} error(s)` : `test-generator: ${state.generateCount} generation(s), ${state.exportCount} export(s)`,
372
+ ok: true,
373
+ message: `test-generator: ${state.generateCount} generation(s), ${state.exportCount} export(s)${state.errorCount ? ` (${state.errorCount} error(s))` : ""}`,
374
374
  counters: {
375
375
  generated: state.generateCount,
376
376
  exports: state.exportCount,
@@ -47,8 +47,9 @@ function readConfig(raw) {
47
47
  function hashTodos(todos) {
48
48
  const sorted = todos.map((t) => `${t.id}|${t.status}|${t.content ?? ""}`).sort();
49
49
  let h = 2166136261;
50
- for (let i = 0; i < sorted.join("\n").length; i++) {
51
- h ^= sorted.join("\n").charCodeAt(i);
50
+ const joined = sorted.join("\n");
51
+ for (let i = 0; i < joined.length; i++) {
52
+ h ^= joined.charCodeAt(i);
52
53
  h = h * 16777619 >>> 0;
53
54
  }
54
55
  return h.toString(16);
@@ -1,6 +1,7 @@
1
1
  // src/todo-tracker/index.ts
2
2
  import { randomUUID } from "node:crypto";
3
3
  import * as fsp from "node:fs/promises";
4
+ import { dirname } from "node:path";
4
5
  import { atomicWrite, ensureDir } from "@wrongstack/core/utils";
5
6
  import { nowIso } from "@wrongstack/primitives";
6
7
  function deriveFilePath(api) {
@@ -32,7 +33,7 @@ async function loadFile(filePath) {
32
33
  }
33
34
  }
34
35
  async function saveFile(filePath, file) {
35
- await ensureDir(filePath.replace(/[/\\][^/\\]+$/, ""));
36
+ await ensureDir(dirname(filePath));
36
37
  await atomicWrite(filePath, JSON.stringify(file, null, 2), { mode: 384 });
37
38
  }
38
39
  var state = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/plugins",
3
- "version": "0.313.0",
3
+ "version": "0.316.1",
4
4
  "description": "Official WrongStack collection of focused plugins for code quality, security, observability, planning, and agent coordination",
5
5
  "license": "MIT",
6
6
  "author": "ECOSTACK TECHNOLOGY OÜ",
@@ -303,10 +303,10 @@
303
303
  "vitest": "^4.1.11"
304
304
  },
305
305
  "dependencies": {
306
- "@wrongstack/plugin-sdk": "0.313.0",
307
- "@wrongstack/core": "0.313.0",
308
- "@wrongstack/tools": "0.313.0",
309
- "@wrongstack/primitives": "0.313.0"
306
+ "@wrongstack/core": "0.316.1",
307
+ "@wrongstack/plugin-sdk": "0.316.1",
308
+ "@wrongstack/tools": "0.316.1",
309
+ "@wrongstack/primitives": "0.316.1"
310
310
  },
311
311
  "scripts": {
312
312
  "build": "node ../../scripts/build-package.mjs",
@@ -1,3 +0,0 @@
1
- /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
- export { BoundedMap, BoundedSet, type BoundedMapOptions } from '@wrongstack/plugin-sdk/runtime';
3
- //# sourceMappingURL=bounded-map.d.ts.map
@@ -1,3 +0,0 @@
1
- /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
- export { clearLocalBinCache, findOnPath, resolveExecInvocation, resolveFirstNodeBin, resolveNodeBin, resolveWin32Command, type ExecInvocation, type ResolvedNodeBin, } from '@wrongstack/plugin-sdk/runtime';
3
- //# sourceMappingURL=local-bin.d.ts.map
@@ -1,3 +0,0 @@
1
- /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
- export { UNSERIALIZABLE, safeJsonStringify } from '@wrongstack/plugin-sdk/runtime';
3
- //# sourceMappingURL=safe-json.d.ts.map