@wrongstack/tools 0.286.0 → 0.289.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/README.md +2 -2
- package/dist/_edit-match.d.ts.map +1 -1
- package/dist/_fetch-guard.d.ts.map +1 -1
- package/dist/_redact-command.d.ts.map +1 -1
- package/dist/_spawn-stream.d.ts.map +1 -1
- package/dist/audit.js +61 -14
- package/dist/audit.js.map +3 -3
- package/dist/bash.d.ts +1 -1
- package/dist/bash.d.ts.map +1 -1
- package/dist/bash.js +97 -19
- package/dist/bash.js.map +3 -3
- package/dist/batch-tool-use.d.ts +1 -1
- package/dist/batch-tool-use.d.ts.map +1 -1
- package/dist/batch-tool-use.js +26 -8
- package/dist/batch-tool-use.js.map +2 -2
- package/dist/browser/index.js +1 -1
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/types.d.ts +1 -1
- package/dist/browser/types.d.ts.map +1 -1
- package/dist/builtin.d.ts +12 -10
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +2107 -864
- package/dist/builtin.js.map +4 -4
- package/dist/codebase-index/background-indexer.d.ts.map +1 -1
- package/dist/codebase-index/bm25.d.ts +1 -2
- package/dist/codebase-index/bm25.d.ts.map +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts +7 -0
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
- package/dist/codebase-index/go-parser.d.ts.map +1 -1
- package/dist/codebase-index/index-service.d.ts +11 -1
- package/dist/codebase-index/index-service.d.ts.map +1 -1
- package/dist/codebase-index/index.d.ts +2 -1
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +934 -283
- package/dist/codebase-index/index.js.map +4 -4
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/py-parser.d.ts.map +1 -1
- package/dist/codebase-index/rs-parser.d.ts.map +1 -1
- package/dist/codebase-index/schema.d.ts +43 -1
- package/dist/codebase-index/schema.d.ts.map +1 -1
- package/dist/codebase-index/ts-parser.d.ts.map +1 -1
- package/dist/codebase-index/worker-protocol.d.ts +1 -0
- package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
- package/dist/codebase-index/worker.js +793 -241
- package/dist/codebase-index/worker.js.map +4 -4
- package/dist/codebase-index/writer.d.ts +27 -1
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/e2e.js +477 -0
- package/dist/e2e.js.map +7 -0
- package/dist/edit.d.ts.map +1 -1
- package/dist/edit.js +36 -12
- package/dist/edit.js.map +3 -3
- package/dist/exec.d.ts +1 -1
- package/dist/exec.d.ts.map +1 -1
- package/dist/exec.js +60 -5
- package/dist/exec.js.map +3 -3
- package/dist/fetch.js +2 -2
- package/dist/fetch.js.map +2 -2
- package/dist/format.js +61 -14
- package/dist/format.js.map +3 -3
- package/dist/glob.js +2 -2
- package/dist/glob.js.map +1 -1
- package/dist/grep.d.ts.map +1 -1
- package/dist/grep.js +2 -2
- package/dist/grep.js.map +2 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2535 -865
- package/dist/index.js.map +4 -4
- package/dist/install.js +61 -14
- package/dist/install.js.map +3 -3
- package/dist/kanban.d.ts +16 -2
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +795 -532
- package/dist/kanban.js.map +4 -4
- package/dist/languages/detect.d.ts.map +1 -1
- package/dist/languages/index.js +61 -14
- package/dist/languages/index.js.map +3 -3
- package/dist/languages/types.d.ts +7 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/lint.js +61 -14
- package/dist/lint.js.map +3 -3
- package/dist/next-steps.d.ts +6 -2
- package/dist/next-steps.d.ts.map +1 -1
- package/dist/next-steps.js +3 -3
- package/dist/next-steps.js.map +2 -2
- package/dist/outdated.js +61 -14
- package/dist/outdated.js.map +3 -3
- package/dist/pack.js +2104 -861
- package/dist/pack.js.map +4 -4
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +158 -4
- package/dist/plan.js.map +4 -4
- package/dist/process-registry.js +12 -5
- package/dist/process-registry.js.map +2 -2
- package/dist/search.js +2 -2
- package/dist/search.js.map +2 -2
- package/dist/session-kanban.d.ts +47 -0
- package/dist/session-kanban.d.ts.map +1 -0
- package/dist/session-kanban.js +626 -0
- package/dist/session-kanban.js.map +7 -0
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +167 -4
- package/dist/task.js.map +4 -4
- package/dist/test.js +61 -14
- package/dist/test.js.map +3 -3
- package/dist/todo.d.ts.map +1 -1
- package/dist/todo.js +159 -7
- package/dist/todo.js.map +4 -4
- package/dist/tool-icon-map.d.ts.map +1 -1
- package/dist/tool-icons.d.ts.map +1 -1
- package/dist/tool-icons.js +7 -1
- package/dist/tool-icons.js.map +2 -2
- package/dist/tree.js +2 -2
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js +61 -14
- package/dist/typecheck.js.map +3 -3
- package/package.json +11 -3
package/dist/index.js
CHANGED
|
@@ -404,8 +404,10 @@ function redactCommand(cmd) {
|
|
|
404
404
|
const flag = match.slice(0, match.indexOf(expectDefined(delim)) + 1);
|
|
405
405
|
return `${flag}[REDACTED]`;
|
|
406
406
|
}
|
|
407
|
-
|
|
408
|
-
|
|
407
|
+
if (match.startsWith("--")) {
|
|
408
|
+
return match;
|
|
409
|
+
}
|
|
410
|
+
return `${match.slice(0, 2)}[REDACTED]`;
|
|
409
411
|
});
|
|
410
412
|
}
|
|
411
413
|
return result;
|
|
@@ -417,9 +419,14 @@ var init_redact_command = __esm({
|
|
|
417
419
|
SENSITIVE_FLAG_PATTERNS = [
|
|
418
420
|
// --flag=value or --flag "value" (value captured up to next space or comma)
|
|
419
421
|
/--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
|
|
420
|
-
// -
|
|
421
|
-
|
|
422
|
-
/
|
|
422
|
+
// -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
|
|
423
|
+
// (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
|
|
424
|
+
// NOTE: synced with @wrongstack/core observability/redact-command.ts.
|
|
425
|
+
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]+/,
|
|
426
|
+
// -p|-password|-a (redis auth) short flags: attached + separated + =value.
|
|
427
|
+
// Same token-start anchor; over-redaction is an accepted tradeoff for a
|
|
428
|
+
// redaction function. Synced with core copy.
|
|
429
|
+
/(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
|
|
423
430
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
424
431
|
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
425
432
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
@@ -802,7 +809,12 @@ var init_process_registry = __esm({
|
|
|
802
809
|
|
|
803
810
|
// src/_spawn-stream.ts
|
|
804
811
|
import { spawn as spawn2 } from "node:child_process";
|
|
805
|
-
import {
|
|
812
|
+
import {
|
|
813
|
+
buildChildEnv,
|
|
814
|
+
emitProcessCompleted,
|
|
815
|
+
emitProcessOutput,
|
|
816
|
+
emitProcessStarted
|
|
817
|
+
} from "@wrongstack/core";
|
|
806
818
|
async function* spawnStream(opts) {
|
|
807
819
|
const max = opts.maxBytes ?? 2e5;
|
|
808
820
|
const flushAt = opts.flushBytes ?? 4 * 1024;
|
|
@@ -827,6 +839,19 @@ async function* spawnStream(opts) {
|
|
|
827
839
|
});
|
|
828
840
|
const registry = getProcessRegistry();
|
|
829
841
|
const pid = child.pid;
|
|
842
|
+
const processStartedAt = Date.now();
|
|
843
|
+
let stdoutBytes = 0;
|
|
844
|
+
let stderrBytes = 0;
|
|
845
|
+
let telemetryCompleted = false;
|
|
846
|
+
emitProcessStarted({
|
|
847
|
+
...pid !== void 0 ? { pid } : {},
|
|
848
|
+
parentPid: process.pid,
|
|
849
|
+
command: redactCommand(`${opts.cmd} ${opts.args.join(" ")}`),
|
|
850
|
+
args: redactCommand(opts.args.join(" ")).split(" ").filter(Boolean),
|
|
851
|
+
cwd: opts.cwd,
|
|
852
|
+
background: false,
|
|
853
|
+
startedAt: new Date(processStartedAt).toISOString()
|
|
854
|
+
});
|
|
830
855
|
if (typeof pid === "number") {
|
|
831
856
|
registry.register({
|
|
832
857
|
pid,
|
|
@@ -855,6 +880,8 @@ async function* spawnStream(opts) {
|
|
|
855
880
|
};
|
|
856
881
|
const onOut = (c) => {
|
|
857
882
|
const s = c.toString();
|
|
883
|
+
stdoutBytes += c.byteLength;
|
|
884
|
+
emitProcessOutput({ pid, stream: "stdout", chunk: c });
|
|
858
885
|
if (stdout.length < max) stdout += s;
|
|
859
886
|
spool.write(s);
|
|
860
887
|
queue.push({ kind: "out", data: s });
|
|
@@ -867,6 +894,8 @@ async function* spawnStream(opts) {
|
|
|
867
894
|
};
|
|
868
895
|
const onErr = (c) => {
|
|
869
896
|
const s = c.toString();
|
|
897
|
+
stderrBytes += c.byteLength;
|
|
898
|
+
emitProcessOutput({ pid, stream: "stderr", chunk: c });
|
|
870
899
|
if (stderr.length < max) stderr += s;
|
|
871
900
|
spool.write(s);
|
|
872
901
|
queue.push({ kind: "err", data: s });
|
|
@@ -884,9 +913,25 @@ async function* spawnStream(opts) {
|
|
|
884
913
|
queue.push({ kind: "error", data: e.message });
|
|
885
914
|
wake();
|
|
886
915
|
});
|
|
887
|
-
|
|
916
|
+
const completeTelemetry = (code, signal, timedOut = false) => {
|
|
917
|
+
if (telemetryCompleted) return;
|
|
918
|
+
telemetryCompleted = true;
|
|
919
|
+
emitProcessCompleted({
|
|
920
|
+
...pid !== void 0 ? { pid } : {},
|
|
921
|
+
exitCode: code,
|
|
922
|
+
...signal ? { signal } : {},
|
|
923
|
+
durationMs: Date.now() - processStartedAt,
|
|
924
|
+
stdoutBytes,
|
|
925
|
+
stderrBytes,
|
|
926
|
+
timedOut,
|
|
927
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
928
|
+
});
|
|
929
|
+
};
|
|
930
|
+
child.on("close", (code, signal) => {
|
|
888
931
|
if (typeof pid === "number") registry.unregister(pid);
|
|
889
|
-
|
|
932
|
+
const exitCode2 = code ?? (signal ? 1 : 0);
|
|
933
|
+
completeTelemetry(exitCode2, signal ?? void 0);
|
|
934
|
+
queue.push({ kind: "close", data: "", code: exitCode2, ...signal ? { signal } : {} });
|
|
890
935
|
wake();
|
|
891
936
|
});
|
|
892
937
|
const onAbort = () => {
|
|
@@ -899,6 +944,7 @@ async function* spawnStream(opts) {
|
|
|
899
944
|
}
|
|
900
945
|
}
|
|
901
946
|
queue.push({ kind: "close", data: "", code: 124 });
|
|
947
|
+
completeTelemetry(124, "SIGKILL", true);
|
|
902
948
|
wake();
|
|
903
949
|
};
|
|
904
950
|
if (isWin) {
|
|
@@ -2506,13 +2552,14 @@ async function detectLanguageWorkspaces(options) {
|
|
|
2506
2552
|
const target = options.target ? await canonicalInside(resolveFrom(cwd, options.target), projectRoot, "target") : void 0;
|
|
2507
2553
|
const profiles = (options.profiles ?? languageProfileRegistry.list()).filter((profile) => !options.language || profile.id === options.language).slice().sort((a, b) => a.id.localeCompare(b.id));
|
|
2508
2554
|
const limits = normalizeLimits(options.limits);
|
|
2555
|
+
const extraIgnores = new Set(options.ignoredDirectories ?? []);
|
|
2509
2556
|
const state = {
|
|
2510
2557
|
entries: 0,
|
|
2511
2558
|
truncated: false,
|
|
2512
2559
|
sourcePaths: /* @__PURE__ */ new Map(),
|
|
2513
2560
|
candidates: /* @__PURE__ */ new Map()
|
|
2514
2561
|
};
|
|
2515
|
-
await scanDirectory(projectRoot, 0, profiles, limits, state, options.signal);
|
|
2562
|
+
await scanDirectory(projectRoot, 0, profiles, limits, state, extraIgnores, options.signal);
|
|
2516
2563
|
addSourceFallbacks(projectRoot, profiles, state);
|
|
2517
2564
|
if (target) addTargetEvidence(target, projectRoot, profiles, state);
|
|
2518
2565
|
const workspaces = await Promise.all(
|
|
@@ -2526,7 +2573,7 @@ async function detectLanguageWorkspaces(options) {
|
|
|
2526
2573
|
workspaces
|
|
2527
2574
|
};
|
|
2528
2575
|
}
|
|
2529
|
-
async function scanDirectory(directory, depth, profiles, limits, state, signal) {
|
|
2576
|
+
async function scanDirectory(directory, depth, profiles, limits, state, extraIgnores, signal) {
|
|
2530
2577
|
signal?.throwIfAborted();
|
|
2531
2578
|
if (depth > limits.maxDepth || state.entries >= limits.maxEntries) {
|
|
2532
2579
|
state.truncated = true;
|
|
@@ -2549,20 +2596,20 @@ async function scanDirectory(directory, depth, profiles, limits, state, signal)
|
|
|
2549
2596
|
const fullPath = path4.join(directory, entry.name);
|
|
2550
2597
|
if (entry.isSymbolicLink()) continue;
|
|
2551
2598
|
if (entry.isDirectory()) {
|
|
2552
|
-
if (shouldIgnoreDirectory(entry.name, profiles)) continue;
|
|
2599
|
+
if (shouldIgnoreDirectory(entry.name, profiles, extraIgnores)) continue;
|
|
2553
2600
|
if (depth >= limits.maxDepth) {
|
|
2554
2601
|
state.truncated = true;
|
|
2555
2602
|
continue;
|
|
2556
2603
|
}
|
|
2557
|
-
await scanDirectory(fullPath, depth + 1, profiles, limits, state, signal);
|
|
2604
|
+
await scanDirectory(fullPath, depth + 1, profiles, limits, state, extraIgnores, signal);
|
|
2558
2605
|
continue;
|
|
2559
2606
|
}
|
|
2560
2607
|
if (!entry.isFile()) continue;
|
|
2561
2608
|
collectFileEvidence(directory, fullPath, entry.name, profiles, state);
|
|
2562
2609
|
}
|
|
2563
2610
|
}
|
|
2564
|
-
function collectFileEvidence(directory, fullPath,
|
|
2565
|
-
const lower =
|
|
2611
|
+
function collectFileEvidence(directory, fullPath, basename10, profiles, state) {
|
|
2612
|
+
const lower = basename10.toLowerCase();
|
|
2566
2613
|
const extension = path4.extname(lower);
|
|
2567
2614
|
for (const profile of profiles) {
|
|
2568
2615
|
const detector = profile.detectors.find(
|
|
@@ -2573,7 +2620,7 @@ function collectFileEvidence(directory, fullPath, basename8, profiles, state) {
|
|
|
2573
2620
|
candidate.evidence.push({
|
|
2574
2621
|
kind: detector.kind,
|
|
2575
2622
|
path: fullPath,
|
|
2576
|
-
value:
|
|
2623
|
+
value: basename10,
|
|
2577
2624
|
weight: detector.weight
|
|
2578
2625
|
});
|
|
2579
2626
|
if (detector.kind === "manifest" || detector.kind === "config") {
|
|
@@ -2683,8 +2730,8 @@ function getCandidate(state, profile, root) {
|
|
|
2683
2730
|
}
|
|
2684
2731
|
return candidate;
|
|
2685
2732
|
}
|
|
2686
|
-
function shouldIgnoreDirectory(name, profiles) {
|
|
2687
|
-
if (GLOBAL_IGNORES.has(name) || name.startsWith(".")) return true;
|
|
2733
|
+
function shouldIgnoreDirectory(name, profiles, extraIgnores) {
|
|
2734
|
+
if (GLOBAL_IGNORES.has(name) || extraIgnores.has(name) || name.startsWith(".")) return true;
|
|
2688
2735
|
return profiles.some((profile) => profile.ignoredDirectories.includes(name));
|
|
2689
2736
|
}
|
|
2690
2737
|
function normalizeLimits(input) {
|
|
@@ -5210,6 +5257,11 @@ function parseAuditOutput(json2, exitCode) {
|
|
|
5210
5257
|
// src/bash.ts
|
|
5211
5258
|
import { spawn as spawn3 } from "node:child_process";
|
|
5212
5259
|
import * as os4 from "node:os";
|
|
5260
|
+
import {
|
|
5261
|
+
emitProcessCompleted as emitProcessCompleted2,
|
|
5262
|
+
emitProcessOutput as emitProcessOutput2,
|
|
5263
|
+
emitProcessStarted as emitProcessStarted2
|
|
5264
|
+
} from "@wrongstack/core";
|
|
5213
5265
|
|
|
5214
5266
|
// src/_env.ts
|
|
5215
5267
|
import { buildChildEnv as buildChildEnv2 } from "@wrongstack/core";
|
|
@@ -6085,6 +6137,32 @@ var bashTool = {
|
|
|
6085
6137
|
}
|
|
6086
6138
|
}
|
|
6087
6139
|
const pid2 = child2.pid;
|
|
6140
|
+
let stdoutBytes2 = 0;
|
|
6141
|
+
let stderrBytes2 = 0;
|
|
6142
|
+
let telemetryCompleted2 = false;
|
|
6143
|
+
emitProcessStarted2({
|
|
6144
|
+
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
6145
|
+
parentPid: process.pid,
|
|
6146
|
+
command: redactCommand(`${shell} ${args.join(" ")}`),
|
|
6147
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
6148
|
+
cwd: ctx.projectRoot,
|
|
6149
|
+
background: true,
|
|
6150
|
+
startedAt: new Date(startedAt).toISOString()
|
|
6151
|
+
});
|
|
6152
|
+
const completeBackground = (exitCode, signal) => {
|
|
6153
|
+
if (telemetryCompleted2) return;
|
|
6154
|
+
telemetryCompleted2 = true;
|
|
6155
|
+
emitProcessCompleted2({
|
|
6156
|
+
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
6157
|
+
exitCode,
|
|
6158
|
+
...signal ? { signal } : {},
|
|
6159
|
+
durationMs: Date.now() - startedAt,
|
|
6160
|
+
stdoutBytes: stdoutBytes2,
|
|
6161
|
+
stderrBytes: stderrBytes2,
|
|
6162
|
+
timedOut: false,
|
|
6163
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6164
|
+
});
|
|
6165
|
+
};
|
|
6088
6166
|
if (typeof pid2 === "number") {
|
|
6089
6167
|
registry.register({
|
|
6090
6168
|
pid: pid2,
|
|
@@ -6097,7 +6175,10 @@ var bashTool = {
|
|
|
6097
6175
|
});
|
|
6098
6176
|
child2.on("close", () => registry.unregister(pid2));
|
|
6099
6177
|
}
|
|
6100
|
-
const onBgData = (chunk) => {
|
|
6178
|
+
const onBgData = (chunk, stream) => {
|
|
6179
|
+
if (stream === "stdout") stdoutBytes2 += chunk.byteLength;
|
|
6180
|
+
else stderrBytes2 += chunk.byteLength;
|
|
6181
|
+
emitProcessOutput2({ pid: pid2, stream, chunk });
|
|
6101
6182
|
if (truncated) return;
|
|
6102
6183
|
const remain = MAX_OUTPUT - buf2.length;
|
|
6103
6184
|
if (remain > 0) {
|
|
@@ -6105,24 +6186,28 @@ var bashTool = {
|
|
|
6105
6186
|
}
|
|
6106
6187
|
if (buf2.length >= MAX_OUTPUT) {
|
|
6107
6188
|
truncated = true;
|
|
6108
|
-
child2.stdout?.off("data",
|
|
6109
|
-
child2.stderr?.off("data",
|
|
6189
|
+
child2.stdout?.off("data", onBgStdout);
|
|
6190
|
+
child2.stderr?.off("data", onBgStderr);
|
|
6110
6191
|
}
|
|
6111
6192
|
};
|
|
6112
|
-
|
|
6113
|
-
|
|
6193
|
+
const onBgStdout = (chunk) => onBgData(chunk, "stdout");
|
|
6194
|
+
const onBgStderr = (chunk) => onBgData(chunk, "stderr");
|
|
6195
|
+
child2.stdout?.on("data", onBgStdout);
|
|
6196
|
+
child2.stderr?.on("data", onBgStderr);
|
|
6114
6197
|
const cleanupBackground = () => {
|
|
6115
|
-
child2.stdout?.off("data",
|
|
6116
|
-
child2.stderr?.off("data",
|
|
6198
|
+
child2.stdout?.off("data", onBgStdout);
|
|
6199
|
+
child2.stderr?.off("data", onBgStderr);
|
|
6117
6200
|
};
|
|
6118
6201
|
child2.on("error", () => {
|
|
6119
6202
|
cleanupBackground();
|
|
6120
6203
|
if (typeof pid2 === "number") registry.unregister(pid2);
|
|
6121
6204
|
registry.afterCall(Date.now() - startedAt, true, bypassBreaker);
|
|
6205
|
+
completeBackground(1);
|
|
6122
6206
|
});
|
|
6123
|
-
child2.on("close", () => {
|
|
6207
|
+
child2.on("close", (code, signal) => {
|
|
6124
6208
|
cleanupBackground();
|
|
6125
6209
|
registry.afterCall(Date.now() - startedAt, false, bypassBreaker);
|
|
6210
|
+
completeBackground(code ?? (signal ? 1 : 0), signal ?? void 0);
|
|
6126
6211
|
});
|
|
6127
6212
|
if (typeof pid2 === "number") child2.unref();
|
|
6128
6213
|
yield {
|
|
@@ -6162,6 +6247,32 @@ var bashTool = {
|
|
|
6162
6247
|
}
|
|
6163
6248
|
}
|
|
6164
6249
|
const pid = child.pid;
|
|
6250
|
+
let stdoutBytes = 0;
|
|
6251
|
+
let stderrBytes = 0;
|
|
6252
|
+
let telemetryCompleted = false;
|
|
6253
|
+
emitProcessStarted2({
|
|
6254
|
+
...pid !== void 0 ? { pid } : {},
|
|
6255
|
+
parentPid: process.pid,
|
|
6256
|
+
command: redactCommand(`${shell} ${args.join(" ")}`),
|
|
6257
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
6258
|
+
cwd: ctx.projectRoot,
|
|
6259
|
+
background: false,
|
|
6260
|
+
startedAt: new Date(startedAt).toISOString()
|
|
6261
|
+
});
|
|
6262
|
+
const completeForeground = (exitCode, signal) => {
|
|
6263
|
+
if (telemetryCompleted) return;
|
|
6264
|
+
telemetryCompleted = true;
|
|
6265
|
+
emitProcessCompleted2({
|
|
6266
|
+
...pid !== void 0 ? { pid } : {},
|
|
6267
|
+
exitCode,
|
|
6268
|
+
...signal ? { signal } : {},
|
|
6269
|
+
durationMs: Date.now() - startedAt,
|
|
6270
|
+
stdoutBytes,
|
|
6271
|
+
stderrBytes,
|
|
6272
|
+
timedOut,
|
|
6273
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6274
|
+
});
|
|
6275
|
+
};
|
|
6165
6276
|
if (typeof pid === "number") {
|
|
6166
6277
|
registry.register({
|
|
6167
6278
|
pid,
|
|
@@ -6263,8 +6374,11 @@ var bashTool = {
|
|
|
6263
6374
|
child.stderr?.resume();
|
|
6264
6375
|
}
|
|
6265
6376
|
};
|
|
6266
|
-
const onData = (chunk) => {
|
|
6377
|
+
const onData = (chunk, stream) => {
|
|
6267
6378
|
const text = chunk.toString();
|
|
6379
|
+
if (stream === "stdout") stdoutBytes += chunk.byteLength;
|
|
6380
|
+
else stderrBytes += chunk.byteLength;
|
|
6381
|
+
emitProcessOutput2({ pid, stream, chunk });
|
|
6268
6382
|
if (buf.length < MAX_OUTPUT) {
|
|
6269
6383
|
buf += text.slice(0, MAX_OUTPUT - buf.length);
|
|
6270
6384
|
}
|
|
@@ -6273,17 +6387,21 @@ var bashTool = {
|
|
|
6273
6387
|
push({ kind: "data", text });
|
|
6274
6388
|
pauseIfFlooded();
|
|
6275
6389
|
};
|
|
6276
|
-
|
|
6277
|
-
|
|
6390
|
+
const onStdoutData = (chunk) => onData(chunk, "stdout");
|
|
6391
|
+
const onStderrData = (chunk) => onData(chunk, "stderr");
|
|
6392
|
+
child.stdout?.on("data", onStdoutData);
|
|
6393
|
+
child.stderr?.on("data", onStderrData);
|
|
6278
6394
|
child.on("error", (err) => {
|
|
6279
6395
|
for (const t of timers) clearTimeout(t);
|
|
6280
6396
|
registry.afterCall(Date.now() - startedAt, true);
|
|
6397
|
+
completeForeground(1);
|
|
6281
6398
|
push({ kind: "error", err });
|
|
6282
6399
|
});
|
|
6283
|
-
child.on("close", (code) => {
|
|
6400
|
+
child.on("close", (code, signal) => {
|
|
6284
6401
|
for (const t of timers) clearTimeout(t);
|
|
6285
6402
|
if (typeof pid === "number") registry.unregister(pid);
|
|
6286
6403
|
registry.afterCall(Date.now() - startedAt, code !== 0 && code !== null);
|
|
6404
|
+
completeForeground(timedOut ? 124 : code ?? (signal ? 1 : 0), signal ?? void 0);
|
|
6287
6405
|
push({ kind: "end", code });
|
|
6288
6406
|
});
|
|
6289
6407
|
try {
|
|
@@ -6328,8 +6446,8 @@ ${hint}` : ""),
|
|
|
6328
6446
|
for (const t of timers) clearTimeout(t);
|
|
6329
6447
|
spool.finalize();
|
|
6330
6448
|
if (isWin4) opts.signal.removeEventListener("abort", onAbort);
|
|
6331
|
-
child.stdout?.off("data",
|
|
6332
|
-
child.stderr?.off("data",
|
|
6449
|
+
child.stdout?.off("data", onStdoutData);
|
|
6450
|
+
child.stderr?.off("data", onStderrData);
|
|
6333
6451
|
child.stdout?.destroy();
|
|
6334
6452
|
child.stderr?.destroy();
|
|
6335
6453
|
if (child.exitCode === null && !child.killed) {
|
|
@@ -6373,6 +6491,9 @@ ${hint}` : ""),
|
|
|
6373
6491
|
};
|
|
6374
6492
|
|
|
6375
6493
|
// src/batch-tool-use.ts
|
|
6494
|
+
import {
|
|
6495
|
+
GOVERNED_TOOL_EXECUTOR_META_KEY
|
|
6496
|
+
} from "@wrongstack/core";
|
|
6376
6497
|
var batchToolUseTool = {
|
|
6377
6498
|
name: "batch_tool_use",
|
|
6378
6499
|
category: "Meta",
|
|
@@ -6409,7 +6530,7 @@ var batchToolUseTool = {
|
|
|
6409
6530
|
},
|
|
6410
6531
|
required: ["calls"]
|
|
6411
6532
|
},
|
|
6412
|
-
async execute(input, ctx,
|
|
6533
|
+
async execute(input, ctx, _opts) {
|
|
6413
6534
|
if (!input?.calls || input.calls.length === 0) {
|
|
6414
6535
|
return {
|
|
6415
6536
|
results: [],
|
|
@@ -6419,18 +6540,33 @@ var batchToolUseTool = {
|
|
|
6419
6540
|
stop_on_error: false
|
|
6420
6541
|
};
|
|
6421
6542
|
}
|
|
6543
|
+
const governedExecute = ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY];
|
|
6544
|
+
if (typeof governedExecute !== "function") {
|
|
6545
|
+
return {
|
|
6546
|
+
results: input.calls.map((call) => ({
|
|
6547
|
+
tool: call.tool,
|
|
6548
|
+
success: false,
|
|
6549
|
+
error: "governed nested execution is unavailable; call the tool directly",
|
|
6550
|
+
executionMs: 0
|
|
6551
|
+
})),
|
|
6552
|
+
total: input.calls.length,
|
|
6553
|
+
succeeded: 0,
|
|
6554
|
+
failed: input.calls.length,
|
|
6555
|
+
stop_on_error: input.stop_on_error ?? false
|
|
6556
|
+
};
|
|
6557
|
+
}
|
|
6422
6558
|
const results = [];
|
|
6423
6559
|
let succeeded = 0;
|
|
6424
6560
|
let failed = 0;
|
|
6425
6561
|
if (input.parallel !== false) {
|
|
6426
|
-
const promises = input.calls.map(async (call) => executeSingle(call, ctx,
|
|
6562
|
+
const promises = input.calls.map(async (call) => executeSingle(call, ctx, governedExecute));
|
|
6427
6563
|
const allResults = await Promise.all(promises);
|
|
6428
6564
|
results.push(...allResults);
|
|
6429
6565
|
succeeded = allResults.filter((r) => r.success).length;
|
|
6430
6566
|
failed = allResults.filter((r) => !r.success).length;
|
|
6431
6567
|
} else {
|
|
6432
6568
|
for (const call of input.calls) {
|
|
6433
|
-
const result = await executeSingle(call, ctx,
|
|
6569
|
+
const result = await executeSingle(call, ctx, governedExecute);
|
|
6434
6570
|
results.push(result);
|
|
6435
6571
|
if (result.success) {
|
|
6436
6572
|
succeeded++;
|
|
@@ -6449,9 +6585,9 @@ var batchToolUseTool = {
|
|
|
6449
6585
|
};
|
|
6450
6586
|
}
|
|
6451
6587
|
};
|
|
6452
|
-
async function executeSingle(call, ctx,
|
|
6588
|
+
async function executeSingle(call, ctx, governedExecute) {
|
|
6453
6589
|
const start = Date.now();
|
|
6454
|
-
const tool = ctx.tools.find((
|
|
6590
|
+
const tool = ctx.tools.find((candidate) => candidate.name === call.tool);
|
|
6455
6591
|
if (!tool) {
|
|
6456
6592
|
return {
|
|
6457
6593
|
tool: call.tool,
|
|
@@ -6461,11 +6597,11 @@ async function executeSingle(call, ctx, opts) {
|
|
|
6461
6597
|
};
|
|
6462
6598
|
}
|
|
6463
6599
|
try {
|
|
6464
|
-
const result = await
|
|
6600
|
+
const result = await governedExecute(call.tool, call.input);
|
|
6465
6601
|
return {
|
|
6466
6602
|
tool: call.tool,
|
|
6467
|
-
success:
|
|
6468
|
-
result,
|
|
6603
|
+
success: result.success,
|
|
6604
|
+
...result.success ? { result: result.result } : { error: result.error ?? "nested tool failed" },
|
|
6469
6605
|
executionMs: Date.now() - start
|
|
6470
6606
|
};
|
|
6471
6607
|
} catch (e) {
|
|
@@ -6870,7 +7006,7 @@ var BrowserSessionManager = class {
|
|
|
6870
7006
|
constructor(options, launcher = defaultLauncher) {
|
|
6871
7007
|
this.launcher = launcher;
|
|
6872
7008
|
this.artifacts = new BrowserArtifactStore(options.artifactRoot);
|
|
6873
|
-
this.allowPrivateHosts = options.allowPrivateHosts ??
|
|
7009
|
+
this.allowPrivateHosts = options.allowPrivateHosts ?? true;
|
|
6874
7010
|
this.allowedPrivateOrigins = options.allowedPrivateOrigins ?? [];
|
|
6875
7011
|
this.networkProxy = new BrowserNetworkGuardProxy({
|
|
6876
7012
|
allowPrivateHosts: this.allowPrivateHosts,
|
|
@@ -7807,7 +7943,7 @@ import * as path13 from "node:path";
|
|
|
7807
7943
|
import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core";
|
|
7808
7944
|
|
|
7809
7945
|
// src/codebase-index/schema.ts
|
|
7810
|
-
var SCHEMA_VERSION =
|
|
7946
|
+
var SCHEMA_VERSION = 3;
|
|
7811
7947
|
|
|
7812
7948
|
// src/codebase-index/lsp-kind.ts
|
|
7813
7949
|
function lspKindToInternalKind(k) {
|
|
@@ -7850,7 +7986,7 @@ function tokenise(text) {
|
|
|
7850
7986
|
return sanitised.toLowerCase().split(" ").filter(Boolean);
|
|
7851
7987
|
}
|
|
7852
7988
|
function splitName(name) {
|
|
7853
|
-
return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
7989
|
+
return name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([\p{L}])(\d)/gu, "$1 $2").replace(/(\d)([\p{L}])/gu, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
7854
7990
|
}
|
|
7855
7991
|
function buildIndexableText(name, signature, docComment) {
|
|
7856
7992
|
return [splitName(name), name, signature, docComment].filter(Boolean).join(" ");
|
|
@@ -7876,14 +8012,12 @@ function buildBm25Index(docs) {
|
|
|
7876
8012
|
return new Bm25Index(documents, df, N, avgLen);
|
|
7877
8013
|
}
|
|
7878
8014
|
var Bm25Index = class {
|
|
7879
|
-
constructor(documents,
|
|
8015
|
+
constructor(documents, _df, N, avgLen) {
|
|
7880
8016
|
this.documents = documents;
|
|
7881
|
-
this.df = df;
|
|
7882
8017
|
this.N = N;
|
|
7883
8018
|
this.safeAvgLen = avgLen === 0 ? 1 : avgLen;
|
|
7884
8019
|
}
|
|
7885
8020
|
documents;
|
|
7886
|
-
df;
|
|
7887
8021
|
N;
|
|
7888
8022
|
safeAvgLen;
|
|
7889
8023
|
score(query, filter) {
|
|
@@ -7896,10 +8030,13 @@ var Bm25Index = class {
|
|
|
7896
8030
|
for (const qTerm of qTokens) {
|
|
7897
8031
|
let tf = 0;
|
|
7898
8032
|
for (const t of doc.tokens) {
|
|
7899
|
-
if (t
|
|
8033
|
+
if (t.startsWith(qTerm)) tf++;
|
|
7900
8034
|
}
|
|
7901
8035
|
if (tf === 0) continue;
|
|
7902
|
-
|
|
8036
|
+
let dfVal = 0;
|
|
8037
|
+
for (const candidate of this.documents) {
|
|
8038
|
+
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
8039
|
+
}
|
|
7903
8040
|
if (dfVal === 0) continue;
|
|
7904
8041
|
const idf = Math.log((this.N - dfVal + 0.5) / (dfVal + 0.5) + 1);
|
|
7905
8042
|
const lenRatio = B * (doc.len / this.safeAvgLen);
|
|
@@ -7932,6 +8069,29 @@ var Bm25Index = class {
|
|
|
7932
8069
|
|
|
7933
8070
|
// src/codebase-index/writer.ts
|
|
7934
8071
|
var DB_FILE = "index.db";
|
|
8072
|
+
function escapeLike(value) {
|
|
8073
|
+
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
8074
|
+
}
|
|
8075
|
+
function assignRefsToSymbols(refs, symbols) {
|
|
8076
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
8077
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
8078
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8079
|
+
const assigned = [];
|
|
8080
|
+
for (const ref of refs) {
|
|
8081
|
+
let owner2;
|
|
8082
|
+
for (const symbol of ordered) {
|
|
8083
|
+
if (symbol.line > ref.line) break;
|
|
8084
|
+
owner2 = symbol;
|
|
8085
|
+
}
|
|
8086
|
+
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
8087
|
+
if (!owner2 || owner2.id <= 0) continue;
|
|
8088
|
+
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
8089
|
+
if (seen.has(key)) continue;
|
|
8090
|
+
seen.add(key);
|
|
8091
|
+
assigned.push({ ...ref, fromId: owner2.id });
|
|
8092
|
+
}
|
|
8093
|
+
return assigned;
|
|
8094
|
+
}
|
|
7935
8095
|
function resolveIndexDir(projectRoot, override) {
|
|
7936
8096
|
return override ?? resolveWstackPaths2({ projectRoot }).projectCodebaseIndex;
|
|
7937
8097
|
}
|
|
@@ -7985,7 +8145,7 @@ function sleepSync(ms) {
|
|
|
7985
8145
|
} catch {
|
|
7986
8146
|
}
|
|
7987
8147
|
}
|
|
7988
|
-
var IndexStore = class {
|
|
8148
|
+
var IndexStore = class _IndexStore {
|
|
7989
8149
|
db;
|
|
7990
8150
|
/** Absolute path to this project's index directory. */
|
|
7991
8151
|
indexDir;
|
|
@@ -8037,10 +8197,7 @@ var IndexStore = class {
|
|
|
8037
8197
|
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
8038
8198
|
throw new LockError(`SQLite lock conflict after ${MAX_LOCK_RETRIES} retries: ${msg}`);
|
|
8039
8199
|
}
|
|
8040
|
-
const delay = Math.min(
|
|
8041
|
-
LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt,
|
|
8042
|
-
LOCK_RETRY_MAX_DELAY_MS
|
|
8043
|
-
);
|
|
8200
|
+
const delay = Math.min(LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt, LOCK_RETRY_MAX_DELAY_MS);
|
|
8044
8201
|
sleepSync(delay);
|
|
8045
8202
|
}
|
|
8046
8203
|
}
|
|
@@ -8122,8 +8279,24 @@ var IndexStore = class {
|
|
|
8122
8279
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)");
|
|
8123
8280
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)");
|
|
8124
8281
|
try {
|
|
8125
|
-
this.db.exec(
|
|
8282
|
+
this.db.exec(
|
|
8283
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')"
|
|
8284
|
+
);
|
|
8126
8285
|
this.ftsAvailable = true;
|
|
8286
|
+
const symbolCount = Number(
|
|
8287
|
+
this.db.prepare("SELECT COUNT(*) AS n FROM symbols").get()?.n ?? 0
|
|
8288
|
+
);
|
|
8289
|
+
const ftsCount = Number(
|
|
8290
|
+
this.db.prepare("SELECT COUNT(*) AS n FROM symbols_fts").get()?.n ?? 0
|
|
8291
|
+
);
|
|
8292
|
+
if (symbolCount !== ftsCount) {
|
|
8293
|
+
this.db.exec("DELETE FROM symbols_fts");
|
|
8294
|
+
const insert = this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)");
|
|
8295
|
+
const rows = this.db.prepare("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
|
|
8296
|
+
for (const row of rows) {
|
|
8297
|
+
insert.run(row.id, buildIndexableText(row.name, row.signature, row.doc_comment));
|
|
8298
|
+
}
|
|
8299
|
+
}
|
|
8127
8300
|
} catch {
|
|
8128
8301
|
this.ftsAvailable = false;
|
|
8129
8302
|
}
|
|
@@ -8181,7 +8354,9 @@ var IndexStore = class {
|
|
|
8181
8354
|
deleteSymbolsForFile(file) {
|
|
8182
8355
|
this.runWithRetry(() => {
|
|
8183
8356
|
if (this.ftsAvailable) {
|
|
8184
|
-
this.stmt(
|
|
8357
|
+
this.stmt(
|
|
8358
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8359
|
+
).run(file);
|
|
8185
8360
|
}
|
|
8186
8361
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
8187
8362
|
});
|
|
@@ -8193,9 +8368,21 @@ var IndexStore = class {
|
|
|
8193
8368
|
*/
|
|
8194
8369
|
deleteFile(file) {
|
|
8195
8370
|
this.runWithRetry(() => {
|
|
8196
|
-
this.
|
|
8197
|
-
|
|
8198
|
-
|
|
8371
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
8372
|
+
try {
|
|
8373
|
+
if (this.ftsAvailable) {
|
|
8374
|
+
this.db.prepare(
|
|
8375
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8376
|
+
).run(file);
|
|
8377
|
+
}
|
|
8378
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)").run(file);
|
|
8379
|
+
this.db.prepare("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
8380
|
+
this.db.prepare("DELETE FROM files WHERE file = ?").run(file);
|
|
8381
|
+
this.db.exec("COMMIT");
|
|
8382
|
+
} catch (err) {
|
|
8383
|
+
this.db.exec("ROLLBACK");
|
|
8384
|
+
throw err;
|
|
8385
|
+
}
|
|
8199
8386
|
});
|
|
8200
8387
|
}
|
|
8201
8388
|
// ─── File metadata ──────────────────────────────────────────────────────────
|
|
@@ -8218,14 +8405,22 @@ var IndexStore = class {
|
|
|
8218
8405
|
).all(file);
|
|
8219
8406
|
if (!rows.length) return null;
|
|
8220
8407
|
const r = expectDefined2(rows[0]);
|
|
8221
|
-
return {
|
|
8408
|
+
return {
|
|
8409
|
+
file: r.file,
|
|
8410
|
+
lang: r.lang,
|
|
8411
|
+
mtimeMs: r.mtime_ms,
|
|
8412
|
+
symbolCount: r.symbol_count,
|
|
8413
|
+
lastIndexed: r.last_indexed
|
|
8414
|
+
};
|
|
8222
8415
|
}
|
|
8223
8416
|
getAllFileMetas() {
|
|
8224
|
-
return this.db.prepare(
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8417
|
+
return this.db.prepare("SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files").all().map((r) => ({
|
|
8418
|
+
file: r.file,
|
|
8419
|
+
lang: r.lang,
|
|
8420
|
+
mtimeMs: r.mtime_ms,
|
|
8421
|
+
symbolCount: r.symbol_count,
|
|
8422
|
+
lastIndexed: r.last_indexed
|
|
8423
|
+
}));
|
|
8229
8424
|
}
|
|
8230
8425
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
8231
8426
|
search(query, filter) {
|
|
@@ -8249,8 +8444,8 @@ var IndexStore = class {
|
|
|
8249
8444
|
values.push(filter.lang);
|
|
8250
8445
|
}
|
|
8251
8446
|
if (filter?.file) {
|
|
8252
|
-
conditions.push("file LIKE ?");
|
|
8253
|
-
values.push(`%${filter.file}%`);
|
|
8447
|
+
conditions.push("replace(file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
8448
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
8254
8449
|
}
|
|
8255
8450
|
if (query.trim()) {
|
|
8256
8451
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
@@ -8288,9 +8483,11 @@ var IndexStore = class {
|
|
|
8288
8483
|
* "users", camelCase-split text makes "complex" find "complexOperation").
|
|
8289
8484
|
*/
|
|
8290
8485
|
searchRanked(query, filter, limit) {
|
|
8486
|
+
const rawLimit = Number.isFinite(limit) ? Math.trunc(limit) : 20;
|
|
8487
|
+
const safeLimit = Math.max(1, Math.min(rawLimit, 100));
|
|
8291
8488
|
const tokens = tokenise(query);
|
|
8292
8489
|
if (tokens.length === 0 || !this.ftsAvailable) {
|
|
8293
|
-
return this.searchRankedFallback(query, filter,
|
|
8490
|
+
return this.searchRankedFallback(query, filter, safeLimit);
|
|
8294
8491
|
}
|
|
8295
8492
|
let effectiveKind = filter?.kind;
|
|
8296
8493
|
if (filter?.lspKind !== void 0) {
|
|
@@ -8310,11 +8507,13 @@ var IndexStore = class {
|
|
|
8310
8507
|
values.push(filter.lang);
|
|
8311
8508
|
}
|
|
8312
8509
|
if (filter?.file) {
|
|
8313
|
-
conditions.push("s.file LIKE ?");
|
|
8314
|
-
values.push(`%${filter.file}%`);
|
|
8510
|
+
conditions.push("replace(s.file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
8511
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
8315
8512
|
}
|
|
8316
8513
|
const where = conditions.join(" AND ");
|
|
8317
|
-
const countRows = this.db.prepare(
|
|
8514
|
+
const countRows = this.db.prepare(
|
|
8515
|
+
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
8516
|
+
).all(...values);
|
|
8318
8517
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
8319
8518
|
if (total === 0) return { results: [], total: 0 };
|
|
8320
8519
|
const rows = this.db.prepare(
|
|
@@ -8323,9 +8522,13 @@ var IndexStore = class {
|
|
|
8323
8522
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
8324
8523
|
FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
8325
8524
|
WHERE ${where}
|
|
8326
|
-
ORDER BY
|
|
8525
|
+
ORDER BY
|
|
8526
|
+
CASE WHEN lower(s.name) = lower(?) THEN 0
|
|
8527
|
+
WHEN lower(s.name) LIKE lower(?) ESCAPE '\\' THEN 1
|
|
8528
|
+
ELSE 2 END,
|
|
8529
|
+
bm25(symbols_fts), lower(s.name), s.file, s.line, s.col, s.id
|
|
8327
8530
|
LIMIT ?`
|
|
8328
|
-
).all(...values,
|
|
8531
|
+
).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
8329
8532
|
return {
|
|
8330
8533
|
results: rows.map((r) => ({
|
|
8331
8534
|
id: r.id,
|
|
@@ -8355,10 +8558,28 @@ var IndexStore = class {
|
|
|
8355
8558
|
}
|
|
8356
8559
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
8357
8560
|
const bm25 = buildBm25Index(
|
|
8358
|
-
candidates.map((c) => ({
|
|
8561
|
+
candidates.map((c) => ({
|
|
8562
|
+
id: c.id,
|
|
8563
|
+
text: buildIndexableText(c.name, c.signature, c.docComment)
|
|
8564
|
+
}))
|
|
8359
8565
|
);
|
|
8360
8566
|
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
8361
|
-
|
|
8567
|
+
const q = query.trim().toLowerCase();
|
|
8568
|
+
const rank = (id) => {
|
|
8569
|
+
const name = candidateById.get(id)?.name.toLowerCase() ?? "";
|
|
8570
|
+
if (name === q) return 0;
|
|
8571
|
+
if (name.startsWith(q)) return 1;
|
|
8572
|
+
return 2;
|
|
8573
|
+
};
|
|
8574
|
+
scored.sort((a, b) => {
|
|
8575
|
+
const rankDiff = rank(a.id) - rank(b.id);
|
|
8576
|
+
if (rankDiff !== 0) return rankDiff;
|
|
8577
|
+
const scoreDiff = b.score - a.score;
|
|
8578
|
+
if (scoreDiff !== 0) return scoreDiff;
|
|
8579
|
+
const left = expectDefined2(candidateById.get(a.id));
|
|
8580
|
+
const right = expectDefined2(candidateById.get(b.id));
|
|
8581
|
+
return left.name.localeCompare(right.name) || left.file.localeCompare(right.file) || left.line - right.line || left.col - right.col || left.id - right.id;
|
|
8582
|
+
});
|
|
8362
8583
|
const qTokens = tokenise(query);
|
|
8363
8584
|
const results = scored.slice(0, limit).map(({ id, score }) => {
|
|
8364
8585
|
const c = expectDefined2(candidateById.get(id));
|
|
@@ -8367,9 +8588,7 @@ var IndexStore = class {
|
|
|
8367
8588
|
return { results, total: candidates.length };
|
|
8368
8589
|
}
|
|
8369
8590
|
getAllIndexable() {
|
|
8370
|
-
return this.db.prepare("SELECT id, text FROM symbols").all().map(
|
|
8371
|
-
({ id, text }) => ({ id, text })
|
|
8372
|
-
);
|
|
8591
|
+
return this.db.prepare("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
|
|
8373
8592
|
}
|
|
8374
8593
|
/**
|
|
8375
8594
|
* Largest symbol id currently in the table (0 when empty). New ids must be
|
|
@@ -8385,22 +8604,16 @@ var IndexStore = class {
|
|
|
8385
8604
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
8386
8605
|
getStats() {
|
|
8387
8606
|
const sizeBytes = this.sizeBytes();
|
|
8388
|
-
const lastRows = this.db.prepare(
|
|
8389
|
-
"SELECT value FROM metadata WHERE key = 'last_indexed'"
|
|
8390
|
-
).all();
|
|
8607
|
+
const lastRows = this.db.prepare("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
8391
8608
|
const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
|
|
8392
8609
|
const totalRows = this.db.prepare("SELECT COUNT(*) FROM symbols").all();
|
|
8393
8610
|
const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
|
|
8394
8611
|
const fileRows = this.db.prepare("SELECT COUNT(*) FROM files").all();
|
|
8395
8612
|
const totalFiles = fileRows[0] ? Number(fileRows[0]["COUNT(*)"]) : 0;
|
|
8396
|
-
const langRows = this.db.prepare(
|
|
8397
|
-
"SELECT lang, COUNT(*) FROM symbols GROUP BY lang"
|
|
8398
|
-
).all();
|
|
8613
|
+
const langRows = this.db.prepare("SELECT lang, COUNT(*) FROM symbols GROUP BY lang").all();
|
|
8399
8614
|
const byLang = {};
|
|
8400
8615
|
for (const row of langRows) byLang[row.lang] = Number(row["COUNT(*)"]);
|
|
8401
|
-
const kindRows = this.db.prepare(
|
|
8402
|
-
"SELECT kind, COUNT(*) FROM symbols GROUP BY kind"
|
|
8403
|
-
).all();
|
|
8616
|
+
const kindRows = this.db.prepare("SELECT kind, COUNT(*) FROM symbols GROUP BY kind").all();
|
|
8404
8617
|
const byKind = {};
|
|
8405
8618
|
for (const row of kindRows) byKind[row.kind] = Number(row["COUNT(*)"]);
|
|
8406
8619
|
return {
|
|
@@ -8416,17 +8629,31 @@ var IndexStore = class {
|
|
|
8416
8629
|
}
|
|
8417
8630
|
setLastIndexed(ts2) {
|
|
8418
8631
|
this.runWithRetry(() => {
|
|
8419
|
-
this.db.prepare(
|
|
8420
|
-
|
|
8421
|
-
|
|
8632
|
+
this.db.prepare("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
|
|
8633
|
+
});
|
|
8634
|
+
}
|
|
8635
|
+
getMetadata(key) {
|
|
8636
|
+
const rows = this.db.prepare("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
8637
|
+
return rows[0]?.value;
|
|
8638
|
+
}
|
|
8639
|
+
setMetadata(key, value) {
|
|
8640
|
+
this.runWithRetry(() => {
|
|
8641
|
+
this.db.prepare("INSERT OR REPLACE INTO metadata(key, value) VALUES(?, ?)").run(key, value);
|
|
8422
8642
|
});
|
|
8423
8643
|
}
|
|
8424
8644
|
clearAll() {
|
|
8425
8645
|
this.runWithRetry(() => {
|
|
8426
|
-
this.db.exec("
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8646
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
8647
|
+
try {
|
|
8648
|
+
this.db.exec("DELETE FROM refs");
|
|
8649
|
+
this.db.exec("DELETE FROM symbols");
|
|
8650
|
+
this.db.exec("DELETE FROM files");
|
|
8651
|
+
if (this.ftsAvailable) this.db.exec("DELETE FROM symbols_fts");
|
|
8652
|
+
this.db.exec("COMMIT");
|
|
8653
|
+
} catch (err) {
|
|
8654
|
+
this.db.exec("ROLLBACK");
|
|
8655
|
+
throw err;
|
|
8656
|
+
}
|
|
8430
8657
|
});
|
|
8431
8658
|
}
|
|
8432
8659
|
// ─── Ref CRUD ────────────────────────────────────────────────────────────────
|
|
@@ -8517,6 +8744,7 @@ var IndexStore = class {
|
|
|
8517
8744
|
const allInserted = [];
|
|
8518
8745
|
const refsToInsert = [];
|
|
8519
8746
|
for (const entry of entries) {
|
|
8747
|
+
const insertedForEntry = [];
|
|
8520
8748
|
for (const s of entry.symbols) {
|
|
8521
8749
|
const id = nextId++;
|
|
8522
8750
|
symStmt.run(
|
|
@@ -8534,9 +8762,11 @@ var IndexStore = class {
|
|
|
8534
8762
|
s.file
|
|
8535
8763
|
);
|
|
8536
8764
|
ftsStmt?.run(id, buildIndexableText(s.name, s.signature, s.docComment));
|
|
8537
|
-
|
|
8765
|
+
const inserted = { ...s, id };
|
|
8766
|
+
allInserted.push(inserted);
|
|
8767
|
+
insertedForEntry.push(inserted);
|
|
8538
8768
|
}
|
|
8539
|
-
|
|
8769
|
+
refsToInsert.push(...assignRefsToSymbols(entry.refs, insertedForEntry));
|
|
8540
8770
|
}
|
|
8541
8771
|
if (refsToInsert.length > 0) {
|
|
8542
8772
|
const refStmt = this.db.prepare(
|
|
@@ -8574,9 +8804,7 @@ var IndexStore = class {
|
|
|
8574
8804
|
*/
|
|
8575
8805
|
deleteRefsForFile(file) {
|
|
8576
8806
|
this.runWithRetry(() => {
|
|
8577
|
-
this.db.prepare(
|
|
8578
|
-
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
8579
|
-
).run(file);
|
|
8807
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
|
|
8580
8808
|
});
|
|
8581
8809
|
}
|
|
8582
8810
|
/**
|
|
@@ -8617,9 +8845,7 @@ var IndexStore = class {
|
|
|
8617
8845
|
* Find all references FROM a given symbol (what does this symbol call/use?).
|
|
8618
8846
|
*/
|
|
8619
8847
|
findRefsFrom(symbolId) {
|
|
8620
|
-
return this.db.prepare(
|
|
8621
|
-
"SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?"
|
|
8622
|
-
).all(symbolId).map((r) => ({
|
|
8848
|
+
return this.db.prepare("SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?").all(symbolId).map((r) => ({
|
|
8623
8849
|
id: r.id,
|
|
8624
8850
|
fromId: r.from_id,
|
|
8625
8851
|
toName: r.to_name,
|
|
@@ -8636,6 +8862,325 @@ var IndexStore = class {
|
|
|
8636
8862
|
return 0;
|
|
8637
8863
|
}
|
|
8638
8864
|
}
|
|
8865
|
+
// ─── CodeMap graph aggregation ──────────────────────────────────────────────
|
|
8866
|
+
/**
|
|
8867
|
+
* Derive a monorepo package name from an absolute file path.
|
|
8868
|
+
* Handles both `packages/<name>/...` (workspace) and `src/<name>/...` layouts.
|
|
8869
|
+
* Returns `undefined` for files outside any recognisable package structure.
|
|
8870
|
+
*/
|
|
8871
|
+
static derivePackage(filePath) {
|
|
8872
|
+
const f = filePath.replace(/\\/g, "/");
|
|
8873
|
+
const pkgsIdx = f.indexOf("/packages/");
|
|
8874
|
+
if (pkgsIdx !== -1) {
|
|
8875
|
+
const rest = f.slice(pkgsIdx + "/packages/".length);
|
|
8876
|
+
const seg = rest.split("/")[0];
|
|
8877
|
+
return seg ? `@wrongstack/${seg}` : void 0;
|
|
8878
|
+
}
|
|
8879
|
+
const appsIdx = f.indexOf("/apps/");
|
|
8880
|
+
if (appsIdx !== -1) {
|
|
8881
|
+
const rest = f.slice(appsIdx + "/apps/".length);
|
|
8882
|
+
const seg = rest.split("/")[0];
|
|
8883
|
+
return seg ? `app:${seg}` : void 0;
|
|
8884
|
+
}
|
|
8885
|
+
return void 0;
|
|
8886
|
+
}
|
|
8887
|
+
static packageFromImport(moduleName) {
|
|
8888
|
+
if (!moduleName.startsWith("@wrongstack/")) return void 0;
|
|
8889
|
+
const parts = moduleName.split("/");
|
|
8890
|
+
return parts[1] ? `@wrongstack/${parts[1]}` : void 0;
|
|
8891
|
+
}
|
|
8892
|
+
static resolveRelativeImport(fromFile, moduleName, indexedFiles) {
|
|
8893
|
+
if (!moduleName.startsWith(".")) return void 0;
|
|
8894
|
+
const normalizedFrom = fromFile.replace(/\\/g, "/");
|
|
8895
|
+
const absolute = path13.posix.normalize(
|
|
8896
|
+
path13.posix.join(path13.posix.dirname(normalizedFrom), moduleName)
|
|
8897
|
+
);
|
|
8898
|
+
const extension = path13.posix.extname(absolute);
|
|
8899
|
+
const base = extension ? absolute.slice(0, -extension.length) : absolute;
|
|
8900
|
+
const candidates = [
|
|
8901
|
+
absolute,
|
|
8902
|
+
...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
|
|
8903
|
+
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path13.posix.join(absolute, `index${ext}`)),
|
|
8904
|
+
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path13.posix.join(base, `index${ext}`))
|
|
8905
|
+
];
|
|
8906
|
+
const indexedByPortablePath = new Map(
|
|
8907
|
+
[...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
|
|
8908
|
+
);
|
|
8909
|
+
for (const candidate of candidates) {
|
|
8910
|
+
const indexed = indexedByPortablePath.get(candidate.toLocaleLowerCase());
|
|
8911
|
+
if (indexed) return indexed;
|
|
8912
|
+
}
|
|
8913
|
+
return void 0;
|
|
8914
|
+
}
|
|
8915
|
+
/**
|
|
8916
|
+
* Package-level graph: each workspace package is a node; edges are derived
|
|
8917
|
+
* from cross-package symbol references (a symbol in package A references a
|
|
8918
|
+
* symbol resolved in package B). Node metadata includes symbol/file counts.
|
|
8919
|
+
*/
|
|
8920
|
+
getPackageGraph() {
|
|
8921
|
+
const symbols = this.db.prepare("SELECT file, id, name, kind, lang, line FROM symbols ORDER BY id").all();
|
|
8922
|
+
const pkgNodes = /* @__PURE__ */ new Map();
|
|
8923
|
+
const fileToPkg = /* @__PURE__ */ new Map();
|
|
8924
|
+
const symbolToPkg = /* @__PURE__ */ new Map();
|
|
8925
|
+
for (const s of symbols) {
|
|
8926
|
+
const pkg = _IndexStore.derivePackage(s.file) ?? "(root)";
|
|
8927
|
+
fileToPkg.set(s.file, pkg);
|
|
8928
|
+
symbolToPkg.set(s.id, pkg);
|
|
8929
|
+
const node = pkgNodes.get(pkg);
|
|
8930
|
+
if (node) {
|
|
8931
|
+
node.symbolCount = (node.symbolCount ?? 0) + 1;
|
|
8932
|
+
} else {
|
|
8933
|
+
pkgNodes.set(pkg, {
|
|
8934
|
+
id: `pkg:${pkg}`,
|
|
8935
|
+
label: pkg,
|
|
8936
|
+
kind: "package",
|
|
8937
|
+
package: pkg,
|
|
8938
|
+
symbolCount: 1,
|
|
8939
|
+
fileCount: 0
|
|
8940
|
+
});
|
|
8941
|
+
}
|
|
8942
|
+
}
|
|
8943
|
+
const files = this.db.prepare("SELECT DISTINCT file FROM files").all();
|
|
8944
|
+
for (const { file } of files) {
|
|
8945
|
+
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
8946
|
+
const node = pkgNodes.get(pkg);
|
|
8947
|
+
if (node) node.fileCount = (node.fileCount ?? 0) + 1;
|
|
8948
|
+
}
|
|
8949
|
+
const refRows = this.db.prepare(
|
|
8950
|
+
`SELECT r.from_id, r.to_id, r.call_type
|
|
8951
|
+
FROM refs r
|
|
8952
|
+
WHERE r.to_id IS NOT NULL`
|
|
8953
|
+
).all();
|
|
8954
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
8955
|
+
for (const r of refRows) {
|
|
8956
|
+
if (r.call_type === "import") continue;
|
|
8957
|
+
const fromPkg = symbolToPkg.get(r.from_id);
|
|
8958
|
+
const toPkg = symbolToPkg.get(r.to_id);
|
|
8959
|
+
if (!fromPkg || !toPkg || fromPkg === toPkg) continue;
|
|
8960
|
+
const key = `${fromPkg}\0${toPkg}`;
|
|
8961
|
+
let e = edgeMap.get(key);
|
|
8962
|
+
if (!e) {
|
|
8963
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8964
|
+
edgeMap.set(key, e);
|
|
8965
|
+
}
|
|
8966
|
+
e.weight++;
|
|
8967
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
8968
|
+
}
|
|
8969
|
+
const importRows = this.db.prepare(
|
|
8970
|
+
`SELECT r.from_id, r.to_name
|
|
8971
|
+
FROM refs r
|
|
8972
|
+
WHERE r.call_type = 'import'`
|
|
8973
|
+
).all();
|
|
8974
|
+
for (const r of importRows) {
|
|
8975
|
+
const fromPkg = symbolToPkg.get(r.from_id);
|
|
8976
|
+
const toPkg = _IndexStore.packageFromImport(r.to_name);
|
|
8977
|
+
if (!fromPkg || !toPkg || fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
8978
|
+
const key = `${fromPkg}\0${toPkg}`;
|
|
8979
|
+
let edge = edgeMap.get(key);
|
|
8980
|
+
if (!edge) {
|
|
8981
|
+
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8982
|
+
edgeMap.set(key, edge);
|
|
8983
|
+
}
|
|
8984
|
+
edge.weight++;
|
|
8985
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
|
|
8986
|
+
}
|
|
8987
|
+
const edges = [];
|
|
8988
|
+
for (const [key, e] of edgeMap) {
|
|
8989
|
+
const [source, target] = key.split("\0");
|
|
8990
|
+
let bestType = "call";
|
|
8991
|
+
let bestCount = 0;
|
|
8992
|
+
for (const [t, c] of e.types) {
|
|
8993
|
+
if (c > bestCount) {
|
|
8994
|
+
bestType = t;
|
|
8995
|
+
bestCount = c;
|
|
8996
|
+
}
|
|
8997
|
+
}
|
|
8998
|
+
edges.push({
|
|
8999
|
+
source: `pkg:${source}`,
|
|
9000
|
+
target: `pkg:${target}`,
|
|
9001
|
+
weight: e.weight,
|
|
9002
|
+
refType: bestType
|
|
9003
|
+
});
|
|
9004
|
+
}
|
|
9005
|
+
return { nodes: [...pkgNodes.values()], edges };
|
|
9006
|
+
}
|
|
9007
|
+
/**
|
|
9008
|
+
* File-level graph for a single package: each file is a node; edges are
|
|
9009
|
+
* derived from cross-file symbol references within the package.
|
|
9010
|
+
*/
|
|
9011
|
+
getFileGraph(packageFilter) {
|
|
9012
|
+
const allSymbols = this.db.prepare("SELECT file, id, name, kind, lang, line FROM symbols ORDER BY id").all();
|
|
9013
|
+
const pkgSyms = allSymbols.filter(
|
|
9014
|
+
(s) => (_IndexStore.derivePackage(s.file) ?? "(root)") === packageFilter
|
|
9015
|
+
);
|
|
9016
|
+
if (pkgSyms.length === 0) return { nodes: [], edges: [] };
|
|
9017
|
+
const fileNodes = /* @__PURE__ */ new Map();
|
|
9018
|
+
const symToFile = /* @__PURE__ */ new Map();
|
|
9019
|
+
const fileStats = /* @__PURE__ */ new Map();
|
|
9020
|
+
for (const s of allSymbols) {
|
|
9021
|
+
symToFile.set(s.id, s.file);
|
|
9022
|
+
const current = fileStats.get(s.file);
|
|
9023
|
+
fileStats.set(s.file, {
|
|
9024
|
+
count: (current?.count ?? 0) + 1,
|
|
9025
|
+
lang: current?.lang ?? s.lang
|
|
9026
|
+
});
|
|
9027
|
+
}
|
|
9028
|
+
const localFiles = new Set(pkgSyms.map((s) => s.file));
|
|
9029
|
+
const indexedFiles = new Set(allSymbols.map((s) => s.file));
|
|
9030
|
+
const ensureFileNode = (file) => {
|
|
9031
|
+
if (fileNodes.has(file)) return;
|
|
9032
|
+
const stats = fileStats.get(file);
|
|
9033
|
+
fileNodes.set(file, {
|
|
9034
|
+
id: `file:${file}`,
|
|
9035
|
+
label: file.replace(/\\/g, "/").split("/").pop() ?? file,
|
|
9036
|
+
kind: "file",
|
|
9037
|
+
package: _IndexStore.derivePackage(file) ?? "(root)",
|
|
9038
|
+
file,
|
|
9039
|
+
symbolCount: stats?.count ?? 0,
|
|
9040
|
+
lang: stats?.lang,
|
|
9041
|
+
external: !localFiles.has(file)
|
|
9042
|
+
});
|
|
9043
|
+
};
|
|
9044
|
+
for (const file of localFiles) {
|
|
9045
|
+
ensureFileNode(file);
|
|
9046
|
+
}
|
|
9047
|
+
const refRows = this.db.prepare(
|
|
9048
|
+
`SELECT r.from_id, r.to_id, r.call_type
|
|
9049
|
+
FROM refs r
|
|
9050
|
+
WHERE r.to_id IS NOT NULL`
|
|
9051
|
+
).all();
|
|
9052
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
9053
|
+
for (const r of refRows) {
|
|
9054
|
+
if (r.call_type === "import") continue;
|
|
9055
|
+
const fromFile = symToFile.get(r.from_id);
|
|
9056
|
+
const toFile = symToFile.get(r.to_id);
|
|
9057
|
+
if (!fromFile || !toFile || fromFile === toFile) continue;
|
|
9058
|
+
if (!localFiles.has(fromFile) && !localFiles.has(toFile)) continue;
|
|
9059
|
+
ensureFileNode(fromFile);
|
|
9060
|
+
ensureFileNode(toFile);
|
|
9061
|
+
const key = `${fromFile}\0${toFile}`;
|
|
9062
|
+
let e = edgeMap.get(key);
|
|
9063
|
+
if (!e) {
|
|
9064
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9065
|
+
edgeMap.set(key, e);
|
|
9066
|
+
}
|
|
9067
|
+
e.weight++;
|
|
9068
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
9069
|
+
}
|
|
9070
|
+
const importRows = this.db.prepare(
|
|
9071
|
+
`SELECT r.from_id, r.to_name
|
|
9072
|
+
FROM refs r
|
|
9073
|
+
WHERE r.call_type = 'import'`
|
|
9074
|
+
).all();
|
|
9075
|
+
for (const r of importRows) {
|
|
9076
|
+
const fromFile = symToFile.get(r.from_id);
|
|
9077
|
+
if (!fromFile || !localFiles.has(fromFile)) continue;
|
|
9078
|
+
const toFile = _IndexStore.resolveRelativeImport(fromFile, r.to_name, indexedFiles);
|
|
9079
|
+
if (!toFile || fromFile === toFile) continue;
|
|
9080
|
+
ensureFileNode(fromFile);
|
|
9081
|
+
ensureFileNode(toFile);
|
|
9082
|
+
const key = `${fromFile}\0${toFile}`;
|
|
9083
|
+
let edge = edgeMap.get(key);
|
|
9084
|
+
if (!edge) {
|
|
9085
|
+
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9086
|
+
edgeMap.set(key, edge);
|
|
9087
|
+
}
|
|
9088
|
+
edge.weight++;
|
|
9089
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
|
|
9090
|
+
}
|
|
9091
|
+
const edges = [];
|
|
9092
|
+
for (const [key, e] of edgeMap) {
|
|
9093
|
+
const [source, target] = key.split("\0");
|
|
9094
|
+
let bestType = "call";
|
|
9095
|
+
let bestCount = 0;
|
|
9096
|
+
for (const [t, c] of e.types) {
|
|
9097
|
+
if (c > bestCount) {
|
|
9098
|
+
bestType = t;
|
|
9099
|
+
bestCount = c;
|
|
9100
|
+
}
|
|
9101
|
+
}
|
|
9102
|
+
edges.push({
|
|
9103
|
+
source: `file:${source}`,
|
|
9104
|
+
target: `file:${target}`,
|
|
9105
|
+
weight: e.weight,
|
|
9106
|
+
refType: bestType
|
|
9107
|
+
});
|
|
9108
|
+
}
|
|
9109
|
+
return { nodes: [...fileNodes.values()], edges };
|
|
9110
|
+
}
|
|
9111
|
+
/**
|
|
9112
|
+
* Symbol-level graph for a single file: each symbol is a node; edges are
|
|
9113
|
+
* derived from intra-file and cross-file symbol references (who calls whom).
|
|
9114
|
+
*/
|
|
9115
|
+
getSymbolGraph(fileFilter) {
|
|
9116
|
+
const allSymbols = this.db.prepare(
|
|
9117
|
+
"SELECT id, name, kind, lang, file, line, signature, scope FROM symbols ORDER BY file, line, id"
|
|
9118
|
+
).all();
|
|
9119
|
+
const syms = allSymbols.filter((symbol) => symbol.file === fileFilter);
|
|
9120
|
+
if (syms.length === 0) return { nodes: [], edges: [] };
|
|
9121
|
+
const symById = new Map(allSymbols.map((symbol) => [symbol.id, symbol]));
|
|
9122
|
+
const relatedIds = new Set(syms.map((symbol) => symbol.id));
|
|
9123
|
+
const toGraphNode = (s) => ({
|
|
9124
|
+
id: `sym:${s.id}`,
|
|
9125
|
+
label: s.name,
|
|
9126
|
+
kind: "symbol",
|
|
9127
|
+
symbolId: s.id,
|
|
9128
|
+
symbolKind: s.kind,
|
|
9129
|
+
file: s.file,
|
|
9130
|
+
package: _IndexStore.derivePackage(s.file) ?? "(root)",
|
|
9131
|
+
lang: s.lang,
|
|
9132
|
+
line: s.line,
|
|
9133
|
+
signature: s.signature,
|
|
9134
|
+
scope: s.scope,
|
|
9135
|
+
external: s.file !== fileFilter
|
|
9136
|
+
});
|
|
9137
|
+
const symIds = new Set(syms.map((s) => s.id));
|
|
9138
|
+
const refRows = this.db.prepare(
|
|
9139
|
+
`SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
9140
|
+
FROM refs r
|
|
9141
|
+
WHERE r.from_id IN (SELECT id FROM symbols WHERE file = ?)
|
|
9142
|
+
OR r.to_id IN (SELECT id FROM symbols WHERE file = ?)`
|
|
9143
|
+
).all(fileFilter, fileFilter);
|
|
9144
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
9145
|
+
for (const r of refRows) {
|
|
9146
|
+
if (r.to_id === null) continue;
|
|
9147
|
+
if (!symIds.has(r.from_id) && !symIds.has(r.to_id)) continue;
|
|
9148
|
+
relatedIds.add(r.from_id);
|
|
9149
|
+
relatedIds.add(r.to_id);
|
|
9150
|
+
const key = `${r.from_id}\0${r.to_id}`;
|
|
9151
|
+
let e = edgeMap.get(key);
|
|
9152
|
+
if (!e) {
|
|
9153
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9154
|
+
edgeMap.set(key, e);
|
|
9155
|
+
}
|
|
9156
|
+
e.weight++;
|
|
9157
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
9158
|
+
}
|
|
9159
|
+
const edges = [];
|
|
9160
|
+
for (const [key, e] of edgeMap) {
|
|
9161
|
+
const [fromId, toId] = key.split("\0").map(Number);
|
|
9162
|
+
let bestType = "call";
|
|
9163
|
+
let bestCount = 0;
|
|
9164
|
+
for (const [t, c] of e.types) {
|
|
9165
|
+
if (c > bestCount) {
|
|
9166
|
+
bestType = t;
|
|
9167
|
+
bestCount = c;
|
|
9168
|
+
}
|
|
9169
|
+
}
|
|
9170
|
+
edges.push({
|
|
9171
|
+
source: `sym:${fromId}`,
|
|
9172
|
+
target: `sym:${toId}`,
|
|
9173
|
+
weight: e.weight,
|
|
9174
|
+
refType: bestType
|
|
9175
|
+
});
|
|
9176
|
+
}
|
|
9177
|
+
const nodes = [...relatedIds].map((id) => symById.get(id)).filter((symbol) => symbol !== void 0).sort((a, b) => {
|
|
9178
|
+
const aExternal = a.file === fileFilter ? 0 : 1;
|
|
9179
|
+
const bExternal = b.file === fileFilter ? 0 : 1;
|
|
9180
|
+
return aExternal - bExternal || a.file.localeCompare(b.file) || a.line - b.line || a.id - b.id;
|
|
9181
|
+
}).map(toGraphNode);
|
|
9182
|
+
return { nodes, edges };
|
|
9183
|
+
}
|
|
8639
9184
|
close() {
|
|
8640
9185
|
this.stmtCache.clear();
|
|
8641
9186
|
try {
|
|
@@ -8730,6 +9275,14 @@ function getJsDoc(node, sourceFile) {
|
|
|
8730
9275
|
}
|
|
8731
9276
|
return "";
|
|
8732
9277
|
}
|
|
9278
|
+
function hasFunctionLikeAncestor(node) {
|
|
9279
|
+
let current = node.parent;
|
|
9280
|
+
while (current) {
|
|
9281
|
+
if (ts.isFunctionLike(current)) return true;
|
|
9282
|
+
current = current.parent;
|
|
9283
|
+
}
|
|
9284
|
+
return false;
|
|
9285
|
+
}
|
|
8733
9286
|
function buildScope(node) {
|
|
8734
9287
|
const parts = [];
|
|
8735
9288
|
let current = node.parent;
|
|
@@ -8757,6 +9310,10 @@ function parseSymbols(opts) {
|
|
|
8757
9310
|
function visit(node) {
|
|
8758
9311
|
const kind = kindOf(node);
|
|
8759
9312
|
if (kind) {
|
|
9313
|
+
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && hasFunctionLikeAncestor(node)) {
|
|
9314
|
+
ts.forEachChild(node, visit);
|
|
9315
|
+
return;
|
|
9316
|
+
}
|
|
8760
9317
|
const nameNode = node.name;
|
|
8761
9318
|
if (!nameNode || !ts.isIdentifier(nameNode)) return;
|
|
8762
9319
|
const name = nameNode.text;
|
|
@@ -8844,6 +9401,7 @@ function detectLang(file) {
|
|
|
8844
9401
|
}
|
|
8845
9402
|
|
|
8846
9403
|
// src/codebase-index/go-parser.ts
|
|
9404
|
+
init_win32_resolve();
|
|
8847
9405
|
import { spawn as spawn4 } from "node:child_process";
|
|
8848
9406
|
import * as os5 from "node:os";
|
|
8849
9407
|
import * as path14 from "node:path";
|
|
@@ -9152,27 +9710,42 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
9152
9710
|
try {
|
|
9153
9711
|
const scriptPath = path14.join(tmpDir, "parse.go");
|
|
9154
9712
|
await fs10.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
9155
|
-
const
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9713
|
+
const goBinary = resolveWin32Command("go");
|
|
9714
|
+
const goResult = await new Promise(
|
|
9715
|
+
(resolve15, reject) => {
|
|
9716
|
+
let settled = false;
|
|
9717
|
+
const proc = spawn4(goBinary, ["run", scriptPath], {
|
|
9718
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
9719
|
+
windowsHide: true
|
|
9720
|
+
});
|
|
9721
|
+
proc.on("error", (err) => {
|
|
9722
|
+
if (settled) return;
|
|
9723
|
+
settled = true;
|
|
9724
|
+
reject(err);
|
|
9725
|
+
});
|
|
9726
|
+
let stdout2 = "";
|
|
9727
|
+
proc.stdout?.on("data", (chunk) => {
|
|
9728
|
+
stdout2 += chunk.toString();
|
|
9729
|
+
});
|
|
9730
|
+
proc.stderr?.resume();
|
|
9731
|
+
proc.stdin?.write(content);
|
|
9732
|
+
proc.stdin?.end();
|
|
9733
|
+
const timer = setTimeout(() => {
|
|
9734
|
+
if (settled) return;
|
|
9735
|
+
settled = true;
|
|
9171
9736
|
proc.kill("SIGKILL");
|
|
9172
9737
|
reject(new Error("timeout"));
|
|
9173
|
-
}, 15e3)
|
|
9174
|
-
|
|
9175
|
-
|
|
9738
|
+
}, 15e3);
|
|
9739
|
+
timer.unref?.();
|
|
9740
|
+
proc.on("close", (code2) => {
|
|
9741
|
+
if (settled) return;
|
|
9742
|
+
settled = true;
|
|
9743
|
+
clearTimeout(timer);
|
|
9744
|
+
resolve15({ code: code2, stdout: stdout2 });
|
|
9745
|
+
});
|
|
9746
|
+
}
|
|
9747
|
+
);
|
|
9748
|
+
const { code, stdout } = goResult;
|
|
9176
9749
|
if (code !== 0 || !stdout.trim()) {
|
|
9177
9750
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
9178
9751
|
}
|
|
@@ -9199,7 +9772,8 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
9199
9772
|
}
|
|
9200
9773
|
|
|
9201
9774
|
// src/codebase-index/py-parser.ts
|
|
9202
|
-
|
|
9775
|
+
init_win32_resolve();
|
|
9776
|
+
import { spawn as spawn5, spawnSync } from "node:child_process";
|
|
9203
9777
|
import * as fs11 from "node:fs/promises";
|
|
9204
9778
|
import * as os6 from "node:os";
|
|
9205
9779
|
import * as path15 from "node:path";
|
|
@@ -9414,36 +9988,74 @@ visitor.visit(tree)
|
|
|
9414
9988
|
|
|
9415
9989
|
print(json.dumps([s.to_dict() for s in syms]))
|
|
9416
9990
|
`;
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9991
|
+
function resolvePython() {
|
|
9992
|
+
const candidates = process.platform === "win32" ? ["python3", "python", "py"] : ["python3", "python"];
|
|
9993
|
+
for (const name of candidates) {
|
|
9994
|
+
const resolved = resolveWin32Command(name);
|
|
9995
|
+
const result = spawnSync(resolved, ["--version"], {
|
|
9996
|
+
stdio: "pipe",
|
|
9997
|
+
timeout: 5e3
|
|
9998
|
+
});
|
|
9999
|
+
if (result.error) continue;
|
|
10000
|
+
if (result.status !== 0) continue;
|
|
10001
|
+
return resolved;
|
|
10002
|
+
}
|
|
10003
|
+
return null;
|
|
10004
|
+
}
|
|
10005
|
+
function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
10006
|
+
return new Promise((resolve15, reject) => {
|
|
10007
|
+
let settled = false;
|
|
10008
|
+
const proc = spawn5(pyBinary, [scriptPath, filePath], {
|
|
9427
10009
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9428
10010
|
windowsHide: true
|
|
9429
10011
|
});
|
|
10012
|
+
proc.on("error", (err) => {
|
|
10013
|
+
if (settled) return;
|
|
10014
|
+
settled = true;
|
|
10015
|
+
reject(err);
|
|
10016
|
+
});
|
|
9430
10017
|
proc.stdin?.write(content);
|
|
9431
10018
|
proc.stdin?.end();
|
|
9432
10019
|
let stdout = "";
|
|
9433
10020
|
proc.stdout?.on("data", (chunk) => {
|
|
9434
10021
|
stdout += chunk.toString();
|
|
9435
10022
|
});
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
)
|
|
9446
|
-
|
|
10023
|
+
proc.stderr?.resume();
|
|
10024
|
+
const timer = setTimeout(() => {
|
|
10025
|
+
if (settled) return;
|
|
10026
|
+
settled = true;
|
|
10027
|
+
proc.kill("SIGKILL");
|
|
10028
|
+
reject(new Error("timeout"));
|
|
10029
|
+
}, 15e3);
|
|
10030
|
+
timer.unref?.();
|
|
10031
|
+
proc.on("close", (code) => {
|
|
10032
|
+
if (settled) return;
|
|
10033
|
+
settled = true;
|
|
10034
|
+
clearTimeout(timer);
|
|
10035
|
+
resolve15({ code, stdout });
|
|
10036
|
+
});
|
|
10037
|
+
});
|
|
10038
|
+
}
|
|
10039
|
+
var _cachedScriptPath = null;
|
|
10040
|
+
var _cachedPyBinary = null;
|
|
10041
|
+
async function syncPyParse(filePath, content, lang) {
|
|
10042
|
+
try {
|
|
10043
|
+
if (!_cachedScriptPath) {
|
|
10044
|
+
const tmpDir = path15.join(os6.tmpdir(), "ws-py-parse");
|
|
10045
|
+
await fs11.mkdir(tmpDir, { recursive: true });
|
|
10046
|
+
_cachedScriptPath = path15.join(tmpDir, "parse.py");
|
|
10047
|
+
await fs11.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
10048
|
+
}
|
|
10049
|
+
if (!_cachedPyBinary) {
|
|
10050
|
+
_cachedPyBinary = resolvePython();
|
|
10051
|
+
if (!_cachedPyBinary) return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
10052
|
+
}
|
|
10053
|
+
const { code, stdout } = await spawnPyParser(
|
|
10054
|
+
_cachedPyBinary,
|
|
10055
|
+
_cachedScriptPath,
|
|
10056
|
+
filePath,
|
|
10057
|
+
content
|
|
10058
|
+
);
|
|
9447
10059
|
if (code !== 0 || !stdout.trim()) {
|
|
9448
10060
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
9449
10061
|
}
|
|
@@ -9468,6 +10080,7 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
9468
10080
|
}
|
|
9469
10081
|
|
|
9470
10082
|
// src/codebase-index/rs-parser.ts
|
|
10083
|
+
init_win32_resolve();
|
|
9471
10084
|
import { expectDefined as expectDefined3 } from "@wrongstack/core";
|
|
9472
10085
|
import { execFileSync, spawn as spawn6 } from "node:child_process";
|
|
9473
10086
|
import * as fs12 from "node:fs/promises";
|
|
@@ -9512,30 +10125,45 @@ async function tryNativeParse(file, content) {
|
|
|
9512
10125
|
const crateDir = path16.join(toolsDir, "syn-parser");
|
|
9513
10126
|
const tmpFile = path16.join(crateDir, "src", "input.rs");
|
|
9514
10127
|
await fs12.writeFile(tmpFile, content, "utf8");
|
|
9515
|
-
const
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
10128
|
+
const cargoBinary = resolveWin32Command("cargo");
|
|
10129
|
+
const result = await new Promise(
|
|
10130
|
+
(resolve15, reject) => {
|
|
10131
|
+
let settled = false;
|
|
10132
|
+
const proc = spawn6(
|
|
10133
|
+
cargoBinary,
|
|
10134
|
+
["run", "--manifest-path", path16.join(toolsDir, "Cargo.toml")],
|
|
10135
|
+
{
|
|
10136
|
+
cwd: process.cwd(),
|
|
10137
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
10138
|
+
windowsHide: true
|
|
10139
|
+
}
|
|
10140
|
+
);
|
|
10141
|
+
proc.on("error", (err) => {
|
|
10142
|
+
if (settled) return;
|
|
10143
|
+
settled = true;
|
|
10144
|
+
reject(err);
|
|
10145
|
+
});
|
|
10146
|
+
let stdout2 = "";
|
|
10147
|
+
proc.stdout?.on("data", (chunk) => {
|
|
10148
|
+
stdout2 += chunk.toString();
|
|
10149
|
+
});
|
|
10150
|
+
proc.stderr?.resume();
|
|
10151
|
+
const timer = setTimeout(() => {
|
|
10152
|
+
if (settled) return;
|
|
10153
|
+
settled = true;
|
|
9534
10154
|
proc.kill("SIGKILL");
|
|
9535
10155
|
reject(new Error("timeout"));
|
|
9536
|
-
}, 15e3)
|
|
9537
|
-
|
|
9538
|
-
|
|
10156
|
+
}, 15e3);
|
|
10157
|
+
timer.unref?.();
|
|
10158
|
+
proc.on("close", (c) => {
|
|
10159
|
+
if (settled) return;
|
|
10160
|
+
settled = true;
|
|
10161
|
+
clearTimeout(timer);
|
|
10162
|
+
resolve15({ code: c, stdout: stdout2 });
|
|
10163
|
+
});
|
|
10164
|
+
}
|
|
10165
|
+
);
|
|
10166
|
+
const { code, stdout } = result;
|
|
9539
10167
|
if (code === 0 && stdout.trim()) {
|
|
9540
10168
|
const symbols = JSON.parse(stdout.trim());
|
|
9541
10169
|
return {
|
|
@@ -9630,9 +10258,9 @@ function parseSymbols5(opts) {
|
|
|
9630
10258
|
function regexParse2(opts) {
|
|
9631
10259
|
const { file, content, lang } = opts;
|
|
9632
10260
|
const symbols = [];
|
|
9633
|
-
const
|
|
9634
|
-
const isPackageJson =
|
|
9635
|
-
const isTsconfig =
|
|
10261
|
+
const basename10 = path17.basename(file).toLowerCase();
|
|
10262
|
+
const isPackageJson = basename10 === "package.json";
|
|
10263
|
+
const isTsconfig = basename10 === "tsconfig.json" || basename10 === "tsconfig.build.json";
|
|
9636
10264
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
9637
10265
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
9638
10266
|
const lines = content.split("\n");
|
|
@@ -10034,9 +10662,7 @@ function yieldEventLoop() {
|
|
|
10034
10662
|
function throwIfAborted(signal) {
|
|
10035
10663
|
if (!signal?.aborted) return;
|
|
10036
10664
|
if (signal.reason instanceof Error) throw signal.reason;
|
|
10037
|
-
throw new Error(
|
|
10038
|
-
typeof signal.reason === "string" ? signal.reason : "Indexing cancelled"
|
|
10039
|
-
);
|
|
10665
|
+
throw new Error(typeof signal.reason === "string" ? signal.reason : "Indexing cancelled");
|
|
10040
10666
|
}
|
|
10041
10667
|
function isAbortError(err) {
|
|
10042
10668
|
return err instanceof DOMException && err.name === "AbortError";
|
|
@@ -10052,8 +10678,20 @@ var DEFAULT_IGNORE = [
|
|
|
10052
10678
|
"__snapshots__",
|
|
10053
10679
|
".nyc_output"
|
|
10054
10680
|
];
|
|
10681
|
+
var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
|
|
10682
|
+
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
10683
|
+
function isWithinProject(projectRoot, file) {
|
|
10684
|
+
const rel = path19.relative(projectRoot, file);
|
|
10685
|
+
return rel !== "" && !rel.startsWith(`..${path19.sep}`) && rel !== ".." && !path19.isAbsolute(rel);
|
|
10686
|
+
}
|
|
10687
|
+
function isMissingPathError(err) {
|
|
10688
|
+
const code = err?.code;
|
|
10689
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
10690
|
+
}
|
|
10055
10691
|
async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
10056
10692
|
const results = [];
|
|
10693
|
+
const errors = [];
|
|
10694
|
+
let complete = true;
|
|
10057
10695
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
10058
10696
|
const globs = [
|
|
10059
10697
|
{ ext: ".ts", pat: compileGlob2("**/*.ts") },
|
|
@@ -10077,7 +10715,9 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10077
10715
|
let entries;
|
|
10078
10716
|
try {
|
|
10079
10717
|
entries = await fs14.readdir(dir, { withFileTypes: true });
|
|
10080
|
-
} catch {
|
|
10718
|
+
} catch (err) {
|
|
10719
|
+
complete = false;
|
|
10720
|
+
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10081
10721
|
return;
|
|
10082
10722
|
}
|
|
10083
10723
|
dirCount++;
|
|
@@ -10089,8 +10729,8 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10089
10729
|
if (isGitIgnored(rel, true)) continue;
|
|
10090
10730
|
await walk2(full);
|
|
10091
10731
|
} else if (e.isFile()) {
|
|
10092
|
-
if (isGitIgnored(rel, false)) continue;
|
|
10093
|
-
const ext = path19.extname(e.name);
|
|
10732
|
+
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
10733
|
+
const ext = path19.extname(e.name).toLowerCase();
|
|
10094
10734
|
for (const { ext: extName, pat } of globs) {
|
|
10095
10735
|
if (ext === extName && (pat.test(rel) || pat.test(e.name))) {
|
|
10096
10736
|
results.push(full);
|
|
@@ -10101,7 +10741,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10101
10741
|
}
|
|
10102
10742
|
};
|
|
10103
10743
|
await walk2(projectRoot);
|
|
10104
|
-
return results;
|
|
10744
|
+
return { files: results, complete, errors };
|
|
10105
10745
|
}
|
|
10106
10746
|
async function parseFile(file, content, lang) {
|
|
10107
10747
|
switch (lang) {
|
|
@@ -10124,6 +10764,26 @@ async function parseFile(file, content, lang) {
|
|
|
10124
10764
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
10125
10765
|
}
|
|
10126
10766
|
}
|
|
10767
|
+
function assignRefsToSymbols2(refs, symbols) {
|
|
10768
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
10769
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
10770
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10771
|
+
const assigned = [];
|
|
10772
|
+
for (const ref of refs) {
|
|
10773
|
+
let owner2;
|
|
10774
|
+
for (const symbol of ordered) {
|
|
10775
|
+
if (symbol.line > ref.line) break;
|
|
10776
|
+
owner2 = symbol;
|
|
10777
|
+
}
|
|
10778
|
+
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
10779
|
+
if (!owner2 || owner2.id <= 0) continue;
|
|
10780
|
+
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
10781
|
+
if (seen.has(key)) continue;
|
|
10782
|
+
seen.add(key);
|
|
10783
|
+
assigned.push({ ...ref, fromId: owner2.id });
|
|
10784
|
+
}
|
|
10785
|
+
return assigned;
|
|
10786
|
+
}
|
|
10127
10787
|
async function runIndexer(_ctx, opts) {
|
|
10128
10788
|
const store = new IndexStore(opts.projectRoot, { indexDir: opts.indexDir });
|
|
10129
10789
|
try {
|
|
@@ -10136,7 +10796,9 @@ async function runIndexer(_ctx, opts) {
|
|
|
10136
10796
|
}
|
|
10137
10797
|
}
|
|
10138
10798
|
async function runIndexerWithStore(store, opts) {
|
|
10139
|
-
const { projectRoot,
|
|
10799
|
+
const { projectRoot, langs, ignore = [], signal } = opts;
|
|
10800
|
+
const relationGraphVersion = "2";
|
|
10801
|
+
const force = (opts.force ?? false) || store.getMetadata("relation_graph_version") !== relationGraphVersion;
|
|
10140
10802
|
const startMs = Date.now();
|
|
10141
10803
|
const errors = [];
|
|
10142
10804
|
const langStats = {};
|
|
@@ -10145,10 +10807,18 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10145
10807
|
const isGitIgnored = await loadGitignoreMatcher(projectRoot);
|
|
10146
10808
|
let files;
|
|
10147
10809
|
let discoveredFiles = null;
|
|
10810
|
+
let discoveryComplete = true;
|
|
10148
10811
|
if (opts.files && opts.files.length > 0) {
|
|
10149
|
-
files = opts.files.map((f) => path19.resolve(projectRoot, f)).filter((f) =>
|
|
10812
|
+
files = opts.files.map((f) => path19.resolve(projectRoot, f)).filter((f) => {
|
|
10813
|
+
if (!isWithinProject(projectRoot, f)) return false;
|
|
10814
|
+
const rel = path19.relative(projectRoot, f).replace(/\\/g, "/");
|
|
10815
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path19.basename(f)) && !isGitIgnored(rel, false);
|
|
10816
|
+
});
|
|
10150
10817
|
} else {
|
|
10151
|
-
|
|
10818
|
+
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
10819
|
+
files = discovery.files;
|
|
10820
|
+
errors.push(...discovery.errors);
|
|
10821
|
+
discoveryComplete = discovery.complete;
|
|
10152
10822
|
discoveredFiles = new Set(files);
|
|
10153
10823
|
}
|
|
10154
10824
|
if (langs && langs.length > 0) {
|
|
@@ -10173,36 +10843,66 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10173
10843
|
}
|
|
10174
10844
|
const statOpts = signal ? { signal } : {};
|
|
10175
10845
|
const statReadParse = await Promise.allSettled(
|
|
10176
|
-
batchFiles.map(
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
|
|
10189
|
-
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
if (
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10846
|
+
batchFiles.map(
|
|
10847
|
+
async (file) => {
|
|
10848
|
+
let stat18;
|
|
10849
|
+
try {
|
|
10850
|
+
stat18 = await fs14.stat(file, statOpts);
|
|
10851
|
+
} catch (e) {
|
|
10852
|
+
if (isAbortError(e)) throw e;
|
|
10853
|
+
return {
|
|
10854
|
+
file,
|
|
10855
|
+
stat: null,
|
|
10856
|
+
lang: "",
|
|
10857
|
+
parsed: null,
|
|
10858
|
+
error: `stat error: ${e instanceof Error ? e.message : String(e)}`,
|
|
10859
|
+
missing: isMissingPathError(e)
|
|
10860
|
+
};
|
|
10861
|
+
}
|
|
10862
|
+
if (!stat18.isFile()) return { file, stat: stat18, lang: "", parsed: null };
|
|
10863
|
+
const lang = detectLang(file);
|
|
10864
|
+
if (!lang) return { file, stat: stat18, lang: "", parsed: null };
|
|
10865
|
+
if (stat18.size > MAX_INDEX_FILE_BYTES) {
|
|
10866
|
+
return {
|
|
10867
|
+
file,
|
|
10868
|
+
stat: stat18,
|
|
10869
|
+
lang,
|
|
10870
|
+
parsed: null,
|
|
10871
|
+
error: `file too large (${stat18.size} bytes; max ${MAX_INDEX_FILE_BYTES})`
|
|
10872
|
+
};
|
|
10873
|
+
}
|
|
10874
|
+
const meta = existingMeta.get(file);
|
|
10875
|
+
if (!force && meta && meta.mtimeMs === Math.floor(stat18.mtimeMs)) {
|
|
10876
|
+
return { file, stat: stat18, lang, parsed: null, skippedMeta: meta };
|
|
10877
|
+
}
|
|
10878
|
+
let content;
|
|
10879
|
+
try {
|
|
10880
|
+
content = await fs14.readFile(file, { encoding: "utf8", signal });
|
|
10881
|
+
} catch (e) {
|
|
10882
|
+
if (isAbortError(e)) throw e;
|
|
10883
|
+
return {
|
|
10884
|
+
file,
|
|
10885
|
+
stat: stat18,
|
|
10886
|
+
lang,
|
|
10887
|
+
parsed: null,
|
|
10888
|
+
error: `read error: ${e instanceof Error ? e.message : String(e)}`
|
|
10889
|
+
};
|
|
10890
|
+
}
|
|
10891
|
+
let parsed;
|
|
10892
|
+
try {
|
|
10893
|
+
parsed = await parseFile(file, content, lang);
|
|
10894
|
+
} catch (e) {
|
|
10895
|
+
return {
|
|
10896
|
+
file,
|
|
10897
|
+
stat: stat18,
|
|
10898
|
+
lang,
|
|
10899
|
+
parsed: null,
|
|
10900
|
+
error: `parse error: ${e instanceof Error ? e.message : String(e)}`
|
|
10901
|
+
};
|
|
10902
|
+
}
|
|
10903
|
+
return { file, stat: stat18, lang, parsed, content };
|
|
10203
10904
|
}
|
|
10204
|
-
|
|
10205
|
-
})
|
|
10905
|
+
)
|
|
10206
10906
|
);
|
|
10207
10907
|
const batchEntries = [];
|
|
10208
10908
|
const deleteForFiles = [];
|
|
@@ -10217,8 +10917,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10217
10917
|
}
|
|
10218
10918
|
const result = settled.value;
|
|
10219
10919
|
if (result.error) {
|
|
10220
|
-
if (result.
|
|
10221
|
-
|
|
10920
|
+
if (result.missing) store.deleteFile(file);
|
|
10921
|
+
errors.push(`${file}: ${result.error}`);
|
|
10222
10922
|
continue;
|
|
10223
10923
|
}
|
|
10224
10924
|
const { stat: stat18, lang, parsed } = result;
|
|
@@ -10242,6 +10942,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10242
10942
|
continue;
|
|
10243
10943
|
}
|
|
10244
10944
|
if (parsed.symbols.length === 0) {
|
|
10945
|
+
store.deleteRefsForFile(file);
|
|
10946
|
+
store.deleteSymbolsForFile(file);
|
|
10245
10947
|
store.upsertFile({
|
|
10246
10948
|
file,
|
|
10247
10949
|
lang,
|
|
@@ -10252,15 +10954,11 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10252
10954
|
filesIndexed++;
|
|
10253
10955
|
continue;
|
|
10254
10956
|
}
|
|
10255
|
-
const refs = [];
|
|
10256
|
-
if (parsed.refs && parsed.refs.length > 0) {
|
|
10257
|
-
for (const r of parsed.refs) refs.push({ ...r, fromId: 0 });
|
|
10258
|
-
}
|
|
10259
10957
|
batchEntries.push({
|
|
10260
10958
|
file,
|
|
10261
10959
|
lang,
|
|
10262
10960
|
symbols: parsed.symbols,
|
|
10263
|
-
refs,
|
|
10961
|
+
refs: parsed.refs ?? [],
|
|
10264
10962
|
mtimeMs: Math.floor(stat18.mtimeMs),
|
|
10265
10963
|
symbolCount: parsed.symbols.length
|
|
10266
10964
|
});
|
|
@@ -10268,34 +10966,12 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10268
10966
|
}
|
|
10269
10967
|
if (batchEntries.length > 0) {
|
|
10270
10968
|
try {
|
|
10271
|
-
|
|
10272
|
-
let cursor = 0;
|
|
10969
|
+
store.commitBatch(batchEntries, { deleteForFiles });
|
|
10273
10970
|
for (const entry of batchEntries) {
|
|
10274
10971
|
const count = entry.symbols.length;
|
|
10275
|
-
const symbolsWithIds = inserted.slice(cursor, cursor + count);
|
|
10276
|
-
cursor += count;
|
|
10277
10972
|
symbolsIndexed += count;
|
|
10278
10973
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
|
|
10279
10974
|
filesIndexed++;
|
|
10280
|
-
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
10281
|
-
const refsByLine = /* @__PURE__ */ new Map();
|
|
10282
|
-
for (let i = 0; i < symbolsWithIds.length; i++) {
|
|
10283
|
-
const sym = symbolsWithIds[i];
|
|
10284
|
-
let arr = refsByLine.get(sym.line);
|
|
10285
|
-
if (!arr) {
|
|
10286
|
-
arr = [];
|
|
10287
|
-
refsByLine.set(sym.line, arr);
|
|
10288
|
-
}
|
|
10289
|
-
arr.push(i);
|
|
10290
|
-
}
|
|
10291
|
-
for (const ref of entry.refs) {
|
|
10292
|
-
const indices = refsByLine.get(ref.line);
|
|
10293
|
-
if (indices && indices.length > 0) {
|
|
10294
|
-
const idx = indices.shift();
|
|
10295
|
-
ref.fromId = symbolsWithIds[idx].id;
|
|
10296
|
-
}
|
|
10297
|
-
}
|
|
10298
|
-
}
|
|
10299
10975
|
}
|
|
10300
10976
|
} catch (err) {
|
|
10301
10977
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -10309,23 +10985,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10309
10985
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
|
|
10310
10986
|
filesIndexed++;
|
|
10311
10987
|
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
10312
|
-
const
|
|
10313
|
-
for (const sym of symbolsWithIds) {
|
|
10314
|
-
let arr = refsByLine.get(sym.line);
|
|
10315
|
-
if (!arr) {
|
|
10316
|
-
arr = [];
|
|
10317
|
-
refsByLine.set(sym.line, arr);
|
|
10318
|
-
}
|
|
10319
|
-
arr.push(sym);
|
|
10320
|
-
}
|
|
10321
|
-
const fallbackBatch = [];
|
|
10322
|
-
for (const ref of entry.refs) {
|
|
10323
|
-
const syms = refsByLine.get(ref.line);
|
|
10324
|
-
if (syms && syms.length > 0) {
|
|
10325
|
-
const sym = syms.shift();
|
|
10326
|
-
fallbackBatch.push({ ...ref, fromId: sym.id });
|
|
10327
|
-
}
|
|
10328
|
-
}
|
|
10988
|
+
const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
|
|
10329
10989
|
if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
|
|
10330
10990
|
}
|
|
10331
10991
|
store.upsertFile({
|
|
@@ -10336,19 +10996,23 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10336
10996
|
lastIndexed: Date.now()
|
|
10337
10997
|
});
|
|
10338
10998
|
} catch (innerErr) {
|
|
10339
|
-
errors.push(
|
|
10999
|
+
errors.push(
|
|
11000
|
+
`fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
|
|
11001
|
+
);
|
|
10340
11002
|
}
|
|
10341
11003
|
}
|
|
10342
11004
|
}
|
|
10343
11005
|
}
|
|
10344
11006
|
}
|
|
10345
|
-
if (discoveredFiles) {
|
|
11007
|
+
if (discoveredFiles && discoveryComplete) {
|
|
10346
11008
|
for (const [file_] of existingMeta) {
|
|
10347
11009
|
if (!discoveredFiles.has(file_)) {
|
|
10348
11010
|
store.deleteFile(file_);
|
|
10349
11011
|
}
|
|
10350
11012
|
}
|
|
10351
11013
|
}
|
|
11014
|
+
store.resolveRefs();
|
|
11015
|
+
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
10352
11016
|
const durationMs = Date.now() - startMs;
|
|
10353
11017
|
store.setLastIndexed(Date.now());
|
|
10354
11018
|
return {
|
|
@@ -10408,11 +11072,35 @@ function statsService(args) {
|
|
|
10408
11072
|
store.close();
|
|
10409
11073
|
}
|
|
10410
11074
|
}
|
|
11075
|
+
function packageGraphService(args) {
|
|
11076
|
+
const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
|
|
11077
|
+
try {
|
|
11078
|
+
return store.getPackageGraph();
|
|
11079
|
+
} finally {
|
|
11080
|
+
store.close();
|
|
11081
|
+
}
|
|
11082
|
+
}
|
|
11083
|
+
function fileGraphService(args) {
|
|
11084
|
+
const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
|
|
11085
|
+
try {
|
|
11086
|
+
return store.getFileGraph(args.packageFilter);
|
|
11087
|
+
} finally {
|
|
11088
|
+
store.close();
|
|
11089
|
+
}
|
|
11090
|
+
}
|
|
11091
|
+
function symbolGraphService(args) {
|
|
11092
|
+
const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
|
|
11093
|
+
try {
|
|
11094
|
+
return store.getSymbolGraph(args.fileFilter);
|
|
11095
|
+
} finally {
|
|
11096
|
+
store.close();
|
|
11097
|
+
}
|
|
11098
|
+
}
|
|
10411
11099
|
|
|
10412
11100
|
// src/codebase-index/background-indexer.ts
|
|
10413
11101
|
var DEFAULT_FULL_INDEX_TIMEOUT_MS = 24e4;
|
|
10414
11102
|
var DEFAULT_INCREMENTAL_TIMEOUT_MS = 6e4;
|
|
10415
|
-
var DEFAULT_QUERY_TIMEOUT_MS =
|
|
11103
|
+
var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
10416
11104
|
var _ready = false;
|
|
10417
11105
|
var _indexing = false;
|
|
10418
11106
|
var _currentFile = 0;
|
|
@@ -10490,14 +11178,22 @@ function ensureWorker() {
|
|
|
10490
11178
|
if (!entry) return;
|
|
10491
11179
|
pending.delete(msg.id);
|
|
10492
11180
|
if (msg.ok) entry.resolve(msg.result);
|
|
10493
|
-
else
|
|
11181
|
+
else {
|
|
11182
|
+
const error = msg.errorName === "LockError" ? new LockError(msg.error) : new Error(msg.error);
|
|
11183
|
+
if (msg.errorName && msg.errorName !== "Error") {
|
|
11184
|
+
error.name = msg.errorName;
|
|
11185
|
+
}
|
|
11186
|
+
entry.reject(error);
|
|
11187
|
+
}
|
|
10494
11188
|
});
|
|
10495
11189
|
w.on("error", (err) => {
|
|
11190
|
+
if (worker !== w) return;
|
|
10496
11191
|
worker = null;
|
|
10497
11192
|
failAllPending(err);
|
|
10498
11193
|
});
|
|
10499
11194
|
w.on("exit", () => {
|
|
10500
|
-
if (worker
|
|
11195
|
+
if (worker !== w) return;
|
|
11196
|
+
worker = null;
|
|
10501
11197
|
failAllPending(new Error("codebase-index worker exited"));
|
|
10502
11198
|
});
|
|
10503
11199
|
worker = w;
|
|
@@ -10522,6 +11218,11 @@ function shutdownCodebaseIndexHost() {
|
|
|
10522
11218
|
function callIndexOp(op, args, opts) {
|
|
10523
11219
|
const w = ensureWorker();
|
|
10524
11220
|
if (!w) return callInline(op, args, opts);
|
|
11221
|
+
if (opts.signal?.aborted) {
|
|
11222
|
+
return Promise.reject(
|
|
11223
|
+
opts.signal.reason instanceof Error ? opts.signal.reason : new Error("Indexing cancelled")
|
|
11224
|
+
);
|
|
11225
|
+
}
|
|
10525
11226
|
return new Promise((resolve15, reject) => {
|
|
10526
11227
|
const id = nextRpcId++;
|
|
10527
11228
|
const timer = setTimeout(() => {
|
|
@@ -10536,8 +11237,7 @@ function callIndexOp(op, args, opts) {
|
|
|
10536
11237
|
const onAbort = () => {
|
|
10537
11238
|
w.postMessage({ type: "cancel", id });
|
|
10538
11239
|
};
|
|
10539
|
-
|
|
10540
|
-
else opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
11240
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
10541
11241
|
const cleanup = () => {
|
|
10542
11242
|
clearTimeout(timer);
|
|
10543
11243
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
@@ -10725,7 +11425,7 @@ var codebaseIndexTool = {
|
|
|
10725
11425
|
category: "Project",
|
|
10726
11426
|
icon: "index",
|
|
10727
11427
|
description: "Build or incrementally update the project-wide symbol index. This powers fast codebase search and understanding. By default it only processes files that have changed since the last indexing run.",
|
|
10728
|
-
usageHint: "
|
|
11428
|
+
usageHint: "CREATE OR REFRESH THE SEARCH INDEX:\n\n- When `codebase-stats` or `codebase-search` reports no persisted index, call this without arguments, then retry the search.\n- Normal and first-time usage: call without arguments for an incremental build.\n- Use `force: true` only for a corrupt/stale index or an explicitly requested clean rebuild.\n- Use `langs` to restrict to specific languages if you only care about certain parts of the project.\nThis tool is relatively expensive \u2014 do not call it on every turn. Use it when the index is stale or before heavy codebase-search sessions.",
|
|
10729
11429
|
permission: "confirm",
|
|
10730
11430
|
mutating: true,
|
|
10731
11431
|
capabilities: ["fs.write.outside-project"],
|
|
@@ -10781,12 +11481,14 @@ var codebaseSearchTool = {
|
|
|
10781
11481
|
name: "codebase-search",
|
|
10782
11482
|
category: "Project",
|
|
10783
11483
|
icon: "index",
|
|
10784
|
-
description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback.
|
|
10785
|
-
usageHint: "
|
|
11484
|
+
description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback. Prefer this before broad `tree`, `glob`, or `grep` exploration when finding code by name or concept. Set `preferLsp: true` for live precision when the LSP plugin is active (supersedes codebase-lsp-search).",
|
|
11485
|
+
usageHint: "FIRST CHOICE FOR INDEXABLE CODE UNDERSTANDING:\n\n- Call before broad `tree`, `glob`, or `grep` exploration when locating symbols, concepts, definitions, or candidate modules.\n- `kind` filter is very useful (e.g. only functions or only interfaces).\n- Combine with `file` filter to scope to a specific directory or module.\n- If `indexStatus` reports no persisted data, run `codebase-index` and retry.\nUse `grep` afterwards for exact text, regexes, unsupported content, or concrete usage sites.",
|
|
10786
11486
|
permission: "auto",
|
|
10787
11487
|
mutating: false,
|
|
10788
11488
|
capabilities: ["fs.read"],
|
|
10789
|
-
|
|
11489
|
+
// The index host has its own 30s read watchdog. Leave enough headroom for
|
|
11490
|
+
// worker teardown and structured timeout reporting.
|
|
11491
|
+
timeoutMs: 35e3,
|
|
10790
11492
|
inputSchema: {
|
|
10791
11493
|
type: "object",
|
|
10792
11494
|
properties: {
|
|
@@ -10796,11 +11498,34 @@ var codebaseSearchTool = {
|
|
|
10796
11498
|
},
|
|
10797
11499
|
kind: {
|
|
10798
11500
|
type: "string",
|
|
10799
|
-
|
|
11501
|
+
enum: [
|
|
11502
|
+
"class",
|
|
11503
|
+
"interface",
|
|
11504
|
+
"enum",
|
|
11505
|
+
"type",
|
|
11506
|
+
"function",
|
|
11507
|
+
"method",
|
|
11508
|
+
"var",
|
|
11509
|
+
"const",
|
|
11510
|
+
"let",
|
|
11511
|
+
"property",
|
|
11512
|
+
"parameter",
|
|
11513
|
+
"namespace",
|
|
11514
|
+
"object",
|
|
11515
|
+
"literal",
|
|
11516
|
+
"schema",
|
|
11517
|
+
"struct",
|
|
11518
|
+
"trait",
|
|
11519
|
+
"impl",
|
|
11520
|
+
"static",
|
|
11521
|
+
"mod"
|
|
11522
|
+
],
|
|
11523
|
+
description: "Filter by indexed symbol kind"
|
|
10800
11524
|
},
|
|
10801
11525
|
lang: {
|
|
10802
11526
|
type: "string",
|
|
10803
|
-
|
|
11527
|
+
enum: ["ts", "tsx", "js", "jsx", "go", "py", "rs", "json", "yaml"],
|
|
11528
|
+
description: "Filter by indexed language"
|
|
10804
11529
|
},
|
|
10805
11530
|
lspKind: {
|
|
10806
11531
|
type: "integer",
|
|
@@ -10825,20 +11550,12 @@ var codebaseSearchTool = {
|
|
|
10825
11550
|
},
|
|
10826
11551
|
async execute(input, ctx, execOpts) {
|
|
10827
11552
|
const state = getIndexState();
|
|
10828
|
-
if (!state.ready) {
|
|
11553
|
+
if (state.indexing && !state.ready) {
|
|
10829
11554
|
return {
|
|
10830
11555
|
results: [],
|
|
10831
11556
|
total: 0,
|
|
10832
11557
|
query: input.query,
|
|
10833
|
-
indexStatus:
|
|
10834
|
-
};
|
|
10835
|
-
}
|
|
10836
|
-
if (state.indexing) {
|
|
10837
|
-
return {
|
|
10838
|
-
results: [],
|
|
10839
|
-
total: 0,
|
|
10840
|
-
query: input.query,
|
|
10841
|
-
indexStatus: `Index refresh in progress (${state.currentFile}/${state.totalFiles} files). Results may be incomplete.`
|
|
11558
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
10842
11559
|
};
|
|
10843
11560
|
}
|
|
10844
11561
|
if (state.lastError) {
|
|
@@ -10851,21 +11568,37 @@ var codebaseSearchTool = {
|
|
|
10851
11568
|
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
10852
11569
|
};
|
|
10853
11570
|
}
|
|
10854
|
-
const limit = Math.min(input.limit ?? 20, 100);
|
|
11571
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 20), 100));
|
|
10855
11572
|
const { results, total } = await searchCodebaseIndex(
|
|
10856
11573
|
{
|
|
10857
11574
|
projectRoot: ctx.projectRoot,
|
|
10858
11575
|
indexDir: codebaseIndexDirOverride(ctx),
|
|
10859
11576
|
query: input.query,
|
|
10860
|
-
kind: input.kind,
|
|
10861
|
-
lang: input.lang,
|
|
11577
|
+
kind: input.kind?.toLowerCase(),
|
|
11578
|
+
lang: input.lang?.toLowerCase(),
|
|
10862
11579
|
file: input.file,
|
|
10863
11580
|
lspKind: input.lspKind,
|
|
10864
11581
|
limit
|
|
10865
11582
|
},
|
|
10866
11583
|
{ signal: execOpts?.signal }
|
|
10867
11584
|
);
|
|
10868
|
-
|
|
11585
|
+
let hasPersistedIndex = state.ready || total > 0;
|
|
11586
|
+
if (!hasPersistedIndex) {
|
|
11587
|
+
try {
|
|
11588
|
+
const stats = await codebaseIndexStats(
|
|
11589
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
11590
|
+
{ signal: execOpts?.signal }
|
|
11591
|
+
);
|
|
11592
|
+
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
11593
|
+
} catch {
|
|
11594
|
+
}
|
|
11595
|
+
}
|
|
11596
|
+
return {
|
|
11597
|
+
results,
|
|
11598
|
+
total,
|
|
11599
|
+
query: input.query,
|
|
11600
|
+
...hasPersistedIndex ? {} : { indexStatus: "No persisted index data found. Run codebase-index to build it." }
|
|
11601
|
+
};
|
|
10869
11602
|
}
|
|
10870
11603
|
};
|
|
10871
11604
|
|
|
@@ -10874,12 +11607,15 @@ var codebaseStatsTool = {
|
|
|
10874
11607
|
name: "codebase-stats",
|
|
10875
11608
|
category: "Project",
|
|
10876
11609
|
icon: "index",
|
|
10877
|
-
description: "
|
|
10878
|
-
usageHint: "
|
|
11610
|
+
description: "Check whether a persisted codebase index exists and report its health and statistics (symbols, files, language/kind breakdown, size, last update).",
|
|
11611
|
+
usageHint: "CHECK ONCE BEFORE BROAD CODE EXPLORATION:\n\n- Call before broad `tree`, `glob`, or `grep` exploration to see whether index-backed search is available.\n- If it reports no persisted data, run `codebase-index`, then use `codebase-search`.\n- No arguments required.\nLightweight and safe to call frequently.",
|
|
10879
11612
|
permission: "auto",
|
|
10880
11613
|
mutating: false,
|
|
10881
11614
|
capabilities: ["fs.read"],
|
|
10882
|
-
|
|
11615
|
+
// The index host has its own 30s read watchdog. Keep the outer tool budget
|
|
11616
|
+
// longer so the host can return a structured timeout instead of being
|
|
11617
|
+
// pre-empted by the generic tool executor.
|
|
11618
|
+
timeoutMs: 35e3,
|
|
10883
11619
|
inputSchema: {
|
|
10884
11620
|
type: "object",
|
|
10885
11621
|
properties: {},
|
|
@@ -10887,7 +11623,8 @@ var codebaseStatsTool = {
|
|
|
10887
11623
|
},
|
|
10888
11624
|
async execute(_input, ctx, execOpts) {
|
|
10889
11625
|
const idxState = getIndexState();
|
|
10890
|
-
|
|
11626
|
+
const indexPath = resolveIndexDir(ctx.projectRoot, codebaseIndexDirOverride(ctx));
|
|
11627
|
+
if (idxState.indexing) {
|
|
10891
11628
|
return {
|
|
10892
11629
|
totalSymbols: 0,
|
|
10893
11630
|
totalFiles: 0,
|
|
@@ -10895,22 +11632,47 @@ var codebaseStatsTool = {
|
|
|
10895
11632
|
byKind: {},
|
|
10896
11633
|
lastIndexed: null,
|
|
10897
11634
|
sizeBytes: 0,
|
|
10898
|
-
indexPath
|
|
11635
|
+
indexPath,
|
|
10899
11636
|
version: SCHEMA_VERSION,
|
|
10900
|
-
|
|
11637
|
+
statsAvailable: false,
|
|
11638
|
+
indexing: {
|
|
11639
|
+
currentFile: idxState.currentFile,
|
|
11640
|
+
totalFiles: idxState.totalFiles
|
|
11641
|
+
},
|
|
11642
|
+
indexStatus: idxState.ready ? `Index refresh in progress (${idxState.currentFile}/${idxState.totalFiles} files). The persisted snapshot remains available to codebase-search; retry stats after refresh.` : `Startup indexing in progress (${idxState.currentFile}/${idxState.totalFiles} files). Do not start another index; retry stats or codebase-search after completion.`
|
|
10901
11643
|
};
|
|
10902
11644
|
}
|
|
10903
|
-
|
|
10904
|
-
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
|
-
|
|
11645
|
+
let stats;
|
|
11646
|
+
try {
|
|
11647
|
+
stats = await codebaseIndexStats(
|
|
11648
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
11649
|
+
{ signal: execOpts?.signal }
|
|
11650
|
+
);
|
|
11651
|
+
} catch (err) {
|
|
11652
|
+
if (!(err instanceof IndexTimeoutError) && err?.name !== "IndexTimeoutError") {
|
|
11653
|
+
throw err;
|
|
11654
|
+
}
|
|
10908
11655
|
return {
|
|
10909
|
-
|
|
10910
|
-
|
|
11656
|
+
totalSymbols: 0,
|
|
11657
|
+
totalFiles: 0,
|
|
11658
|
+
byLang: {},
|
|
11659
|
+
byKind: {},
|
|
11660
|
+
lastIndexed: null,
|
|
11661
|
+
sizeBytes: 0,
|
|
11662
|
+
indexPath,
|
|
11663
|
+
version: SCHEMA_VERSION,
|
|
11664
|
+
statsAvailable: false,
|
|
11665
|
+
indexStatus: "Index statistics timed out. Do not repeatedly retry stats; try codebase-search directly or use the appropriate grep/glob/tree fallback."
|
|
10911
11666
|
};
|
|
10912
11667
|
}
|
|
10913
11668
|
const circuit = idxState.circuit;
|
|
11669
|
+
const hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
11670
|
+
let indexStatus;
|
|
11671
|
+
if (circuit.state === "open") {
|
|
11672
|
+
indexStatus = `${hasPersistedIndex ? "Indexing" : "No persisted index data found, and indexing"} is paused after repeated failures (last: ${circuit.lastFailure ?? "unknown"}); auto-retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s, or run /codebase-reindex. ` + (hasPersistedIndex ? "Stats reflect the last successful build." : "Build the index after recovery.");
|
|
11673
|
+
} else if (!hasPersistedIndex) {
|
|
11674
|
+
indexStatus = "No persisted index data found. Run codebase-index to build it before broad code exploration.";
|
|
11675
|
+
}
|
|
10914
11676
|
return {
|
|
10915
11677
|
totalSymbols: stats.totalSymbols,
|
|
10916
11678
|
totalFiles: stats.totalFiles,
|
|
@@ -10920,9 +11682,8 @@ var codebaseStatsTool = {
|
|
|
10920
11682
|
sizeBytes: stats.sizeBytes,
|
|
10921
11683
|
indexPath: stats.indexPath,
|
|
10922
11684
|
version: stats.version,
|
|
10923
|
-
|
|
10924
|
-
|
|
10925
|
-
} : {}
|
|
11685
|
+
statsAvailable: true,
|
|
11686
|
+
...indexStatus ? { indexStatus } : {}
|
|
10926
11687
|
};
|
|
10927
11688
|
}
|
|
10928
11689
|
};
|
|
@@ -11954,25 +12715,46 @@ function findLadderMatches(fileLf, oldLf) {
|
|
|
11954
12715
|
const needleLines = oldLf.split("\n");
|
|
11955
12716
|
if (needleLines.length > fileLines.length) return void 0;
|
|
11956
12717
|
const offsets = lineOffsets(fileLines);
|
|
12718
|
+
const fileTrimEnd = fileLines.map((l) => l.trimEnd());
|
|
12719
|
+
const needleTrimEnd = needleLines.map((l) => l.trimEnd());
|
|
11957
12720
|
const trailing = windowScan(
|
|
11958
|
-
|
|
11959
|
-
|
|
12721
|
+
fileTrimEnd,
|
|
12722
|
+
needleTrimEnd,
|
|
11960
12723
|
offsets,
|
|
11961
|
-
|
|
12724
|
+
fileLines,
|
|
12725
|
+
(a, b) => a === b
|
|
11962
12726
|
);
|
|
11963
12727
|
if (trailing.length > 0) return { tier: "trailing-whitespace", matches: trailing };
|
|
11964
|
-
const normalizedLen =
|
|
12728
|
+
const normalizedLen = needleTrimEnd.reduce((n, l) => n + l.trimStart().length, 0);
|
|
11965
12729
|
if (normalizedLen < MIN_NORMALIZED_NEEDLE_CHARS) return void 0;
|
|
11966
|
-
const
|
|
12730
|
+
const fileTrimmed = fileTrimEnd.map((l) => l.trimStart());
|
|
12731
|
+
const needleTrimmed = needleTrimEnd.map((l) => l.trimStart());
|
|
12732
|
+
const normalized = windowScan(
|
|
12733
|
+
fileTrimmed,
|
|
12734
|
+
needleTrimmed,
|
|
12735
|
+
offsets,
|
|
12736
|
+
fileLines,
|
|
12737
|
+
(a, b) => a === b
|
|
12738
|
+
);
|
|
11967
12739
|
if (normalized.length > 0) return { tier: "whitespace-normalized", matches: normalized };
|
|
11968
12740
|
return fuzzyScan(fileLines, needleLines, offsets);
|
|
11969
12741
|
}
|
|
11970
12742
|
function lineAt(text, pos) {
|
|
12743
|
+
if (pos < 512) {
|
|
12744
|
+
let line2 = 1;
|
|
12745
|
+
for (let i = 0; i < pos; i++) {
|
|
12746
|
+
if (text.charCodeAt(i) === 10) line2++;
|
|
12747
|
+
}
|
|
12748
|
+
return line2;
|
|
12749
|
+
}
|
|
11971
12750
|
let line = 1;
|
|
11972
|
-
|
|
11973
|
-
|
|
12751
|
+
let search = 0;
|
|
12752
|
+
while (true) {
|
|
12753
|
+
const idx = text.indexOf("\n", search);
|
|
12754
|
+
if (idx === -1 || idx >= pos) return line;
|
|
12755
|
+
search = idx + 1;
|
|
12756
|
+
line++;
|
|
11974
12757
|
}
|
|
11975
|
-
return line;
|
|
11976
12758
|
}
|
|
11977
12759
|
function lineOffsets(lines) {
|
|
11978
12760
|
const out = new Array(lines.length);
|
|
@@ -11991,19 +12773,19 @@ function windowToMatch(fileLines, offsets, start, windowLen) {
|
|
|
11991
12773
|
startLine: start + 1
|
|
11992
12774
|
};
|
|
11993
12775
|
}
|
|
11994
|
-
function windowScan(
|
|
12776
|
+
function windowScan(comparisonLines, needleLines, offsets, originalLines, eq) {
|
|
11995
12777
|
const n = needleLines.length;
|
|
11996
12778
|
const out = [];
|
|
11997
|
-
for (let i = 0; i + n <=
|
|
12779
|
+
for (let i = 0; i + n <= comparisonLines.length; i++) {
|
|
11998
12780
|
let all = true;
|
|
11999
12781
|
for (let j = 0; j < n; j++) {
|
|
12000
|
-
if (!eq(
|
|
12782
|
+
if (!eq(comparisonLines[i + j], needleLines[j])) {
|
|
12001
12783
|
all = false;
|
|
12002
12784
|
break;
|
|
12003
12785
|
}
|
|
12004
12786
|
}
|
|
12005
12787
|
if (all) {
|
|
12006
|
-
out.push(windowToMatch(
|
|
12788
|
+
out.push(windowToMatch(originalLines, offsets, i, n));
|
|
12007
12789
|
i += n - 1;
|
|
12008
12790
|
}
|
|
12009
12791
|
}
|
|
@@ -12325,8 +13107,10 @@ var editTool = {
|
|
|
12325
13107
|
note: autoReadNote
|
|
12326
13108
|
};
|
|
12327
13109
|
}
|
|
13110
|
+
opts?.signal?.throwIfAborted();
|
|
12328
13111
|
const ladder = findLadderMatches(fileLf, oldLf);
|
|
12329
13112
|
if (!ladder) {
|
|
13113
|
+
opts?.signal?.throwIfAborted();
|
|
12330
13114
|
const hint = nearestMatchHint(fileLf, oldLf);
|
|
12331
13115
|
throw new ToolValidationError({
|
|
12332
13116
|
message: `edit: no match for old_string in "${input.path}".${hint ? ` Nearest match near line ${hint.line}:
|
|
@@ -12394,6 +13178,7 @@ Compare this against your old_string and retry with the file's actual text.` : "
|
|
|
12394
13178
|
before: original,
|
|
12395
13179
|
after: newFile
|
|
12396
13180
|
});
|
|
13181
|
+
opts?.signal?.throwIfAborted();
|
|
12397
13182
|
const diff = unifiedDiff(original, newFile, {
|
|
12398
13183
|
fromFile: input.path,
|
|
12399
13184
|
toFile: input.path
|
|
@@ -12417,6 +13202,11 @@ Compare this against your old_string and retry with the file's actual text.` : "
|
|
|
12417
13202
|
|
|
12418
13203
|
// src/exec.ts
|
|
12419
13204
|
import { spawn as spawn8 } from "node:child_process";
|
|
13205
|
+
import {
|
|
13206
|
+
emitProcessCompleted as emitProcessCompleted3,
|
|
13207
|
+
emitProcessOutput as emitProcessOutput3,
|
|
13208
|
+
emitProcessStarted as emitProcessStarted3
|
|
13209
|
+
} from "@wrongstack/core";
|
|
12420
13210
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils/error";
|
|
12421
13211
|
init_output_spool();
|
|
12422
13212
|
init_util();
|
|
@@ -13271,12 +14061,30 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13271
14061
|
resolve15(result);
|
|
13272
14062
|
};
|
|
13273
14063
|
const startedAt = Date.now();
|
|
14064
|
+
let stdoutBytes = 0;
|
|
14065
|
+
let stderrBytes = 0;
|
|
14066
|
+
let telemetryCompleted = false;
|
|
14067
|
+
let timedOut = false;
|
|
13274
14068
|
const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
|
|
13275
14069
|
const resolved = resolveWin32Command(cmd);
|
|
13276
14070
|
const needsShell = isWin3 && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
13277
14071
|
const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
|
|
13278
14072
|
const spawnCmd = shim?.command ?? resolved;
|
|
13279
14073
|
const spawnArgs = shim?.args ?? args;
|
|
14074
|
+
const emitCompletedOnce = (exitCode, pid2, signal2) => {
|
|
14075
|
+
if (telemetryCompleted) return;
|
|
14076
|
+
telemetryCompleted = true;
|
|
14077
|
+
emitProcessCompleted3({
|
|
14078
|
+
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
14079
|
+
exitCode,
|
|
14080
|
+
...signal2 ? { signal: signal2 } : {},
|
|
14081
|
+
durationMs: Date.now() - startedAt,
|
|
14082
|
+
stdoutBytes,
|
|
14083
|
+
stderrBytes,
|
|
14084
|
+
timedOut,
|
|
14085
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
14086
|
+
});
|
|
14087
|
+
};
|
|
13280
14088
|
let child;
|
|
13281
14089
|
try {
|
|
13282
14090
|
child = spawn8(spawnCmd, spawnArgs, {
|
|
@@ -13289,6 +14097,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13289
14097
|
});
|
|
13290
14098
|
} catch (err) {
|
|
13291
14099
|
spool.finalize();
|
|
14100
|
+
emitProcessStarted3({
|
|
14101
|
+
parentPid: process.pid,
|
|
14102
|
+
command: redactCommand(`${cmd} ${args.join(" ")}`),
|
|
14103
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
14104
|
+
cwd,
|
|
14105
|
+
background: false,
|
|
14106
|
+
startedAt: new Date(startedAt).toISOString()
|
|
14107
|
+
});
|
|
14108
|
+
emitCompletedOnce(1, void 0);
|
|
13292
14109
|
finish({
|
|
13293
14110
|
command: cmd,
|
|
13294
14111
|
args,
|
|
@@ -13301,6 +14118,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13301
14118
|
});
|
|
13302
14119
|
return;
|
|
13303
14120
|
}
|
|
14121
|
+
emitProcessStarted3({
|
|
14122
|
+
...child.pid !== void 0 ? { pid: child.pid } : {},
|
|
14123
|
+
parentPid: process.pid,
|
|
14124
|
+
command: redactCommand(`${cmd} ${args.join(" ")}`),
|
|
14125
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
14126
|
+
cwd,
|
|
14127
|
+
background: false,
|
|
14128
|
+
startedAt: new Date(startedAt).toISOString()
|
|
14129
|
+
});
|
|
13304
14130
|
child.on("error", (err) => {
|
|
13305
14131
|
const isAbort = err && err.code === "ABORT_ERR";
|
|
13306
14132
|
const stderrText = isAbort ? `Aborted: ${err.message}` : err.message;
|
|
@@ -13309,6 +14135,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13309
14135
|
if (typeof pid === "number") registry.unregister(pid);
|
|
13310
14136
|
registry.afterCall(Date.now() - startedAt, true);
|
|
13311
14137
|
spool.finalize();
|
|
14138
|
+
emitCompletedOnce(isAbort ? 124 : 1, child.pid, isAbort ? "ABORT" : void 0);
|
|
13312
14139
|
finish({
|
|
13313
14140
|
command: cmd,
|
|
13314
14141
|
args,
|
|
@@ -13328,6 +14155,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13328
14155
|
}
|
|
13329
14156
|
const timer = setTimeout(() => {
|
|
13330
14157
|
killed = true;
|
|
14158
|
+
timedOut = true;
|
|
13331
14159
|
if (typeof pid === "number") registry.kill(pid);
|
|
13332
14160
|
else child.kill("SIGTERM");
|
|
13333
14161
|
}, timeout);
|
|
@@ -13342,11 +14170,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13342
14170
|
}
|
|
13343
14171
|
child.stdout?.on("data", (chunk) => {
|
|
13344
14172
|
const text = chunk.toString();
|
|
14173
|
+
stdoutBytes += chunk.byteLength;
|
|
14174
|
+
emitProcessOutput3({ pid, stream: "stdout", chunk });
|
|
13345
14175
|
if (stdout.length < MAX_OUTPUT2) stdout += text;
|
|
13346
14176
|
spool.write(text);
|
|
13347
14177
|
});
|
|
13348
14178
|
child.stderr?.on("data", (chunk) => {
|
|
13349
14179
|
const text = chunk.toString();
|
|
14180
|
+
stderrBytes += chunk.byteLength;
|
|
14181
|
+
emitProcessOutput3({ pid, stream: "stderr", chunk });
|
|
13350
14182
|
if (stderr.length < MAX_OUTPUT2) stderr += text;
|
|
13351
14183
|
spool.write(text);
|
|
13352
14184
|
});
|
|
@@ -13356,6 +14188,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13356
14188
|
if (typeof pid === "number") registry.unregister(pid);
|
|
13357
14189
|
const durationMs = Date.now() - startedAt;
|
|
13358
14190
|
const exitCode = killed ? 124 : code ?? 1;
|
|
14191
|
+
emitCompletedOnce(exitCode, pid);
|
|
13359
14192
|
registry.afterCall(durationMs, exitCode !== 0);
|
|
13360
14193
|
const spooled = spool.finalize();
|
|
13361
14194
|
finish({
|
|
@@ -13431,7 +14264,7 @@ function guardedLookup(hostname4, options, callback) {
|
|
|
13431
14264
|
var pinnedAgent;
|
|
13432
14265
|
function getPinnedDispatcher() {
|
|
13433
14266
|
if (!pinnedAgent) {
|
|
13434
|
-
pinnedAgent = new Agent({ connect: { lookup: guardedLookup } });
|
|
14267
|
+
pinnedAgent = new Agent({ allowH2: false, connect: { lookup: guardedLookup } });
|
|
13435
14268
|
}
|
|
13436
14269
|
return pinnedAgent;
|
|
13437
14270
|
}
|
|
@@ -13533,7 +14366,7 @@ async function assertNotPrivate(hostname4) {
|
|
|
13533
14366
|
}
|
|
13534
14367
|
}
|
|
13535
14368
|
} catch (err) {
|
|
13536
|
-
if (err instanceof
|
|
14369
|
+
if (err instanceof ToolValidationError2) throw err;
|
|
13537
14370
|
}
|
|
13538
14371
|
}
|
|
13539
14372
|
}
|
|
@@ -13881,7 +14714,7 @@ async function detectFixer(cwd) {
|
|
|
13881
14714
|
init_util();
|
|
13882
14715
|
import { spawn as spawn9 } from "node:child_process";
|
|
13883
14716
|
import { statSync as statSync3 } from "node:fs";
|
|
13884
|
-
import { dirname as dirname12, resolve as resolve11, sep as
|
|
14717
|
+
import { dirname as dirname12, resolve as resolve11, sep as sep5 } from "node:path";
|
|
13885
14718
|
import { assessCommitSafety, buildChildEnv as buildChildEnv4 } from "@wrongstack/core";
|
|
13886
14719
|
var TIMEOUT_MS2 = 3e4;
|
|
13887
14720
|
var MAX_OUTPUT3 = 1e5;
|
|
@@ -14026,7 +14859,7 @@ function validateWorktreeInput(input, projectRoot) {
|
|
|
14026
14859
|
if ((input.worktreeAction === "add" || input.worktreeAction === "remove") && input.worktreePath) {
|
|
14027
14860
|
const root = resolve11(projectRoot);
|
|
14028
14861
|
const abs = resolve11(root, input.worktreePath);
|
|
14029
|
-
if (abs !== root && !abs.startsWith(root +
|
|
14862
|
+
if (abs !== root && !abs.startsWith(root + sep5)) {
|
|
14030
14863
|
return reject(`unsafe worktree path (escapes project root): ${input.worktreePath}`);
|
|
14031
14864
|
}
|
|
14032
14865
|
}
|
|
@@ -14189,8 +15022,8 @@ var WALK_CONCURRENCY = 16;
|
|
|
14189
15022
|
var globTool = {
|
|
14190
15023
|
name: "glob",
|
|
14191
15024
|
category: "Filesystem",
|
|
14192
|
-
description: "Find files
|
|
14193
|
-
usageHint: "
|
|
15025
|
+
description: "Find files by path pattern. Use index-backed `codebase-search` first for code symbols or concepts when it is live.",
|
|
15026
|
+
usageHint: "PATH DISCOVERY AND SEARCH SCOPING:\n\n- When `codebase-search` is live, use it first for code concepts; use `glob` for filenames, path patterns, and non-indexed files.\n- Combine with `path` and `limit`.\n- Default ignores common build/dependency directories.\nMuch more efficient than shell `find` for most use cases inside the agent.",
|
|
14194
15027
|
selection: {
|
|
14195
15028
|
doNotUseWhen: "you need to search inside file contents.",
|
|
14196
15029
|
useInstead: ["grep"]
|
|
@@ -14378,8 +15211,8 @@ var NATIVE_MAX_FILE_BYTES = 1e6;
|
|
|
14378
15211
|
var grepTool = {
|
|
14379
15212
|
name: "grep",
|
|
14380
15213
|
category: "Search",
|
|
14381
|
-
description: "Search
|
|
14382
|
-
usageHint: '
|
|
15214
|
+
description: "Search exact file contents using a regular expression. Use index-backed `codebase-search` first for symbol or concept discovery when it is live. Prefers ripgrep for speed and features when available.",
|
|
15215
|
+
usageHint: 'EXACT-TEXT AND REGEX SEARCH:\n\n- When `codebase-search` is live, use it first for symbols and concepts; use `grep` for exact text, regexes, unsupported content, and concrete usage sites.\n- `pattern` is a regular expression.\n- Use `output_mode: "content"` (default) to get matching lines with context.\n- Use `"files_with_matches"` when you only need the list of files.\n- Use `"count"` for quick statistics.\n- `glob` and `path` let you narrow the search scope significantly.\n- Always prefer this over `bash grep` when searching code.',
|
|
14383
15216
|
selection: {
|
|
14384
15217
|
doNotUseWhen: "you only need to locate files by name or path pattern.",
|
|
14385
15218
|
useInstead: ["glob"]
|
|
@@ -15401,7 +16234,8 @@ function toYaml(data, indent = 0) {
|
|
|
15401
16234
|
}
|
|
15402
16235
|
|
|
15403
16236
|
// src/kanban.ts
|
|
15404
|
-
import {
|
|
16237
|
+
import { randomUUID } from "node:crypto";
|
|
16238
|
+
import { deserializeTaskGraph as deserializeTaskGraph2, loadTasks as loadTasks2, serializeTaskGraph } from "@wrongstack/core";
|
|
15405
16239
|
import {
|
|
15406
16240
|
addCheckToTask,
|
|
15407
16241
|
addColumn,
|
|
@@ -15413,18 +16247,19 @@ import {
|
|
|
15413
16247
|
assignTask,
|
|
15414
16248
|
claimReadyTask,
|
|
15415
16249
|
copyTaskToBoard,
|
|
15416
|
-
createBoard,
|
|
16250
|
+
createBoard as createBoard2,
|
|
16251
|
+
createBoardFromTaskGraph,
|
|
15417
16252
|
duplicateBoard,
|
|
15418
16253
|
exportBoardAsMarkdown,
|
|
15419
16254
|
exportBoardToTaskGraph,
|
|
15420
16255
|
generateBoardFromDescription,
|
|
15421
|
-
getBoard,
|
|
16256
|
+
getBoard as getBoard2,
|
|
15422
16257
|
getKanbanOrchestrationSnapshot,
|
|
15423
16258
|
getKanbanQueueHealth,
|
|
15424
16259
|
getTask,
|
|
15425
16260
|
getTaskChain,
|
|
15426
16261
|
heartbeatTaskAssignment,
|
|
15427
|
-
listBoards,
|
|
16262
|
+
listBoards as listBoards2,
|
|
15428
16263
|
listKanbanEvents,
|
|
15429
16264
|
listReadyTasks,
|
|
15430
16265
|
mergeTasks,
|
|
@@ -15432,26 +16267,637 @@ import {
|
|
|
15432
16267
|
parseLinesIntoTasks,
|
|
15433
16268
|
recoverStaleTaskAssignments,
|
|
15434
16269
|
releaseTaskClaim,
|
|
15435
|
-
removeBoard,
|
|
16270
|
+
removeBoard as removeBoard2,
|
|
15436
16271
|
removeColumn,
|
|
15437
16272
|
removeTask,
|
|
15438
16273
|
searchKanban,
|
|
15439
16274
|
setTaskChain,
|
|
15440
16275
|
splitTask,
|
|
15441
|
-
syncBoardFromTaskGraph,
|
|
16276
|
+
syncBoardFromTaskGraph as syncBoardFromTaskGraph2,
|
|
16277
|
+
transitionTask,
|
|
15442
16278
|
transferTaskToBoard,
|
|
15443
|
-
|
|
16279
|
+
touchKanbanPresence as touchKanbanPresence2,
|
|
16280
|
+
updateBoard as updateBoard2,
|
|
15444
16281
|
updateCheckOnTask,
|
|
15445
16282
|
updateColumn,
|
|
15446
16283
|
updateGoalMetricOnTask,
|
|
15447
16284
|
updateTask,
|
|
15448
16285
|
updateTaskAssignment
|
|
15449
16286
|
} from "@wrongstack/kanban";
|
|
16287
|
+
|
|
16288
|
+
// src/session-kanban.ts
|
|
16289
|
+
import { watch } from "node:fs";
|
|
16290
|
+
import { basename as basename9, dirname as dirname13 } from "node:path";
|
|
16291
|
+
import {
|
|
16292
|
+
deserializeTaskGraph,
|
|
16293
|
+
GlobalMailbox,
|
|
16294
|
+
loadPlan,
|
|
16295
|
+
loadTasks,
|
|
16296
|
+
mutatePlan,
|
|
16297
|
+
mutateTasks
|
|
16298
|
+
} from "@wrongstack/core";
|
|
16299
|
+
import { resolveWstackPaths as resolveWstackPaths3 } from "@wrongstack/core/utils";
|
|
16300
|
+
import {
|
|
16301
|
+
createBoard,
|
|
16302
|
+
getBoard,
|
|
16303
|
+
getKanbanDir,
|
|
16304
|
+
listBoards,
|
|
16305
|
+
removeBoard,
|
|
16306
|
+
syncBoardFromTaskGraph,
|
|
16307
|
+
touchKanbanPresence,
|
|
16308
|
+
updateBoard
|
|
16309
|
+
} from "@wrongstack/kanban";
|
|
16310
|
+
var SESSION_BOARD_TAG = "session-work";
|
|
16311
|
+
var MIRROR_DISABLED_ENV = "WRONGSTACK_KANBAN_TASK_MIRROR";
|
|
16312
|
+
var SESSION_KANBAN_COLUMNS = [
|
|
16313
|
+
{ id: "todo", title: "Todo", order: 0, wipLimit: 0, color: "#2563eb" },
|
|
16314
|
+
{ id: "in-progress", title: "Running", order: 1, wipLimit: 1, color: "#d97706" },
|
|
16315
|
+
{ id: "review", title: "Preview", order: 2, wipLimit: 0, color: "#7c3aed" },
|
|
16316
|
+
{ id: "done", title: "Done", order: 3, wipLimit: 0, color: "#16a34a" }
|
|
16317
|
+
];
|
|
16318
|
+
var boardQueue = /* @__PURE__ */ new Map();
|
|
16319
|
+
var boardEnsures = /* @__PURE__ */ new Map();
|
|
16320
|
+
var bindings = /* @__PURE__ */ new WeakMap();
|
|
16321
|
+
var suppressedTodoMirrors = /* @__PURE__ */ new WeakSet();
|
|
16322
|
+
var activeSessionBoards = /* @__PURE__ */ new Map();
|
|
16323
|
+
function boardKey(projectRoot, sessionId) {
|
|
16324
|
+
return `${projectRoot}\0${sessionId}`;
|
|
16325
|
+
}
|
|
16326
|
+
function sessionTag(sessionId) {
|
|
16327
|
+
return `session:${sessionId}`;
|
|
16328
|
+
}
|
|
16329
|
+
function sessionBoardTitle(sessionId) {
|
|
16330
|
+
const leaf = sessionId.split(/[\\/]/).filter(Boolean).pop() ?? sessionId;
|
|
16331
|
+
return `Session ${leaf.slice(0, 12)}`;
|
|
16332
|
+
}
|
|
16333
|
+
function sessionBoardTags(sessionId) {
|
|
16334
|
+
return ["session", SESSION_BOARD_TAG, sessionTag(sessionId)];
|
|
16335
|
+
}
|
|
16336
|
+
function sessionIdFromTags(tags) {
|
|
16337
|
+
const tag = tags?.find((candidate) => candidate.startsWith("session:"));
|
|
16338
|
+
return tag?.slice("session:".length) || null;
|
|
16339
|
+
}
|
|
16340
|
+
function isOwnedSessionBoard(tags) {
|
|
16341
|
+
return Boolean(tags?.includes(SESSION_BOARD_TAG) && sessionIdFromTags(tags));
|
|
16342
|
+
}
|
|
16343
|
+
function retainActiveSessionBoard(projectRoot, sessionId) {
|
|
16344
|
+
const key = boardKey(projectRoot, sessionId);
|
|
16345
|
+
activeSessionBoards.set(key, (activeSessionBoards.get(key) ?? 0) + 1);
|
|
16346
|
+
}
|
|
16347
|
+
function releaseActiveSessionBoard(projectRoot, sessionId) {
|
|
16348
|
+
const key = boardKey(projectRoot, sessionId);
|
|
16349
|
+
const remaining = (activeSessionBoards.get(key) ?? 0) - 1;
|
|
16350
|
+
if (remaining > 0) activeSessionBoards.set(key, remaining);
|
|
16351
|
+
else activeSessionBoards.delete(key);
|
|
16352
|
+
}
|
|
16353
|
+
function isSessionBoardActive(projectRoot, sessionId) {
|
|
16354
|
+
return (activeSessionBoards.get(boardKey(projectRoot, sessionId)) ?? 0) > 0;
|
|
16355
|
+
}
|
|
16356
|
+
function sameColumns(columns) {
|
|
16357
|
+
return columns.length === SESSION_KANBAN_COLUMNS.length && columns.every((column, index) => column.id === SESSION_KANBAN_COLUMNS[index]?.id);
|
|
16358
|
+
}
|
|
16359
|
+
async function ensureSessionKanbanBoard(projectRoot, sessionId) {
|
|
16360
|
+
if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return null;
|
|
16361
|
+
const key = boardKey(projectRoot, sessionId);
|
|
16362
|
+
const inFlight = boardEnsures.get(key);
|
|
16363
|
+
if (inFlight) return inFlight;
|
|
16364
|
+
const promise = (async () => {
|
|
16365
|
+
const summary = (await listBoards(projectRoot)).find(
|
|
16366
|
+
(board2) => board2.tags?.includes(sessionTag(sessionId))
|
|
16367
|
+
);
|
|
16368
|
+
let board = summary ? await getBoard(projectRoot, summary.id) : null;
|
|
16369
|
+
if (!board) {
|
|
16370
|
+
return createBoard(projectRoot, {
|
|
16371
|
+
title: sessionBoardTitle(sessionId),
|
|
16372
|
+
description: "Live session work: todos, tasks, and plan items.",
|
|
16373
|
+
tags: sessionBoardTags(sessionId),
|
|
16374
|
+
columns: SESSION_KANBAN_COLUMNS,
|
|
16375
|
+
generatedBy: `session-kanban:${sessionId}`
|
|
16376
|
+
});
|
|
16377
|
+
}
|
|
16378
|
+
if (!sameColumns(board.columns) || !board.tags?.includes(SESSION_BOARD_TAG)) {
|
|
16379
|
+
board = await updateBoard(projectRoot, board.id, {
|
|
16380
|
+
title: sessionBoardTitle(sessionId),
|
|
16381
|
+
description: "Live session work: todos, tasks, and plan items.",
|
|
16382
|
+
tags: [.../* @__PURE__ */ new Set([...board.tags ?? [], ...sessionBoardTags(sessionId)])],
|
|
16383
|
+
columns: SESSION_KANBAN_COLUMNS
|
|
16384
|
+
}) ?? board;
|
|
16385
|
+
}
|
|
16386
|
+
return board;
|
|
16387
|
+
})();
|
|
16388
|
+
boardEnsures.set(key, promise);
|
|
16389
|
+
try {
|
|
16390
|
+
return await promise;
|
|
16391
|
+
} finally {
|
|
16392
|
+
boardEnsures.delete(key);
|
|
16393
|
+
}
|
|
16394
|
+
}
|
|
16395
|
+
function enqueueBoardWork(projectRoot, sessionId, work) {
|
|
16396
|
+
const key = boardKey(projectRoot, sessionId);
|
|
16397
|
+
const previous = boardQueue.get(key) ?? Promise.resolve();
|
|
16398
|
+
const result = previous.catch(() => void 0).then(work);
|
|
16399
|
+
const tail = result.then(
|
|
16400
|
+
() => void 0,
|
|
16401
|
+
() => void 0
|
|
16402
|
+
);
|
|
16403
|
+
boardQueue.set(key, tail);
|
|
16404
|
+
void tail.then(() => {
|
|
16405
|
+
if (boardQueue.get(key) === tail) boardQueue.delete(key);
|
|
16406
|
+
});
|
|
16407
|
+
return result;
|
|
16408
|
+
}
|
|
16409
|
+
async function removeEmptySessionBoard(projectRoot, boardId, sessionId) {
|
|
16410
|
+
return enqueueBoardWork(projectRoot, sessionId, async () => {
|
|
16411
|
+
if (isSessionBoardActive(projectRoot, sessionId)) return null;
|
|
16412
|
+
const board = await getBoard(projectRoot, boardId);
|
|
16413
|
+
if (!board || board.tasks.length > 0 || !isOwnedSessionBoard(board.tags)) return null;
|
|
16414
|
+
if (sessionIdFromTags(board.tags) !== sessionId) return null;
|
|
16415
|
+
return await removeBoard(projectRoot, board.id) ? board.id : null;
|
|
16416
|
+
});
|
|
16417
|
+
}
|
|
16418
|
+
async function cleanupSessionKanbanBoardIfEmpty(projectRoot, sessionId) {
|
|
16419
|
+
if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return [];
|
|
16420
|
+
if (isSessionBoardActive(projectRoot, sessionId)) return [];
|
|
16421
|
+
const candidates = (await listBoards(projectRoot)).filter(
|
|
16422
|
+
(board) => board.taskCount === 0 && isOwnedSessionBoard(board.tags) && sessionIdFromTags(board.tags) === sessionId
|
|
16423
|
+
);
|
|
16424
|
+
const removed = await Promise.all(
|
|
16425
|
+
candidates.map((board) => removeEmptySessionBoard(projectRoot, board.id, sessionId))
|
|
16426
|
+
);
|
|
16427
|
+
return removed.filter((boardId) => Boolean(boardId));
|
|
16428
|
+
}
|
|
16429
|
+
async function cleanupEmptySessionKanbanBoards(projectRoot, activeSessionId = "") {
|
|
16430
|
+
if (!projectRoot || process.env[MIRROR_DISABLED_ENV] === "0") return [];
|
|
16431
|
+
const candidates = (await listBoards(projectRoot)).flatMap((board) => {
|
|
16432
|
+
const ownerSessionId = sessionIdFromTags(board.tags);
|
|
16433
|
+
return board.taskCount === 0 && isOwnedSessionBoard(board.tags) && ownerSessionId && ownerSessionId !== activeSessionId && !isSessionBoardActive(projectRoot, ownerSessionId) ? [{ boardId: board.id, sessionId: ownerSessionId }] : [];
|
|
16434
|
+
});
|
|
16435
|
+
const removed = await Promise.all(
|
|
16436
|
+
candidates.map(
|
|
16437
|
+
({ boardId, sessionId }) => removeEmptySessionBoard(projectRoot, boardId, sessionId)
|
|
16438
|
+
)
|
|
16439
|
+
);
|
|
16440
|
+
return removed.filter((boardId) => Boolean(boardId));
|
|
16441
|
+
}
|
|
16442
|
+
async function projectGraph(projectRoot, sessionId, graph, sourceSystem) {
|
|
16443
|
+
if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return null;
|
|
16444
|
+
return enqueueBoardWork(projectRoot, sessionId, async () => {
|
|
16445
|
+
const board = await ensureSessionKanbanBoard(projectRoot, sessionId);
|
|
16446
|
+
if (!board) return null;
|
|
16447
|
+
const result = await syncBoardFromTaskGraph(
|
|
16448
|
+
projectRoot,
|
|
16449
|
+
board.id,
|
|
16450
|
+
deserializeTaskGraph(graph),
|
|
16451
|
+
{
|
|
16452
|
+
sourceSystem,
|
|
16453
|
+
tags: [.../* @__PURE__ */ new Set([...board.tags ?? [], ...sessionBoardTags(sessionId)])],
|
|
16454
|
+
archiveMissingTasks: true,
|
|
16455
|
+
includeCompletedTasks: true
|
|
16456
|
+
}
|
|
16457
|
+
);
|
|
16458
|
+
return result?.board ?? null;
|
|
16459
|
+
});
|
|
16460
|
+
}
|
|
16461
|
+
function todoListToSerializedGraph(todos, sessionId) {
|
|
16462
|
+
const nodes = todos.map((todo, index) => ({
|
|
16463
|
+
id: todo.id,
|
|
16464
|
+
title: todo.content,
|
|
16465
|
+
description: todo.activeForm ?? "",
|
|
16466
|
+
type: "chore",
|
|
16467
|
+
priority: "medium",
|
|
16468
|
+
status: todo.status,
|
|
16469
|
+
createdAt: index,
|
|
16470
|
+
updatedAt: index
|
|
16471
|
+
}));
|
|
16472
|
+
return {
|
|
16473
|
+
id: `todo:${sessionId}`,
|
|
16474
|
+
specId: `todo:${sessionId}`,
|
|
16475
|
+
title: "Session todos",
|
|
16476
|
+
nodes,
|
|
16477
|
+
edges: [],
|
|
16478
|
+
rootNodes: nodes.map((node) => node.id),
|
|
16479
|
+
createdAt: 0,
|
|
16480
|
+
updatedAt: 0
|
|
16481
|
+
};
|
|
16482
|
+
}
|
|
16483
|
+
function taskFileToSerializedGraph(tasks, sessionId) {
|
|
16484
|
+
const ids = new Set(tasks.map((task) => task.id));
|
|
16485
|
+
const nodes = tasks.map((task, index) => ({
|
|
16486
|
+
id: task.id,
|
|
16487
|
+
title: task.title,
|
|
16488
|
+
description: task.description ?? "",
|
|
16489
|
+
type: task.type,
|
|
16490
|
+
priority: task.priority,
|
|
16491
|
+
status: task.status,
|
|
16492
|
+
...task.assignee ? { assignee: task.assignee } : {},
|
|
16493
|
+
...task.estimateHours !== void 0 ? { estimateHours: task.estimateHours } : {},
|
|
16494
|
+
createdAt: index,
|
|
16495
|
+
updatedAt: index
|
|
16496
|
+
}));
|
|
16497
|
+
const edges = tasks.flatMap(
|
|
16498
|
+
(task) => (task.dependsOn ?? []).filter((dependency) => ids.has(dependency)).map((dependency) => ({
|
|
16499
|
+
id: `${dependency}->${task.id}`,
|
|
16500
|
+
from: dependency,
|
|
16501
|
+
to: task.id,
|
|
16502
|
+
type: "depends_on"
|
|
16503
|
+
}))
|
|
16504
|
+
);
|
|
16505
|
+
const hasIncoming = new Set(edges.map((edge) => edge.to));
|
|
16506
|
+
const rootNodes = nodes.filter((node) => !hasIncoming.has(node.id)).map((node) => node.id);
|
|
16507
|
+
return {
|
|
16508
|
+
// Keep the historical graph id so existing mirrored task cards are reused.
|
|
16509
|
+
id: `session:${sessionId}`,
|
|
16510
|
+
specId: `session:${sessionId}`,
|
|
16511
|
+
title: "Session tasks",
|
|
16512
|
+
nodes,
|
|
16513
|
+
edges,
|
|
16514
|
+
rootNodes: rootNodes.length ? rootNodes : nodes[0] ? [nodes[0].id] : [],
|
|
16515
|
+
createdAt: 0,
|
|
16516
|
+
updatedAt: 0
|
|
16517
|
+
};
|
|
16518
|
+
}
|
|
16519
|
+
var PLAN_STATUS_TO_TASK = {
|
|
16520
|
+
open: "pending",
|
|
16521
|
+
in_progress: "in_progress",
|
|
16522
|
+
done: "completed"
|
|
16523
|
+
};
|
|
16524
|
+
function planFileToSerializedGraph(items, sessionId) {
|
|
16525
|
+
const nodes = items.map((item, index) => ({
|
|
16526
|
+
id: item.id,
|
|
16527
|
+
title: item.title,
|
|
16528
|
+
description: item.details ?? "",
|
|
16529
|
+
type: "chore",
|
|
16530
|
+
priority: "medium",
|
|
16531
|
+
status: PLAN_STATUS_TO_TASK[item.status],
|
|
16532
|
+
createdAt: index,
|
|
16533
|
+
updatedAt: index
|
|
16534
|
+
}));
|
|
16535
|
+
return {
|
|
16536
|
+
id: `plan:${sessionId}`,
|
|
16537
|
+
specId: `plan:${sessionId}`,
|
|
16538
|
+
title: "Session plan",
|
|
16539
|
+
nodes,
|
|
16540
|
+
edges: [],
|
|
16541
|
+
rootNodes: nodes.map((node) => node.id),
|
|
16542
|
+
createdAt: 0,
|
|
16543
|
+
updatedAt: 0
|
|
16544
|
+
};
|
|
16545
|
+
}
|
|
16546
|
+
function projectSessionTodosToKanban(projectRoot, todos, sessionId) {
|
|
16547
|
+
return projectGraph(
|
|
16548
|
+
projectRoot,
|
|
16549
|
+
sessionId,
|
|
16550
|
+
todoListToSerializedGraph(todos, sessionId),
|
|
16551
|
+
"session-todo"
|
|
16552
|
+
);
|
|
16553
|
+
}
|
|
16554
|
+
function projectSessionTasksToKanban(projectRoot, tasks, sessionId) {
|
|
16555
|
+
return projectGraph(
|
|
16556
|
+
projectRoot,
|
|
16557
|
+
sessionId,
|
|
16558
|
+
taskFileToSerializedGraph(tasks, sessionId),
|
|
16559
|
+
"session-task"
|
|
16560
|
+
);
|
|
16561
|
+
}
|
|
16562
|
+
function projectSessionPlanToKanban(projectRoot, items, sessionId) {
|
|
16563
|
+
return projectGraph(
|
|
16564
|
+
projectRoot,
|
|
16565
|
+
sessionId,
|
|
16566
|
+
planFileToSerializedGraph(items, sessionId),
|
|
16567
|
+
"session-plan"
|
|
16568
|
+
);
|
|
16569
|
+
}
|
|
16570
|
+
function fireAndForget(work) {
|
|
16571
|
+
void work.catch(() => {
|
|
16572
|
+
});
|
|
16573
|
+
}
|
|
16574
|
+
function broadcastTodoUpdate(context, todos) {
|
|
16575
|
+
const sessionId = context.session?.id ?? "";
|
|
16576
|
+
if (!context.agentId || !sessionId) return;
|
|
16577
|
+
const projectDir = resolveWstackPaths3({ projectRoot: context.projectRoot }).projectDir;
|
|
16578
|
+
const mailbox = new GlobalMailbox(projectDir);
|
|
16579
|
+
void mailbox.send({
|
|
16580
|
+
from: context.agentId,
|
|
16581
|
+
to: "*",
|
|
16582
|
+
type: "status",
|
|
16583
|
+
subject: `Kanban todo list updated (${todos.length} item${todos.length === 1 ? "" : "s"})`,
|
|
16584
|
+
body: JSON.stringify({
|
|
16585
|
+
kind: "kanban.todos.updated",
|
|
16586
|
+
sessionId,
|
|
16587
|
+
revision: context.state.revision,
|
|
16588
|
+
todos
|
|
16589
|
+
}),
|
|
16590
|
+
priority: "normal",
|
|
16591
|
+
senderSessionId: sessionId,
|
|
16592
|
+
ttlMs: 6 * 60 * 60 * 1e3
|
|
16593
|
+
}).catch(() => {
|
|
16594
|
+
});
|
|
16595
|
+
}
|
|
16596
|
+
function notifyTodoUpdate(context, todos) {
|
|
16597
|
+
const summary = todos.length ? todos.map((todo) => `- [${todo.status}] ${todo.content} (${todo.id})`).join("\n") : "- No active todos remain.";
|
|
16598
|
+
const text = `[KANBAN TODO UPDATE]
|
|
16599
|
+
Another Kanban agent reassessed the shared board. The canonical todo list is now:
|
|
16600
|
+
${summary}
|
|
16601
|
+
Reassess your current plan before continuing; do not rely on the initial todo snapshot.`;
|
|
16602
|
+
const state = context.state;
|
|
16603
|
+
if (typeof state.appendBlockToLastUserMessage === "function") {
|
|
16604
|
+
if (state.appendBlockToLastUserMessage({ type: "text", text })) return;
|
|
16605
|
+
}
|
|
16606
|
+
if (typeof state.appendMessage === "function") {
|
|
16607
|
+
state.appendMessage({ role: "user", content: [{ type: "text", text }] });
|
|
16608
|
+
}
|
|
16609
|
+
}
|
|
16610
|
+
function mirrorSessionTodosToKanban(projectRoot, todos, sessionId) {
|
|
16611
|
+
fireAndForget(projectSessionTodosToKanban(projectRoot, todos, sessionId));
|
|
16612
|
+
}
|
|
16613
|
+
function mirrorSessionTasksToKanban(projectRoot, tasks, sessionId) {
|
|
16614
|
+
fireAndForget(projectSessionTasksToKanban(projectRoot, tasks, sessionId));
|
|
16615
|
+
}
|
|
16616
|
+
function mirrorSessionPlanToKanban(projectRoot, items, sessionId) {
|
|
16617
|
+
fireAndForget(projectSessionPlanToKanban(projectRoot, items, sessionId));
|
|
16618
|
+
}
|
|
16619
|
+
function attachSessionKanbanMirror(context) {
|
|
16620
|
+
const existing = bindings.get(context);
|
|
16621
|
+
if (existing) return existing;
|
|
16622
|
+
const attachedProjectRoot = context.projectRoot ?? "";
|
|
16623
|
+
let registeredSessionId = "";
|
|
16624
|
+
const syncActiveSessionRegistration = () => {
|
|
16625
|
+
if (!attachedProjectRoot) return;
|
|
16626
|
+
const currentSessionId = context.session?.id ?? "";
|
|
16627
|
+
if (currentSessionId === registeredSessionId) return;
|
|
16628
|
+
if (registeredSessionId) {
|
|
16629
|
+
releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
|
|
16630
|
+
fireAndForget(cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId));
|
|
16631
|
+
}
|
|
16632
|
+
registeredSessionId = currentSessionId;
|
|
16633
|
+
if (registeredSessionId) {
|
|
16634
|
+
retainActiveSessionBoard(attachedProjectRoot, registeredSessionId);
|
|
16635
|
+
}
|
|
16636
|
+
};
|
|
16637
|
+
syncActiveSessionRegistration();
|
|
16638
|
+
let watcher = null;
|
|
16639
|
+
let watchedDir = "";
|
|
16640
|
+
let timer = null;
|
|
16641
|
+
let boardWatcher = null;
|
|
16642
|
+
let watchedBoardId = "";
|
|
16643
|
+
let boardTimer = null;
|
|
16644
|
+
let presenceTimer = null;
|
|
16645
|
+
const sessionId = () => context.session?.id ?? "";
|
|
16646
|
+
const refreshFiles = async () => {
|
|
16647
|
+
const id = sessionId();
|
|
16648
|
+
if (!id) return;
|
|
16649
|
+
const planPath = context.meta["plan.path"];
|
|
16650
|
+
if (typeof planPath === "string" && planPath) {
|
|
16651
|
+
const plan = await loadPlan(planPath);
|
|
16652
|
+
if (plan) await projectSessionPlanToKanban(context.projectRoot, plan.items, id);
|
|
16653
|
+
}
|
|
16654
|
+
const taskPath = context.meta["task.path"];
|
|
16655
|
+
if (typeof taskPath === "string" && taskPath) {
|
|
16656
|
+
const tasks = await loadTasks(taskPath);
|
|
16657
|
+
if (tasks) await projectSessionTasksToKanban(context.projectRoot, tasks.tasks, id);
|
|
16658
|
+
}
|
|
16659
|
+
};
|
|
16660
|
+
const refreshBoard = async () => {
|
|
16661
|
+
if (!watchedBoardId) return;
|
|
16662
|
+
const board = await getBoard(context.projectRoot, watchedBoardId);
|
|
16663
|
+
if (board) applySessionKanbanBoardToTodos(context, board);
|
|
16664
|
+
};
|
|
16665
|
+
const configureBoardWatcher = async () => {
|
|
16666
|
+
const id = sessionId();
|
|
16667
|
+
const board = id ? await ensureSessionKanbanBoard(context.projectRoot, id) : null;
|
|
16668
|
+
if (!board || board.id === watchedBoardId) return;
|
|
16669
|
+
boardWatcher?.close();
|
|
16670
|
+
boardWatcher = null;
|
|
16671
|
+
watchedBoardId = board.id;
|
|
16672
|
+
try {
|
|
16673
|
+
const boardFileName = `${board.id}.json`;
|
|
16674
|
+
boardWatcher = watch(
|
|
16675
|
+
getKanbanDir(context.projectRoot),
|
|
16676
|
+
{ persistent: false },
|
|
16677
|
+
(_event, filename) => {
|
|
16678
|
+
if (filename?.toString() !== boardFileName) return;
|
|
16679
|
+
if (boardTimer) clearTimeout(boardTimer);
|
|
16680
|
+
boardTimer = setTimeout(() => fireAndForget(refreshBoard()), 60);
|
|
16681
|
+
}
|
|
16682
|
+
);
|
|
16683
|
+
const touchPresence = () => touchKanbanPresence(context.projectRoot, board.id, {
|
|
16684
|
+
sessionId: id,
|
|
16685
|
+
agentId: context.agentId,
|
|
16686
|
+
agentName: context.agentName
|
|
16687
|
+
});
|
|
16688
|
+
fireAndForget(touchPresence());
|
|
16689
|
+
if (presenceTimer) clearInterval(presenceTimer);
|
|
16690
|
+
presenceTimer = setInterval(() => fireAndForget(touchPresence()), 6e4);
|
|
16691
|
+
presenceTimer.unref?.();
|
|
16692
|
+
boardWatcher.on("error", () => {
|
|
16693
|
+
boardWatcher?.close();
|
|
16694
|
+
boardWatcher = null;
|
|
16695
|
+
watchedBoardId = "";
|
|
16696
|
+
});
|
|
16697
|
+
} catch {
|
|
16698
|
+
boardWatcher = null;
|
|
16699
|
+
watchedBoardId = "";
|
|
16700
|
+
}
|
|
16701
|
+
};
|
|
16702
|
+
const configureWatcher = () => {
|
|
16703
|
+
const planPath = context.meta["plan.path"];
|
|
16704
|
+
const taskPath = context.meta["task.path"];
|
|
16705
|
+
const candidate = typeof planPath === "string" && planPath ? dirname13(planPath) : typeof taskPath === "string" && taskPath ? dirname13(taskPath) : "";
|
|
16706
|
+
if (!candidate || candidate === watchedDir) return;
|
|
16707
|
+
watcher?.close();
|
|
16708
|
+
watcher = null;
|
|
16709
|
+
watchedDir = candidate;
|
|
16710
|
+
try {
|
|
16711
|
+
watcher = watch(candidate, { persistent: false }, (_event, filename) => {
|
|
16712
|
+
const name = filename?.toString();
|
|
16713
|
+
const currentPlanPath = context.meta["plan.path"];
|
|
16714
|
+
const currentTaskPath = context.meta["task.path"];
|
|
16715
|
+
const planName = typeof currentPlanPath === "string" ? basename9(currentPlanPath) : "";
|
|
16716
|
+
const taskName = typeof currentTaskPath === "string" ? basename9(currentTaskPath) : "";
|
|
16717
|
+
if (name && name !== planName && name !== taskName) return;
|
|
16718
|
+
if (timer) clearTimeout(timer);
|
|
16719
|
+
timer = setTimeout(() => fireAndForget(refreshFiles()), 60);
|
|
16720
|
+
});
|
|
16721
|
+
watcher.on("error", () => watcher?.close());
|
|
16722
|
+
} catch {
|
|
16723
|
+
watcher = null;
|
|
16724
|
+
watchedDir = "";
|
|
16725
|
+
}
|
|
16726
|
+
};
|
|
16727
|
+
const unsubscribe = context.state.onChange((change) => {
|
|
16728
|
+
if (change.kind === "todos_replaced" && !suppressedTodoMirrors.has(context)) {
|
|
16729
|
+
mirrorSessionTodosToKanban(
|
|
16730
|
+
context.projectRoot,
|
|
16731
|
+
change.completedSnapshot ?? change.todos,
|
|
16732
|
+
sessionId()
|
|
16733
|
+
);
|
|
16734
|
+
return;
|
|
16735
|
+
}
|
|
16736
|
+
if (change.kind === "meta_set" && (change.key === "plan.path" || change.key === "task.path")) {
|
|
16737
|
+
syncActiveSessionRegistration();
|
|
16738
|
+
configureWatcher();
|
|
16739
|
+
fireAndForget(ensureSessionKanbanBoard(context.projectRoot, sessionId()));
|
|
16740
|
+
fireAndForget(configureBoardWatcher());
|
|
16741
|
+
fireAndForget(refreshFiles());
|
|
16742
|
+
}
|
|
16743
|
+
});
|
|
16744
|
+
configureWatcher();
|
|
16745
|
+
fireAndForget(configureBoardWatcher());
|
|
16746
|
+
const detach = () => {
|
|
16747
|
+
unsubscribe();
|
|
16748
|
+
if (timer) clearTimeout(timer);
|
|
16749
|
+
if (boardTimer) clearTimeout(boardTimer);
|
|
16750
|
+
if (presenceTimer) clearInterval(presenceTimer);
|
|
16751
|
+
watcher?.close();
|
|
16752
|
+
boardWatcher?.close();
|
|
16753
|
+
bindings.delete(context);
|
|
16754
|
+
if (attachedProjectRoot && registeredSessionId) {
|
|
16755
|
+
releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
|
|
16756
|
+
fireAndForget(cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId));
|
|
16757
|
+
registeredSessionId = "";
|
|
16758
|
+
}
|
|
16759
|
+
};
|
|
16760
|
+
bindings.set(context, detach);
|
|
16761
|
+
return detach;
|
|
16762
|
+
}
|
|
16763
|
+
async function hydrateSessionKanban(context) {
|
|
16764
|
+
const id = context.session?.id ?? "";
|
|
16765
|
+
if (!id) return null;
|
|
16766
|
+
await cleanupEmptySessionKanbanBoards(context.projectRoot, id);
|
|
16767
|
+
let board = await ensureSessionKanbanBoard(context.projectRoot, id);
|
|
16768
|
+
if (context.todos.length) {
|
|
16769
|
+
board = await projectSessionTodosToKanban(context.projectRoot, context.todos, id);
|
|
16770
|
+
}
|
|
16771
|
+
const planPath = context.meta["plan.path"];
|
|
16772
|
+
if (typeof planPath === "string" && planPath) {
|
|
16773
|
+
const plan = await loadPlan(planPath);
|
|
16774
|
+
if (plan) board = await projectSessionPlanToKanban(context.projectRoot, plan.items, id);
|
|
16775
|
+
}
|
|
16776
|
+
const taskPath = context.meta["task.path"];
|
|
16777
|
+
if (typeof taskPath === "string" && taskPath) {
|
|
16778
|
+
const tasks = await loadTasks(taskPath);
|
|
16779
|
+
if (tasks) board = await projectSessionTasksToKanban(context.projectRoot, tasks.tasks, id);
|
|
16780
|
+
}
|
|
16781
|
+
return board;
|
|
16782
|
+
}
|
|
16783
|
+
function sourceStatus(task) {
|
|
16784
|
+
if (task.status === "completed") return "completed";
|
|
16785
|
+
if (task.status === "in_progress") return "in_progress";
|
|
16786
|
+
if (task.status === "review") return "review";
|
|
16787
|
+
if (task.status === "blocked") return "blocked";
|
|
16788
|
+
if (task.status === "failed") return "failed";
|
|
16789
|
+
return "pending";
|
|
16790
|
+
}
|
|
16791
|
+
function todoStatus(task) {
|
|
16792
|
+
const status = sourceStatus(task);
|
|
16793
|
+
if (status === "completed") return "completed";
|
|
16794
|
+
if (status === "in_progress" || status === "review") return "in_progress";
|
|
16795
|
+
return "pending";
|
|
16796
|
+
}
|
|
16797
|
+
function sessionTodoFromTask(task) {
|
|
16798
|
+
return {
|
|
16799
|
+
id: task.origin?.taskId ?? task.id,
|
|
16800
|
+
content: task.title,
|
|
16801
|
+
status: todoStatus(task),
|
|
16802
|
+
...task.description ? { activeForm: task.description } : {}
|
|
16803
|
+
};
|
|
16804
|
+
}
|
|
16805
|
+
function sameTodos(left, right) {
|
|
16806
|
+
return left.length === right.length && left.every((todo, index) => {
|
|
16807
|
+
const candidate = right[index];
|
|
16808
|
+
return candidate?.id === todo.id && candidate.content === todo.content && candidate.status === todo.status && candidate.activeForm === todo.activeForm && candidate.promotedFromPlan === todo.promotedFromPlan && candidate.promotedFromTask === todo.promotedFromTask;
|
|
16809
|
+
});
|
|
16810
|
+
}
|
|
16811
|
+
function applySessionKanbanBoardToTodos(context, board) {
|
|
16812
|
+
const sessionId = context.session?.id ?? "";
|
|
16813
|
+
if (!sessionId || sessionIdFromTags(board.tags) !== sessionId || !isOwnedSessionBoard(board.tags)) {
|
|
16814
|
+
return [...context.todos];
|
|
16815
|
+
}
|
|
16816
|
+
const projectedTodos = board.tasks.filter(
|
|
16817
|
+
(task) => task.status !== "archived" && (!task.origin || task.origin.system === "session-todo" || (task.origin.graphId ?? "").startsWith("todo:"))
|
|
16818
|
+
).sort((left, right) => {
|
|
16819
|
+
const leftColumn = board.columns.find((column) => column.id === left.columnId)?.order ?? 0;
|
|
16820
|
+
const rightColumn = board.columns.find((column) => column.id === right.columnId)?.order ?? 0;
|
|
16821
|
+
return leftColumn - rightColumn || left.order - right.order || left.createdAt.localeCompare(right.createdAt);
|
|
16822
|
+
}).map(sessionTodoFromTask);
|
|
16823
|
+
const allCompleted = projectedTodos.length > 0 && projectedTodos.every((todo) => todo.status === "completed");
|
|
16824
|
+
const effectiveTodos = allCompleted ? [] : projectedTodos;
|
|
16825
|
+
if (sameTodos(context.todos, effectiveTodos)) return [...context.todos];
|
|
16826
|
+
suppressedTodoMirrors.add(context);
|
|
16827
|
+
try {
|
|
16828
|
+
context.state.replaceTodos(projectedTodos);
|
|
16829
|
+
} finally {
|
|
16830
|
+
suppressedTodoMirrors.delete(context);
|
|
16831
|
+
}
|
|
16832
|
+
notifyTodoUpdate(context, context.todos);
|
|
16833
|
+
broadcastTodoUpdate(context, context.todos);
|
|
16834
|
+
return [...context.todos];
|
|
16835
|
+
}
|
|
16836
|
+
async function applySessionKanbanTaskToSource(context, task, options = {}) {
|
|
16837
|
+
const originId = task.origin?.taskId;
|
|
16838
|
+
const graphId = task.origin?.graphId ?? "";
|
|
16839
|
+
if (!originId) return { source: null };
|
|
16840
|
+
if (task.origin?.system === "session-todo" || graphId.startsWith("todo:")) {
|
|
16841
|
+
const next = options.remove ? context.todos.filter((todo) => todo.id !== originId) : context.todos.map(
|
|
16842
|
+
(todo) => todo.id === originId ? {
|
|
16843
|
+
...todo,
|
|
16844
|
+
content: task.title,
|
|
16845
|
+
status: sourceStatus(task) === "completed" ? "completed" : sourceStatus(task) === "in_progress" || sourceStatus(task) === "review" ? "in_progress" : "pending"
|
|
16846
|
+
} : todo
|
|
16847
|
+
);
|
|
16848
|
+
suppressedTodoMirrors.add(context);
|
|
16849
|
+
try {
|
|
16850
|
+
context.state.replaceTodos(next);
|
|
16851
|
+
} finally {
|
|
16852
|
+
suppressedTodoMirrors.delete(context);
|
|
16853
|
+
}
|
|
16854
|
+
return { source: "todo", todos: [...context.todos] };
|
|
16855
|
+
}
|
|
16856
|
+
const id = context.session?.id ?? "";
|
|
16857
|
+
if (task.origin?.system === "session-plan" || graphId.startsWith("plan:")) {
|
|
16858
|
+
const planPath = context.meta["plan.path"];
|
|
16859
|
+
if (typeof planPath !== "string" || !planPath) return { source: "plan" };
|
|
16860
|
+
const plan = await mutatePlan(planPath, id, (file) => ({
|
|
16861
|
+
...file,
|
|
16862
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16863
|
+
items: options.remove ? file.items.filter((item) => item.id !== originId) : file.items.map(
|
|
16864
|
+
(item) => item.id === originId ? {
|
|
16865
|
+
...item,
|
|
16866
|
+
title: task.title,
|
|
16867
|
+
details: task.description,
|
|
16868
|
+
status: task.status === "completed" ? "done" : task.status === "in_progress" || task.status === "review" ? "in_progress" : "open",
|
|
16869
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16870
|
+
} : item
|
|
16871
|
+
)
|
|
16872
|
+
}));
|
|
16873
|
+
return { source: "plan", plan };
|
|
16874
|
+
}
|
|
16875
|
+
if (task.origin?.system === "session-task" || task.origin?.system === "session" || graphId.startsWith("session:")) {
|
|
16876
|
+
const taskPath = context.meta["task.path"];
|
|
16877
|
+
if (typeof taskPath !== "string" || !taskPath) return { source: "task" };
|
|
16878
|
+
const tasks = await mutateTasks(taskPath, id, (file) => ({
|
|
16879
|
+
...file,
|
|
16880
|
+
tasks: options.remove ? file.tasks.filter((item) => item.id !== originId) : file.tasks.map(
|
|
16881
|
+
(item) => item.id === originId ? {
|
|
16882
|
+
...item,
|
|
16883
|
+
title: task.title,
|
|
16884
|
+
description: task.description,
|
|
16885
|
+
status: sourceStatus(task),
|
|
16886
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16887
|
+
} : item
|
|
16888
|
+
)
|
|
16889
|
+
}));
|
|
16890
|
+
return { source: "task", tasks };
|
|
16891
|
+
}
|
|
16892
|
+
return { source: null };
|
|
16893
|
+
}
|
|
16894
|
+
|
|
16895
|
+
// src/kanban.ts
|
|
15450
16896
|
var kanbanTool = {
|
|
15451
16897
|
name: "kanban",
|
|
15452
16898
|
category: "Project",
|
|
15453
|
-
description: "Manage project-scoped multi-kanban boards stored under .wrongstack/kanbans.
|
|
15454
|
-
usageHint: "Use this for durable project kanban state.
|
|
16899
|
+
description: "Manage and audit project-scoped multi-kanban boards stored under .wrongstack/kanbans. Managed cards enforce fully specified details and adjacent Backlog \u2192 Todo \u2192 Running \u2192 Review \u2192 Done transitions with persistent comments and evidence. Successful board access records live agent/session presence.",
|
|
16900
|
+
usageHint: "Use this for durable project kanban state. Reassess with get_board whenever evidence changes; agents may add, update, split, merge, reprioritize, or remove tasks so the board remains a live plan. Presence includes active/last-seen session and agent metadata. For managed boards, fully fill card details, use transition_task after every material step, attach truthful evidence, and never use update_task/move_task to bypass lifecycle guards. Worker completion enters Review; only passed acceptance criteria plus review evidence allow Done.",
|
|
15455
16901
|
permission: "confirm",
|
|
15456
16902
|
mutating: true,
|
|
15457
16903
|
capabilities: ["fs.write"],
|
|
@@ -15473,6 +16919,8 @@ var kanbanTool = {
|
|
|
15473
16919
|
"export_markdown",
|
|
15474
16920
|
"export_task_graph",
|
|
15475
16921
|
"sync_task_graph",
|
|
16922
|
+
"create_from_graph",
|
|
16923
|
+
"import_session_tasks",
|
|
15476
16924
|
"search_tasks",
|
|
15477
16925
|
"ready_tasks",
|
|
15478
16926
|
"snapshot",
|
|
@@ -15486,6 +16934,7 @@ var kanbanTool = {
|
|
|
15486
16934
|
"transfer_task",
|
|
15487
16935
|
"get_task",
|
|
15488
16936
|
"update_task",
|
|
16937
|
+
"transition_task",
|
|
15489
16938
|
"move_task",
|
|
15490
16939
|
"delete_task",
|
|
15491
16940
|
"set_chain",
|
|
@@ -15516,9 +16965,14 @@ var kanbanTool = {
|
|
|
15516
16965
|
targetColumnId: { type: "string" },
|
|
15517
16966
|
title: { type: "string" },
|
|
15518
16967
|
description: { type: "string" },
|
|
16968
|
+
dueDate: { type: "string" },
|
|
15519
16969
|
tags: { type: "array", items: { type: "string" } },
|
|
15520
16970
|
labels: { type: "array", items: { type: "string" } },
|
|
15521
16971
|
priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
|
|
16972
|
+
taskType: {
|
|
16973
|
+
type: "string",
|
|
16974
|
+
enum: ["feature", "bugfix", "refactor", "docs", "test", "chore"]
|
|
16975
|
+
},
|
|
15522
16976
|
status: {
|
|
15523
16977
|
type: "string",
|
|
15524
16978
|
enum: [
|
|
@@ -15558,11 +17012,36 @@ var kanbanTool = {
|
|
|
15558
17012
|
type: "string",
|
|
15559
17013
|
enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
|
|
15560
17014
|
},
|
|
17015
|
+
lifecycleStage: {
|
|
17016
|
+
type: "string",
|
|
17017
|
+
enum: ["backlog", "todo", "running", "review", "done"]
|
|
17018
|
+
},
|
|
17019
|
+
transitionAction: { type: "string" },
|
|
17020
|
+
transitionComment: { type: "string" },
|
|
17021
|
+
attachmentUrl: { type: "string" },
|
|
17022
|
+
attachmentTitle: { type: "string" },
|
|
17023
|
+
attachmentType: {
|
|
17024
|
+
type: "string",
|
|
17025
|
+
enum: ["issue", "pr", "doc", "commit", "design", "file", "url", "other"]
|
|
17026
|
+
},
|
|
15561
17027
|
releaseStatus: { type: "string", enum: ["pending", "ready", "blocked"] },
|
|
15562
17028
|
releaseReason: { type: "string" },
|
|
15563
17029
|
clearAssignee: { type: "boolean" },
|
|
15564
|
-
recoveryMode: { type: "string", enum: ["release", "retry", "fail"] },
|
|
17030
|
+
recoveryMode: { type: "string", enum: ["auto", "release", "retry", "fail"] },
|
|
15565
17031
|
recoveryNow: { type: "string" },
|
|
17032
|
+
recoveryPolicyFailOnCostCeiling: { type: "boolean" },
|
|
17033
|
+
recoveryPolicyReleaseOnFailureKinds: { type: "array", items: { type: "string" } },
|
|
17034
|
+
recoveryPolicyReleaseOnHeartbeatDue: { type: "boolean" },
|
|
17035
|
+
recoveryPolicyRetryPolicyOverride: {
|
|
17036
|
+
type: "string",
|
|
17037
|
+
enum: ["off", "incremental", "exponential"]
|
|
17038
|
+
},
|
|
17039
|
+
assignee: { type: "string" },
|
|
17040
|
+
costCeilingUsd: { type: "number" },
|
|
17041
|
+
retryPolicy: { type: "string", enum: ["off", "incremental", "exponential"] },
|
|
17042
|
+
lastFailureKind: { type: "string" },
|
|
17043
|
+
dependsOn: { type: "array", items: { type: "string" } },
|
|
17044
|
+
estimatedHours: { type: "number" },
|
|
15566
17045
|
taskGraph: { type: "object" },
|
|
15567
17046
|
graphId: { type: "string" },
|
|
15568
17047
|
specId: { type: "string" },
|
|
@@ -15615,538 +17094,632 @@ var kanbanTool = {
|
|
|
15615
17094
|
async execute(input, ctx) {
|
|
15616
17095
|
const projectRoot = ctx.projectRoot;
|
|
15617
17096
|
if (!projectRoot) return fail("No project root is available.");
|
|
17097
|
+
const withPresence = async (result) => {
|
|
17098
|
+
const boardId = result.board?.id ?? input.boardId;
|
|
17099
|
+
if (!result.ok || !boardId || !ctx.session?.id || !ctx.agentId) return result;
|
|
17100
|
+
try {
|
|
17101
|
+
const board = await touchKanbanPresence2(projectRoot, boardId, {
|
|
17102
|
+
sessionId: ctx.session.id,
|
|
17103
|
+
agentId: ctx.agentId,
|
|
17104
|
+
agentName: ctx.agentName,
|
|
17105
|
+
taskId: input.taskId ?? result.task?.id,
|
|
17106
|
+
runTaskId: input.runTaskId
|
|
17107
|
+
});
|
|
17108
|
+
return board ? { ...result, board } : result;
|
|
17109
|
+
} catch {
|
|
17110
|
+
return result;
|
|
17111
|
+
}
|
|
17112
|
+
};
|
|
15618
17113
|
try {
|
|
15619
|
-
|
|
15620
|
-
|
|
15621
|
-
|
|
15622
|
-
|
|
15623
|
-
|
|
15624
|
-
case "get_board": {
|
|
15625
|
-
const board = await requireBoard(projectRoot, input.boardId);
|
|
15626
|
-
return board ? okBoard(board) : fail("Board not found.");
|
|
15627
|
-
}
|
|
15628
|
-
case "create_board": {
|
|
15629
|
-
if (!input.title) return fail("create_board requires title.");
|
|
15630
|
-
const board = await createBoard(projectRoot, {
|
|
15631
|
-
title: input.title,
|
|
15632
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
15633
|
-
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
15634
|
-
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
|
|
15635
|
-
});
|
|
15636
|
-
return { ok: true, message: `Board created: ${board.title}`, board };
|
|
15637
|
-
}
|
|
15638
|
-
case "update_board": {
|
|
15639
|
-
if (!input.boardId) return fail("update_board requires boardId.");
|
|
15640
|
-
const board = await updateBoard(projectRoot, input.boardId, {
|
|
15641
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
15642
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
15643
|
-
...input.tags !== void 0 ? { tags: input.tags } : {}
|
|
15644
|
-
});
|
|
15645
|
-
return board ? okBoard(board, "Board updated.") : fail("Board not found.");
|
|
15646
|
-
}
|
|
15647
|
-
case "duplicate_board": {
|
|
15648
|
-
if (!input.boardId) return fail("duplicate_board requires boardId.");
|
|
15649
|
-
const board = await duplicateBoard(projectRoot, input.boardId, {
|
|
15650
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
15651
|
-
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
15652
|
-
...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
|
|
15653
|
-
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
15654
|
-
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
|
|
15655
|
-
});
|
|
15656
|
-
return board ? okBoard(board, "Board duplicated.") : fail("Board not found.");
|
|
15657
|
-
}
|
|
15658
|
-
case "delete_board": {
|
|
15659
|
-
if (!input.boardId) return fail("delete_board requires boardId.");
|
|
15660
|
-
const removed = await removeBoard(projectRoot, input.boardId);
|
|
15661
|
-
return { ok: removed, message: removed ? "Board deleted." : "Board not found." };
|
|
15662
|
-
}
|
|
15663
|
-
case "generate_board": {
|
|
15664
|
-
if (!input.description) return fail("generate_board requires description.");
|
|
15665
|
-
const boardInput = generateBoardFromDescription({
|
|
15666
|
-
description: input.description,
|
|
15667
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
15668
|
-
...input.context !== void 0 ? { context: input.context } : {},
|
|
15669
|
-
...input.columns !== void 0 ? { columns: input.columns } : {}
|
|
15670
|
-
});
|
|
15671
|
-
const board = await createBoard(projectRoot, boardInput);
|
|
15672
|
-
for (const taskInput2 of parseLinesIntoTasks(
|
|
15673
|
-
input.description,
|
|
15674
|
-
board.columns[0]?.id ?? "backlog"
|
|
15675
|
-
)) {
|
|
15676
|
-
await addTask(projectRoot, board.id, taskInput2);
|
|
17114
|
+
const result = await (async () => {
|
|
17115
|
+
switch (input.action) {
|
|
17116
|
+
case "list_boards": {
|
|
17117
|
+
const boards = await listBoards2(projectRoot);
|
|
17118
|
+
return { ok: true, message: `${boards.length} board(s).`, boards };
|
|
15677
17119
|
}
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
const board = await requireBoard(projectRoot, input.boardId);
|
|
15682
|
-
if (!board) return fail("Board not found.");
|
|
15683
|
-
return {
|
|
15684
|
-
ok: true,
|
|
15685
|
-
message: "Board exported.",
|
|
15686
|
-
board,
|
|
15687
|
-
markdown: exportBoardAsMarkdown(board)
|
|
15688
|
-
};
|
|
15689
|
-
}
|
|
15690
|
-
case "export_task_graph": {
|
|
15691
|
-
if (!input.boardId) return fail("export_task_graph requires boardId.");
|
|
15692
|
-
const exported = await exportBoardToTaskGraph(projectRoot, input.boardId, {
|
|
15693
|
-
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
15694
|
-
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
15695
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
15696
|
-
...input.preserveOriginTaskIds !== void 0 ? { preserveOriginTaskIds: input.preserveOriginTaskIds } : {},
|
|
15697
|
-
...input.includeArchived !== void 0 ? { includeArchived: input.includeArchived } : {}
|
|
15698
|
-
});
|
|
15699
|
-
if (!exported) return fail("Board not found.");
|
|
15700
|
-
return {
|
|
15701
|
-
ok: true,
|
|
15702
|
-
message: `Task graph exported with ${exported.graph.nodes.size} node(s).`,
|
|
15703
|
-
board: exported.board,
|
|
15704
|
-
taskGraph: serializeTaskGraph(exported.graph)
|
|
15705
|
-
};
|
|
15706
|
-
}
|
|
15707
|
-
case "sync_task_graph": {
|
|
15708
|
-
if (!input.boardId || !input.taskGraph) {
|
|
15709
|
-
return fail("sync_task_graph requires boardId and taskGraph.");
|
|
17120
|
+
case "get_board": {
|
|
17121
|
+
const board = await requireBoard(projectRoot, input.boardId);
|
|
17122
|
+
return board ? okBoard(board) : fail("Board not found.");
|
|
15710
17123
|
}
|
|
15711
|
-
|
|
15712
|
-
|
|
15713
|
-
|
|
15714
|
-
|
|
15715
|
-
|
|
15716
|
-
|
|
15717
|
-
|
|
15718
|
-
|
|
15719
|
-
|
|
15720
|
-
...input.archiveMissingTasks !== void 0 ? { archiveMissingTasks: input.archiveMissingTasks } : {},
|
|
15721
|
-
...input.preserveManualDependencies !== void 0 ? { preserveManualDependencies: input.preserveManualDependencies } : {}
|
|
15722
|
-
});
|
|
15723
|
-
return result ? {
|
|
15724
|
-
ok: true,
|
|
15725
|
-
message: `Task graph synced: ${result.createdTaskIds.length} created, ${result.updatedTaskIds.length} updated, ${result.archivedTaskIds.length} archived.`,
|
|
15726
|
-
board: result.board
|
|
15727
|
-
} : fail("Board not found.");
|
|
15728
|
-
}
|
|
15729
|
-
case "search_tasks": {
|
|
15730
|
-
const tasks = await searchKanban(projectRoot, {
|
|
15731
|
-
query: input.query,
|
|
15732
|
-
boardId: input.boardId,
|
|
15733
|
-
assignedAgent: input.agentId,
|
|
15734
|
-
status: input.status,
|
|
15735
|
-
priority: input.priority,
|
|
15736
|
-
label: input.labels?.[0],
|
|
15737
|
-
chainId: input.chainId
|
|
15738
|
-
});
|
|
15739
|
-
return { ok: true, message: `${tasks.length} task(s) matched.`, tasks };
|
|
15740
|
-
}
|
|
15741
|
-
case "ready_tasks": {
|
|
15742
|
-
const tasks = await listReadyTasks(projectRoot, {
|
|
15743
|
-
query: input.query,
|
|
15744
|
-
boardId: input.boardId,
|
|
15745
|
-
assignedAgent: input.agentId,
|
|
15746
|
-
priority: input.priority,
|
|
15747
|
-
label: input.labels?.[0],
|
|
15748
|
-
chainId: input.chainId,
|
|
15749
|
-
limit: input.limit
|
|
15750
|
-
});
|
|
15751
|
-
return { ok: true, message: `${tasks.length} ready task(s).`, tasks };
|
|
15752
|
-
}
|
|
15753
|
-
case "snapshot": {
|
|
15754
|
-
const snapshot = await getKanbanOrchestrationSnapshot(projectRoot, {
|
|
15755
|
-
query: input.query,
|
|
15756
|
-
boardId: input.boardId,
|
|
15757
|
-
assignedAgent: input.agentId,
|
|
15758
|
-
status: input.status,
|
|
15759
|
-
priority: input.priority,
|
|
15760
|
-
label: input.labels?.[0],
|
|
15761
|
-
chainId: input.chainId
|
|
15762
|
-
});
|
|
15763
|
-
return {
|
|
15764
|
-
ok: true,
|
|
15765
|
-
message: `${snapshot.ready.length} ready, ${snapshot.running.length} running, ${snapshot.blocked.length} blocked.`,
|
|
15766
|
-
snapshot
|
|
15767
|
-
};
|
|
15768
|
-
}
|
|
15769
|
-
case "add_column": {
|
|
15770
|
-
if (!input.boardId || !input.title) return fail("add_column requires boardId and title.");
|
|
15771
|
-
const result = await addColumn(projectRoot, input.boardId, {
|
|
15772
|
-
title: input.title,
|
|
15773
|
-
...input.description !== void 0 ? { description: input.description } : {}
|
|
15774
|
-
});
|
|
15775
|
-
return result ? okBoard(result.board, "Column added.") : fail("Board not found.");
|
|
15776
|
-
}
|
|
15777
|
-
case "update_column": {
|
|
15778
|
-
if (!input.boardId || !input.columnId)
|
|
15779
|
-
return fail("update_column requires boardId and columnId.");
|
|
15780
|
-
const board = await updateColumn(projectRoot, input.boardId, input.columnId, {
|
|
15781
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
15782
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
15783
|
-
...input.order !== void 0 ? { order: input.order } : {}
|
|
15784
|
-
});
|
|
15785
|
-
return board ? okBoard(board, "Column updated.") : fail("Column not found.");
|
|
15786
|
-
}
|
|
15787
|
-
case "delete_column": {
|
|
15788
|
-
if (!input.boardId || !input.columnId)
|
|
15789
|
-
return fail("delete_column requires boardId and columnId.");
|
|
15790
|
-
const board = await removeColumn(projectRoot, input.boardId, input.columnId, {
|
|
15791
|
-
moveTasksToColumnId: input.moveTasksToColumnId
|
|
15792
|
-
});
|
|
15793
|
-
return board ? okBoard(board, "Column deleted.") : fail("Column not found.");
|
|
15794
|
-
}
|
|
15795
|
-
case "add_task": {
|
|
15796
|
-
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
15797
|
-
const result = await addTask(projectRoot, input.boardId, taskInput(input));
|
|
15798
|
-
return result ? okTask(result.board, result.task, "Task added.") : fail("Board not found.");
|
|
15799
|
-
}
|
|
15800
|
-
case "split_task": {
|
|
15801
|
-
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
15802
|
-
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
17124
|
+
case "create_board": {
|
|
17125
|
+
if (!input.title) return fail("create_board requires title.");
|
|
17126
|
+
const board = await createBoard2(projectRoot, {
|
|
17127
|
+
title: input.title,
|
|
17128
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17129
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
17130
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
|
|
17131
|
+
});
|
|
17132
|
+
return { ok: true, message: `Board created: ${board.title}`, board };
|
|
15803
17133
|
}
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
|
|
15809
|
-
|
|
15810
|
-
|
|
15811
|
-
|
|
15812
|
-
...input.chainChildren !== void 0 ? { chainChildren: input.chainChildren } : {},
|
|
15813
|
-
...input.rewireDependents !== void 0 ? { rewireDependents: input.rewireDependents } : {}
|
|
15814
|
-
});
|
|
15815
|
-
return result ? {
|
|
15816
|
-
ok: true,
|
|
15817
|
-
message: `${result.children.length} child task(s) created.`,
|
|
15818
|
-
board: result.board,
|
|
15819
|
-
task: result.parent,
|
|
15820
|
-
children: result.children
|
|
15821
|
-
} : fail("Task not found.");
|
|
15822
|
-
}
|
|
15823
|
-
case "merge_tasks": {
|
|
15824
|
-
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
15825
|
-
return fail("merge_tasks requires boardId, taskIds, and title.");
|
|
17134
|
+
case "update_board": {
|
|
17135
|
+
if (!input.boardId) return fail("update_board requires boardId.");
|
|
17136
|
+
const board = await updateBoard2(projectRoot, input.boardId, {
|
|
17137
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17138
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17139
|
+
...input.tags !== void 0 ? { tags: input.tags } : {}
|
|
17140
|
+
});
|
|
17141
|
+
return board ? okBoard(board, "Board updated.") : fail("Board not found.");
|
|
15826
17142
|
}
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
15833
|
-
|
|
15834
|
-
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
case "copy_task": {
|
|
15838
|
-
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
15839
|
-
return fail("copy_task requires boardId, taskId, and targetBoardId.");
|
|
17143
|
+
case "duplicate_board": {
|
|
17144
|
+
if (!input.boardId) return fail("duplicate_board requires boardId.");
|
|
17145
|
+
const board = await duplicateBoard(projectRoot, input.boardId, {
|
|
17146
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17147
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
17148
|
+
...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
|
|
17149
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
17150
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
|
|
17151
|
+
});
|
|
17152
|
+
return board ? okBoard(board, "Board duplicated.") : fail("Board not found.");
|
|
15840
17153
|
}
|
|
15841
|
-
|
|
15842
|
-
|
|
15843
|
-
input.boardId
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
|
|
15850
|
-
...input.
|
|
17154
|
+
case "delete_board": {
|
|
17155
|
+
if (!input.boardId) return fail("delete_board requires boardId.");
|
|
17156
|
+
const removed = await removeBoard2(projectRoot, input.boardId);
|
|
17157
|
+
return { ok: removed, message: removed ? "Board deleted." : "Board not found." };
|
|
17158
|
+
}
|
|
17159
|
+
case "generate_board": {
|
|
17160
|
+
if (!input.description) return fail("generate_board requires description.");
|
|
17161
|
+
const boardInput = generateBoardFromDescription({
|
|
17162
|
+
description: input.description,
|
|
17163
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17164
|
+
...input.context !== void 0 ? { context: input.context } : {},
|
|
17165
|
+
...input.columns !== void 0 ? { columns: input.columns } : {}
|
|
17166
|
+
});
|
|
17167
|
+
const board = await createBoard2(projectRoot, boardInput);
|
|
17168
|
+
for (const taskInput2 of parseLinesIntoTasks(
|
|
17169
|
+
input.description,
|
|
17170
|
+
board.columns[0]?.id ?? "backlog"
|
|
17171
|
+
)) {
|
|
17172
|
+
await addTask(projectRoot, board.id, taskInput2);
|
|
15851
17173
|
}
|
|
15852
|
-
|
|
15853
|
-
return result ? okTask(result.targetBoard, result.task, "Task copied to target board.") : fail("Board or task not found.");
|
|
15854
|
-
}
|
|
15855
|
-
case "transfer_task": {
|
|
15856
|
-
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
15857
|
-
return fail("transfer_task requires boardId, taskId, and targetBoardId.");
|
|
17174
|
+
return okBoard(await getBoard2(projectRoot, board.id) ?? board, "Board generated.");
|
|
15858
17175
|
}
|
|
15859
|
-
|
|
15860
|
-
projectRoot,
|
|
15861
|
-
|
|
15862
|
-
|
|
15863
|
-
|
|
15864
|
-
|
|
17176
|
+
case "export_markdown": {
|
|
17177
|
+
const board = await requireBoard(projectRoot, input.boardId);
|
|
17178
|
+
if (!board) return fail("Board not found.");
|
|
17179
|
+
return {
|
|
17180
|
+
ok: true,
|
|
17181
|
+
message: "Board exported.",
|
|
17182
|
+
board,
|
|
17183
|
+
markdown: exportBoardAsMarkdown(board)
|
|
17184
|
+
};
|
|
17185
|
+
}
|
|
17186
|
+
case "export_task_graph": {
|
|
17187
|
+
if (!input.boardId) return fail("export_task_graph requires boardId.");
|
|
17188
|
+
const exported = await exportBoardToTaskGraph(projectRoot, input.boardId, {
|
|
17189
|
+
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
17190
|
+
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
17191
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17192
|
+
...input.preserveOriginTaskIds !== void 0 ? { preserveOriginTaskIds: input.preserveOriginTaskIds } : {},
|
|
17193
|
+
...input.includeArchived !== void 0 ? { includeArchived: input.includeArchived } : {}
|
|
17194
|
+
});
|
|
17195
|
+
if (!exported) return fail("Board not found.");
|
|
17196
|
+
return {
|
|
17197
|
+
ok: true,
|
|
17198
|
+
message: `Task graph exported with ${exported.graph.nodes.size} node(s).`,
|
|
17199
|
+
board: exported.board,
|
|
17200
|
+
taskGraph: serializeTaskGraph(exported.graph)
|
|
17201
|
+
};
|
|
17202
|
+
}
|
|
17203
|
+
case "sync_task_graph": {
|
|
17204
|
+
if (!input.boardId || !input.taskGraph) {
|
|
17205
|
+
return fail("sync_task_graph requires boardId and taskGraph.");
|
|
17206
|
+
}
|
|
17207
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
17208
|
+
const result2 = await syncBoardFromTaskGraph2(projectRoot, input.boardId, graph, {
|
|
17209
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17210
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17211
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
17212
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
17213
|
+
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
17214
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
17215
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
17216
|
+
...input.archiveMissingTasks !== void 0 ? { archiveMissingTasks: input.archiveMissingTasks } : {},
|
|
17217
|
+
...input.preserveManualDependencies !== void 0 ? { preserveManualDependencies: input.preserveManualDependencies } : {}
|
|
17218
|
+
});
|
|
17219
|
+
return result2 ? {
|
|
17220
|
+
ok: true,
|
|
17221
|
+
message: `Task graph synced: ${result2.createdTaskIds.length} created, ${result2.updatedTaskIds.length} updated, ${result2.archivedTaskIds.length} archived.`,
|
|
17222
|
+
board: result2.board
|
|
17223
|
+
} : fail("Board not found.");
|
|
17224
|
+
}
|
|
17225
|
+
case "create_from_graph": {
|
|
17226
|
+
if (!input.taskGraph) return fail("create_from_graph requires taskGraph.");
|
|
17227
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
17228
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
17229
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17230
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17231
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
17232
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
17233
|
+
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
17234
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
17235
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {}
|
|
17236
|
+
});
|
|
17237
|
+
return {
|
|
17238
|
+
ok: true,
|
|
17239
|
+
message: `Created board "${board.title}" from task graph with ${board.tasks.length} tasks.`,
|
|
17240
|
+
board
|
|
17241
|
+
};
|
|
17242
|
+
}
|
|
17243
|
+
case "import_session_tasks": {
|
|
17244
|
+
const taskPath = ctx.meta?.["task.path"];
|
|
17245
|
+
if (!taskPath) return fail("No session task file for this session.");
|
|
17246
|
+
const file = await loadTasks2(taskPath);
|
|
17247
|
+
if (!file || file.tasks.length === 0) return fail("No session tasks to import.");
|
|
17248
|
+
const sessionId = ctx.session?.id ?? file.sessionId ?? "session";
|
|
17249
|
+
const graph = deserializeTaskGraph2(taskFileToSerializedGraph(file.tasks, sessionId));
|
|
17250
|
+
const tags = ["session", `session:${sessionId}`];
|
|
17251
|
+
const existing = (await listBoards2(projectRoot)).find(
|
|
17252
|
+
(b) => b.tags?.includes(`session:${sessionId}`)
|
|
17253
|
+
);
|
|
17254
|
+
if (existing) {
|
|
17255
|
+
const result2 = await syncBoardFromTaskGraph2(projectRoot, existing.id, graph, {
|
|
17256
|
+
sourceSystem: "session",
|
|
17257
|
+
tags,
|
|
17258
|
+
archiveMissingTasks: true,
|
|
17259
|
+
includeCompletedTasks: true
|
|
17260
|
+
});
|
|
17261
|
+
return result2 ? {
|
|
17262
|
+
ok: true,
|
|
17263
|
+
message: `Synced ${file.tasks.length} session tasks into board "${result2.board.title}".`,
|
|
17264
|
+
board: result2.board
|
|
17265
|
+
} : fail("Session board vanished mid-sync.");
|
|
17266
|
+
}
|
|
17267
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
17268
|
+
title: `Session tasks (${sessionId.slice(0, 8)})`,
|
|
17269
|
+
sourceSystem: "session",
|
|
17270
|
+
tags
|
|
17271
|
+
});
|
|
17272
|
+
return {
|
|
17273
|
+
ok: true,
|
|
17274
|
+
message: `Imported ${file.tasks.length} session tasks into new board "${board.title}".`,
|
|
17275
|
+
board
|
|
17276
|
+
};
|
|
17277
|
+
}
|
|
17278
|
+
case "search_tasks": {
|
|
17279
|
+
const tasks = await searchKanban(projectRoot, {
|
|
17280
|
+
query: input.query,
|
|
17281
|
+
boardId: input.boardId,
|
|
17282
|
+
assignedAgent: input.agentId,
|
|
17283
|
+
status: input.status,
|
|
17284
|
+
priority: input.priority,
|
|
17285
|
+
label: input.labels?.[0],
|
|
17286
|
+
chainId: input.chainId
|
|
17287
|
+
});
|
|
17288
|
+
return { ok: true, message: `${tasks.length} task(s) matched.`, tasks };
|
|
17289
|
+
}
|
|
17290
|
+
case "ready_tasks": {
|
|
17291
|
+
const tasks = await listReadyTasks(projectRoot, {
|
|
17292
|
+
query: input.query,
|
|
17293
|
+
boardId: input.boardId,
|
|
17294
|
+
assignedAgent: input.agentId,
|
|
17295
|
+
priority: input.priority,
|
|
17296
|
+
label: input.labels?.[0],
|
|
17297
|
+
chainId: input.chainId,
|
|
17298
|
+
limit: input.limit
|
|
17299
|
+
});
|
|
17300
|
+
return { ok: true, message: `${tasks.length} ready task(s).`, tasks };
|
|
17301
|
+
}
|
|
17302
|
+
case "snapshot": {
|
|
17303
|
+
const snapshot = await getKanbanOrchestrationSnapshot(projectRoot, {
|
|
17304
|
+
query: input.query,
|
|
17305
|
+
boardId: input.boardId,
|
|
17306
|
+
assignedAgent: input.agentId,
|
|
17307
|
+
status: input.status,
|
|
17308
|
+
priority: input.priority,
|
|
17309
|
+
label: input.labels?.[0],
|
|
17310
|
+
chainId: input.chainId
|
|
17311
|
+
});
|
|
17312
|
+
return {
|
|
17313
|
+
ok: true,
|
|
17314
|
+
message: `${snapshot.ready.length} ready, ${snapshot.running.length} running, ${snapshot.blocked.length} blocked.`,
|
|
17315
|
+
snapshot
|
|
17316
|
+
};
|
|
17317
|
+
}
|
|
17318
|
+
case "add_column": {
|
|
17319
|
+
if (!input.boardId || !input.title) return fail("add_column requires boardId and title.");
|
|
17320
|
+
const result2 = await addColumn(projectRoot, input.boardId, {
|
|
17321
|
+
title: input.title,
|
|
17322
|
+
...input.description !== void 0 ? { description: input.description } : {}
|
|
17323
|
+
});
|
|
17324
|
+
return result2 ? okBoard(result2.board, "Column added.") : fail("Board not found.");
|
|
17325
|
+
}
|
|
17326
|
+
case "update_column": {
|
|
17327
|
+
if (!input.boardId || !input.columnId)
|
|
17328
|
+
return fail("update_column requires boardId and columnId.");
|
|
17329
|
+
const board = await updateColumn(projectRoot, input.boardId, input.columnId, {
|
|
17330
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17331
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17332
|
+
...input.order !== void 0 ? { order: input.order } : {}
|
|
17333
|
+
});
|
|
17334
|
+
return board ? okBoard(board, "Column updated.") : fail("Column not found.");
|
|
17335
|
+
}
|
|
17336
|
+
case "delete_column": {
|
|
17337
|
+
if (!input.boardId || !input.columnId)
|
|
17338
|
+
return fail("delete_column requires boardId and columnId.");
|
|
17339
|
+
const board = await removeColumn(projectRoot, input.boardId, input.columnId, {
|
|
17340
|
+
moveTasksToColumnId: input.moveTasksToColumnId
|
|
17341
|
+
});
|
|
17342
|
+
return board ? okBoard(board, "Column deleted.") : fail("Column not found.");
|
|
17343
|
+
}
|
|
17344
|
+
case "add_task": {
|
|
17345
|
+
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
17346
|
+
const result2 = await addTask(projectRoot, input.boardId, taskInput(input));
|
|
17347
|
+
return result2 ? okTask(result2.board, result2.task, "Task added.") : fail("Board not found.");
|
|
17348
|
+
}
|
|
17349
|
+
case "split_task": {
|
|
17350
|
+
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
17351
|
+
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
17352
|
+
}
|
|
17353
|
+
const result2 = await splitTask(projectRoot, input.boardId, input.taskId, {
|
|
17354
|
+
titles: input.childTitles,
|
|
17355
|
+
...input.targetColumnId !== void 0 ? { columnId: input.targetColumnId } : {},
|
|
17356
|
+
...input.inheritAssignment !== void 0 ? { inheritAssignment: input.inheritAssignment } : {},
|
|
17357
|
+
...input.inheritLabels !== void 0 ? { inheritLabels: input.inheritLabels } : {},
|
|
17358
|
+
...input.inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria: input.inheritSuccessCriteria } : {},
|
|
17359
|
+
...input.inheritGoalMetrics !== void 0 ? { inheritGoalMetrics: input.inheritGoalMetrics } : {},
|
|
17360
|
+
...input.inheritDependencies !== void 0 ? { inheritDependencies: input.inheritDependencies } : {},
|
|
17361
|
+
...input.chainChildren !== void 0 ? { chainChildren: input.chainChildren } : {},
|
|
17362
|
+
...input.rewireDependents !== void 0 ? { rewireDependents: input.rewireDependents } : {}
|
|
17363
|
+
});
|
|
17364
|
+
return result2 ? {
|
|
17365
|
+
ok: true,
|
|
17366
|
+
message: `${result2.children.length} child task(s) created.`,
|
|
17367
|
+
board: result2.board,
|
|
17368
|
+
task: result2.parent,
|
|
17369
|
+
children: result2.children
|
|
17370
|
+
} : fail("Task not found.");
|
|
17371
|
+
}
|
|
17372
|
+
case "merge_tasks": {
|
|
17373
|
+
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
17374
|
+
return fail("merge_tasks requires boardId, taskIds, and title.");
|
|
17375
|
+
}
|
|
17376
|
+
const result2 = await mergeTasks(projectRoot, input.boardId, {
|
|
17377
|
+
taskIds: input.taskIds,
|
|
17378
|
+
title: input.title,
|
|
17379
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
15865
17380
|
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
15866
|
-
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
15867
17381
|
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
15868
|
-
...input.
|
|
17382
|
+
...input.closeSourceTasks !== void 0 ? { closeSourceTasks: input.closeSourceTasks } : {}
|
|
17383
|
+
});
|
|
17384
|
+
return result2 ? okTask(result2.board, result2.task, "Tasks merged.") : fail("Board or task not found.");
|
|
17385
|
+
}
|
|
17386
|
+
case "copy_task": {
|
|
17387
|
+
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
17388
|
+
return fail("copy_task requires boardId, taskId, and targetBoardId.");
|
|
15869
17389
|
}
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
15875
|
-
|
|
15876
|
-
|
|
15877
|
-
|
|
15878
|
-
|
|
15879
|
-
|
|
15880
|
-
|
|
15881
|
-
|
|
15882
|
-
|
|
15883
|
-
input.boardId,
|
|
15884
|
-
input.taskId,
|
|
15885
|
-
taskPatch(input)
|
|
15886
|
-
);
|
|
15887
|
-
return board ? okBoard(board, "Task updated.") : fail("Task not found.");
|
|
15888
|
-
}
|
|
15889
|
-
case "move_task": {
|
|
15890
|
-
if (!input.boardId || !input.taskId || !input.targetColumnId) {
|
|
15891
|
-
return fail("move_task requires boardId, taskId, and targetColumnId.");
|
|
17390
|
+
const result2 = await copyTaskToBoard(
|
|
17391
|
+
projectRoot,
|
|
17392
|
+
input.boardId,
|
|
17393
|
+
input.taskId,
|
|
17394
|
+
input.targetBoardId,
|
|
17395
|
+
{
|
|
17396
|
+
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
17397
|
+
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
17398
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
17399
|
+
...input.preserveDependencies !== void 0 ? { preserveDependencies: input.preserveDependencies } : {}
|
|
17400
|
+
}
|
|
17401
|
+
);
|
|
17402
|
+
return result2 ? okTask(result2.targetBoard, result2.task, "Task copied to target board.") : fail("Board or task not found.");
|
|
15892
17403
|
}
|
|
15893
|
-
|
|
15894
|
-
|
|
15895
|
-
|
|
15896
|
-
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
|
|
15908
|
-
|
|
15909
|
-
|
|
15910
|
-
return fail("set_chain requires boardId and taskIds.");
|
|
17404
|
+
case "transfer_task": {
|
|
17405
|
+
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
17406
|
+
return fail("transfer_task requires boardId, taskId, and targetBoardId.");
|
|
17407
|
+
}
|
|
17408
|
+
const result2 = await transferTaskToBoard(
|
|
17409
|
+
projectRoot,
|
|
17410
|
+
input.boardId,
|
|
17411
|
+
input.taskId,
|
|
17412
|
+
input.targetBoardId,
|
|
17413
|
+
{
|
|
17414
|
+
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
17415
|
+
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
17416
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
17417
|
+
...input.preserveDependencies !== void 0 ? { preserveDependencies: input.preserveDependencies } : {}
|
|
17418
|
+
}
|
|
17419
|
+
);
|
|
17420
|
+
return result2 ? okTask(result2.targetBoard, result2.task, "Task transferred to target board.") : fail("Board or task not found.");
|
|
15911
17421
|
}
|
|
15912
|
-
|
|
15913
|
-
|
|
15914
|
-
|
|
15915
|
-
|
|
15916
|
-
});
|
|
15917
|
-
return result ? {
|
|
15918
|
-
ok: true,
|
|
15919
|
-
message: `Chain set: ${result.chainId}`,
|
|
15920
|
-
board: result.board,
|
|
15921
|
-
chain: result.tasks
|
|
15922
|
-
} : fail("Board or task not found.");
|
|
15923
|
-
}
|
|
15924
|
-
case "get_chain": {
|
|
15925
|
-
if (!input.boardId || !(input.taskId || input.chainId)) {
|
|
15926
|
-
return fail("get_chain requires boardId and taskId or chainId.");
|
|
17422
|
+
case "get_task": {
|
|
17423
|
+
if (!input.boardId || !input.taskId) return fail("get_task requires boardId and taskId.");
|
|
17424
|
+
const task = await getTask(projectRoot, input.boardId, input.taskId);
|
|
17425
|
+
return task ? { ok: true, message: "Task loaded.", task } : fail("Task not found.");
|
|
15927
17426
|
}
|
|
15928
|
-
|
|
15929
|
-
|
|
15930
|
-
|
|
15931
|
-
|
|
15932
|
-
|
|
15933
|
-
|
|
15934
|
-
|
|
15935
|
-
|
|
15936
|
-
|
|
15937
|
-
|
|
15938
|
-
} : fail("Chain not found.");
|
|
15939
|
-
}
|
|
15940
|
-
case "claim_task": {
|
|
15941
|
-
const result = await claimReadyTask(projectRoot, {
|
|
15942
|
-
...input.boardId !== void 0 ? { boardId: input.boardId } : {},
|
|
15943
|
-
...input.taskId !== void 0 ? { taskId: input.taskId } : {},
|
|
15944
|
-
...assignmentInput(input),
|
|
15945
|
-
status: input.assignmentStatus ?? "queued"
|
|
15946
|
-
});
|
|
15947
|
-
return result ? okTask(result.board, result.task, "Task claimed.") : fail("No ready kanban task matched the claim.");
|
|
15948
|
-
}
|
|
15949
|
-
case "release_task": {
|
|
15950
|
-
if (!input.boardId || !input.taskId) {
|
|
15951
|
-
return fail("release_task requires boardId and taskId.");
|
|
17427
|
+
case "update_task": {
|
|
17428
|
+
if (!input.boardId || !input.taskId)
|
|
17429
|
+
return fail("update_task requires boardId and taskId.");
|
|
17430
|
+
const board = await updateTask(
|
|
17431
|
+
projectRoot,
|
|
17432
|
+
input.boardId,
|
|
17433
|
+
input.taskId,
|
|
17434
|
+
taskPatch(input)
|
|
17435
|
+
);
|
|
17436
|
+
return board ? okBoard(board, "Task updated.") : fail("Task not found.");
|
|
15952
17437
|
}
|
|
15953
|
-
|
|
15954
|
-
|
|
15955
|
-
|
|
15956
|
-
|
|
15957
|
-
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
|
|
15961
|
-
|
|
15962
|
-
|
|
15963
|
-
|
|
15964
|
-
|
|
15965
|
-
|
|
15966
|
-
|
|
15967
|
-
|
|
15968
|
-
|
|
15969
|
-
|
|
15970
|
-
|
|
15971
|
-
|
|
15972
|
-
|
|
15973
|
-
return fail("
|
|
15974
|
-
const assignmentStatus = input.assignmentStatus ?? (input.status === "completed" ? "completed" : input.error ? "failed" : void 0);
|
|
15975
|
-
const board = await updateTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
15976
|
-
...assignmentStatus !== void 0 ? { status: assignmentStatus } : {},
|
|
15977
|
-
...input.subagentId !== void 0 ? { subagentId: input.subagentId } : {},
|
|
15978
|
-
...input.runTaskId !== void 0 ? { runTaskId: input.runTaskId } : {},
|
|
15979
|
-
...input.lastResult !== void 0 ? { lastResult: input.lastResult } : {},
|
|
15980
|
-
...input.error !== void 0 ? { error: input.error } : {},
|
|
15981
|
-
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
15982
|
-
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
15983
|
-
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
15984
|
-
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
15985
|
-
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
15986
|
-
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
15987
|
-
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {}
|
|
15988
|
-
});
|
|
15989
|
-
return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
|
|
15990
|
-
}
|
|
15991
|
-
case "heartbeat_assignment": {
|
|
15992
|
-
if (!input.boardId || !input.taskId) {
|
|
15993
|
-
return fail("heartbeat_assignment requires boardId and taskId.");
|
|
17438
|
+
case "transition_task": {
|
|
17439
|
+
if (!input.boardId || !input.taskId || !input.lifecycleStage || !input.author || !input.transitionComment) {
|
|
17440
|
+
return fail(
|
|
17441
|
+
"transition_task requires boardId, taskId, lifecycleStage, author, and transitionComment."
|
|
17442
|
+
);
|
|
17443
|
+
}
|
|
17444
|
+
const result2 = await transitionTask(projectRoot, input.boardId, input.taskId, {
|
|
17445
|
+
to: input.lifecycleStage,
|
|
17446
|
+
actor: input.author,
|
|
17447
|
+
comment: input.transitionComment,
|
|
17448
|
+
...input.transitionAction !== void 0 ? { action: input.transitionAction } : {},
|
|
17449
|
+
...input.attachmentUrl !== void 0 ? {
|
|
17450
|
+
attachment: {
|
|
17451
|
+
url: input.attachmentUrl,
|
|
17452
|
+
type: input.attachmentType ?? "url",
|
|
17453
|
+
...input.attachmentTitle !== void 0 ? { title: input.attachmentTitle } : {}
|
|
17454
|
+
}
|
|
17455
|
+
} : {},
|
|
17456
|
+
patch: taskPatch(input)
|
|
17457
|
+
});
|
|
17458
|
+
return result2 ? okTask(result2.board, result2.task, `Task advanced to ${result2.transition.to}.`) : fail("Board or task not found.");
|
|
15994
17459
|
}
|
|
15995
|
-
|
|
15996
|
-
|
|
15997
|
-
|
|
15998
|
-
|
|
15999
|
-
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
input.recoveryPolicyRetryPolicyOverride !== void 0
|
|
16008
|
-
].some(Boolean);
|
|
16009
|
-
const result = await recoverStaleTaskAssignments(projectRoot, input.boardId, {
|
|
16010
|
-
...input.recoveryMode !== void 0 ? { mode: input.recoveryMode } : {},
|
|
16011
|
-
...input.recoveryNow !== void 0 ? { now: input.recoveryNow } : {},
|
|
16012
|
-
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
16013
|
-
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {},
|
|
16014
|
-
...policyFields ? {
|
|
16015
|
-
policy: {
|
|
16016
|
-
...input.recoveryPolicyFailOnCostCeiling !== void 0 ? { failWhenCostCeilingSet: input.recoveryPolicyFailOnCostCeiling } : {},
|
|
16017
|
-
...input.recoveryPolicyReleaseOnFailureKinds !== void 0 ? {
|
|
16018
|
-
releaseOnFailureKinds: input.recoveryPolicyReleaseOnFailureKinds
|
|
16019
|
-
} : {},
|
|
16020
|
-
...input.recoveryPolicyReleaseOnHeartbeatDue !== void 0 ? {
|
|
16021
|
-
releaseOnHeartbeatDue: input.recoveryPolicyReleaseOnHeartbeatDue
|
|
16022
|
-
} : {},
|
|
16023
|
-
...input.recoveryPolicyRetryPolicyOverride !== void 0 ? {
|
|
16024
|
-
retryPolicyOverride: input.recoveryPolicyRetryPolicyOverride
|
|
16025
|
-
} : {}
|
|
16026
|
-
}
|
|
16027
|
-
} : {}
|
|
16028
|
-
});
|
|
16029
|
-
return result ? {
|
|
16030
|
-
ok: true,
|
|
16031
|
-
message: `Recovered ${result.tasks.length} stale assignment(s).`,
|
|
16032
|
-
board: result.board,
|
|
16033
|
-
recoveredTasks: result.tasks
|
|
16034
|
-
} : { ok: true, message: "No stale assignment matched.", recoveredTasks: [] };
|
|
16035
|
-
}
|
|
16036
|
-
case "events": {
|
|
16037
|
-
if (!input.boardId) return fail("events requires boardId.");
|
|
16038
|
-
const eventList = await listKanbanEvents(projectRoot, input.boardId);
|
|
16039
|
-
return {
|
|
16040
|
-
ok: true,
|
|
16041
|
-
message: `${eventList.length} event(s).`,
|
|
16042
|
-
events: eventList
|
|
16043
|
-
};
|
|
16044
|
-
}
|
|
16045
|
-
case "queue_health": {
|
|
16046
|
-
const health = await getKanbanQueueHealth(projectRoot, {
|
|
16047
|
-
...input.boardId !== void 0 ? { boardId: input.boardId } : {}
|
|
16048
|
-
});
|
|
16049
|
-
return {
|
|
16050
|
-
ok: true,
|
|
16051
|
-
message: `Counts: ready=${health.counts.ready}, running=${health.counts.running}, stale=${health.staleAssignments.count}.`,
|
|
16052
|
-
queueHealth: health
|
|
16053
|
-
};
|
|
16054
|
-
}
|
|
16055
|
-
case "add_dependency": {
|
|
16056
|
-
if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
|
|
16057
|
-
return fail("add_dependency requires boardId, taskId, and dependencyTaskId.");
|
|
17460
|
+
case "move_task": {
|
|
17461
|
+
if (!input.boardId || !input.taskId || !input.targetColumnId) {
|
|
17462
|
+
return fail("move_task requires boardId, taskId, and targetColumnId.");
|
|
17463
|
+
}
|
|
17464
|
+
const board = await moveTask(
|
|
17465
|
+
projectRoot,
|
|
17466
|
+
input.boardId,
|
|
17467
|
+
input.taskId,
|
|
17468
|
+
input.targetColumnId,
|
|
17469
|
+
input.order
|
|
17470
|
+
);
|
|
17471
|
+
return board ? okBoard(board, "Task moved.") : fail("Move failed.");
|
|
16058
17472
|
}
|
|
16059
|
-
|
|
16060
|
-
|
|
16061
|
-
|
|
16062
|
-
input.taskId
|
|
16063
|
-
|
|
16064
|
-
);
|
|
16065
|
-
return board ? okBoard(board, "Dependency added.") : fail("Task not found.");
|
|
16066
|
-
}
|
|
16067
|
-
case "add_goal_metric": {
|
|
16068
|
-
if (!input.boardId || !input.taskId || !input.metricName) {
|
|
16069
|
-
return fail("add_goal_metric requires boardId, taskId, and metricName.");
|
|
17473
|
+
case "delete_task": {
|
|
17474
|
+
if (!input.boardId || !input.taskId)
|
|
17475
|
+
return fail("delete_task requires boardId and taskId.");
|
|
17476
|
+
const board = await removeTask(projectRoot, input.boardId, input.taskId);
|
|
17477
|
+
return board ? okBoard(board, "Task deleted.") : fail("Task not found.");
|
|
16070
17478
|
}
|
|
16071
|
-
|
|
16072
|
-
|
|
16073
|
-
|
|
16074
|
-
|
|
16075
|
-
|
|
16076
|
-
|
|
16077
|
-
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
|
|
16081
|
-
|
|
16082
|
-
|
|
16083
|
-
|
|
17479
|
+
case "set_chain": {
|
|
17480
|
+
if (!input.boardId || !input.taskIds?.length) {
|
|
17481
|
+
return fail("set_chain requires boardId and taskIds.");
|
|
17482
|
+
}
|
|
17483
|
+
const result2 = await setTaskChain(projectRoot, input.boardId, {
|
|
17484
|
+
taskIds: input.taskIds,
|
|
17485
|
+
...input.chainId !== void 0 ? { chainId: input.chainId } : {},
|
|
17486
|
+
...input.enforceDependencies !== void 0 ? { enforceDependencies: input.enforceDependencies } : {}
|
|
17487
|
+
});
|
|
17488
|
+
return result2 ? {
|
|
17489
|
+
ok: true,
|
|
17490
|
+
message: `Chain set: ${result2.chainId}`,
|
|
17491
|
+
board: result2.board,
|
|
17492
|
+
chain: result2.tasks
|
|
17493
|
+
} : fail("Board or task not found.");
|
|
16084
17494
|
}
|
|
16085
|
-
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
17495
|
+
case "get_chain": {
|
|
17496
|
+
if (!input.boardId || !(input.taskId || input.chainId)) {
|
|
17497
|
+
return fail("get_chain requires boardId and taskId or chainId.");
|
|
17498
|
+
}
|
|
17499
|
+
const result2 = await getTaskChain(
|
|
17500
|
+
projectRoot,
|
|
17501
|
+
input.boardId,
|
|
17502
|
+
input.taskId ?? input.chainId ?? ""
|
|
17503
|
+
);
|
|
17504
|
+
return result2 ? {
|
|
17505
|
+
ok: true,
|
|
17506
|
+
message: `Chain loaded: ${result2.chainId}`,
|
|
17507
|
+
board: result2.board,
|
|
17508
|
+
chain: result2.tasks
|
|
17509
|
+
} : fail("Chain not found.");
|
|
17510
|
+
}
|
|
17511
|
+
case "claim_task": {
|
|
17512
|
+
const result2 = await claimReadyTask(projectRoot, {
|
|
17513
|
+
...input.boardId !== void 0 ? { boardId: input.boardId } : {},
|
|
17514
|
+
...input.taskId !== void 0 ? { taskId: input.taskId } : {},
|
|
17515
|
+
...assignmentInput(input),
|
|
17516
|
+
status: input.assignmentStatus ?? "queued"
|
|
17517
|
+
});
|
|
17518
|
+
return result2 ? okTask(result2.board, result2.task, "Task claimed.") : fail("No ready kanban task matched the claim.");
|
|
17519
|
+
}
|
|
17520
|
+
case "release_task": {
|
|
17521
|
+
if (!input.boardId || !input.taskId) {
|
|
17522
|
+
return fail("release_task requires boardId and taskId.");
|
|
17523
|
+
}
|
|
17524
|
+
const board = await releaseTaskClaim(projectRoot, input.boardId, input.taskId, {
|
|
17525
|
+
...input.releaseStatus !== void 0 ? { status: input.releaseStatus } : {},
|
|
17526
|
+
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
17527
|
+
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {}
|
|
17528
|
+
});
|
|
17529
|
+
return board ? okBoard(board, "Task claim released.") : fail("Task not found.");
|
|
17530
|
+
}
|
|
17531
|
+
case "assign_task": {
|
|
17532
|
+
if (!input.boardId || !input.taskId)
|
|
17533
|
+
return fail("assign_task requires boardId and taskId.");
|
|
17534
|
+
const board = await assignTask(
|
|
17535
|
+
projectRoot,
|
|
17536
|
+
input.boardId,
|
|
17537
|
+
input.taskId,
|
|
17538
|
+
assignmentInput(input)
|
|
17539
|
+
);
|
|
17540
|
+
return board ? okBoard(board, "Task assigned.") : fail("Task not found.");
|
|
17541
|
+
}
|
|
17542
|
+
case "mark_assignment": {
|
|
17543
|
+
if (!input.boardId || !input.taskId)
|
|
17544
|
+
return fail("mark_assignment requires boardId and taskId.");
|
|
17545
|
+
const assignmentStatus = input.assignmentStatus ?? (input.status === "completed" ? "completed" : input.error ? "failed" : void 0);
|
|
17546
|
+
const board = await updateTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
17547
|
+
...assignmentStatus !== void 0 ? { status: assignmentStatus } : {},
|
|
17548
|
+
...input.subagentId !== void 0 ? { subagentId: input.subagentId } : {},
|
|
17549
|
+
...input.runTaskId !== void 0 ? { runTaskId: input.runTaskId } : {},
|
|
17550
|
+
...input.lastResult !== void 0 ? { lastResult: input.lastResult } : {},
|
|
17551
|
+
...input.error !== void 0 ? { error: input.error } : {},
|
|
17552
|
+
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
17553
|
+
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
17554
|
+
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
17555
|
+
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
17556
|
+
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
17557
|
+
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
17558
|
+
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {}
|
|
17559
|
+
});
|
|
17560
|
+
return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
|
|
17561
|
+
}
|
|
17562
|
+
case "heartbeat_assignment": {
|
|
17563
|
+
if (!input.boardId || !input.taskId) {
|
|
17564
|
+
return fail("heartbeat_assignment requires boardId and taskId.");
|
|
17565
|
+
}
|
|
17566
|
+
const board = await heartbeatTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
17567
|
+
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
17568
|
+
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {}
|
|
17569
|
+
});
|
|
17570
|
+
return board ? okBoard(board, "Assignment heartbeat updated.") : fail("Task assignment not found.");
|
|
17571
|
+
}
|
|
17572
|
+
case "recover_stale": {
|
|
17573
|
+
if (!input.boardId) return fail("recover_stale requires boardId.");
|
|
17574
|
+
const policyFields = [
|
|
17575
|
+
input.recoveryPolicyFailOnCostCeiling !== void 0,
|
|
17576
|
+
input.recoveryPolicyReleaseOnFailureKinds !== void 0,
|
|
17577
|
+
input.recoveryPolicyReleaseOnHeartbeatDue !== void 0,
|
|
17578
|
+
input.recoveryPolicyRetryPolicyOverride !== void 0
|
|
17579
|
+
].some(Boolean);
|
|
17580
|
+
const result2 = await recoverStaleTaskAssignments(projectRoot, input.boardId, {
|
|
17581
|
+
...input.recoveryMode !== void 0 ? { mode: input.recoveryMode } : {},
|
|
17582
|
+
...input.recoveryNow !== void 0 ? { now: input.recoveryNow } : {},
|
|
17583
|
+
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
17584
|
+
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {},
|
|
17585
|
+
...policyFields ? {
|
|
17586
|
+
policy: {
|
|
17587
|
+
...input.recoveryPolicyFailOnCostCeiling !== void 0 ? { failWhenCostCeilingSet: input.recoveryPolicyFailOnCostCeiling } : {},
|
|
17588
|
+
...input.recoveryPolicyReleaseOnFailureKinds !== void 0 ? {
|
|
17589
|
+
releaseOnFailureKinds: input.recoveryPolicyReleaseOnFailureKinds
|
|
17590
|
+
} : {},
|
|
17591
|
+
...input.recoveryPolicyReleaseOnHeartbeatDue !== void 0 ? {
|
|
17592
|
+
releaseOnHeartbeatDue: input.recoveryPolicyReleaseOnHeartbeatDue
|
|
17593
|
+
} : {},
|
|
17594
|
+
...input.recoveryPolicyRetryPolicyOverride !== void 0 ? {
|
|
17595
|
+
retryPolicyOverride: input.recoveryPolicyRetryPolicyOverride
|
|
17596
|
+
} : {}
|
|
17597
|
+
}
|
|
17598
|
+
} : {}
|
|
17599
|
+
});
|
|
17600
|
+
return result2 ? {
|
|
17601
|
+
ok: true,
|
|
17602
|
+
message: `Recovered ${result2.tasks.length} stale assignment(s).`,
|
|
17603
|
+
board: result2.board,
|
|
17604
|
+
recoveredTasks: result2.tasks
|
|
17605
|
+
} : { ok: true, message: "No stale assignment matched.", recoveredTasks: [] };
|
|
17606
|
+
}
|
|
17607
|
+
case "events": {
|
|
17608
|
+
if (!input.boardId) return fail("events requires boardId.");
|
|
17609
|
+
const eventList = await listKanbanEvents(projectRoot, input.boardId);
|
|
17610
|
+
return {
|
|
17611
|
+
ok: true,
|
|
17612
|
+
message: `${eventList.length} event(s).`,
|
|
17613
|
+
events: eventList
|
|
17614
|
+
};
|
|
17615
|
+
}
|
|
17616
|
+
case "queue_health": {
|
|
17617
|
+
const health = await getKanbanQueueHealth(projectRoot, {
|
|
17618
|
+
...input.boardId !== void 0 ? { boardId: input.boardId } : {}
|
|
17619
|
+
});
|
|
17620
|
+
return {
|
|
17621
|
+
ok: true,
|
|
17622
|
+
message: `Counts: ready=${health.counts.ready}, running=${health.counts.running}, stale=${health.staleAssignments.count}.`,
|
|
17623
|
+
queueHealth: health
|
|
17624
|
+
};
|
|
17625
|
+
}
|
|
17626
|
+
case "add_dependency": {
|
|
17627
|
+
if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
|
|
17628
|
+
return fail("add_dependency requires boardId, taskId, and dependencyTaskId.");
|
|
17629
|
+
}
|
|
17630
|
+
const board = await addDependency(
|
|
17631
|
+
projectRoot,
|
|
17632
|
+
input.boardId,
|
|
17633
|
+
input.taskId,
|
|
17634
|
+
input.dependencyTaskId
|
|
17635
|
+
);
|
|
17636
|
+
return board ? okBoard(board, "Dependency added.") : fail("Task not found.");
|
|
17637
|
+
}
|
|
17638
|
+
case "add_goal_metric": {
|
|
17639
|
+
if (!input.boardId || !input.taskId || !input.metricName) {
|
|
17640
|
+
return fail("add_goal_metric requires boardId, taskId, and metricName.");
|
|
17641
|
+
}
|
|
17642
|
+
const board = await addGoalMetricToTask(projectRoot, input.boardId, input.taskId, {
|
|
17643
|
+
name: input.metricName,
|
|
16092
17644
|
...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
|
|
16093
17645
|
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
16094
17646
|
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
16095
17647
|
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
16096
17648
|
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17649
|
+
});
|
|
17650
|
+
return board ? okBoard(board, "Goal metric added.") : fail("Task not found.");
|
|
17651
|
+
}
|
|
17652
|
+
case "update_goal_metric": {
|
|
17653
|
+
if (!input.boardId || !input.taskId || !input.metricId) {
|
|
17654
|
+
return fail("update_goal_metric requires boardId, taskId, and metricId.");
|
|
16097
17655
|
}
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
|
|
16101
|
-
|
|
16102
|
-
|
|
16103
|
-
|
|
17656
|
+
const board = await updateGoalMetricOnTask(
|
|
17657
|
+
projectRoot,
|
|
17658
|
+
input.boardId,
|
|
17659
|
+
input.taskId,
|
|
17660
|
+
input.metricId,
|
|
17661
|
+
{
|
|
17662
|
+
...input.metricName !== void 0 ? { name: input.metricName } : {},
|
|
17663
|
+
...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
|
|
17664
|
+
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
17665
|
+
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
17666
|
+
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
17667
|
+
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17668
|
+
}
|
|
17669
|
+
);
|
|
17670
|
+
return board ? okBoard(board, "Goal metric updated.") : fail("Metric not found.");
|
|
16104
17671
|
}
|
|
16105
|
-
|
|
16106
|
-
|
|
16107
|
-
|
|
16108
|
-
|
|
16109
|
-
|
|
16110
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
16114
|
-
return
|
|
17672
|
+
case "add_check": {
|
|
17673
|
+
if (!input.boardId || !input.taskId || !input.checkDescription) {
|
|
17674
|
+
return fail("add_check requires boardId, taskId, and checkDescription.");
|
|
17675
|
+
}
|
|
17676
|
+
const board = await addCheckToTask(projectRoot, input.boardId, input.taskId, {
|
|
17677
|
+
description: input.checkDescription,
|
|
17678
|
+
type: "manual",
|
|
17679
|
+
status: input.checkStatus
|
|
17680
|
+
});
|
|
17681
|
+
return board ? okBoard(board, "Check added.") : fail("Task not found.");
|
|
16115
17682
|
}
|
|
16116
|
-
|
|
16117
|
-
|
|
16118
|
-
|
|
16119
|
-
input.taskId,
|
|
16120
|
-
input.checkId,
|
|
16121
|
-
{
|
|
16122
|
-
...input.checkDescription !== void 0 ? { description: input.checkDescription } : {},
|
|
16123
|
-
...input.checkStatus !== void 0 ? { status: input.checkStatus } : {}
|
|
17683
|
+
case "update_check": {
|
|
17684
|
+
if (!input.boardId || !input.taskId || !input.checkId) {
|
|
17685
|
+
return fail("update_check requires boardId, taskId, and checkId.");
|
|
16124
17686
|
}
|
|
16125
|
-
|
|
16126
|
-
|
|
16127
|
-
|
|
16128
|
-
|
|
16129
|
-
|
|
16130
|
-
|
|
16131
|
-
|
|
16132
|
-
|
|
16133
|
-
|
|
16134
|
-
|
|
16135
|
-
|
|
16136
|
-
|
|
16137
|
-
|
|
16138
|
-
|
|
16139
|
-
|
|
16140
|
-
|
|
16141
|
-
|
|
16142
|
-
|
|
16143
|
-
|
|
16144
|
-
|
|
16145
|
-
|
|
17687
|
+
const board = await updateCheckOnTask(
|
|
17688
|
+
projectRoot,
|
|
17689
|
+
input.boardId,
|
|
17690
|
+
input.taskId,
|
|
17691
|
+
input.checkId,
|
|
17692
|
+
{
|
|
17693
|
+
...input.checkDescription !== void 0 ? { description: input.checkDescription } : {},
|
|
17694
|
+
...input.checkStatus !== void 0 ? { status: input.checkStatus } : {}
|
|
17695
|
+
}
|
|
17696
|
+
);
|
|
17697
|
+
return board ? okBoard(board, "Check updated.") : fail("Check not found.");
|
|
17698
|
+
}
|
|
17699
|
+
case "add_note": {
|
|
17700
|
+
if (!input.boardId || !input.taskId || !input.note)
|
|
17701
|
+
return fail("add_note requires boardId, taskId, and note.");
|
|
17702
|
+
const board = await addNoteToTask(projectRoot, input.boardId, input.taskId, {
|
|
17703
|
+
author: input.author ?? "agent",
|
|
17704
|
+
content: input.note
|
|
17705
|
+
});
|
|
17706
|
+
return board ? okBoard(board, "Note added.") : fail("Task not found.");
|
|
17707
|
+
}
|
|
17708
|
+
case "add_link": {
|
|
17709
|
+
if (!input.boardId || !input.taskId || !input.url)
|
|
17710
|
+
return fail("add_link requires boardId, taskId, and url.");
|
|
17711
|
+
const board = await addLinkToTask(projectRoot, input.boardId, input.taskId, {
|
|
17712
|
+
url: input.url,
|
|
17713
|
+
type: input.linkType ?? "url",
|
|
17714
|
+
...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
|
|
17715
|
+
});
|
|
17716
|
+
return board ? okBoard(board, "Link added.") : fail("Task not found.");
|
|
17717
|
+
}
|
|
17718
|
+
default:
|
|
17719
|
+
return fail(`Unknown kanban action: ${input.action}`);
|
|
16146
17720
|
}
|
|
16147
|
-
|
|
16148
|
-
|
|
16149
|
-
}
|
|
17721
|
+
})();
|
|
17722
|
+
return withPresence(result);
|
|
16150
17723
|
} catch (err) {
|
|
16151
17724
|
return fail(err instanceof Error ? err.message : String(err));
|
|
16152
17725
|
}
|
|
@@ -16162,7 +17735,7 @@ function okTask(board, task, message) {
|
|
|
16162
17735
|
return { ok: true, message, board, task };
|
|
16163
17736
|
}
|
|
16164
17737
|
async function requireBoard(projectRoot, boardId) {
|
|
16165
|
-
return boardId ?
|
|
17738
|
+
return boardId ? getBoard2(projectRoot, boardId) : null;
|
|
16166
17739
|
}
|
|
16167
17740
|
function taskInput(input) {
|
|
16168
17741
|
const assignment = hasAssignmentInput(input) ? assignmentForTaskCreate(input) : void 0;
|
|
@@ -16170,26 +17743,99 @@ function taskInput(input) {
|
|
|
16170
17743
|
title: input.title ?? "",
|
|
16171
17744
|
columnId: input.columnId,
|
|
16172
17745
|
description: input.description,
|
|
17746
|
+
dueDate: input.dueDate,
|
|
16173
17747
|
priority: input.priority,
|
|
17748
|
+
...input.taskType !== void 0 ? { type: input.taskType } : {},
|
|
16174
17749
|
status: input.status,
|
|
16175
17750
|
labels: input.labels,
|
|
16176
17751
|
...assignment?.agentId ?? assignment?.role ?? assignment?.name ? { assignedAgent: assignment.agentId ?? assignment.role ?? assignment.name } : {},
|
|
16177
17752
|
...input.assignee ?? assignment?.name ?? assignment?.agentId ? { assignee: input.assignee ?? assignment?.name ?? assignment?.agentId } : {},
|
|
16178
|
-
...input
|
|
16179
|
-
...
|
|
17753
|
+
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
17754
|
+
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
|
|
17755
|
+
...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {},
|
|
17756
|
+
...assignment ? { assignment } : {},
|
|
17757
|
+
...input.order !== void 0 ? { order: input.order } : {},
|
|
17758
|
+
// Sprint 3 durable task-level fields
|
|
17759
|
+
...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
|
|
17760
|
+
...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
|
|
17761
|
+
// Relationships — chainId alone cannot construct a KanbanTaskChainRef,
|
|
17762
|
+
// so chain/previousTaskId/nextTaskId must be set via update_task.
|
|
17763
|
+
...input.childTitles !== void 0 ? { childTaskIds: input.childTitles } : {},
|
|
17764
|
+
// Detail objects
|
|
17765
|
+
...input.checkDescription !== void 0 ? {
|
|
17766
|
+
successCriteria: [
|
|
17767
|
+
{
|
|
17768
|
+
id: randomUUID(),
|
|
17769
|
+
description: input.checkDescription,
|
|
17770
|
+
type: "manual",
|
|
17771
|
+
status: input.checkStatus ?? "pending"
|
|
17772
|
+
}
|
|
17773
|
+
]
|
|
17774
|
+
} : {},
|
|
17775
|
+
...input.metricName !== void 0 ? {
|
|
17776
|
+
goalMetrics: [
|
|
17777
|
+
{
|
|
17778
|
+
id: randomUUID(),
|
|
17779
|
+
name: input.metricName,
|
|
17780
|
+
status: input.metricStatus ?? "pending",
|
|
17781
|
+
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
17782
|
+
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
17783
|
+
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
17784
|
+
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17785
|
+
}
|
|
17786
|
+
]
|
|
17787
|
+
} : {},
|
|
17788
|
+
...input.url !== void 0 ? {
|
|
17789
|
+
links: [
|
|
17790
|
+
{
|
|
17791
|
+
url: input.url,
|
|
17792
|
+
type: input.linkType ?? "url",
|
|
17793
|
+
...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
|
|
17794
|
+
}
|
|
17795
|
+
]
|
|
17796
|
+
} : {},
|
|
17797
|
+
...input.note !== void 0 ? {
|
|
17798
|
+
notes: [
|
|
17799
|
+
{
|
|
17800
|
+
id: randomUUID(),
|
|
17801
|
+
author: input.author ?? "agent",
|
|
17802
|
+
content: input.note,
|
|
17803
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17804
|
+
}
|
|
17805
|
+
]
|
|
17806
|
+
} : {},
|
|
17807
|
+
// Origin tracking
|
|
17808
|
+
...input.graphId !== void 0 ? {
|
|
17809
|
+
origin: {
|
|
17810
|
+
system: input.sourceSystem ?? "kanban-tool",
|
|
17811
|
+
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
17812
|
+
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
17813
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {}
|
|
17814
|
+
}
|
|
17815
|
+
} : {}
|
|
16180
17816
|
};
|
|
16181
17817
|
}
|
|
17818
|
+
function mergedDependsOn(input) {
|
|
17819
|
+
const ids = [
|
|
17820
|
+
...input.dependsOn ?? [],
|
|
17821
|
+
...input.dependencyTaskId !== void 0 ? [input.dependencyTaskId] : []
|
|
17822
|
+
].filter((id, i, arr) => id && arr.indexOf(id) === i);
|
|
17823
|
+
return ids.length > 0 ? ids : void 0;
|
|
17824
|
+
}
|
|
16182
17825
|
function taskPatch(input) {
|
|
16183
17826
|
return {
|
|
16184
17827
|
title: input.title,
|
|
16185
17828
|
description: input.description,
|
|
17829
|
+
dueDate: input.dueDate,
|
|
16186
17830
|
columnId: input.columnId,
|
|
16187
17831
|
order: input.order,
|
|
16188
17832
|
priority: input.priority,
|
|
17833
|
+
...input.taskType !== void 0 ? { type: input.taskType } : {},
|
|
16189
17834
|
status: input.status,
|
|
16190
17835
|
labels: input.labels,
|
|
16191
17836
|
assignedAgent: input.agentId,
|
|
16192
|
-
...input
|
|
17837
|
+
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
17838
|
+
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {}
|
|
16193
17839
|
};
|
|
16194
17840
|
}
|
|
16195
17841
|
function assignmentInput(input) {
|
|
@@ -16934,15 +18580,15 @@ import {
|
|
|
16934
18580
|
deriveTodosFromPlanItem,
|
|
16935
18581
|
formatPlan,
|
|
16936
18582
|
getPlanTemplate,
|
|
16937
|
-
mutatePlan,
|
|
18583
|
+
mutatePlan as mutatePlan2,
|
|
16938
18584
|
removePlanItem,
|
|
16939
18585
|
setPlanItemStatus
|
|
16940
18586
|
} from "@wrongstack/core";
|
|
16941
18587
|
import {
|
|
16942
|
-
mutateTasks,
|
|
18588
|
+
mutateTasks as mutateTasks2,
|
|
16943
18589
|
formatTaskList
|
|
16944
18590
|
} from "@wrongstack/core";
|
|
16945
|
-
import { randomUUID } from "node:crypto";
|
|
18591
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
16946
18592
|
var planTool = {
|
|
16947
18593
|
name: "plan",
|
|
16948
18594
|
category: "Session",
|
|
@@ -17026,7 +18672,7 @@ var planTool = {
|
|
|
17026
18672
|
let didTaskify = false;
|
|
17027
18673
|
let plan;
|
|
17028
18674
|
try {
|
|
17029
|
-
plan = await
|
|
18675
|
+
plan = await mutatePlan2(planPath, sessionId, async (p) => {
|
|
17030
18676
|
switch (input.action) {
|
|
17031
18677
|
case "show":
|
|
17032
18678
|
break;
|
|
@@ -17152,6 +18798,7 @@ var planTool = {
|
|
|
17152
18798
|
open: 0
|
|
17153
18799
|
};
|
|
17154
18800
|
}
|
|
18801
|
+
await projectSessionPlanToKanban(ctx.projectRoot, plan.items, sessionId);
|
|
17155
18802
|
if (early) return early;
|
|
17156
18803
|
if (didTaskify) {
|
|
17157
18804
|
const taskPathRaw = ctx.meta["task.path"];
|
|
@@ -17165,9 +18812,9 @@ var planTool = {
|
|
|
17165
18812
|
}
|
|
17166
18813
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
17167
18814
|
try {
|
|
17168
|
-
const taskFile = await
|
|
18815
|
+
const taskFile = await mutateTasks2(taskPath, sessionId, (f) => {
|
|
17169
18816
|
f.tasks.push({
|
|
17170
|
-
id: `task_${
|
|
18817
|
+
id: `task_${randomUUID2()}`,
|
|
17171
18818
|
title: taskifyMeta.title,
|
|
17172
18819
|
description: taskifyMeta.details || void 0,
|
|
17173
18820
|
type: "feature",
|
|
@@ -18302,14 +19949,14 @@ import {
|
|
|
18302
19949
|
formatTaskList as formatTaskList2
|
|
18303
19950
|
} from "@wrongstack/core";
|
|
18304
19951
|
import {
|
|
18305
|
-
mutateTasks as
|
|
19952
|
+
mutateTasks as mutateTasks3
|
|
18306
19953
|
} from "@wrongstack/core";
|
|
18307
19954
|
import {
|
|
18308
19955
|
addPlanItem as addPlanItem2,
|
|
18309
|
-
mutatePlan as
|
|
19956
|
+
mutatePlan as mutatePlan3,
|
|
18310
19957
|
formatPlan as formatPlan2
|
|
18311
19958
|
} from "@wrongstack/core";
|
|
18312
|
-
import { randomUUID as
|
|
19959
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
18313
19960
|
function findTaskIndex(tasks, query) {
|
|
18314
19961
|
const asNum = Number.parseInt(query, 10);
|
|
18315
19962
|
if (!Number.isNaN(asNum)) {
|
|
@@ -18426,7 +20073,7 @@ var taskTool = {
|
|
|
18426
20073
|
let todosToReplace = null;
|
|
18427
20074
|
let file;
|
|
18428
20075
|
try {
|
|
18429
|
-
file = await
|
|
20076
|
+
file = await mutateTasks3(taskPath, sessionId, async (f) => {
|
|
18430
20077
|
switch (input.action) {
|
|
18431
20078
|
case "show":
|
|
18432
20079
|
break;
|
|
@@ -18497,7 +20144,7 @@ var taskTool = {
|
|
|
18497
20144
|
}
|
|
18498
20145
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
18499
20146
|
const newTask = {
|
|
18500
|
-
id: `task_${Date.now()}_${
|
|
20147
|
+
id: `task_${Date.now()}_${randomUUID3().slice(0, 8)}`,
|
|
18501
20148
|
title: t.title,
|
|
18502
20149
|
description: t.description,
|
|
18503
20150
|
type: t.type || "feature",
|
|
@@ -18558,7 +20205,7 @@ var taskTool = {
|
|
|
18558
20205
|
});
|
|
18559
20206
|
if (match.description) {
|
|
18560
20207
|
todos.push({
|
|
18561
|
-
id: `todo_${ts2}_${
|
|
20208
|
+
id: `todo_${ts2}_${randomUUID3().slice(0, 6)}`,
|
|
18562
20209
|
content: match.description.slice(0, 200),
|
|
18563
20210
|
status: "pending",
|
|
18564
20211
|
promotedFromTask: match.id
|
|
@@ -18567,7 +20214,7 @@ var taskTool = {
|
|
|
18567
20214
|
if (input.subtasks && input.subtasks.length > 0) {
|
|
18568
20215
|
for (const st of input.subtasks) {
|
|
18569
20216
|
todos.push({
|
|
18570
|
-
id: `todo_${ts2}_${
|
|
20217
|
+
id: `todo_${ts2}_${randomUUID3().slice(0, 6)}`,
|
|
18571
20218
|
content: st,
|
|
18572
20219
|
status: "pending",
|
|
18573
20220
|
promotedFromTask: match.id
|
|
@@ -18616,6 +20263,7 @@ var taskTool = {
|
|
|
18616
20263
|
};
|
|
18617
20264
|
}
|
|
18618
20265
|
if (todosToReplace) ctx.state.replaceTodos(todosToReplace);
|
|
20266
|
+
await projectSessionTasksToKanban(ctx.projectRoot, file.tasks, sessionId);
|
|
18619
20267
|
if (early) return early;
|
|
18620
20268
|
if (didPlanify) {
|
|
18621
20269
|
const { title, details } = planifyMeta;
|
|
@@ -18629,7 +20277,7 @@ var taskTool = {
|
|
|
18629
20277
|
}
|
|
18630
20278
|
let formatted = "";
|
|
18631
20279
|
try {
|
|
18632
|
-
await
|
|
20280
|
+
await mutatePlan3(planPath, sessionId, (pf) => {
|
|
18633
20281
|
const { plan: updated } = addPlanItem2(pf, title, details || void 0);
|
|
18634
20282
|
formatted = formatPlan2(updated);
|
|
18635
20283
|
return updated;
|
|
@@ -18861,8 +20509,7 @@ function parseResult(runner, result, duration) {
|
|
|
18861
20509
|
}
|
|
18862
20510
|
|
|
18863
20511
|
// src/todo.ts
|
|
18864
|
-
import { loadPlan, savePlan, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core";
|
|
18865
|
-
import { loadTasks, saveTasks } from "@wrongstack/core";
|
|
20512
|
+
import { loadPlan as loadPlan2, loadTasks as loadTasks3, savePlan, saveTasks, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core";
|
|
18866
20513
|
var todoTool = {
|
|
18867
20514
|
name: "todo",
|
|
18868
20515
|
category: "Session",
|
|
@@ -18871,7 +20518,7 @@ var todoTool = {
|
|
|
18871
20518
|
permission: "auto",
|
|
18872
20519
|
mutating: false,
|
|
18873
20520
|
// mutates only conversation state (ctx.todos), not external state — no confirmation needed
|
|
18874
|
-
timeoutMs:
|
|
20521
|
+
timeoutMs: 5e3,
|
|
18875
20522
|
capabilities: ["session.todo"],
|
|
18876
20523
|
icon: "todo",
|
|
18877
20524
|
inputSchema: {
|
|
@@ -18923,16 +20570,21 @@ var todoTool = {
|
|
|
18923
20570
|
}
|
|
18924
20571
|
}
|
|
18925
20572
|
ctx.state.replaceTodos(items);
|
|
20573
|
+
await projectSessionTodosToKanban(ctx.projectRoot, items, ctx.session?.id ?? "session");
|
|
18926
20574
|
const completedPlanIds = /* @__PURE__ */ new Set();
|
|
18927
20575
|
const completedTaskIds = /* @__PURE__ */ new Set();
|
|
18928
20576
|
const pendingPlanIds = /* @__PURE__ */ new Set();
|
|
18929
20577
|
const pendingTaskIds = /* @__PURE__ */ new Set();
|
|
18930
20578
|
for (const item of items) {
|
|
18931
20579
|
if (item.promotedFromPlan) {
|
|
18932
|
-
(item.status === "completed" ? completedPlanIds : pendingPlanIds).add(
|
|
20580
|
+
(item.status === "completed" ? completedPlanIds : pendingPlanIds).add(
|
|
20581
|
+
item.promotedFromPlan
|
|
20582
|
+
);
|
|
18933
20583
|
}
|
|
18934
20584
|
if (item.promotedFromTask) {
|
|
18935
|
-
(item.status === "completed" ? completedTaskIds : pendingTaskIds).add(
|
|
20585
|
+
(item.status === "completed" ? completedTaskIds : pendingTaskIds).add(
|
|
20586
|
+
item.promotedFromTask
|
|
20587
|
+
);
|
|
18936
20588
|
}
|
|
18937
20589
|
}
|
|
18938
20590
|
for (const planId of completedPlanIds) {
|
|
@@ -18940,7 +20592,7 @@ var todoTool = {
|
|
|
18940
20592
|
const planPath = ctx.meta["plan.path"];
|
|
18941
20593
|
if (typeof planPath !== "string" || !planPath) continue;
|
|
18942
20594
|
try {
|
|
18943
|
-
const plan = await
|
|
20595
|
+
const plan = await loadPlan2(planPath);
|
|
18944
20596
|
if (plan) {
|
|
18945
20597
|
const updated = setPlanItemStatus2(plan, planId, "done");
|
|
18946
20598
|
await savePlan(planPath, updated);
|
|
@@ -18953,7 +20605,7 @@ var todoTool = {
|
|
|
18953
20605
|
const taskPath = ctx.meta["task.path"];
|
|
18954
20606
|
if (typeof taskPath !== "string" || !taskPath) continue;
|
|
18955
20607
|
try {
|
|
18956
|
-
const file = await
|
|
20608
|
+
const file = await loadTasks3(taskPath);
|
|
18957
20609
|
if (file) {
|
|
18958
20610
|
const task = file.tasks.find((t) => t.id === taskId);
|
|
18959
20611
|
if (task && task.status !== "completed") {
|
|
@@ -19268,8 +20920,8 @@ var DEFAULT_IGNORE5 = [
|
|
|
19268
20920
|
var treeTool = {
|
|
19269
20921
|
name: "tree",
|
|
19270
20922
|
category: "Filesystem",
|
|
19271
|
-
description: "Display a
|
|
19272
|
-
usageHint: "
|
|
20923
|
+
description: "Display a project or subpath directory tree. Use it for structural layout; use index-backed `codebase-search` first for code understanding when it is live.",
|
|
20924
|
+
usageHint: "DIRECTORY-LAYOUT EXPLORATION:\n\n- When `codebase-search` is live, use it first for symbols and concepts; use `tree` when the directory hierarchy itself matters.\n- Tune `depth` (default 3) and use `glob`/`exclude` to focus the view.\n- Prefer this over raw `bash find` or `glob` + manual reading when you need a quick structural overview.\nOutput is truncated for very large trees.",
|
|
19273
20925
|
permission: "auto",
|
|
19274
20926
|
mutating: false,
|
|
19275
20927
|
capabilities: ["fs.read"],
|
|
@@ -19672,15 +21324,15 @@ var OPTIONAL_TOOLS = [
|
|
|
19672
21324
|
toolUseTool,
|
|
19673
21325
|
batchToolUseTool,
|
|
19674
21326
|
toolHelpTool,
|
|
19675
|
-
codebaseIndexTool,
|
|
19676
|
-
codebaseSearchTool,
|
|
19677
|
-
codebaseStatsTool,
|
|
19678
21327
|
setWorkingDirTool
|
|
19679
21328
|
];
|
|
19680
21329
|
var TIER1_TOOLS = [
|
|
19681
21330
|
readTool,
|
|
19682
21331
|
writeTool,
|
|
19683
21332
|
editTool,
|
|
21333
|
+
codebaseStatsTool,
|
|
21334
|
+
codebaseSearchTool,
|
|
21335
|
+
codebaseIndexTool,
|
|
19684
21336
|
bashTool,
|
|
19685
21337
|
grepTool,
|
|
19686
21338
|
globTool,
|
|
@@ -19719,9 +21371,6 @@ var TIER3_TOOLS = [
|
|
|
19719
21371
|
toolUseTool,
|
|
19720
21372
|
batchToolUseTool,
|
|
19721
21373
|
toolHelpTool,
|
|
19722
|
-
codebaseIndexTool,
|
|
19723
|
-
codebaseSearchTool,
|
|
19724
|
-
codebaseStatsTool,
|
|
19725
21374
|
setWorkingDirTool
|
|
19726
21375
|
];
|
|
19727
21376
|
var builtinTools = [
|
|
@@ -19730,6 +21379,9 @@ var builtinTools = [
|
|
|
19730
21379
|
readTool,
|
|
19731
21380
|
writeTool,
|
|
19732
21381
|
editTool,
|
|
21382
|
+
codebaseStatsTool,
|
|
21383
|
+
codebaseSearchTool,
|
|
21384
|
+
codebaseIndexTool,
|
|
19733
21385
|
replaceTool,
|
|
19734
21386
|
globTool,
|
|
19735
21387
|
grepTool,
|
|
@@ -19764,9 +21416,6 @@ var builtinTools = [
|
|
|
19764
21416
|
toolUseTool,
|
|
19765
21417
|
batchToolUseTool,
|
|
19766
21418
|
toolHelpTool,
|
|
19767
|
-
codebaseIndexTool,
|
|
19768
|
-
codebaseSearchTool,
|
|
19769
|
-
codebaseStatsTool,
|
|
19770
21419
|
setWorkingDirTool
|
|
19771
21420
|
];
|
|
19772
21421
|
|
|
@@ -20922,7 +22571,13 @@ var TOOL_ICON_MAP = {
|
|
|
20922
22571
|
think: "brain",
|
|
20923
22572
|
reason: "brain",
|
|
20924
22573
|
analyze: "brain",
|
|
20925
|
-
reasoning: "brain"
|
|
22574
|
+
reasoning: "brain",
|
|
22575
|
+
// Language intelligence
|
|
22576
|
+
lsp_diagnostics: "code",
|
|
22577
|
+
lsp_definition: "search",
|
|
22578
|
+
lsp_completion: "code",
|
|
22579
|
+
lsp_rename: "edit",
|
|
22580
|
+
"codebase-lsp-search": "index"
|
|
20926
22581
|
};
|
|
20927
22582
|
function getToolIcon(toolName) {
|
|
20928
22583
|
return TOOL_ICON_MAP[toolName.toLowerCase()] ?? "fallback";
|
|
@@ -20991,13 +22646,17 @@ export {
|
|
|
20991
22646
|
LanguageProfileRegistry,
|
|
20992
22647
|
OPTIONAL_TOOLS,
|
|
20993
22648
|
PRIMARY_LANGUAGE_PROFILES,
|
|
22649
|
+
SESSION_KANBAN_COLUMNS,
|
|
20994
22650
|
TIER1_TOOLS,
|
|
20995
22651
|
TIER2_TOOLS,
|
|
20996
22652
|
TIER3_TOOLS,
|
|
20997
22653
|
TOOL_ICON_CONFIG,
|
|
20998
22654
|
TOOL_ICON_MAP,
|
|
20999
22655
|
_resetProcessRegistry,
|
|
22656
|
+
applySessionKanbanBoardToTodos,
|
|
22657
|
+
applySessionKanbanTaskToSource,
|
|
21000
22658
|
assertBrowserUrlAllowed,
|
|
22659
|
+
attachSessionKanbanMirror,
|
|
21001
22660
|
auditTool,
|
|
21002
22661
|
bashTool,
|
|
21003
22662
|
batchToolUseTool,
|
|
@@ -21040,11 +22699,13 @@ export {
|
|
|
21040
22699
|
e2ePlanTool,
|
|
21041
22700
|
editTool,
|
|
21042
22701
|
enqueueReindex,
|
|
22702
|
+
ensureSessionKanbanBoard,
|
|
21043
22703
|
ensureSessionShell,
|
|
21044
22704
|
execTool,
|
|
21045
22705
|
executeLanguagePlan,
|
|
21046
22706
|
executePackagePlan,
|
|
21047
22707
|
fetchTool,
|
|
22708
|
+
fileGraphService,
|
|
21048
22709
|
forgetTool,
|
|
21049
22710
|
formatGlobalStatus,
|
|
21050
22711
|
formatInstanceList,
|
|
@@ -21061,6 +22722,7 @@ export {
|
|
|
21061
22722
|
gitTool,
|
|
21062
22723
|
globTool,
|
|
21063
22724
|
grepTool,
|
|
22725
|
+
hydrateSessionKanban,
|
|
21064
22726
|
indexCircuitBreaker,
|
|
21065
22727
|
installTool,
|
|
21066
22728
|
isExecCommandAllowed,
|
|
@@ -21077,15 +22739,22 @@ export {
|
|
|
21077
22739
|
listInstances,
|
|
21078
22740
|
logsTool,
|
|
21079
22741
|
makeSkillTool,
|
|
22742
|
+
mirrorSessionPlanToKanban,
|
|
22743
|
+
mirrorSessionTasksToKanban,
|
|
22744
|
+
mirrorSessionTodosToKanban,
|
|
21080
22745
|
normalizeShell,
|
|
21081
22746
|
onIndexStateChange,
|
|
21082
22747
|
outdatedTool,
|
|
22748
|
+
packageGraphService,
|
|
21083
22749
|
parseLanguageDiagnostics,
|
|
21084
22750
|
parsePackageReports,
|
|
21085
22751
|
parsePrivateOriginAllowlist,
|
|
21086
22752
|
patchTool,
|
|
21087
22753
|
planLanguageOperation,
|
|
21088
22754
|
planTool,
|
|
22755
|
+
projectSessionPlanToKanban,
|
|
22756
|
+
projectSessionTasksToKanban,
|
|
22757
|
+
projectSessionTodosToKanban,
|
|
21089
22758
|
readTool,
|
|
21090
22759
|
redactBrowserText,
|
|
21091
22760
|
relatedMemoryTool,
|
|
@@ -21107,6 +22776,7 @@ export {
|
|
|
21107
22776
|
shutdownCodebaseIndexHost,
|
|
21108
22777
|
startProcessGuardian,
|
|
21109
22778
|
stopProcessGuardian,
|
|
22779
|
+
symbolGraphService,
|
|
21110
22780
|
testTool,
|
|
21111
22781
|
todoTool,
|
|
21112
22782
|
toolHelpTool,
|