@wrongstack/tools 0.287.0 → 0.291.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_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/auto-proceed-loop-guard.d.ts +128 -0
- package/dist/auto-proceed-loop-guard.d.ts.map +1 -0
- package/dist/auto-proceed-loop-guard.js +46 -0
- package/dist/auto-proceed-loop-guard.js.map +7 -0
- 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/builtin.d.ts +12 -10
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +1766 -864
- package/dist/builtin.js.map +3 -3
- package/dist/codebase-index/background-indexer.d.ts +1 -1
- 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/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 +888 -254
- package/dist/codebase-index/index.js.map +3 -3
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/refs-extractor.d.ts +2 -17
- package/dist/codebase-index/refs-extractor.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 +738 -212
- package/dist/codebase-index/worker.js.map +3 -3
- package/dist/codebase-index/writer.d.ts +60 -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/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 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2001 -873
- package/dist/index.js.map +3 -3
- package/dist/install.js +61 -14
- package/dist/install.js.map +3 -3
- package/dist/kanban.d.ts +21 -2
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +713 -572
- package/dist/kanban.js.map +3 -3
- 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/outdated.js +61 -14
- package/dist/outdated.js.map +3 -3
- package/dist/pack.js +1763 -861
- package/dist/pack.js.map +3 -3
- package/dist/plan.js +4 -0
- package/dist/plan.js.map +2 -2
- package/dist/process-registry.js +12 -5
- package/dist/process-registry.js.map +2 -2
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js.map +2 -2
- package/dist/search.js +2 -2
- package/dist/search.js.map +2 -2
- package/dist/session-kanban.d.ts +9 -0
- package/dist/session-kanban.d.ts.map +1 -1
- package/dist/session-kanban.js +197 -3
- package/dist/session-kanban.js.map +2 -2
- package/dist/task.js +4 -0
- package/dist/task.js.map +2 -2
- package/dist/test.js +61 -14
- package/dist/test.js.map +3 -3
- package/dist/todo.js +4 -0
- package/dist/todo.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 +13 -5
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, basename9, 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) {
|
|
@@ -7825,7 +7943,7 @@ import * as path13 from "node:path";
|
|
|
7825
7943
|
import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core";
|
|
7826
7944
|
|
|
7827
7945
|
// src/codebase-index/schema.ts
|
|
7828
|
-
var SCHEMA_VERSION =
|
|
7946
|
+
var SCHEMA_VERSION = 3;
|
|
7829
7947
|
|
|
7830
7948
|
// src/codebase-index/lsp-kind.ts
|
|
7831
7949
|
function lspKindToInternalKind(k) {
|
|
@@ -7868,7 +7986,7 @@ function tokenise(text) {
|
|
|
7868
7986
|
return sanitised.toLowerCase().split(" ").filter(Boolean);
|
|
7869
7987
|
}
|
|
7870
7988
|
function splitName(name) {
|
|
7871
|
-
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();
|
|
7872
7990
|
}
|
|
7873
7991
|
function buildIndexableText(name, signature, docComment) {
|
|
7874
7992
|
return [splitName(name), name, signature, docComment].filter(Boolean).join(" ");
|
|
@@ -7894,14 +8012,12 @@ function buildBm25Index(docs) {
|
|
|
7894
8012
|
return new Bm25Index(documents, df, N, avgLen);
|
|
7895
8013
|
}
|
|
7896
8014
|
var Bm25Index = class {
|
|
7897
|
-
constructor(documents,
|
|
8015
|
+
constructor(documents, _df, N, avgLen) {
|
|
7898
8016
|
this.documents = documents;
|
|
7899
|
-
this.df = df;
|
|
7900
8017
|
this.N = N;
|
|
7901
8018
|
this.safeAvgLen = avgLen === 0 ? 1 : avgLen;
|
|
7902
8019
|
}
|
|
7903
8020
|
documents;
|
|
7904
|
-
df;
|
|
7905
8021
|
N;
|
|
7906
8022
|
safeAvgLen;
|
|
7907
8023
|
score(query, filter) {
|
|
@@ -7914,10 +8030,13 @@ var Bm25Index = class {
|
|
|
7914
8030
|
for (const qTerm of qTokens) {
|
|
7915
8031
|
let tf = 0;
|
|
7916
8032
|
for (const t of doc.tokens) {
|
|
7917
|
-
if (t
|
|
8033
|
+
if (t.startsWith(qTerm)) tf++;
|
|
7918
8034
|
}
|
|
7919
8035
|
if (tf === 0) continue;
|
|
7920
|
-
|
|
8036
|
+
let dfVal = 0;
|
|
8037
|
+
for (const candidate of this.documents) {
|
|
8038
|
+
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
8039
|
+
}
|
|
7921
8040
|
if (dfVal === 0) continue;
|
|
7922
8041
|
const idf = Math.log((this.N - dfVal + 0.5) / (dfVal + 0.5) + 1);
|
|
7923
8042
|
const lenRatio = B * (doc.len / this.safeAvgLen);
|
|
@@ -7950,6 +8069,29 @@ var Bm25Index = class {
|
|
|
7950
8069
|
|
|
7951
8070
|
// src/codebase-index/writer.ts
|
|
7952
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
|
+
}
|
|
7953
8095
|
function resolveIndexDir(projectRoot, override) {
|
|
7954
8096
|
return override ?? resolveWstackPaths2({ projectRoot }).projectCodebaseIndex;
|
|
7955
8097
|
}
|
|
@@ -7957,6 +8099,53 @@ function codebaseIndexDirOverride(ctx) {
|
|
|
7957
8099
|
const v = ctx.meta?.["codebaseIndexDir"];
|
|
7958
8100
|
return typeof v === "string" ? v : void 0;
|
|
7959
8101
|
}
|
|
8102
|
+
var StorePool = class {
|
|
8103
|
+
stores = /* @__PURE__ */ new Map();
|
|
8104
|
+
key(projectRoot, indexDir) {
|
|
8105
|
+
return `${projectRoot}\0${indexDir ?? ""}`;
|
|
8106
|
+
}
|
|
8107
|
+
/** Borrow a store. Creates it on first access for this key. */
|
|
8108
|
+
acquire(projectRoot, opts) {
|
|
8109
|
+
const k = this.key(projectRoot, opts?.indexDir);
|
|
8110
|
+
let store = this.stores.get(k);
|
|
8111
|
+
if (!store) {
|
|
8112
|
+
store = new IndexStore(projectRoot, { indexDir: opts?.indexDir });
|
|
8113
|
+
this.stores.set(k, store);
|
|
8114
|
+
}
|
|
8115
|
+
return store;
|
|
8116
|
+
}
|
|
8117
|
+
/** Return the store to the pool. The connection stays warm for subsequent
|
|
8118
|
+
* operations on the same (projectRoot, indexDir). */
|
|
8119
|
+
release(_store) {
|
|
8120
|
+
}
|
|
8121
|
+
/** Close every pooled connection and drain the pool. Call on shutdown. */
|
|
8122
|
+
closeAll() {
|
|
8123
|
+
for (const store of this.stores.values()) {
|
|
8124
|
+
try {
|
|
8125
|
+
store.close();
|
|
8126
|
+
} catch {
|
|
8127
|
+
}
|
|
8128
|
+
}
|
|
8129
|
+
this.stores.clear();
|
|
8130
|
+
}
|
|
8131
|
+
/** Remove one store from the pool. Used by tests that need isolation. */
|
|
8132
|
+
evict(projectRoot, indexDir) {
|
|
8133
|
+
const k = this.key(projectRoot, indexDir);
|
|
8134
|
+
const store = this.stores.get(k);
|
|
8135
|
+
if (store) {
|
|
8136
|
+
try {
|
|
8137
|
+
store.close();
|
|
8138
|
+
} catch {
|
|
8139
|
+
}
|
|
8140
|
+
this.stores.delete(k);
|
|
8141
|
+
}
|
|
8142
|
+
}
|
|
8143
|
+
/** True when the pool holds a connection for the given key. */
|
|
8144
|
+
has(projectRoot, indexDir) {
|
|
8145
|
+
return this.stores.has(this.key(projectRoot, indexDir));
|
|
8146
|
+
}
|
|
8147
|
+
};
|
|
8148
|
+
var indexStorePool = new StorePool();
|
|
7960
8149
|
var warningSilenced = false;
|
|
7961
8150
|
function silenceSqliteExperimentalWarning() {
|
|
7962
8151
|
if (warningSilenced) return;
|
|
@@ -8003,7 +8192,7 @@ function sleepSync(ms) {
|
|
|
8003
8192
|
} catch {
|
|
8004
8193
|
}
|
|
8005
8194
|
}
|
|
8006
|
-
var IndexStore = class {
|
|
8195
|
+
var IndexStore = class _IndexStore {
|
|
8007
8196
|
db;
|
|
8008
8197
|
/** Absolute path to this project's index directory. */
|
|
8009
8198
|
indexDir;
|
|
@@ -8055,10 +8244,7 @@ var IndexStore = class {
|
|
|
8055
8244
|
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
8056
8245
|
throw new LockError(`SQLite lock conflict after ${MAX_LOCK_RETRIES} retries: ${msg}`);
|
|
8057
8246
|
}
|
|
8058
|
-
const delay = Math.min(
|
|
8059
|
-
LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt,
|
|
8060
|
-
LOCK_RETRY_MAX_DELAY_MS
|
|
8061
|
-
);
|
|
8247
|
+
const delay = Math.min(LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt, LOCK_RETRY_MAX_DELAY_MS);
|
|
8062
8248
|
sleepSync(delay);
|
|
8063
8249
|
}
|
|
8064
8250
|
}
|
|
@@ -8140,8 +8326,24 @@ var IndexStore = class {
|
|
|
8140
8326
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)");
|
|
8141
8327
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)");
|
|
8142
8328
|
try {
|
|
8143
|
-
this.db.exec(
|
|
8329
|
+
this.db.exec(
|
|
8330
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')"
|
|
8331
|
+
);
|
|
8144
8332
|
this.ftsAvailable = true;
|
|
8333
|
+
const symbolCount = Number(
|
|
8334
|
+
this.db.prepare("SELECT COUNT(*) AS n FROM symbols").get()?.n ?? 0
|
|
8335
|
+
);
|
|
8336
|
+
const ftsCount = Number(
|
|
8337
|
+
this.db.prepare("SELECT COUNT(*) AS n FROM symbols_fts").get()?.n ?? 0
|
|
8338
|
+
);
|
|
8339
|
+
if (symbolCount !== ftsCount) {
|
|
8340
|
+
this.db.exec("DELETE FROM symbols_fts");
|
|
8341
|
+
const insert = this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)");
|
|
8342
|
+
const rows = this.db.prepare("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
|
|
8343
|
+
for (const row of rows) {
|
|
8344
|
+
insert.run(row.id, buildIndexableText(row.name, row.signature, row.doc_comment));
|
|
8345
|
+
}
|
|
8346
|
+
}
|
|
8145
8347
|
} catch {
|
|
8146
8348
|
this.ftsAvailable = false;
|
|
8147
8349
|
}
|
|
@@ -8161,7 +8363,7 @@ var IndexStore = class {
|
|
|
8161
8363
|
return this.runWithRetry(() => {
|
|
8162
8364
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8163
8365
|
try {
|
|
8164
|
-
const maxRows = this.
|
|
8366
|
+
const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
8165
8367
|
let nextId = (maxRows[0]?.m ?? 0) + 1;
|
|
8166
8368
|
const stmt = this.db.prepare(
|
|
8167
8369
|
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
@@ -8199,7 +8401,9 @@ var IndexStore = class {
|
|
|
8199
8401
|
deleteSymbolsForFile(file) {
|
|
8200
8402
|
this.runWithRetry(() => {
|
|
8201
8403
|
if (this.ftsAvailable) {
|
|
8202
|
-
this.stmt(
|
|
8404
|
+
this.stmt(
|
|
8405
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8406
|
+
).run(file);
|
|
8203
8407
|
}
|
|
8204
8408
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
8205
8409
|
});
|
|
@@ -8211,9 +8415,21 @@ var IndexStore = class {
|
|
|
8211
8415
|
*/
|
|
8212
8416
|
deleteFile(file) {
|
|
8213
8417
|
this.runWithRetry(() => {
|
|
8214
|
-
this.
|
|
8215
|
-
|
|
8216
|
-
|
|
8418
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
8419
|
+
try {
|
|
8420
|
+
if (this.ftsAvailable) {
|
|
8421
|
+
this.db.prepare(
|
|
8422
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8423
|
+
).run(file);
|
|
8424
|
+
}
|
|
8425
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)").run(file);
|
|
8426
|
+
this.db.prepare("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
8427
|
+
this.db.prepare("DELETE FROM files WHERE file = ?").run(file);
|
|
8428
|
+
this.db.exec("COMMIT");
|
|
8429
|
+
} catch (err) {
|
|
8430
|
+
this.db.exec("ROLLBACK");
|
|
8431
|
+
throw err;
|
|
8432
|
+
}
|
|
8217
8433
|
});
|
|
8218
8434
|
}
|
|
8219
8435
|
// ─── File metadata ──────────────────────────────────────────────────────────
|
|
@@ -8236,14 +8452,22 @@ var IndexStore = class {
|
|
|
8236
8452
|
).all(file);
|
|
8237
8453
|
if (!rows.length) return null;
|
|
8238
8454
|
const r = expectDefined2(rows[0]);
|
|
8239
|
-
return {
|
|
8455
|
+
return {
|
|
8456
|
+
file: r.file,
|
|
8457
|
+
lang: r.lang,
|
|
8458
|
+
mtimeMs: r.mtime_ms,
|
|
8459
|
+
symbolCount: r.symbol_count,
|
|
8460
|
+
lastIndexed: r.last_indexed
|
|
8461
|
+
};
|
|
8240
8462
|
}
|
|
8241
8463
|
getAllFileMetas() {
|
|
8242
|
-
return this.db.prepare(
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8464
|
+
return this.db.prepare("SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files").all().map((r) => ({
|
|
8465
|
+
file: r.file,
|
|
8466
|
+
lang: r.lang,
|
|
8467
|
+
mtimeMs: r.mtime_ms,
|
|
8468
|
+
symbolCount: r.symbol_count,
|
|
8469
|
+
lastIndexed: r.last_indexed
|
|
8470
|
+
}));
|
|
8247
8471
|
}
|
|
8248
8472
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
8249
8473
|
search(query, filter) {
|
|
@@ -8267,8 +8491,8 @@ var IndexStore = class {
|
|
|
8267
8491
|
values.push(filter.lang);
|
|
8268
8492
|
}
|
|
8269
8493
|
if (filter?.file) {
|
|
8270
|
-
conditions.push("file LIKE ?");
|
|
8271
|
-
values.push(`%${filter.file}%`);
|
|
8494
|
+
conditions.push("replace(file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
8495
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
8272
8496
|
}
|
|
8273
8497
|
if (query.trim()) {
|
|
8274
8498
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
@@ -8306,9 +8530,11 @@ var IndexStore = class {
|
|
|
8306
8530
|
* "users", camelCase-split text makes "complex" find "complexOperation").
|
|
8307
8531
|
*/
|
|
8308
8532
|
searchRanked(query, filter, limit) {
|
|
8533
|
+
const rawLimit = Number.isFinite(limit) ? Math.trunc(limit) : 20;
|
|
8534
|
+
const safeLimit = Math.max(1, Math.min(rawLimit, 100));
|
|
8309
8535
|
const tokens = tokenise(query);
|
|
8310
8536
|
if (tokens.length === 0 || !this.ftsAvailable) {
|
|
8311
|
-
return this.searchRankedFallback(query, filter,
|
|
8537
|
+
return this.searchRankedFallback(query, filter, safeLimit);
|
|
8312
8538
|
}
|
|
8313
8539
|
let effectiveKind = filter?.kind;
|
|
8314
8540
|
if (filter?.lspKind !== void 0) {
|
|
@@ -8328,11 +8554,13 @@ var IndexStore = class {
|
|
|
8328
8554
|
values.push(filter.lang);
|
|
8329
8555
|
}
|
|
8330
8556
|
if (filter?.file) {
|
|
8331
|
-
conditions.push("s.file LIKE ?");
|
|
8332
|
-
values.push(`%${filter.file}%`);
|
|
8557
|
+
conditions.push("replace(s.file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
8558
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
8333
8559
|
}
|
|
8334
8560
|
const where = conditions.join(" AND ");
|
|
8335
|
-
const countRows = this.db.prepare(
|
|
8561
|
+
const countRows = this.db.prepare(
|
|
8562
|
+
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
8563
|
+
).all(...values);
|
|
8336
8564
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
8337
8565
|
if (total === 0) return { results: [], total: 0 };
|
|
8338
8566
|
const rows = this.db.prepare(
|
|
@@ -8341,9 +8569,13 @@ var IndexStore = class {
|
|
|
8341
8569
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
8342
8570
|
FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
8343
8571
|
WHERE ${where}
|
|
8344
|
-
ORDER BY
|
|
8572
|
+
ORDER BY
|
|
8573
|
+
CASE WHEN lower(s.name) = lower(?) THEN 0
|
|
8574
|
+
WHEN lower(s.name) LIKE lower(?) ESCAPE '\\' THEN 1
|
|
8575
|
+
ELSE 2 END,
|
|
8576
|
+
bm25(symbols_fts), lower(s.name), s.file, s.line, s.col, s.id
|
|
8345
8577
|
LIMIT ?`
|
|
8346
|
-
).all(...values,
|
|
8578
|
+
).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
8347
8579
|
return {
|
|
8348
8580
|
results: rows.map((r) => ({
|
|
8349
8581
|
id: r.id,
|
|
@@ -8373,10 +8605,28 @@ var IndexStore = class {
|
|
|
8373
8605
|
}
|
|
8374
8606
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
8375
8607
|
const bm25 = buildBm25Index(
|
|
8376
|
-
candidates.map((c) => ({
|
|
8608
|
+
candidates.map((c) => ({
|
|
8609
|
+
id: c.id,
|
|
8610
|
+
text: buildIndexableText(c.name, c.signature, c.docComment)
|
|
8611
|
+
}))
|
|
8377
8612
|
);
|
|
8378
8613
|
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
8379
|
-
|
|
8614
|
+
const q = query.trim().toLowerCase();
|
|
8615
|
+
const rank = (id) => {
|
|
8616
|
+
const name = candidateById.get(id)?.name.toLowerCase() ?? "";
|
|
8617
|
+
if (name === q) return 0;
|
|
8618
|
+
if (name.startsWith(q)) return 1;
|
|
8619
|
+
return 2;
|
|
8620
|
+
};
|
|
8621
|
+
scored.sort((a, b) => {
|
|
8622
|
+
const rankDiff = rank(a.id) - rank(b.id);
|
|
8623
|
+
if (rankDiff !== 0) return rankDiff;
|
|
8624
|
+
const scoreDiff = b.score - a.score;
|
|
8625
|
+
if (scoreDiff !== 0) return scoreDiff;
|
|
8626
|
+
const left = expectDefined2(candidateById.get(a.id));
|
|
8627
|
+
const right = expectDefined2(candidateById.get(b.id));
|
|
8628
|
+
return left.name.localeCompare(right.name) || left.file.localeCompare(right.file) || left.line - right.line || left.col - right.col || left.id - right.id;
|
|
8629
|
+
});
|
|
8380
8630
|
const qTokens = tokenise(query);
|
|
8381
8631
|
const results = scored.slice(0, limit).map(({ id, score }) => {
|
|
8382
8632
|
const c = expectDefined2(candidateById.get(id));
|
|
@@ -8385,9 +8635,7 @@ var IndexStore = class {
|
|
|
8385
8635
|
return { results, total: candidates.length };
|
|
8386
8636
|
}
|
|
8387
8637
|
getAllIndexable() {
|
|
8388
|
-
return this.
|
|
8389
|
-
({ id, text }) => ({ id, text })
|
|
8390
|
-
);
|
|
8638
|
+
return this.stmt("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
|
|
8391
8639
|
}
|
|
8392
8640
|
/**
|
|
8393
8641
|
* Largest symbol id currently in the table (0 when empty). New ids must be
|
|
@@ -8397,28 +8645,22 @@ var IndexStore = class {
|
|
|
8397
8645
|
* `symbols.id`). Ids may have gaps — that is fine.
|
|
8398
8646
|
*/
|
|
8399
8647
|
getMaxSymbolId() {
|
|
8400
|
-
const rows = this.
|
|
8648
|
+
const rows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
8401
8649
|
return rows[0]?.m ?? 0;
|
|
8402
8650
|
}
|
|
8403
8651
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
8404
8652
|
getStats() {
|
|
8405
8653
|
const sizeBytes = this.sizeBytes();
|
|
8406
|
-
const lastRows = this.db.prepare(
|
|
8407
|
-
"SELECT value FROM metadata WHERE key = 'last_indexed'"
|
|
8408
|
-
).all();
|
|
8654
|
+
const lastRows = this.db.prepare("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
8409
8655
|
const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
|
|
8410
|
-
const totalRows = this.
|
|
8656
|
+
const totalRows = this.stmt("SELECT COUNT(*) FROM symbols").all();
|
|
8411
8657
|
const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
|
|
8412
|
-
const fileRows = this.
|
|
8658
|
+
const fileRows = this.stmt("SELECT COUNT(*) FROM files").all();
|
|
8413
8659
|
const totalFiles = fileRows[0] ? Number(fileRows[0]["COUNT(*)"]) : 0;
|
|
8414
|
-
const langRows = this.
|
|
8415
|
-
"SELECT lang, COUNT(*) FROM symbols GROUP BY lang"
|
|
8416
|
-
).all();
|
|
8660
|
+
const langRows = this.stmt("SELECT lang, COUNT(*) FROM symbols GROUP BY lang").all();
|
|
8417
8661
|
const byLang = {};
|
|
8418
8662
|
for (const row of langRows) byLang[row.lang] = Number(row["COUNT(*)"]);
|
|
8419
|
-
const kindRows = this.
|
|
8420
|
-
"SELECT kind, COUNT(*) FROM symbols GROUP BY kind"
|
|
8421
|
-
).all();
|
|
8663
|
+
const kindRows = this.stmt("SELECT kind, COUNT(*) FROM symbols GROUP BY kind").all();
|
|
8422
8664
|
const byKind = {};
|
|
8423
8665
|
for (const row of kindRows) byKind[row.kind] = Number(row["COUNT(*)"]);
|
|
8424
8666
|
return {
|
|
@@ -8434,17 +8676,34 @@ var IndexStore = class {
|
|
|
8434
8676
|
}
|
|
8435
8677
|
setLastIndexed(ts2) {
|
|
8436
8678
|
this.runWithRetry(() => {
|
|
8437
|
-
this.db.prepare(
|
|
8438
|
-
|
|
8439
|
-
|
|
8679
|
+
this.db.prepare("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
|
|
8680
|
+
});
|
|
8681
|
+
}
|
|
8682
|
+
getMetadata(key) {
|
|
8683
|
+
const rows = this.db.prepare("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
8684
|
+
return rows[0]?.value;
|
|
8685
|
+
}
|
|
8686
|
+
setMetadata(key, value) {
|
|
8687
|
+
this.runWithRetry(() => {
|
|
8688
|
+
this.db.prepare("INSERT OR REPLACE INTO metadata(key, value) VALUES(?, ?)").run(key, value);
|
|
8440
8689
|
});
|
|
8441
8690
|
}
|
|
8442
8691
|
clearAll() {
|
|
8443
8692
|
this.runWithRetry(() => {
|
|
8444
|
-
this.db.exec("
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8693
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
8694
|
+
try {
|
|
8695
|
+
this.db.exec("DROP TABLE IF EXISTS refs");
|
|
8696
|
+
this.db.exec("DROP TABLE IF EXISTS symbols");
|
|
8697
|
+
this.db.exec("DROP TABLE IF EXISTS files");
|
|
8698
|
+
this.db.exec("DROP TABLE IF EXISTS metadata");
|
|
8699
|
+
if (this.ftsAvailable) this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
8700
|
+
this.db.exec("COMMIT");
|
|
8701
|
+
this.stmtCache.clear();
|
|
8702
|
+
this.initSchema();
|
|
8703
|
+
} catch (err) {
|
|
8704
|
+
this.db.exec("ROLLBACK");
|
|
8705
|
+
throw err;
|
|
8706
|
+
}
|
|
8448
8707
|
});
|
|
8449
8708
|
}
|
|
8450
8709
|
// ─── Ref CRUD ────────────────────────────────────────────────────────────────
|
|
@@ -8525,7 +8784,7 @@ var IndexStore = class {
|
|
|
8525
8784
|
).run(...options.deleteForFiles);
|
|
8526
8785
|
this.db.prepare(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
8527
8786
|
}
|
|
8528
|
-
const maxRows = this.
|
|
8787
|
+
const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
8529
8788
|
let nextId = (maxRows[0]?.m ?? 0) + 1;
|
|
8530
8789
|
const symStmt = this.db.prepare(
|
|
8531
8790
|
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
@@ -8535,6 +8794,7 @@ var IndexStore = class {
|
|
|
8535
8794
|
const allInserted = [];
|
|
8536
8795
|
const refsToInsert = [];
|
|
8537
8796
|
for (const entry of entries) {
|
|
8797
|
+
const insertedForEntry = [];
|
|
8538
8798
|
for (const s of entry.symbols) {
|
|
8539
8799
|
const id = nextId++;
|
|
8540
8800
|
symStmt.run(
|
|
@@ -8552,9 +8812,11 @@ var IndexStore = class {
|
|
|
8552
8812
|
s.file
|
|
8553
8813
|
);
|
|
8554
8814
|
ftsStmt?.run(id, buildIndexableText(s.name, s.signature, s.docComment));
|
|
8555
|
-
|
|
8815
|
+
const inserted = { ...s, id };
|
|
8816
|
+
allInserted.push(inserted);
|
|
8817
|
+
insertedForEntry.push(inserted);
|
|
8556
8818
|
}
|
|
8557
|
-
|
|
8819
|
+
refsToInsert.push(...assignRefsToSymbols(entry.refs, insertedForEntry));
|
|
8558
8820
|
}
|
|
8559
8821
|
if (refsToInsert.length > 0) {
|
|
8560
8822
|
const refStmt = this.db.prepare(
|
|
@@ -8592,9 +8854,7 @@ var IndexStore = class {
|
|
|
8592
8854
|
*/
|
|
8593
8855
|
deleteRefsForFile(file) {
|
|
8594
8856
|
this.runWithRetry(() => {
|
|
8595
|
-
this.db.prepare(
|
|
8596
|
-
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
8597
|
-
).run(file);
|
|
8857
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
|
|
8598
8858
|
});
|
|
8599
8859
|
}
|
|
8600
8860
|
/**
|
|
@@ -8635,9 +8895,7 @@ var IndexStore = class {
|
|
|
8635
8895
|
* Find all references FROM a given symbol (what does this symbol call/use?).
|
|
8636
8896
|
*/
|
|
8637
8897
|
findRefsFrom(symbolId) {
|
|
8638
|
-
return this.db.prepare(
|
|
8639
|
-
"SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?"
|
|
8640
|
-
).all(symbolId).map((r) => ({
|
|
8898
|
+
return this.db.prepare("SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?").all(symbolId).map((r) => ({
|
|
8641
8899
|
id: r.id,
|
|
8642
8900
|
fromId: r.from_id,
|
|
8643
8901
|
toName: r.to_name,
|
|
@@ -8654,6 +8912,356 @@ var IndexStore = class {
|
|
|
8654
8912
|
return 0;
|
|
8655
8913
|
}
|
|
8656
8914
|
}
|
|
8915
|
+
// ─── CodeMap graph aggregation ──────────────────────────────────────────────
|
|
8916
|
+
/**
|
|
8917
|
+
* Derive a monorepo package name from an absolute file path.
|
|
8918
|
+
* Handles both `packages/<name>/...` (workspace) and `src/<name>/...` layouts.
|
|
8919
|
+
* Returns `undefined` for files outside any recognisable package structure.
|
|
8920
|
+
*/
|
|
8921
|
+
static derivePackage(filePath) {
|
|
8922
|
+
const f = filePath.replace(/\\/g, "/");
|
|
8923
|
+
const pkgsIdx = f.indexOf("/packages/");
|
|
8924
|
+
if (pkgsIdx !== -1) {
|
|
8925
|
+
const rest = f.slice(pkgsIdx + "/packages/".length);
|
|
8926
|
+
const seg = rest.split("/")[0];
|
|
8927
|
+
return seg ? `@wrongstack/${seg}` : void 0;
|
|
8928
|
+
}
|
|
8929
|
+
const appsIdx = f.indexOf("/apps/");
|
|
8930
|
+
if (appsIdx !== -1) {
|
|
8931
|
+
const rest = f.slice(appsIdx + "/apps/".length);
|
|
8932
|
+
const seg = rest.split("/")[0];
|
|
8933
|
+
return seg ? `app:${seg}` : void 0;
|
|
8934
|
+
}
|
|
8935
|
+
return void 0;
|
|
8936
|
+
}
|
|
8937
|
+
static packageFromImport(moduleName) {
|
|
8938
|
+
if (!moduleName.startsWith("@wrongstack/")) return void 0;
|
|
8939
|
+
const parts = moduleName.split("/");
|
|
8940
|
+
return parts[1] ? `@wrongstack/${parts[1]}` : void 0;
|
|
8941
|
+
}
|
|
8942
|
+
static resolveRelativeImport(fromFile, moduleName, indexedFiles) {
|
|
8943
|
+
if (!moduleName.startsWith(".")) return void 0;
|
|
8944
|
+
const normalizedFrom = fromFile.replace(/\\/g, "/");
|
|
8945
|
+
const absolute = path13.posix.normalize(
|
|
8946
|
+
path13.posix.join(path13.posix.dirname(normalizedFrom), moduleName)
|
|
8947
|
+
);
|
|
8948
|
+
const extension = path13.posix.extname(absolute);
|
|
8949
|
+
const base = extension ? absolute.slice(0, -extension.length) : absolute;
|
|
8950
|
+
const candidates = [
|
|
8951
|
+
absolute,
|
|
8952
|
+
...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
|
|
8953
|
+
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path13.posix.join(absolute, `index${ext}`)),
|
|
8954
|
+
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path13.posix.join(base, `index${ext}`))
|
|
8955
|
+
];
|
|
8956
|
+
const indexedByPortablePath = new Map(
|
|
8957
|
+
[...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
|
|
8958
|
+
);
|
|
8959
|
+
for (const candidate of candidates) {
|
|
8960
|
+
const indexed = indexedByPortablePath.get(candidate.toLocaleLowerCase());
|
|
8961
|
+
if (indexed) return indexed;
|
|
8962
|
+
}
|
|
8963
|
+
return void 0;
|
|
8964
|
+
}
|
|
8965
|
+
/**
|
|
8966
|
+
* Package-level graph: each workspace package is a node; edges are derived
|
|
8967
|
+
* from cross-package symbol references (a symbol in package A references a
|
|
8968
|
+
* symbol resolved in package B). Node metadata includes symbol/file counts.
|
|
8969
|
+
*/
|
|
8970
|
+
getPackageGraph() {
|
|
8971
|
+
const symbols = this.db.prepare("SELECT file, id FROM symbols ORDER BY id").all();
|
|
8972
|
+
const pkgNodes = /* @__PURE__ */ new Map();
|
|
8973
|
+
const fileToPkg = /* @__PURE__ */ new Map();
|
|
8974
|
+
const symbolToPkg = /* @__PURE__ */ new Map();
|
|
8975
|
+
for (const s of symbols) {
|
|
8976
|
+
const pkg = _IndexStore.derivePackage(s.file) ?? "(root)";
|
|
8977
|
+
fileToPkg.set(s.file, pkg);
|
|
8978
|
+
symbolToPkg.set(s.id, pkg);
|
|
8979
|
+
const node = pkgNodes.get(pkg);
|
|
8980
|
+
if (node) {
|
|
8981
|
+
node.symbolCount = (node.symbolCount ?? 0) + 1;
|
|
8982
|
+
} else {
|
|
8983
|
+
pkgNodes.set(pkg, {
|
|
8984
|
+
id: `pkg:${pkg}`,
|
|
8985
|
+
label: pkg,
|
|
8986
|
+
kind: "package",
|
|
8987
|
+
package: pkg,
|
|
8988
|
+
symbolCount: 1,
|
|
8989
|
+
fileCount: 0
|
|
8990
|
+
});
|
|
8991
|
+
}
|
|
8992
|
+
}
|
|
8993
|
+
const files = this.db.prepare("SELECT DISTINCT file FROM files").all();
|
|
8994
|
+
for (const { file } of files) {
|
|
8995
|
+
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
8996
|
+
const node = pkgNodes.get(pkg);
|
|
8997
|
+
if (node) node.fileCount = (node.fileCount ?? 0) + 1;
|
|
8998
|
+
}
|
|
8999
|
+
const refRows = this.db.prepare(
|
|
9000
|
+
`SELECT r.from_id, r.to_id, r.call_type
|
|
9001
|
+
FROM refs r
|
|
9002
|
+
WHERE r.to_id IS NOT NULL`
|
|
9003
|
+
).all();
|
|
9004
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
9005
|
+
for (const r of refRows) {
|
|
9006
|
+
if (r.call_type === "import") continue;
|
|
9007
|
+
const fromPkg = symbolToPkg.get(r.from_id);
|
|
9008
|
+
const toPkg = symbolToPkg.get(r.to_id);
|
|
9009
|
+
if (!fromPkg || !toPkg || fromPkg === toPkg) continue;
|
|
9010
|
+
const key = `${fromPkg}\0${toPkg}`;
|
|
9011
|
+
let e = edgeMap.get(key);
|
|
9012
|
+
if (!e) {
|
|
9013
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9014
|
+
edgeMap.set(key, e);
|
|
9015
|
+
}
|
|
9016
|
+
e.weight++;
|
|
9017
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
9018
|
+
}
|
|
9019
|
+
const importRows = this.db.prepare(
|
|
9020
|
+
`SELECT r.from_id, r.to_name
|
|
9021
|
+
FROM refs r
|
|
9022
|
+
WHERE r.call_type = 'import'`
|
|
9023
|
+
).all();
|
|
9024
|
+
for (const r of importRows) {
|
|
9025
|
+
const fromPkg = symbolToPkg.get(r.from_id);
|
|
9026
|
+
const toPkg = _IndexStore.packageFromImport(r.to_name);
|
|
9027
|
+
if (!fromPkg || !toPkg || fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
9028
|
+
const key = `${fromPkg}\0${toPkg}`;
|
|
9029
|
+
let edge = edgeMap.get(key);
|
|
9030
|
+
if (!edge) {
|
|
9031
|
+
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9032
|
+
edgeMap.set(key, edge);
|
|
9033
|
+
}
|
|
9034
|
+
edge.weight++;
|
|
9035
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
|
|
9036
|
+
}
|
|
9037
|
+
const edges = [];
|
|
9038
|
+
for (const [key, e] of edgeMap) {
|
|
9039
|
+
const [source, target] = key.split("\0");
|
|
9040
|
+
let bestType = "call";
|
|
9041
|
+
let bestCount = 0;
|
|
9042
|
+
for (const [t, c] of e.types) {
|
|
9043
|
+
if (c > bestCount) {
|
|
9044
|
+
bestType = t;
|
|
9045
|
+
bestCount = c;
|
|
9046
|
+
}
|
|
9047
|
+
}
|
|
9048
|
+
edges.push({
|
|
9049
|
+
source: `pkg:${source}`,
|
|
9050
|
+
target: `pkg:${target}`,
|
|
9051
|
+
weight: e.weight,
|
|
9052
|
+
refType: bestType
|
|
9053
|
+
});
|
|
9054
|
+
}
|
|
9055
|
+
return { nodes: [...pkgNodes.values()], edges };
|
|
9056
|
+
}
|
|
9057
|
+
/**
|
|
9058
|
+
* File-level graph for a single package: each file is a node; edges are
|
|
9059
|
+
* derived from cross-file symbol references within the package.
|
|
9060
|
+
*/
|
|
9061
|
+
getFileGraph(packageFilter) {
|
|
9062
|
+
const allFiles = this.db.prepare("SELECT DISTINCT file FROM symbols").all();
|
|
9063
|
+
const pkgFilePaths = allFiles.filter((f) => (_IndexStore.derivePackage(f.file) ?? "(root)") === packageFilter).map((f) => f.file);
|
|
9064
|
+
const localFiles = new Set(pkgFilePaths);
|
|
9065
|
+
if (localFiles.size === 0) return { nodes: [], edges: [] };
|
|
9066
|
+
const filePlaceholders = [...localFiles].map(() => "?").join(",");
|
|
9067
|
+
const pkgSyms = this.db.prepare(
|
|
9068
|
+
`SELECT file, id, name, kind, lang, line FROM symbols WHERE file IN (${filePlaceholders}) ORDER BY id`
|
|
9069
|
+
).all(...pkgFilePaths);
|
|
9070
|
+
const fileNodes = /* @__PURE__ */ new Map();
|
|
9071
|
+
const symToFile = /* @__PURE__ */ new Map();
|
|
9072
|
+
const fileStats = /* @__PURE__ */ new Map();
|
|
9073
|
+
for (const s of pkgSyms) {
|
|
9074
|
+
symToFile.set(s.id, s.file);
|
|
9075
|
+
const current = fileStats.get(s.file);
|
|
9076
|
+
fileStats.set(s.file, {
|
|
9077
|
+
count: (current?.count ?? 0) + 1,
|
|
9078
|
+
lang: current?.lang ?? s.lang
|
|
9079
|
+
});
|
|
9080
|
+
}
|
|
9081
|
+
const ensureFileNode = (file) => {
|
|
9082
|
+
if (fileNodes.has(file)) return;
|
|
9083
|
+
const stats = fileStats.get(file);
|
|
9084
|
+
fileNodes.set(file, {
|
|
9085
|
+
id: `file:${file}`,
|
|
9086
|
+
label: file.replace(/\\/g, "/").split("/").pop() ?? file,
|
|
9087
|
+
kind: "file",
|
|
9088
|
+
package: _IndexStore.derivePackage(file) ?? "(root)",
|
|
9089
|
+
file,
|
|
9090
|
+
symbolCount: stats?.count ?? 0,
|
|
9091
|
+
lang: stats?.lang,
|
|
9092
|
+
external: !localFiles.has(file)
|
|
9093
|
+
});
|
|
9094
|
+
};
|
|
9095
|
+
for (const file of localFiles) {
|
|
9096
|
+
ensureFileNode(file);
|
|
9097
|
+
}
|
|
9098
|
+
const indexedFiles = new Set(allFiles.map((f) => f.file));
|
|
9099
|
+
const refRows = this.db.prepare(
|
|
9100
|
+
`SELECT r.from_id, r.to_id, r.call_type
|
|
9101
|
+
FROM refs r
|
|
9102
|
+
WHERE (r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
9103
|
+
OR r.to_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders})))
|
|
9104
|
+
AND r.to_id IS NOT NULL`
|
|
9105
|
+
).all(...pkgFilePaths, ...pkgFilePaths);
|
|
9106
|
+
const knownSymIds = new Set(pkgSyms.map((s) => s.id));
|
|
9107
|
+
const crossRefIds = /* @__PURE__ */ new Set();
|
|
9108
|
+
for (const r of refRows) {
|
|
9109
|
+
if (!knownSymIds.has(r.from_id)) crossRefIds.add(r.from_id);
|
|
9110
|
+
if (!knownSymIds.has(r.to_id)) crossRefIds.add(r.to_id);
|
|
9111
|
+
}
|
|
9112
|
+
if (crossRefIds.size > 0) {
|
|
9113
|
+
const crossPlaceholders = [...crossRefIds].map(() => "?").join(",");
|
|
9114
|
+
const extras = this.db.prepare(
|
|
9115
|
+
`SELECT id, file FROM symbols WHERE id IN (${crossPlaceholders})`
|
|
9116
|
+
).all(...crossRefIds);
|
|
9117
|
+
for (const x of extras) {
|
|
9118
|
+
symToFile.set(x.id, x.file);
|
|
9119
|
+
if (!fileStats.has(x.file)) {
|
|
9120
|
+
fileStats.set(x.file, { count: 0, lang: "ts" });
|
|
9121
|
+
}
|
|
9122
|
+
}
|
|
9123
|
+
}
|
|
9124
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
9125
|
+
for (const r of refRows) {
|
|
9126
|
+
if (r.call_type === "import") continue;
|
|
9127
|
+
const fromFile = symToFile.get(r.from_id);
|
|
9128
|
+
const toFile = symToFile.get(r.to_id);
|
|
9129
|
+
if (!fromFile || !toFile || fromFile === toFile) continue;
|
|
9130
|
+
if (!localFiles.has(fromFile) && !localFiles.has(toFile)) continue;
|
|
9131
|
+
ensureFileNode(fromFile);
|
|
9132
|
+
ensureFileNode(toFile);
|
|
9133
|
+
const key = `${fromFile}\0${toFile}`;
|
|
9134
|
+
let e = edgeMap.get(key);
|
|
9135
|
+
if (!e) {
|
|
9136
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9137
|
+
edgeMap.set(key, e);
|
|
9138
|
+
}
|
|
9139
|
+
e.weight++;
|
|
9140
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
9141
|
+
}
|
|
9142
|
+
const importRows = this.db.prepare(
|
|
9143
|
+
`SELECT r.from_id, r.to_name
|
|
9144
|
+
FROM refs r
|
|
9145
|
+
WHERE r.call_type = 'import'
|
|
9146
|
+
AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))`
|
|
9147
|
+
).all(...pkgFilePaths);
|
|
9148
|
+
for (const r of importRows) {
|
|
9149
|
+
const fromFile = symToFile.get(r.from_id);
|
|
9150
|
+
if (!fromFile || !localFiles.has(fromFile)) continue;
|
|
9151
|
+
const toFile = _IndexStore.resolveRelativeImport(fromFile, r.to_name, indexedFiles);
|
|
9152
|
+
if (!toFile || fromFile === toFile) continue;
|
|
9153
|
+
ensureFileNode(fromFile);
|
|
9154
|
+
ensureFileNode(toFile);
|
|
9155
|
+
const key = `${fromFile}\0${toFile}`;
|
|
9156
|
+
let edge = edgeMap.get(key);
|
|
9157
|
+
if (!edge) {
|
|
9158
|
+
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9159
|
+
edgeMap.set(key, edge);
|
|
9160
|
+
}
|
|
9161
|
+
edge.weight++;
|
|
9162
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
|
|
9163
|
+
}
|
|
9164
|
+
const edges = [];
|
|
9165
|
+
for (const [key, e] of edgeMap) {
|
|
9166
|
+
const [source, target] = key.split("\0");
|
|
9167
|
+
let bestType = "call";
|
|
9168
|
+
let bestCount = 0;
|
|
9169
|
+
for (const [t, c] of e.types) {
|
|
9170
|
+
if (c > bestCount) {
|
|
9171
|
+
bestType = t;
|
|
9172
|
+
bestCount = c;
|
|
9173
|
+
}
|
|
9174
|
+
}
|
|
9175
|
+
edges.push({
|
|
9176
|
+
source: `file:${source}`,
|
|
9177
|
+
target: `file:${target}`,
|
|
9178
|
+
weight: e.weight,
|
|
9179
|
+
refType: bestType
|
|
9180
|
+
});
|
|
9181
|
+
}
|
|
9182
|
+
return { nodes: [...fileNodes.values()], edges };
|
|
9183
|
+
}
|
|
9184
|
+
/**
|
|
9185
|
+
* Symbol-level graph for a single file: each symbol is a node; edges are
|
|
9186
|
+
* derived from intra-file and cross-file symbol references (who calls whom).
|
|
9187
|
+
*/
|
|
9188
|
+
getSymbolGraph(fileFilter) {
|
|
9189
|
+
const syms = this.db.prepare(
|
|
9190
|
+
"SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE file = ? ORDER BY line, id"
|
|
9191
|
+
).all(fileFilter);
|
|
9192
|
+
if (syms.length === 0) return { nodes: [], edges: [] };
|
|
9193
|
+
const symById = new Map(syms.map((symbol) => [symbol.id, symbol]));
|
|
9194
|
+
const relatedIds = new Set(syms.map((symbol) => symbol.id));
|
|
9195
|
+
const toGraphNode = (s) => ({
|
|
9196
|
+
id: `sym:${s.id}`,
|
|
9197
|
+
label: s.name,
|
|
9198
|
+
kind: "symbol",
|
|
9199
|
+
symbolId: s.id,
|
|
9200
|
+
symbolKind: s.kind,
|
|
9201
|
+
file: s.file,
|
|
9202
|
+
package: _IndexStore.derivePackage(s.file) ?? "(root)",
|
|
9203
|
+
lang: s.lang,
|
|
9204
|
+
line: s.line,
|
|
9205
|
+
signature: s.signature,
|
|
9206
|
+
scope: s.scope,
|
|
9207
|
+
external: s.file !== fileFilter
|
|
9208
|
+
});
|
|
9209
|
+
const symIds = new Set(syms.map((s) => s.id));
|
|
9210
|
+
const refRows = this.db.prepare(
|
|
9211
|
+
`SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
9212
|
+
FROM refs r
|
|
9213
|
+
WHERE r.from_id IN (SELECT id FROM symbols WHERE file = ?)
|
|
9214
|
+
OR r.to_id IN (SELECT id FROM symbols WHERE file = ?)`
|
|
9215
|
+
).all(fileFilter, fileFilter);
|
|
9216
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
9217
|
+
for (const r of refRows) {
|
|
9218
|
+
if (r.to_id === null) continue;
|
|
9219
|
+
if (!symIds.has(r.from_id) && !symIds.has(r.to_id)) continue;
|
|
9220
|
+
relatedIds.add(r.from_id);
|
|
9221
|
+
relatedIds.add(r.to_id);
|
|
9222
|
+
const key = `${r.from_id}\0${r.to_id}`;
|
|
9223
|
+
let e = edgeMap.get(key);
|
|
9224
|
+
if (!e) {
|
|
9225
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9226
|
+
edgeMap.set(key, e);
|
|
9227
|
+
}
|
|
9228
|
+
e.weight++;
|
|
9229
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
9230
|
+
}
|
|
9231
|
+
const edges = [];
|
|
9232
|
+
for (const [key, e] of edgeMap) {
|
|
9233
|
+
const [fromId, toId] = key.split("\0").map(Number);
|
|
9234
|
+
let bestType = "call";
|
|
9235
|
+
let bestCount = 0;
|
|
9236
|
+
for (const [t, c] of e.types) {
|
|
9237
|
+
if (c > bestCount) {
|
|
9238
|
+
bestType = t;
|
|
9239
|
+
bestCount = c;
|
|
9240
|
+
}
|
|
9241
|
+
}
|
|
9242
|
+
edges.push({
|
|
9243
|
+
source: `sym:${fromId}`,
|
|
9244
|
+
target: `sym:${toId}`,
|
|
9245
|
+
weight: e.weight,
|
|
9246
|
+
refType: bestType
|
|
9247
|
+
});
|
|
9248
|
+
}
|
|
9249
|
+
const loadedIds = new Set(syms.map((s) => s.id));
|
|
9250
|
+
const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
|
|
9251
|
+
if (missingIds.length > 0) {
|
|
9252
|
+
const placeholders = missingIds.map(() => "?").join(",");
|
|
9253
|
+
const extras = this.db.prepare(
|
|
9254
|
+
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders})`
|
|
9255
|
+
).all(...missingIds);
|
|
9256
|
+
for (const s of extras) symById.set(s.id, s);
|
|
9257
|
+
}
|
|
9258
|
+
const nodes = [...relatedIds].map((id) => symById.get(id)).filter((symbol) => symbol !== void 0).sort((a, b) => {
|
|
9259
|
+
const aExternal = a.file === fileFilter ? 0 : 1;
|
|
9260
|
+
const bExternal = b.file === fileFilter ? 0 : 1;
|
|
9261
|
+
return aExternal - bExternal || a.file.localeCompare(b.file) || a.line - b.line || a.id - b.id;
|
|
9262
|
+
}).map(toGraphNode);
|
|
9263
|
+
return { nodes, edges };
|
|
9264
|
+
}
|
|
8657
9265
|
close() {
|
|
8658
9266
|
this.stmtCache.clear();
|
|
8659
9267
|
try {
|
|
@@ -8672,6 +9280,7 @@ import { Worker } from "node:worker_threads";
|
|
|
8672
9280
|
import { expectDefined as expectDefined6 } from "@wrongstack/core";
|
|
8673
9281
|
import * as fs14 from "node:fs/promises";
|
|
8674
9282
|
import * as path19 from "node:path";
|
|
9283
|
+
import { availableParallelism } from "node:os";
|
|
8675
9284
|
import { compileGlob as compileGlob2 } from "@wrongstack/core";
|
|
8676
9285
|
|
|
8677
9286
|
// src/codebase-index/ts-parser.ts
|
|
@@ -8728,8 +9337,7 @@ function extToLang(ext) {
|
|
|
8728
9337
|
return null;
|
|
8729
9338
|
}
|
|
8730
9339
|
}
|
|
8731
|
-
function getSignature(node, sourceFile) {
|
|
8732
|
-
const printer = ts.createPrinter({});
|
|
9340
|
+
function getSignature(printer, node, sourceFile) {
|
|
8733
9341
|
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
8734
9342
|
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
8735
9343
|
}
|
|
@@ -8748,20 +9356,14 @@ function getJsDoc(node, sourceFile) {
|
|
|
8748
9356
|
}
|
|
8749
9357
|
return "";
|
|
8750
9358
|
}
|
|
8751
|
-
function
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
if (
|
|
8756
|
-
parts.
|
|
8757
|
-
} else if (ts.isMethodDeclaration(current) || ts.isGetAccessor(current) || ts.isSetAccessor(current) || ts.isPropertyDeclaration(current) || ts.isFunctionDeclaration(current)) {
|
|
8758
|
-
if (current.name && ts.isIdentifier(current.name)) {
|
|
8759
|
-
parts.unshift(current.name.text);
|
|
8760
|
-
}
|
|
9359
|
+
function pushScopeName(node, parts) {
|
|
9360
|
+
if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
|
|
9361
|
+
parts.push(node.name?.text ?? "Anon");
|
|
9362
|
+
} else if (ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node) || ts.isPropertyDeclaration(node) || ts.isFunctionDeclaration(node)) {
|
|
9363
|
+
if (node.name && ts.isIdentifier(node.name)) {
|
|
9364
|
+
parts.push(node.name.text);
|
|
8761
9365
|
}
|
|
8762
|
-
current = current.parent;
|
|
8763
9366
|
}
|
|
8764
|
-
return parts.join(".");
|
|
8765
9367
|
}
|
|
8766
9368
|
function parseSymbols(opts) {
|
|
8767
9369
|
const { file, content, lang } = opts;
|
|
@@ -8772,41 +9374,39 @@ function parseSymbols(opts) {
|
|
|
8772
9374
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
8773
9375
|
}
|
|
8774
9376
|
const symbols = [];
|
|
8775
|
-
|
|
9377
|
+
const refs = [];
|
|
9378
|
+
const printer = ts.createPrinter({});
|
|
9379
|
+
function visit(node, funcDepth, scopeParts) {
|
|
8776
9380
|
const kind = kindOf(node);
|
|
8777
9381
|
if (kind) {
|
|
8778
|
-
const
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
9382
|
+
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && funcDepth > 0) {
|
|
9383
|
+
} else {
|
|
9384
|
+
const nameNode = node.name;
|
|
9385
|
+
if (!nameNode || !ts.isIdentifier(nameNode)) {
|
|
9386
|
+
return;
|
|
9387
|
+
}
|
|
9388
|
+
const name = nameNode.text;
|
|
9389
|
+
const pos2 = nameNode.getStart(sourceFile);
|
|
9390
|
+
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(pos2);
|
|
9391
|
+
const scope = scopeParts.join(".");
|
|
9392
|
+
const signature = getSignature(printer, node, sourceFile);
|
|
9393
|
+
const docComment = getJsDoc(node, sourceFile);
|
|
9394
|
+
const text = [name, signature, docComment].filter(Boolean).join(" | ");
|
|
9395
|
+
symbols.push({
|
|
9396
|
+
id: 0,
|
|
9397
|
+
lang,
|
|
9398
|
+
kind,
|
|
9399
|
+
name,
|
|
9400
|
+
file,
|
|
9401
|
+
line: line2 + 1,
|
|
9402
|
+
col: character,
|
|
9403
|
+
signature,
|
|
9404
|
+
docComment,
|
|
9405
|
+
scope,
|
|
9406
|
+
text
|
|
9407
|
+
});
|
|
9408
|
+
}
|
|
8800
9409
|
}
|
|
8801
|
-
ts.forEachChild(node, visit);
|
|
8802
|
-
}
|
|
8803
|
-
visit(sourceFile);
|
|
8804
|
-
const refs = extractRefs(sourceFile);
|
|
8805
|
-
return { file, lang, symbols, refs, mtimeMs: Date.now() };
|
|
8806
|
-
}
|
|
8807
|
-
function extractRefs(sourceFile) {
|
|
8808
|
-
const refs = [];
|
|
8809
|
-
function visit(node) {
|
|
8810
9410
|
const pos = node.getStart(sourceFile);
|
|
8811
9411
|
const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
|
|
8812
9412
|
const lineNum = line + 1;
|
|
@@ -8831,10 +9431,14 @@ function extractRefs(sourceFile) {
|
|
|
8831
9431
|
const moduleName = getModuleName(node);
|
|
8832
9432
|
if (moduleName) refs.push({ fromId: 0, toName: moduleName, callType: "import", line: lineNum });
|
|
8833
9433
|
}
|
|
8834
|
-
|
|
9434
|
+
const scopeIdx = scopeParts.length;
|
|
9435
|
+
pushScopeName(node, scopeParts);
|
|
9436
|
+
const childFuncDepth = ts.isFunctionLike(node) ? funcDepth + 1 : funcDepth;
|
|
9437
|
+
ts.forEachChild(node, (child) => visit(child, childFuncDepth, scopeParts));
|
|
9438
|
+
scopeParts.length = scopeIdx;
|
|
8835
9439
|
}
|
|
8836
|
-
visit(sourceFile);
|
|
8837
|
-
return deduplicateRefs(refs);
|
|
9440
|
+
visit(sourceFile, 0, []);
|
|
9441
|
+
return { file, lang, symbols, refs: deduplicateRefs(refs), mtimeMs: Date.now() };
|
|
8838
9442
|
}
|
|
8839
9443
|
function getTypeName(name) {
|
|
8840
9444
|
if (ts.isIdentifier(name)) return name.text;
|
|
@@ -9719,9 +10323,9 @@ function parseSymbols5(opts) {
|
|
|
9719
10323
|
function regexParse2(opts) {
|
|
9720
10324
|
const { file, content, lang } = opts;
|
|
9721
10325
|
const symbols = [];
|
|
9722
|
-
const
|
|
9723
|
-
const isPackageJson =
|
|
9724
|
-
const isTsconfig =
|
|
10326
|
+
const basename10 = path17.basename(file).toLowerCase();
|
|
10327
|
+
const isPackageJson = basename10 === "package.json";
|
|
10328
|
+
const isTsconfig = basename10 === "tsconfig.json" || basename10 === "tsconfig.build.json";
|
|
9725
10329
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
9726
10330
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
9727
10331
|
const lines = content.split("\n");
|
|
@@ -10116,16 +10720,14 @@ async function loadGitignoreMatcher(projectRoot) {
|
|
|
10116
10720
|
|
|
10117
10721
|
// src/codebase-index/indexer.ts
|
|
10118
10722
|
var YIELD_EVERY_N = 50;
|
|
10119
|
-
var PARALLEL_BATCH =
|
|
10723
|
+
var PARALLEL_BATCH = Math.min(availableParallelism() * 4, 40);
|
|
10120
10724
|
function yieldEventLoop() {
|
|
10121
10725
|
return new Promise((resolve15) => setImmediate(resolve15));
|
|
10122
10726
|
}
|
|
10123
10727
|
function throwIfAborted(signal) {
|
|
10124
10728
|
if (!signal?.aborted) return;
|
|
10125
10729
|
if (signal.reason instanceof Error) throw signal.reason;
|
|
10126
|
-
throw new Error(
|
|
10127
|
-
typeof signal.reason === "string" ? signal.reason : "Indexing cancelled"
|
|
10128
|
-
);
|
|
10730
|
+
throw new Error(typeof signal.reason === "string" ? signal.reason : "Indexing cancelled");
|
|
10129
10731
|
}
|
|
10130
10732
|
function isAbortError(err) {
|
|
10131
10733
|
return err instanceof DOMException && err.name === "AbortError";
|
|
@@ -10141,8 +10743,20 @@ var DEFAULT_IGNORE = [
|
|
|
10141
10743
|
"__snapshots__",
|
|
10142
10744
|
".nyc_output"
|
|
10143
10745
|
];
|
|
10746
|
+
var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
|
|
10747
|
+
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
10748
|
+
function isWithinProject(projectRoot, file) {
|
|
10749
|
+
const rel = path19.relative(projectRoot, file);
|
|
10750
|
+
return rel !== "" && !rel.startsWith(`..${path19.sep}`) && rel !== ".." && !path19.isAbsolute(rel);
|
|
10751
|
+
}
|
|
10752
|
+
function isMissingPathError(err) {
|
|
10753
|
+
const code = err?.code;
|
|
10754
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
10755
|
+
}
|
|
10144
10756
|
async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
10145
10757
|
const results = [];
|
|
10758
|
+
const errors = [];
|
|
10759
|
+
let complete = true;
|
|
10146
10760
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
10147
10761
|
const globs = [
|
|
10148
10762
|
{ ext: ".ts", pat: compileGlob2("**/*.ts") },
|
|
@@ -10166,7 +10780,9 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10166
10780
|
let entries;
|
|
10167
10781
|
try {
|
|
10168
10782
|
entries = await fs14.readdir(dir, { withFileTypes: true });
|
|
10169
|
-
} catch {
|
|
10783
|
+
} catch (err) {
|
|
10784
|
+
complete = false;
|
|
10785
|
+
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10170
10786
|
return;
|
|
10171
10787
|
}
|
|
10172
10788
|
dirCount++;
|
|
@@ -10178,8 +10794,8 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10178
10794
|
if (isGitIgnored(rel, true)) continue;
|
|
10179
10795
|
await walk2(full);
|
|
10180
10796
|
} else if (e.isFile()) {
|
|
10181
|
-
if (isGitIgnored(rel, false)) continue;
|
|
10182
|
-
const ext = path19.extname(e.name);
|
|
10797
|
+
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
10798
|
+
const ext = path19.extname(e.name).toLowerCase();
|
|
10183
10799
|
for (const { ext: extName, pat } of globs) {
|
|
10184
10800
|
if (ext === extName && (pat.test(rel) || pat.test(e.name))) {
|
|
10185
10801
|
results.push(full);
|
|
@@ -10190,7 +10806,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10190
10806
|
}
|
|
10191
10807
|
};
|
|
10192
10808
|
await walk2(projectRoot);
|
|
10193
|
-
return results;
|
|
10809
|
+
return { files: results, complete, errors };
|
|
10194
10810
|
}
|
|
10195
10811
|
async function parseFile(file, content, lang) {
|
|
10196
10812
|
switch (lang) {
|
|
@@ -10213,6 +10829,26 @@ async function parseFile(file, content, lang) {
|
|
|
10213
10829
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
10214
10830
|
}
|
|
10215
10831
|
}
|
|
10832
|
+
function assignRefsToSymbols2(refs, symbols) {
|
|
10833
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
10834
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
10835
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10836
|
+
const assigned = [];
|
|
10837
|
+
for (const ref of refs) {
|
|
10838
|
+
let owner2;
|
|
10839
|
+
for (const symbol of ordered) {
|
|
10840
|
+
if (symbol.line > ref.line) break;
|
|
10841
|
+
owner2 = symbol;
|
|
10842
|
+
}
|
|
10843
|
+
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
10844
|
+
if (!owner2 || owner2.id <= 0) continue;
|
|
10845
|
+
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
10846
|
+
if (seen.has(key)) continue;
|
|
10847
|
+
seen.add(key);
|
|
10848
|
+
assigned.push({ ...ref, fromId: owner2.id });
|
|
10849
|
+
}
|
|
10850
|
+
return assigned;
|
|
10851
|
+
}
|
|
10216
10852
|
async function runIndexer(_ctx, opts) {
|
|
10217
10853
|
const store = new IndexStore(opts.projectRoot, { indexDir: opts.indexDir });
|
|
10218
10854
|
try {
|
|
@@ -10225,7 +10861,9 @@ async function runIndexer(_ctx, opts) {
|
|
|
10225
10861
|
}
|
|
10226
10862
|
}
|
|
10227
10863
|
async function runIndexerWithStore(store, opts) {
|
|
10228
|
-
const { projectRoot,
|
|
10864
|
+
const { projectRoot, langs, ignore = [], signal } = opts;
|
|
10865
|
+
const relationGraphVersion = "2";
|
|
10866
|
+
const force = (opts.force ?? false) || store.getMetadata("relation_graph_version") !== relationGraphVersion;
|
|
10229
10867
|
const startMs = Date.now();
|
|
10230
10868
|
const errors = [];
|
|
10231
10869
|
const langStats = {};
|
|
@@ -10234,10 +10872,18 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10234
10872
|
const isGitIgnored = await loadGitignoreMatcher(projectRoot);
|
|
10235
10873
|
let files;
|
|
10236
10874
|
let discoveredFiles = null;
|
|
10875
|
+
let discoveryComplete = true;
|
|
10237
10876
|
if (opts.files && opts.files.length > 0) {
|
|
10238
|
-
files = opts.files.map((f) => path19.resolve(projectRoot, f)).filter((f) =>
|
|
10877
|
+
files = opts.files.map((f) => path19.resolve(projectRoot, f)).filter((f) => {
|
|
10878
|
+
if (!isWithinProject(projectRoot, f)) return false;
|
|
10879
|
+
const rel = path19.relative(projectRoot, f).replace(/\\/g, "/");
|
|
10880
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path19.basename(f)) && !isGitIgnored(rel, false);
|
|
10881
|
+
});
|
|
10239
10882
|
} else {
|
|
10240
|
-
|
|
10883
|
+
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
10884
|
+
files = discovery.files;
|
|
10885
|
+
errors.push(...discovery.errors);
|
|
10886
|
+
discoveryComplete = discovery.complete;
|
|
10241
10887
|
discoveredFiles = new Set(files);
|
|
10242
10888
|
}
|
|
10243
10889
|
if (langs && langs.length > 0) {
|
|
@@ -10252,46 +10898,79 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10252
10898
|
if (!force) {
|
|
10253
10899
|
for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
|
|
10254
10900
|
}
|
|
10901
|
+
let filesSinceLastYield = 0;
|
|
10255
10902
|
for (let batchStart = 0; batchStart < files.length; batchStart += PARALLEL_BATCH) {
|
|
10256
10903
|
const batchEnd = Math.min(batchStart + PARALLEL_BATCH, files.length);
|
|
10257
10904
|
const batchFiles = files.slice(batchStart, batchEnd);
|
|
10258
10905
|
opts.onProgress?.(batchEnd, files.length);
|
|
10259
|
-
|
|
10906
|
+
filesSinceLastYield += batchFiles.length;
|
|
10907
|
+
if (filesSinceLastYield >= YIELD_EVERY_N) {
|
|
10908
|
+
filesSinceLastYield = 0;
|
|
10260
10909
|
await yieldEventLoop();
|
|
10261
10910
|
throwIfAborted(signal);
|
|
10262
10911
|
}
|
|
10263
10912
|
const statOpts = signal ? { signal } : {};
|
|
10264
10913
|
const statReadParse = await Promise.allSettled(
|
|
10265
|
-
batchFiles.map(
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
if (
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10914
|
+
batchFiles.map(
|
|
10915
|
+
async (file) => {
|
|
10916
|
+
let stat18;
|
|
10917
|
+
try {
|
|
10918
|
+
stat18 = await fs14.stat(file, statOpts);
|
|
10919
|
+
} catch (e) {
|
|
10920
|
+
if (isAbortError(e)) throw e;
|
|
10921
|
+
return {
|
|
10922
|
+
file,
|
|
10923
|
+
stat: null,
|
|
10924
|
+
lang: "",
|
|
10925
|
+
parsed: null,
|
|
10926
|
+
error: `stat error: ${e instanceof Error ? e.message : String(e)}`,
|
|
10927
|
+
missing: isMissingPathError(e)
|
|
10928
|
+
};
|
|
10929
|
+
}
|
|
10930
|
+
if (!stat18.isFile()) return { file, stat: stat18, lang: "", parsed: null };
|
|
10931
|
+
const lang = detectLang(file);
|
|
10932
|
+
if (!lang) return { file, stat: stat18, lang: "", parsed: null };
|
|
10933
|
+
if (stat18.size > MAX_INDEX_FILE_BYTES) {
|
|
10934
|
+
return {
|
|
10935
|
+
file,
|
|
10936
|
+
stat: stat18,
|
|
10937
|
+
lang,
|
|
10938
|
+
parsed: null,
|
|
10939
|
+
error: `file too large (${stat18.size} bytes; max ${MAX_INDEX_FILE_BYTES})`
|
|
10940
|
+
};
|
|
10941
|
+
}
|
|
10942
|
+
const meta = existingMeta.get(file);
|
|
10943
|
+
if (!force && meta && meta.mtimeMs === Math.floor(stat18.mtimeMs)) {
|
|
10944
|
+
return { file, stat: stat18, lang, parsed: null, skippedMeta: meta };
|
|
10945
|
+
}
|
|
10946
|
+
let content;
|
|
10947
|
+
try {
|
|
10948
|
+
content = await fs14.readFile(file, { encoding: "utf8", signal });
|
|
10949
|
+
} catch (e) {
|
|
10950
|
+
if (isAbortError(e)) throw e;
|
|
10951
|
+
return {
|
|
10952
|
+
file,
|
|
10953
|
+
stat: stat18,
|
|
10954
|
+
lang,
|
|
10955
|
+
parsed: null,
|
|
10956
|
+
error: `read error: ${e instanceof Error ? e.message : String(e)}`
|
|
10957
|
+
};
|
|
10958
|
+
}
|
|
10959
|
+
let parsed;
|
|
10960
|
+
try {
|
|
10961
|
+
parsed = await parseFile(file, content, lang);
|
|
10962
|
+
} catch (e) {
|
|
10963
|
+
return {
|
|
10964
|
+
file,
|
|
10965
|
+
stat: stat18,
|
|
10966
|
+
lang,
|
|
10967
|
+
parsed: null,
|
|
10968
|
+
error: `parse error: ${e instanceof Error ? e.message : String(e)}`
|
|
10969
|
+
};
|
|
10970
|
+
}
|
|
10971
|
+
return { file, stat: stat18, lang, parsed, content };
|
|
10292
10972
|
}
|
|
10293
|
-
|
|
10294
|
-
})
|
|
10973
|
+
)
|
|
10295
10974
|
);
|
|
10296
10975
|
const batchEntries = [];
|
|
10297
10976
|
const deleteForFiles = [];
|
|
@@ -10306,8 +10985,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10306
10985
|
}
|
|
10307
10986
|
const result = settled.value;
|
|
10308
10987
|
if (result.error) {
|
|
10309
|
-
if (result.
|
|
10310
|
-
|
|
10988
|
+
if (result.missing) store.deleteFile(file);
|
|
10989
|
+
errors.push(`${file}: ${result.error}`);
|
|
10311
10990
|
continue;
|
|
10312
10991
|
}
|
|
10313
10992
|
const { stat: stat18, lang, parsed } = result;
|
|
@@ -10331,6 +11010,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10331
11010
|
continue;
|
|
10332
11011
|
}
|
|
10333
11012
|
if (parsed.symbols.length === 0) {
|
|
11013
|
+
store.deleteRefsForFile(file);
|
|
11014
|
+
store.deleteSymbolsForFile(file);
|
|
10334
11015
|
store.upsertFile({
|
|
10335
11016
|
file,
|
|
10336
11017
|
lang,
|
|
@@ -10341,15 +11022,11 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10341
11022
|
filesIndexed++;
|
|
10342
11023
|
continue;
|
|
10343
11024
|
}
|
|
10344
|
-
const refs = [];
|
|
10345
|
-
if (parsed.refs && parsed.refs.length > 0) {
|
|
10346
|
-
for (const r of parsed.refs) refs.push({ ...r, fromId: 0 });
|
|
10347
|
-
}
|
|
10348
11025
|
batchEntries.push({
|
|
10349
11026
|
file,
|
|
10350
11027
|
lang,
|
|
10351
11028
|
symbols: parsed.symbols,
|
|
10352
|
-
refs,
|
|
11029
|
+
refs: parsed.refs ?? [],
|
|
10353
11030
|
mtimeMs: Math.floor(stat18.mtimeMs),
|
|
10354
11031
|
symbolCount: parsed.symbols.length
|
|
10355
11032
|
});
|
|
@@ -10357,34 +11034,12 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10357
11034
|
}
|
|
10358
11035
|
if (batchEntries.length > 0) {
|
|
10359
11036
|
try {
|
|
10360
|
-
|
|
10361
|
-
let cursor = 0;
|
|
11037
|
+
store.commitBatch(batchEntries, { deleteForFiles });
|
|
10362
11038
|
for (const entry of batchEntries) {
|
|
10363
11039
|
const count = entry.symbols.length;
|
|
10364
|
-
const symbolsWithIds = inserted.slice(cursor, cursor + count);
|
|
10365
|
-
cursor += count;
|
|
10366
11040
|
symbolsIndexed += count;
|
|
10367
11041
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
|
|
10368
11042
|
filesIndexed++;
|
|
10369
|
-
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
10370
|
-
const refsByLine = /* @__PURE__ */ new Map();
|
|
10371
|
-
for (let i = 0; i < symbolsWithIds.length; i++) {
|
|
10372
|
-
const sym = symbolsWithIds[i];
|
|
10373
|
-
let arr = refsByLine.get(sym.line);
|
|
10374
|
-
if (!arr) {
|
|
10375
|
-
arr = [];
|
|
10376
|
-
refsByLine.set(sym.line, arr);
|
|
10377
|
-
}
|
|
10378
|
-
arr.push(i);
|
|
10379
|
-
}
|
|
10380
|
-
for (const ref of entry.refs) {
|
|
10381
|
-
const indices = refsByLine.get(ref.line);
|
|
10382
|
-
if (indices && indices.length > 0) {
|
|
10383
|
-
const idx = indices.shift();
|
|
10384
|
-
ref.fromId = symbolsWithIds[idx].id;
|
|
10385
|
-
}
|
|
10386
|
-
}
|
|
10387
|
-
}
|
|
10388
11043
|
}
|
|
10389
11044
|
} catch (err) {
|
|
10390
11045
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -10398,23 +11053,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10398
11053
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
|
|
10399
11054
|
filesIndexed++;
|
|
10400
11055
|
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
10401
|
-
const
|
|
10402
|
-
for (const sym of symbolsWithIds) {
|
|
10403
|
-
let arr = refsByLine.get(sym.line);
|
|
10404
|
-
if (!arr) {
|
|
10405
|
-
arr = [];
|
|
10406
|
-
refsByLine.set(sym.line, arr);
|
|
10407
|
-
}
|
|
10408
|
-
arr.push(sym);
|
|
10409
|
-
}
|
|
10410
|
-
const fallbackBatch = [];
|
|
10411
|
-
for (const ref of entry.refs) {
|
|
10412
|
-
const syms = refsByLine.get(ref.line);
|
|
10413
|
-
if (syms && syms.length > 0) {
|
|
10414
|
-
const sym = syms.shift();
|
|
10415
|
-
fallbackBatch.push({ ...ref, fromId: sym.id });
|
|
10416
|
-
}
|
|
10417
|
-
}
|
|
11056
|
+
const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
|
|
10418
11057
|
if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
|
|
10419
11058
|
}
|
|
10420
11059
|
store.upsertFile({
|
|
@@ -10425,19 +11064,23 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10425
11064
|
lastIndexed: Date.now()
|
|
10426
11065
|
});
|
|
10427
11066
|
} catch (innerErr) {
|
|
10428
|
-
errors.push(
|
|
11067
|
+
errors.push(
|
|
11068
|
+
`fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
|
|
11069
|
+
);
|
|
10429
11070
|
}
|
|
10430
11071
|
}
|
|
10431
11072
|
}
|
|
10432
11073
|
}
|
|
10433
11074
|
}
|
|
10434
|
-
if (discoveredFiles) {
|
|
11075
|
+
if (discoveredFiles && discoveryComplete) {
|
|
10435
11076
|
for (const [file_] of existingMeta) {
|
|
10436
11077
|
if (!discoveredFiles.has(file_)) {
|
|
10437
11078
|
store.deleteFile(file_);
|
|
10438
11079
|
}
|
|
10439
11080
|
}
|
|
10440
11081
|
}
|
|
11082
|
+
store.resolveRefs();
|
|
11083
|
+
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
10441
11084
|
const durationMs = Date.now() - startMs;
|
|
10442
11085
|
store.setLastIndexed(Date.now());
|
|
10443
11086
|
return {
|
|
@@ -10473,7 +11116,7 @@ async function indexService(args, hooks = {}) {
|
|
|
10473
11116
|
});
|
|
10474
11117
|
}
|
|
10475
11118
|
function searchService(args) {
|
|
10476
|
-
const store =
|
|
11119
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
10477
11120
|
try {
|
|
10478
11121
|
return store.searchRanked(
|
|
10479
11122
|
args.query,
|
|
@@ -10486,22 +11129,46 @@ function searchService(args) {
|
|
|
10486
11129
|
args.limit
|
|
10487
11130
|
);
|
|
10488
11131
|
} finally {
|
|
10489
|
-
|
|
11132
|
+
indexStorePool.release(store);
|
|
10490
11133
|
}
|
|
10491
11134
|
}
|
|
10492
11135
|
function statsService(args) {
|
|
10493
|
-
const store =
|
|
11136
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
10494
11137
|
try {
|
|
10495
11138
|
return store.getStats();
|
|
10496
11139
|
} finally {
|
|
10497
|
-
|
|
11140
|
+
indexStorePool.release(store);
|
|
11141
|
+
}
|
|
11142
|
+
}
|
|
11143
|
+
function packageGraphService(args) {
|
|
11144
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
11145
|
+
try {
|
|
11146
|
+
return store.getPackageGraph();
|
|
11147
|
+
} finally {
|
|
11148
|
+
indexStorePool.release(store);
|
|
11149
|
+
}
|
|
11150
|
+
}
|
|
11151
|
+
function fileGraphService(args) {
|
|
11152
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
11153
|
+
try {
|
|
11154
|
+
return store.getFileGraph(args.packageFilter);
|
|
11155
|
+
} finally {
|
|
11156
|
+
indexStorePool.release(store);
|
|
11157
|
+
}
|
|
11158
|
+
}
|
|
11159
|
+
function symbolGraphService(args) {
|
|
11160
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
11161
|
+
try {
|
|
11162
|
+
return store.getSymbolGraph(args.fileFilter);
|
|
11163
|
+
} finally {
|
|
11164
|
+
indexStorePool.release(store);
|
|
10498
11165
|
}
|
|
10499
11166
|
}
|
|
10500
11167
|
|
|
10501
11168
|
// src/codebase-index/background-indexer.ts
|
|
10502
11169
|
var DEFAULT_FULL_INDEX_TIMEOUT_MS = 24e4;
|
|
10503
11170
|
var DEFAULT_INCREMENTAL_TIMEOUT_MS = 6e4;
|
|
10504
|
-
var DEFAULT_QUERY_TIMEOUT_MS =
|
|
11171
|
+
var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
10505
11172
|
var _ready = false;
|
|
10506
11173
|
var _indexing = false;
|
|
10507
11174
|
var _currentFile = 0;
|
|
@@ -10579,14 +11246,22 @@ function ensureWorker() {
|
|
|
10579
11246
|
if (!entry) return;
|
|
10580
11247
|
pending.delete(msg.id);
|
|
10581
11248
|
if (msg.ok) entry.resolve(msg.result);
|
|
10582
|
-
else
|
|
11249
|
+
else {
|
|
11250
|
+
const error = msg.errorName === "LockError" ? new LockError(msg.error) : new Error(msg.error);
|
|
11251
|
+
if (msg.errorName && msg.errorName !== "Error") {
|
|
11252
|
+
error.name = msg.errorName;
|
|
11253
|
+
}
|
|
11254
|
+
entry.reject(error);
|
|
11255
|
+
}
|
|
10583
11256
|
});
|
|
10584
11257
|
w.on("error", (err) => {
|
|
11258
|
+
if (worker !== w) return;
|
|
10585
11259
|
worker = null;
|
|
10586
11260
|
failAllPending(err);
|
|
10587
11261
|
});
|
|
10588
11262
|
w.on("exit", () => {
|
|
10589
|
-
if (worker
|
|
11263
|
+
if (worker !== w) return;
|
|
11264
|
+
worker = null;
|
|
10590
11265
|
failAllPending(new Error("codebase-index worker exited"));
|
|
10591
11266
|
});
|
|
10592
11267
|
worker = w;
|
|
@@ -10603,14 +11278,28 @@ function terminateWorker(reason) {
|
|
|
10603
11278
|
if (w) void w.terminate().catch(() => {
|
|
10604
11279
|
});
|
|
10605
11280
|
}
|
|
10606
|
-
function shutdownCodebaseIndexHost() {
|
|
11281
|
+
async function shutdownCodebaseIndexHost() {
|
|
10607
11282
|
cancelPendingReindexes();
|
|
10608
|
-
|
|
11283
|
+
indexStorePool.closeAll();
|
|
11284
|
+
const w = worker;
|
|
11285
|
+
worker = null;
|
|
11286
|
+
failAllPending(new Error("codebase-index host shut down"));
|
|
10609
11287
|
workerUnavailable = false;
|
|
11288
|
+
if (w) {
|
|
11289
|
+
try {
|
|
11290
|
+
await w.terminate();
|
|
11291
|
+
} catch {
|
|
11292
|
+
}
|
|
11293
|
+
}
|
|
10610
11294
|
}
|
|
10611
11295
|
function callIndexOp(op, args, opts) {
|
|
10612
11296
|
const w = ensureWorker();
|
|
10613
11297
|
if (!w) return callInline(op, args, opts);
|
|
11298
|
+
if (opts.signal?.aborted) {
|
|
11299
|
+
return Promise.reject(
|
|
11300
|
+
opts.signal.reason instanceof Error ? opts.signal.reason : new Error("Indexing cancelled")
|
|
11301
|
+
);
|
|
11302
|
+
}
|
|
10614
11303
|
return new Promise((resolve15, reject) => {
|
|
10615
11304
|
const id = nextRpcId++;
|
|
10616
11305
|
const timer = setTimeout(() => {
|
|
@@ -10625,8 +11314,7 @@ function callIndexOp(op, args, opts) {
|
|
|
10625
11314
|
const onAbort = () => {
|
|
10626
11315
|
w.postMessage({ type: "cancel", id });
|
|
10627
11316
|
};
|
|
10628
|
-
|
|
10629
|
-
else opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
11317
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
10630
11318
|
const cleanup = () => {
|
|
10631
11319
|
clearTimeout(timer);
|
|
10632
11320
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
@@ -10814,7 +11502,7 @@ var codebaseIndexTool = {
|
|
|
10814
11502
|
category: "Project",
|
|
10815
11503
|
icon: "index",
|
|
10816
11504
|
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.",
|
|
10817
|
-
usageHint: "
|
|
11505
|
+
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.",
|
|
10818
11506
|
permission: "confirm",
|
|
10819
11507
|
mutating: true,
|
|
10820
11508
|
capabilities: ["fs.write.outside-project"],
|
|
@@ -10870,12 +11558,14 @@ var codebaseSearchTool = {
|
|
|
10870
11558
|
name: "codebase-search",
|
|
10871
11559
|
category: "Project",
|
|
10872
11560
|
icon: "index",
|
|
10873
|
-
description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback.
|
|
10874
|
-
usageHint: "
|
|
11561
|
+
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).",
|
|
11562
|
+
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.",
|
|
10875
11563
|
permission: "auto",
|
|
10876
11564
|
mutating: false,
|
|
10877
11565
|
capabilities: ["fs.read"],
|
|
10878
|
-
|
|
11566
|
+
// The index host has its own 30s read watchdog. Leave enough headroom for
|
|
11567
|
+
// worker teardown and structured timeout reporting.
|
|
11568
|
+
timeoutMs: 35e3,
|
|
10879
11569
|
inputSchema: {
|
|
10880
11570
|
type: "object",
|
|
10881
11571
|
properties: {
|
|
@@ -10885,11 +11575,34 @@ var codebaseSearchTool = {
|
|
|
10885
11575
|
},
|
|
10886
11576
|
kind: {
|
|
10887
11577
|
type: "string",
|
|
10888
|
-
|
|
11578
|
+
enum: [
|
|
11579
|
+
"class",
|
|
11580
|
+
"interface",
|
|
11581
|
+
"enum",
|
|
11582
|
+
"type",
|
|
11583
|
+
"function",
|
|
11584
|
+
"method",
|
|
11585
|
+
"var",
|
|
11586
|
+
"const",
|
|
11587
|
+
"let",
|
|
11588
|
+
"property",
|
|
11589
|
+
"parameter",
|
|
11590
|
+
"namespace",
|
|
11591
|
+
"object",
|
|
11592
|
+
"literal",
|
|
11593
|
+
"schema",
|
|
11594
|
+
"struct",
|
|
11595
|
+
"trait",
|
|
11596
|
+
"impl",
|
|
11597
|
+
"static",
|
|
11598
|
+
"mod"
|
|
11599
|
+
],
|
|
11600
|
+
description: "Filter by indexed symbol kind"
|
|
10889
11601
|
},
|
|
10890
11602
|
lang: {
|
|
10891
11603
|
type: "string",
|
|
10892
|
-
|
|
11604
|
+
enum: ["ts", "tsx", "js", "jsx", "go", "py", "rs", "json", "yaml"],
|
|
11605
|
+
description: "Filter by indexed language"
|
|
10893
11606
|
},
|
|
10894
11607
|
lspKind: {
|
|
10895
11608
|
type: "integer",
|
|
@@ -10914,20 +11627,12 @@ var codebaseSearchTool = {
|
|
|
10914
11627
|
},
|
|
10915
11628
|
async execute(input, ctx, execOpts) {
|
|
10916
11629
|
const state = getIndexState();
|
|
10917
|
-
if (!state.ready) {
|
|
10918
|
-
return {
|
|
10919
|
-
results: [],
|
|
10920
|
-
total: 0,
|
|
10921
|
-
query: input.query,
|
|
10922
|
-
indexStatus: state.indexing ? `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.` : "Index not yet built. The codebase is being indexed at startup \u2014 search will be available shortly."
|
|
10923
|
-
};
|
|
10924
|
-
}
|
|
10925
|
-
if (state.indexing) {
|
|
11630
|
+
if (state.indexing && !state.ready) {
|
|
10926
11631
|
return {
|
|
10927
11632
|
results: [],
|
|
10928
11633
|
total: 0,
|
|
10929
11634
|
query: input.query,
|
|
10930
|
-
indexStatus: `
|
|
11635
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
10931
11636
|
};
|
|
10932
11637
|
}
|
|
10933
11638
|
if (state.lastError) {
|
|
@@ -10940,21 +11645,37 @@ var codebaseSearchTool = {
|
|
|
10940
11645
|
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
10941
11646
|
};
|
|
10942
11647
|
}
|
|
10943
|
-
const limit = Math.min(input.limit ?? 20, 100);
|
|
11648
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 20), 100));
|
|
10944
11649
|
const { results, total } = await searchCodebaseIndex(
|
|
10945
11650
|
{
|
|
10946
11651
|
projectRoot: ctx.projectRoot,
|
|
10947
11652
|
indexDir: codebaseIndexDirOverride(ctx),
|
|
10948
11653
|
query: input.query,
|
|
10949
|
-
kind: input.kind,
|
|
10950
|
-
lang: input.lang,
|
|
11654
|
+
kind: input.kind?.toLowerCase(),
|
|
11655
|
+
lang: input.lang?.toLowerCase(),
|
|
10951
11656
|
file: input.file,
|
|
10952
11657
|
lspKind: input.lspKind,
|
|
10953
11658
|
limit
|
|
10954
11659
|
},
|
|
10955
11660
|
{ signal: execOpts?.signal }
|
|
10956
11661
|
);
|
|
10957
|
-
|
|
11662
|
+
let hasPersistedIndex = state.ready || total > 0;
|
|
11663
|
+
if (!hasPersistedIndex) {
|
|
11664
|
+
try {
|
|
11665
|
+
const stats = await codebaseIndexStats(
|
|
11666
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
11667
|
+
{ signal: execOpts?.signal }
|
|
11668
|
+
);
|
|
11669
|
+
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
11670
|
+
} catch {
|
|
11671
|
+
}
|
|
11672
|
+
}
|
|
11673
|
+
return {
|
|
11674
|
+
results,
|
|
11675
|
+
total,
|
|
11676
|
+
query: input.query,
|
|
11677
|
+
...hasPersistedIndex ? {} : { indexStatus: "No persisted index data found. Run codebase-index to build it." }
|
|
11678
|
+
};
|
|
10958
11679
|
}
|
|
10959
11680
|
};
|
|
10960
11681
|
|
|
@@ -10963,12 +11684,15 @@ var codebaseStatsTool = {
|
|
|
10963
11684
|
name: "codebase-stats",
|
|
10964
11685
|
category: "Project",
|
|
10965
11686
|
icon: "index",
|
|
10966
|
-
description: "
|
|
10967
|
-
usageHint: "
|
|
11687
|
+
description: "Check whether a persisted codebase index exists and report its health and statistics (symbols, files, language/kind breakdown, size, last update).",
|
|
11688
|
+
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.",
|
|
10968
11689
|
permission: "auto",
|
|
10969
11690
|
mutating: false,
|
|
10970
11691
|
capabilities: ["fs.read"],
|
|
10971
|
-
|
|
11692
|
+
// The index host has its own 30s read watchdog. Keep the outer tool budget
|
|
11693
|
+
// longer so the host can return a structured timeout instead of being
|
|
11694
|
+
// pre-empted by the generic tool executor.
|
|
11695
|
+
timeoutMs: 35e3,
|
|
10972
11696
|
inputSchema: {
|
|
10973
11697
|
type: "object",
|
|
10974
11698
|
properties: {},
|
|
@@ -10976,7 +11700,8 @@ var codebaseStatsTool = {
|
|
|
10976
11700
|
},
|
|
10977
11701
|
async execute(_input, ctx, execOpts) {
|
|
10978
11702
|
const idxState = getIndexState();
|
|
10979
|
-
|
|
11703
|
+
const indexPath = resolveIndexDir(ctx.projectRoot, codebaseIndexDirOverride(ctx));
|
|
11704
|
+
if (idxState.indexing) {
|
|
10980
11705
|
return {
|
|
10981
11706
|
totalSymbols: 0,
|
|
10982
11707
|
totalFiles: 0,
|
|
@@ -10984,22 +11709,47 @@ var codebaseStatsTool = {
|
|
|
10984
11709
|
byKind: {},
|
|
10985
11710
|
lastIndexed: null,
|
|
10986
11711
|
sizeBytes: 0,
|
|
10987
|
-
indexPath
|
|
11712
|
+
indexPath,
|
|
10988
11713
|
version: SCHEMA_VERSION,
|
|
10989
|
-
|
|
11714
|
+
statsAvailable: false,
|
|
11715
|
+
indexing: {
|
|
11716
|
+
currentFile: idxState.currentFile,
|
|
11717
|
+
totalFiles: idxState.totalFiles
|
|
11718
|
+
},
|
|
11719
|
+
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.`
|
|
10990
11720
|
};
|
|
10991
11721
|
}
|
|
10992
|
-
|
|
10993
|
-
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
|
|
11722
|
+
let stats;
|
|
11723
|
+
try {
|
|
11724
|
+
stats = await codebaseIndexStats(
|
|
11725
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
11726
|
+
{ signal: execOpts?.signal }
|
|
11727
|
+
);
|
|
11728
|
+
} catch (err) {
|
|
11729
|
+
if (!(err instanceof IndexTimeoutError) && err?.name !== "IndexTimeoutError") {
|
|
11730
|
+
throw err;
|
|
11731
|
+
}
|
|
10997
11732
|
return {
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11733
|
+
totalSymbols: 0,
|
|
11734
|
+
totalFiles: 0,
|
|
11735
|
+
byLang: {},
|
|
11736
|
+
byKind: {},
|
|
11737
|
+
lastIndexed: null,
|
|
11738
|
+
sizeBytes: 0,
|
|
11739
|
+
indexPath,
|
|
11740
|
+
version: SCHEMA_VERSION,
|
|
11741
|
+
statsAvailable: false,
|
|
11742
|
+
indexStatus: "Index statistics timed out. Do not repeatedly retry stats; try codebase-search directly or use the appropriate grep/glob/tree fallback."
|
|
11743
|
+
};
|
|
11001
11744
|
}
|
|
11002
11745
|
const circuit = idxState.circuit;
|
|
11746
|
+
const hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
11747
|
+
let indexStatus;
|
|
11748
|
+
if (circuit.state === "open") {
|
|
11749
|
+
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.");
|
|
11750
|
+
} else if (!hasPersistedIndex) {
|
|
11751
|
+
indexStatus = "No persisted index data found. Run codebase-index to build it before broad code exploration.";
|
|
11752
|
+
}
|
|
11003
11753
|
return {
|
|
11004
11754
|
totalSymbols: stats.totalSymbols,
|
|
11005
11755
|
totalFiles: stats.totalFiles,
|
|
@@ -11009,9 +11759,8 @@ var codebaseStatsTool = {
|
|
|
11009
11759
|
sizeBytes: stats.sizeBytes,
|
|
11010
11760
|
indexPath: stats.indexPath,
|
|
11011
11761
|
version: stats.version,
|
|
11012
|
-
|
|
11013
|
-
|
|
11014
|
-
} : {}
|
|
11762
|
+
statsAvailable: true,
|
|
11763
|
+
...indexStatus ? { indexStatus } : {}
|
|
11015
11764
|
};
|
|
11016
11765
|
}
|
|
11017
11766
|
};
|
|
@@ -12530,6 +13279,11 @@ Compare this against your old_string and retry with the file's actual text.` : "
|
|
|
12530
13279
|
|
|
12531
13280
|
// src/exec.ts
|
|
12532
13281
|
import { spawn as spawn8 } from "node:child_process";
|
|
13282
|
+
import {
|
|
13283
|
+
emitProcessCompleted as emitProcessCompleted3,
|
|
13284
|
+
emitProcessOutput as emitProcessOutput3,
|
|
13285
|
+
emitProcessStarted as emitProcessStarted3
|
|
13286
|
+
} from "@wrongstack/core";
|
|
12533
13287
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils/error";
|
|
12534
13288
|
init_output_spool();
|
|
12535
13289
|
init_util();
|
|
@@ -13384,12 +14138,30 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13384
14138
|
resolve15(result);
|
|
13385
14139
|
};
|
|
13386
14140
|
const startedAt = Date.now();
|
|
14141
|
+
let stdoutBytes = 0;
|
|
14142
|
+
let stderrBytes = 0;
|
|
14143
|
+
let telemetryCompleted = false;
|
|
14144
|
+
let timedOut = false;
|
|
13387
14145
|
const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
|
|
13388
14146
|
const resolved = resolveWin32Command(cmd);
|
|
13389
14147
|
const needsShell = isWin3 && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
13390
14148
|
const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
|
|
13391
14149
|
const spawnCmd = shim?.command ?? resolved;
|
|
13392
14150
|
const spawnArgs = shim?.args ?? args;
|
|
14151
|
+
const emitCompletedOnce = (exitCode, pid2, signal2) => {
|
|
14152
|
+
if (telemetryCompleted) return;
|
|
14153
|
+
telemetryCompleted = true;
|
|
14154
|
+
emitProcessCompleted3({
|
|
14155
|
+
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
14156
|
+
exitCode,
|
|
14157
|
+
...signal2 ? { signal: signal2 } : {},
|
|
14158
|
+
durationMs: Date.now() - startedAt,
|
|
14159
|
+
stdoutBytes,
|
|
14160
|
+
stderrBytes,
|
|
14161
|
+
timedOut,
|
|
14162
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
14163
|
+
});
|
|
14164
|
+
};
|
|
13393
14165
|
let child;
|
|
13394
14166
|
try {
|
|
13395
14167
|
child = spawn8(spawnCmd, spawnArgs, {
|
|
@@ -13402,6 +14174,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13402
14174
|
});
|
|
13403
14175
|
} catch (err) {
|
|
13404
14176
|
spool.finalize();
|
|
14177
|
+
emitProcessStarted3({
|
|
14178
|
+
parentPid: process.pid,
|
|
14179
|
+
command: redactCommand(`${cmd} ${args.join(" ")}`),
|
|
14180
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
14181
|
+
cwd,
|
|
14182
|
+
background: false,
|
|
14183
|
+
startedAt: new Date(startedAt).toISOString()
|
|
14184
|
+
});
|
|
14185
|
+
emitCompletedOnce(1, void 0);
|
|
13405
14186
|
finish({
|
|
13406
14187
|
command: cmd,
|
|
13407
14188
|
args,
|
|
@@ -13414,6 +14195,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13414
14195
|
});
|
|
13415
14196
|
return;
|
|
13416
14197
|
}
|
|
14198
|
+
emitProcessStarted3({
|
|
14199
|
+
...child.pid !== void 0 ? { pid: child.pid } : {},
|
|
14200
|
+
parentPid: process.pid,
|
|
14201
|
+
command: redactCommand(`${cmd} ${args.join(" ")}`),
|
|
14202
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
14203
|
+
cwd,
|
|
14204
|
+
background: false,
|
|
14205
|
+
startedAt: new Date(startedAt).toISOString()
|
|
14206
|
+
});
|
|
13417
14207
|
child.on("error", (err) => {
|
|
13418
14208
|
const isAbort = err && err.code === "ABORT_ERR";
|
|
13419
14209
|
const stderrText = isAbort ? `Aborted: ${err.message}` : err.message;
|
|
@@ -13422,6 +14212,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13422
14212
|
if (typeof pid === "number") registry.unregister(pid);
|
|
13423
14213
|
registry.afterCall(Date.now() - startedAt, true);
|
|
13424
14214
|
spool.finalize();
|
|
14215
|
+
emitCompletedOnce(isAbort ? 124 : 1, child.pid, isAbort ? "ABORT" : void 0);
|
|
13425
14216
|
finish({
|
|
13426
14217
|
command: cmd,
|
|
13427
14218
|
args,
|
|
@@ -13441,6 +14232,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13441
14232
|
}
|
|
13442
14233
|
const timer = setTimeout(() => {
|
|
13443
14234
|
killed = true;
|
|
14235
|
+
timedOut = true;
|
|
13444
14236
|
if (typeof pid === "number") registry.kill(pid);
|
|
13445
14237
|
else child.kill("SIGTERM");
|
|
13446
14238
|
}, timeout);
|
|
@@ -13455,11 +14247,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13455
14247
|
}
|
|
13456
14248
|
child.stdout?.on("data", (chunk) => {
|
|
13457
14249
|
const text = chunk.toString();
|
|
14250
|
+
stdoutBytes += chunk.byteLength;
|
|
14251
|
+
emitProcessOutput3({ pid, stream: "stdout", chunk });
|
|
13458
14252
|
if (stdout.length < MAX_OUTPUT2) stdout += text;
|
|
13459
14253
|
spool.write(text);
|
|
13460
14254
|
});
|
|
13461
14255
|
child.stderr?.on("data", (chunk) => {
|
|
13462
14256
|
const text = chunk.toString();
|
|
14257
|
+
stderrBytes += chunk.byteLength;
|
|
14258
|
+
emitProcessOutput3({ pid, stream: "stderr", chunk });
|
|
13463
14259
|
if (stderr.length < MAX_OUTPUT2) stderr += text;
|
|
13464
14260
|
spool.write(text);
|
|
13465
14261
|
});
|
|
@@ -13469,6 +14265,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13469
14265
|
if (typeof pid === "number") registry.unregister(pid);
|
|
13470
14266
|
const durationMs = Date.now() - startedAt;
|
|
13471
14267
|
const exitCode = killed ? 124 : code ?? 1;
|
|
14268
|
+
emitCompletedOnce(exitCode, pid);
|
|
13472
14269
|
registry.afterCall(durationMs, exitCode !== 0);
|
|
13473
14270
|
const spooled = spool.finalize();
|
|
13474
14271
|
finish({
|
|
@@ -13544,7 +14341,7 @@ function guardedLookup(hostname4, options, callback) {
|
|
|
13544
14341
|
var pinnedAgent;
|
|
13545
14342
|
function getPinnedDispatcher() {
|
|
13546
14343
|
if (!pinnedAgent) {
|
|
13547
|
-
pinnedAgent = new Agent({ connect: { lookup: guardedLookup } });
|
|
14344
|
+
pinnedAgent = new Agent({ allowH2: false, connect: { lookup: guardedLookup } });
|
|
13548
14345
|
}
|
|
13549
14346
|
return pinnedAgent;
|
|
13550
14347
|
}
|
|
@@ -13646,7 +14443,7 @@ async function assertNotPrivate(hostname4) {
|
|
|
13646
14443
|
}
|
|
13647
14444
|
}
|
|
13648
14445
|
} catch (err) {
|
|
13649
|
-
if (err instanceof
|
|
14446
|
+
if (err instanceof ToolValidationError2) throw err;
|
|
13650
14447
|
}
|
|
13651
14448
|
}
|
|
13652
14449
|
}
|
|
@@ -13994,7 +14791,7 @@ async function detectFixer(cwd) {
|
|
|
13994
14791
|
init_util();
|
|
13995
14792
|
import { spawn as spawn9 } from "node:child_process";
|
|
13996
14793
|
import { statSync as statSync3 } from "node:fs";
|
|
13997
|
-
import { dirname as dirname12, resolve as resolve11, sep as
|
|
14794
|
+
import { dirname as dirname12, resolve as resolve11, sep as sep5 } from "node:path";
|
|
13998
14795
|
import { assessCommitSafety, buildChildEnv as buildChildEnv4 } from "@wrongstack/core";
|
|
13999
14796
|
var TIMEOUT_MS2 = 3e4;
|
|
14000
14797
|
var MAX_OUTPUT3 = 1e5;
|
|
@@ -14139,7 +14936,7 @@ function validateWorktreeInput(input, projectRoot) {
|
|
|
14139
14936
|
if ((input.worktreeAction === "add" || input.worktreeAction === "remove") && input.worktreePath) {
|
|
14140
14937
|
const root = resolve11(projectRoot);
|
|
14141
14938
|
const abs = resolve11(root, input.worktreePath);
|
|
14142
|
-
if (abs !== root && !abs.startsWith(root +
|
|
14939
|
+
if (abs !== root && !abs.startsWith(root + sep5)) {
|
|
14143
14940
|
return reject(`unsafe worktree path (escapes project root): ${input.worktreePath}`);
|
|
14144
14941
|
}
|
|
14145
14942
|
}
|
|
@@ -14302,8 +15099,8 @@ var WALK_CONCURRENCY = 16;
|
|
|
14302
15099
|
var globTool = {
|
|
14303
15100
|
name: "glob",
|
|
14304
15101
|
category: "Filesystem",
|
|
14305
|
-
description: "Find files
|
|
14306
|
-
usageHint: "
|
|
15102
|
+
description: "Find files by path pattern. Use index-backed `codebase-search` first for code symbols or concepts when it is live.",
|
|
15103
|
+
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.",
|
|
14307
15104
|
selection: {
|
|
14308
15105
|
doNotUseWhen: "you need to search inside file contents.",
|
|
14309
15106
|
useInstead: ["grep"]
|
|
@@ -14491,8 +15288,8 @@ var NATIVE_MAX_FILE_BYTES = 1e6;
|
|
|
14491
15288
|
var grepTool = {
|
|
14492
15289
|
name: "grep",
|
|
14493
15290
|
category: "Search",
|
|
14494
|
-
description: "Search
|
|
14495
|
-
usageHint: '
|
|
15291
|
+
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.",
|
|
15292
|
+
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.',
|
|
14496
15293
|
selection: {
|
|
14497
15294
|
doNotUseWhen: "you only need to locate files by name or path pattern.",
|
|
14498
15295
|
useInstead: ["glob"]
|
|
@@ -15514,6 +16311,7 @@ function toYaml(data, indent = 0) {
|
|
|
15514
16311
|
}
|
|
15515
16312
|
|
|
15516
16313
|
// src/kanban.ts
|
|
16314
|
+
import { randomUUID } from "node:crypto";
|
|
15517
16315
|
import { deserializeTaskGraph as deserializeTaskGraph2, loadTasks as loadTasks2, serializeTaskGraph } from "@wrongstack/core";
|
|
15518
16316
|
import {
|
|
15519
16317
|
addCheckToTask,
|
|
@@ -15531,7 +16329,7 @@ import {
|
|
|
15531
16329
|
duplicateBoard,
|
|
15532
16330
|
exportBoardAsMarkdown,
|
|
15533
16331
|
exportBoardToTaskGraph,
|
|
15534
|
-
|
|
16332
|
+
createBoardFromText,
|
|
15535
16333
|
getBoard as getBoard2,
|
|
15536
16334
|
getKanbanOrchestrationSnapshot,
|
|
15537
16335
|
getKanbanQueueHealth,
|
|
@@ -15553,7 +16351,9 @@ import {
|
|
|
15553
16351
|
setTaskChain,
|
|
15554
16352
|
splitTask,
|
|
15555
16353
|
syncBoardFromTaskGraph as syncBoardFromTaskGraph2,
|
|
16354
|
+
transitionTask,
|
|
15556
16355
|
transferTaskToBoard,
|
|
16356
|
+
touchKanbanPresence as touchKanbanPresence2,
|
|
15557
16357
|
updateBoard as updateBoard2,
|
|
15558
16358
|
updateCheckOnTask,
|
|
15559
16359
|
updateColumn,
|
|
@@ -15564,20 +16364,24 @@ import {
|
|
|
15564
16364
|
|
|
15565
16365
|
// src/session-kanban.ts
|
|
15566
16366
|
import { watch } from "node:fs";
|
|
15567
|
-
import { basename as
|
|
16367
|
+
import { basename as basename9, dirname as dirname13 } from "node:path";
|
|
15568
16368
|
import {
|
|
15569
16369
|
deserializeTaskGraph,
|
|
16370
|
+
GlobalMailbox,
|
|
15570
16371
|
loadPlan,
|
|
15571
16372
|
loadTasks,
|
|
15572
16373
|
mutatePlan,
|
|
15573
16374
|
mutateTasks
|
|
15574
16375
|
} from "@wrongstack/core";
|
|
16376
|
+
import { resolveWstackPaths as resolveWstackPaths3 } from "@wrongstack/core/utils";
|
|
15575
16377
|
import {
|
|
15576
16378
|
createBoard,
|
|
15577
16379
|
getBoard,
|
|
16380
|
+
getKanbanDir,
|
|
15578
16381
|
listBoards,
|
|
15579
16382
|
removeBoard,
|
|
15580
16383
|
syncBoardFromTaskGraph,
|
|
16384
|
+
touchKanbanPresence,
|
|
15581
16385
|
updateBoard
|
|
15582
16386
|
} from "@wrongstack/kanban";
|
|
15583
16387
|
var SESSION_BOARD_TAG = "session-work";
|
|
@@ -15590,12 +16394,17 @@ var SESSION_KANBAN_COLUMNS = [
|
|
|
15590
16394
|
];
|
|
15591
16395
|
var boardQueue = /* @__PURE__ */ new Map();
|
|
15592
16396
|
var boardEnsures = /* @__PURE__ */ new Map();
|
|
16397
|
+
var pendingMirrors = /* @__PURE__ */ new Map();
|
|
16398
|
+
var activeMirrors = /* @__PURE__ */ new Set();
|
|
15593
16399
|
var bindings = /* @__PURE__ */ new WeakMap();
|
|
15594
16400
|
var suppressedTodoMirrors = /* @__PURE__ */ new WeakSet();
|
|
15595
16401
|
var activeSessionBoards = /* @__PURE__ */ new Map();
|
|
15596
16402
|
function boardKey(projectRoot, sessionId) {
|
|
15597
16403
|
return `${projectRoot}\0${sessionId}`;
|
|
15598
16404
|
}
|
|
16405
|
+
function mirrorKey(projectRoot, sessionId, sourceSystem) {
|
|
16406
|
+
return `${boardKey(projectRoot, sessionId)}\0${sourceSystem}`;
|
|
16407
|
+
}
|
|
15599
16408
|
function sessionTag(sessionId) {
|
|
15600
16409
|
return `session:${sessionId}`;
|
|
15601
16410
|
}
|
|
@@ -15731,6 +16540,43 @@ async function projectGraph(projectRoot, sessionId, graph, sourceSystem) {
|
|
|
15731
16540
|
return result?.board ?? null;
|
|
15732
16541
|
});
|
|
15733
16542
|
}
|
|
16543
|
+
function queueLatestMirror(projectRoot, sessionId, graph, sourceSystem) {
|
|
16544
|
+
if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return;
|
|
16545
|
+
const key = mirrorKey(projectRoot, sessionId, sourceSystem);
|
|
16546
|
+
pendingMirrors.set(key, { projectRoot, sessionId, graph, sourceSystem });
|
|
16547
|
+
if (activeMirrors.has(key)) return;
|
|
16548
|
+
activeMirrors.add(key);
|
|
16549
|
+
void (async () => {
|
|
16550
|
+
try {
|
|
16551
|
+
for (; ; ) {
|
|
16552
|
+
const pending2 = pendingMirrors.get(key);
|
|
16553
|
+
if (!pending2) break;
|
|
16554
|
+
pendingMirrors.delete(key);
|
|
16555
|
+
try {
|
|
16556
|
+
await projectGraph(
|
|
16557
|
+
pending2.projectRoot,
|
|
16558
|
+
pending2.sessionId,
|
|
16559
|
+
pending2.graph,
|
|
16560
|
+
pending2.sourceSystem
|
|
16561
|
+
);
|
|
16562
|
+
} catch {
|
|
16563
|
+
}
|
|
16564
|
+
}
|
|
16565
|
+
} finally {
|
|
16566
|
+
activeMirrors.delete(key);
|
|
16567
|
+
const pending2 = pendingMirrors.get(key);
|
|
16568
|
+
if (pending2) {
|
|
16569
|
+
pendingMirrors.delete(key);
|
|
16570
|
+
queueLatestMirror(
|
|
16571
|
+
pending2.projectRoot,
|
|
16572
|
+
pending2.sessionId,
|
|
16573
|
+
pending2.graph,
|
|
16574
|
+
pending2.sourceSystem
|
|
16575
|
+
);
|
|
16576
|
+
}
|
|
16577
|
+
}
|
|
16578
|
+
})();
|
|
16579
|
+
}
|
|
15734
16580
|
function todoListToSerializedGraph(todos, sessionId) {
|
|
15735
16581
|
const nodes = todos.map((todo, index) => ({
|
|
15736
16582
|
id: todo.id,
|
|
@@ -15844,14 +16690,65 @@ function fireAndForget(work) {
|
|
|
15844
16690
|
void work.catch(() => {
|
|
15845
16691
|
});
|
|
15846
16692
|
}
|
|
16693
|
+
function broadcastTodoUpdate(context, todos) {
|
|
16694
|
+
const sessionId = context.session?.id ?? "";
|
|
16695
|
+
if (!context.agentId || !sessionId) return;
|
|
16696
|
+
const projectDir = resolveWstackPaths3({ projectRoot: context.projectRoot }).projectDir;
|
|
16697
|
+
const mailbox = new GlobalMailbox(projectDir);
|
|
16698
|
+
void mailbox.send({
|
|
16699
|
+
from: context.agentId,
|
|
16700
|
+
to: "*",
|
|
16701
|
+
type: "status",
|
|
16702
|
+
subject: `Kanban todo list updated (${todos.length} item${todos.length === 1 ? "" : "s"})`,
|
|
16703
|
+
body: JSON.stringify({
|
|
16704
|
+
kind: "kanban.todos.updated",
|
|
16705
|
+
sessionId,
|
|
16706
|
+
revision: context.state.revision,
|
|
16707
|
+
todos
|
|
16708
|
+
}),
|
|
16709
|
+
priority: "normal",
|
|
16710
|
+
senderSessionId: sessionId,
|
|
16711
|
+
ttlMs: 6 * 60 * 60 * 1e3
|
|
16712
|
+
}).catch(() => {
|
|
16713
|
+
});
|
|
16714
|
+
}
|
|
16715
|
+
function notifyTodoUpdate(context, todos) {
|
|
16716
|
+
const summary = todos.length ? todos.map((todo) => `- [${todo.status}] ${todo.content} (${todo.id})`).join("\n") : "- No active todos remain.";
|
|
16717
|
+
const text = `[KANBAN TODO UPDATE]
|
|
16718
|
+
Another Kanban agent reassessed the shared board. The canonical todo list is now:
|
|
16719
|
+
${summary}
|
|
16720
|
+
Reassess your current plan before continuing; do not rely on the initial todo snapshot.`;
|
|
16721
|
+
const state = context.state;
|
|
16722
|
+
if (typeof state.appendBlockToLastUserMessage === "function") {
|
|
16723
|
+
if (state.appendBlockToLastUserMessage({ type: "text", text })) return;
|
|
16724
|
+
}
|
|
16725
|
+
if (typeof state.appendMessage === "function") {
|
|
16726
|
+
state.appendMessage({ role: "user", content: [{ type: "text", text }] });
|
|
16727
|
+
}
|
|
16728
|
+
}
|
|
15847
16729
|
function mirrorSessionTodosToKanban(projectRoot, todos, sessionId) {
|
|
15848
|
-
|
|
16730
|
+
queueLatestMirror(
|
|
16731
|
+
projectRoot,
|
|
16732
|
+
sessionId,
|
|
16733
|
+
todoListToSerializedGraph(todos, sessionId),
|
|
16734
|
+
"session-todo"
|
|
16735
|
+
);
|
|
15849
16736
|
}
|
|
15850
16737
|
function mirrorSessionTasksToKanban(projectRoot, tasks, sessionId) {
|
|
15851
|
-
|
|
16738
|
+
queueLatestMirror(
|
|
16739
|
+
projectRoot,
|
|
16740
|
+
sessionId,
|
|
16741
|
+
taskFileToSerializedGraph(tasks, sessionId),
|
|
16742
|
+
"session-task"
|
|
16743
|
+
);
|
|
15852
16744
|
}
|
|
15853
16745
|
function mirrorSessionPlanToKanban(projectRoot, items, sessionId) {
|
|
15854
|
-
|
|
16746
|
+
queueLatestMirror(
|
|
16747
|
+
projectRoot,
|
|
16748
|
+
sessionId,
|
|
16749
|
+
planFileToSerializedGraph(items, sessionId),
|
|
16750
|
+
"session-plan"
|
|
16751
|
+
);
|
|
15855
16752
|
}
|
|
15856
16753
|
function attachSessionKanbanMirror(context) {
|
|
15857
16754
|
const existing = bindings.get(context);
|
|
@@ -15875,6 +16772,10 @@ function attachSessionKanbanMirror(context) {
|
|
|
15875
16772
|
let watcher = null;
|
|
15876
16773
|
let watchedDir = "";
|
|
15877
16774
|
let timer = null;
|
|
16775
|
+
let boardWatcher = null;
|
|
16776
|
+
let watchedBoardId = "";
|
|
16777
|
+
let boardTimer = null;
|
|
16778
|
+
let presenceTimer = null;
|
|
15878
16779
|
const sessionId = () => context.session?.id ?? "";
|
|
15879
16780
|
const refreshFiles = async () => {
|
|
15880
16781
|
const id = sessionId();
|
|
@@ -15890,6 +16791,48 @@ function attachSessionKanbanMirror(context) {
|
|
|
15890
16791
|
if (tasks) await projectSessionTasksToKanban(context.projectRoot, tasks.tasks, id);
|
|
15891
16792
|
}
|
|
15892
16793
|
};
|
|
16794
|
+
const refreshBoard = async () => {
|
|
16795
|
+
if (!watchedBoardId) return;
|
|
16796
|
+
const board = await getBoard(context.projectRoot, watchedBoardId);
|
|
16797
|
+
if (board) applySessionKanbanBoardToTodos(context, board);
|
|
16798
|
+
};
|
|
16799
|
+
const configureBoardWatcher = async () => {
|
|
16800
|
+
const id = sessionId();
|
|
16801
|
+
const board = id ? await ensureSessionKanbanBoard(context.projectRoot, id) : null;
|
|
16802
|
+
if (!board || board.id === watchedBoardId) return;
|
|
16803
|
+
boardWatcher?.close();
|
|
16804
|
+
boardWatcher = null;
|
|
16805
|
+
watchedBoardId = board.id;
|
|
16806
|
+
try {
|
|
16807
|
+
const boardFileName = `${board.id}.json`;
|
|
16808
|
+
boardWatcher = watch(
|
|
16809
|
+
getKanbanDir(context.projectRoot),
|
|
16810
|
+
{ persistent: false },
|
|
16811
|
+
(_event, filename) => {
|
|
16812
|
+
if (filename?.toString() !== boardFileName) return;
|
|
16813
|
+
if (boardTimer) clearTimeout(boardTimer);
|
|
16814
|
+
boardTimer = setTimeout(() => fireAndForget(refreshBoard()), 60);
|
|
16815
|
+
}
|
|
16816
|
+
);
|
|
16817
|
+
const touchPresence = () => touchKanbanPresence(context.projectRoot, board.id, {
|
|
16818
|
+
sessionId: id,
|
|
16819
|
+
agentId: context.agentId,
|
|
16820
|
+
agentName: context.agentName
|
|
16821
|
+
});
|
|
16822
|
+
fireAndForget(touchPresence());
|
|
16823
|
+
if (presenceTimer) clearInterval(presenceTimer);
|
|
16824
|
+
presenceTimer = setInterval(() => fireAndForget(touchPresence()), 6e4);
|
|
16825
|
+
presenceTimer.unref?.();
|
|
16826
|
+
boardWatcher.on("error", () => {
|
|
16827
|
+
boardWatcher?.close();
|
|
16828
|
+
boardWatcher = null;
|
|
16829
|
+
watchedBoardId = "";
|
|
16830
|
+
});
|
|
16831
|
+
} catch {
|
|
16832
|
+
boardWatcher = null;
|
|
16833
|
+
watchedBoardId = "";
|
|
16834
|
+
}
|
|
16835
|
+
};
|
|
15893
16836
|
const configureWatcher = () => {
|
|
15894
16837
|
const planPath = context.meta["plan.path"];
|
|
15895
16838
|
const taskPath = context.meta["task.path"];
|
|
@@ -15903,8 +16846,8 @@ function attachSessionKanbanMirror(context) {
|
|
|
15903
16846
|
const name = filename?.toString();
|
|
15904
16847
|
const currentPlanPath = context.meta["plan.path"];
|
|
15905
16848
|
const currentTaskPath = context.meta["task.path"];
|
|
15906
|
-
const planName = typeof currentPlanPath === "string" ?
|
|
15907
|
-
const taskName = typeof currentTaskPath === "string" ?
|
|
16849
|
+
const planName = typeof currentPlanPath === "string" ? basename9(currentPlanPath) : "";
|
|
16850
|
+
const taskName = typeof currentTaskPath === "string" ? basename9(currentTaskPath) : "";
|
|
15908
16851
|
if (name && name !== planName && name !== taskName) return;
|
|
15909
16852
|
if (timer) clearTimeout(timer);
|
|
15910
16853
|
timer = setTimeout(() => fireAndForget(refreshFiles()), 60);
|
|
@@ -15928,14 +16871,19 @@ function attachSessionKanbanMirror(context) {
|
|
|
15928
16871
|
syncActiveSessionRegistration();
|
|
15929
16872
|
configureWatcher();
|
|
15930
16873
|
fireAndForget(ensureSessionKanbanBoard(context.projectRoot, sessionId()));
|
|
16874
|
+
fireAndForget(configureBoardWatcher());
|
|
15931
16875
|
fireAndForget(refreshFiles());
|
|
15932
16876
|
}
|
|
15933
16877
|
});
|
|
15934
16878
|
configureWatcher();
|
|
16879
|
+
fireAndForget(configureBoardWatcher());
|
|
15935
16880
|
const detach = () => {
|
|
15936
16881
|
unsubscribe();
|
|
15937
16882
|
if (timer) clearTimeout(timer);
|
|
16883
|
+
if (boardTimer) clearTimeout(boardTimer);
|
|
16884
|
+
if (presenceTimer) clearInterval(presenceTimer);
|
|
15938
16885
|
watcher?.close();
|
|
16886
|
+
boardWatcher?.close();
|
|
15939
16887
|
bindings.delete(context);
|
|
15940
16888
|
if (attachedProjectRoot && registeredSessionId) {
|
|
15941
16889
|
releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
|
|
@@ -15974,6 +16922,51 @@ function sourceStatus(task) {
|
|
|
15974
16922
|
if (task.status === "failed") return "failed";
|
|
15975
16923
|
return "pending";
|
|
15976
16924
|
}
|
|
16925
|
+
function todoStatus(task) {
|
|
16926
|
+
const status = sourceStatus(task);
|
|
16927
|
+
if (status === "completed") return "completed";
|
|
16928
|
+
if (status === "in_progress" || status === "review") return "in_progress";
|
|
16929
|
+
return "pending";
|
|
16930
|
+
}
|
|
16931
|
+
function sessionTodoFromTask(task) {
|
|
16932
|
+
return {
|
|
16933
|
+
id: task.origin?.taskId ?? task.id,
|
|
16934
|
+
content: task.title,
|
|
16935
|
+
status: todoStatus(task),
|
|
16936
|
+
...task.description ? { activeForm: task.description } : {}
|
|
16937
|
+
};
|
|
16938
|
+
}
|
|
16939
|
+
function sameTodos(left, right) {
|
|
16940
|
+
return left.length === right.length && left.every((todo, index) => {
|
|
16941
|
+
const candidate = right[index];
|
|
16942
|
+
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;
|
|
16943
|
+
});
|
|
16944
|
+
}
|
|
16945
|
+
function applySessionKanbanBoardToTodos(context, board) {
|
|
16946
|
+
const sessionId = context.session?.id ?? "";
|
|
16947
|
+
if (!sessionId || sessionIdFromTags(board.tags) !== sessionId || !isOwnedSessionBoard(board.tags)) {
|
|
16948
|
+
return [...context.todos];
|
|
16949
|
+
}
|
|
16950
|
+
const projectedTodos = board.tasks.filter(
|
|
16951
|
+
(task) => task.status !== "archived" && (!task.origin || task.origin.system === "session-todo" || (task.origin.graphId ?? "").startsWith("todo:"))
|
|
16952
|
+
).sort((left, right) => {
|
|
16953
|
+
const leftColumn = board.columns.find((column) => column.id === left.columnId)?.order ?? 0;
|
|
16954
|
+
const rightColumn = board.columns.find((column) => column.id === right.columnId)?.order ?? 0;
|
|
16955
|
+
return leftColumn - rightColumn || left.order - right.order || left.createdAt.localeCompare(right.createdAt);
|
|
16956
|
+
}).map(sessionTodoFromTask);
|
|
16957
|
+
const allCompleted = projectedTodos.length > 0 && projectedTodos.every((todo) => todo.status === "completed");
|
|
16958
|
+
const effectiveTodos = allCompleted ? [] : projectedTodos;
|
|
16959
|
+
if (sameTodos(context.todos, effectiveTodos)) return [...context.todos];
|
|
16960
|
+
suppressedTodoMirrors.add(context);
|
|
16961
|
+
try {
|
|
16962
|
+
context.state.replaceTodos(projectedTodos);
|
|
16963
|
+
} finally {
|
|
16964
|
+
suppressedTodoMirrors.delete(context);
|
|
16965
|
+
}
|
|
16966
|
+
notifyTodoUpdate(context, context.todos);
|
|
16967
|
+
broadcastTodoUpdate(context, context.todos);
|
|
16968
|
+
return [...context.todos];
|
|
16969
|
+
}
|
|
15977
16970
|
async function applySessionKanbanTaskToSource(context, task, options = {}) {
|
|
15978
16971
|
const originId = task.origin?.taskId;
|
|
15979
16972
|
const graphId = task.origin?.graphId ?? "";
|
|
@@ -16037,8 +17030,8 @@ async function applySessionKanbanTaskToSource(context, task, options = {}) {
|
|
|
16037
17030
|
var kanbanTool = {
|
|
16038
17031
|
name: "kanban",
|
|
16039
17032
|
category: "Project",
|
|
16040
|
-
description: "Manage project-scoped multi-kanban boards stored under .wrongstack/kanbans.
|
|
16041
|
-
usageHint: "Use this for durable project kanban state.
|
|
17033
|
+
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.",
|
|
17034
|
+
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.",
|
|
16042
17035
|
permission: "confirm",
|
|
16043
17036
|
mutating: true,
|
|
16044
17037
|
capabilities: ["fs.write"],
|
|
@@ -16075,6 +17068,7 @@ var kanbanTool = {
|
|
|
16075
17068
|
"transfer_task",
|
|
16076
17069
|
"get_task",
|
|
16077
17070
|
"update_task",
|
|
17071
|
+
"transition_task",
|
|
16078
17072
|
"move_task",
|
|
16079
17073
|
"delete_task",
|
|
16080
17074
|
"set_chain",
|
|
@@ -16105,6 +17099,7 @@ var kanbanTool = {
|
|
|
16105
17099
|
targetColumnId: { type: "string" },
|
|
16106
17100
|
title: { type: "string" },
|
|
16107
17101
|
description: { type: "string" },
|
|
17102
|
+
dueDate: { type: "string" },
|
|
16108
17103
|
tags: { type: "array", items: { type: "string" } },
|
|
16109
17104
|
labels: { type: "array", items: { type: "string" } },
|
|
16110
17105
|
priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
|
|
@@ -16151,6 +17146,18 @@ var kanbanTool = {
|
|
|
16151
17146
|
type: "string",
|
|
16152
17147
|
enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
|
|
16153
17148
|
},
|
|
17149
|
+
lifecycleStage: {
|
|
17150
|
+
type: "string",
|
|
17151
|
+
enum: ["backlog", "todo", "running", "review", "done"]
|
|
17152
|
+
},
|
|
17153
|
+
transitionAction: { type: "string" },
|
|
17154
|
+
transitionComment: { type: "string" },
|
|
17155
|
+
attachmentUrl: { type: "string" },
|
|
17156
|
+
attachmentTitle: { type: "string" },
|
|
17157
|
+
attachmentType: {
|
|
17158
|
+
type: "string",
|
|
17159
|
+
enum: ["issue", "pr", "doc", "commit", "design", "file", "url", "other"]
|
|
17160
|
+
},
|
|
16154
17161
|
releaseStatus: { type: "string", enum: ["pending", "ready", "blocked"] },
|
|
16155
17162
|
releaseReason: { type: "string" },
|
|
16156
17163
|
clearAssignee: { type: "boolean" },
|
|
@@ -16221,591 +17228,649 @@ var kanbanTool = {
|
|
|
16221
17228
|
async execute(input, ctx) {
|
|
16222
17229
|
const projectRoot = ctx.projectRoot;
|
|
16223
17230
|
if (!projectRoot) return fail("No project root is available.");
|
|
17231
|
+
const withPresence = async (result) => {
|
|
17232
|
+
const boardId = result.board?.id ?? input.boardId;
|
|
17233
|
+
if (!result.ok || !boardId || !ctx.session?.id || !ctx.agentId) return result;
|
|
17234
|
+
try {
|
|
17235
|
+
const board = await touchKanbanPresence2(projectRoot, boardId, {
|
|
17236
|
+
sessionId: ctx.session.id,
|
|
17237
|
+
agentId: ctx.agentId,
|
|
17238
|
+
agentName: ctx.agentName,
|
|
17239
|
+
taskId: input.taskId ?? result.task?.id,
|
|
17240
|
+
runTaskId: input.runTaskId
|
|
17241
|
+
});
|
|
17242
|
+
return board ? { ...result, board } : result;
|
|
17243
|
+
} catch {
|
|
17244
|
+
return result;
|
|
17245
|
+
}
|
|
17246
|
+
};
|
|
16224
17247
|
try {
|
|
16225
|
-
|
|
16226
|
-
|
|
16227
|
-
|
|
16228
|
-
|
|
16229
|
-
|
|
16230
|
-
case "get_board": {
|
|
16231
|
-
const board = await requireBoard(projectRoot, input.boardId);
|
|
16232
|
-
return board ? okBoard(board) : fail("Board not found.");
|
|
16233
|
-
}
|
|
16234
|
-
case "create_board": {
|
|
16235
|
-
if (!input.title) return fail("create_board requires title.");
|
|
16236
|
-
const board = await createBoard2(projectRoot, {
|
|
16237
|
-
title: input.title,
|
|
16238
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
16239
|
-
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
16240
|
-
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
|
|
16241
|
-
});
|
|
16242
|
-
return { ok: true, message: `Board created: ${board.title}`, board };
|
|
16243
|
-
}
|
|
16244
|
-
case "update_board": {
|
|
16245
|
-
if (!input.boardId) return fail("update_board requires boardId.");
|
|
16246
|
-
const board = await updateBoard2(projectRoot, input.boardId, {
|
|
16247
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
16248
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
16249
|
-
...input.tags !== void 0 ? { tags: input.tags } : {}
|
|
16250
|
-
});
|
|
16251
|
-
return board ? okBoard(board, "Board updated.") : fail("Board not found.");
|
|
16252
|
-
}
|
|
16253
|
-
case "duplicate_board": {
|
|
16254
|
-
if (!input.boardId) return fail("duplicate_board requires boardId.");
|
|
16255
|
-
const board = await duplicateBoard(projectRoot, input.boardId, {
|
|
16256
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
16257
|
-
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
16258
|
-
...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
|
|
16259
|
-
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
16260
|
-
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
|
|
16261
|
-
});
|
|
16262
|
-
return board ? okBoard(board, "Board duplicated.") : fail("Board not found.");
|
|
16263
|
-
}
|
|
16264
|
-
case "delete_board": {
|
|
16265
|
-
if (!input.boardId) return fail("delete_board requires boardId.");
|
|
16266
|
-
const removed = await removeBoard2(projectRoot, input.boardId);
|
|
16267
|
-
return { ok: removed, message: removed ? "Board deleted." : "Board not found." };
|
|
16268
|
-
}
|
|
16269
|
-
case "generate_board": {
|
|
16270
|
-
if (!input.description) return fail("generate_board requires description.");
|
|
16271
|
-
const boardInput = generateBoardFromDescription({
|
|
16272
|
-
description: input.description,
|
|
16273
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
16274
|
-
...input.context !== void 0 ? { context: input.context } : {},
|
|
16275
|
-
...input.columns !== void 0 ? { columns: input.columns } : {}
|
|
16276
|
-
});
|
|
16277
|
-
const board = await createBoard2(projectRoot, boardInput);
|
|
16278
|
-
for (const taskInput2 of parseLinesIntoTasks(
|
|
16279
|
-
input.description,
|
|
16280
|
-
board.columns[0]?.id ?? "backlog"
|
|
16281
|
-
)) {
|
|
16282
|
-
await addTask(projectRoot, board.id, taskInput2);
|
|
17248
|
+
const result = await (async () => {
|
|
17249
|
+
switch (input.action) {
|
|
17250
|
+
case "list_boards": {
|
|
17251
|
+
const boards = await listBoards2(projectRoot);
|
|
17252
|
+
return { ok: true, message: `${boards.length} board(s).`, boards };
|
|
16283
17253
|
}
|
|
16284
|
-
|
|
16285
|
-
|
|
16286
|
-
|
|
16287
|
-
const board = await requireBoard(projectRoot, input.boardId);
|
|
16288
|
-
if (!board) return fail("Board not found.");
|
|
16289
|
-
return {
|
|
16290
|
-
ok: true,
|
|
16291
|
-
message: "Board exported.",
|
|
16292
|
-
board,
|
|
16293
|
-
markdown: exportBoardAsMarkdown(board)
|
|
16294
|
-
};
|
|
16295
|
-
}
|
|
16296
|
-
case "export_task_graph": {
|
|
16297
|
-
if (!input.boardId) return fail("export_task_graph requires boardId.");
|
|
16298
|
-
const exported = await exportBoardToTaskGraph(projectRoot, input.boardId, {
|
|
16299
|
-
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
16300
|
-
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
16301
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
16302
|
-
...input.preserveOriginTaskIds !== void 0 ? { preserveOriginTaskIds: input.preserveOriginTaskIds } : {},
|
|
16303
|
-
...input.includeArchived !== void 0 ? { includeArchived: input.includeArchived } : {}
|
|
16304
|
-
});
|
|
16305
|
-
if (!exported) return fail("Board not found.");
|
|
16306
|
-
return {
|
|
16307
|
-
ok: true,
|
|
16308
|
-
message: `Task graph exported with ${exported.graph.nodes.size} node(s).`,
|
|
16309
|
-
board: exported.board,
|
|
16310
|
-
taskGraph: serializeTaskGraph(exported.graph)
|
|
16311
|
-
};
|
|
16312
|
-
}
|
|
16313
|
-
case "sync_task_graph": {
|
|
16314
|
-
if (!input.boardId || !input.taskGraph) {
|
|
16315
|
-
return fail("sync_task_graph requires boardId and taskGraph.");
|
|
17254
|
+
case "get_board": {
|
|
17255
|
+
const board = await requireBoard(projectRoot, input.boardId);
|
|
17256
|
+
return board ? okBoard(board) : fail("Board not found.");
|
|
16316
17257
|
}
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
16321
|
-
|
|
16322
|
-
|
|
16323
|
-
|
|
16324
|
-
|
|
16325
|
-
|
|
16326
|
-
|
|
16327
|
-
|
|
16328
|
-
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
|
|
16334
|
-
|
|
16335
|
-
|
|
16336
|
-
|
|
16337
|
-
|
|
16338
|
-
|
|
16339
|
-
|
|
16340
|
-
|
|
16341
|
-
|
|
16342
|
-
|
|
16343
|
-
|
|
16344
|
-
|
|
16345
|
-
|
|
16346
|
-
}
|
|
16347
|
-
|
|
16348
|
-
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
}
|
|
16352
|
-
|
|
16353
|
-
|
|
16354
|
-
|
|
16355
|
-
|
|
16356
|
-
|
|
16357
|
-
|
|
16358
|
-
|
|
16359
|
-
|
|
16360
|
-
|
|
16361
|
-
|
|
16362
|
-
|
|
16363
|
-
|
|
16364
|
-
|
|
16365
|
-
|
|
17258
|
+
case "create_board": {
|
|
17259
|
+
if (!input.title) return fail("create_board requires title.");
|
|
17260
|
+
const board = await createBoard2(projectRoot, {
|
|
17261
|
+
title: input.title,
|
|
17262
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17263
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
17264
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
|
|
17265
|
+
});
|
|
17266
|
+
return { ok: true, message: `Board created: ${board.title}`, board };
|
|
17267
|
+
}
|
|
17268
|
+
case "update_board": {
|
|
17269
|
+
if (!input.boardId) return fail("update_board requires boardId.");
|
|
17270
|
+
const board = await updateBoard2(projectRoot, input.boardId, {
|
|
17271
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17272
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17273
|
+
...input.tags !== void 0 ? { tags: input.tags } : {}
|
|
17274
|
+
});
|
|
17275
|
+
return board ? okBoard(board, "Board updated.") : fail("Board not found.");
|
|
17276
|
+
}
|
|
17277
|
+
case "duplicate_board": {
|
|
17278
|
+
if (!input.boardId) return fail("duplicate_board requires boardId.");
|
|
17279
|
+
const board = await duplicateBoard(projectRoot, input.boardId, {
|
|
17280
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17281
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
17282
|
+
...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
|
|
17283
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
17284
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
|
|
17285
|
+
});
|
|
17286
|
+
return board ? okBoard(board, "Board duplicated.") : fail("Board not found.");
|
|
17287
|
+
}
|
|
17288
|
+
case "delete_board": {
|
|
17289
|
+
if (!input.boardId) return fail("delete_board requires boardId.");
|
|
17290
|
+
const removed = await removeBoard2(projectRoot, input.boardId);
|
|
17291
|
+
return { ok: removed, message: removed ? "Board deleted." : "Board not found." };
|
|
17292
|
+
}
|
|
17293
|
+
case "generate_board": {
|
|
17294
|
+
if (!input.description) return fail("generate_board requires description.");
|
|
17295
|
+
const boardInput = createBoardFromText({
|
|
17296
|
+
description: input.description,
|
|
17297
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17298
|
+
...input.context !== void 0 ? { context: input.context } : {},
|
|
17299
|
+
...input.columns !== void 0 ? { columns: input.columns } : {}
|
|
17300
|
+
});
|
|
17301
|
+
const board = await createBoard2(projectRoot, boardInput);
|
|
17302
|
+
for (const taskInput2 of parseLinesIntoTasks(
|
|
17303
|
+
input.description,
|
|
17304
|
+
board.columns[0]?.id ?? "backlog"
|
|
17305
|
+
)) {
|
|
17306
|
+
await addTask(projectRoot, board.id, taskInput2);
|
|
17307
|
+
}
|
|
17308
|
+
return okBoard(await getBoard2(projectRoot, board.id) ?? board, "Board generated.");
|
|
17309
|
+
}
|
|
17310
|
+
case "export_markdown": {
|
|
17311
|
+
const board = await requireBoard(projectRoot, input.boardId);
|
|
17312
|
+
if (!board) return fail("Board not found.");
|
|
17313
|
+
return {
|
|
17314
|
+
ok: true,
|
|
17315
|
+
message: "Board exported.",
|
|
17316
|
+
board,
|
|
17317
|
+
markdown: exportBoardAsMarkdown(board)
|
|
17318
|
+
};
|
|
17319
|
+
}
|
|
17320
|
+
case "export_task_graph": {
|
|
17321
|
+
if (!input.boardId) return fail("export_task_graph requires boardId.");
|
|
17322
|
+
const exported = await exportBoardToTaskGraph(projectRoot, input.boardId, {
|
|
17323
|
+
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
17324
|
+
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
17325
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17326
|
+
...input.preserveOriginTaskIds !== void 0 ? { preserveOriginTaskIds: input.preserveOriginTaskIds } : {},
|
|
17327
|
+
...input.includeArchived !== void 0 ? { includeArchived: input.includeArchived } : {}
|
|
17328
|
+
});
|
|
17329
|
+
if (!exported) return fail("Board not found.");
|
|
17330
|
+
return {
|
|
17331
|
+
ok: true,
|
|
17332
|
+
message: `Task graph exported with ${exported.graph.nodes.size} node(s).`,
|
|
17333
|
+
board: exported.board,
|
|
17334
|
+
taskGraph: serializeTaskGraph(exported.graph)
|
|
17335
|
+
};
|
|
17336
|
+
}
|
|
17337
|
+
case "sync_task_graph": {
|
|
17338
|
+
if (!input.boardId || !input.taskGraph) {
|
|
17339
|
+
return fail("sync_task_graph requires boardId and taskGraph.");
|
|
17340
|
+
}
|
|
17341
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
17342
|
+
const result2 = await syncBoardFromTaskGraph2(projectRoot, input.boardId, graph, {
|
|
17343
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17344
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17345
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
17346
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
17347
|
+
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
17348
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
17349
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
17350
|
+
...input.archiveMissingTasks !== void 0 ? { archiveMissingTasks: input.archiveMissingTasks } : {},
|
|
17351
|
+
...input.preserveManualDependencies !== void 0 ? { preserveManualDependencies: input.preserveManualDependencies } : {}
|
|
17352
|
+
});
|
|
17353
|
+
return result2 ? {
|
|
17354
|
+
ok: true,
|
|
17355
|
+
message: `Task graph synced: ${result2.createdTaskIds.length} created, ${result2.updatedTaskIds.length} updated, ${result2.archivedTaskIds.length} archived.`,
|
|
17356
|
+
board: result2.board
|
|
17357
|
+
} : fail("Board not found.");
|
|
17358
|
+
}
|
|
17359
|
+
case "create_from_graph": {
|
|
17360
|
+
if (!input.taskGraph) return fail("create_from_graph requires taskGraph.");
|
|
17361
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
17362
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
17363
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17364
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17365
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
17366
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
17367
|
+
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
17368
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
17369
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {}
|
|
17370
|
+
});
|
|
17371
|
+
return {
|
|
17372
|
+
ok: true,
|
|
17373
|
+
message: `Created board "${board.title}" from task graph with ${board.tasks.length} tasks.`,
|
|
17374
|
+
board
|
|
17375
|
+
};
|
|
17376
|
+
}
|
|
17377
|
+
case "import_session_tasks": {
|
|
17378
|
+
const taskPath = ctx.meta?.["task.path"];
|
|
17379
|
+
if (!taskPath) return fail("No session task file for this session.");
|
|
17380
|
+
const file = await loadTasks2(taskPath);
|
|
17381
|
+
if (!file || file.tasks.length === 0) return fail("No session tasks to import.");
|
|
17382
|
+
const sessionId = ctx.session?.id ?? file.sessionId ?? "session";
|
|
17383
|
+
const graph = deserializeTaskGraph2(taskFileToSerializedGraph(file.tasks, sessionId));
|
|
17384
|
+
const tags = ["session", `session:${sessionId}`];
|
|
17385
|
+
const existing = (await listBoards2(projectRoot)).find(
|
|
17386
|
+
(b) => b.tags?.includes(`session:${sessionId}`)
|
|
17387
|
+
);
|
|
17388
|
+
if (existing) {
|
|
17389
|
+
const result2 = await syncBoardFromTaskGraph2(projectRoot, existing.id, graph, {
|
|
17390
|
+
sourceSystem: "session",
|
|
17391
|
+
tags,
|
|
17392
|
+
archiveMissingTasks: true,
|
|
17393
|
+
includeCompletedTasks: true
|
|
17394
|
+
});
|
|
17395
|
+
return result2 ? {
|
|
17396
|
+
ok: true,
|
|
17397
|
+
message: `Synced ${file.tasks.length} session tasks into board "${result2.board.title}".`,
|
|
17398
|
+
board: result2.board
|
|
17399
|
+
} : fail("Session board vanished mid-sync.");
|
|
17400
|
+
}
|
|
17401
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
17402
|
+
title: `Session tasks (${sessionId.slice(0, 8)})`,
|
|
16366
17403
|
sourceSystem: "session",
|
|
16367
|
-
tags
|
|
16368
|
-
archiveMissingTasks: true,
|
|
16369
|
-
includeCompletedTasks: true
|
|
17404
|
+
tags
|
|
16370
17405
|
});
|
|
16371
|
-
return
|
|
17406
|
+
return {
|
|
16372
17407
|
ok: true,
|
|
16373
|
-
message: `
|
|
16374
|
-
board
|
|
16375
|
-
}
|
|
17408
|
+
message: `Imported ${file.tasks.length} session tasks into new board "${board.title}".`,
|
|
17409
|
+
board
|
|
17410
|
+
};
|
|
16376
17411
|
}
|
|
16377
|
-
|
|
16378
|
-
|
|
16379
|
-
|
|
16380
|
-
|
|
16381
|
-
|
|
16382
|
-
|
|
16383
|
-
|
|
16384
|
-
|
|
16385
|
-
|
|
16386
|
-
|
|
16387
|
-
|
|
16388
|
-
case "search_tasks": {
|
|
16389
|
-
const tasks = await searchKanban(projectRoot, {
|
|
16390
|
-
query: input.query,
|
|
16391
|
-
boardId: input.boardId,
|
|
16392
|
-
assignedAgent: input.agentId,
|
|
16393
|
-
status: input.status,
|
|
16394
|
-
priority: input.priority,
|
|
16395
|
-
label: input.labels?.[0],
|
|
16396
|
-
chainId: input.chainId
|
|
16397
|
-
});
|
|
16398
|
-
return { ok: true, message: `${tasks.length} task(s) matched.`, tasks };
|
|
16399
|
-
}
|
|
16400
|
-
case "ready_tasks": {
|
|
16401
|
-
const tasks = await listReadyTasks(projectRoot, {
|
|
16402
|
-
query: input.query,
|
|
16403
|
-
boardId: input.boardId,
|
|
16404
|
-
assignedAgent: input.agentId,
|
|
16405
|
-
priority: input.priority,
|
|
16406
|
-
label: input.labels?.[0],
|
|
16407
|
-
chainId: input.chainId,
|
|
16408
|
-
limit: input.limit
|
|
16409
|
-
});
|
|
16410
|
-
return { ok: true, message: `${tasks.length} ready task(s).`, tasks };
|
|
16411
|
-
}
|
|
16412
|
-
case "snapshot": {
|
|
16413
|
-
const snapshot = await getKanbanOrchestrationSnapshot(projectRoot, {
|
|
16414
|
-
query: input.query,
|
|
16415
|
-
boardId: input.boardId,
|
|
16416
|
-
assignedAgent: input.agentId,
|
|
16417
|
-
status: input.status,
|
|
16418
|
-
priority: input.priority,
|
|
16419
|
-
label: input.labels?.[0],
|
|
16420
|
-
chainId: input.chainId
|
|
16421
|
-
});
|
|
16422
|
-
return {
|
|
16423
|
-
ok: true,
|
|
16424
|
-
message: `${snapshot.ready.length} ready, ${snapshot.running.length} running, ${snapshot.blocked.length} blocked.`,
|
|
16425
|
-
snapshot
|
|
16426
|
-
};
|
|
16427
|
-
}
|
|
16428
|
-
case "add_column": {
|
|
16429
|
-
if (!input.boardId || !input.title) return fail("add_column requires boardId and title.");
|
|
16430
|
-
const result = await addColumn(projectRoot, input.boardId, {
|
|
16431
|
-
title: input.title,
|
|
16432
|
-
...input.description !== void 0 ? { description: input.description } : {}
|
|
16433
|
-
});
|
|
16434
|
-
return result ? okBoard(result.board, "Column added.") : fail("Board not found.");
|
|
16435
|
-
}
|
|
16436
|
-
case "update_column": {
|
|
16437
|
-
if (!input.boardId || !input.columnId)
|
|
16438
|
-
return fail("update_column requires boardId and columnId.");
|
|
16439
|
-
const board = await updateColumn(projectRoot, input.boardId, input.columnId, {
|
|
16440
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
16441
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
16442
|
-
...input.order !== void 0 ? { order: input.order } : {}
|
|
16443
|
-
});
|
|
16444
|
-
return board ? okBoard(board, "Column updated.") : fail("Column not found.");
|
|
16445
|
-
}
|
|
16446
|
-
case "delete_column": {
|
|
16447
|
-
if (!input.boardId || !input.columnId)
|
|
16448
|
-
return fail("delete_column requires boardId and columnId.");
|
|
16449
|
-
const board = await removeColumn(projectRoot, input.boardId, input.columnId, {
|
|
16450
|
-
moveTasksToColumnId: input.moveTasksToColumnId
|
|
16451
|
-
});
|
|
16452
|
-
return board ? okBoard(board, "Column deleted.") : fail("Column not found.");
|
|
16453
|
-
}
|
|
16454
|
-
case "add_task": {
|
|
16455
|
-
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
16456
|
-
const result = await addTask(projectRoot, input.boardId, taskInput(input));
|
|
16457
|
-
return result ? okTask(result.board, result.task, "Task added.") : fail("Board not found.");
|
|
16458
|
-
}
|
|
16459
|
-
case "split_task": {
|
|
16460
|
-
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
16461
|
-
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
17412
|
+
case "search_tasks": {
|
|
17413
|
+
const tasks = await searchKanban(projectRoot, {
|
|
17414
|
+
query: input.query,
|
|
17415
|
+
boardId: input.boardId,
|
|
17416
|
+
assignedAgent: input.agentId,
|
|
17417
|
+
status: input.status,
|
|
17418
|
+
priority: input.priority,
|
|
17419
|
+
label: input.labels?.[0],
|
|
17420
|
+
chainId: input.chainId
|
|
17421
|
+
});
|
|
17422
|
+
return { ok: true, message: `${tasks.length} task(s) matched.`, tasks };
|
|
16462
17423
|
}
|
|
16463
|
-
|
|
16464
|
-
|
|
16465
|
-
|
|
16466
|
-
|
|
16467
|
-
|
|
16468
|
-
|
|
16469
|
-
|
|
16470
|
-
|
|
16471
|
-
|
|
16472
|
-
|
|
16473
|
-
|
|
16474
|
-
return result ? {
|
|
16475
|
-
ok: true,
|
|
16476
|
-
message: `${result.children.length} child task(s) created.`,
|
|
16477
|
-
board: result.board,
|
|
16478
|
-
task: result.parent,
|
|
16479
|
-
children: result.children
|
|
16480
|
-
} : fail("Task not found.");
|
|
16481
|
-
}
|
|
16482
|
-
case "merge_tasks": {
|
|
16483
|
-
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
16484
|
-
return fail("merge_tasks requires boardId, taskIds, and title.");
|
|
17424
|
+
case "ready_tasks": {
|
|
17425
|
+
const tasks = await listReadyTasks(projectRoot, {
|
|
17426
|
+
query: input.query,
|
|
17427
|
+
boardId: input.boardId,
|
|
17428
|
+
assignedAgent: input.agentId,
|
|
17429
|
+
priority: input.priority,
|
|
17430
|
+
label: input.labels?.[0],
|
|
17431
|
+
chainId: input.chainId,
|
|
17432
|
+
limit: input.limit
|
|
17433
|
+
});
|
|
17434
|
+
return { ok: true, message: `${tasks.length} ready task(s).`, tasks };
|
|
16485
17435
|
}
|
|
16486
|
-
|
|
16487
|
-
|
|
16488
|
-
|
|
16489
|
-
|
|
16490
|
-
|
|
16491
|
-
|
|
16492
|
-
|
|
16493
|
-
|
|
16494
|
-
|
|
16495
|
-
|
|
16496
|
-
|
|
16497
|
-
|
|
16498
|
-
|
|
17436
|
+
case "snapshot": {
|
|
17437
|
+
const snapshot = await getKanbanOrchestrationSnapshot(projectRoot, {
|
|
17438
|
+
query: input.query,
|
|
17439
|
+
boardId: input.boardId,
|
|
17440
|
+
assignedAgent: input.agentId,
|
|
17441
|
+
status: input.status,
|
|
17442
|
+
priority: input.priority,
|
|
17443
|
+
label: input.labels?.[0],
|
|
17444
|
+
chainId: input.chainId
|
|
17445
|
+
});
|
|
17446
|
+
return {
|
|
17447
|
+
ok: true,
|
|
17448
|
+
message: `${snapshot.ready.length} ready, ${snapshot.running.length} running, ${snapshot.blocked.length} blocked.`,
|
|
17449
|
+
snapshot
|
|
17450
|
+
};
|
|
16499
17451
|
}
|
|
16500
|
-
|
|
16501
|
-
|
|
16502
|
-
input.boardId,
|
|
16503
|
-
|
|
16504
|
-
|
|
16505
|
-
|
|
16506
|
-
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
17452
|
+
case "add_column": {
|
|
17453
|
+
if (!input.boardId || !input.title) return fail("add_column requires boardId and title.");
|
|
17454
|
+
const result2 = await addColumn(projectRoot, input.boardId, {
|
|
17455
|
+
title: input.title,
|
|
17456
|
+
...input.description !== void 0 ? { description: input.description } : {}
|
|
17457
|
+
});
|
|
17458
|
+
return result2 ? okBoard(result2.board, "Column added.") : fail("Board not found.");
|
|
17459
|
+
}
|
|
17460
|
+
case "update_column": {
|
|
17461
|
+
if (!input.boardId || !input.columnId)
|
|
17462
|
+
return fail("update_column requires boardId and columnId.");
|
|
17463
|
+
const board = await updateColumn(projectRoot, input.boardId, input.columnId, {
|
|
17464
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17465
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17466
|
+
...input.order !== void 0 ? { order: input.order } : {}
|
|
17467
|
+
});
|
|
17468
|
+
return board ? okBoard(board, "Column updated.") : fail("Column not found.");
|
|
17469
|
+
}
|
|
17470
|
+
case "delete_column": {
|
|
17471
|
+
if (!input.boardId || !input.columnId)
|
|
17472
|
+
return fail("delete_column requires boardId and columnId.");
|
|
17473
|
+
const board = await removeColumn(projectRoot, input.boardId, input.columnId, {
|
|
17474
|
+
moveTasksToColumnId: input.moveTasksToColumnId
|
|
17475
|
+
});
|
|
17476
|
+
return board ? okBoard(board, "Column deleted.") : fail("Column not found.");
|
|
17477
|
+
}
|
|
17478
|
+
case "add_task": {
|
|
17479
|
+
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
17480
|
+
const result2 = await addTask(projectRoot, input.boardId, taskInput(input));
|
|
17481
|
+
return result2 ? okTask(result2.board, result2.task, "Task added.") : fail("Board not found.");
|
|
17482
|
+
}
|
|
17483
|
+
case "split_task": {
|
|
17484
|
+
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
17485
|
+
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
16510
17486
|
}
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
|
|
16514
|
-
|
|
16515
|
-
|
|
16516
|
-
|
|
17487
|
+
const result2 = await splitTask(projectRoot, input.boardId, input.taskId, {
|
|
17488
|
+
titles: input.childTitles,
|
|
17489
|
+
...input.targetColumnId !== void 0 ? { columnId: input.targetColumnId } : {},
|
|
17490
|
+
...input.inheritAssignment !== void 0 ? { inheritAssignment: input.inheritAssignment } : {},
|
|
17491
|
+
...input.inheritLabels !== void 0 ? { inheritLabels: input.inheritLabels } : {},
|
|
17492
|
+
...input.inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria: input.inheritSuccessCriteria } : {},
|
|
17493
|
+
...input.inheritGoalMetrics !== void 0 ? { inheritGoalMetrics: input.inheritGoalMetrics } : {},
|
|
17494
|
+
...input.inheritDependencies !== void 0 ? { inheritDependencies: input.inheritDependencies } : {},
|
|
17495
|
+
...input.chainChildren !== void 0 ? { chainChildren: input.chainChildren } : {},
|
|
17496
|
+
...input.rewireDependents !== void 0 ? { rewireDependents: input.rewireDependents } : {}
|
|
17497
|
+
});
|
|
17498
|
+
return result2 ? {
|
|
17499
|
+
ok: true,
|
|
17500
|
+
message: `${result2.children.length} child task(s) created.`,
|
|
17501
|
+
board: result2.board,
|
|
17502
|
+
task: result2.parent,
|
|
17503
|
+
children: result2.children
|
|
17504
|
+
} : fail("Task not found.");
|
|
16517
17505
|
}
|
|
16518
|
-
|
|
16519
|
-
|
|
16520
|
-
|
|
16521
|
-
|
|
16522
|
-
input.
|
|
16523
|
-
|
|
17506
|
+
case "merge_tasks": {
|
|
17507
|
+
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
17508
|
+
return fail("merge_tasks requires boardId, taskIds, and title.");
|
|
17509
|
+
}
|
|
17510
|
+
const result2 = await mergeTasks(projectRoot, input.boardId, {
|
|
17511
|
+
taskIds: input.taskIds,
|
|
17512
|
+
title: input.title,
|
|
17513
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16524
17514
|
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
16525
|
-
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
16526
17515
|
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
16527
|
-
...input.
|
|
17516
|
+
...input.closeSourceTasks !== void 0 ? { closeSourceTasks: input.closeSourceTasks } : {}
|
|
17517
|
+
});
|
|
17518
|
+
return result2 ? okTask(result2.board, result2.task, "Tasks merged.") : fail("Board or task not found.");
|
|
17519
|
+
}
|
|
17520
|
+
case "copy_task": {
|
|
17521
|
+
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
17522
|
+
return fail("copy_task requires boardId, taskId, and targetBoardId.");
|
|
16528
17523
|
}
|
|
16529
|
-
|
|
16530
|
-
|
|
16531
|
-
|
|
16532
|
-
|
|
16533
|
-
|
|
16534
|
-
|
|
16535
|
-
|
|
16536
|
-
|
|
16537
|
-
|
|
16538
|
-
|
|
16539
|
-
|
|
16540
|
-
|
|
16541
|
-
|
|
16542
|
-
input.boardId,
|
|
16543
|
-
input.taskId,
|
|
16544
|
-
taskPatch(input)
|
|
16545
|
-
);
|
|
16546
|
-
return board ? okBoard(board, "Task updated.") : fail("Task not found.");
|
|
16547
|
-
}
|
|
16548
|
-
case "move_task": {
|
|
16549
|
-
if (!input.boardId || !input.taskId || !input.targetColumnId) {
|
|
16550
|
-
return fail("move_task requires boardId, taskId, and targetColumnId.");
|
|
17524
|
+
const result2 = await copyTaskToBoard(
|
|
17525
|
+
projectRoot,
|
|
17526
|
+
input.boardId,
|
|
17527
|
+
input.taskId,
|
|
17528
|
+
input.targetBoardId,
|
|
17529
|
+
{
|
|
17530
|
+
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
17531
|
+
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
17532
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
17533
|
+
...input.preserveDependencies !== void 0 ? { preserveDependencies: input.preserveDependencies } : {}
|
|
17534
|
+
}
|
|
17535
|
+
);
|
|
17536
|
+
return result2 ? okTask(result2.targetBoard, result2.task, "Task copied to target board.") : fail("Board or task not found.");
|
|
16551
17537
|
}
|
|
16552
|
-
|
|
16553
|
-
|
|
16554
|
-
|
|
16555
|
-
|
|
16556
|
-
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
16560
|
-
|
|
16561
|
-
|
|
16562
|
-
|
|
16563
|
-
|
|
16564
|
-
|
|
16565
|
-
|
|
16566
|
-
|
|
16567
|
-
|
|
16568
|
-
|
|
16569
|
-
return fail("set_chain requires boardId and taskIds.");
|
|
17538
|
+
case "transfer_task": {
|
|
17539
|
+
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
17540
|
+
return fail("transfer_task requires boardId, taskId, and targetBoardId.");
|
|
17541
|
+
}
|
|
17542
|
+
const result2 = await transferTaskToBoard(
|
|
17543
|
+
projectRoot,
|
|
17544
|
+
input.boardId,
|
|
17545
|
+
input.taskId,
|
|
17546
|
+
input.targetBoardId,
|
|
17547
|
+
{
|
|
17548
|
+
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
17549
|
+
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
17550
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
17551
|
+
...input.preserveDependencies !== void 0 ? { preserveDependencies: input.preserveDependencies } : {}
|
|
17552
|
+
}
|
|
17553
|
+
);
|
|
17554
|
+
return result2 ? okTask(result2.targetBoard, result2.task, "Task transferred to target board.") : fail("Board or task not found.");
|
|
16570
17555
|
}
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
|
|
16574
|
-
|
|
16575
|
-
});
|
|
16576
|
-
return result ? {
|
|
16577
|
-
ok: true,
|
|
16578
|
-
message: `Chain set: ${result.chainId}`,
|
|
16579
|
-
board: result.board,
|
|
16580
|
-
chain: result.tasks
|
|
16581
|
-
} : fail("Board or task not found.");
|
|
16582
|
-
}
|
|
16583
|
-
case "get_chain": {
|
|
16584
|
-
if (!input.boardId || !(input.taskId || input.chainId)) {
|
|
16585
|
-
return fail("get_chain requires boardId and taskId or chainId.");
|
|
17556
|
+
case "get_task": {
|
|
17557
|
+
if (!input.boardId || !input.taskId) return fail("get_task requires boardId and taskId.");
|
|
17558
|
+
const task = await getTask(projectRoot, input.boardId, input.taskId);
|
|
17559
|
+
return task ? { ok: true, message: "Task loaded.", task } : fail("Task not found.");
|
|
16586
17560
|
}
|
|
16587
|
-
|
|
16588
|
-
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16594
|
-
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
} : fail("Chain not found.");
|
|
16598
|
-
}
|
|
16599
|
-
case "claim_task": {
|
|
16600
|
-
const result = await claimReadyTask(projectRoot, {
|
|
16601
|
-
...input.boardId !== void 0 ? { boardId: input.boardId } : {},
|
|
16602
|
-
...input.taskId !== void 0 ? { taskId: input.taskId } : {},
|
|
16603
|
-
...assignmentInput(input),
|
|
16604
|
-
status: input.assignmentStatus ?? "queued"
|
|
16605
|
-
});
|
|
16606
|
-
return result ? okTask(result.board, result.task, "Task claimed.") : fail("No ready kanban task matched the claim.");
|
|
16607
|
-
}
|
|
16608
|
-
case "release_task": {
|
|
16609
|
-
if (!input.boardId || !input.taskId) {
|
|
16610
|
-
return fail("release_task requires boardId and taskId.");
|
|
17561
|
+
case "update_task": {
|
|
17562
|
+
if (!input.boardId || !input.taskId)
|
|
17563
|
+
return fail("update_task requires boardId and taskId.");
|
|
17564
|
+
const board = await updateTask(
|
|
17565
|
+
projectRoot,
|
|
17566
|
+
input.boardId,
|
|
17567
|
+
input.taskId,
|
|
17568
|
+
taskPatch(input)
|
|
17569
|
+
);
|
|
17570
|
+
return board ? okBoard(board, "Task updated.") : fail("Task not found.");
|
|
16611
17571
|
}
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
|
|
16622
|
-
|
|
16623
|
-
|
|
16624
|
-
|
|
16625
|
-
|
|
16626
|
-
|
|
16627
|
-
|
|
16628
|
-
|
|
16629
|
-
|
|
16630
|
-
|
|
16631
|
-
|
|
16632
|
-
return fail("
|
|
16633
|
-
const assignmentStatus = input.assignmentStatus ?? (input.status === "completed" ? "completed" : input.error ? "failed" : void 0);
|
|
16634
|
-
const board = await updateTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
16635
|
-
...assignmentStatus !== void 0 ? { status: assignmentStatus } : {},
|
|
16636
|
-
...input.subagentId !== void 0 ? { subagentId: input.subagentId } : {},
|
|
16637
|
-
...input.runTaskId !== void 0 ? { runTaskId: input.runTaskId } : {},
|
|
16638
|
-
...input.lastResult !== void 0 ? { lastResult: input.lastResult } : {},
|
|
16639
|
-
...input.error !== void 0 ? { error: input.error } : {},
|
|
16640
|
-
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
16641
|
-
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
16642
|
-
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
16643
|
-
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
16644
|
-
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
16645
|
-
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
16646
|
-
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {}
|
|
16647
|
-
});
|
|
16648
|
-
return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
|
|
16649
|
-
}
|
|
16650
|
-
case "heartbeat_assignment": {
|
|
16651
|
-
if (!input.boardId || !input.taskId) {
|
|
16652
|
-
return fail("heartbeat_assignment requires boardId and taskId.");
|
|
17572
|
+
case "transition_task": {
|
|
17573
|
+
if (!input.boardId || !input.taskId || !input.lifecycleStage || !input.author || !input.transitionComment) {
|
|
17574
|
+
return fail(
|
|
17575
|
+
"transition_task requires boardId, taskId, lifecycleStage, author, and transitionComment."
|
|
17576
|
+
);
|
|
17577
|
+
}
|
|
17578
|
+
const result2 = await transitionTask(projectRoot, input.boardId, input.taskId, {
|
|
17579
|
+
to: input.lifecycleStage,
|
|
17580
|
+
actor: input.author,
|
|
17581
|
+
comment: input.transitionComment,
|
|
17582
|
+
...input.transitionAction !== void 0 ? { action: input.transitionAction } : {},
|
|
17583
|
+
...input.attachmentUrl !== void 0 ? {
|
|
17584
|
+
attachment: {
|
|
17585
|
+
url: input.attachmentUrl,
|
|
17586
|
+
type: input.attachmentType ?? "url",
|
|
17587
|
+
...input.attachmentTitle !== void 0 ? { title: input.attachmentTitle } : {}
|
|
17588
|
+
}
|
|
17589
|
+
} : {},
|
|
17590
|
+
patch: taskPatch(input)
|
|
17591
|
+
});
|
|
17592
|
+
return result2 ? okTask(result2.board, result2.task, `Task advanced to ${result2.transition.to}.`) : fail("Board or task not found.");
|
|
16653
17593
|
}
|
|
16654
|
-
|
|
16655
|
-
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
|
|
16659
|
-
|
|
16660
|
-
|
|
16661
|
-
|
|
16662
|
-
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
16666
|
-
input.recoveryPolicyRetryPolicyOverride !== void 0
|
|
16667
|
-
].some(Boolean);
|
|
16668
|
-
const result = await recoverStaleTaskAssignments(projectRoot, input.boardId, {
|
|
16669
|
-
...input.recoveryMode !== void 0 ? { mode: input.recoveryMode } : {},
|
|
16670
|
-
...input.recoveryNow !== void 0 ? { now: input.recoveryNow } : {},
|
|
16671
|
-
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
16672
|
-
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {},
|
|
16673
|
-
...policyFields ? {
|
|
16674
|
-
policy: {
|
|
16675
|
-
...input.recoveryPolicyFailOnCostCeiling !== void 0 ? { failWhenCostCeilingSet: input.recoveryPolicyFailOnCostCeiling } : {},
|
|
16676
|
-
...input.recoveryPolicyReleaseOnFailureKinds !== void 0 ? {
|
|
16677
|
-
releaseOnFailureKinds: input.recoveryPolicyReleaseOnFailureKinds
|
|
16678
|
-
} : {},
|
|
16679
|
-
...input.recoveryPolicyReleaseOnHeartbeatDue !== void 0 ? {
|
|
16680
|
-
releaseOnHeartbeatDue: input.recoveryPolicyReleaseOnHeartbeatDue
|
|
16681
|
-
} : {},
|
|
16682
|
-
...input.recoveryPolicyRetryPolicyOverride !== void 0 ? {
|
|
16683
|
-
retryPolicyOverride: input.recoveryPolicyRetryPolicyOverride
|
|
16684
|
-
} : {}
|
|
16685
|
-
}
|
|
16686
|
-
} : {}
|
|
16687
|
-
});
|
|
16688
|
-
return result ? {
|
|
16689
|
-
ok: true,
|
|
16690
|
-
message: `Recovered ${result.tasks.length} stale assignment(s).`,
|
|
16691
|
-
board: result.board,
|
|
16692
|
-
recoveredTasks: result.tasks
|
|
16693
|
-
} : { ok: true, message: "No stale assignment matched.", recoveredTasks: [] };
|
|
16694
|
-
}
|
|
16695
|
-
case "events": {
|
|
16696
|
-
if (!input.boardId) return fail("events requires boardId.");
|
|
16697
|
-
const eventList = await listKanbanEvents(projectRoot, input.boardId);
|
|
16698
|
-
return {
|
|
16699
|
-
ok: true,
|
|
16700
|
-
message: `${eventList.length} event(s).`,
|
|
16701
|
-
events: eventList
|
|
16702
|
-
};
|
|
16703
|
-
}
|
|
16704
|
-
case "queue_health": {
|
|
16705
|
-
const health = await getKanbanQueueHealth(projectRoot, {
|
|
16706
|
-
...input.boardId !== void 0 ? { boardId: input.boardId } : {}
|
|
16707
|
-
});
|
|
16708
|
-
return {
|
|
16709
|
-
ok: true,
|
|
16710
|
-
message: `Counts: ready=${health.counts.ready}, running=${health.counts.running}, stale=${health.staleAssignments.count}.`,
|
|
16711
|
-
queueHealth: health
|
|
16712
|
-
};
|
|
16713
|
-
}
|
|
16714
|
-
case "add_dependency": {
|
|
16715
|
-
if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
|
|
16716
|
-
return fail("add_dependency requires boardId, taskId, and dependencyTaskId.");
|
|
17594
|
+
case "move_task": {
|
|
17595
|
+
if (!input.boardId || !input.taskId || !input.targetColumnId) {
|
|
17596
|
+
return fail("move_task requires boardId, taskId, and targetColumnId.");
|
|
17597
|
+
}
|
|
17598
|
+
const board = await moveTask(
|
|
17599
|
+
projectRoot,
|
|
17600
|
+
input.boardId,
|
|
17601
|
+
input.taskId,
|
|
17602
|
+
input.targetColumnId,
|
|
17603
|
+
input.order
|
|
17604
|
+
);
|
|
17605
|
+
return board ? okBoard(board, "Task moved.") : fail("Move failed.");
|
|
16717
17606
|
}
|
|
16718
|
-
|
|
16719
|
-
|
|
16720
|
-
|
|
16721
|
-
input.taskId
|
|
16722
|
-
|
|
16723
|
-
);
|
|
16724
|
-
return board ? okBoard(board, "Dependency added.") : fail("Task not found.");
|
|
16725
|
-
}
|
|
16726
|
-
case "add_goal_metric": {
|
|
16727
|
-
if (!input.boardId || !input.taskId || !input.metricName) {
|
|
16728
|
-
return fail("add_goal_metric requires boardId, taskId, and metricName.");
|
|
17607
|
+
case "delete_task": {
|
|
17608
|
+
if (!input.boardId || !input.taskId)
|
|
17609
|
+
return fail("delete_task requires boardId and taskId.");
|
|
17610
|
+
const board = await removeTask(projectRoot, input.boardId, input.taskId);
|
|
17611
|
+
return board ? okBoard(board, "Task deleted.") : fail("Task not found.");
|
|
16729
17612
|
}
|
|
16730
|
-
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
|
|
16734
|
-
|
|
16735
|
-
|
|
16736
|
-
|
|
16737
|
-
|
|
16738
|
-
|
|
16739
|
-
|
|
16740
|
-
|
|
16741
|
-
|
|
16742
|
-
|
|
17613
|
+
case "set_chain": {
|
|
17614
|
+
if (!input.boardId || !input.taskIds?.length) {
|
|
17615
|
+
return fail("set_chain requires boardId and taskIds.");
|
|
17616
|
+
}
|
|
17617
|
+
const result2 = await setTaskChain(projectRoot, input.boardId, {
|
|
17618
|
+
taskIds: input.taskIds,
|
|
17619
|
+
...input.chainId !== void 0 ? { chainId: input.chainId } : {},
|
|
17620
|
+
...input.enforceDependencies !== void 0 ? { enforceDependencies: input.enforceDependencies } : {}
|
|
17621
|
+
});
|
|
17622
|
+
return result2 ? {
|
|
17623
|
+
ok: true,
|
|
17624
|
+
message: `Chain set: ${result2.chainId}`,
|
|
17625
|
+
board: result2.board,
|
|
17626
|
+
chain: result2.tasks
|
|
17627
|
+
} : fail("Board or task not found.");
|
|
16743
17628
|
}
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
|
|
16750
|
-
|
|
17629
|
+
case "get_chain": {
|
|
17630
|
+
if (!input.boardId || !(input.taskId || input.chainId)) {
|
|
17631
|
+
return fail("get_chain requires boardId and taskId or chainId.");
|
|
17632
|
+
}
|
|
17633
|
+
const result2 = await getTaskChain(
|
|
17634
|
+
projectRoot,
|
|
17635
|
+
input.boardId,
|
|
17636
|
+
input.taskId ?? input.chainId ?? ""
|
|
17637
|
+
);
|
|
17638
|
+
return result2 ? {
|
|
17639
|
+
ok: true,
|
|
17640
|
+
message: `Chain loaded: ${result2.chainId}`,
|
|
17641
|
+
board: result2.board,
|
|
17642
|
+
chain: result2.tasks
|
|
17643
|
+
} : fail("Chain not found.");
|
|
17644
|
+
}
|
|
17645
|
+
case "claim_task": {
|
|
17646
|
+
const result2 = await claimReadyTask(projectRoot, {
|
|
17647
|
+
...input.boardId !== void 0 ? { boardId: input.boardId } : {},
|
|
17648
|
+
...input.taskId !== void 0 ? { taskId: input.taskId } : {},
|
|
17649
|
+
...assignmentInput(input),
|
|
17650
|
+
status: input.assignmentStatus ?? "queued"
|
|
17651
|
+
});
|
|
17652
|
+
return result2 ? okTask(result2.board, result2.task, "Task claimed.") : fail("No ready kanban task matched the claim.");
|
|
17653
|
+
}
|
|
17654
|
+
case "release_task": {
|
|
17655
|
+
if (!input.boardId || !input.taskId) {
|
|
17656
|
+
return fail("release_task requires boardId and taskId.");
|
|
17657
|
+
}
|
|
17658
|
+
const board = await releaseTaskClaim(projectRoot, input.boardId, input.taskId, {
|
|
17659
|
+
...input.releaseStatus !== void 0 ? { status: input.releaseStatus } : {},
|
|
17660
|
+
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
17661
|
+
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {}
|
|
17662
|
+
});
|
|
17663
|
+
return board ? okBoard(board, "Task claim released.") : fail("Task not found.");
|
|
17664
|
+
}
|
|
17665
|
+
case "assign_task": {
|
|
17666
|
+
if (!input.boardId || !input.taskId)
|
|
17667
|
+
return fail("assign_task requires boardId and taskId.");
|
|
17668
|
+
const board = await assignTask(
|
|
17669
|
+
projectRoot,
|
|
17670
|
+
input.boardId,
|
|
17671
|
+
input.taskId,
|
|
17672
|
+
assignmentInput(input)
|
|
17673
|
+
);
|
|
17674
|
+
return board ? okBoard(board, "Task assigned.") : fail("Task not found.");
|
|
17675
|
+
}
|
|
17676
|
+
case "mark_assignment": {
|
|
17677
|
+
if (!input.boardId || !input.taskId)
|
|
17678
|
+
return fail("mark_assignment requires boardId and taskId.");
|
|
17679
|
+
const assignmentStatus = input.assignmentStatus ?? (input.status === "completed" ? "completed" : input.error ? "failed" : void 0);
|
|
17680
|
+
const board = await updateTaskAssignment(
|
|
17681
|
+
projectRoot,
|
|
17682
|
+
input.boardId,
|
|
17683
|
+
input.taskId,
|
|
17684
|
+
{
|
|
17685
|
+
...assignmentStatus !== void 0 ? { status: assignmentStatus } : {},
|
|
17686
|
+
...input.subagentId !== void 0 ? { subagentId: input.subagentId } : {},
|
|
17687
|
+
...input.runTaskId !== void 0 ? { runTaskId: input.runTaskId } : {},
|
|
17688
|
+
...input.lastResult !== void 0 ? { lastResult: input.lastResult } : {},
|
|
17689
|
+
...input.error !== void 0 ? { error: input.error } : {},
|
|
17690
|
+
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
17691
|
+
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
17692
|
+
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
17693
|
+
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
17694
|
+
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
17695
|
+
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
17696
|
+
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {}
|
|
17697
|
+
},
|
|
17698
|
+
// Ownership fence: when expectedLeaseId is supplied, the write is
|
|
17699
|
+
// applied only if the current assignment still holds this lease.
|
|
17700
|
+
// This prevents a recovered+reassigned stale worker's terminal
|
|
17701
|
+
// mark_assignment from overwriting the successor's state. The check
|
|
17702
|
+
// is atomic inside updateTaskAssignment's mutateBoard lock.
|
|
17703
|
+
input.expectedLeaseId !== void 0 ? { expectedLeaseId: input.expectedLeaseId } : {}
|
|
17704
|
+
);
|
|
17705
|
+
return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
|
|
17706
|
+
}
|
|
17707
|
+
case "heartbeat_assignment": {
|
|
17708
|
+
if (!input.boardId || !input.taskId) {
|
|
17709
|
+
return fail("heartbeat_assignment requires boardId and taskId.");
|
|
17710
|
+
}
|
|
17711
|
+
const board = await heartbeatTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
17712
|
+
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
17713
|
+
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
17714
|
+
// Ownership fence: when expectedLeaseId is supplied, the renewal
|
|
17715
|
+
// is applied only if the current assignment still holds this lease.
|
|
17716
|
+
// This prevents a recovered+reassigned stale worker's heartbeat
|
|
17717
|
+
// from renewing the successor's lease. The check is atomic inside
|
|
17718
|
+
// heartbeatTaskAssignment's mutateBoard lock.
|
|
17719
|
+
...input.expectedLeaseId !== void 0 ? { expectedLeaseId: input.expectedLeaseId } : {}
|
|
17720
|
+
});
|
|
17721
|
+
return board ? okBoard(board, "Assignment heartbeat updated.") : fail("Task assignment not found.");
|
|
17722
|
+
}
|
|
17723
|
+
case "recover_stale": {
|
|
17724
|
+
if (!input.boardId) return fail("recover_stale requires boardId.");
|
|
17725
|
+
const policyFields = [
|
|
17726
|
+
input.recoveryPolicyFailOnCostCeiling !== void 0,
|
|
17727
|
+
input.recoveryPolicyReleaseOnFailureKinds !== void 0,
|
|
17728
|
+
input.recoveryPolicyReleaseOnHeartbeatDue !== void 0,
|
|
17729
|
+
input.recoveryPolicyRetryPolicyOverride !== void 0
|
|
17730
|
+
].some(Boolean);
|
|
17731
|
+
const result2 = await recoverStaleTaskAssignments(projectRoot, input.boardId, {
|
|
17732
|
+
...input.recoveryMode !== void 0 ? { mode: input.recoveryMode } : {},
|
|
17733
|
+
...input.recoveryNow !== void 0 ? { now: input.recoveryNow } : {},
|
|
17734
|
+
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
17735
|
+
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {},
|
|
17736
|
+
...policyFields ? {
|
|
17737
|
+
policy: {
|
|
17738
|
+
...input.recoveryPolicyFailOnCostCeiling !== void 0 ? { failWhenCostCeilingSet: input.recoveryPolicyFailOnCostCeiling } : {},
|
|
17739
|
+
...input.recoveryPolicyReleaseOnFailureKinds !== void 0 ? {
|
|
17740
|
+
releaseOnFailureKinds: input.recoveryPolicyReleaseOnFailureKinds
|
|
17741
|
+
} : {},
|
|
17742
|
+
...input.recoveryPolicyReleaseOnHeartbeatDue !== void 0 ? {
|
|
17743
|
+
releaseOnHeartbeatDue: input.recoveryPolicyReleaseOnHeartbeatDue
|
|
17744
|
+
} : {},
|
|
17745
|
+
...input.recoveryPolicyRetryPolicyOverride !== void 0 ? {
|
|
17746
|
+
retryPolicyOverride: input.recoveryPolicyRetryPolicyOverride
|
|
17747
|
+
} : {}
|
|
17748
|
+
}
|
|
17749
|
+
} : {}
|
|
17750
|
+
});
|
|
17751
|
+
return result2 ? {
|
|
17752
|
+
ok: true,
|
|
17753
|
+
message: `Recovered ${result2.tasks.length} stale assignment(s).`,
|
|
17754
|
+
board: result2.board,
|
|
17755
|
+
recoveredTasks: result2.tasks
|
|
17756
|
+
} : { ok: true, message: "No stale assignment matched.", recoveredTasks: [] };
|
|
17757
|
+
}
|
|
17758
|
+
case "events": {
|
|
17759
|
+
if (!input.boardId) return fail("events requires boardId.");
|
|
17760
|
+
const eventList = await listKanbanEvents(projectRoot, input.boardId);
|
|
17761
|
+
return {
|
|
17762
|
+
ok: true,
|
|
17763
|
+
message: `${eventList.length} event(s).`,
|
|
17764
|
+
events: eventList
|
|
17765
|
+
};
|
|
17766
|
+
}
|
|
17767
|
+
case "queue_health": {
|
|
17768
|
+
const health = await getKanbanQueueHealth(projectRoot, {
|
|
17769
|
+
...input.boardId !== void 0 ? { boardId: input.boardId } : {}
|
|
17770
|
+
});
|
|
17771
|
+
return {
|
|
17772
|
+
ok: true,
|
|
17773
|
+
message: `Counts: ready=${health.counts.ready}, running=${health.counts.running}, stale=${health.staleAssignments.count}.`,
|
|
17774
|
+
queueHealth: health
|
|
17775
|
+
};
|
|
17776
|
+
}
|
|
17777
|
+
case "add_dependency": {
|
|
17778
|
+
if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
|
|
17779
|
+
return fail("add_dependency requires boardId, taskId, and dependencyTaskId.");
|
|
17780
|
+
}
|
|
17781
|
+
const board = await addDependency(
|
|
17782
|
+
projectRoot,
|
|
17783
|
+
input.boardId,
|
|
17784
|
+
input.taskId,
|
|
17785
|
+
input.dependencyTaskId
|
|
17786
|
+
);
|
|
17787
|
+
return board ? okBoard(board, "Dependency added.") : fail("Task not found.");
|
|
17788
|
+
}
|
|
17789
|
+
case "add_goal_metric": {
|
|
17790
|
+
if (!input.boardId || !input.taskId || !input.metricName) {
|
|
17791
|
+
return fail("add_goal_metric requires boardId, taskId, and metricName.");
|
|
17792
|
+
}
|
|
17793
|
+
const board = await addGoalMetricToTask(projectRoot, input.boardId, input.taskId, {
|
|
17794
|
+
name: input.metricName,
|
|
16751
17795
|
...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
|
|
16752
17796
|
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
16753
17797
|
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
16754
17798
|
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
16755
17799
|
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17800
|
+
});
|
|
17801
|
+
return board ? okBoard(board, "Goal metric added.") : fail("Task not found.");
|
|
17802
|
+
}
|
|
17803
|
+
case "update_goal_metric": {
|
|
17804
|
+
if (!input.boardId || !input.taskId || !input.metricId) {
|
|
17805
|
+
return fail("update_goal_metric requires boardId, taskId, and metricId.");
|
|
16756
17806
|
}
|
|
16757
|
-
|
|
16758
|
-
|
|
16759
|
-
|
|
16760
|
-
|
|
16761
|
-
|
|
16762
|
-
|
|
17807
|
+
const board = await updateGoalMetricOnTask(
|
|
17808
|
+
projectRoot,
|
|
17809
|
+
input.boardId,
|
|
17810
|
+
input.taskId,
|
|
17811
|
+
input.metricId,
|
|
17812
|
+
{
|
|
17813
|
+
...input.metricName !== void 0 ? { name: input.metricName } : {},
|
|
17814
|
+
...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
|
|
17815
|
+
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
17816
|
+
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
17817
|
+
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
17818
|
+
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17819
|
+
}
|
|
17820
|
+
);
|
|
17821
|
+
return board ? okBoard(board, "Goal metric updated.") : fail("Metric not found.");
|
|
16763
17822
|
}
|
|
16764
|
-
|
|
16765
|
-
|
|
16766
|
-
|
|
16767
|
-
|
|
16768
|
-
|
|
16769
|
-
|
|
16770
|
-
|
|
16771
|
-
|
|
16772
|
-
|
|
16773
|
-
return
|
|
17823
|
+
case "add_check": {
|
|
17824
|
+
if (!input.boardId || !input.taskId || !input.checkDescription) {
|
|
17825
|
+
return fail("add_check requires boardId, taskId, and checkDescription.");
|
|
17826
|
+
}
|
|
17827
|
+
const board = await addCheckToTask(projectRoot, input.boardId, input.taskId, {
|
|
17828
|
+
description: input.checkDescription,
|
|
17829
|
+
type: "manual",
|
|
17830
|
+
status: input.checkStatus
|
|
17831
|
+
});
|
|
17832
|
+
return board ? okBoard(board, "Check added.") : fail("Task not found.");
|
|
16774
17833
|
}
|
|
16775
|
-
|
|
16776
|
-
|
|
16777
|
-
|
|
16778
|
-
input.taskId,
|
|
16779
|
-
input.checkId,
|
|
16780
|
-
{
|
|
16781
|
-
...input.checkDescription !== void 0 ? { description: input.checkDescription } : {},
|
|
16782
|
-
...input.checkStatus !== void 0 ? { status: input.checkStatus } : {}
|
|
17834
|
+
case "update_check": {
|
|
17835
|
+
if (!input.boardId || !input.taskId || !input.checkId) {
|
|
17836
|
+
return fail("update_check requires boardId, taskId, and checkId.");
|
|
16783
17837
|
}
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
|
|
16792
|
-
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
|
|
16797
|
-
|
|
16798
|
-
|
|
16799
|
-
|
|
16800
|
-
|
|
16801
|
-
|
|
16802
|
-
|
|
16803
|
-
|
|
16804
|
-
|
|
17838
|
+
const board = await updateCheckOnTask(
|
|
17839
|
+
projectRoot,
|
|
17840
|
+
input.boardId,
|
|
17841
|
+
input.taskId,
|
|
17842
|
+
input.checkId,
|
|
17843
|
+
{
|
|
17844
|
+
...input.checkDescription !== void 0 ? { description: input.checkDescription } : {},
|
|
17845
|
+
...input.checkStatus !== void 0 ? { status: input.checkStatus } : {}
|
|
17846
|
+
}
|
|
17847
|
+
);
|
|
17848
|
+
return board ? okBoard(board, "Check updated.") : fail("Check not found.");
|
|
17849
|
+
}
|
|
17850
|
+
case "add_note": {
|
|
17851
|
+
if (!input.boardId || !input.taskId || !input.note)
|
|
17852
|
+
return fail("add_note requires boardId, taskId, and note.");
|
|
17853
|
+
const board = await addNoteToTask(projectRoot, input.boardId, input.taskId, {
|
|
17854
|
+
author: input.author ?? "agent",
|
|
17855
|
+
content: input.note
|
|
17856
|
+
});
|
|
17857
|
+
return board ? okBoard(board, "Note added.") : fail("Task not found.");
|
|
17858
|
+
}
|
|
17859
|
+
case "add_link": {
|
|
17860
|
+
if (!input.boardId || !input.taskId || !input.url)
|
|
17861
|
+
return fail("add_link requires boardId, taskId, and url.");
|
|
17862
|
+
const board = await addLinkToTask(projectRoot, input.boardId, input.taskId, {
|
|
17863
|
+
url: input.url,
|
|
17864
|
+
type: input.linkType ?? "url",
|
|
17865
|
+
...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
|
|
17866
|
+
});
|
|
17867
|
+
return board ? okBoard(board, "Link added.") : fail("Task not found.");
|
|
17868
|
+
}
|
|
17869
|
+
default:
|
|
17870
|
+
return fail(`Unknown kanban action: ${input.action}`);
|
|
16805
17871
|
}
|
|
16806
|
-
|
|
16807
|
-
|
|
16808
|
-
}
|
|
17872
|
+
})();
|
|
17873
|
+
return withPresence(result);
|
|
16809
17874
|
} catch (err) {
|
|
16810
17875
|
return fail(err instanceof Error ? err.message : String(err));
|
|
16811
17876
|
}
|
|
@@ -16829,6 +17894,7 @@ function taskInput(input) {
|
|
|
16829
17894
|
title: input.title ?? "",
|
|
16830
17895
|
columnId: input.columnId,
|
|
16831
17896
|
description: input.description,
|
|
17897
|
+
dueDate: input.dueDate,
|
|
16832
17898
|
priority: input.priority,
|
|
16833
17899
|
...input.taskType !== void 0 ? { type: input.taskType } : {},
|
|
16834
17900
|
status: input.status,
|
|
@@ -16837,7 +17903,67 @@ function taskInput(input) {
|
|
|
16837
17903
|
...input.assignee ?? assignment?.name ?? assignment?.agentId ? { assignee: input.assignee ?? assignment?.name ?? assignment?.agentId } : {},
|
|
16838
17904
|
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
16839
17905
|
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
|
|
16840
|
-
...
|
|
17906
|
+
...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {},
|
|
17907
|
+
...assignment ? { assignment } : {},
|
|
17908
|
+
...input.order !== void 0 ? { order: input.order } : {},
|
|
17909
|
+
// Sprint 3 durable task-level fields
|
|
17910
|
+
...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
|
|
17911
|
+
...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
|
|
17912
|
+
// Relationships — chainId alone cannot construct a KanbanTaskChainRef,
|
|
17913
|
+
// so chain/previousTaskId/nextTaskId must be set via update_task.
|
|
17914
|
+
...input.childTitles !== void 0 ? { childTaskIds: input.childTitles } : {},
|
|
17915
|
+
// Detail objects
|
|
17916
|
+
...input.checkDescription !== void 0 ? {
|
|
17917
|
+
successCriteria: [
|
|
17918
|
+
{
|
|
17919
|
+
id: randomUUID(),
|
|
17920
|
+
description: input.checkDescription,
|
|
17921
|
+
type: "manual",
|
|
17922
|
+
status: input.checkStatus ?? "pending"
|
|
17923
|
+
}
|
|
17924
|
+
]
|
|
17925
|
+
} : {},
|
|
17926
|
+
...input.metricName !== void 0 ? {
|
|
17927
|
+
goalMetrics: [
|
|
17928
|
+
{
|
|
17929
|
+
id: randomUUID(),
|
|
17930
|
+
name: input.metricName,
|
|
17931
|
+
status: input.metricStatus ?? "pending",
|
|
17932
|
+
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
17933
|
+
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
17934
|
+
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
17935
|
+
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17936
|
+
}
|
|
17937
|
+
]
|
|
17938
|
+
} : {},
|
|
17939
|
+
...input.url !== void 0 ? {
|
|
17940
|
+
links: [
|
|
17941
|
+
{
|
|
17942
|
+
url: input.url,
|
|
17943
|
+
type: input.linkType ?? "url",
|
|
17944
|
+
...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
|
|
17945
|
+
}
|
|
17946
|
+
]
|
|
17947
|
+
} : {},
|
|
17948
|
+
...input.note !== void 0 ? {
|
|
17949
|
+
notes: [
|
|
17950
|
+
{
|
|
17951
|
+
id: randomUUID(),
|
|
17952
|
+
author: input.author ?? "agent",
|
|
17953
|
+
content: input.note,
|
|
17954
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17955
|
+
}
|
|
17956
|
+
]
|
|
17957
|
+
} : {},
|
|
17958
|
+
// Origin tracking
|
|
17959
|
+
...input.graphId !== void 0 ? {
|
|
17960
|
+
origin: {
|
|
17961
|
+
system: input.sourceSystem ?? "kanban-tool",
|
|
17962
|
+
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
17963
|
+
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
17964
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {}
|
|
17965
|
+
}
|
|
17966
|
+
} : {}
|
|
16841
17967
|
};
|
|
16842
17968
|
}
|
|
16843
17969
|
function mergedDependsOn(input) {
|
|
@@ -16851,6 +17977,7 @@ function taskPatch(input) {
|
|
|
16851
17977
|
return {
|
|
16852
17978
|
title: input.title,
|
|
16853
17979
|
description: input.description,
|
|
17980
|
+
dueDate: input.dueDate,
|
|
16854
17981
|
columnId: input.columnId,
|
|
16855
17982
|
order: input.order,
|
|
16856
17983
|
priority: input.priority,
|
|
@@ -17612,7 +18739,7 @@ import {
|
|
|
17612
18739
|
mutateTasks as mutateTasks2,
|
|
17613
18740
|
formatTaskList
|
|
17614
18741
|
} from "@wrongstack/core";
|
|
17615
|
-
import { randomUUID } from "node:crypto";
|
|
18742
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
17616
18743
|
var planTool = {
|
|
17617
18744
|
name: "plan",
|
|
17618
18745
|
category: "Session",
|
|
@@ -17838,7 +18965,7 @@ var planTool = {
|
|
|
17838
18965
|
try {
|
|
17839
18966
|
const taskFile = await mutateTasks2(taskPath, sessionId, (f) => {
|
|
17840
18967
|
f.tasks.push({
|
|
17841
|
-
id: `task_${
|
|
18968
|
+
id: `task_${randomUUID2()}`,
|
|
17842
18969
|
title: taskifyMeta.title,
|
|
17843
18970
|
description: taskifyMeta.details || void 0,
|
|
17844
18971
|
type: "feature",
|
|
@@ -18980,7 +20107,7 @@ import {
|
|
|
18980
20107
|
mutatePlan as mutatePlan3,
|
|
18981
20108
|
formatPlan as formatPlan2
|
|
18982
20109
|
} from "@wrongstack/core";
|
|
18983
|
-
import { randomUUID as
|
|
20110
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
18984
20111
|
function findTaskIndex(tasks, query) {
|
|
18985
20112
|
const asNum = Number.parseInt(query, 10);
|
|
18986
20113
|
if (!Number.isNaN(asNum)) {
|
|
@@ -19168,7 +20295,7 @@ var taskTool = {
|
|
|
19168
20295
|
}
|
|
19169
20296
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
19170
20297
|
const newTask = {
|
|
19171
|
-
id: `task_${Date.now()}_${
|
|
20298
|
+
id: `task_${Date.now()}_${randomUUID3().slice(0, 8)}`,
|
|
19172
20299
|
title: t.title,
|
|
19173
20300
|
description: t.description,
|
|
19174
20301
|
type: t.type || "feature",
|
|
@@ -19229,7 +20356,7 @@ var taskTool = {
|
|
|
19229
20356
|
});
|
|
19230
20357
|
if (match.description) {
|
|
19231
20358
|
todos.push({
|
|
19232
|
-
id: `todo_${ts2}_${
|
|
20359
|
+
id: `todo_${ts2}_${randomUUID3().slice(0, 6)}`,
|
|
19233
20360
|
content: match.description.slice(0, 200),
|
|
19234
20361
|
status: "pending",
|
|
19235
20362
|
promotedFromTask: match.id
|
|
@@ -19238,7 +20365,7 @@ var taskTool = {
|
|
|
19238
20365
|
if (input.subtasks && input.subtasks.length > 0) {
|
|
19239
20366
|
for (const st of input.subtasks) {
|
|
19240
20367
|
todos.push({
|
|
19241
|
-
id: `todo_${ts2}_${
|
|
20368
|
+
id: `todo_${ts2}_${randomUUID3().slice(0, 6)}`,
|
|
19242
20369
|
content: st,
|
|
19243
20370
|
status: "pending",
|
|
19244
20371
|
promotedFromTask: match.id
|
|
@@ -19944,8 +21071,8 @@ var DEFAULT_IGNORE5 = [
|
|
|
19944
21071
|
var treeTool = {
|
|
19945
21072
|
name: "tree",
|
|
19946
21073
|
category: "Filesystem",
|
|
19947
|
-
description: "Display a
|
|
19948
|
-
usageHint: "
|
|
21074
|
+
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.",
|
|
21075
|
+
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.",
|
|
19949
21076
|
permission: "auto",
|
|
19950
21077
|
mutating: false,
|
|
19951
21078
|
capabilities: ["fs.read"],
|
|
@@ -20348,15 +21475,15 @@ var OPTIONAL_TOOLS = [
|
|
|
20348
21475
|
toolUseTool,
|
|
20349
21476
|
batchToolUseTool,
|
|
20350
21477
|
toolHelpTool,
|
|
20351
|
-
codebaseIndexTool,
|
|
20352
|
-
codebaseSearchTool,
|
|
20353
|
-
codebaseStatsTool,
|
|
20354
21478
|
setWorkingDirTool
|
|
20355
21479
|
];
|
|
20356
21480
|
var TIER1_TOOLS = [
|
|
20357
21481
|
readTool,
|
|
20358
21482
|
writeTool,
|
|
20359
21483
|
editTool,
|
|
21484
|
+
codebaseStatsTool,
|
|
21485
|
+
codebaseSearchTool,
|
|
21486
|
+
codebaseIndexTool,
|
|
20360
21487
|
bashTool,
|
|
20361
21488
|
grepTool,
|
|
20362
21489
|
globTool,
|
|
@@ -20395,9 +21522,6 @@ var TIER3_TOOLS = [
|
|
|
20395
21522
|
toolUseTool,
|
|
20396
21523
|
batchToolUseTool,
|
|
20397
21524
|
toolHelpTool,
|
|
20398
|
-
codebaseIndexTool,
|
|
20399
|
-
codebaseSearchTool,
|
|
20400
|
-
codebaseStatsTool,
|
|
20401
21525
|
setWorkingDirTool
|
|
20402
21526
|
];
|
|
20403
21527
|
var builtinTools = [
|
|
@@ -20406,6 +21530,9 @@ var builtinTools = [
|
|
|
20406
21530
|
readTool,
|
|
20407
21531
|
writeTool,
|
|
20408
21532
|
editTool,
|
|
21533
|
+
codebaseStatsTool,
|
|
21534
|
+
codebaseSearchTool,
|
|
21535
|
+
codebaseIndexTool,
|
|
20409
21536
|
replaceTool,
|
|
20410
21537
|
globTool,
|
|
20411
21538
|
grepTool,
|
|
@@ -20440,9 +21567,6 @@ var builtinTools = [
|
|
|
20440
21567
|
toolUseTool,
|
|
20441
21568
|
batchToolUseTool,
|
|
20442
21569
|
toolHelpTool,
|
|
20443
|
-
codebaseIndexTool,
|
|
20444
|
-
codebaseSearchTool,
|
|
20445
|
-
codebaseStatsTool,
|
|
20446
21570
|
setWorkingDirTool
|
|
20447
21571
|
];
|
|
20448
21572
|
|
|
@@ -21680,6 +22804,7 @@ export {
|
|
|
21680
22804
|
TOOL_ICON_CONFIG,
|
|
21681
22805
|
TOOL_ICON_MAP,
|
|
21682
22806
|
_resetProcessRegistry,
|
|
22807
|
+
applySessionKanbanBoardToTodos,
|
|
21683
22808
|
applySessionKanbanTaskToSource,
|
|
21684
22809
|
assertBrowserUrlAllowed,
|
|
21685
22810
|
attachSessionKanbanMirror,
|
|
@@ -21731,6 +22856,7 @@ export {
|
|
|
21731
22856
|
executeLanguagePlan,
|
|
21732
22857
|
executePackagePlan,
|
|
21733
22858
|
fetchTool,
|
|
22859
|
+
fileGraphService,
|
|
21734
22860
|
forgetTool,
|
|
21735
22861
|
formatGlobalStatus,
|
|
21736
22862
|
formatInstanceList,
|
|
@@ -21770,6 +22896,7 @@ export {
|
|
|
21770
22896
|
normalizeShell,
|
|
21771
22897
|
onIndexStateChange,
|
|
21772
22898
|
outdatedTool,
|
|
22899
|
+
packageGraphService,
|
|
21773
22900
|
parseLanguageDiagnostics,
|
|
21774
22901
|
parsePackageReports,
|
|
21775
22902
|
parsePrivateOriginAllowlist,
|
|
@@ -21800,6 +22927,7 @@ export {
|
|
|
21800
22927
|
shutdownCodebaseIndexHost,
|
|
21801
22928
|
startProcessGuardian,
|
|
21802
22929
|
stopProcessGuardian,
|
|
22930
|
+
symbolGraphService,
|
|
21803
22931
|
testTool,
|
|
21804
22932
|
todoTool,
|
|
21805
22933
|
toolHelpTool,
|