@wrongstack/tools 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 (182) hide show
  1. package/dist/_edit-match.d.ts +3 -2
  2. package/dist/_output-spool.d.ts +4 -3
  3. package/dist/_shell-pick.d.ts +2 -1
  4. package/dist/_spawn-stream.d.ts +2 -1
  5. package/dist/_syntax-check.d.ts +2 -1
  6. package/dist/audit.js +10 -4055
  7. package/dist/bash-kill-guard.d.ts +3 -6
  8. package/dist/bash.js +9 -2308
  9. package/dist/batch-tool-use.js +2 -187
  10. package/dist/browser/index.js +25 -1339
  11. package/dist/browser/network-guard-proxy.d.ts +2 -1
  12. package/dist/builtin.js +69 -33949
  13. package/dist/chunk-22B7GE5T.js +260 -0
  14. package/dist/chunk-2VSZC6QM.js +113 -0
  15. package/dist/chunk-3PVYQFRQ.js +472 -0
  16. package/dist/chunk-42BHI7TN.js +337 -0
  17. package/dist/chunk-4BHUSMNL.js +104 -0
  18. package/dist/chunk-4I4LWFOO.js +431 -0
  19. package/dist/chunk-4UESMQDP.js +122 -0
  20. package/dist/chunk-553IQUMO.js +478 -0
  21. package/dist/chunk-5H4GSJAI.js +223 -0
  22. package/dist/chunk-6RNIFLG4.js +305 -0
  23. package/dist/chunk-7EY5DCRO.js +434 -0
  24. package/dist/chunk-A6BRWIXN.js +317 -0
  25. package/dist/chunk-AHHDWNXC.js +200 -0
  26. package/dist/chunk-BSLVR56S.js +199 -0
  27. package/dist/chunk-BTSFTGIL.js +7 -0
  28. package/dist/chunk-D33S4RQG.js +222 -0
  29. package/dist/chunk-DA4KNE2H.js +4412 -0
  30. package/dist/chunk-DR7JOQTC.js +520 -0
  31. package/dist/chunk-EEOFQDT6.js +898 -0
  32. package/dist/chunk-EJWSCQ6D.js +137 -0
  33. package/dist/chunk-EMRGN4KM.js +170 -0
  34. package/dist/chunk-FQZOHLG3.js +89 -0
  35. package/dist/chunk-GOIZNNOA.js +221 -0
  36. package/dist/chunk-HFXSNKCV.js +196 -0
  37. package/dist/chunk-HYX2TZ7O.js +1272 -0
  38. package/dist/chunk-I64BS3VC.js +15 -0
  39. package/dist/chunk-IVWV2UM6.js +955 -0
  40. package/dist/chunk-IXMNTNW5.js +238 -0
  41. package/dist/chunk-IYK7XGDZ.js +892 -0
  42. package/dist/chunk-J5SKD6LN.js +235 -0
  43. package/dist/chunk-JNNVRHMV.js +2111 -0
  44. package/dist/chunk-JOHXDF3D.js +295 -0
  45. package/dist/chunk-JRJJX3L5.js +424 -0
  46. package/dist/chunk-K3WNSHUF.js +1371 -0
  47. package/dist/chunk-L25CVUVR.js +2830 -0
  48. package/dist/chunk-LIJQMYYX.js +59 -0
  49. package/dist/chunk-LWKUTMD5.js +197 -0
  50. package/dist/chunk-MQX2KXVW.js +54 -0
  51. package/dist/chunk-MZZN7VCD.js +359 -0
  52. package/dist/chunk-NBDZMMG2.js +169 -0
  53. package/dist/chunk-NKJW5GFO.js +366 -0
  54. package/dist/chunk-NKNOBUBU.js +522 -0
  55. package/dist/chunk-O4MBV4AX.js +134 -0
  56. package/dist/chunk-OUBDL3WH.js +3515 -0
  57. package/dist/chunk-PQKK5JH7.js +101 -0
  58. package/dist/chunk-PY2IOS7C.js +1000 -0
  59. package/dist/chunk-Q5JPPCZC.js +12 -0
  60. package/dist/chunk-QBV725YG.js +128 -0
  61. package/dist/chunk-QZQSPUBF.js +315 -0
  62. package/dist/chunk-RGISBZBD.js +231 -0
  63. package/dist/chunk-RJZQWEMS.js +84 -0
  64. package/dist/chunk-RXISBFR5.js +315 -0
  65. package/dist/chunk-S5CCLZL3.js +99 -0
  66. package/dist/chunk-SDLVH5RZ.js +178 -0
  67. package/dist/chunk-T2F4S7U4.js +1723 -0
  68. package/dist/chunk-TEBQ4VPZ.js +163 -0
  69. package/dist/chunk-U4D5S3VQ.js +21 -0
  70. package/dist/chunk-U4VAATKP.js +193 -0
  71. package/dist/chunk-UCYQTZ7F.js +113 -0
  72. package/dist/chunk-UKU2ZX4C.js +203 -0
  73. package/dist/chunk-UNSKG6DC.js +159 -0
  74. package/dist/chunk-V43EQ6VR.js +68 -0
  75. package/dist/chunk-VB3MJGCV.js +610 -0
  76. package/dist/chunk-VFKPWSOX.js +154 -0
  77. package/dist/chunk-VJNHFZ7J.js +190 -0
  78. package/dist/chunk-VXECAJJ7.js +1091 -0
  79. package/dist/chunk-WHIIIREL.js +223 -0
  80. package/dist/chunk-Z6DAKQ3U.js +1 -0
  81. package/dist/chunk-ZJQ4IGXK.js +474 -0
  82. package/dist/chunk-ZLTFLC25.js +385 -0
  83. package/dist/circuit-breaker.js +3 -191
  84. package/dist/codebase-index/ast-invariant-engine.d.ts +5 -4
  85. package/dist/codebase-index/binary-frame.d.ts +0 -2
  86. package/dist/codebase-index/bm25.d.ts +1 -1
  87. package/dist/codebase-index/circuit-breaker.d.ts +2 -1
  88. package/dist/codebase-index/codebase-ast-replace-tool.d.ts +3 -2
  89. package/dist/codebase-index/codebase-impact-analysis-tool.d.ts +4 -3
  90. package/dist/codebase-index/codebase-invariant-check-tool.d.ts +3 -2
  91. package/dist/codebase-index/codebase-repo-map-tool.d.ts +3 -2
  92. package/dist/codebase-index/codebase-skeleton-tool.d.ts +3 -2
  93. package/dist/codebase-index/codebase-targeted-test-tool.d.ts +3 -2
  94. package/dist/codebase-index/content-hash.d.ts +0 -2
  95. package/dist/codebase-index/import-extractor.d.ts +0 -13
  96. package/dist/codebase-index/index-service.d.ts +1 -1
  97. package/dist/codebase-index/index.js +84 -13688
  98. package/dist/codebase-index/languages.d.ts +2 -1
  99. package/dist/codebase-index/module-roots.d.ts +3 -2
  100. package/dist/codebase-index/parser-batch.d.ts +0 -5
  101. package/dist/codebase-index/parser-dispatch.d.ts +2 -1
  102. package/dist/codebase-index/parser-output.d.ts +4 -3
  103. package/dist/codebase-index/parser-worker-pool.d.ts +0 -2
  104. package/dist/codebase-index/parser-worker-script.d.ts +1 -16
  105. package/dist/codebase-index/parser-worker-script.js +8 -3995
  106. package/dist/codebase-index/project-server-client-state.d.ts +2 -2
  107. package/dist/codebase-index/project-server-endpoint.d.ts +0 -15
  108. package/dist/codebase-index/project-server.js +60 -8548
  109. package/dist/codebase-index/tree-sitter/visitor.d.ts +2 -1
  110. package/dist/codebase-index/tree-sitter-parser.d.ts +0 -2
  111. package/dist/codebase-index/ts-parser.d.ts +1 -1
  112. package/dist/codebase-index/vector-search.d.ts +2 -1
  113. package/dist/codebase-index/wal-maintenance.d.ts +3 -2
  114. package/dist/codebase-index/worker.js +20 -8416
  115. package/dist/codebase-index/writer-admin.d.ts +0 -1
  116. package/dist/codebase-index/writer-search.d.ts +0 -9
  117. package/dist/design.js +2 -299
  118. package/dist/diff.js +4 -256
  119. package/dist/document.js +4 -222
  120. package/dist/e2e.js +5 -476
  121. package/dist/edit.js +14 -10200
  122. package/dist/exec.js +16 -3131
  123. package/dist/fetch.js +8 -398
  124. package/dist/format.js +10 -3750
  125. package/dist/generic-parser-VUXDIAOZ.js +13 -0
  126. package/dist/git.js +4 -380
  127. package/dist/glob.js +6 -280
  128. package/dist/go-parser-JXYKJHRC.js +469 -0
  129. package/dist/grep.js +7 -604
  130. package/dist/index.js +318 -35387
  131. package/dist/install.js +10 -4091
  132. package/dist/json-parser-IBHZ4BZC.js +213 -0
  133. package/dist/json.js +4 -665
  134. package/dist/kanban-contract-actions.d.ts +1 -1
  135. package/dist/kanban-detail-actions.d.ts +2 -1
  136. package/dist/kanban-presence.d.ts +2 -1
  137. package/dist/kanban-serializer.d.ts +0 -8
  138. package/dist/kanban-split-task-handler.d.ts +2 -2
  139. package/dist/kanban-tool-types.d.ts +2 -1
  140. package/dist/kanban.js +3 -2096
  141. package/dist/languages/diagnostics.d.ts +2 -1
  142. package/dist/languages/index.js +23 -4664
  143. package/dist/languages/legacy-bridge.d.ts +3 -2
  144. package/dist/languages/types.d.ts +2 -1
  145. package/dist/legacy-bridge-7M6QLUQN.js +18 -0
  146. package/dist/lint.js +10 -3706
  147. package/dist/logs.js +4 -287
  148. package/dist/memory.js +6 -215
  149. package/dist/mode.js +3 -99
  150. package/dist/outdated.js +4 -5170
  151. package/dist/pack.js +65 -33897
  152. package/dist/parser-dispatch-QH4XPGG6.js +14 -0
  153. package/dist/patch.js +13 -9935
  154. package/dist/plan.js +5 -3286
  155. package/dist/process-registry.js +8 -651
  156. package/dist/ps-slash.js +12 -1439
  157. package/dist/pwsh.d.ts +0 -1
  158. package/dist/py-parser-IS4HKYMF.js +16 -0
  159. package/dist/read.js +13 -9816
  160. package/dist/replace.js +15 -10014
  161. package/dist/rs-parser-XHUUO2KZ.js +81 -0
  162. package/dist/scaffold.js +4 -253
  163. package/dist/search.js +5 -585
  164. package/dist/session-kanban-sync.d.ts +0 -6
  165. package/dist/session-kanban.js +30 -972
  166. package/dist/skill.js +2 -215
  167. package/dist/spawn-background.d.ts +1 -1
  168. package/dist/task.js +5 -3410
  169. package/dist/test.js +10 -3773
  170. package/dist/todo.js +4 -2891
  171. package/dist/tool-help.js +3 -123
  172. package/dist/tool-search.js +3 -79
  173. package/dist/tool-tier.js +68 -33984
  174. package/dist/tool-use.js +2 -93
  175. package/dist/tree-sitter-parser-AIERW3YI.js +15 -0
  176. package/dist/tree.js +4 -288
  177. package/dist/ts-parser-RCTYKSVI.js +232 -0
  178. package/dist/typecheck.js +10 -3753
  179. package/dist/win32.js +6 -60
  180. package/dist/write.js +14 -9882
  181. package/dist/yaml-parser-4SAXV3PL.js +157 -0
  182. package/package.json +5 -5
@@ -0,0 +1,169 @@
1
+ // src/_fetch-guard.ts
2
+ import * as dns from "node:dns/promises";
3
+ import * as net from "node:net";
4
+ import { isPrivateIPv4, isPrivateIPv6 } from "@wrongstack/core/utils";
5
+ import { FetchError, ToolValidationError } from "@wrongstack/core/types";
6
+ import { Agent, fetch as undiciFetch } from "undici";
7
+ var nativeGlobalFetch = globalThis.fetch;
8
+ var ALLOW_PRIVATE = process.env["WRONGSTACK_FETCH_ALLOW_PRIVATE"] === "1";
9
+ if (ALLOW_PRIVATE && !process.env["CI"]) {
10
+ console.warn(
11
+ "[WrongStack] WARNING: WRONGSTACK_FETCH_ALLOW_PRIVATE=1 is active \u2014\n fetch tool can now access private IPs (10.x, 192.168.x, 169.254.x),\n cloud metadata endpoints, and plaintext HTTP. Use only on isolated networks."
12
+ );
13
+ }
14
+ function guardedLookup(hostname, options, callback) {
15
+ dns.lookup(hostname, { all: true }).then((records) => {
16
+ const family = options?.family;
17
+ const byFamily = family === 4 || family === 6 ? records.filter((r) => r.family === family) : records;
18
+ const list = byFamily.length > 0 ? byFamily : records;
19
+ if (!ALLOW_PRIVATE) {
20
+ for (const r of list) {
21
+ const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
22
+ if (bad) {
23
+ callback(
24
+ Object.assign(new Error(`fetch: resolved to private address ${r.address}`), {
25
+ code: "EAI_FAIL"
26
+ })
27
+ );
28
+ return;
29
+ }
30
+ }
31
+ }
32
+ if (options?.all) {
33
+ callback(
34
+ null,
35
+ list.map((r) => ({ address: r.address, family: r.family }))
36
+ );
37
+ return;
38
+ }
39
+ const first = list.at(0);
40
+ if (!first) {
41
+ callback(
42
+ Object.assign(new Error(`fetch: no address for ${hostname}`), { code: "ENOTFOUND" })
43
+ );
44
+ return;
45
+ }
46
+ callback(null, first.address, first.family);
47
+ }).catch((err) => callback(err));
48
+ }
49
+ var pinnedAgent;
50
+ function getPinnedDispatcher() {
51
+ if (!pinnedAgent) {
52
+ pinnedAgent = new Agent({ allowH2: false, connect: { lookup: guardedLookup } });
53
+ }
54
+ return pinnedAgent;
55
+ }
56
+ function dispatcherFetch() {
57
+ return globalThis.fetch === nativeGlobalFetch ? undiciFetch : globalThis.fetch;
58
+ }
59
+ var _beforeExitRegistered = false;
60
+ if (!_beforeExitRegistered) {
61
+ _beforeExitRegistered = true;
62
+ process.on("beforeExit", () => {
63
+ pinnedAgent?.destroy();
64
+ pinnedAgent = void 0;
65
+ });
66
+ }
67
+ async function guardedFetch(url, maxRedirects, signal, headers = {
68
+ "user-agent": "WrongStack/1.0 (+https://wrongstack.com)",
69
+ accept: "text/html,application/json;q=0.9,text/plain;q=0.8,*/*;q=0.1"
70
+ }) {
71
+ let redirectCount = 0;
72
+ let currentUrl = url;
73
+ for (; ; ) {
74
+ const parsed = new URL(currentUrl);
75
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
76
+ throw new ToolValidationError({
77
+ message: `fetch: redirect to unsupported protocol "${parsed.protocol}"`,
78
+ field: "url"
79
+ });
80
+ }
81
+ if (parsed.protocol === "http:" && !ALLOW_PRIVATE) {
82
+ throw new ToolValidationError({
83
+ message: "fetch: redirect to http:// blocked (HTTPS required by default)",
84
+ field: "url"
85
+ });
86
+ }
87
+ await assertNotPrivate(parsed.hostname);
88
+ const init = {
89
+ redirect: "manual",
90
+ signal,
91
+ headers,
92
+ dispatcher: getPinnedDispatcher()
93
+ };
94
+ const res = await dispatcherFetch()(currentUrl, init);
95
+ if (res.status < 300 || res.status > 399) {
96
+ return res;
97
+ }
98
+ try {
99
+ await res.body?.cancel();
100
+ } catch {
101
+ }
102
+ redirectCount++;
103
+ if (redirectCount > maxRedirects) {
104
+ throw new FetchError({
105
+ message: `fetch: exceeded ${maxRedirects} redirects`,
106
+ status: res.status,
107
+ context: { url: currentUrl, maxRedirects, redirectCount }
108
+ });
109
+ }
110
+ const location = res.headers.get("location");
111
+ if (!location) {
112
+ throw new FetchError({
113
+ message: "fetch: redirect status with no location header",
114
+ status: res.status,
115
+ context: { url: currentUrl, redirectCount }
116
+ });
117
+ }
118
+ currentUrl = new URL(location, currentUrl).toString();
119
+ }
120
+ }
121
+ async function assertNotPrivate(hostname) {
122
+ if (ALLOW_PRIVATE) return;
123
+ const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
124
+ if (host === "localhost" || host.endsWith(".localhost")) {
125
+ throw new ToolValidationError({
126
+ message: "fetch: blocked localhost target",
127
+ field: "url"
128
+ });
129
+ }
130
+ const ipVersion = net.isIP(host);
131
+ if (ipVersion === 4) {
132
+ if (isPrivateIPv4(host)) {
133
+ throw new ToolValidationError({
134
+ message: `fetch: blocked private/loopback address "${host}"`,
135
+ field: "url"
136
+ });
137
+ }
138
+ } else if (ipVersion === 6) {
139
+ if (isPrivateIPv6(host)) {
140
+ throw new ToolValidationError({
141
+ message: `fetch: blocked private/loopback address "${host}"`,
142
+ field: "url"
143
+ });
144
+ }
145
+ } else {
146
+ try {
147
+ const records = await dns.lookup(host, { all: true });
148
+ for (const r of records) {
149
+ const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
150
+ if (bad) {
151
+ throw new ToolValidationError({
152
+ message: `fetch: resolved to private address ${r.address}`,
153
+ field: "url"
154
+ });
155
+ }
156
+ }
157
+ } catch (err) {
158
+ if (err instanceof ToolValidationError) throw err;
159
+ }
160
+ }
161
+ }
162
+
163
+ export {
164
+ ALLOW_PRIVATE,
165
+ guardedLookup,
166
+ guardedFetch,
167
+ assertNotPrivate
168
+ };
169
+ //# sourceMappingURL=chunk-NBDZMMG2.js.map
@@ -0,0 +1,366 @@
1
+ import {
2
+ todoTool
3
+ } from "./chunk-7EY5DCRO.js";
4
+ import {
5
+ projectSessionPlanToKanban
6
+ } from "./chunk-PY2IOS7C.js";
7
+
8
+ // src/plan.ts
9
+ import { randomUUID } from "node:crypto";
10
+ import {
11
+ addPlanItem,
12
+ clearPlan,
13
+ deriveTodosFromPlanItem,
14
+ formatPlan,
15
+ getPlanTemplate,
16
+ mutatePlan,
17
+ mutateTasks,
18
+ removePlanItem,
19
+ setPlanItemStatus
20
+ } from "@wrongstack/core/storage";
21
+ import { formatTaskList } from "@wrongstack/core/utils";
22
+ var planTool = {
23
+ name: "plan",
24
+ category: "Session",
25
+ description: 'Manage a session-persistent strategic plan. The plan is written to disk and survives conversation resumptions within the same session, but is isolated to this session \u2014 other sessions have their own separate plans. Unlike todos (which are per-turn and lost on restart), a plan tracks high-level progress across multiple turns. Use this to outline big-picture work, then promote concrete items into the todo list when ready to execute. By default plans are isolated to this session; use `scope: "project"` to store the plan in a shared project-level file visible to all sessions.',
26
+ usageHint: 'RECOMMENDED FOR COMPLEX, MULTI-PHASE WORK:\n\n- Start by creating a high-level plan with `action: "add"` or using templates (`template_use`).\n- Use `action: "status"` with `open`, `in_progress`, or `done` for exact status changes.\n- Use `promote` to turn a plan item into actionable todos.\n- Use `taskify` to convert a plan item into a structured task (with type/priority/deps).\n- Unfinished plan items cannot be removed or cleared; complete them first.\n- Keep plans at the "why and what" level, and todos at the "how and next step" level.\n- Common templates: "new-feature", "bug-fix", "refactor", "release", "security-audit".\n\nThis tool is excellent for maintaining long-term direction across many turns within a session. Plans survive resume but are not shared across separate sessions.\nUse `scope: "project"` to use a shared project-level plan file.',
27
+ permission: "confirm",
28
+ // WS-046: gives permission decisions something to key on.
29
+ // The action performed; plan has no single file or path subject.
30
+ subjectKey: "action",
31
+ mutating: true,
32
+ capabilities: ["fs.write"],
33
+ icon: "plan",
34
+ timeoutMs: 3e4,
35
+ inputSchema: {
36
+ type: "object",
37
+ properties: {
38
+ action: {
39
+ type: "string",
40
+ enum: [
41
+ "show",
42
+ "add",
43
+ "status",
44
+ "start",
45
+ "done",
46
+ "remove",
47
+ "promote",
48
+ "template_use",
49
+ "clear",
50
+ "taskify"
51
+ ],
52
+ description: "The operation to perform on the plan board."
53
+ },
54
+ title: {
55
+ type: "string",
56
+ description: "Title of the plan item. Required for action=add."
57
+ },
58
+ details: {
59
+ type: "string",
60
+ description: "Additional details or description for a new plan item (action=add)."
61
+ },
62
+ target: {
63
+ type: "string",
64
+ description: "Identifier for the target plan item (id, 1-based index, or partial title). Required for most actions except add/show/clear."
65
+ },
66
+ status: {
67
+ type: "string",
68
+ enum: ["open", "in_progress", "done"],
69
+ description: "Exact plan item status for action=status."
70
+ },
71
+ subtasks: {
72
+ type: "array",
73
+ items: { type: "string" },
74
+ description: "List of subtask titles. Used with promote to break a plan item into multiple todos."
75
+ },
76
+ template: {
77
+ type: "string",
78
+ description: "Template identifier when using action=template_use. Common values: new-feature, bug-fix, refactor, release, security-audit."
79
+ },
80
+ scope: {
81
+ type: "string",
82
+ enum: ["session", "project"],
83
+ description: 'Storage scope: "session" (default, isolated to this session) or "project" (shared across all sessions for this project).'
84
+ }
85
+ },
86
+ required: ["action"]
87
+ },
88
+ async execute(input, ctx) {
89
+ const sessionPlanPath = ctx.meta["plan.path"];
90
+ let planPath;
91
+ if (input.scope === "project") {
92
+ if (typeof sessionPlanPath === "string") {
93
+ const lastSep = Math.max(
94
+ sessionPlanPath.lastIndexOf("/"),
95
+ sessionPlanPath.lastIndexOf("\\")
96
+ );
97
+ if (lastSep < 0) {
98
+ return {
99
+ ok: false,
100
+ message: `Cannot derive the project-scoped plan path: session plan path "${sessionPlanPath}" has no directory component.`,
101
+ plan: "",
102
+ count: 0,
103
+ open: 0
104
+ };
105
+ }
106
+ planPath = sessionPlanPath.slice(0, lastSep + 1) + "backlog.plan.json";
107
+ }
108
+ } else {
109
+ planPath = sessionPlanPath;
110
+ }
111
+ if (typeof planPath !== "string" || !planPath) {
112
+ return {
113
+ ok: false,
114
+ message: "Plan storage path is not configured for this session.",
115
+ plan: "",
116
+ count: 0,
117
+ open: 0
118
+ };
119
+ }
120
+ const sessionId = ctx.session?.id ?? "unknown";
121
+ let early = null;
122
+ const taskifyMeta = { title: "", details: "" };
123
+ let didTaskify = false;
124
+ let todosToReplace = null;
125
+ let plan;
126
+ try {
127
+ plan = await mutatePlan(planPath, sessionId, async (p) => {
128
+ switch (input.action) {
129
+ case "show":
130
+ break;
131
+ case "add": {
132
+ const title = input.title?.trim();
133
+ if (!title) {
134
+ early = mkResult(p, false, "add requires `title`.");
135
+ return p;
136
+ }
137
+ const { plan: updated } = addPlanItem(p, title, input.details?.trim() || void 0);
138
+ return updated;
139
+ }
140
+ case "start":
141
+ case "done": {
142
+ if (!input.target) {
143
+ early = mkResult(
144
+ p,
145
+ false,
146
+ `${input.action} requires \`target\` (id|index|substring).`
147
+ );
148
+ return p;
149
+ }
150
+ const next = setPlanItemStatus(
151
+ p,
152
+ input.target,
153
+ input.action === "start" ? "in_progress" : "done"
154
+ );
155
+ if (next === p) {
156
+ early = mkResult(p, false, `No plan item matched "${input.target}".`);
157
+ return p;
158
+ }
159
+ return next;
160
+ }
161
+ case "status": {
162
+ if (!input.target || !input.status) {
163
+ early = mkResult(
164
+ p,
165
+ false,
166
+ "status requires `target` (id|index|substring) and `status`."
167
+ );
168
+ return p;
169
+ }
170
+ const next = setPlanItemStatus(p, input.target, input.status);
171
+ if (next === p) {
172
+ early = mkResult(p, false, `No plan item matched "${input.target}".`);
173
+ return p;
174
+ }
175
+ return next;
176
+ }
177
+ case "remove": {
178
+ if (!input.target) {
179
+ early = mkResult(p, false, "remove requires `target` (id|index|substring).");
180
+ return p;
181
+ }
182
+ const next = removePlanItem(p, input.target);
183
+ if (next === p) {
184
+ early = mkResult(p, false, `No plan item matched "${input.target}".`);
185
+ return p;
186
+ }
187
+ const nextIds = new Set(next.items.map((item) => item.id));
188
+ const removed = p.items.find((item) => !nextIds.has(item.id));
189
+ if (removed?.status !== "done") {
190
+ early = mkResult(
191
+ p,
192
+ false,
193
+ `Plan item "${removed?.title ?? input.target}" is not done and cannot be removed. Complete it first.`
194
+ );
195
+ return p;
196
+ }
197
+ return next;
198
+ }
199
+ case "promote": {
200
+ if (!input.target) {
201
+ early = mkResult(
202
+ p,
203
+ false,
204
+ `${input.action} requires \`target\` (id|index|substring).`
205
+ );
206
+ return p;
207
+ }
208
+ const derived = deriveTodosFromPlanItem(p, input.target, input.subtasks);
209
+ if (!derived) {
210
+ early = mkResult(p, false, `No plan item matched "${input.target}".`);
211
+ return p;
212
+ }
213
+ todosToReplace = derived.todos;
214
+ early = mkResult(
215
+ derived.plan,
216
+ true,
217
+ `${input.action} ok \u2014 ${derived.todos.length} todo(s) created.`,
218
+ derived.todos
219
+ );
220
+ return derived.plan;
221
+ }
222
+ case "template_use": {
223
+ const templateName = input.template?.trim();
224
+ if (!templateName) {
225
+ early = mkResult(p, false, "template_use requires `template` name.");
226
+ return p;
227
+ }
228
+ const template = getPlanTemplate(templateName);
229
+ if (!template) {
230
+ early = mkResult(p, false, `Unknown template "${templateName}".`);
231
+ return p;
232
+ }
233
+ let updated = p;
234
+ for (const item of template.items) {
235
+ ({ plan: updated } = addPlanItem(updated, item.title, item.details));
236
+ }
237
+ early = mkResult(
238
+ updated,
239
+ true,
240
+ `Applied template "${template.name}" \u2014 ${template.items.length} items added.`
241
+ );
242
+ return updated;
243
+ }
244
+ case "clear":
245
+ if (p.items.some((item) => item.status !== "done")) {
246
+ early = mkResult(
247
+ p,
248
+ false,
249
+ "Plan contains unfinished items and cannot be cleared. Complete them first."
250
+ );
251
+ return p;
252
+ }
253
+ return clearPlan(p);
254
+ case "taskify": {
255
+ if (!input.target) {
256
+ early = mkResult(
257
+ p,
258
+ false,
259
+ "taskify requires `target` (plan item id|index|substring)."
260
+ );
261
+ return p;
262
+ }
263
+ let itemIdx = -1;
264
+ const asNum = Number.parseInt(input.target, 10);
265
+ if (!Number.isNaN(asNum) && asNum >= 1 && asNum <= p.items.length) {
266
+ itemIdx = asNum - 1;
267
+ } else {
268
+ itemIdx = p.items.findIndex((it) => it.id === input.target);
269
+ if (itemIdx === -1) {
270
+ const lower = input.target.toLowerCase();
271
+ itemIdx = p.items.findIndex((it) => it.title.toLowerCase().includes(lower));
272
+ }
273
+ }
274
+ if (itemIdx === -1 || !p.items[itemIdx]) {
275
+ early = mkResult(p, false, `No plan item matched "${input.target}".`);
276
+ return p;
277
+ }
278
+ const item = p.items[itemIdx];
279
+ taskifyMeta.title = item.title;
280
+ taskifyMeta.details = item.details ?? "";
281
+ didTaskify = true;
282
+ break;
283
+ }
284
+ default:
285
+ early = mkResult(p, false, `Unknown action "${input.action}".`);
286
+ return p;
287
+ }
288
+ return p;
289
+ });
290
+ } catch (err) {
291
+ return {
292
+ ok: false,
293
+ message: `Plan change not saved \u2014 ${err instanceof Error ? err.message : String(err)}`,
294
+ plan: "",
295
+ count: 0,
296
+ open: 0
297
+ };
298
+ }
299
+ ctx.meta["plan.path.resolved"] = planPath;
300
+ await projectSessionPlanToKanban(ctx.projectRoot, plan.items, sessionId);
301
+ if (todosToReplace) {
302
+ await todoTool.execute({ todos: todosToReplace }, ctx, {
303
+ signal: AbortSignal.timeout(3e4)
304
+ });
305
+ }
306
+ if (early) return early;
307
+ if (didTaskify) {
308
+ const taskPathRaw = ctx.meta["task.path"];
309
+ if (typeof taskPathRaw !== "string" || !taskPathRaw) {
310
+ return mkResult(plan, false, "Task storage path not configured \u2014 cannot taskify.");
311
+ }
312
+ let taskPath = taskPathRaw;
313
+ if (input.scope === "project") {
314
+ const lastSep = Math.max(taskPath.lastIndexOf("/"), taskPath.lastIndexOf("\\"));
315
+ taskPath = lastSep >= 0 ? taskPath.slice(0, lastSep + 1) + "backlog.tasks.json" : "backlog.tasks.json";
316
+ }
317
+ const now = (/* @__PURE__ */ new Date()).toISOString();
318
+ try {
319
+ const taskFile = await mutateTasks(taskPath, sessionId, (f) => {
320
+ f.tasks.push({
321
+ id: `task_${randomUUID()}`,
322
+ title: taskifyMeta.title,
323
+ description: taskifyMeta.details || void 0,
324
+ type: "feature",
325
+ priority: "medium",
326
+ status: "pending",
327
+ createdAt: now,
328
+ updatedAt: now
329
+ });
330
+ return f;
331
+ });
332
+ ctx.meta["task.path.resolved"] = taskPath;
333
+ return mkResult(
334
+ plan,
335
+ true,
336
+ `taskify ok \u2014 added "${taskifyMeta.title}" to tasks.
337
+ ${formatTaskList(taskFile.tasks)}`
338
+ );
339
+ } catch (err) {
340
+ return mkResult(
341
+ plan,
342
+ false,
343
+ `taskify: task not saved \u2014 ${err instanceof Error ? err.message : String(err)}`
344
+ );
345
+ }
346
+ }
347
+ return mkResult(plan, true, `Plan ${input.action} ok.`);
348
+ }
349
+ };
350
+ function mkResult(plan, ok, message, todos) {
351
+ const open = plan.items.filter((i) => i.status !== "done").length;
352
+ const result = {
353
+ ok,
354
+ message,
355
+ plan: formatPlan(plan),
356
+ count: plan.items.length,
357
+ open
358
+ };
359
+ if (todos !== void 0) result.todos = todos;
360
+ return result;
361
+ }
362
+
363
+ export {
364
+ planTool
365
+ };
366
+ //# sourceMappingURL=chunk-NKJW5GFO.js.map