@wrongstack/tools 0.305.0 → 0.306.0

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 (66) hide show
  1. package/dist/_shell-pick.d.ts +4 -5
  2. package/dist/_util.d.ts +22 -5
  3. package/dist/audit.d.ts +0 -1
  4. package/dist/audit.js +135 -46
  5. package/dist/bash.js +61 -37
  6. package/dist/browser/index.js +29 -9
  7. package/dist/browser/types.d.ts +7 -1
  8. package/dist/builtin.js +1280 -728
  9. package/dist/codebase-index/codebase-search-tool.d.ts +5 -0
  10. package/dist/codebase-index/index.d.ts +1 -0
  11. package/dist/codebase-index/index.js +225 -153
  12. package/dist/codebase-index/project-server-endpoint.d.ts +1 -2
  13. package/dist/codebase-index/project-server.js +19 -20
  14. package/dist/diff.d.ts +5 -0
  15. package/dist/diff.js +78 -12
  16. package/dist/document.js +18 -6
  17. package/dist/edit.js +69 -16
  18. package/dist/exec.js +44 -22
  19. package/dist/fetch.js +13 -1
  20. package/dist/format.d.ts +4 -2
  21. package/dist/format.js +81 -31
  22. package/dist/glob.js +12 -4
  23. package/dist/grep.d.ts +2 -0
  24. package/dist/grep.js +15 -4
  25. package/dist/index.js +1357 -765
  26. package/dist/install.js +96 -37
  27. package/dist/kanban-tool-types.d.ts +6 -1
  28. package/dist/kanban.js +60 -0
  29. package/dist/languages/index.js +28 -13
  30. package/dist/lint.js +28 -13
  31. package/dist/logs.d.ts +0 -1
  32. package/dist/logs.js +44 -13
  33. package/dist/memory.d.ts +8 -0
  34. package/dist/memory.js +23 -3
  35. package/dist/mode.d.ts +1 -1
  36. package/dist/mode.js +3 -0
  37. package/dist/next-steps.d.ts +2 -3
  38. package/dist/next-steps.js +3 -3
  39. package/dist/outdated.d.ts +0 -3
  40. package/dist/outdated.js +89 -48
  41. package/dist/pack.js +1280 -728
  42. package/dist/plan.js +76 -3
  43. package/dist/process-registry.d.ts +8 -2
  44. package/dist/process-registry.js +28 -13
  45. package/dist/ps-slash.js +22 -12
  46. package/dist/read.js +10 -4
  47. package/dist/replace.d.ts +4 -0
  48. package/dist/replace.js +104 -7
  49. package/dist/search.d.ts +6 -0
  50. package/dist/search.js +47 -26
  51. package/dist/session-kanban.d.ts +25 -0
  52. package/dist/session-kanban.js +16 -0
  53. package/dist/skill.d.ts +6 -0
  54. package/dist/skill.js +9 -10
  55. package/dist/task.js +66 -2
  56. package/dist/test.js +28 -13
  57. package/dist/todo.js +64 -2
  58. package/dist/tool-icons.js +4 -2
  59. package/dist/tool-summary.d.ts +1 -1
  60. package/dist/tool-summary.js +76 -1
  61. package/dist/tool-tier.js +1280 -728
  62. package/dist/tree.js +9 -10
  63. package/dist/typecheck.d.ts +0 -2
  64. package/dist/typecheck.js +98 -31
  65. package/dist/write.js +58 -10
  66. package/package.json +4 -3
package/dist/memory.d.ts CHANGED
@@ -16,10 +16,18 @@ interface RememberOutput {
16
16
  interface ForgetInput {
17
17
  query: string;
18
18
  scope?: MemoryScope | undefined;
19
+ /** Preview: list what WOULD be deleted without deleting anything. */
20
+ dry_run?: boolean | undefined;
19
21
  }
20
22
  interface ForgetOutput {
21
23
  removed: number;
22
24
  scope: MemoryScope;
25
+ /** True when this was a preview run — nothing was deleted. */
26
+ dryRun?: boolean | undefined;
27
+ /** dry_run only: texts of the entries the query matches (capped at 20). */
28
+ matches?: string[] | undefined;
29
+ /** dry_run only: total number of matching entries (may exceed matches.length). */
30
+ matched?: number | undefined;
23
31
  }
24
32
  export declare function rememberTool(memory: MemoryStore): Tool<RememberInput, RememberOutput>;
25
33
  export declare function forgetTool(memory: MemoryStore): Tool<ForgetInput, ForgetOutput>;
package/dist/memory.js CHANGED
@@ -62,17 +62,25 @@ function forgetTool(memory) {
62
62
  return {
63
63
  name: "forget",
64
64
  category: "Session",
65
- description: "Remove memory entries that contain the given substring (case-insensitive). Use with caution.",
66
- usageHint: "This permanently deletes matching memories in the chosen scope.\n- Provide a reasonably specific `query` to avoid deleting unrelated memories.\n- Always double-check before calling with broad queries.\n- Use `remember` + `forget` together to maintain clean long-term memory.",
65
+ description: "Remove memory entries that contain the given substring (case-insensitive). Use with caution. Pass `dry_run: true` to preview the matching entries (capped at 20) without deleting anything.",
66
+ usageHint: "This permanently deletes matching memories in the chosen scope.\n- Provide a reasonably specific `query` to avoid deleting unrelated memories.\n- Always double-check before calling with broad queries \u2014 `dry_run: true` previews the matches without deleting.\n- Use `remember` + `forget` together to maintain clean long-term memory.",
67
67
  permission: "confirm",
68
+ // WS-046: gives permission decisions something to key on — the substring
69
+ // being forgotten.
70
+ subjectKey: "query",
68
71
  mutating: true,
69
72
  timeoutMs: 2e3,
70
73
  capabilities: ["memory.delete"],
74
+ icon: "settings",
71
75
  inputSchema: {
72
76
  type: "object",
73
77
  properties: {
74
78
  query: { type: "string" },
75
- scope: { type: "string", enum: ["project-agents", "project-memory", "user-memory"] }
79
+ scope: { type: "string", enum: ["project-agents", "project-memory", "user-memory"] },
80
+ dry_run: {
81
+ type: "boolean",
82
+ description: "When true, return the matched entries (capped at 20) WITHOUT deleting them. Default false."
83
+ }
76
84
  },
77
85
  required: ["query"]
78
86
  },
@@ -84,6 +92,18 @@ function forgetTool(memory) {
84
92
  });
85
93
  }
86
94
  const scope = input.scope ?? "project-memory";
95
+ if (input.dry_run) {
96
+ const entries = await memory.list(scope);
97
+ const needle = input.query.toLowerCase();
98
+ const matching = entries.filter((entry) => entry.text.toLowerCase().includes(needle));
99
+ return {
100
+ removed: 0,
101
+ scope,
102
+ dryRun: true,
103
+ matched: matching.length,
104
+ matches: matching.slice(0, 20).map((entry) => entry.text)
105
+ };
106
+ }
87
107
  const removed = await memory.forget(input.query, scope);
88
108
  return { removed, scope };
89
109
  }
package/dist/mode.d.ts CHANGED
@@ -8,7 +8,7 @@ interface ModeOutput {
8
8
  action: string;
9
9
  currentMode?: string | undefined;
10
10
  modes?: {
11
- id: string | undefined;
11
+ id: string;
12
12
  name: string;
13
13
  description: string;
14
14
  family: ModeFamily;
package/dist/mode.js CHANGED
@@ -12,6 +12,9 @@ function createModeTool(modeStore) {
12
12
  description: "Manage agent operating modes. Modes change the agent's behavior, personality, and system prompt for different workflows (e.g. coding, security review, planning).",
13
13
  usageHint: "POWERFUL BEHAVIOR CONTROL TOOL:\n\n- Use `list` to see available modes.\n- Use `set <modeId>` to switch the agent into a specific role/mode.\n- Use `get` to check current mode.\n- Use `clear` to return to default behavior.\nSwitching modes is very effective for specialized tasks. The mode change affects how the agent reasons and which guidelines it follows.",
14
14
  permission: "confirm",
15
+ // WS-046: gives permission decisions something to key on — the mode being
16
+ // activated. Permission semantics are unchanged.
17
+ subjectKey: "mode",
15
18
  mutating: true,
16
19
  timeoutMs: 5e3,
17
20
  capabilities: ["session.mode"],
@@ -73,12 +73,11 @@ export declare function isFinalTurnStopReason(stopReason: string | undefined): b
73
73
  * Parse canonical "<nextsteps>" blocks from assistant output (or raw numbered lines).
74
74
  *
75
75
  * @param content — raw assistant message text or subagent output
76
- * @param strict retained for compatibility; assistant-output paths always require the canonical XML tag.
77
- * @param requireHeading — when true, a canonical XML tag must precede the item list.
76
+ * @param requireHeading when true (default), a canonical XML tag must precede the item list.
78
77
  * when false, numbered/bullet items are parsed from anywhere in text
79
78
  * (used by /suggest subagent output which has no heading).
80
79
  */
81
- export declare function parseNextSteps(content: string, strict?: boolean, requireHeading?: boolean): ParseNextStepsResult;
80
+ export declare function parseNextSteps(content: string, requireHeading?: boolean): ParseNextStepsResult;
82
81
  /**
83
82
  * Strip <nextsteps>...</nextsteps> blocks from subagent output text.
84
83
  * Subagent results should not contain suggestion blocks — those belong to
@@ -5,9 +5,9 @@ function isFinalTurnStopReason(stopReason) {
5
5
  var NEXT_STEPS_TAG_RE = /<nextsteps\b[^>]*>\s*\n+/i;
6
6
  var ITEM_RE = /^(?:(\d+)[.)]\s*|[-*•]\s*)(.+?)(\s+auto="true")?$/;
7
7
  var MAX_STEPS = 6;
8
- function parseNextSteps(content, strict = false, requireHeading = true) {
8
+ function parseNextSteps(content, requireHeading = true) {
9
9
  if (requireHeading) {
10
- return parseWithHeading(content, strict);
10
+ return parseWithHeading(content);
11
11
  }
12
12
  return parseRawNumbered(content);
13
13
  }
@@ -44,7 +44,7 @@ function parseRawNumbered(content) {
44
44
  autoTexts: steps.filter((s) => s.auto).map((s) => s.text)
45
45
  };
46
46
  }
47
- function parseWithHeading(content, _strict) {
47
+ function parseWithHeading(content) {
48
48
  const headingMatch = NEXT_STEPS_TAG_RE.exec(content);
49
49
  if (!headingMatch) {
50
50
  return { steps: [], texts: [], stripped: content, autoTexts: [] };
@@ -1,9 +1,6 @@
1
1
  import type { Tool } from '@wrongstack/core/types';
2
2
  interface OutdatedInput {
3
3
  cwd?: string | undefined;
4
- format?: 'list' | 'table' | undefined;
5
- include_deprecated?: boolean | undefined;
6
- check?: string | string[] | undefined;
7
4
  }
8
5
  interface OutdatedPackage {
9
6
  name: string;
package/dist/outdated.js CHANGED
@@ -17,19 +17,48 @@ var __export = (target, all) => {
17
17
  import * as fsp from "node:fs/promises";
18
18
  import * as path from "node:path";
19
19
  import * as Core from "@wrongstack/core/utils";
20
- async function detectPackageManager(cwd) {
21
- const { stat: stat5 } = await import("node:fs/promises");
22
- try {
23
- await stat5(`${cwd}/pnpm-lock.yaml`);
24
- return "pnpm";
25
- } catch {
20
+ async function detectPackageManager(cwd, stopAt) {
21
+ let dir = path.resolve(cwd);
22
+ const stop = stopAt ? path.resolve(stopAt) : dir;
23
+ for (; ; ) {
24
+ const found = await detectPackageManagerInDir(dir);
25
+ if (found) return found;
26
+ if (dir === stop) break;
27
+ const parent = path.dirname(dir);
28
+ const relParent = path.relative(stop, parent);
29
+ if (parent === dir || relParent.startsWith("..") || path.isAbsolute(relParent)) break;
30
+ dir = parent;
26
31
  }
32
+ return "npm";
33
+ }
34
+ async function detectPackageManagerInDir(dir) {
35
+ const fs6 = await import("node:fs/promises");
27
36
  try {
28
- await stat5(`${cwd}/yarn.lock`);
29
- return "yarn";
37
+ const raw = await fs6.readFile(path.join(dir, "package.json"), "utf8");
38
+ const declared = JSON.parse(raw).packageManager;
39
+ if (typeof declared === "string") {
40
+ const name = declared.split("@")[0] ?? "";
41
+ if (name === "pnpm" || name === "yarn") return name;
42
+ if (name === "npm" || name === "bun") return "npm";
43
+ }
30
44
  } catch {
31
45
  }
32
- return "npm";
46
+ const lockfiles = [
47
+ ["pnpm-lock.yaml", "pnpm"],
48
+ ["yarn.lock", "yarn"],
49
+ ["bun.lockb", "npm"],
50
+ ["bun.lock", "npm"],
51
+ ["package-lock.json", "npm"],
52
+ ["npm-shrinkwrap.json", "npm"]
53
+ ];
54
+ for (const [file, manager] of lockfiles) {
55
+ try {
56
+ await fs6.stat(`${dir}/${file}`);
57
+ return manager;
58
+ } catch {
59
+ }
60
+ }
61
+ return null;
33
62
  }
34
63
  function resolvePath(input, ctx) {
35
64
  return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);
@@ -2719,8 +2748,11 @@ var init_redact_command = __esm({
2719
2748
  /--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
2720
2749
  // -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
2721
2750
  // (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
2751
+ // The value must be token-like (>= 8 chars) so ordinary combined flags such
2752
+ // as `tar -tf` / `ssh -tt` are not eaten. Global flag: EVERY occurrence is
2753
+ // redacted, not just the first.
2722
2754
  // NOTE: synced with @wrongstack/core observability/redact-command.ts.
2723
- /(?<![-\w])-t(?:[=\s]+)?[^\s,-]+/,
2755
+ /(?<![-\w])-t(?:[=\s]+)?[^\s,-]{8,}/g,
2724
2756
  // -p|-password|-a (redis auth) short flags: attached + separated + =value.
2725
2757
  // Same token-start anchor; over-redaction is an accepted tradeoff for a
2726
2758
  // redaction function. Synced with core copy.
@@ -2728,8 +2760,9 @@ var init_redact_command = __esm({
2728
2760
  // env var–style secrets: TOKEN=x, API_KEY=y, etc.
2729
2761
  /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
2730
2762
  // Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
2731
- // when preceded by a flag name (e.g. --github-token=EyJ...).
2732
- /--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/
2763
+ // when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
2764
+ // every such flag in the command line is redacted, not just the first.
2765
+ /--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/g
2733
2766
  ];
2734
2767
  }
2735
2768
  });
@@ -2815,11 +2848,15 @@ var init_process_registry = __esm({
2815
2848
  return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;
2816
2849
  }
2817
2850
  _canSignalProcessGroup(p) {
2818
- return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && typeof p.child.pid === "number" && p.child.pid === p.pid;
2851
+ return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && p.child !== null && typeof p.child.pid === "number" && p.child.pid === p.pid;
2819
2852
  }
2820
2853
  _killChildDirect(p, signal) {
2821
2854
  try {
2822
- p.child.kill(signal);
2855
+ if (p.child) {
2856
+ p.child.kill(signal);
2857
+ return;
2858
+ }
2859
+ if (this._isSafeSignalPid(p.pid)) process.kill(p.pid, signal);
2823
2860
  } catch {
2824
2861
  }
2825
2862
  }
@@ -3017,15 +3054,15 @@ var init_process_registry = __esm({
3017
3054
  this._pruneStale(pid);
3018
3055
  const p = this.processes.get(pid);
3019
3056
  if (!p) return false;
3020
- if (p.killed) return true;
3057
+ if (p.killed && opts.force !== true) return true;
3021
3058
  if (p.protected && opts.includeProtected !== true) return false;
3022
3059
  if (opts.preserveBackground && p.background) return false;
3023
3060
  const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
3024
3061
  const isWin2 = os.platform() === "win32";
3025
3062
  if (isWin2) {
3026
- const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
3063
+ const liveRealChild = p.child === null || p.child.exitCode === null && typeof p.child.pid === "number";
3027
3064
  const directFallback = () => {
3028
- if (p.child.exitCode === null) {
3065
+ if (p.child && p.child.exitCode === null) {
3029
3066
  try {
3030
3067
  p.child.kill("SIGKILL");
3031
3068
  } catch {
@@ -3037,10 +3074,7 @@ var init_process_registry = __esm({
3037
3074
  onSettled: directFallback
3038
3075
  })) {
3039
3076
  } else {
3040
- try {
3041
- p.child.kill(force ? "SIGKILL" : "SIGTERM");
3042
- } catch {
3043
- }
3077
+ this._killChildDirect(p, force ? "SIGKILL" : "SIGTERM");
3044
3078
  }
3045
3079
  p.killed = true;
3046
3080
  return true;
@@ -3051,7 +3085,7 @@ var init_process_registry = __esm({
3051
3085
  } else {
3052
3086
  this._killPosix(p, "SIGTERM");
3053
3087
  const timer = setTimeout(() => {
3054
- if (this.processes.has(pid) && !p.child.killed) {
3088
+ if (this.processes.has(pid) && !p.child?.killed) {
3055
3089
  this._killPosix(p, "SIGKILL");
3056
3090
  }
3057
3091
  }, graceMs);
@@ -3106,6 +3140,16 @@ var init_process_registry = __esm({
3106
3140
  * before reusing a PID, but we want to clean up before that becomes a risk.
3107
3141
  */
3108
3142
  _isStaleEntry(entry) {
3143
+ if (entry.child === null) {
3144
+ if (Date.now() - entry.startedAt <= 6e4) return false;
3145
+ if (os.platform() === "win32") return false;
3146
+ try {
3147
+ process.kill(entry.pid, 0);
3148
+ return false;
3149
+ } catch (err) {
3150
+ return err.code !== "EPERM";
3151
+ }
3152
+ }
3109
3153
  return entry.child.exitCode !== null && Date.now() - entry.startedAt > 6e4;
3110
3154
  }
3111
3155
  /**
@@ -4972,25 +5016,12 @@ var outdatedTool = {
4972
5016
  inputSchema: {
4973
5017
  type: "object",
4974
5018
  properties: {
4975
- cwd: { type: "string", description: "Working directory (default: cwd)" },
4976
- format: {
4977
- type: "string",
4978
- enum: ["list", "table"],
4979
- description: "Output format (default: list)"
4980
- },
4981
- include_deprecated: {
4982
- type: "boolean",
4983
- description: "Include deprecated packages (default: false)"
4984
- },
4985
- check: {
4986
- type: "string",
4987
- description: "Specific package(s) to check (comma-separated)"
4988
- }
5019
+ cwd: { type: "string", description: "Working directory (default: cwd)" }
4989
5020
  }
4990
5021
  },
4991
5022
  async execute(input, ctx, opts) {
4992
5023
  const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
4993
- const manager = await detectPackageManager(cwd);
5024
+ const manager = await detectPackageManager(cwd, ctx.projectRoot);
4994
5025
  if (manager === "npm") {
4995
5026
  try {
4996
5027
  const { detectNonJsEcosystem: detectNonJsEcosystem2 } = await Promise.resolve().then(() => (init_legacy_bridge(), legacy_bridge_exports));
@@ -5043,8 +5074,6 @@ var outdatedTool = {
5043
5074
  }
5044
5075
  }
5045
5076
  const args = ["outdated", "--json"];
5046
- if (input.format === "table") args.push("--table");
5047
- if (input.include_deprecated) args.push("--include", "deprecated");
5048
5077
  return runOutdated(manager, args, cwd, opts.signal);
5049
5078
  }
5050
5079
  };
@@ -5098,27 +5127,39 @@ function parseOutdatedOutput(json, exitCode) {
5098
5127
  truncated: false
5099
5128
  };
5100
5129
  }
5130
+ const truncated = json.length >= 1e5 || Buffer.byteLength(json, "utf8") > COMMAND_OUTPUT_MAX_BYTES;
5131
+ let parsedOk = false;
5101
5132
  try {
5102
5133
  const data = JSON.parse(json);
5134
+ parsedOk = true;
5103
5135
  for (const name of Object.keys(data)) {
5104
- const info = data[name];
5136
+ const info = data[name] ?? {};
5137
+ const str = (v) => typeof v === "string" ? v : void 0;
5105
5138
  packages.push({
5106
5139
  name,
5107
- current: info.current ?? "unknown",
5108
- latest: info.latest ?? "unknown",
5109
- wanted: info.wanted ?? "unknown",
5110
- type: info.type ?? "unknown",
5111
- location: info.location ?? name
5140
+ current: str(info["current"]) ?? "unknown",
5141
+ latest: str(info["latest"]) ?? "unknown",
5142
+ wanted: str(info["wanted"]) ?? "unknown",
5143
+ // npm calls it `type`; pnpm calls it `dependencyType`.
5144
+ type: str(info["type"]) ?? str(info["dependencyType"]) ?? "unknown",
5145
+ location: str(info["location"]) ?? name
5112
5146
  });
5113
5147
  }
5114
5148
  } catch {
5149
+ }
5150
+ const outdatedFound = parsedOk && exitCode === 1;
5151
+ let output = normalizeCommandOutput(json);
5152
+ if (outdatedFound) {
5153
+ output = `${output}
5154
+
5155
+ Note: exit code 1 from \`outdated\` means outdated packages were found (expected); treated as success.`;
5115
5156
  }
5116
5157
  return {
5117
- exit_code: exitCode,
5158
+ exit_code: outdatedFound ? 0 : exitCode,
5118
5159
  packages,
5119
5160
  total: packages.length,
5120
- output: json,
5121
- truncated: json.length >= 1e5
5161
+ output,
5162
+ truncated
5122
5163
  };
5123
5164
  }
5124
5165
  export {