@wrongstack/tools 0.319.0 → 0.320.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 (119) hide show
  1. package/dist/_edit-match.d.ts +1 -3
  2. package/dist/_util.d.ts +7 -0
  3. package/dist/audit.js +8 -8
  4. package/dist/bash.js +6 -6
  5. package/dist/browser/index.js +1 -1
  6. package/dist/builtin.d.ts +10 -0
  7. package/dist/builtin.js +54 -52
  8. package/dist/{chunk-65EMYN5G.js → chunk-23EX5WDO.js} +6 -3
  9. package/dist/{chunk-2472NT76.js → chunk-2OXHS3KZ.js} +17 -5
  10. package/dist/{chunk-7WV3D4S3.js → chunk-34VLZ35Z.js} +75 -36
  11. package/dist/{chunk-MYRMD6PZ.js → chunk-3GIKYHLA.js} +32 -8
  12. package/dist/{chunk-X5K3U6GI.js → chunk-3M7LLN7W.js} +71 -7
  13. package/dist/{chunk-VALQFAY2.js → chunk-4BABEJC2.js} +158 -45
  14. package/dist/{chunk-42BHI7TN.js → chunk-4CQWNZ62.js} +2 -2
  15. package/dist/{chunk-WNUJR3H4.js → chunk-5UFVCIUB.js} +40 -16
  16. package/dist/{chunk-UMXVT7ST.js → chunk-6AAOT4PW.js} +58 -10
  17. package/dist/{chunk-H6AWAZ2Y.js → chunk-B4HTSV6Z.js} +57 -6
  18. package/dist/{chunk-5ZYEGP2P.js → chunk-BK732J7I.js} +72 -27
  19. package/dist/{chunk-KD5EHYA2.js → chunk-C2DQKVLO.js} +61 -28
  20. package/dist/{chunk-Z4VBTVUM.js → chunk-DFQDJT22.js} +99 -12
  21. package/dist/{chunk-6RNIFLG4.js → chunk-ELRRTWNP.js} +38 -6
  22. package/dist/{chunk-3PVYQFRQ.js → chunk-ESLPBT5A.js} +2 -1
  23. package/dist/{chunk-YZHK4TLF.js → chunk-FQNXYA6W.js} +45 -6
  24. package/dist/{chunk-OYVPBPCL.js → chunk-HR67OWEP.js} +72 -33
  25. package/dist/{chunk-ZDUGI5BC.js → chunk-I7FBZOEP.js} +2 -2
  26. package/dist/{chunk-HBM4QNXU.js → chunk-IU4DV2PA.js} +18 -8
  27. package/dist/{chunk-TFMVHYJI.js → chunk-J43O3BOX.js} +9 -15
  28. package/dist/{chunk-ODFVG54U.js → chunk-J74TMK4M.js} +37 -13
  29. package/dist/{chunk-COUNXG4X.js → chunk-JLYA4X7K.js} +75 -8
  30. package/dist/{chunk-FQZOHLG3.js → chunk-JSNSMG5R.js} +3 -2
  31. package/dist/{chunk-SYXW3Y5Z.js → chunk-JVRLTEO7.js} +2 -2
  32. package/dist/{chunk-V43EQ6VR.js → chunk-KE7A76VC.js} +3 -2
  33. package/dist/{chunk-ZKSBHXVB.js → chunk-KWXIKGE3.js} +64 -15
  34. package/dist/{chunk-RK47IWO3.js → chunk-LRDIRJJL.js} +62 -30
  35. package/dist/{chunk-UC3RKRIV.js → chunk-NDIT3SBZ.js} +3 -3
  36. package/dist/{chunk-C3W3V54D.js → chunk-NSPNFOJ7.js} +57 -27
  37. package/dist/{chunk-TX3R5NVV.js → chunk-NU2IZ72J.js} +7 -7
  38. package/dist/{chunk-WHIIIREL.js → chunk-NYRLWDT5.js} +7 -3
  39. package/dist/{chunk-PMHVSHMU.js → chunk-OTPTQBYE.js} +80 -19
  40. package/dist/{chunk-ZHH3BEWU.js → chunk-OYP27E3I.js} +4 -4
  41. package/dist/{chunk-W4GAAI3M.js → chunk-PV6HIZQD.js} +2 -2
  42. package/dist/{chunk-NKNOBUBU.js → chunk-QGF2C7TF.js} +2 -2
  43. package/dist/{chunk-PQTONQ4P.js → chunk-QPNYDH5B.js} +19 -5
  44. package/dist/{chunk-VFF3Q5Z3.js → chunk-QSNVDHOZ.js} +37 -9
  45. package/dist/{chunk-45JPRMC2.js → chunk-QWKSKFKZ.js} +7 -3
  46. package/dist/{chunk-2VSZC6QM.js → chunk-SCMRM6NL.js} +6 -4
  47. package/dist/{chunk-RJZQWEMS.js → chunk-T77XOX7E.js} +3 -2
  48. package/dist/{chunk-QZDOQRF7.js → chunk-UZFY3CKD.js} +35 -14
  49. package/dist/{chunk-D4CXUZIF.js → chunk-VD2LV5WO.js} +82 -13
  50. package/dist/{chunk-DPKTXJQS.js → chunk-VPALDFYM.js} +2 -2
  51. package/dist/{chunk-IIF5HIVF.js → chunk-VVZPQPTY.js} +2 -2
  52. package/dist/{chunk-HWDB2G7J.js → chunk-W232EFEM.js} +49 -36
  53. package/dist/{chunk-FJEQZHZN.js → chunk-W2CECGTG.js} +24 -14
  54. package/dist/{chunk-4Q7LFU4T.js → chunk-WAQASTXB.js} +20 -9
  55. package/dist/{chunk-CWALNI6K.js → chunk-X6KXNHUO.js} +1 -1
  56. package/dist/{chunk-OCUXN6Y3.js → chunk-XRNN44QY.js} +1 -1
  57. package/dist/{chunk-N5O3FK5Q.js → chunk-XSSFBS5I.js} +70 -29
  58. package/dist/{chunk-Y7QXSE7N.js → chunk-YTKSNTT4.js} +44 -10
  59. package/dist/{chunk-MRKWZCHZ.js → chunk-Z5Q3TXRY.js} +3 -3
  60. package/dist/{chunk-PRXZOIEW.js → chunk-Z6IHMJMC.js} +104 -59
  61. package/dist/{chunk-NWZ6XGBJ.js → chunk-ZBMANLYH.js} +5 -3
  62. package/dist/{chunk-OJ4FIBAN.js → chunk-ZMW6WJFT.js} +30 -23
  63. package/dist/{chunk-QLF74MSV.js → chunk-ZUFHAHTE.js} +12 -6
  64. package/dist/codebase-index/index.js +12 -12
  65. package/dist/codebase-index/parser-worker-script.js +3 -3
  66. package/dist/codebase-index/project-server.js +5 -5
  67. package/dist/codebase-index/worker.js +4 -4
  68. package/dist/design.d.ts +1 -1
  69. package/dist/design.js +1 -1
  70. package/dist/diff.js +3 -2
  71. package/dist/document.js +2 -2
  72. package/dist/e2e.js +2 -2
  73. package/dist/edit.js +9 -9
  74. package/dist/exec.js +6 -6
  75. package/dist/fetch.js +2 -2
  76. package/dist/format.js +8 -8
  77. package/dist/git.js +2 -2
  78. package/dist/glob.js +3 -3
  79. package/dist/{go-parser-IKHW67WE.js → go-parser-D7ELWQ7F.js} +2 -2
  80. package/dist/grep.d.ts +2 -0
  81. package/dist/grep.js +5 -3
  82. package/dist/index.js +56 -56
  83. package/dist/install.js +8 -8
  84. package/dist/{json-parser-2EOVP5MR.js → json-parser-34DHYCPW.js} +1 -18
  85. package/dist/json.js +2 -2
  86. package/dist/kanban-tool-types.d.ts +2 -0
  87. package/dist/kanban.js +2 -2
  88. package/dist/languages/index.js +6 -6
  89. package/dist/legacy-bridge-FOV5ODDI.js +18 -0
  90. package/dist/lint.js +8 -8
  91. package/dist/logs.js +2 -2
  92. package/dist/memory.js +1 -1
  93. package/dist/next-steps.d.ts +7 -0
  94. package/dist/next-steps.js +82 -38
  95. package/dist/outdated.js +5 -3
  96. package/dist/pack.js +53 -53
  97. package/dist/{parser-dispatch-3PDBKIQ7.js → parser-dispatch-BDVCJXTV.js} +4 -4
  98. package/dist/patch.js +8 -8
  99. package/dist/plan.js +4 -4
  100. package/dist/process-registry.js +1 -1
  101. package/dist/ps-slash.js +3 -3
  102. package/dist/{py-parser-RFWG2HYI.js → py-parser-QC6MR4GB.js} +3 -3
  103. package/dist/read.js +8 -8
  104. package/dist/replace.js +9 -8
  105. package/dist/scaffold.js +2 -2
  106. package/dist/search.js +1 -1
  107. package/dist/session-kanban.js +1 -1
  108. package/dist/task.js +4 -4
  109. package/dist/test.js +8 -8
  110. package/dist/todo.js +3 -3
  111. package/dist/tool-search.js +1 -1
  112. package/dist/tool-tier.js +54 -54
  113. package/dist/{tree-sitter-parser-FGKHJGSX.js → tree-sitter-parser-GILYQLS4.js} +2 -2
  114. package/dist/tree.js +2 -2
  115. package/dist/typecheck.js +8 -8
  116. package/dist/win32.js +1 -1
  117. package/dist/write.js +9 -9
  118. package/package.json +5 -5
  119. package/dist/legacy-bridge-EC7GM2IW.js +0 -18
@@ -74,9 +74,7 @@ export declare function adjustIndent(newLf: string, fromIndent: string, toIndent
74
74
  };
75
75
  /**
76
76
  * Best-effort locator for the no-match error: find the window whose trimmed
77
- * lines are most similar to the needle (cheap common-prefix scoring — this
78
- * runs on the failure path where the needle by definition doesn't match, so
79
- * precision matters less than never being slow). Returns the 1-based line
77
+ * lines are most similar to the needle. Returns the 1-based line
80
78
  * and a short snippet the model can use to correct itself without re-reading.
81
79
  */
82
80
  export declare function nearestMatchHint(fileLf: string, oldLf: string): {
package/dist/_util.d.ts CHANGED
@@ -92,6 +92,13 @@ export declare function truncateDiffPayload(diff: string, maxBytes: number): {
92
92
  text: string;
93
93
  truncated: boolean;
94
94
  };
95
+ /**
96
+ * Truncate to `max` UTF-8 bytes keeping BOTH ends, appending a marker that
97
+ * reports the true omitted byte count. Like `truncateHeadTail` and
98
+ * `truncateDiffPayload`, marker room is reserved from the budget and both
99
+ * slices are cut byte-accurately (not by UTF-16 code unit), so the result
100
+ * never exceeds `max` — the contract `fetchTool`'s `maxOutputBytes` relies on.
101
+ */
95
102
  export declare function truncateMiddle(s: string, max: number): string;
96
103
  export declare function isBinaryBuffer(buf: Buffer): boolean;
97
104
  /** Unified byte cap for all command tool output fed to the model. */
package/dist/audit.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  auditTool
3
- } from "./chunk-QZDOQRF7.js";
4
- import "./chunk-ZDUGI5BC.js";
5
- import "./chunk-UC3RKRIV.js";
6
- import "./chunk-ZHH3BEWU.js";
7
- import "./chunk-2VSZC6QM.js";
8
- import "./chunk-3PVYQFRQ.js";
3
+ } from "./chunk-UZFY3CKD.js";
4
+ import "./chunk-I7FBZOEP.js";
5
+ import "./chunk-NDIT3SBZ.js";
6
+ import "./chunk-OYP27E3I.js";
7
+ import "./chunk-SCMRM6NL.js";
8
+ import "./chunk-ESLPBT5A.js";
9
9
  import "./chunk-HFXSNKCV.js";
10
- import "./chunk-V43EQ6VR.js";
11
- import "./chunk-QLF74MSV.js";
10
+ import "./chunk-KE7A76VC.js";
11
+ import "./chunk-ZUFHAHTE.js";
12
12
  export {
13
13
  auditTool
14
14
  };
package/dist/bash.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  bashTool
3
- } from "./chunk-WNUJR3H4.js";
3
+ } from "./chunk-5UFVCIUB.js";
4
4
  import "./chunk-BTSFTGIL.js";
5
- import "./chunk-NKNOBUBU.js";
6
- import "./chunk-2VSZC6QM.js";
7
- import "./chunk-3PVYQFRQ.js";
5
+ import "./chunk-QGF2C7TF.js";
6
+ import "./chunk-SCMRM6NL.js";
7
+ import "./chunk-ESLPBT5A.js";
8
8
  import "./chunk-HFXSNKCV.js";
9
- import "./chunk-V43EQ6VR.js";
10
- import "./chunk-QLF74MSV.js";
9
+ import "./chunk-KE7A76VC.js";
10
+ import "./chunk-ZUFHAHTE.js";
11
11
  export {
12
12
  bashTool
13
13
  };
@@ -27,7 +27,7 @@ import {
27
27
  resolvePinnedBrowserTarget,
28
28
  safeBrowserUrl,
29
29
  shutdownBrowserTools
30
- } from "../chunk-45JPRMC2.js";
30
+ } from "../chunk-QWKSKFKZ.js";
31
31
  export {
32
32
  BrowserArtifactStore,
33
33
  BrowserNetworkGuardProxy,
package/dist/builtin.d.ts CHANGED
@@ -1,4 +1,13 @@
1
1
  import type { Tool } from '@wrongstack/core/types';
2
+ /**
3
+ * Provider-facing descriptions for every built-in tool.
4
+ *
5
+ * Keep these decision-oriented: state the primary job, the appropriate time
6
+ * to use the tool, and any important boundary. This is deliberately kept next
7
+ * to the canonical catalog so tools discovered lazily receive the same useful
8
+ * guidance as directly exposed tools.
9
+ */
10
+ export declare const BUILTIN_TOOL_DESCRIPTIONS: Readonly<Record<string, string>>;
2
11
  /**
3
12
  * Non-essential tools that can be omitted in token-saving mode to reduce
4
13
  * per-request token consumption. Each tool definition adds ~50-200 tokens
@@ -55,5 +64,6 @@ export declare const TIER2_TOOLS: Tool[];
55
64
  * tokens while keeping all core capabilities available.
56
65
  */
57
66
  export declare const TIER3_TOOLS: Tool[];
67
+ /** The executable catalog always exposes the reviewed description above. */
58
68
  export declare const builtinTools: Tool[];
59
69
  //# sourceMappingURL=builtin.d.ts.map
package/dist/builtin.js CHANGED
@@ -1,74 +1,76 @@
1
1
  import {
2
+ BUILTIN_TOOL_DESCRIPTIONS,
2
3
  OFF_ONLY_TOOLS,
3
4
  OPTIONAL_TOOLS,
4
5
  TIER1_TOOLS,
5
6
  TIER2_TOOLS,
6
7
  TIER3_TOOLS,
7
8
  builtinTools
8
- } from "./chunk-VALQFAY2.js";
9
- import "./chunk-DPKTXJQS.js";
10
- import "./chunk-RK47IWO3.js";
11
- import "./chunk-OCUXN6Y3.js";
12
- import "./chunk-H6AWAZ2Y.js";
13
- import "./chunk-X5K3U6GI.js";
14
- import "./chunk-RJZQWEMS.js";
9
+ } from "./chunk-4BABEJC2.js";
10
+ import "./chunk-VPALDFYM.js";
11
+ import "./chunk-LRDIRJJL.js";
12
+ import "./chunk-XRNN44QY.js";
13
+ import "./chunk-B4HTSV6Z.js";
14
+ import "./chunk-3M7LLN7W.js";
15
+ import "./chunk-T77XOX7E.js";
15
16
  import "./chunk-FRN7TIAA.js";
16
17
  import "./chunk-WXJY3BKV.js";
17
18
  import "./chunk-QBV725YG.js";
18
- import "./chunk-6RNIFLG4.js";
19
- import "./chunk-Y7QXSE7N.js";
20
- import "./chunk-4Q7LFU4T.js";
21
- import "./chunk-QZDOQRF7.js";
22
- import "./chunk-Z4VBTVUM.js";
23
- import "./chunk-YZHK4TLF.js";
24
- import "./chunk-HBM4QNXU.js";
25
- import "./chunk-2472NT76.js";
26
- import "./chunk-UMXVT7ST.js";
27
- import "./chunk-COUNXG4X.js";
28
- import "./chunk-D4CXUZIF.js";
29
- import "./chunk-7WV3D4S3.js";
30
- import "./chunk-HWDB2G7J.js";
31
- import "./chunk-ZKSBHXVB.js";
32
- import "./chunk-ODFVG54U.js";
33
- import "./chunk-VFF3Q5Z3.js";
34
- import "./chunk-ZDUGI5BC.js";
35
- import "./chunk-UC3RKRIV.js";
36
- import "./chunk-ZHH3BEWU.js";
37
- import "./chunk-OYVPBPCL.js";
38
- import "./chunk-OJ4FIBAN.js";
39
- import "./chunk-N5O3FK5Q.js";
19
+ import "./chunk-ELRRTWNP.js";
20
+ import "./chunk-YTKSNTT4.js";
21
+ import "./chunk-WAQASTXB.js";
22
+ import "./chunk-UZFY3CKD.js";
23
+ import "./chunk-DFQDJT22.js";
24
+ import "./chunk-FQNXYA6W.js";
25
+ import "./chunk-IU4DV2PA.js";
26
+ import "./chunk-2OXHS3KZ.js";
27
+ import "./chunk-6AAOT4PW.js";
28
+ import "./chunk-JLYA4X7K.js";
29
+ import "./chunk-VD2LV5WO.js";
30
+ import "./chunk-34VLZ35Z.js";
31
+ import "./chunk-W232EFEM.js";
32
+ import "./chunk-KWXIKGE3.js";
33
+ import "./chunk-J74TMK4M.js";
34
+ import "./chunk-QSNVDHOZ.js";
35
+ import "./chunk-I7FBZOEP.js";
36
+ import "./chunk-NDIT3SBZ.js";
37
+ import "./chunk-OYP27E3I.js";
38
+ import "./chunk-HR67OWEP.js";
39
+ import "./chunk-ZMW6WJFT.js";
40
+ import "./chunk-XSSFBS5I.js";
40
41
  import "./chunk-Q5JPPCZC.js";
41
- import "./chunk-NWZ6XGBJ.js";
42
- import "./chunk-WNUJR3H4.js";
43
- import "./chunk-PMHVSHMU.js";
42
+ import "./chunk-ZBMANLYH.js";
43
+ import "./chunk-5UFVCIUB.js";
44
+ import "./chunk-OTPTQBYE.js";
44
45
  import "./chunk-BTSFTGIL.js";
45
- import "./chunk-NKNOBUBU.js";
46
- import "./chunk-2VSZC6QM.js";
47
- import "./chunk-3PVYQFRQ.js";
46
+ import "./chunk-QGF2C7TF.js";
47
+ import "./chunk-SCMRM6NL.js";
48
+ import "./chunk-ESLPBT5A.js";
48
49
  import "./chunk-HFXSNKCV.js";
49
- import "./chunk-KD5EHYA2.js";
50
- import "./chunk-PQTONQ4P.js";
50
+ import "./chunk-C2DQKVLO.js";
51
+ import "./chunk-QPNYDH5B.js";
51
52
  import "./chunk-NBDZMMG2.js";
52
- import "./chunk-5ZYEGP2P.js";
53
- import "./chunk-FJEQZHZN.js";
54
- import "./chunk-CWALNI6K.js";
55
- import "./chunk-45JPRMC2.js";
56
- import "./chunk-C3W3V54D.js";
57
- import "./chunk-MYRMD6PZ.js";
58
- import "./chunk-PRXZOIEW.js";
59
- import "./chunk-FQZOHLG3.js";
60
- import "./chunk-MRKWZCHZ.js";
61
- import "./chunk-W4GAAI3M.js";
62
- import "./chunk-TFMVHYJI.js";
63
- import "./chunk-TX3R5NVV.js";
64
- import "./chunk-IIF5HIVF.js";
53
+ import "./chunk-BK732J7I.js";
54
+ import "./chunk-W2CECGTG.js";
55
+ import "./chunk-X6KXNHUO.js";
56
+ import "./chunk-QWKSKFKZ.js";
57
+ import "./chunk-NSPNFOJ7.js";
58
+ import "./chunk-3GIKYHLA.js";
59
+ import "./chunk-Z6IHMJMC.js";
60
+ import "./chunk-JSNSMG5R.js";
61
+ import "./chunk-Z5Q3TXRY.js";
62
+ import "./chunk-PV6HIZQD.js";
63
+ import "./chunk-J43O3BOX.js";
64
+ import "./chunk-NU2IZ72J.js";
65
+ import "./chunk-VVZPQPTY.js";
65
66
  import "./chunk-6IEBD27U.js";
66
67
  import "./chunk-UCYQTZ7F.js";
67
68
  import "./chunk-YOHO4DVT.js";
68
69
  import "./chunk-DTQ75VBC.js";
69
- import "./chunk-V43EQ6VR.js";
70
- import "./chunk-QLF74MSV.js";
70
+ import "./chunk-KE7A76VC.js";
71
+ import "./chunk-ZUFHAHTE.js";
71
72
  export {
73
+ BUILTIN_TOOL_DESCRIPTIONS,
72
74
  OFF_ONLY_TOOLS,
73
75
  OPTIONAL_TOOLS,
74
76
  TIER1_TOOLS,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  builtinToolsPack
3
- } from "./chunk-SYXW3Y5Z.js";
3
+ } from "./chunk-JVRLTEO7.js";
4
4
  import {
5
5
  TIER1_TOOLS,
6
6
  TIER2_TOOLS
7
- } from "./chunk-VALQFAY2.js";
7
+ } from "./chunk-4BABEJC2.js";
8
8
 
9
9
  // src/tool-tier.ts
10
10
  function toolNameSet(tools) {
@@ -18,6 +18,7 @@ var BUILTIN_TIER_COUNTS = {
18
18
  aggressive: TIER1_TOOLS.length
19
19
  };
20
20
  function selectBuiltinToolsForTier(tier, allTools) {
21
+ if (!Array.isArray(allTools)) return [];
21
22
  switch (tier) {
22
23
  case "off":
23
24
  return [...allTools];
@@ -35,6 +36,8 @@ function selectBuiltinToolsForTier(tier, allTools) {
35
36
  const tier1Names = toolNameSet(TIER1_TOOLS);
36
37
  return allTools.filter((tool) => tier1Names.has(tool.name));
37
38
  }
39
+ default:
40
+ return [...allTools];
38
41
  }
39
42
  }
40
43
  function registerBuiltinToolTier(options) {
@@ -51,4 +54,4 @@ export {
51
54
  selectBuiltinToolsForTier,
52
55
  registerBuiltinToolTier
53
56
  };
54
- //# sourceMappingURL=chunk-65EMYN5G.js.map
57
+ //# sourceMappingURL=chunk-23EX5WDO.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  safeResolveReal
3
- } from "./chunk-QLF74MSV.js";
3
+ } from "./chunk-ZUFHAHTE.js";
4
4
 
5
5
  // src/scaffold.ts
6
6
  import * as fs from "node:fs/promises";
@@ -132,13 +132,23 @@ var scaffoldTool = {
132
132
  },
133
133
  required: ["template", "name"]
134
134
  },
135
- async execute(input, ctx) {
135
+ async execute(input, ctx, _opts) {
136
+ const signal = _opts?.signal ?? ctx?.signal;
137
+ signal?.throwIfAborted();
136
138
  const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
137
139
  const name = input.name;
138
140
  const vars = { name, ...input.vars };
139
141
  const builtIn = BUILT_IN_TEMPLATES[input.template];
140
142
  if (builtIn) {
141
- return await handleBuiltIn(name, builtIn.files, cwd, ctx, input.dry_run ?? false, vars);
143
+ return await handleBuiltIn(
144
+ name,
145
+ builtIn.files,
146
+ cwd,
147
+ ctx,
148
+ input.dry_run ?? false,
149
+ vars,
150
+ signal
151
+ );
142
152
  }
143
153
  return {
144
154
  template: input.template,
@@ -150,10 +160,11 @@ var scaffoldTool = {
150
160
  };
151
161
  }
152
162
  };
153
- async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
163
+ async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars, signal) {
154
164
  const files = [];
155
165
  let filesCreated = 0;
156
166
  for (const [filePath, content] of Object.entries(templateFiles)) {
167
+ if (signal?.aborted) break;
157
168
  const resolvedPath = substituteVars(filePath, name, vars);
158
169
  const joinedPath = path.join(cwd, resolvedPath);
159
170
  const root = path.resolve(ctx.projectRoot ?? ctx.cwd ?? process.cwd());
@@ -189,6 +200,7 @@ function substituteVars(content, name, vars) {
189
200
  result = result.replace(/\{\{name\}\}/g, () => kebab);
190
201
  result = result.replace(/\{\{Name\}\}/g, () => pascal);
191
202
  for (const [k, v] of Object.entries(vars)) {
203
+ if (k === "name" || k === "Name") continue;
192
204
  result = result.replace(new RegExp(`\\{\\{${escapeRegexLiteral(k)}\\}\\}`, "g"), () => v);
193
205
  }
194
206
  return result;
@@ -197,4 +209,4 @@ function substituteVars(content, name, vars) {
197
209
  export {
198
210
  scaffoldTool
199
211
  };
200
- //# sourceMappingURL=chunk-2472NT76.js.map
212
+ //# sourceMappingURL=chunk-2OXHS3KZ.js.map
@@ -1,6 +1,9 @@
1
+ import {
2
+ mapWithConcurrency
3
+ } from "./chunk-ZBMANLYH.js";
1
4
  import {
2
5
  safeResolveReal
3
- } from "./chunk-QLF74MSV.js";
6
+ } from "./chunk-ZUFHAHTE.js";
4
7
 
5
8
  // src/diff.ts
6
9
  import { spawn } from "node:child_process";
@@ -59,6 +62,7 @@ var diffTool = {
59
62
  },
60
63
  async execute(input, ctx, opts) {
61
64
  const signal = opts?.signal ?? ctx.signal ?? new AbortController().signal;
65
+ signal.throwIfAborted();
62
66
  if (input.a !== void 0 || input.b !== void 0) {
63
67
  return await gitDiff(input, ctx, signal);
64
68
  }
@@ -66,13 +70,25 @@ var diffTool = {
66
70
  }
67
71
  };
68
72
  async function gitDiff(input, ctx, signal) {
69
- if (input.a?.startsWith("-")) {
73
+ if (input.a !== void 0 && (typeof input.a !== "string" || !input.a.trim())) {
74
+ throw new ToolValidationError({
75
+ message: "diff: ref 'a' cannot be empty or whitespace-only",
76
+ field: "a"
77
+ });
78
+ }
79
+ if (input.b !== void 0 && (typeof input.b !== "string" || !input.b.trim())) {
80
+ throw new ToolValidationError({
81
+ message: "diff: ref 'b' cannot be empty or whitespace-only",
82
+ field: "b"
83
+ });
84
+ }
85
+ if (typeof input.a === "string" && input.a.trim().startsWith("-")) {
70
86
  throw new ToolValidationError({
71
87
  message: `diff: unsafe ref "${input.a}" \u2014 refs may not begin with '-' (flag injection)`,
72
88
  field: "a"
73
89
  });
74
90
  }
75
- if (input.b?.startsWith("-")) {
91
+ if (typeof input.b === "string" && input.b.trim().startsWith("-")) {
76
92
  throw new ToolValidationError({
77
93
  message: `diff: unsafe ref "${input.b}" \u2014 refs may not begin with '-' (flag injection)`,
78
94
  field: "b"
@@ -82,9 +98,16 @@ async function gitDiff(input, ctx, signal) {
82
98
  const statMode = requestedMode === "stat";
83
99
  const effectiveMode = statMode ? "stat" : "unified";
84
100
  const sideBySideNote = requestedMode === "side-by-side" ? "side-by-side output is not supported; a unified diff was produced instead." : void 0;
85
- const gitDir = findGitDir(ctx.cwd);
101
+ const basePath = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
102
+ const gitDir = findGitDir(basePath);
86
103
  if (!gitDir) {
87
- return { diff: "", files: [], truncated: false, mode: effectiveMode };
104
+ return {
105
+ diff: "",
106
+ files: [],
107
+ truncated: false,
108
+ mode: effectiveMode,
109
+ note: "Not a git repository (or any parent up to root)."
110
+ };
88
111
  }
89
112
  const args = ["diff", "--no-color"];
90
113
  if (statMode) args.push("--stat");
@@ -93,11 +116,14 @@ async function gitDiff(input, ctx, signal) {
93
116
  if (Number.isFinite(contextLines)) args.push(`-U${contextLines}`);
94
117
  }
95
118
  if (input.staged) args.push("--staged");
96
- if (input.a) args.push(input.a);
97
- if (input.b) args.push(input.b);
119
+ if (input.a) args.push(input.a.trim());
120
+ if (input.b) args.push(input.b.trim());
98
121
  if (input.files) {
99
- const files = Array.isArray(input.files) ? input.files : input.files.split(",");
100
- args.push("--", ...files.map((f) => f.trim()));
122
+ const rawFiles = Array.isArray(input.files) ? input.files : typeof input.files === "string" ? input.files.split(",") : [];
123
+ const files = rawFiles.filter((f) => typeof f === "string").map((f) => f.trim().replace(/\\/g, "/")).filter(Boolean);
124
+ if (files.length > 0) {
125
+ args.push("--", ...files);
126
+ }
101
127
  }
102
128
  const result = await runGit(args, gitDir, signal);
103
129
  let diff = result.stdout;
@@ -110,12 +136,14 @@ async function gitDiff(input, ctx, signal) {
110
136
  \u2026[git diff truncated: ${result.stdout.length - clipped.length} of ${result.stdout.length} characters omitted]`;
111
137
  truncated = true;
112
138
  }
139
+ const exitNote = result.exitCode !== 0 ? `git diff exited with code ${result.exitCode}: ${result.stderr.trim() || "command failed"}` : void 0;
140
+ const combinedNote = [sideBySideNote, exitNote].filter(Boolean).join("\n") || void 0;
113
141
  return {
114
142
  diff,
115
143
  files: [],
116
144
  truncated,
117
145
  mode: effectiveMode,
118
- note: sideBySideNote
146
+ note: combinedNote
119
147
  };
120
148
  }
121
149
  function findGitDir(cwd) {
@@ -133,7 +161,10 @@ function findGitDir(cwd) {
133
161
  return null;
134
162
  }
135
163
  function runGit(args, cwd, signal) {
136
- return new Promise((resolve) => {
164
+ if (signal.aborted) {
165
+ return Promise.resolve({ stdout: "", stderr: "Aborted", exitCode: 124 });
166
+ }
167
+ return new Promise((resolve2) => {
137
168
  let stdout = "";
138
169
  let stderr = "";
139
170
  const child = spawn("git", args, {
@@ -149,13 +180,15 @@ function runGit(args, cwd, signal) {
149
180
  child.stderr?.on("data", (c) => {
150
181
  stderr += c.toString();
151
182
  });
152
- child.on("close", (code) => resolve({ stdout, stderr, exitCode: code ?? 0 }));
153
- child.on("error", (e) => resolve({ stdout: "", stderr: e.message, exitCode: 1 }));
183
+ child.on("close", (code) => resolve2({ stdout, stderr, exitCode: code ?? 0 }));
184
+ child.on("error", (e) => resolve2({ stdout: "", stderr: e.message, exitCode: 1 }));
154
185
  });
155
186
  }
156
- async function fileDiff(input, ctx, _signal) {
187
+ async function fileDiff(input, ctx, signal) {
188
+ signal.throwIfAborted();
157
189
  void input.context;
158
- const files = input.files ? (Array.isArray(input.files) ? input.files : input.files.split(",")).map((f) => f.trim()).filter(Boolean) : [];
190
+ const rawFiles = Array.isArray(input.files) ? input.files : typeof input.files === "string" ? input.files.split(",") : [];
191
+ const files = rawFiles.filter((f) => typeof f === "string").map((f) => f.trim()).filter(Boolean);
159
192
  if (files.length === 0) {
160
193
  return {
161
194
  diff: "No files specified",
@@ -164,25 +197,26 @@ async function fileDiff(input, ctx, _signal) {
164
197
  mode: "dump"
165
198
  };
166
199
  }
167
- const fileEntries = await Promise.all(
168
- files.map(async (file) => {
169
- const absPath = await safeResolveReal(file, ctx);
170
- const stat2 = await fs.stat(absPath).catch(() => null);
171
- if (!stat2?.isFile()) return null;
172
- if (stat2.size > MAX_FILE_DUMP_BYTES) {
173
- return {
174
- truncated: true,
175
- output: `--- ${file} (skipped: ${stat2.size} bytes exceeds the ${MAX_FILE_DUMP_BYTES} limit; use the read tool with offset/limit) ---`
176
- };
177
- }
178
- const content = await fs.readFile(absPath, "utf8");
179
- const lines = content.split(/\r?\n/);
200
+ const basePath = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
201
+ const fileEntries = await mapWithConcurrency(files, 16, async (file) => {
202
+ signal.throwIfAborted();
203
+ const fileToResolve = path.isAbsolute(file) ? file : path.resolve(basePath, file);
204
+ const absPath = await safeResolveReal(fileToResolve, ctx);
205
+ const stat2 = await fs.stat(absPath).catch(() => null);
206
+ if (!stat2?.isFile()) return null;
207
+ if (stat2.size > MAX_FILE_DUMP_BYTES) {
180
208
  return {
181
- truncated: false,
182
- output: formatWithLineNumbers(file, lines)
209
+ truncated: true,
210
+ output: `--- ${file} (skipped: ${stat2.size} bytes exceeds the ${MAX_FILE_DUMP_BYTES} limit; use the read tool with offset/limit) ---`
183
211
  };
184
- })
185
- );
212
+ }
213
+ const content = await fs.readFile(absPath, "utf8");
214
+ const lines = content.split(/\r?\n/);
215
+ return {
216
+ truncated: false,
217
+ output: formatWithLineNumbers(file, lines)
218
+ };
219
+ });
186
220
  const results = [];
187
221
  let truncated = false;
188
222
  for (const entry of fileEntries) {
@@ -201,13 +235,18 @@ async function fileDiff(input, ctx, _signal) {
201
235
  };
202
236
  }
203
237
  function formatWithLineNumbers(file, lines) {
204
- const width = String(lines.length).length;
205
- const numbered = lines.map((line, i) => `${String(i + 1).padStart(width)} | ${line}`).join("\n");
238
+ const count = lines.length;
239
+ const width = String(count).length;
240
+ const parts = new Array(count);
241
+ for (let i = 0; i < count; i++) {
242
+ const numStr = String(i + 1).padStart(width, " ");
243
+ parts[i] = `${numStr} | ${lines[i]}`;
244
+ }
206
245
  return `--- ${file} (line-numbered dump, not a unified diff) ---
207
- ${numbered}`;
246
+ ${parts.join("\n")}`;
208
247
  }
209
248
 
210
249
  export {
211
250
  diffTool
212
251
  };
213
- //# sourceMappingURL=chunk-7WV3D4S3.js.map
252
+ //# sourceMappingURL=chunk-34VLZ35Z.js.map
@@ -1,18 +1,18 @@
1
1
  import {
2
2
  checkSyntax
3
- } from "./chunk-FQZOHLG3.js";
3
+ } from "./chunk-JSNSMG5R.js";
4
4
  import {
5
5
  enqueueReindex
6
- } from "./chunk-MRKWZCHZ.js";
6
+ } from "./chunk-Z5Q3TXRY.js";
7
7
  import {
8
8
  safeResolveReal,
9
9
  sha256hex,
10
10
  truncateDiffPayload
11
- } from "./chunk-QLF74MSV.js";
11
+ } from "./chunk-ZUFHAHTE.js";
12
12
 
13
13
  // src/write.ts
14
14
  import * as fs from "node:fs/promises";
15
- import { ToolValidationError } from "@wrongstack/core/types";
15
+ import { FsError, ToolValidationError } from "@wrongstack/core/types";
16
16
  import {
17
17
  atomicWrite,
18
18
  detectNewlineStyle,
@@ -72,6 +72,15 @@ var writeTool = {
72
72
  yield { type: "final", output: await finishWrite(input, ctx, prepared, signal) };
73
73
  }
74
74
  };
75
+ function countLines(text) {
76
+ if (!text) return 0;
77
+ const trimmed = text.endsWith("\n") ? text.slice(0, -1) : text;
78
+ let count = 1;
79
+ for (let i = 0; i < trimmed.length; i++) {
80
+ if (trimmed.charCodeAt(i) === 10) count++;
81
+ }
82
+ return count;
83
+ }
75
84
  async function writeFile(input, ctx, signal) {
76
85
  signal?.throwIfAborted();
77
86
  return finishWrite(input, ctx, await prepareWrite(input, ctx), signal);
@@ -94,6 +103,14 @@ async function prepareWrite(input, ctx) {
94
103
  let prev = "";
95
104
  try {
96
105
  const stat2 = await fs.stat(absPath);
106
+ if (stat2.isDirectory()) {
107
+ throw new FsError({
108
+ message: `write: "${input.path}" is an existing directory and cannot be overwritten`,
109
+ code: "FS_WRITE_FAILED",
110
+ path: absPath,
111
+ context: { reason: "is-directory" }
112
+ });
113
+ }
97
114
  existed = stat2.isFile();
98
115
  if (existed) {
99
116
  prev = await fs.readFile(absPath, "utf8");
@@ -116,11 +133,13 @@ async function finishWrite(input, ctx, prepared, signal) {
116
133
  enqueueReindex({ projectRoot: ctx.projectRoot, files: [prepared.absPath] });
117
134
  } catch {
118
135
  }
119
- const rawDiff = prepared.existed ? unifiedDiff(prepared.prev, content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
120
- + (new file, ${content.split("\n").length} lines)`;
136
+ const isIdentical = prepared.existed && prepared.prev === content;
137
+ const rawDiff = prepared.existed ? isIdentical ? "(no-op: file content is identical to existing content)" : unifiedDiff(prepared.prev, content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
138
+ + (new file, ${countLines(content)} lines)`;
121
139
  const { text: diff, truncated: diffTruncated } = truncateDiffPayload(rawDiff, MAX_DIFF_BYTES);
140
+ const contentHash = sha256hex(content);
122
141
  const stat2 = await fs.stat(prepared.absPath);
123
- ctx.recordRead(prepared.absPath, stat2.mtimeMs, "write", sha256hex(content));
142
+ ctx.recordRead(prepared.absPath, stat2.mtimeMs, "write", contentHash);
124
143
  ctx.session?.recordFileChange?.({
125
144
  path: prepared.absPath,
126
145
  action: prepared.existed ? "modified" : "created",
@@ -134,6 +153,11 @@ async function finishWrite(input, ctx, prepared, signal) {
134
153
  ).catch(() => void 0);
135
154
  const hasSyntaxErrors = syntax !== void 0 && syntax.errors.length > 0;
136
155
  const notes = [];
156
+ if (isIdentical) {
157
+ notes.push(
158
+ "Write completed with no content changes (new content is identical to existing content)."
159
+ );
160
+ }
137
161
  if (diffTruncated) {
138
162
  notes.push("Diff truncated to the 256 KiB output budget \u2014 the full write is on disk.");
139
163
  }
@@ -155,4 +179,4 @@ async function finishWrite(input, ctx, prepared, signal) {
155
179
  export {
156
180
  writeTool
157
181
  };
158
- //# sourceMappingURL=chunk-MYRMD6PZ.js.map
182
+ //# sourceMappingURL=chunk-3GIKYHLA.js.map