@wrongstack/tools 0.297.0 → 0.298.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.
- package/dist/_spawn-stream.d.ts +2 -0
- package/dist/_util.d.ts +29 -1
- package/dist/audit.js +113 -65
- package/dist/bash.js +5 -4
- package/dist/batch-tool-use.js +70 -7
- package/dist/browser/index.js +31 -1
- package/dist/builtin.js +841 -351
- package/dist/codebase-index/index.js +68 -16
- package/dist/codebase-index/project-server-protocol.d.ts +26 -0
- package/dist/codebase-index/project-server.js +60 -16
- package/dist/codebase-index/worker.js +36 -13
- package/dist/codebase-index/writer-search-helpers.d.ts +16 -0
- package/dist/codebase-index/writer.d.ts +13 -1
- package/dist/design.d.ts +1 -1
- package/dist/design.js +4 -3
- package/dist/diff.js +11 -1
- package/dist/e2e.js +9 -6
- package/dist/edit.js +9 -6
- package/dist/exec.js +560 -529
- package/dist/format.js +116 -65
- package/dist/git.js +22 -2
- package/dist/glob.js +11 -5
- package/dist/grep.js +160 -104
- package/dist/index.js +792 -290
- package/dist/install.js +120 -68
- package/dist/json.js +64 -18
- package/dist/kanban-board-inputs.d.ts +21 -4
- package/dist/kanban.js +20 -5
- package/dist/languages/index.js +128 -71
- package/dist/lint.js +113 -65
- package/dist/outdated.js +86 -26
- package/dist/pack.js +841 -351
- package/dist/patch.js +11 -1
- package/dist/plan.js +32 -10
- package/dist/read.js +77 -23
- package/dist/replace.js +11 -3
- package/dist/scaffold.js +9 -6
- package/dist/session-kanban.js +21 -5
- package/dist/task.js +3 -1
- package/dist/test.js +113 -65
- package/dist/todo.js +3 -1
- package/dist/tool-tier.js +841 -351
- package/dist/tool-use.d.ts +1 -1
- package/dist/tool-use.js +28 -4
- package/dist/tree.d.ts +1 -0
- package/dist/tree.js +44 -8
- package/dist/typecheck.js +113 -65
- package/dist/write.js +9 -6
- package/package.json +5 -4
- package/dist/_concurrency.d.ts.map +0 -1
- package/dist/_danger-detect.d.ts.map +0 -1
- package/dist/_edit-match.d.ts.map +0 -1
- package/dist/_env.d.ts.map +0 -1
- package/dist/_fetch-guard.d.ts.map +0 -1
- package/dist/_output-spool.d.ts.map +0 -1
- package/dist/_redact-command.d.ts.map +0 -1
- package/dist/_regex.d.ts.map +0 -1
- package/dist/_session-shell.d.ts.map +0 -1
- package/dist/_shell-pick.d.ts.map +0 -1
- package/dist/_spawn-stream.d.ts.map +0 -1
- package/dist/_syntax-check.d.ts.map +0 -1
- package/dist/_util.d.ts.map +0 -1
- package/dist/_win32-resolve.d.ts.map +0 -1
- package/dist/audit.d.ts.map +0 -1
- package/dist/audit.js.map +0 -7
- package/dist/auto-proceed-loop-guard.d.ts.map +0 -1
- package/dist/auto-proceed-loop-guard.js.map +0 -7
- package/dist/bash-kill-guard.d.ts.map +0 -1
- package/dist/bash.d.ts.map +0 -1
- package/dist/bash.js.map +0 -7
- package/dist/batch-tool-use.d.ts.map +0 -1
- package/dist/batch-tool-use.js.map +0 -7
- package/dist/browser/artifacts.d.ts.map +0 -1
- package/dist/browser/index.d.ts.map +0 -1
- package/dist/browser/index.js.map +0 -7
- package/dist/browser/manager.d.ts.map +0 -1
- package/dist/browser/network-guard-proxy.d.ts.map +0 -1
- package/dist/browser/security.d.ts.map +0 -1
- package/dist/browser/tools.d.ts.map +0 -1
- package/dist/browser/types.d.ts.map +0 -1
- package/dist/builtin.d.ts.map +0 -1
- package/dist/builtin.js.map +0 -7
- package/dist/circuit-breaker.d.ts.map +0 -1
- package/dist/circuit-breaker.js.map +0 -7
- package/dist/codebase-index/background-indexer.d.ts.map +0 -1
- package/dist/codebase-index/bm25.d.ts.map +0 -1
- package/dist/codebase-index/circuit-breaker.d.ts.map +0 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +0 -1
- package/dist/codebase-index/codebase-search-tool.d.ts.map +0 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +0 -1
- package/dist/codebase-index/dead-code-scan.d.ts.map +0 -1
- package/dist/codebase-index/generic-parser.d.ts.map +0 -1
- package/dist/codebase-index/gitignore.d.ts.map +0 -1
- package/dist/codebase-index/go-parser.d.ts.map +0 -1
- package/dist/codebase-index/index-service.d.ts.map +0 -1
- package/dist/codebase-index/index.d.ts.map +0 -1
- package/dist/codebase-index/index.js.map +0 -7
- package/dist/codebase-index/indexer.d.ts.map +0 -1
- package/dist/codebase-index/json-parser.d.ts.map +0 -1
- package/dist/codebase-index/languages.d.ts.map +0 -1
- package/dist/codebase-index/lsp-kind.d.ts.map +0 -1
- package/dist/codebase-index/parser-dispatch.d.ts.map +0 -1
- package/dist/codebase-index/project-server-cache.d.ts.map +0 -1
- package/dist/codebase-index/project-server-client.d.ts.map +0 -1
- package/dist/codebase-index/project-server-endpoint.d.ts.map +0 -1
- package/dist/codebase-index/project-server-protocol.d.ts.map +0 -1
- package/dist/codebase-index/project-server.d.ts.map +0 -1
- package/dist/codebase-index/project-server.js.map +0 -7
- package/dist/codebase-index/py-parser.d.ts.map +0 -1
- package/dist/codebase-index/refs-extractor.d.ts.map +0 -1
- package/dist/codebase-index/rs-parser.d.ts.map +0 -1
- package/dist/codebase-index/schema.d.ts.map +0 -1
- package/dist/codebase-index/spawn-gate.d.ts.map +0 -1
- package/dist/codebase-index/sqlite-runtime.d.ts.map +0 -1
- package/dist/codebase-index/ts-parser.d.ts.map +0 -1
- package/dist/codebase-index/worker-protocol.d.ts.map +0 -1
- package/dist/codebase-index/worker.d.ts.map +0 -1
- package/dist/codebase-index/worker.js.map +0 -7
- package/dist/codebase-index/writer-admin.d.ts.map +0 -1
- package/dist/codebase-index/writer-bulk-insert.d.ts.map +0 -1
- package/dist/codebase-index/writer-graph-helpers.d.ts.map +0 -1
- package/dist/codebase-index/writer-graph-reader.d.ts.map +0 -1
- package/dist/codebase-index/writer-helpers.d.ts.map +0 -1
- package/dist/codebase-index/writer-pragmas.d.ts.map +0 -1
- package/dist/codebase-index/writer-ref-mapper.d.ts.map +0 -1
- package/dist/codebase-index/writer-schema.d.ts.map +0 -1
- package/dist/codebase-index/writer-search-helpers.d.ts.map +0 -1
- package/dist/codebase-index/writer-store-pool.d.ts.map +0 -1
- package/dist/codebase-index/writer.d.ts.map +0 -1
- package/dist/codebase-index/yaml-parser.d.ts.map +0 -1
- package/dist/design.d.ts.map +0 -1
- package/dist/design.js.map +0 -7
- package/dist/diff.d.ts.map +0 -1
- package/dist/diff.js.map +0 -7
- package/dist/document.d.ts.map +0 -1
- package/dist/document.js.map +0 -7
- package/dist/e2e.d.ts.map +0 -1
- package/dist/e2e.js.map +0 -7
- package/dist/edit.d.ts.map +0 -1
- package/dist/edit.js.map +0 -7
- package/dist/exec-kill-guard.d.ts.map +0 -1
- package/dist/exec.d.ts.map +0 -1
- package/dist/exec.js.map +0 -7
- package/dist/fetch.d.ts.map +0 -1
- package/dist/fetch.js.map +0 -7
- package/dist/format.d.ts.map +0 -1
- package/dist/format.js.map +0 -7
- package/dist/git.d.ts.map +0 -1
- package/dist/git.js.map +0 -7
- package/dist/glob.d.ts.map +0 -1
- package/dist/glob.js.map +0 -7
- package/dist/grep.d.ts.map +0 -1
- package/dist/grep.js.map +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -7
- package/dist/install.d.ts.map +0 -1
- package/dist/install.js.map +0 -7
- package/dist/json.d.ts.map +0 -1
- package/dist/json.js.map +0 -7
- package/dist/kanban-board-inputs.d.ts.map +0 -1
- package/dist/kanban-decomposition-actions.d.ts.map +0 -1
- package/dist/kanban-detail-actions.d.ts.map +0 -1
- package/dist/kanban-evidence-bridge.d.ts.map +0 -1
- package/dist/kanban-presence.d.ts.map +0 -1
- package/dist/kanban-split-task-handler.d.ts.map +0 -1
- package/dist/kanban-task-inputs.d.ts.map +0 -1
- package/dist/kanban-tool-results.d.ts.map +0 -1
- package/dist/kanban-tool-schema.d.ts.map +0 -1
- package/dist/kanban-tool-types.d.ts.map +0 -1
- package/dist/kanban.d.ts.map +0 -1
- package/dist/kanban.js.map +0 -7
- package/dist/languages/detect.d.ts.map +0 -1
- package/dist/languages/diagnostics.d.ts.map +0 -1
- package/dist/languages/execute-tool.d.ts.map +0 -1
- package/dist/languages/execute.d.ts.map +0 -1
- package/dist/languages/index.d.ts.map +0 -1
- package/dist/languages/index.js.map +0 -7
- package/dist/languages/legacy-bridge.d.ts.map +0 -1
- package/dist/languages/package-tool.d.ts.map +0 -1
- package/dist/languages/plan.d.ts.map +0 -1
- package/dist/languages/profile-helpers.d.ts.map +0 -1
- package/dist/languages/profiles/additional.d.ts.map +0 -1
- package/dist/languages/profiles/primary.d.ts.map +0 -1
- package/dist/languages/registry.d.ts.map +0 -1
- package/dist/languages/tool.d.ts.map +0 -1
- package/dist/languages/types.d.ts.map +0 -1
- package/dist/lint.d.ts.map +0 -1
- package/dist/lint.js.map +0 -7
- package/dist/logs.d.ts.map +0 -1
- package/dist/logs.js.map +0 -7
- package/dist/memory.d.ts.map +0 -1
- package/dist/memory.js.map +0 -7
- package/dist/mode.d.ts.map +0 -1
- package/dist/mode.js.map +0 -7
- package/dist/next-steps.d.ts.map +0 -1
- package/dist/next-steps.js.map +0 -7
- package/dist/outdated.d.ts.map +0 -1
- package/dist/outdated.js.map +0 -7
- package/dist/pack.d.ts.map +0 -1
- package/dist/pack.js.map +0 -7
- package/dist/patch.d.ts.map +0 -1
- package/dist/patch.js.map +0 -7
- package/dist/plan.d.ts.map +0 -1
- package/dist/plan.js.map +0 -7
- package/dist/process-guardian.d.ts.map +0 -1
- package/dist/process-registry-persistent.d.ts.map +0 -1
- package/dist/process-registry.d.ts.map +0 -1
- package/dist/process-registry.js.map +0 -7
- package/dist/ps-slash.d.ts.map +0 -1
- package/dist/ps-slash.js.map +0 -7
- package/dist/read.d.ts.map +0 -1
- package/dist/read.js.map +0 -7
- package/dist/replace.d.ts.map +0 -1
- package/dist/replace.js.map +0 -7
- package/dist/scaffold.d.ts.map +0 -1
- package/dist/scaffold.js.map +0 -7
- package/dist/search.d.ts.map +0 -1
- package/dist/search.js.map +0 -7
- package/dist/session-kanban.d.ts.map +0 -1
- package/dist/session-kanban.js.map +0 -7
- package/dist/set-working-dir.d.ts.map +0 -1
- package/dist/skill.d.ts.map +0 -1
- package/dist/skill.js.map +0 -7
- package/dist/spawn-background.d.ts.map +0 -1
- package/dist/task.d.ts.map +0 -1
- package/dist/task.js.map +0 -7
- package/dist/test.d.ts.map +0 -1
- package/dist/test.js.map +0 -7
- package/dist/todo.d.ts.map +0 -1
- package/dist/todo.js.map +0 -7
- package/dist/tool-diff.d.ts.map +0 -1
- package/dist/tool-diff.js.map +0 -7
- package/dist/tool-help.d.ts.map +0 -1
- package/dist/tool-help.js.map +0 -7
- package/dist/tool-icon-map.d.ts.map +0 -1
- package/dist/tool-icons.d.ts.map +0 -1
- package/dist/tool-icons.js.map +0 -7
- package/dist/tool-search.d.ts.map +0 -1
- package/dist/tool-search.js.map +0 -7
- package/dist/tool-summary.d.ts.map +0 -1
- package/dist/tool-summary.js.map +0 -7
- package/dist/tool-tier.d.ts.map +0 -1
- package/dist/tool-tier.js.map +0 -7
- package/dist/tool-use.d.ts.map +0 -1
- package/dist/tool-use.js.map +0 -7
- package/dist/tree.d.ts.map +0 -1
- package/dist/tree.js.map +0 -7
- package/dist/typecheck.d.ts.map +0 -1
- package/dist/typecheck.js.map +0 -7
- package/dist/win32.d.ts.map +0 -1
- package/dist/win32.js.map +0 -7
- package/dist/write.d.ts.map +0 -1
- package/dist/write.js.map +0 -7
package/dist/patch.js
CHANGED
|
@@ -45,6 +45,10 @@ var patchTool = {
|
|
|
45
45
|
useInstead: ["edit"]
|
|
46
46
|
},
|
|
47
47
|
permission: "confirm",
|
|
48
|
+
// WS-046: gives permission decisions something to key on.
|
|
49
|
+
// The target directory. The patch body itself is unstable (every diff
|
|
50
|
+
// differs), so it could never re-match its own stored rule.
|
|
51
|
+
subjectKey: "directory",
|
|
48
52
|
mutating: true,
|
|
49
53
|
capabilities: ["fs.write"],
|
|
50
54
|
icon: "edit",
|
|
@@ -168,7 +172,13 @@ function runPatch(args, cwd, signal) {
|
|
|
168
172
|
let stdout = "";
|
|
169
173
|
let stderr = "";
|
|
170
174
|
const env = { ...buildChildEnv(), LANG: "C", LC_ALL: "C" };
|
|
171
|
-
const child = spawn("patch", args, {
|
|
175
|
+
const child = spawn("patch", args, {
|
|
176
|
+
cwd,
|
|
177
|
+
signal,
|
|
178
|
+
env,
|
|
179
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
180
|
+
windowsHide: true
|
|
181
|
+
});
|
|
172
182
|
child.stdout?.on("data", (c) => {
|
|
173
183
|
stdout += c.toString();
|
|
174
184
|
});
|
package/dist/plan.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/plan.ts
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import {
|
|
3
4
|
addPlanItem,
|
|
4
5
|
clearPlan,
|
|
@@ -6,14 +7,11 @@ import {
|
|
|
6
7
|
formatPlan,
|
|
7
8
|
getPlanTemplate,
|
|
8
9
|
mutatePlan as mutatePlan2,
|
|
10
|
+
mutateTasks as mutateTasks2,
|
|
9
11
|
removePlanItem,
|
|
10
12
|
setPlanItemStatus
|
|
11
13
|
} from "@wrongstack/core/storage";
|
|
12
|
-
import {
|
|
13
|
-
mutateTasks as mutateTasks2
|
|
14
|
-
} from "@wrongstack/core/storage";
|
|
15
14
|
import { formatTaskList } from "@wrongstack/core/utils";
|
|
16
|
-
import { randomUUID } from "node:crypto";
|
|
17
15
|
|
|
18
16
|
// src/session-kanban.ts
|
|
19
17
|
import { getSharedProjectMailbox } from "@wrongstack/core/coordination";
|
|
@@ -77,7 +75,9 @@ async function ensureSessionKanbanBoard(projectRoot, sessionId) {
|
|
|
77
75
|
description: "Live session work: todos, tasks, and plan items.",
|
|
78
76
|
tags: sessionBoardTags(sessionId),
|
|
79
77
|
columns: SESSION_KANBAN_COLUMNS,
|
|
80
|
-
generatedBy: `session-kanban:${sessionId}
|
|
78
|
+
generatedBy: `session-kanban:${sessionId}`,
|
|
79
|
+
kind: "session_mirror",
|
|
80
|
+
retention: { mode: "archive_after_ttl", ttlMs: 7 * 24 * 60 * 60 * 1e3 }
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
if (!sameColumns(board.columns) || !board.tags?.includes(SESSION_BOARD_TAG)) {
|
|
@@ -173,6 +173,9 @@ var planTool = {
|
|
|
173
173
|
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.',
|
|
174
174
|
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 `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- 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.',
|
|
175
175
|
permission: "confirm",
|
|
176
|
+
// WS-046: gives permission decisions something to key on.
|
|
177
|
+
// The action performed; plan has no single file or path subject.
|
|
178
|
+
subjectKey: "action",
|
|
176
179
|
mutating: true,
|
|
177
180
|
capabilities: ["fs.write"],
|
|
178
181
|
icon: "plan",
|
|
@@ -229,7 +232,10 @@ var planTool = {
|
|
|
229
232
|
let planPath;
|
|
230
233
|
if (input.scope === "project") {
|
|
231
234
|
if (typeof sessionPlanPath === "string") {
|
|
232
|
-
const lastSep = Math.max(
|
|
235
|
+
const lastSep = Math.max(
|
|
236
|
+
sessionPlanPath.lastIndexOf("/"),
|
|
237
|
+
sessionPlanPath.lastIndexOf("\\")
|
|
238
|
+
);
|
|
233
239
|
planPath = lastSep >= 0 ? sessionPlanPath.slice(0, lastSep + 1) + "backlog.plan.json" : "backlog.plan.json";
|
|
234
240
|
}
|
|
235
241
|
} else {
|
|
@@ -266,7 +272,11 @@ var planTool = {
|
|
|
266
272
|
case "start":
|
|
267
273
|
case "done": {
|
|
268
274
|
if (!input.target) {
|
|
269
|
-
early = mkResult(
|
|
275
|
+
early = mkResult(
|
|
276
|
+
p,
|
|
277
|
+
false,
|
|
278
|
+
`${input.action} requires \`target\` (id|index|substring).`
|
|
279
|
+
);
|
|
270
280
|
return p;
|
|
271
281
|
}
|
|
272
282
|
const next = setPlanItemStatus(
|
|
@@ -294,7 +304,11 @@ var planTool = {
|
|
|
294
304
|
}
|
|
295
305
|
case "promote": {
|
|
296
306
|
if (!input.target) {
|
|
297
|
-
early = mkResult(
|
|
307
|
+
early = mkResult(
|
|
308
|
+
p,
|
|
309
|
+
false,
|
|
310
|
+
`${input.action} requires \`target\` (id|index|substring).`
|
|
311
|
+
);
|
|
298
312
|
return p;
|
|
299
313
|
}
|
|
300
314
|
const derived = deriveTodosFromPlanItem(p, input.target, input.subtasks);
|
|
@@ -337,7 +351,11 @@ var planTool = {
|
|
|
337
351
|
return clearPlan(p);
|
|
338
352
|
case "taskify": {
|
|
339
353
|
if (!input.target) {
|
|
340
|
-
early = mkResult(
|
|
354
|
+
early = mkResult(
|
|
355
|
+
p,
|
|
356
|
+
false,
|
|
357
|
+
"taskify requires `target` (plan item id|index|substring)."
|
|
358
|
+
);
|
|
341
359
|
return p;
|
|
342
360
|
}
|
|
343
361
|
let itemIdx = -1;
|
|
@@ -410,7 +428,11 @@ var planTool = {
|
|
|
410
428
|
${formatTaskList(taskFile.tasks)}`
|
|
411
429
|
);
|
|
412
430
|
} catch (err) {
|
|
413
|
-
return mkResult(
|
|
431
|
+
return mkResult(
|
|
432
|
+
plan,
|
|
433
|
+
false,
|
|
434
|
+
`taskify: task not saved \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
435
|
+
);
|
|
414
436
|
}
|
|
415
437
|
}
|
|
416
438
|
return mkResult(plan, true, `Plan ${input.action} ok.`);
|
package/dist/read.js
CHANGED
|
@@ -1610,9 +1610,9 @@ function parseSymbols6(opts) {
|
|
|
1610
1610
|
function regexParse2(opts) {
|
|
1611
1611
|
const { file, content, lang } = opts;
|
|
1612
1612
|
const symbols = [];
|
|
1613
|
-
const
|
|
1614
|
-
const isPackageJson =
|
|
1615
|
-
const isTsconfig =
|
|
1613
|
+
const basename6 = path8.basename(file).toLowerCase();
|
|
1614
|
+
const isPackageJson = basename6 === "package.json";
|
|
1615
|
+
const isTsconfig = basename6 === "tsconfig.json" || basename6 === "tsconfig.build.json";
|
|
1616
1616
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
1617
1617
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
1618
1618
|
const lines = content.split("\n");
|
|
@@ -2001,12 +2001,13 @@ function ensureInsideRoot(absPath, ctx) {
|
|
|
2001
2001
|
function safeResolve(input, ctx) {
|
|
2002
2002
|
return ensureInsideRoot(resolvePath(input, ctx), ctx);
|
|
2003
2003
|
}
|
|
2004
|
-
async function
|
|
2005
|
-
if (ctx.allowOutsideProjectRoot) return;
|
|
2004
|
+
async function resolveRealInsideRoot(absPath, ctx) {
|
|
2005
|
+
if (ctx.allowOutsideProjectRoot) return absPath;
|
|
2006
2006
|
const realRoots = await Promise.all(
|
|
2007
2007
|
allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r)))
|
|
2008
2008
|
);
|
|
2009
2009
|
let probe2 = absPath;
|
|
2010
|
+
const pendingTail = [];
|
|
2010
2011
|
for (; ; ) {
|
|
2011
2012
|
let real;
|
|
2012
2013
|
try {
|
|
@@ -2014,13 +2015,16 @@ async function assertRealInsideRoot(absPath, ctx) {
|
|
|
2014
2015
|
} catch (err) {
|
|
2015
2016
|
if (err.code === "ENOENT") {
|
|
2016
2017
|
const parent = path.dirname(probe2);
|
|
2017
|
-
if (parent === probe2) return;
|
|
2018
|
+
if (parent === probe2) return absPath;
|
|
2019
|
+
pendingTail.unshift(path.basename(probe2));
|
|
2018
2020
|
probe2 = parent;
|
|
2019
2021
|
continue;
|
|
2020
2022
|
}
|
|
2021
2023
|
throw err;
|
|
2022
2024
|
}
|
|
2023
|
-
if (isInsideAny(real, realRoots))
|
|
2025
|
+
if (isInsideAny(real, realRoots)) {
|
|
2026
|
+
return pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
|
|
2027
|
+
}
|
|
2024
2028
|
throw new Error(
|
|
2025
2029
|
`Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
|
|
2026
2030
|
);
|
|
@@ -2028,8 +2032,7 @@ async function assertRealInsideRoot(absPath, ctx) {
|
|
|
2028
2032
|
}
|
|
2029
2033
|
async function safeResolveReal(input, ctx) {
|
|
2030
2034
|
const abs = safeResolve(input, ctx);
|
|
2031
|
-
await
|
|
2032
|
-
return abs;
|
|
2035
|
+
return await resolveRealInsideRoot(abs, ctx);
|
|
2033
2036
|
}
|
|
2034
2037
|
function isBinaryBuffer(buf) {
|
|
2035
2038
|
const len = Math.min(buf.length, 8192);
|
|
@@ -3006,6 +3009,7 @@ var REFS_INDEX_SQL = [
|
|
|
3006
3009
|
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
|
|
3007
3010
|
|
|
3008
3011
|
// src/codebase-index/writer-search-helpers.ts
|
|
3012
|
+
var SEARCH_CANDIDATE_SCAN_CAP = 5e3;
|
|
3009
3013
|
function normalizeSearchLimit(limit) {
|
|
3010
3014
|
return typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : void 0;
|
|
3011
3015
|
}
|
|
@@ -3035,8 +3039,8 @@ function buildWriterSearchWhere(query, filter) {
|
|
|
3035
3039
|
}
|
|
3036
3040
|
if (query.trim()) {
|
|
3037
3041
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
3038
|
-
conditions.push(`(${tokens.map(() => "text LIKE ?").join(" OR ")})`);
|
|
3039
|
-
for (const token of tokens) values.push(`%${token}%`);
|
|
3042
|
+
conditions.push(`(${tokens.map(() => "text LIKE ? ESCAPE '\\'").join(" OR ")})`);
|
|
3043
|
+
for (const token of tokens) values.push(`%${escapeLike(token)}%`);
|
|
3040
3044
|
}
|
|
3041
3045
|
return { where: conditions.length ? `WHERE ${conditions.join(" AND ")}` : "", values };
|
|
3042
3046
|
}
|
|
@@ -3145,6 +3149,7 @@ var StorePool = class {
|
|
|
3145
3149
|
|
|
3146
3150
|
// src/codebase-index/writer.ts
|
|
3147
3151
|
var DB_FILE2 = "index.db";
|
|
3152
|
+
var MAX_STATEMENT_CACHE = 128;
|
|
3148
3153
|
var IndexStore = class _IndexStore {
|
|
3149
3154
|
db;
|
|
3150
3155
|
/** Absolute path to this project's index directory. */
|
|
@@ -3182,12 +3187,31 @@ var IndexStore = class _IndexStore {
|
|
|
3182
3187
|
// Dirty on open so the first getOrBuildBm25() rebuilds against current rows;
|
|
3183
3188
|
// an empty or pre-existing corpus makes a stale IDF table meaningless.
|
|
3184
3189
|
bm25Dirty = true;
|
|
3185
|
-
/**
|
|
3190
|
+
/**
|
|
3191
|
+
* Prepare-once helper: compile `sql` on first use, reuse thereafter.
|
|
3192
|
+
*
|
|
3193
|
+
* Bounded LRU rather than an open Map. The cache is keyed by SQL TEXT, and
|
|
3194
|
+
* the fallback search builder emits one `text LIKE ?` clause per query token
|
|
3195
|
+
* — so the SQL varies with the token count and a stream of differently-sized
|
|
3196
|
+
* queries grew the cache without limit. Sage's store already bounds its
|
|
3197
|
+
* equivalent at 128 (WS-096).
|
|
3198
|
+
*
|
|
3199
|
+
* Re-inserting on a hit keeps the hot fixed-SQL statements (upsertFile,
|
|
3200
|
+
* insertRefs, …) at the young end, so a burst of one-off search SQL evicts
|
|
3201
|
+
* itself rather than the reindex hot path.
|
|
3202
|
+
*/
|
|
3186
3203
|
stmt(sql) {
|
|
3187
|
-
|
|
3188
|
-
if (
|
|
3189
|
-
|
|
3190
|
-
this.stmtCache.set(sql,
|
|
3204
|
+
const cached = this.stmtCache.get(sql);
|
|
3205
|
+
if (cached !== void 0) {
|
|
3206
|
+
this.stmtCache.delete(sql);
|
|
3207
|
+
this.stmtCache.set(sql, cached);
|
|
3208
|
+
return cached;
|
|
3209
|
+
}
|
|
3210
|
+
const s = this.db.prepare(sql);
|
|
3211
|
+
this.stmtCache.set(sql, s);
|
|
3212
|
+
if (this.stmtCache.size > MAX_STATEMENT_CACHE) {
|
|
3213
|
+
const oldest = this.stmtCache.keys().next();
|
|
3214
|
+
if (!oldest.done) this.stmtCache.delete(oldest.value);
|
|
3191
3215
|
}
|
|
3192
3216
|
return s;
|
|
3193
3217
|
}
|
|
@@ -3460,7 +3484,7 @@ var IndexStore = class _IndexStore {
|
|
|
3460
3484
|
const { where, values } = built;
|
|
3461
3485
|
const limit = normalizeSearchLimit(opts?.limit);
|
|
3462
3486
|
const limitSql = limit !== void 0 ? " LIMIT ?" : "";
|
|
3463
|
-
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment
|
|
3487
|
+
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment FROM symbols ${where}${limitSql}`;
|
|
3464
3488
|
const binds = limit !== void 0 ? [...values, limit] : values;
|
|
3465
3489
|
const rows = this.stmt(sql).all(
|
|
3466
3490
|
...binds
|
|
@@ -3589,11 +3613,13 @@ var IndexStore = class _IndexStore {
|
|
|
3589
3613
|
/** Legacy ranked path: LIKE candidates + in-process BM25 + JS snippets. */
|
|
3590
3614
|
searchRankedFallback(query, filter, limit) {
|
|
3591
3615
|
if (!query.trim()) {
|
|
3592
|
-
const
|
|
3593
|
-
if (
|
|
3594
|
-
return { results: this.search(query, filter, { limit }), total };
|
|
3616
|
+
const total2 = this.countSearch(query, filter);
|
|
3617
|
+
if (total2 === 0) return { results: [], total: 0 };
|
|
3618
|
+
return { results: this.search(query, filter, { limit }), total: total2 };
|
|
3595
3619
|
}
|
|
3596
|
-
const
|
|
3620
|
+
const total = this.countSearch(query, filter);
|
|
3621
|
+
if (total === 0) return { results: [], total: 0 };
|
|
3622
|
+
const candidates = this.search(query, filter, { limit: SEARCH_CANDIDATE_SCAN_CAP });
|
|
3597
3623
|
if (candidates.length === 0) return { results: [], total: 0 };
|
|
3598
3624
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
3599
3625
|
const bm25 = this.getOrBuildBm25();
|
|
@@ -3619,7 +3645,7 @@ var IndexStore = class _IndexStore {
|
|
|
3619
3645
|
const c = expectDefined4(candidateById.get(id));
|
|
3620
3646
|
return { ...c, score, snippet: bm25.extractSnippet(id, qTokens) };
|
|
3621
3647
|
});
|
|
3622
|
-
return { results, total
|
|
3648
|
+
return { results, total };
|
|
3623
3649
|
}
|
|
3624
3650
|
getAllIndexable() {
|
|
3625
3651
|
return getAllIndexableWithStatement((sql) => this.stmt(sql));
|
|
@@ -4726,6 +4752,8 @@ var ProjectServerConnection = class {
|
|
|
4726
4752
|
connectResolve = null;
|
|
4727
4753
|
connectReject = null;
|
|
4728
4754
|
nextId = 1;
|
|
4755
|
+
/** WS-027: read from the owner-only metadata file — see currentAuthToken. */
|
|
4756
|
+
authToken;
|
|
4729
4757
|
pending = /* @__PURE__ */ new Map();
|
|
4730
4758
|
transition(status, options = {}) {
|
|
4731
4759
|
const previous = connectionStates.get(this.endpoint);
|
|
@@ -4862,6 +4890,31 @@ var ProjectServerConnection = class {
|
|
|
4862
4890
|
this.transition("offline");
|
|
4863
4891
|
maybeStopHeartbeatLoop();
|
|
4864
4892
|
}
|
|
4893
|
+
/**
|
|
4894
|
+
* WS-027: the per-process token, read from the daemon's owner-only metadata
|
|
4895
|
+
* file — never from the `hello` frame, which the daemon sends to every
|
|
4896
|
+
* socket that connects (the mistake WS-028 found in the SAGE daemon).
|
|
4897
|
+
*
|
|
4898
|
+
* Read lazily and re-read while unknown: the daemon starts listening before
|
|
4899
|
+
* it writes metadata (endpoint ownership has to be won first), and a
|
|
4900
|
+
* respawned daemon mints a new token, so a cached wrong one would be sticky.
|
|
4901
|
+
*/
|
|
4902
|
+
currentAuthToken() {
|
|
4903
|
+
if (this.authToken === void 0) {
|
|
4904
|
+
try {
|
|
4905
|
+
const raw = fs10.readFileSync(
|
|
4906
|
+
projectIndexServerMetadataPath(this.projectRoot, this.indexDir),
|
|
4907
|
+
"utf8"
|
|
4908
|
+
);
|
|
4909
|
+
const parsed = JSON.parse(raw);
|
|
4910
|
+
if (typeof parsed.authToken === "string" && parsed.authToken.length > 0) {
|
|
4911
|
+
this.authToken = parsed.authToken;
|
|
4912
|
+
}
|
|
4913
|
+
} catch {
|
|
4914
|
+
}
|
|
4915
|
+
}
|
|
4916
|
+
return this.authToken;
|
|
4917
|
+
}
|
|
4865
4918
|
request(message, options) {
|
|
4866
4919
|
const socket = this.socket;
|
|
4867
4920
|
if (!socket || socket.destroyed) {
|
|
@@ -4905,7 +4958,7 @@ var ProjectServerConnection = class {
|
|
|
4905
4958
|
return;
|
|
4906
4959
|
}
|
|
4907
4960
|
}
|
|
4908
|
-
this.write({ ...message, id });
|
|
4961
|
+
this.write({ ...message, id, authToken: this.currentAuthToken() });
|
|
4909
4962
|
});
|
|
4910
4963
|
}
|
|
4911
4964
|
async ensureConnected(spawnIfMissing) {
|
|
@@ -4952,6 +5005,7 @@ var ProjectServerConnection = class {
|
|
|
4952
5005
|
connectOnce() {
|
|
4953
5006
|
this.socket?.destroy();
|
|
4954
5007
|
this.socket = null;
|
|
5008
|
+
this.authToken = void 0;
|
|
4955
5009
|
this.info = null;
|
|
4956
5010
|
this.activity = null;
|
|
4957
5011
|
this.health = null;
|
package/dist/replace.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// src/replace.ts
|
|
2
|
-
import { expectDefined } from "@wrongstack/core/utils";
|
|
3
2
|
import { spawn } from "node:child_process";
|
|
4
3
|
import * as fs from "node:fs/promises";
|
|
5
4
|
import * as path2 from "node:path";
|
|
5
|
+
import { ToolValidationError } from "@wrongstack/core/types";
|
|
6
6
|
import {
|
|
7
7
|
atomicWrite,
|
|
8
8
|
buildChildEnv,
|
|
9
9
|
compileGlob,
|
|
10
10
|
detectNewlineStyle,
|
|
11
|
+
expectDefined,
|
|
11
12
|
normalizeToLf,
|
|
12
13
|
toStyle,
|
|
13
14
|
unifiedDiff
|
|
14
15
|
} from "@wrongstack/core/utils";
|
|
15
|
-
import { ToolValidationError } from "@wrongstack/core/types";
|
|
16
16
|
|
|
17
17
|
// src/_regex.ts
|
|
18
18
|
var MAX_PATTERN_LEN = 256;
|
|
@@ -100,6 +100,10 @@ var replaceTool = {
|
|
|
100
100
|
description: "Perform a search-and-replace across multiple files using a regex pattern. This is a powerful bulk transformation tool. Dry-run is ON by default \u2014 set `dry_run: false` to apply changes.",
|
|
101
101
|
usageHint: "DANGEROUS IF USED CARELESSLY \u2014 review the diff output carefully.\n\nRecommended workflow:\n1. Run without `dry_run: false` first to see exactly what would change (dry-run is the default).\n2. Review the diff output, then re-run with `dry_run: false` to apply.\n3. Use a specific enough `pattern` (and `glob` / `files`) to avoid accidental broad changes.\n4. `replace_all` controls whether only the first match per file or all matches are replaced.\nThis tool is excellent for large-scale refactors (renaming, import updates, etc.) but must be used with caution.",
|
|
102
102
|
permission: "confirm",
|
|
103
|
+
// WS-046: gives permission decisions something to key on.
|
|
104
|
+
// The file scope being rewritten, not the pattern: a trust rule should say
|
|
105
|
+
// where a bulk replace may run.
|
|
106
|
+
subjectKey: "files",
|
|
103
107
|
mutating: true,
|
|
104
108
|
capabilities: ["fs.write"],
|
|
105
109
|
icon: "edit",
|
|
@@ -259,7 +263,11 @@ async function globFiles(pattern, base, extraGlob) {
|
|
|
259
263
|
function checkRg() {
|
|
260
264
|
return new Promise((resolve2) => {
|
|
261
265
|
try {
|
|
262
|
-
const p = spawn("rg", ["--version"], {
|
|
266
|
+
const p = spawn("rg", ["--version"], {
|
|
267
|
+
env: buildChildEnv(),
|
|
268
|
+
stdio: "ignore",
|
|
269
|
+
windowsHide: true
|
|
270
|
+
});
|
|
263
271
|
p.on("error", () => resolve2(false));
|
|
264
272
|
p.on("close", (code) => resolve2(code === 0));
|
|
265
273
|
} catch {
|
package/dist/scaffold.js
CHANGED
|
@@ -127,6 +127,7 @@ var scaffoldTool = {
|
|
|
127
127
|
usageHint: "PREFERRED FOR SCAFFOLDING:\n\n- Use built-in templates when they match your needs (e.g. react-component, npm-package).\n- Supports `dry_run` so you can preview exactly what will be created.\n- Has the powerful `fs.write.outside-project` capability \u2014 review paths carefully.\nMuch cleaner and safer than manually writing multiple files.",
|
|
128
128
|
permission: "confirm",
|
|
129
129
|
mutating: true,
|
|
130
|
+
subjectKey: "name",
|
|
130
131
|
capabilities: ["fs.write.outside-project", "fs.write"],
|
|
131
132
|
icon: "scaffold",
|
|
132
133
|
timeoutMs: 3e4,
|
|
@@ -201,15 +202,17 @@ async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
|
|
|
201
202
|
output: dryRun ? `Would create ${filesCreated} files: ${files.join(", ")}` : `Created ${filesCreated} files: ${files.join(", ")}`
|
|
202
203
|
};
|
|
203
204
|
}
|
|
205
|
+
function escapeRegexLiteral(value) {
|
|
206
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
207
|
+
}
|
|
204
208
|
function substituteVars(content, name, vars) {
|
|
209
|
+
const kebab = name.toLowerCase().replace(/\s+/g, "-");
|
|
210
|
+
const pascal = name.replace(/(?:^|[-_\s]+)([a-z])/g, (_, c) => c.toUpperCase());
|
|
205
211
|
let result = content;
|
|
206
|
-
result = result.replace(/\{\{name\}\}/g,
|
|
207
|
-
result = result.replace(
|
|
208
|
-
/\{\{Name\}\}/g,
|
|
209
|
-
name.replace(/(?:^|[-_\s]+)([a-z])/g, (_, c) => c.toUpperCase())
|
|
210
|
-
);
|
|
212
|
+
result = result.replace(/\{\{name\}\}/g, () => kebab);
|
|
213
|
+
result = result.replace(/\{\{Name\}\}/g, () => pascal);
|
|
211
214
|
for (const [k, v] of Object.entries(vars)) {
|
|
212
|
-
result = result.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v);
|
|
215
|
+
result = result.replace(new RegExp(`\\{\\{${escapeRegexLiteral(k)}\\}\\}`, "g"), () => v);
|
|
213
216
|
}
|
|
214
217
|
return result;
|
|
215
218
|
}
|
package/dist/session-kanban.js
CHANGED
|
@@ -90,7 +90,9 @@ async function ensureSessionKanbanBoard(projectRoot, sessionId) {
|
|
|
90
90
|
description: "Live session work: todos, tasks, and plan items.",
|
|
91
91
|
tags: sessionBoardTags(sessionId),
|
|
92
92
|
columns: SESSION_KANBAN_COLUMNS,
|
|
93
|
-
generatedBy: `session-kanban:${sessionId}
|
|
93
|
+
generatedBy: `session-kanban:${sessionId}`,
|
|
94
|
+
kind: "session_mirror",
|
|
95
|
+
retention: { mode: "archive_after_ttl", ttlMs: 7 * 24 * 60 * 60 * 1e3 }
|
|
94
96
|
});
|
|
95
97
|
}
|
|
96
98
|
if (!sameColumns(board.columns) || !board.tags?.includes(SESSION_BOARD_TAG)) {
|
|
@@ -325,12 +327,26 @@ function projectSessionPlanToKanban(projectRoot, items, sessionId) {
|
|
|
325
327
|
function fireAndForget(context, work) {
|
|
326
328
|
void work.catch((err) => {
|
|
327
329
|
const message = err instanceof Error ? err.message : String(err);
|
|
328
|
-
console.warn(
|
|
330
|
+
console.warn(
|
|
331
|
+
JSON.stringify({
|
|
332
|
+
level: "warn",
|
|
333
|
+
event: "session-kanban",
|
|
334
|
+
context,
|
|
335
|
+
message,
|
|
336
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
337
|
+
})
|
|
338
|
+
);
|
|
329
339
|
});
|
|
330
340
|
}
|
|
331
341
|
function broadcastTodoUpdate(context, todos) {
|
|
332
342
|
const sessionId = context.session?.id ?? "";
|
|
333
343
|
if (!context.agentId || !sessionId) return;
|
|
344
|
+
const statusCounts = { pending: 0, inProgress: 0, completed: 0 };
|
|
345
|
+
for (const todo of todos) {
|
|
346
|
+
if (todo.status === "completed") statusCounts.completed++;
|
|
347
|
+
else if (todo.status === "in_progress") statusCounts.inProgress++;
|
|
348
|
+
else statusCounts.pending++;
|
|
349
|
+
}
|
|
334
350
|
const projectDir = resolveWstackPaths({ projectRoot: context.projectRoot }).projectDir;
|
|
335
351
|
const mailbox = getSharedProjectMailbox(projectDir);
|
|
336
352
|
void mailbox.send({
|
|
@@ -342,11 +358,11 @@ function broadcastTodoUpdate(context, todos) {
|
|
|
342
358
|
kind: "kanban.todos.updated",
|
|
343
359
|
sessionId,
|
|
344
360
|
revision: context.state.revision,
|
|
345
|
-
todos
|
|
361
|
+
todoCount: todos.length,
|
|
362
|
+
statusCounts
|
|
346
363
|
}),
|
|
347
364
|
priority: "normal",
|
|
348
|
-
senderSessionId: sessionId
|
|
349
|
-
ttlMs: 6 * 60 * 60 * 1e3
|
|
365
|
+
senderSessionId: sessionId
|
|
350
366
|
}).catch(() => {
|
|
351
367
|
});
|
|
352
368
|
}
|
package/dist/task.js
CHANGED
|
@@ -78,7 +78,9 @@ async function ensureSessionKanbanBoard(projectRoot, sessionId) {
|
|
|
78
78
|
description: "Live session work: todos, tasks, and plan items.",
|
|
79
79
|
tags: sessionBoardTags(sessionId),
|
|
80
80
|
columns: SESSION_KANBAN_COLUMNS,
|
|
81
|
-
generatedBy: `session-kanban:${sessionId}
|
|
81
|
+
generatedBy: `session-kanban:${sessionId}`,
|
|
82
|
+
kind: "session_mirror",
|
|
83
|
+
retention: { mode: "archive_after_ttl", ttlMs: 7 * 24 * 60 * 60 * 1e3 }
|
|
82
84
|
});
|
|
83
85
|
}
|
|
84
86
|
if (!sameColumns(board.columns) || !board.tags?.includes(SESSION_BOARD_TAG)) {
|