@wrongstack/plugins 0.317.0 → 0.317.2

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.js +12 -10
  2. package/dist/agent-handoff.js +30 -10
  3. package/dist/auto-doc.js +22 -9
  4. package/dist/auto-escalate.js +17 -7
  5. package/dist/auto-i18n-extractor.js +19 -9
  6. package/dist/branch-guard.js +12 -9
  7. package/dist/changelog-writer/index.d.ts +3 -3
  8. package/dist/changelog-writer.js +59 -14
  9. package/dist/checkpoint.js +18 -7
  10. package/dist/code-metrics.js +17 -9
  11. package/dist/commit-validator.js +18 -11
  12. package/dist/config-validator.js +5 -3
  13. package/dist/context-pins.js +17 -6
  14. package/dist/cost-tracker.js +24 -16
  15. package/dist/cron.js +17 -13
  16. package/dist/dead-code-detector.js +5 -4
  17. package/dist/dep-guard.js +5 -4
  18. package/dist/dependency-vulnerability-gate.js +64 -24
  19. package/dist/diff-summary.js +6 -2
  20. package/dist/doc-sync-guard.js +12 -2
  21. package/dist/duplicate-code-detector.js +87 -15
  22. package/dist/error-lens.js +6 -5
  23. package/dist/feature-flag-tracker.js +43 -9
  24. package/dist/file-watcher.js +26 -3
  25. package/dist/format-on-save.js +9 -5
  26. package/dist/git-autocommit.js +20 -12
  27. package/dist/gitignore-guard.js +7 -4
  28. package/dist/import-organizer.js +9 -5
  29. package/dist/index.js +1675 -745
  30. package/dist/injection-shield/index.d.ts +1 -0
  31. package/dist/injection-shield.js +30 -5
  32. package/dist/interface-contract-guard.js +4 -2
  33. package/dist/knowledge-graph.js +40 -14
  34. package/dist/license-audit-gate.js +61 -8
  35. package/dist/lint-gate.js +13 -5
  36. package/dist/llm-cache.js +20 -6
  37. package/dist/loop-breaker.js +16 -8
  38. package/dist/migration-planner.js +80 -11
  39. package/dist/model-router.js +16 -9
  40. package/dist/notify-hub.js +14 -5
  41. package/dist/path-guard.js +7 -3
  42. package/dist/performance-regression-gate.js +12 -6
  43. package/dist/plugin-stack-observer.js +25 -3
  44. package/dist/pr-drafter.js +16 -7
  45. package/dist/process-guard.js +2 -1
  46. package/dist/prompt-firewall.js +9 -7
  47. package/dist/refactor-suggester.js +37 -8
  48. package/dist/release-notes-generator/index.d.ts +9 -0
  49. package/dist/release-notes-generator.js +50 -12
  50. package/dist/schema-evolution-guard.js +33 -6
  51. package/dist/secret-scanner.js +10 -7
  52. package/dist/security-hotspot-scanner.js +13 -8
  53. package/dist/semantic-search-indexer/index.d.ts +11 -0
  54. package/dist/semantic-search-indexer.js +67 -19
  55. package/dist/semver-bump/index.d.ts +3 -3
  56. package/dist/semver-bump.js +30 -15
  57. package/dist/session-recap/index.d.ts +16 -0
  58. package/dist/session-recap.js +18 -9
  59. package/dist/shell-check.js +27 -6
  60. package/dist/smart-rename.js +18 -9
  61. package/dist/spec-linker.js +70 -19
  62. package/dist/template-engine.js +24 -14
  63. package/dist/test-coverage-gate.js +1 -1
  64. package/dist/test-flake-detector.js +28 -5
  65. package/dist/test-generator/index.d.ts +10 -0
  66. package/dist/test-generator.js +35 -13
  67. package/dist/todo-listener.js +2 -2
  68. package/dist/todo-tracker.js +19 -11
  69. package/dist/token-budget.js +14 -9
  70. package/dist/token-throttle.js +8 -3
  71. package/dist/type-gate.js +9 -5
  72. package/package.json +5 -5
@@ -47,12 +47,15 @@ function readConfig(raw) {
47
47
  return { ...DEFAULTS };
48
48
  }
49
49
  const r = raw;
50
- const scanOnChange = Array.isArray(r["scanOnChange"]) ? r["scanOnChange"].filter((x) => typeof x === "string") : DEFAULTS.scanOnChange;
50
+ const rawScan = r["scanOnChange"] ?? r["scan_on_change"] ?? r["extensions"] ?? r["file_extensions"];
51
+ const scanOnChange = Array.isArray(rawScan) ? rawScan.filter((x) => typeof x === "string") : DEFAULTS.scanOnChange;
51
52
  scanOnChangeSet = new Set(scanOnChange);
53
+ const rawSev = typeof (r["severity"] ?? r["mode"] ?? r["action"]) === "string" ? String(r["severity"] ?? r["mode"] ?? r["action"]).trim().toLowerCase() : void 0;
54
+ const rawMax = r["maxFindings"] ?? r["max_findings"] ?? r["limit"];
52
55
  return {
53
56
  enabled: r["enabled"] === true,
54
- severity: r["severity"] === "block" ? "block" : DEFAULTS.severity,
55
- maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 100 ? r["maxFindings"] : DEFAULTS.maxFindings,
57
+ severity: rawSev === "block" ? "block" : DEFAULTS.severity,
58
+ maxFindings: typeof rawMax === "number" && rawMax >= 1 && rawMax <= 100 ? rawMax : DEFAULTS.maxFindings,
56
59
  scanOnChange
57
60
  };
58
61
  }
@@ -72,7 +75,7 @@ var PATTERNS = [
72
75
  {
73
76
  type: "hardcoded_http",
74
77
  severity: "medium",
75
- regex: /http:\/\/[a-zA-Z0-9][^\s'")\\]*/i
78
+ regex: /http:\/\/(?!(?:localhost|127\.0\.0\.1|0\.0\.0\.0|www\.w3\.org|schemas\.microsoft\.com)\b)[a-zA-Z0-9][^\s'")\\]*/i
76
79
  },
77
80
  {
78
81
  type: "console_log_credentials",
@@ -250,7 +253,7 @@ var plugin = {
250
253
  if (!cfg.enabled) return;
251
254
  if (input.toolResult?.isError) return;
252
255
  const inp = input.toolInput ?? {};
253
- const rawPath = inp["path"] ?? inp["filePath"] ?? inp["file_path"];
256
+ const rawPath = inp["path"] ?? inp["TargetFile"] ?? inp["filePath"] ?? inp["targetFile"] ?? inp["file_path"] ?? inp["file"];
254
257
  const sourcePath = typeof rawPath === "string" ? rawPath : void 0;
255
258
  if (!sourcePath || !(0, runtime_exports.withinProject)(sourcePath)) return;
256
259
  const ext = extname(sourcePath).toLowerCase();
@@ -315,12 +318,14 @@ Review or remove the risky pattern(s).`;
315
318
  mutating: false,
316
319
  async execute(input) {
317
320
  if (!cfg.enabled) return { ok: false, error: "security-hotspot-scanner is disabled" };
318
- const result = await scanPath(input.path, cfg);
321
+ const raw = input;
322
+ const targetPath = (typeof input.path === "string" ? input.path : void 0) ?? (typeof raw["directory"] === "string" ? raw["directory"] : void 0) ?? (typeof raw["dir"] === "string" ? raw["dir"] : void 0) ?? (typeof raw["SearchDirectory"] === "string" ? raw["SearchDirectory"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? ".";
323
+ const result = await scanPath(targetPath, cfg);
319
324
  state.scanCount += 1;
320
325
  state.fileScanCount += result.filesScanned;
321
326
  state.findingCount += result.findings.length;
322
327
  if (!result.scanned) {
323
- return { ok: false, error: result.error, path: input.path };
328
+ return { ok: false, error: result.error, path: targetPath };
324
329
  }
325
330
  state.lastResult = {
326
331
  path: result.path,
@@ -330,7 +335,7 @@ Review or remove the risky pattern(s).`;
330
335
  };
331
336
  return {
332
337
  ok: true,
333
- path: input.path,
338
+ path: targetPath,
334
339
  filesScanned: result.filesScanned,
335
340
  findings: result.findings,
336
341
  findingCount: result.findings.length,
@@ -31,6 +31,17 @@
31
31
  * @public
32
32
  */
33
33
  import type { Plugin } from '@wrongstack/core/types';
34
+ interface SemanticSearchConfig {
35
+ enabled: boolean;
36
+ includeExtensions: string[];
37
+ excludePatterns: string[];
38
+ maxFileBytes: number;
39
+ defaultLimit: number;
40
+ minTokenLength: number;
41
+ maxMatchesPerFile: number;
42
+ maxFiles: number;
43
+ }
44
+ export declare function readConfig(raw: unknown): SemanticSearchConfig;
34
45
  declare const plugin: Plugin;
35
46
  export default plugin;
36
47
  //# sourceMappingURL=index.d.ts.map
@@ -14,6 +14,8 @@ var state = {
14
14
  queryCount: 0,
15
15
  reindexCount: 0,
16
16
  buildPromise: null,
17
+ buildGeneration: 0,
18
+ publishedGeneration: 0,
17
19
  hookUnregister: null
18
20
  };
19
21
  var DEFAULTS = {
@@ -61,18 +63,23 @@ var DEFAULTS = {
61
63
  function readConfig(raw) {
62
64
  if (!raw || typeof raw !== "object") return { ...DEFAULTS, includeExtensions: [...DEFAULTS.includeExtensions], excludePatterns: [...DEFAULTS.excludePatterns] };
63
65
  const r = raw;
64
- const includeExtensions = Array.isArray(r["includeExtensions"]) ? r["includeExtensions"].filter((x) => typeof x === "string") : [...DEFAULTS.includeExtensions];
65
- const excludePatterns = Array.isArray(r["excludePatterns"]) ? r["excludePatterns"].filter((x) => typeof x === "string") : [...DEFAULTS.excludePatterns];
66
+ const rawExts = r["includeExtensions"] ?? r["include_extensions"] ?? r["extensions"] ?? r["file_extensions"];
67
+ const includeExtensions = Array.isArray(rawExts) ? rawExts.filter((x) => typeof x === "string") : [...DEFAULTS.includeExtensions];
68
+ const rawExclude = r["excludePatterns"] ?? r["exclude_patterns"] ?? r["exclude"];
69
+ const excludePatterns = Array.isArray(rawExclude) ? rawExclude.filter((x) => typeof x === "string") : [...DEFAULTS.excludePatterns];
66
70
  const clamp = (v, min, max, fallback) => typeof v === "number" && Number.isFinite(v) && v >= min && v <= max ? v : fallback;
71
+ const rawBytes = r["maxFileBytes"] ?? r["max_file_bytes"] ?? r["maxBytes"] ?? r["max_bytes"];
72
+ const rawLimit = r["defaultLimit"] ?? r["default_limit"] ?? r["limit"];
73
+ const rawMaxFiles = r["maxFiles"] ?? r["max_files"];
67
74
  return {
68
75
  enabled: r["enabled"] !== false,
69
76
  includeExtensions,
70
77
  excludePatterns,
71
- maxFileBytes: clamp(r["maxFileBytes"], 1024, 5e7, DEFAULTS.maxFileBytes),
72
- defaultLimit: clamp(r["defaultLimit"], 1, 1e3, DEFAULTS.defaultLimit),
78
+ maxFileBytes: clamp(rawBytes, 1024, 5e7, DEFAULTS.maxFileBytes),
79
+ defaultLimit: clamp(rawLimit, 1, 1e3, DEFAULTS.defaultLimit),
73
80
  minTokenLength: clamp(r["minTokenLength"], 1, 10, DEFAULTS.minTokenLength),
74
81
  maxMatchesPerFile: clamp(r["maxMatchesPerFile"], 1, 100, DEFAULTS.maxMatchesPerFile),
75
- maxFiles: clamp(r["maxFiles"], 1, 5e4, DEFAULTS.maxFiles)
82
+ maxFiles: clamp(rawMaxFiles, 1, 5e4, DEFAULTS.maxFiles)
76
83
  };
77
84
  }
78
85
  function normalizeSlashes(p) {
@@ -221,25 +228,30 @@ async function buildIndex(rootPath, cfg) {
221
228
  if (rootStats.isFile()) {
222
229
  const relPath = normalizeSlashes(relative(normalizeSlashes(process.cwd()), rootPath));
223
230
  await indexFileFromStats(rootPath, relPath === "" ? "." : relPath, rootStats, cfg);
224
- state.fileCount = state.index.files.size;
231
+ state.fileCount = state.index ? state.index.files.size : 0;
225
232
  } else if (rootStats.isDirectory()) {
226
233
  const fileBatch = [];
227
234
  await walkDirectory(rootPath, cfg, excludes, fileBatch);
228
235
  await flushFileBatch(fileBatch, cfg);
229
236
  }
230
- state.termCount = state.index.terms.size;
237
+ state.termCount = state.index ? state.index.terms.size : 0;
231
238
  }
232
239
  async function ensureIndex(rootPath, cfg) {
233
- if (state.index && state.cachedPath === rootPath) return;
234
- state.buildPromise ??= buildIndex(rootPath, cfg).finally(() => {
235
- state.buildPromise = null;
236
- });
237
- await state.buildPromise;
238
- if (!state.index || state.cachedPath !== rootPath) {
239
- state.buildPromise = buildIndex(rootPath, cfg).finally(() => {
240
+ for (; ; ) {
241
+ if (state.index && state.cachedPath === rootPath && state.publishedGeneration === state.buildGeneration) {
242
+ return;
243
+ }
244
+ const gen = state.buildGeneration;
245
+ state.buildPromise ??= buildIndex(rootPath, cfg).finally(() => {
240
246
  state.buildPromise = null;
241
247
  });
242
248
  await state.buildPromise;
249
+ if (gen === state.buildGeneration && state.index && state.cachedPath === rootPath) {
250
+ state.publishedGeneration = gen;
251
+ return;
252
+ }
253
+ state.index = null;
254
+ state.cachedPath = null;
243
255
  }
244
256
  }
245
257
  function compareRankedCandidates(a, b) {
@@ -379,6 +391,8 @@ var plugin = {
379
391
  state.queryCount = 0;
380
392
  state.reindexCount = 0;
381
393
  state.buildPromise = null;
394
+ state.buildGeneration = 0;
395
+ state.publishedGeneration = 0;
382
396
  if (state.hookUnregister) {
383
397
  try {
384
398
  state.hookUnregister();
@@ -397,6 +411,11 @@ var plugin = {
397
411
  type: "string",
398
412
  description: "Space-separated keywords to search for."
399
413
  },
414
+ q: { type: "string", description: "Alias for `query`." },
415
+ text: { type: "string", description: "Alias for `query`." },
416
+ keyword: { type: "string", description: "Alias for `query`." },
417
+ keywords: { type: "string", description: "Alias for `query`." },
418
+ search: { type: "string", description: "Alias for `query`." },
400
419
  limit: {
401
420
  type: "number",
402
421
  description: "Maximum number of results (defaults to configured defaultLimit)."
@@ -404,9 +423,23 @@ var plugin = {
404
423
  path: {
405
424
  type: "string",
406
425
  description: "Directory or file to search under (defaults to project root)."
407
- }
426
+ },
427
+ directory: { type: "string", description: "Alias for `path`." },
428
+ dir: { type: "string", description: "Alias for `path`." },
429
+ SearchDirectory: { type: "string", description: "Alias for `path`." },
430
+ SearchPath: { type: "string", description: "Alias for `path`." }
408
431
  },
409
- required: ["query"]
432
+ // Schema-validating hosts check this BEFORE execute() normalizes the
433
+ // aliases, so every query alternative must be declared or alias-only
434
+ // inputs are rejected as schema-invalid before they reach the tool.
435
+ anyOf: [
436
+ { required: ["query"] },
437
+ { required: ["q"] },
438
+ { required: ["text"] },
439
+ { required: ["keyword"] },
440
+ { required: ["keywords"] },
441
+ { required: ["search"] }
442
+ ]
410
443
  },
411
444
  permission: "auto",
412
445
  category: "Search",
@@ -417,12 +450,14 @@ var plugin = {
417
450
  if (!cfg.enabled) {
418
451
  return { ok: false, error: "semantic-search-indexer is disabled" };
419
452
  }
420
- const resolved = resolveProjectPath(input.path);
453
+ const rawPath = input.path ?? input["directory"] ?? input["dir"] ?? input["SearchDirectory"] ?? input["SearchPath"] ?? input["TargetFile"] ?? input["targetFile"] ?? input["filePath"] ?? input["file"];
454
+ const resolved = resolveProjectPath(typeof rawPath === "string" ? rawPath : void 0);
421
455
  if (!resolved) {
422
456
  return { ok: false, error: "path outside project root" };
423
457
  }
424
458
  await ensureIndex(resolved, cfg);
425
- const query = String(input.query ?? "");
459
+ const rawQuery = input.query ?? input["q"] ?? input["text"] ?? input["keyword"] ?? input["keywords"] ?? input["search"] ?? "";
460
+ const query = String(rawQuery);
426
461
  const limit = typeof input.limit === "number" && input.limit >= 1 ? Math.floor(input.limit) : cfg.defaultLimit;
427
462
  const results = runQuery(query, limit, cfg);
428
463
  const queryTokens = [...new Set(tokenize(query, cfg.minTokenLength))];
@@ -464,6 +499,16 @@ var plugin = {
464
499
  };
465
500
  }
466
501
  });
502
+ if (typeof api.registerHook === "function") {
503
+ state.hookUnregister = api.registerHook(
504
+ "PostToolUse",
505
+ "write|edit|write_to_file|replace_file_content",
506
+ (() => {
507
+ state.buildGeneration += 1;
508
+ }),
509
+ { background: true }
510
+ );
511
+ }
467
512
  api.log.info("semantic-search-indexer plugin loaded", {
468
513
  version: "0.1.0",
469
514
  defaultLimit: cfg.defaultLimit,
@@ -493,6 +538,8 @@ var plugin = {
493
538
  state.queryCount = 0;
494
539
  state.reindexCount = 0;
495
540
  state.buildPromise = null;
541
+ state.buildGeneration = 0;
542
+ state.publishedGeneration = 0;
496
543
  api.log.info("semantic-search-indexer: teardown complete", { final });
497
544
  },
498
545
  async health() {
@@ -511,5 +558,6 @@ var plugin = {
511
558
  };
512
559
  var semantic_search_indexer_default = plugin;
513
560
  export {
514
- semantic_search_indexer_default as default
561
+ semantic_search_indexer_default as default,
562
+ readConfig
515
563
  };
@@ -15,9 +15,9 @@ interface ConventionalCommit {
15
15
  message: string;
16
16
  breaking: boolean;
17
17
  }
18
- /** Parse a conventional-commit subject line. Accepts the breaking `!` both
19
- * before and after the scope (`feat!: x`, `feat(api)!: x`). */
20
- export declare function parseConventional(subject: string): Omit<ConventionalCommit, 'hash'>;
18
+ /** Parse a conventional-commit subject line and optional body. Accepts the breaking `!` both
19
+ * before and after the scope (`feat!: x`, `feat(api)!: x`), and checks body for BREAKING CHANGE. */
20
+ export declare function parseConventional(subject: string, body?: string): Omit<ConventionalCommit, 'hash'>;
21
21
  export declare function determineBump(commits: ConventionalCommit[]): BumpType;
22
22
  declare const plugin: Plugin;
23
23
  export default plugin;
@@ -109,26 +109,43 @@ function bumpVersion(version, part) {
109
109
  }
110
110
  return `${major}.${minor}.${patch}`;
111
111
  }
112
- function parseConventional(subject) {
112
+ function parseConventional(subject, body = "") {
113
113
  const m = subject.match(/^(\w+)(!)?(?:\(([^)]+)\))?(!)?:\s+(.+)/);
114
+ const hasBreakingInSubject = !!(m?.[2] ?? m?.[4]);
115
+ const hasBreakingInBody = /(?:^|\W)BREAKING[ -]CHANGES?(?!\w)/i.test(body);
114
116
  return {
115
117
  type: m?.[1] ?? "chore",
116
- breaking: !!(m?.[2] ?? m?.[4]),
118
+ breaking: hasBreakingInSubject || hasBreakingInBody,
117
119
  scope: m?.[3],
118
120
  message: m?.[5] ?? subject
119
121
  };
120
122
  }
121
- async function getRecentCommits(sinceTag, cwd) {
122
- const range = sinceTag ? `${sinceTag}..HEAD` : "-30";
123
- const output = await runGit(["log", range, "--format=%H %s"], cwd);
123
+ function parseGitLogOutput(output) {
124
124
  if (!output) return [];
125
- return output.split("\n").filter(Boolean).map((line) => {
125
+ if (output.includes("") || output.includes("")) {
126
+ return output.split("").map((block) => block.trim()).filter(Boolean).map((block) => {
127
+ const parts = block.split("");
128
+ const hash = parts[0] ?? "";
129
+ const subject = parts[1] ?? "";
130
+ const body = parts[2] ?? "";
131
+ return { hash, ...parseConventional(subject, body) };
132
+ });
133
+ }
134
+ return output.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
126
135
  const spaceIdx = line.indexOf(" ");
136
+ if (spaceIdx === -1) {
137
+ return { hash: line, ...parseConventional("") };
138
+ }
127
139
  const hash = line.slice(0, spaceIdx);
128
140
  const message = line.slice(spaceIdx + 1);
129
141
  return { hash, ...parseConventional(message) };
130
142
  });
131
143
  }
144
+ async function getRecentCommits(sinceTag, cwd) {
145
+ const range = sinceTag ? `${sinceTag}..HEAD` : "-30";
146
+ const output = await runGit(["log", range, "--format=%H%x1f%s%x1f%b%x1e"], cwd);
147
+ return parseGitLogOutput(output);
148
+ }
132
149
  function determineBump(commits) {
133
150
  if (commits.some((c) => c.breaking)) return "major";
134
151
  if (commits.some((c) => c.type === "feat")) return "minor";
@@ -264,6 +281,7 @@ var plugin = {
264
281
  dry_run: true,
265
282
  currentVersion,
266
283
  suggestedBump: bumpPart,
284
+ bump: bumpPart,
267
285
  newVersion,
268
286
  commitCount: part === "auto" ? commits.length : void 0,
269
287
  message: `Would bump ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
@@ -379,8 +397,10 @@ var plugin = {
379
397
  state.invocationCount += 1;
380
398
  state.perTool["semver_bump"] = (state.perTool["semver_bump"] ?? 0) + 1;
381
399
  const cwd = input["cwd"];
382
- const dryRun = input["dry_run"] ?? false;
383
- const part = input["part"] ?? defaultPart;
400
+ const dryRun = input["dry_run"] ?? input["dryRun"] ?? input["dry"] ?? false;
401
+ const rawPart = input["part"] ?? input["bumpType"] ?? input["bump_type"] ?? input["type"] ?? input["releaseType"] ?? input["release_type"] ?? input["level"] ?? input["increment"] ?? input["bump"];
402
+ const normPart = typeof rawPart === "string" ? rawPart.trim().toLowerCase() : void 0;
403
+ const part = normPart === "major" || normPart === "minor" || normPart === "patch" || normPart === "auto" ? normPart : defaultPart;
384
404
  return performBump(part, dryRun, cwd);
385
405
  }
386
406
  });
@@ -516,15 +536,10 @@ var plugin = {
516
536
  let commits;
517
537
  try {
518
538
  const output = await runGit(
519
- ["log", range === to ? "-30" : range, "--format=%H %s"],
539
+ ["log", range === to ? "-30" : range, "--format=%H%x1f%s%x1f%b%x1e"],
520
540
  safeCwd
521
541
  );
522
- commits = output.split("\n").filter(Boolean).map((line) => {
523
- const spaceIdx = line.indexOf(" ");
524
- const hash = line.slice(0, spaceIdx);
525
- const message = line.slice(spaceIdx + 1);
526
- return { hash, ...parseConventional(message) };
527
- });
542
+ commits = parseGitLogOutput(output);
528
543
  } catch (err) {
529
544
  return { ok: false, error: `Failed to get git log: ${err}` };
530
545
  }
@@ -43,6 +43,22 @@
43
43
  * @public
44
44
  */
45
45
  import type { Plugin } from '@wrongstack/core/types';
46
+ interface SessionRecapConfig {
47
+ enabled: boolean;
48
+ /** Prepended to the broadcast `subject` (mailbox reads this in the inbox). */
49
+ subjectPrefix: string;
50
+ /** Number of last transcript events to include in the recap body. */
51
+ includeTranscriptTail: number;
52
+ /** Hard cap on the recap body size (chars). Default 8 KB. */
53
+ maxBodyChars: number;
54
+ /**
55
+ * When true and `api.llm` is wired, a natural-language summary of the
56
+ * session is written by the LLM and prepended to the recap body.
57
+ * Best-effort — a failure leaves the metrics-only recap intact.
58
+ */
59
+ aiSummary: boolean;
60
+ }
61
+ export declare function readConfig(raw: unknown): SessionRecapConfig;
46
62
  declare const plugin: Plugin;
47
63
  export default plugin;
48
64
  //# sourceMappingURL=index.d.ts.map
@@ -45,12 +45,16 @@ var DEFAULTS = {
45
45
  function readConfig(raw) {
46
46
  if (!raw || typeof raw !== "object") return { ...DEFAULTS };
47
47
  const r = raw;
48
+ const rawPrefix = r["subjectPrefix"] ?? r["subject_prefix"] ?? r["prefix"];
49
+ const rawTail = r["includeTranscriptTail"] ?? r["include_transcript_tail"] ?? r["transcriptTail"] ?? r["transcript_tail"];
50
+ const rawBody = r["maxBodyChars"] ?? r["max_body_chars"] ?? r["maxChars"] ?? r["max_chars"];
51
+ const rawAi = r["aiSummary"] ?? r["ai_summary"] ?? r["useLlm"] ?? r["use_llm"];
48
52
  return {
49
53
  enabled: r["enabled"] !== false,
50
- subjectPrefix: typeof r["subjectPrefix"] === "string" ? r["subjectPrefix"] : DEFAULTS.subjectPrefix,
51
- includeTranscriptTail: typeof r["includeTranscriptTail"] === "number" && r["includeTranscriptTail"] >= 0 ? r["includeTranscriptTail"] : DEFAULTS.includeTranscriptTail,
52
- maxBodyChars: typeof r["maxBodyChars"] === "number" && r["maxBodyChars"] > 0 ? r["maxBodyChars"] : DEFAULTS.maxBodyChars,
53
- aiSummary: r["aiSummary"] === true
54
+ subjectPrefix: typeof rawPrefix === "string" ? rawPrefix : DEFAULTS.subjectPrefix,
55
+ includeTranscriptTail: typeof rawTail === "number" && rawTail >= 0 ? rawTail : DEFAULTS.includeTranscriptTail,
56
+ maxBodyChars: typeof rawBody === "number" && rawBody > 0 ? rawBody : DEFAULTS.maxBodyChars,
57
+ aiSummary: rawAi === true
54
58
  };
55
59
  }
56
60
  function touchActivity() {
@@ -223,8 +227,9 @@ var plugin = {
223
227
  touchActivity();
224
228
  const p = payload;
225
229
  const model = p?.model ?? "unknown";
226
- const input = p?.usage?.input ?? 0;
227
- const output = p?.usage?.output ?? 0;
230
+ const u = p?.usage;
231
+ const input = typeof u?.["input"] === "number" ? u["input"] : typeof u?.["inputTokens"] === "number" ? u["inputTokens"] : typeof u?.["promptTokens"] === "number" ? u["promptTokens"] : typeof u?.["prompt_tokens"] === "number" ? u["prompt_tokens"] : 0;
232
+ const output = typeof u?.["output"] === "number" ? u["output"] : typeof u?.["outputTokens"] === "number" ? u["outputTokens"] : typeof u?.["completionTokens"] === "number" ? u["completionTokens"] : typeof u?.["completion_tokens"] === "number" ? u["completion_tokens"] : 0;
228
233
  bumpModelUsage(model, input, output);
229
234
  });
230
235
  state.eventUnsubscribers.push(offUsage);
@@ -261,10 +266,13 @@ var plugin = {
261
266
  const transcriptPath = api.session?.transcriptPath;
262
267
  const tailEvents = await readTranscriptTail(transcriptPath, cfg.includeTranscriptTail);
263
268
  const duration = formatDuration(state.startedAt, state.lastActivityAt);
269
+ const rawInput = input ?? {};
270
+ const sessionId = (typeof input.sessionId === "string" ? input.sessionId : void 0) ?? (typeof rawInput["session_id"] === "string" ? rawInput["session_id"] : void 0) ?? (typeof rawInput["id"] === "string" ? rawInput["id"] : void 0) ?? null;
271
+ const cwd = (typeof input.cwd === "string" ? input.cwd : void 0) ?? (typeof rawInput["workingDirectory"] === "string" ? rawInput["workingDirectory"] : void 0) ?? (typeof rawInput["dir"] === "string" ? rawInput["dir"] : void 0) ?? null;
264
272
  const recap = {
265
273
  session: {
266
- id: input.sessionId ?? null,
267
- cwd: input.cwd ?? null,
274
+ id: sessionId,
275
+ cwd,
268
276
  startedAt: state.startedAt,
269
277
  endedAt: state.lastActivityAt,
270
278
  duration
@@ -476,5 +484,6 @@ Tokens: ${recap.tokens.total.input} in / ${recap.tokens.total.output} out
476
484
  };
477
485
  var session_recap_default = plugin;
478
486
  export {
479
- session_recap_default as default
487
+ session_recap_default as default,
488
+ readConfig
480
489
  };
@@ -151,10 +151,16 @@ var plugin = {
151
151
  type: "object",
152
152
  properties: {
153
153
  files: {
154
- type: "array",
155
- items: { type: "string" },
156
- description: "Shell script files to check. Mutually exclusive with `directory`."
154
+ description: "Shell script files to check \u2014 a single path string or an array of paths. Mutually exclusive with `directory`. Aliases: `file`, `filePath`, `TargetFile`, `targetFile`, `path`.",
155
+ // execute() normalizes the string form and the alias keys, but
156
+ // schema-validating hosts check this BEFORE execute declare them.
157
+ anyOf: [{ type: "array", items: { type: "string" } }, { type: "string" }]
157
158
  },
159
+ file: { type: "string", description: "Alias for `files` (single path)." },
160
+ filePath: { type: "string", description: "Alias for `files` (single path)." },
161
+ TargetFile: { type: "string", description: "Alias for `files` (single path)." },
162
+ targetFile: { type: "string", description: "Alias for `files` (single path)." },
163
+ path: { type: "string", description: "Alias for `files` (single path)." },
158
164
  directory: {
159
165
  type: "string",
160
166
  default: ".",
@@ -183,8 +189,17 @@ var plugin = {
183
189
  mutating: true,
184
190
  async execute(input) {
185
191
  const inp = input;
186
- const files = inp.files;
187
- const directory = inp.directory ?? ".";
192
+ let files;
193
+ const rawFiles = inp.files ?? input["file"] ?? input["filePath"] ?? input["file_path"] ?? input["TargetFile"] ?? input["targetFile"] ?? input["path"];
194
+ if (rawFiles !== void 0) {
195
+ if (typeof rawFiles === "string" && rawFiles.trim().length > 0) {
196
+ files = [rawFiles.trim()];
197
+ } else if (Array.isArray(rawFiles)) {
198
+ files = rawFiles.filter((f) => typeof f === "string" && f.trim().length > 0);
199
+ }
200
+ }
201
+ const rawDirectory = inp.directory ?? input["dir"] ?? input["SearchDirectory"];
202
+ const directory = typeof rawDirectory === "string" && rawDirectory.length > 0 ? rawDirectory : ".";
188
203
  const pattern = inp.pattern ?? "";
189
204
  const severity = inp.severity ?? "warning";
190
205
  state.invocationCount += 1;
@@ -236,7 +251,13 @@ var plugin = {
236
251
  issues = await runShellCheck(checkFiles, severity);
237
252
  } catch (err) {
238
253
  const msg = err instanceof Error ? err.message : String(err);
239
- return { ok: false, error: msg, issues: [], filesScanned: 0 };
254
+ return {
255
+ ok: false,
256
+ error: msg,
257
+ issues: [],
258
+ filesScanned: 0,
259
+ mode: scannedDirectories ? "directory" : "files"
260
+ };
240
261
  }
241
262
  const byFile = {};
242
263
  for (const issue of issues) {
@@ -1,7 +1,7 @@
1
1
  // src/smart-rename/index.ts
2
2
  import { readFileSync, writeFileSync } from "node:fs";
3
3
  import { extname, isAbsolute, relative, resolve } from "node:path";
4
- var API_VERSION = "^0.1.10";
4
+ var NEW_API_VERSION = "^0.1.10";
5
5
  var state = {
6
6
  renameCount: 0,
7
7
  replacementCount: 0,
@@ -11,12 +11,17 @@ var DEFAULTS = {
11
11
  enabled: true,
12
12
  extensions: [".ts", ".tsx", ".js", ".jsx"]
13
13
  };
14
+ function normalizeExtensions(exts) {
15
+ return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
16
+ }
14
17
  function readConfig(raw) {
15
- if (!raw || typeof raw !== "object") return { ...DEFAULTS };
18
+ if (!raw || typeof raw !== "object") return { ...DEFAULTS, extensions: normalizeExtensions(DEFAULTS.extensions) };
16
19
  const r = raw;
20
+ const rawExts = r["extensions"] ?? r["file_extensions"] ?? r["fileExtensions"];
21
+ const exts = Array.isArray(rawExts) ? rawExts.filter((x) => typeof x === "string") : DEFAULTS.extensions;
17
22
  return {
18
23
  enabled: r["enabled"] !== false,
19
- extensions: Array.isArray(r["extensions"]) ? r["extensions"].filter((x) => typeof x === "string") : DEFAULTS.extensions
24
+ extensions: normalizeExtensions(exts)
20
25
  };
21
26
  }
22
27
  function withinProject(p) {
@@ -58,7 +63,7 @@ var plugin = {
58
63
  name: "smart-rename",
59
64
  version: "0.1.0",
60
65
  description: "Whole-word identifier rename inside a single source file",
61
- apiVersion: API_VERSION,
66
+ apiVersion: NEW_API_VERSION,
62
67
  capabilities: { tools: true },
63
68
  defaultConfig: { ...DEFAULTS },
64
69
  configSchema: {
@@ -100,9 +105,10 @@ var plugin = {
100
105
  capabilities: ["fs.write"],
101
106
  async execute(input) {
102
107
  if (!cfg.enabled) return { ok: false, error: "smart-rename is disabled" };
103
- const rawPath = input.path;
104
- const oldName = input.oldName;
105
- const newName = input.newName;
108
+ const inp = input ?? {};
109
+ const rawPath = inp["path"] ?? inp["filePath"] ?? inp["file_path"] ?? inp["TargetFile"] ?? inp["targetFile"] ?? inp["file"];
110
+ const oldName = inp["oldName"] ?? inp["old_name"] ?? inp["from"];
111
+ const newName = inp["newName"] ?? inp["new_name"] ?? inp["to"];
106
112
  if (!rawPath || typeof rawPath !== "string") {
107
113
  return { ok: false, error: "path is required" };
108
114
  }
@@ -136,7 +142,10 @@ var plugin = {
136
142
  const { preview, replacements } = renameInContent(content, oldName, newName);
137
143
  state.renameCount += 1;
138
144
  state.replacementCount += replacements;
139
- if (input.apply) {
145
+ const isDryRun = inp["dryRun"] === true || inp["dry_run"] === true || inp["dry"] === true || inp["previewOnly"] === true;
146
+ const rawApply = inp["apply"] ?? inp["write"] ?? inp["save"] ?? inp["persist"];
147
+ const shouldApply = Boolean(rawApply) && !isDryRun;
148
+ if (shouldApply) {
140
149
  try {
141
150
  writeFileSync(resolved, preview, "utf-8");
142
151
  } catch (err) {
@@ -149,7 +158,7 @@ var plugin = {
149
158
  path: relativePath(resolved),
150
159
  replacements,
151
160
  preview,
152
- applied: Boolean(input.apply)
161
+ applied: shouldApply
153
162
  };
154
163
  }
155
164
  });