@wrongstack/tools 0.287.0 → 0.289.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_fetch-guard.d.ts.map +1 -1
- package/dist/_redact-command.d.ts.map +1 -1
- package/dist/_spawn-stream.d.ts.map +1 -1
- package/dist/audit.js +61 -14
- package/dist/audit.js.map +3 -3
- package/dist/bash.d.ts +1 -1
- package/dist/bash.d.ts.map +1 -1
- package/dist/bash.js +97 -19
- package/dist/bash.js.map +3 -3
- package/dist/builtin.d.ts +12 -10
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +1620 -803
- package/dist/builtin.js.map +3 -3
- 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 +749 -192
- package/dist/codebase-index/index.js.map +3 -3
- package/dist/codebase-index/indexer.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 +610 -152
- package/dist/codebase-index/worker.js.map +3 -3
- package/dist/codebase-index/writer.d.ts +27 -1
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/e2e.js +477 -0
- package/dist/e2e.js.map +7 -0
- package/dist/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 +1786 -809
- 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 +11 -2
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +695 -571
- 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 +1617 -800
- 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/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 +137 -0
- 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 +7 -3
package/dist/index.js
CHANGED
|
@@ -404,8 +404,10 @@ function redactCommand(cmd) {
|
|
|
404
404
|
const flag = match.slice(0, match.indexOf(expectDefined(delim)) + 1);
|
|
405
405
|
return `${flag}[REDACTED]`;
|
|
406
406
|
}
|
|
407
|
-
|
|
408
|
-
|
|
407
|
+
if (match.startsWith("--")) {
|
|
408
|
+
return match;
|
|
409
|
+
}
|
|
410
|
+
return `${match.slice(0, 2)}[REDACTED]`;
|
|
409
411
|
});
|
|
410
412
|
}
|
|
411
413
|
return result;
|
|
@@ -417,9 +419,14 @@ var init_redact_command = __esm({
|
|
|
417
419
|
SENSITIVE_FLAG_PATTERNS = [
|
|
418
420
|
// --flag=value or --flag "value" (value captured up to next space or comma)
|
|
419
421
|
/--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
|
|
420
|
-
// -
|
|
421
|
-
|
|
422
|
-
/
|
|
422
|
+
// -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
|
|
423
|
+
// (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
|
|
424
|
+
// NOTE: synced with @wrongstack/core observability/redact-command.ts.
|
|
425
|
+
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]+/,
|
|
426
|
+
// -p|-password|-a (redis auth) short flags: attached + separated + =value.
|
|
427
|
+
// Same token-start anchor; over-redaction is an accepted tradeoff for a
|
|
428
|
+
// redaction function. Synced with core copy.
|
|
429
|
+
/(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
|
|
423
430
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
424
431
|
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
425
432
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
@@ -802,7 +809,12 @@ var init_process_registry = __esm({
|
|
|
802
809
|
|
|
803
810
|
// src/_spawn-stream.ts
|
|
804
811
|
import { spawn as spawn2 } from "node:child_process";
|
|
805
|
-
import {
|
|
812
|
+
import {
|
|
813
|
+
buildChildEnv,
|
|
814
|
+
emitProcessCompleted,
|
|
815
|
+
emitProcessOutput,
|
|
816
|
+
emitProcessStarted
|
|
817
|
+
} from "@wrongstack/core";
|
|
806
818
|
async function* spawnStream(opts) {
|
|
807
819
|
const max = opts.maxBytes ?? 2e5;
|
|
808
820
|
const flushAt = opts.flushBytes ?? 4 * 1024;
|
|
@@ -827,6 +839,19 @@ async function* spawnStream(opts) {
|
|
|
827
839
|
});
|
|
828
840
|
const registry = getProcessRegistry();
|
|
829
841
|
const pid = child.pid;
|
|
842
|
+
const processStartedAt = Date.now();
|
|
843
|
+
let stdoutBytes = 0;
|
|
844
|
+
let stderrBytes = 0;
|
|
845
|
+
let telemetryCompleted = false;
|
|
846
|
+
emitProcessStarted({
|
|
847
|
+
...pid !== void 0 ? { pid } : {},
|
|
848
|
+
parentPid: process.pid,
|
|
849
|
+
command: redactCommand(`${opts.cmd} ${opts.args.join(" ")}`),
|
|
850
|
+
args: redactCommand(opts.args.join(" ")).split(" ").filter(Boolean),
|
|
851
|
+
cwd: opts.cwd,
|
|
852
|
+
background: false,
|
|
853
|
+
startedAt: new Date(processStartedAt).toISOString()
|
|
854
|
+
});
|
|
830
855
|
if (typeof pid === "number") {
|
|
831
856
|
registry.register({
|
|
832
857
|
pid,
|
|
@@ -855,6 +880,8 @@ async function* spawnStream(opts) {
|
|
|
855
880
|
};
|
|
856
881
|
const onOut = (c) => {
|
|
857
882
|
const s = c.toString();
|
|
883
|
+
stdoutBytes += c.byteLength;
|
|
884
|
+
emitProcessOutput({ pid, stream: "stdout", chunk: c });
|
|
858
885
|
if (stdout.length < max) stdout += s;
|
|
859
886
|
spool.write(s);
|
|
860
887
|
queue.push({ kind: "out", data: s });
|
|
@@ -867,6 +894,8 @@ async function* spawnStream(opts) {
|
|
|
867
894
|
};
|
|
868
895
|
const onErr = (c) => {
|
|
869
896
|
const s = c.toString();
|
|
897
|
+
stderrBytes += c.byteLength;
|
|
898
|
+
emitProcessOutput({ pid, stream: "stderr", chunk: c });
|
|
870
899
|
if (stderr.length < max) stderr += s;
|
|
871
900
|
spool.write(s);
|
|
872
901
|
queue.push({ kind: "err", data: s });
|
|
@@ -884,9 +913,25 @@ async function* spawnStream(opts) {
|
|
|
884
913
|
queue.push({ kind: "error", data: e.message });
|
|
885
914
|
wake();
|
|
886
915
|
});
|
|
887
|
-
|
|
916
|
+
const completeTelemetry = (code, signal, timedOut = false) => {
|
|
917
|
+
if (telemetryCompleted) return;
|
|
918
|
+
telemetryCompleted = true;
|
|
919
|
+
emitProcessCompleted({
|
|
920
|
+
...pid !== void 0 ? { pid } : {},
|
|
921
|
+
exitCode: code,
|
|
922
|
+
...signal ? { signal } : {},
|
|
923
|
+
durationMs: Date.now() - processStartedAt,
|
|
924
|
+
stdoutBytes,
|
|
925
|
+
stderrBytes,
|
|
926
|
+
timedOut,
|
|
927
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
928
|
+
});
|
|
929
|
+
};
|
|
930
|
+
child.on("close", (code, signal) => {
|
|
888
931
|
if (typeof pid === "number") registry.unregister(pid);
|
|
889
|
-
|
|
932
|
+
const exitCode2 = code ?? (signal ? 1 : 0);
|
|
933
|
+
completeTelemetry(exitCode2, signal ?? void 0);
|
|
934
|
+
queue.push({ kind: "close", data: "", code: exitCode2, ...signal ? { signal } : {} });
|
|
890
935
|
wake();
|
|
891
936
|
});
|
|
892
937
|
const onAbort = () => {
|
|
@@ -899,6 +944,7 @@ async function* spawnStream(opts) {
|
|
|
899
944
|
}
|
|
900
945
|
}
|
|
901
946
|
queue.push({ kind: "close", data: "", code: 124 });
|
|
947
|
+
completeTelemetry(124, "SIGKILL", true);
|
|
902
948
|
wake();
|
|
903
949
|
};
|
|
904
950
|
if (isWin) {
|
|
@@ -2506,13 +2552,14 @@ async function detectLanguageWorkspaces(options) {
|
|
|
2506
2552
|
const target = options.target ? await canonicalInside(resolveFrom(cwd, options.target), projectRoot, "target") : void 0;
|
|
2507
2553
|
const profiles = (options.profiles ?? languageProfileRegistry.list()).filter((profile) => !options.language || profile.id === options.language).slice().sort((a, b) => a.id.localeCompare(b.id));
|
|
2508
2554
|
const limits = normalizeLimits(options.limits);
|
|
2555
|
+
const extraIgnores = new Set(options.ignoredDirectories ?? []);
|
|
2509
2556
|
const state = {
|
|
2510
2557
|
entries: 0,
|
|
2511
2558
|
truncated: false,
|
|
2512
2559
|
sourcePaths: /* @__PURE__ */ new Map(),
|
|
2513
2560
|
candidates: /* @__PURE__ */ new Map()
|
|
2514
2561
|
};
|
|
2515
|
-
await scanDirectory(projectRoot, 0, profiles, limits, state, options.signal);
|
|
2562
|
+
await scanDirectory(projectRoot, 0, profiles, limits, state, extraIgnores, options.signal);
|
|
2516
2563
|
addSourceFallbacks(projectRoot, profiles, state);
|
|
2517
2564
|
if (target) addTargetEvidence(target, projectRoot, profiles, state);
|
|
2518
2565
|
const workspaces = await Promise.all(
|
|
@@ -2526,7 +2573,7 @@ async function detectLanguageWorkspaces(options) {
|
|
|
2526
2573
|
workspaces
|
|
2527
2574
|
};
|
|
2528
2575
|
}
|
|
2529
|
-
async function scanDirectory(directory, depth, profiles, limits, state, signal) {
|
|
2576
|
+
async function scanDirectory(directory, depth, profiles, limits, state, extraIgnores, signal) {
|
|
2530
2577
|
signal?.throwIfAborted();
|
|
2531
2578
|
if (depth > limits.maxDepth || state.entries >= limits.maxEntries) {
|
|
2532
2579
|
state.truncated = true;
|
|
@@ -2549,20 +2596,20 @@ async function scanDirectory(directory, depth, profiles, limits, state, signal)
|
|
|
2549
2596
|
const fullPath = path4.join(directory, entry.name);
|
|
2550
2597
|
if (entry.isSymbolicLink()) continue;
|
|
2551
2598
|
if (entry.isDirectory()) {
|
|
2552
|
-
if (shouldIgnoreDirectory(entry.name, profiles)) continue;
|
|
2599
|
+
if (shouldIgnoreDirectory(entry.name, profiles, extraIgnores)) continue;
|
|
2553
2600
|
if (depth >= limits.maxDepth) {
|
|
2554
2601
|
state.truncated = true;
|
|
2555
2602
|
continue;
|
|
2556
2603
|
}
|
|
2557
|
-
await scanDirectory(fullPath, depth + 1, profiles, limits, state, signal);
|
|
2604
|
+
await scanDirectory(fullPath, depth + 1, profiles, limits, state, extraIgnores, signal);
|
|
2558
2605
|
continue;
|
|
2559
2606
|
}
|
|
2560
2607
|
if (!entry.isFile()) continue;
|
|
2561
2608
|
collectFileEvidence(directory, fullPath, entry.name, profiles, state);
|
|
2562
2609
|
}
|
|
2563
2610
|
}
|
|
2564
|
-
function collectFileEvidence(directory, fullPath,
|
|
2565
|
-
const lower =
|
|
2611
|
+
function collectFileEvidence(directory, fullPath, basename10, profiles, state) {
|
|
2612
|
+
const lower = basename10.toLowerCase();
|
|
2566
2613
|
const extension = path4.extname(lower);
|
|
2567
2614
|
for (const profile of profiles) {
|
|
2568
2615
|
const detector = profile.detectors.find(
|
|
@@ -2573,7 +2620,7 @@ function collectFileEvidence(directory, fullPath, 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
|
}
|
|
@@ -8003,7 +8145,7 @@ function sleepSync(ms) {
|
|
|
8003
8145
|
} catch {
|
|
8004
8146
|
}
|
|
8005
8147
|
}
|
|
8006
|
-
var IndexStore = class {
|
|
8148
|
+
var IndexStore = class _IndexStore {
|
|
8007
8149
|
db;
|
|
8008
8150
|
/** Absolute path to this project's index directory. */
|
|
8009
8151
|
indexDir;
|
|
@@ -8055,10 +8197,7 @@ var IndexStore = class {
|
|
|
8055
8197
|
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
8056
8198
|
throw new LockError(`SQLite lock conflict after ${MAX_LOCK_RETRIES} retries: ${msg}`);
|
|
8057
8199
|
}
|
|
8058
|
-
const delay = Math.min(
|
|
8059
|
-
LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt,
|
|
8060
|
-
LOCK_RETRY_MAX_DELAY_MS
|
|
8061
|
-
);
|
|
8200
|
+
const delay = Math.min(LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt, LOCK_RETRY_MAX_DELAY_MS);
|
|
8062
8201
|
sleepSync(delay);
|
|
8063
8202
|
}
|
|
8064
8203
|
}
|
|
@@ -8140,8 +8279,24 @@ var IndexStore = class {
|
|
|
8140
8279
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)");
|
|
8141
8280
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)");
|
|
8142
8281
|
try {
|
|
8143
|
-
this.db.exec(
|
|
8282
|
+
this.db.exec(
|
|
8283
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')"
|
|
8284
|
+
);
|
|
8144
8285
|
this.ftsAvailable = true;
|
|
8286
|
+
const symbolCount = Number(
|
|
8287
|
+
this.db.prepare("SELECT COUNT(*) AS n FROM symbols").get()?.n ?? 0
|
|
8288
|
+
);
|
|
8289
|
+
const ftsCount = Number(
|
|
8290
|
+
this.db.prepare("SELECT COUNT(*) AS n FROM symbols_fts").get()?.n ?? 0
|
|
8291
|
+
);
|
|
8292
|
+
if (symbolCount !== ftsCount) {
|
|
8293
|
+
this.db.exec("DELETE FROM symbols_fts");
|
|
8294
|
+
const insert = this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)");
|
|
8295
|
+
const rows = this.db.prepare("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
|
|
8296
|
+
for (const row of rows) {
|
|
8297
|
+
insert.run(row.id, buildIndexableText(row.name, row.signature, row.doc_comment));
|
|
8298
|
+
}
|
|
8299
|
+
}
|
|
8145
8300
|
} catch {
|
|
8146
8301
|
this.ftsAvailable = false;
|
|
8147
8302
|
}
|
|
@@ -8199,7 +8354,9 @@ var IndexStore = class {
|
|
|
8199
8354
|
deleteSymbolsForFile(file) {
|
|
8200
8355
|
this.runWithRetry(() => {
|
|
8201
8356
|
if (this.ftsAvailable) {
|
|
8202
|
-
this.stmt(
|
|
8357
|
+
this.stmt(
|
|
8358
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8359
|
+
).run(file);
|
|
8203
8360
|
}
|
|
8204
8361
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
8205
8362
|
});
|
|
@@ -8211,9 +8368,21 @@ var IndexStore = class {
|
|
|
8211
8368
|
*/
|
|
8212
8369
|
deleteFile(file) {
|
|
8213
8370
|
this.runWithRetry(() => {
|
|
8214
|
-
this.
|
|
8215
|
-
|
|
8216
|
-
|
|
8371
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
8372
|
+
try {
|
|
8373
|
+
if (this.ftsAvailable) {
|
|
8374
|
+
this.db.prepare(
|
|
8375
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8376
|
+
).run(file);
|
|
8377
|
+
}
|
|
8378
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)").run(file);
|
|
8379
|
+
this.db.prepare("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
8380
|
+
this.db.prepare("DELETE FROM files WHERE file = ?").run(file);
|
|
8381
|
+
this.db.exec("COMMIT");
|
|
8382
|
+
} catch (err) {
|
|
8383
|
+
this.db.exec("ROLLBACK");
|
|
8384
|
+
throw err;
|
|
8385
|
+
}
|
|
8217
8386
|
});
|
|
8218
8387
|
}
|
|
8219
8388
|
// ─── File metadata ──────────────────────────────────────────────────────────
|
|
@@ -8236,14 +8405,22 @@ var IndexStore = class {
|
|
|
8236
8405
|
).all(file);
|
|
8237
8406
|
if (!rows.length) return null;
|
|
8238
8407
|
const r = expectDefined2(rows[0]);
|
|
8239
|
-
return {
|
|
8408
|
+
return {
|
|
8409
|
+
file: r.file,
|
|
8410
|
+
lang: r.lang,
|
|
8411
|
+
mtimeMs: r.mtime_ms,
|
|
8412
|
+
symbolCount: r.symbol_count,
|
|
8413
|
+
lastIndexed: r.last_indexed
|
|
8414
|
+
};
|
|
8240
8415
|
}
|
|
8241
8416
|
getAllFileMetas() {
|
|
8242
|
-
return this.db.prepare(
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8417
|
+
return this.db.prepare("SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files").all().map((r) => ({
|
|
8418
|
+
file: r.file,
|
|
8419
|
+
lang: r.lang,
|
|
8420
|
+
mtimeMs: r.mtime_ms,
|
|
8421
|
+
symbolCount: r.symbol_count,
|
|
8422
|
+
lastIndexed: r.last_indexed
|
|
8423
|
+
}));
|
|
8247
8424
|
}
|
|
8248
8425
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
8249
8426
|
search(query, filter) {
|
|
@@ -8267,8 +8444,8 @@ var IndexStore = class {
|
|
|
8267
8444
|
values.push(filter.lang);
|
|
8268
8445
|
}
|
|
8269
8446
|
if (filter?.file) {
|
|
8270
|
-
conditions.push("file LIKE ?");
|
|
8271
|
-
values.push(`%${filter.file}%`);
|
|
8447
|
+
conditions.push("replace(file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
8448
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
8272
8449
|
}
|
|
8273
8450
|
if (query.trim()) {
|
|
8274
8451
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
@@ -8306,9 +8483,11 @@ var IndexStore = class {
|
|
|
8306
8483
|
* "users", camelCase-split text makes "complex" find "complexOperation").
|
|
8307
8484
|
*/
|
|
8308
8485
|
searchRanked(query, filter, limit) {
|
|
8486
|
+
const rawLimit = Number.isFinite(limit) ? Math.trunc(limit) : 20;
|
|
8487
|
+
const safeLimit = Math.max(1, Math.min(rawLimit, 100));
|
|
8309
8488
|
const tokens = tokenise(query);
|
|
8310
8489
|
if (tokens.length === 0 || !this.ftsAvailable) {
|
|
8311
|
-
return this.searchRankedFallback(query, filter,
|
|
8490
|
+
return this.searchRankedFallback(query, filter, safeLimit);
|
|
8312
8491
|
}
|
|
8313
8492
|
let effectiveKind = filter?.kind;
|
|
8314
8493
|
if (filter?.lspKind !== void 0) {
|
|
@@ -8328,11 +8507,13 @@ var IndexStore = class {
|
|
|
8328
8507
|
values.push(filter.lang);
|
|
8329
8508
|
}
|
|
8330
8509
|
if (filter?.file) {
|
|
8331
|
-
conditions.push("s.file LIKE ?");
|
|
8332
|
-
values.push(`%${filter.file}%`);
|
|
8510
|
+
conditions.push("replace(s.file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
8511
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
8333
8512
|
}
|
|
8334
8513
|
const where = conditions.join(" AND ");
|
|
8335
|
-
const countRows = this.db.prepare(
|
|
8514
|
+
const countRows = this.db.prepare(
|
|
8515
|
+
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
8516
|
+
).all(...values);
|
|
8336
8517
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
8337
8518
|
if (total === 0) return { results: [], total: 0 };
|
|
8338
8519
|
const rows = this.db.prepare(
|
|
@@ -8341,9 +8522,13 @@ var IndexStore = class {
|
|
|
8341
8522
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
8342
8523
|
FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
8343
8524
|
WHERE ${where}
|
|
8344
|
-
ORDER BY
|
|
8525
|
+
ORDER BY
|
|
8526
|
+
CASE WHEN lower(s.name) = lower(?) THEN 0
|
|
8527
|
+
WHEN lower(s.name) LIKE lower(?) ESCAPE '\\' THEN 1
|
|
8528
|
+
ELSE 2 END,
|
|
8529
|
+
bm25(symbols_fts), lower(s.name), s.file, s.line, s.col, s.id
|
|
8345
8530
|
LIMIT ?`
|
|
8346
|
-
).all(...values,
|
|
8531
|
+
).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
8347
8532
|
return {
|
|
8348
8533
|
results: rows.map((r) => ({
|
|
8349
8534
|
id: r.id,
|
|
@@ -8373,10 +8558,28 @@ var IndexStore = class {
|
|
|
8373
8558
|
}
|
|
8374
8559
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
8375
8560
|
const bm25 = buildBm25Index(
|
|
8376
|
-
candidates.map((c) => ({
|
|
8561
|
+
candidates.map((c) => ({
|
|
8562
|
+
id: c.id,
|
|
8563
|
+
text: buildIndexableText(c.name, c.signature, c.docComment)
|
|
8564
|
+
}))
|
|
8377
8565
|
);
|
|
8378
8566
|
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
8379
|
-
|
|
8567
|
+
const q = query.trim().toLowerCase();
|
|
8568
|
+
const rank = (id) => {
|
|
8569
|
+
const name = candidateById.get(id)?.name.toLowerCase() ?? "";
|
|
8570
|
+
if (name === q) return 0;
|
|
8571
|
+
if (name.startsWith(q)) return 1;
|
|
8572
|
+
return 2;
|
|
8573
|
+
};
|
|
8574
|
+
scored.sort((a, b) => {
|
|
8575
|
+
const rankDiff = rank(a.id) - rank(b.id);
|
|
8576
|
+
if (rankDiff !== 0) return rankDiff;
|
|
8577
|
+
const scoreDiff = b.score - a.score;
|
|
8578
|
+
if (scoreDiff !== 0) return scoreDiff;
|
|
8579
|
+
const left = expectDefined2(candidateById.get(a.id));
|
|
8580
|
+
const right = expectDefined2(candidateById.get(b.id));
|
|
8581
|
+
return left.name.localeCompare(right.name) || left.file.localeCompare(right.file) || left.line - right.line || left.col - right.col || left.id - right.id;
|
|
8582
|
+
});
|
|
8380
8583
|
const qTokens = tokenise(query);
|
|
8381
8584
|
const results = scored.slice(0, limit).map(({ id, score }) => {
|
|
8382
8585
|
const c = expectDefined2(candidateById.get(id));
|
|
@@ -8385,9 +8588,7 @@ var IndexStore = class {
|
|
|
8385
8588
|
return { results, total: candidates.length };
|
|
8386
8589
|
}
|
|
8387
8590
|
getAllIndexable() {
|
|
8388
|
-
return this.db.prepare("SELECT id, text FROM symbols").all().map(
|
|
8389
|
-
({ id, text }) => ({ id, text })
|
|
8390
|
-
);
|
|
8591
|
+
return this.db.prepare("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
|
|
8391
8592
|
}
|
|
8392
8593
|
/**
|
|
8393
8594
|
* Largest symbol id currently in the table (0 when empty). New ids must be
|
|
@@ -8403,22 +8604,16 @@ var IndexStore = class {
|
|
|
8403
8604
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
8404
8605
|
getStats() {
|
|
8405
8606
|
const sizeBytes = this.sizeBytes();
|
|
8406
|
-
const lastRows = this.db.prepare(
|
|
8407
|
-
"SELECT value FROM metadata WHERE key = 'last_indexed'"
|
|
8408
|
-
).all();
|
|
8607
|
+
const lastRows = this.db.prepare("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
8409
8608
|
const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
|
|
8410
8609
|
const totalRows = this.db.prepare("SELECT COUNT(*) FROM symbols").all();
|
|
8411
8610
|
const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
|
|
8412
8611
|
const fileRows = this.db.prepare("SELECT COUNT(*) FROM files").all();
|
|
8413
8612
|
const totalFiles = fileRows[0] ? Number(fileRows[0]["COUNT(*)"]) : 0;
|
|
8414
|
-
const langRows = this.db.prepare(
|
|
8415
|
-
"SELECT lang, COUNT(*) FROM symbols GROUP BY lang"
|
|
8416
|
-
).all();
|
|
8613
|
+
const langRows = this.db.prepare("SELECT lang, COUNT(*) FROM symbols GROUP BY lang").all();
|
|
8417
8614
|
const byLang = {};
|
|
8418
8615
|
for (const row of langRows) byLang[row.lang] = Number(row["COUNT(*)"]);
|
|
8419
|
-
const kindRows = this.db.prepare(
|
|
8420
|
-
"SELECT kind, COUNT(*) FROM symbols GROUP BY kind"
|
|
8421
|
-
).all();
|
|
8616
|
+
const kindRows = this.db.prepare("SELECT kind, COUNT(*) FROM symbols GROUP BY kind").all();
|
|
8422
8617
|
const byKind = {};
|
|
8423
8618
|
for (const row of kindRows) byKind[row.kind] = Number(row["COUNT(*)"]);
|
|
8424
8619
|
return {
|
|
@@ -8434,17 +8629,31 @@ var IndexStore = class {
|
|
|
8434
8629
|
}
|
|
8435
8630
|
setLastIndexed(ts2) {
|
|
8436
8631
|
this.runWithRetry(() => {
|
|
8437
|
-
this.db.prepare(
|
|
8438
|
-
|
|
8439
|
-
|
|
8632
|
+
this.db.prepare("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
|
|
8633
|
+
});
|
|
8634
|
+
}
|
|
8635
|
+
getMetadata(key) {
|
|
8636
|
+
const rows = this.db.prepare("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
8637
|
+
return rows[0]?.value;
|
|
8638
|
+
}
|
|
8639
|
+
setMetadata(key, value) {
|
|
8640
|
+
this.runWithRetry(() => {
|
|
8641
|
+
this.db.prepare("INSERT OR REPLACE INTO metadata(key, value) VALUES(?, ?)").run(key, value);
|
|
8440
8642
|
});
|
|
8441
8643
|
}
|
|
8442
8644
|
clearAll() {
|
|
8443
8645
|
this.runWithRetry(() => {
|
|
8444
|
-
this.db.exec("
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8646
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
8647
|
+
try {
|
|
8648
|
+
this.db.exec("DELETE FROM refs");
|
|
8649
|
+
this.db.exec("DELETE FROM symbols");
|
|
8650
|
+
this.db.exec("DELETE FROM files");
|
|
8651
|
+
if (this.ftsAvailable) this.db.exec("DELETE FROM symbols_fts");
|
|
8652
|
+
this.db.exec("COMMIT");
|
|
8653
|
+
} catch (err) {
|
|
8654
|
+
this.db.exec("ROLLBACK");
|
|
8655
|
+
throw err;
|
|
8656
|
+
}
|
|
8448
8657
|
});
|
|
8449
8658
|
}
|
|
8450
8659
|
// ─── Ref CRUD ────────────────────────────────────────────────────────────────
|
|
@@ -8535,6 +8744,7 @@ var IndexStore = class {
|
|
|
8535
8744
|
const allInserted = [];
|
|
8536
8745
|
const refsToInsert = [];
|
|
8537
8746
|
for (const entry of entries) {
|
|
8747
|
+
const insertedForEntry = [];
|
|
8538
8748
|
for (const s of entry.symbols) {
|
|
8539
8749
|
const id = nextId++;
|
|
8540
8750
|
symStmt.run(
|
|
@@ -8552,9 +8762,11 @@ var IndexStore = class {
|
|
|
8552
8762
|
s.file
|
|
8553
8763
|
);
|
|
8554
8764
|
ftsStmt?.run(id, buildIndexableText(s.name, s.signature, s.docComment));
|
|
8555
|
-
|
|
8765
|
+
const inserted = { ...s, id };
|
|
8766
|
+
allInserted.push(inserted);
|
|
8767
|
+
insertedForEntry.push(inserted);
|
|
8556
8768
|
}
|
|
8557
|
-
|
|
8769
|
+
refsToInsert.push(...assignRefsToSymbols(entry.refs, insertedForEntry));
|
|
8558
8770
|
}
|
|
8559
8771
|
if (refsToInsert.length > 0) {
|
|
8560
8772
|
const refStmt = this.db.prepare(
|
|
@@ -8592,9 +8804,7 @@ var IndexStore = class {
|
|
|
8592
8804
|
*/
|
|
8593
8805
|
deleteRefsForFile(file) {
|
|
8594
8806
|
this.runWithRetry(() => {
|
|
8595
|
-
this.db.prepare(
|
|
8596
|
-
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
8597
|
-
).run(file);
|
|
8807
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
|
|
8598
8808
|
});
|
|
8599
8809
|
}
|
|
8600
8810
|
/**
|
|
@@ -8635,9 +8845,7 @@ var IndexStore = class {
|
|
|
8635
8845
|
* Find all references FROM a given symbol (what does this symbol call/use?).
|
|
8636
8846
|
*/
|
|
8637
8847
|
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) => ({
|
|
8848
|
+
return this.db.prepare("SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?").all(symbolId).map((r) => ({
|
|
8641
8849
|
id: r.id,
|
|
8642
8850
|
fromId: r.from_id,
|
|
8643
8851
|
toName: r.to_name,
|
|
@@ -8654,6 +8862,325 @@ var IndexStore = class {
|
|
|
8654
8862
|
return 0;
|
|
8655
8863
|
}
|
|
8656
8864
|
}
|
|
8865
|
+
// ─── CodeMap graph aggregation ──────────────────────────────────────────────
|
|
8866
|
+
/**
|
|
8867
|
+
* Derive a monorepo package name from an absolute file path.
|
|
8868
|
+
* Handles both `packages/<name>/...` (workspace) and `src/<name>/...` layouts.
|
|
8869
|
+
* Returns `undefined` for files outside any recognisable package structure.
|
|
8870
|
+
*/
|
|
8871
|
+
static derivePackage(filePath) {
|
|
8872
|
+
const f = filePath.replace(/\\/g, "/");
|
|
8873
|
+
const pkgsIdx = f.indexOf("/packages/");
|
|
8874
|
+
if (pkgsIdx !== -1) {
|
|
8875
|
+
const rest = f.slice(pkgsIdx + "/packages/".length);
|
|
8876
|
+
const seg = rest.split("/")[0];
|
|
8877
|
+
return seg ? `@wrongstack/${seg}` : void 0;
|
|
8878
|
+
}
|
|
8879
|
+
const appsIdx = f.indexOf("/apps/");
|
|
8880
|
+
if (appsIdx !== -1) {
|
|
8881
|
+
const rest = f.slice(appsIdx + "/apps/".length);
|
|
8882
|
+
const seg = rest.split("/")[0];
|
|
8883
|
+
return seg ? `app:${seg}` : void 0;
|
|
8884
|
+
}
|
|
8885
|
+
return void 0;
|
|
8886
|
+
}
|
|
8887
|
+
static packageFromImport(moduleName) {
|
|
8888
|
+
if (!moduleName.startsWith("@wrongstack/")) return void 0;
|
|
8889
|
+
const parts = moduleName.split("/");
|
|
8890
|
+
return parts[1] ? `@wrongstack/${parts[1]}` : void 0;
|
|
8891
|
+
}
|
|
8892
|
+
static resolveRelativeImport(fromFile, moduleName, indexedFiles) {
|
|
8893
|
+
if (!moduleName.startsWith(".")) return void 0;
|
|
8894
|
+
const normalizedFrom = fromFile.replace(/\\/g, "/");
|
|
8895
|
+
const absolute = path13.posix.normalize(
|
|
8896
|
+
path13.posix.join(path13.posix.dirname(normalizedFrom), moduleName)
|
|
8897
|
+
);
|
|
8898
|
+
const extension = path13.posix.extname(absolute);
|
|
8899
|
+
const base = extension ? absolute.slice(0, -extension.length) : absolute;
|
|
8900
|
+
const candidates = [
|
|
8901
|
+
absolute,
|
|
8902
|
+
...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
|
|
8903
|
+
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path13.posix.join(absolute, `index${ext}`)),
|
|
8904
|
+
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path13.posix.join(base, `index${ext}`))
|
|
8905
|
+
];
|
|
8906
|
+
const indexedByPortablePath = new Map(
|
|
8907
|
+
[...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
|
|
8908
|
+
);
|
|
8909
|
+
for (const candidate of candidates) {
|
|
8910
|
+
const indexed = indexedByPortablePath.get(candidate.toLocaleLowerCase());
|
|
8911
|
+
if (indexed) return indexed;
|
|
8912
|
+
}
|
|
8913
|
+
return void 0;
|
|
8914
|
+
}
|
|
8915
|
+
/**
|
|
8916
|
+
* Package-level graph: each workspace package is a node; edges are derived
|
|
8917
|
+
* from cross-package symbol references (a symbol in package A references a
|
|
8918
|
+
* symbol resolved in package B). Node metadata includes symbol/file counts.
|
|
8919
|
+
*/
|
|
8920
|
+
getPackageGraph() {
|
|
8921
|
+
const symbols = this.db.prepare("SELECT file, id, name, kind, lang, line FROM symbols ORDER BY id").all();
|
|
8922
|
+
const pkgNodes = /* @__PURE__ */ new Map();
|
|
8923
|
+
const fileToPkg = /* @__PURE__ */ new Map();
|
|
8924
|
+
const symbolToPkg = /* @__PURE__ */ new Map();
|
|
8925
|
+
for (const s of symbols) {
|
|
8926
|
+
const pkg = _IndexStore.derivePackage(s.file) ?? "(root)";
|
|
8927
|
+
fileToPkg.set(s.file, pkg);
|
|
8928
|
+
symbolToPkg.set(s.id, pkg);
|
|
8929
|
+
const node = pkgNodes.get(pkg);
|
|
8930
|
+
if (node) {
|
|
8931
|
+
node.symbolCount = (node.symbolCount ?? 0) + 1;
|
|
8932
|
+
} else {
|
|
8933
|
+
pkgNodes.set(pkg, {
|
|
8934
|
+
id: `pkg:${pkg}`,
|
|
8935
|
+
label: pkg,
|
|
8936
|
+
kind: "package",
|
|
8937
|
+
package: pkg,
|
|
8938
|
+
symbolCount: 1,
|
|
8939
|
+
fileCount: 0
|
|
8940
|
+
});
|
|
8941
|
+
}
|
|
8942
|
+
}
|
|
8943
|
+
const files = this.db.prepare("SELECT DISTINCT file FROM files").all();
|
|
8944
|
+
for (const { file } of files) {
|
|
8945
|
+
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
8946
|
+
const node = pkgNodes.get(pkg);
|
|
8947
|
+
if (node) node.fileCount = (node.fileCount ?? 0) + 1;
|
|
8948
|
+
}
|
|
8949
|
+
const refRows = this.db.prepare(
|
|
8950
|
+
`SELECT r.from_id, r.to_id, r.call_type
|
|
8951
|
+
FROM refs r
|
|
8952
|
+
WHERE r.to_id IS NOT NULL`
|
|
8953
|
+
).all();
|
|
8954
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
8955
|
+
for (const r of refRows) {
|
|
8956
|
+
if (r.call_type === "import") continue;
|
|
8957
|
+
const fromPkg = symbolToPkg.get(r.from_id);
|
|
8958
|
+
const toPkg = symbolToPkg.get(r.to_id);
|
|
8959
|
+
if (!fromPkg || !toPkg || fromPkg === toPkg) continue;
|
|
8960
|
+
const key = `${fromPkg}\0${toPkg}`;
|
|
8961
|
+
let e = edgeMap.get(key);
|
|
8962
|
+
if (!e) {
|
|
8963
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8964
|
+
edgeMap.set(key, e);
|
|
8965
|
+
}
|
|
8966
|
+
e.weight++;
|
|
8967
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
8968
|
+
}
|
|
8969
|
+
const importRows = this.db.prepare(
|
|
8970
|
+
`SELECT r.from_id, r.to_name
|
|
8971
|
+
FROM refs r
|
|
8972
|
+
WHERE r.call_type = 'import'`
|
|
8973
|
+
).all();
|
|
8974
|
+
for (const r of importRows) {
|
|
8975
|
+
const fromPkg = symbolToPkg.get(r.from_id);
|
|
8976
|
+
const toPkg = _IndexStore.packageFromImport(r.to_name);
|
|
8977
|
+
if (!fromPkg || !toPkg || fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
8978
|
+
const key = `${fromPkg}\0${toPkg}`;
|
|
8979
|
+
let edge = edgeMap.get(key);
|
|
8980
|
+
if (!edge) {
|
|
8981
|
+
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8982
|
+
edgeMap.set(key, edge);
|
|
8983
|
+
}
|
|
8984
|
+
edge.weight++;
|
|
8985
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
|
|
8986
|
+
}
|
|
8987
|
+
const edges = [];
|
|
8988
|
+
for (const [key, e] of edgeMap) {
|
|
8989
|
+
const [source, target] = key.split("\0");
|
|
8990
|
+
let bestType = "call";
|
|
8991
|
+
let bestCount = 0;
|
|
8992
|
+
for (const [t, c] of e.types) {
|
|
8993
|
+
if (c > bestCount) {
|
|
8994
|
+
bestType = t;
|
|
8995
|
+
bestCount = c;
|
|
8996
|
+
}
|
|
8997
|
+
}
|
|
8998
|
+
edges.push({
|
|
8999
|
+
source: `pkg:${source}`,
|
|
9000
|
+
target: `pkg:${target}`,
|
|
9001
|
+
weight: e.weight,
|
|
9002
|
+
refType: bestType
|
|
9003
|
+
});
|
|
9004
|
+
}
|
|
9005
|
+
return { nodes: [...pkgNodes.values()], edges };
|
|
9006
|
+
}
|
|
9007
|
+
/**
|
|
9008
|
+
* File-level graph for a single package: each file is a node; edges are
|
|
9009
|
+
* derived from cross-file symbol references within the package.
|
|
9010
|
+
*/
|
|
9011
|
+
getFileGraph(packageFilter) {
|
|
9012
|
+
const allSymbols = this.db.prepare("SELECT file, id, name, kind, lang, line FROM symbols ORDER BY id").all();
|
|
9013
|
+
const pkgSyms = allSymbols.filter(
|
|
9014
|
+
(s) => (_IndexStore.derivePackage(s.file) ?? "(root)") === packageFilter
|
|
9015
|
+
);
|
|
9016
|
+
if (pkgSyms.length === 0) return { nodes: [], edges: [] };
|
|
9017
|
+
const fileNodes = /* @__PURE__ */ new Map();
|
|
9018
|
+
const symToFile = /* @__PURE__ */ new Map();
|
|
9019
|
+
const fileStats = /* @__PURE__ */ new Map();
|
|
9020
|
+
for (const s of allSymbols) {
|
|
9021
|
+
symToFile.set(s.id, s.file);
|
|
9022
|
+
const current = fileStats.get(s.file);
|
|
9023
|
+
fileStats.set(s.file, {
|
|
9024
|
+
count: (current?.count ?? 0) + 1,
|
|
9025
|
+
lang: current?.lang ?? s.lang
|
|
9026
|
+
});
|
|
9027
|
+
}
|
|
9028
|
+
const localFiles = new Set(pkgSyms.map((s) => s.file));
|
|
9029
|
+
const indexedFiles = new Set(allSymbols.map((s) => s.file));
|
|
9030
|
+
const ensureFileNode = (file) => {
|
|
9031
|
+
if (fileNodes.has(file)) return;
|
|
9032
|
+
const stats = fileStats.get(file);
|
|
9033
|
+
fileNodes.set(file, {
|
|
9034
|
+
id: `file:${file}`,
|
|
9035
|
+
label: file.replace(/\\/g, "/").split("/").pop() ?? file,
|
|
9036
|
+
kind: "file",
|
|
9037
|
+
package: _IndexStore.derivePackage(file) ?? "(root)",
|
|
9038
|
+
file,
|
|
9039
|
+
symbolCount: stats?.count ?? 0,
|
|
9040
|
+
lang: stats?.lang,
|
|
9041
|
+
external: !localFiles.has(file)
|
|
9042
|
+
});
|
|
9043
|
+
};
|
|
9044
|
+
for (const file of localFiles) {
|
|
9045
|
+
ensureFileNode(file);
|
|
9046
|
+
}
|
|
9047
|
+
const refRows = this.db.prepare(
|
|
9048
|
+
`SELECT r.from_id, r.to_id, r.call_type
|
|
9049
|
+
FROM refs r
|
|
9050
|
+
WHERE r.to_id IS NOT NULL`
|
|
9051
|
+
).all();
|
|
9052
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
9053
|
+
for (const r of refRows) {
|
|
9054
|
+
if (r.call_type === "import") continue;
|
|
9055
|
+
const fromFile = symToFile.get(r.from_id);
|
|
9056
|
+
const toFile = symToFile.get(r.to_id);
|
|
9057
|
+
if (!fromFile || !toFile || fromFile === toFile) continue;
|
|
9058
|
+
if (!localFiles.has(fromFile) && !localFiles.has(toFile)) continue;
|
|
9059
|
+
ensureFileNode(fromFile);
|
|
9060
|
+
ensureFileNode(toFile);
|
|
9061
|
+
const key = `${fromFile}\0${toFile}`;
|
|
9062
|
+
let e = edgeMap.get(key);
|
|
9063
|
+
if (!e) {
|
|
9064
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9065
|
+
edgeMap.set(key, e);
|
|
9066
|
+
}
|
|
9067
|
+
e.weight++;
|
|
9068
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
9069
|
+
}
|
|
9070
|
+
const importRows = this.db.prepare(
|
|
9071
|
+
`SELECT r.from_id, r.to_name
|
|
9072
|
+
FROM refs r
|
|
9073
|
+
WHERE r.call_type = 'import'`
|
|
9074
|
+
).all();
|
|
9075
|
+
for (const r of importRows) {
|
|
9076
|
+
const fromFile = symToFile.get(r.from_id);
|
|
9077
|
+
if (!fromFile || !localFiles.has(fromFile)) continue;
|
|
9078
|
+
const toFile = _IndexStore.resolveRelativeImport(fromFile, r.to_name, indexedFiles);
|
|
9079
|
+
if (!toFile || fromFile === toFile) continue;
|
|
9080
|
+
ensureFileNode(fromFile);
|
|
9081
|
+
ensureFileNode(toFile);
|
|
9082
|
+
const key = `${fromFile}\0${toFile}`;
|
|
9083
|
+
let edge = edgeMap.get(key);
|
|
9084
|
+
if (!edge) {
|
|
9085
|
+
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9086
|
+
edgeMap.set(key, edge);
|
|
9087
|
+
}
|
|
9088
|
+
edge.weight++;
|
|
9089
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
|
|
9090
|
+
}
|
|
9091
|
+
const edges = [];
|
|
9092
|
+
for (const [key, e] of edgeMap) {
|
|
9093
|
+
const [source, target] = key.split("\0");
|
|
9094
|
+
let bestType = "call";
|
|
9095
|
+
let bestCount = 0;
|
|
9096
|
+
for (const [t, c] of e.types) {
|
|
9097
|
+
if (c > bestCount) {
|
|
9098
|
+
bestType = t;
|
|
9099
|
+
bestCount = c;
|
|
9100
|
+
}
|
|
9101
|
+
}
|
|
9102
|
+
edges.push({
|
|
9103
|
+
source: `file:${source}`,
|
|
9104
|
+
target: `file:${target}`,
|
|
9105
|
+
weight: e.weight,
|
|
9106
|
+
refType: bestType
|
|
9107
|
+
});
|
|
9108
|
+
}
|
|
9109
|
+
return { nodes: [...fileNodes.values()], edges };
|
|
9110
|
+
}
|
|
9111
|
+
/**
|
|
9112
|
+
* Symbol-level graph for a single file: each symbol is a node; edges are
|
|
9113
|
+
* derived from intra-file and cross-file symbol references (who calls whom).
|
|
9114
|
+
*/
|
|
9115
|
+
getSymbolGraph(fileFilter) {
|
|
9116
|
+
const allSymbols = this.db.prepare(
|
|
9117
|
+
"SELECT id, name, kind, lang, file, line, signature, scope FROM symbols ORDER BY file, line, id"
|
|
9118
|
+
).all();
|
|
9119
|
+
const syms = allSymbols.filter((symbol) => symbol.file === fileFilter);
|
|
9120
|
+
if (syms.length === 0) return { nodes: [], edges: [] };
|
|
9121
|
+
const symById = new Map(allSymbols.map((symbol) => [symbol.id, symbol]));
|
|
9122
|
+
const relatedIds = new Set(syms.map((symbol) => symbol.id));
|
|
9123
|
+
const toGraphNode = (s) => ({
|
|
9124
|
+
id: `sym:${s.id}`,
|
|
9125
|
+
label: s.name,
|
|
9126
|
+
kind: "symbol",
|
|
9127
|
+
symbolId: s.id,
|
|
9128
|
+
symbolKind: s.kind,
|
|
9129
|
+
file: s.file,
|
|
9130
|
+
package: _IndexStore.derivePackage(s.file) ?? "(root)",
|
|
9131
|
+
lang: s.lang,
|
|
9132
|
+
line: s.line,
|
|
9133
|
+
signature: s.signature,
|
|
9134
|
+
scope: s.scope,
|
|
9135
|
+
external: s.file !== fileFilter
|
|
9136
|
+
});
|
|
9137
|
+
const symIds = new Set(syms.map((s) => s.id));
|
|
9138
|
+
const refRows = this.db.prepare(
|
|
9139
|
+
`SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
9140
|
+
FROM refs r
|
|
9141
|
+
WHERE r.from_id IN (SELECT id FROM symbols WHERE file = ?)
|
|
9142
|
+
OR r.to_id IN (SELECT id FROM symbols WHERE file = ?)`
|
|
9143
|
+
).all(fileFilter, fileFilter);
|
|
9144
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
9145
|
+
for (const r of refRows) {
|
|
9146
|
+
if (r.to_id === null) continue;
|
|
9147
|
+
if (!symIds.has(r.from_id) && !symIds.has(r.to_id)) continue;
|
|
9148
|
+
relatedIds.add(r.from_id);
|
|
9149
|
+
relatedIds.add(r.to_id);
|
|
9150
|
+
const key = `${r.from_id}\0${r.to_id}`;
|
|
9151
|
+
let e = edgeMap.get(key);
|
|
9152
|
+
if (!e) {
|
|
9153
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9154
|
+
edgeMap.set(key, e);
|
|
9155
|
+
}
|
|
9156
|
+
e.weight++;
|
|
9157
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
9158
|
+
}
|
|
9159
|
+
const edges = [];
|
|
9160
|
+
for (const [key, e] of edgeMap) {
|
|
9161
|
+
const [fromId, toId] = key.split("\0").map(Number);
|
|
9162
|
+
let bestType = "call";
|
|
9163
|
+
let bestCount = 0;
|
|
9164
|
+
for (const [t, c] of e.types) {
|
|
9165
|
+
if (c > bestCount) {
|
|
9166
|
+
bestType = t;
|
|
9167
|
+
bestCount = c;
|
|
9168
|
+
}
|
|
9169
|
+
}
|
|
9170
|
+
edges.push({
|
|
9171
|
+
source: `sym:${fromId}`,
|
|
9172
|
+
target: `sym:${toId}`,
|
|
9173
|
+
weight: e.weight,
|
|
9174
|
+
refType: bestType
|
|
9175
|
+
});
|
|
9176
|
+
}
|
|
9177
|
+
const nodes = [...relatedIds].map((id) => symById.get(id)).filter((symbol) => symbol !== void 0).sort((a, b) => {
|
|
9178
|
+
const aExternal = a.file === fileFilter ? 0 : 1;
|
|
9179
|
+
const bExternal = b.file === fileFilter ? 0 : 1;
|
|
9180
|
+
return aExternal - bExternal || a.file.localeCompare(b.file) || a.line - b.line || a.id - b.id;
|
|
9181
|
+
}).map(toGraphNode);
|
|
9182
|
+
return { nodes, edges };
|
|
9183
|
+
}
|
|
8657
9184
|
close() {
|
|
8658
9185
|
this.stmtCache.clear();
|
|
8659
9186
|
try {
|
|
@@ -8748,6 +9275,14 @@ function getJsDoc(node, sourceFile) {
|
|
|
8748
9275
|
}
|
|
8749
9276
|
return "";
|
|
8750
9277
|
}
|
|
9278
|
+
function hasFunctionLikeAncestor(node) {
|
|
9279
|
+
let current = node.parent;
|
|
9280
|
+
while (current) {
|
|
9281
|
+
if (ts.isFunctionLike(current)) return true;
|
|
9282
|
+
current = current.parent;
|
|
9283
|
+
}
|
|
9284
|
+
return false;
|
|
9285
|
+
}
|
|
8751
9286
|
function buildScope(node) {
|
|
8752
9287
|
const parts = [];
|
|
8753
9288
|
let current = node.parent;
|
|
@@ -8775,6 +9310,10 @@ function parseSymbols(opts) {
|
|
|
8775
9310
|
function visit(node) {
|
|
8776
9311
|
const kind = kindOf(node);
|
|
8777
9312
|
if (kind) {
|
|
9313
|
+
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && hasFunctionLikeAncestor(node)) {
|
|
9314
|
+
ts.forEachChild(node, visit);
|
|
9315
|
+
return;
|
|
9316
|
+
}
|
|
8778
9317
|
const nameNode = node.name;
|
|
8779
9318
|
if (!nameNode || !ts.isIdentifier(nameNode)) return;
|
|
8780
9319
|
const name = nameNode.text;
|
|
@@ -9719,9 +10258,9 @@ function parseSymbols5(opts) {
|
|
|
9719
10258
|
function regexParse2(opts) {
|
|
9720
10259
|
const { file, content, lang } = opts;
|
|
9721
10260
|
const symbols = [];
|
|
9722
|
-
const
|
|
9723
|
-
const isPackageJson =
|
|
9724
|
-
const isTsconfig =
|
|
10261
|
+
const basename10 = path17.basename(file).toLowerCase();
|
|
10262
|
+
const isPackageJson = basename10 === "package.json";
|
|
10263
|
+
const isTsconfig = basename10 === "tsconfig.json" || basename10 === "tsconfig.build.json";
|
|
9725
10264
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
9726
10265
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
9727
10266
|
const lines = content.split("\n");
|
|
@@ -10123,9 +10662,7 @@ function yieldEventLoop() {
|
|
|
10123
10662
|
function throwIfAborted(signal) {
|
|
10124
10663
|
if (!signal?.aborted) return;
|
|
10125
10664
|
if (signal.reason instanceof Error) throw signal.reason;
|
|
10126
|
-
throw new Error(
|
|
10127
|
-
typeof signal.reason === "string" ? signal.reason : "Indexing cancelled"
|
|
10128
|
-
);
|
|
10665
|
+
throw new Error(typeof signal.reason === "string" ? signal.reason : "Indexing cancelled");
|
|
10129
10666
|
}
|
|
10130
10667
|
function isAbortError(err) {
|
|
10131
10668
|
return err instanceof DOMException && err.name === "AbortError";
|
|
@@ -10141,8 +10678,20 @@ var DEFAULT_IGNORE = [
|
|
|
10141
10678
|
"__snapshots__",
|
|
10142
10679
|
".nyc_output"
|
|
10143
10680
|
];
|
|
10681
|
+
var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
|
|
10682
|
+
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
10683
|
+
function isWithinProject(projectRoot, file) {
|
|
10684
|
+
const rel = path19.relative(projectRoot, file);
|
|
10685
|
+
return rel !== "" && !rel.startsWith(`..${path19.sep}`) && rel !== ".." && !path19.isAbsolute(rel);
|
|
10686
|
+
}
|
|
10687
|
+
function isMissingPathError(err) {
|
|
10688
|
+
const code = err?.code;
|
|
10689
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
10690
|
+
}
|
|
10144
10691
|
async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
10145
10692
|
const results = [];
|
|
10693
|
+
const errors = [];
|
|
10694
|
+
let complete = true;
|
|
10146
10695
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
10147
10696
|
const globs = [
|
|
10148
10697
|
{ ext: ".ts", pat: compileGlob2("**/*.ts") },
|
|
@@ -10166,7 +10715,9 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10166
10715
|
let entries;
|
|
10167
10716
|
try {
|
|
10168
10717
|
entries = await fs14.readdir(dir, { withFileTypes: true });
|
|
10169
|
-
} catch {
|
|
10718
|
+
} catch (err) {
|
|
10719
|
+
complete = false;
|
|
10720
|
+
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10170
10721
|
return;
|
|
10171
10722
|
}
|
|
10172
10723
|
dirCount++;
|
|
@@ -10178,8 +10729,8 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10178
10729
|
if (isGitIgnored(rel, true)) continue;
|
|
10179
10730
|
await walk2(full);
|
|
10180
10731
|
} else if (e.isFile()) {
|
|
10181
|
-
if (isGitIgnored(rel, false)) continue;
|
|
10182
|
-
const ext = path19.extname(e.name);
|
|
10732
|
+
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
10733
|
+
const ext = path19.extname(e.name).toLowerCase();
|
|
10183
10734
|
for (const { ext: extName, pat } of globs) {
|
|
10184
10735
|
if (ext === extName && (pat.test(rel) || pat.test(e.name))) {
|
|
10185
10736
|
results.push(full);
|
|
@@ -10190,7 +10741,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10190
10741
|
}
|
|
10191
10742
|
};
|
|
10192
10743
|
await walk2(projectRoot);
|
|
10193
|
-
return results;
|
|
10744
|
+
return { files: results, complete, errors };
|
|
10194
10745
|
}
|
|
10195
10746
|
async function parseFile(file, content, lang) {
|
|
10196
10747
|
switch (lang) {
|
|
@@ -10213,6 +10764,26 @@ async function parseFile(file, content, lang) {
|
|
|
10213
10764
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
10214
10765
|
}
|
|
10215
10766
|
}
|
|
10767
|
+
function assignRefsToSymbols2(refs, symbols) {
|
|
10768
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
10769
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
10770
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10771
|
+
const assigned = [];
|
|
10772
|
+
for (const ref of refs) {
|
|
10773
|
+
let owner2;
|
|
10774
|
+
for (const symbol of ordered) {
|
|
10775
|
+
if (symbol.line > ref.line) break;
|
|
10776
|
+
owner2 = symbol;
|
|
10777
|
+
}
|
|
10778
|
+
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
10779
|
+
if (!owner2 || owner2.id <= 0) continue;
|
|
10780
|
+
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
10781
|
+
if (seen.has(key)) continue;
|
|
10782
|
+
seen.add(key);
|
|
10783
|
+
assigned.push({ ...ref, fromId: owner2.id });
|
|
10784
|
+
}
|
|
10785
|
+
return assigned;
|
|
10786
|
+
}
|
|
10216
10787
|
async function runIndexer(_ctx, opts) {
|
|
10217
10788
|
const store = new IndexStore(opts.projectRoot, { indexDir: opts.indexDir });
|
|
10218
10789
|
try {
|
|
@@ -10225,7 +10796,9 @@ async function runIndexer(_ctx, opts) {
|
|
|
10225
10796
|
}
|
|
10226
10797
|
}
|
|
10227
10798
|
async function runIndexerWithStore(store, opts) {
|
|
10228
|
-
const { projectRoot,
|
|
10799
|
+
const { projectRoot, langs, ignore = [], signal } = opts;
|
|
10800
|
+
const relationGraphVersion = "2";
|
|
10801
|
+
const force = (opts.force ?? false) || store.getMetadata("relation_graph_version") !== relationGraphVersion;
|
|
10229
10802
|
const startMs = Date.now();
|
|
10230
10803
|
const errors = [];
|
|
10231
10804
|
const langStats = {};
|
|
@@ -10234,10 +10807,18 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10234
10807
|
const isGitIgnored = await loadGitignoreMatcher(projectRoot);
|
|
10235
10808
|
let files;
|
|
10236
10809
|
let discoveredFiles = null;
|
|
10810
|
+
let discoveryComplete = true;
|
|
10237
10811
|
if (opts.files && opts.files.length > 0) {
|
|
10238
|
-
files = opts.files.map((f) => path19.resolve(projectRoot, f)).filter((f) =>
|
|
10812
|
+
files = opts.files.map((f) => path19.resolve(projectRoot, f)).filter((f) => {
|
|
10813
|
+
if (!isWithinProject(projectRoot, f)) return false;
|
|
10814
|
+
const rel = path19.relative(projectRoot, f).replace(/\\/g, "/");
|
|
10815
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path19.basename(f)) && !isGitIgnored(rel, false);
|
|
10816
|
+
});
|
|
10239
10817
|
} else {
|
|
10240
|
-
|
|
10818
|
+
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
10819
|
+
files = discovery.files;
|
|
10820
|
+
errors.push(...discovery.errors);
|
|
10821
|
+
discoveryComplete = discovery.complete;
|
|
10241
10822
|
discoveredFiles = new Set(files);
|
|
10242
10823
|
}
|
|
10243
10824
|
if (langs && langs.length > 0) {
|
|
@@ -10262,36 +10843,66 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10262
10843
|
}
|
|
10263
10844
|
const statOpts = signal ? { signal } : {};
|
|
10264
10845
|
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
|
-
|
|
10846
|
+
batchFiles.map(
|
|
10847
|
+
async (file) => {
|
|
10848
|
+
let stat18;
|
|
10849
|
+
try {
|
|
10850
|
+
stat18 = await fs14.stat(file, statOpts);
|
|
10851
|
+
} catch (e) {
|
|
10852
|
+
if (isAbortError(e)) throw e;
|
|
10853
|
+
return {
|
|
10854
|
+
file,
|
|
10855
|
+
stat: null,
|
|
10856
|
+
lang: "",
|
|
10857
|
+
parsed: null,
|
|
10858
|
+
error: `stat error: ${e instanceof Error ? e.message : String(e)}`,
|
|
10859
|
+
missing: isMissingPathError(e)
|
|
10860
|
+
};
|
|
10861
|
+
}
|
|
10862
|
+
if (!stat18.isFile()) return { file, stat: stat18, lang: "", parsed: null };
|
|
10863
|
+
const lang = detectLang(file);
|
|
10864
|
+
if (!lang) return { file, stat: stat18, lang: "", parsed: null };
|
|
10865
|
+
if (stat18.size > MAX_INDEX_FILE_BYTES) {
|
|
10866
|
+
return {
|
|
10867
|
+
file,
|
|
10868
|
+
stat: stat18,
|
|
10869
|
+
lang,
|
|
10870
|
+
parsed: null,
|
|
10871
|
+
error: `file too large (${stat18.size} bytes; max ${MAX_INDEX_FILE_BYTES})`
|
|
10872
|
+
};
|
|
10873
|
+
}
|
|
10874
|
+
const meta = existingMeta.get(file);
|
|
10875
|
+
if (!force && meta && meta.mtimeMs === Math.floor(stat18.mtimeMs)) {
|
|
10876
|
+
return { file, stat: stat18, lang, parsed: null, skippedMeta: meta };
|
|
10877
|
+
}
|
|
10878
|
+
let content;
|
|
10879
|
+
try {
|
|
10880
|
+
content = await fs14.readFile(file, { encoding: "utf8", signal });
|
|
10881
|
+
} catch (e) {
|
|
10882
|
+
if (isAbortError(e)) throw e;
|
|
10883
|
+
return {
|
|
10884
|
+
file,
|
|
10885
|
+
stat: stat18,
|
|
10886
|
+
lang,
|
|
10887
|
+
parsed: null,
|
|
10888
|
+
error: `read error: ${e instanceof Error ? e.message : String(e)}`
|
|
10889
|
+
};
|
|
10890
|
+
}
|
|
10891
|
+
let parsed;
|
|
10892
|
+
try {
|
|
10893
|
+
parsed = await parseFile(file, content, lang);
|
|
10894
|
+
} catch (e) {
|
|
10895
|
+
return {
|
|
10896
|
+
file,
|
|
10897
|
+
stat: stat18,
|
|
10898
|
+
lang,
|
|
10899
|
+
parsed: null,
|
|
10900
|
+
error: `parse error: ${e instanceof Error ? e.message : String(e)}`
|
|
10901
|
+
};
|
|
10902
|
+
}
|
|
10903
|
+
return { file, stat: stat18, lang, parsed, content };
|
|
10292
10904
|
}
|
|
10293
|
-
|
|
10294
|
-
})
|
|
10905
|
+
)
|
|
10295
10906
|
);
|
|
10296
10907
|
const batchEntries = [];
|
|
10297
10908
|
const deleteForFiles = [];
|
|
@@ -10306,8 +10917,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10306
10917
|
}
|
|
10307
10918
|
const result = settled.value;
|
|
10308
10919
|
if (result.error) {
|
|
10309
|
-
if (result.
|
|
10310
|
-
|
|
10920
|
+
if (result.missing) store.deleteFile(file);
|
|
10921
|
+
errors.push(`${file}: ${result.error}`);
|
|
10311
10922
|
continue;
|
|
10312
10923
|
}
|
|
10313
10924
|
const { stat: stat18, lang, parsed } = result;
|
|
@@ -10331,6 +10942,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10331
10942
|
continue;
|
|
10332
10943
|
}
|
|
10333
10944
|
if (parsed.symbols.length === 0) {
|
|
10945
|
+
store.deleteRefsForFile(file);
|
|
10946
|
+
store.deleteSymbolsForFile(file);
|
|
10334
10947
|
store.upsertFile({
|
|
10335
10948
|
file,
|
|
10336
10949
|
lang,
|
|
@@ -10341,15 +10954,11 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10341
10954
|
filesIndexed++;
|
|
10342
10955
|
continue;
|
|
10343
10956
|
}
|
|
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
10957
|
batchEntries.push({
|
|
10349
10958
|
file,
|
|
10350
10959
|
lang,
|
|
10351
10960
|
symbols: parsed.symbols,
|
|
10352
|
-
refs,
|
|
10961
|
+
refs: parsed.refs ?? [],
|
|
10353
10962
|
mtimeMs: Math.floor(stat18.mtimeMs),
|
|
10354
10963
|
symbolCount: parsed.symbols.length
|
|
10355
10964
|
});
|
|
@@ -10357,34 +10966,12 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10357
10966
|
}
|
|
10358
10967
|
if (batchEntries.length > 0) {
|
|
10359
10968
|
try {
|
|
10360
|
-
|
|
10361
|
-
let cursor = 0;
|
|
10969
|
+
store.commitBatch(batchEntries, { deleteForFiles });
|
|
10362
10970
|
for (const entry of batchEntries) {
|
|
10363
10971
|
const count = entry.symbols.length;
|
|
10364
|
-
const symbolsWithIds = inserted.slice(cursor, cursor + count);
|
|
10365
|
-
cursor += count;
|
|
10366
10972
|
symbolsIndexed += count;
|
|
10367
10973
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
|
|
10368
10974
|
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
10975
|
}
|
|
10389
10976
|
} catch (err) {
|
|
10390
10977
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -10398,23 +10985,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10398
10985
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
|
|
10399
10986
|
filesIndexed++;
|
|
10400
10987
|
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
|
-
}
|
|
10988
|
+
const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
|
|
10418
10989
|
if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
|
|
10419
10990
|
}
|
|
10420
10991
|
store.upsertFile({
|
|
@@ -10425,19 +10996,23 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10425
10996
|
lastIndexed: Date.now()
|
|
10426
10997
|
});
|
|
10427
10998
|
} catch (innerErr) {
|
|
10428
|
-
errors.push(
|
|
10999
|
+
errors.push(
|
|
11000
|
+
`fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
|
|
11001
|
+
);
|
|
10429
11002
|
}
|
|
10430
11003
|
}
|
|
10431
11004
|
}
|
|
10432
11005
|
}
|
|
10433
11006
|
}
|
|
10434
|
-
if (discoveredFiles) {
|
|
11007
|
+
if (discoveredFiles && discoveryComplete) {
|
|
10435
11008
|
for (const [file_] of existingMeta) {
|
|
10436
11009
|
if (!discoveredFiles.has(file_)) {
|
|
10437
11010
|
store.deleteFile(file_);
|
|
10438
11011
|
}
|
|
10439
11012
|
}
|
|
10440
11013
|
}
|
|
11014
|
+
store.resolveRefs();
|
|
11015
|
+
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
10441
11016
|
const durationMs = Date.now() - startMs;
|
|
10442
11017
|
store.setLastIndexed(Date.now());
|
|
10443
11018
|
return {
|
|
@@ -10497,11 +11072,35 @@ function statsService(args) {
|
|
|
10497
11072
|
store.close();
|
|
10498
11073
|
}
|
|
10499
11074
|
}
|
|
11075
|
+
function packageGraphService(args) {
|
|
11076
|
+
const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
|
|
11077
|
+
try {
|
|
11078
|
+
return store.getPackageGraph();
|
|
11079
|
+
} finally {
|
|
11080
|
+
store.close();
|
|
11081
|
+
}
|
|
11082
|
+
}
|
|
11083
|
+
function fileGraphService(args) {
|
|
11084
|
+
const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
|
|
11085
|
+
try {
|
|
11086
|
+
return store.getFileGraph(args.packageFilter);
|
|
11087
|
+
} finally {
|
|
11088
|
+
store.close();
|
|
11089
|
+
}
|
|
11090
|
+
}
|
|
11091
|
+
function symbolGraphService(args) {
|
|
11092
|
+
const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
|
|
11093
|
+
try {
|
|
11094
|
+
return store.getSymbolGraph(args.fileFilter);
|
|
11095
|
+
} finally {
|
|
11096
|
+
store.close();
|
|
11097
|
+
}
|
|
11098
|
+
}
|
|
10500
11099
|
|
|
10501
11100
|
// src/codebase-index/background-indexer.ts
|
|
10502
11101
|
var DEFAULT_FULL_INDEX_TIMEOUT_MS = 24e4;
|
|
10503
11102
|
var DEFAULT_INCREMENTAL_TIMEOUT_MS = 6e4;
|
|
10504
|
-
var DEFAULT_QUERY_TIMEOUT_MS =
|
|
11103
|
+
var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
10505
11104
|
var _ready = false;
|
|
10506
11105
|
var _indexing = false;
|
|
10507
11106
|
var _currentFile = 0;
|
|
@@ -10579,14 +11178,22 @@ function ensureWorker() {
|
|
|
10579
11178
|
if (!entry) return;
|
|
10580
11179
|
pending.delete(msg.id);
|
|
10581
11180
|
if (msg.ok) entry.resolve(msg.result);
|
|
10582
|
-
else
|
|
11181
|
+
else {
|
|
11182
|
+
const error = msg.errorName === "LockError" ? new LockError(msg.error) : new Error(msg.error);
|
|
11183
|
+
if (msg.errorName && msg.errorName !== "Error") {
|
|
11184
|
+
error.name = msg.errorName;
|
|
11185
|
+
}
|
|
11186
|
+
entry.reject(error);
|
|
11187
|
+
}
|
|
10583
11188
|
});
|
|
10584
11189
|
w.on("error", (err) => {
|
|
11190
|
+
if (worker !== w) return;
|
|
10585
11191
|
worker = null;
|
|
10586
11192
|
failAllPending(err);
|
|
10587
11193
|
});
|
|
10588
11194
|
w.on("exit", () => {
|
|
10589
|
-
if (worker
|
|
11195
|
+
if (worker !== w) return;
|
|
11196
|
+
worker = null;
|
|
10590
11197
|
failAllPending(new Error("codebase-index worker exited"));
|
|
10591
11198
|
});
|
|
10592
11199
|
worker = w;
|
|
@@ -10611,6 +11218,11 @@ function shutdownCodebaseIndexHost() {
|
|
|
10611
11218
|
function callIndexOp(op, args, opts) {
|
|
10612
11219
|
const w = ensureWorker();
|
|
10613
11220
|
if (!w) return callInline(op, args, opts);
|
|
11221
|
+
if (opts.signal?.aborted) {
|
|
11222
|
+
return Promise.reject(
|
|
11223
|
+
opts.signal.reason instanceof Error ? opts.signal.reason : new Error("Indexing cancelled")
|
|
11224
|
+
);
|
|
11225
|
+
}
|
|
10614
11226
|
return new Promise((resolve15, reject) => {
|
|
10615
11227
|
const id = nextRpcId++;
|
|
10616
11228
|
const timer = setTimeout(() => {
|
|
@@ -10625,8 +11237,7 @@ function callIndexOp(op, args, opts) {
|
|
|
10625
11237
|
const onAbort = () => {
|
|
10626
11238
|
w.postMessage({ type: "cancel", id });
|
|
10627
11239
|
};
|
|
10628
|
-
|
|
10629
|
-
else opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
11240
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
10630
11241
|
const cleanup = () => {
|
|
10631
11242
|
clearTimeout(timer);
|
|
10632
11243
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
@@ -10814,7 +11425,7 @@ var codebaseIndexTool = {
|
|
|
10814
11425
|
category: "Project",
|
|
10815
11426
|
icon: "index",
|
|
10816
11427
|
description: "Build or incrementally update the project-wide symbol index. This powers fast codebase search and understanding. By default it only processes files that have changed since the last indexing run.",
|
|
10817
|
-
usageHint: "
|
|
11428
|
+
usageHint: "CREATE OR REFRESH THE SEARCH INDEX:\n\n- When `codebase-stats` or `codebase-search` reports no persisted index, call this without arguments, then retry the search.\n- Normal and first-time usage: call without arguments for an incremental build.\n- Use `force: true` only for a corrupt/stale index or an explicitly requested clean rebuild.\n- Use `langs` to restrict to specific languages if you only care about certain parts of the project.\nThis tool is relatively expensive \u2014 do not call it on every turn. Use it when the index is stale or before heavy codebase-search sessions.",
|
|
10818
11429
|
permission: "confirm",
|
|
10819
11430
|
mutating: true,
|
|
10820
11431
|
capabilities: ["fs.write.outside-project"],
|
|
@@ -10870,12 +11481,14 @@ var codebaseSearchTool = {
|
|
|
10870
11481
|
name: "codebase-search",
|
|
10871
11482
|
category: "Project",
|
|
10872
11483
|
icon: "index",
|
|
10873
|
-
description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback.
|
|
10874
|
-
usageHint: "
|
|
11484
|
+
description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback. Prefer this before broad `tree`, `glob`, or `grep` exploration when finding code by name or concept. Set `preferLsp: true` for live precision when the LSP plugin is active (supersedes codebase-lsp-search).",
|
|
11485
|
+
usageHint: "FIRST CHOICE FOR INDEXABLE CODE UNDERSTANDING:\n\n- Call before broad `tree`, `glob`, or `grep` exploration when locating symbols, concepts, definitions, or candidate modules.\n- `kind` filter is very useful (e.g. only functions or only interfaces).\n- Combine with `file` filter to scope to a specific directory or module.\n- If `indexStatus` reports no persisted data, run `codebase-index` and retry.\nUse `grep` afterwards for exact text, regexes, unsupported content, or concrete usage sites.",
|
|
10875
11486
|
permission: "auto",
|
|
10876
11487
|
mutating: false,
|
|
10877
11488
|
capabilities: ["fs.read"],
|
|
10878
|
-
|
|
11489
|
+
// The index host has its own 30s read watchdog. Leave enough headroom for
|
|
11490
|
+
// worker teardown and structured timeout reporting.
|
|
11491
|
+
timeoutMs: 35e3,
|
|
10879
11492
|
inputSchema: {
|
|
10880
11493
|
type: "object",
|
|
10881
11494
|
properties: {
|
|
@@ -10885,11 +11498,34 @@ var codebaseSearchTool = {
|
|
|
10885
11498
|
},
|
|
10886
11499
|
kind: {
|
|
10887
11500
|
type: "string",
|
|
10888
|
-
|
|
11501
|
+
enum: [
|
|
11502
|
+
"class",
|
|
11503
|
+
"interface",
|
|
11504
|
+
"enum",
|
|
11505
|
+
"type",
|
|
11506
|
+
"function",
|
|
11507
|
+
"method",
|
|
11508
|
+
"var",
|
|
11509
|
+
"const",
|
|
11510
|
+
"let",
|
|
11511
|
+
"property",
|
|
11512
|
+
"parameter",
|
|
11513
|
+
"namespace",
|
|
11514
|
+
"object",
|
|
11515
|
+
"literal",
|
|
11516
|
+
"schema",
|
|
11517
|
+
"struct",
|
|
11518
|
+
"trait",
|
|
11519
|
+
"impl",
|
|
11520
|
+
"static",
|
|
11521
|
+
"mod"
|
|
11522
|
+
],
|
|
11523
|
+
description: "Filter by indexed symbol kind"
|
|
10889
11524
|
},
|
|
10890
11525
|
lang: {
|
|
10891
11526
|
type: "string",
|
|
10892
|
-
|
|
11527
|
+
enum: ["ts", "tsx", "js", "jsx", "go", "py", "rs", "json", "yaml"],
|
|
11528
|
+
description: "Filter by indexed language"
|
|
10893
11529
|
},
|
|
10894
11530
|
lspKind: {
|
|
10895
11531
|
type: "integer",
|
|
@@ -10914,20 +11550,12 @@ var codebaseSearchTool = {
|
|
|
10914
11550
|
},
|
|
10915
11551
|
async execute(input, ctx, execOpts) {
|
|
10916
11552
|
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) {
|
|
11553
|
+
if (state.indexing && !state.ready) {
|
|
10926
11554
|
return {
|
|
10927
11555
|
results: [],
|
|
10928
11556
|
total: 0,
|
|
10929
11557
|
query: input.query,
|
|
10930
|
-
indexStatus: `
|
|
11558
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
10931
11559
|
};
|
|
10932
11560
|
}
|
|
10933
11561
|
if (state.lastError) {
|
|
@@ -10940,21 +11568,37 @@ var codebaseSearchTool = {
|
|
|
10940
11568
|
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
10941
11569
|
};
|
|
10942
11570
|
}
|
|
10943
|
-
const limit = Math.min(input.limit ?? 20, 100);
|
|
11571
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 20), 100));
|
|
10944
11572
|
const { results, total } = await searchCodebaseIndex(
|
|
10945
11573
|
{
|
|
10946
11574
|
projectRoot: ctx.projectRoot,
|
|
10947
11575
|
indexDir: codebaseIndexDirOverride(ctx),
|
|
10948
11576
|
query: input.query,
|
|
10949
|
-
kind: input.kind,
|
|
10950
|
-
lang: input.lang,
|
|
11577
|
+
kind: input.kind?.toLowerCase(),
|
|
11578
|
+
lang: input.lang?.toLowerCase(),
|
|
10951
11579
|
file: input.file,
|
|
10952
11580
|
lspKind: input.lspKind,
|
|
10953
11581
|
limit
|
|
10954
11582
|
},
|
|
10955
11583
|
{ signal: execOpts?.signal }
|
|
10956
11584
|
);
|
|
10957
|
-
|
|
11585
|
+
let hasPersistedIndex = state.ready || total > 0;
|
|
11586
|
+
if (!hasPersistedIndex) {
|
|
11587
|
+
try {
|
|
11588
|
+
const stats = await codebaseIndexStats(
|
|
11589
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
11590
|
+
{ signal: execOpts?.signal }
|
|
11591
|
+
);
|
|
11592
|
+
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
11593
|
+
} catch {
|
|
11594
|
+
}
|
|
11595
|
+
}
|
|
11596
|
+
return {
|
|
11597
|
+
results,
|
|
11598
|
+
total,
|
|
11599
|
+
query: input.query,
|
|
11600
|
+
...hasPersistedIndex ? {} : { indexStatus: "No persisted index data found. Run codebase-index to build it." }
|
|
11601
|
+
};
|
|
10958
11602
|
}
|
|
10959
11603
|
};
|
|
10960
11604
|
|
|
@@ -10963,12 +11607,15 @@ var codebaseStatsTool = {
|
|
|
10963
11607
|
name: "codebase-stats",
|
|
10964
11608
|
category: "Project",
|
|
10965
11609
|
icon: "index",
|
|
10966
|
-
description: "
|
|
10967
|
-
usageHint: "
|
|
11610
|
+
description: "Check whether a persisted codebase index exists and report its health and statistics (symbols, files, language/kind breakdown, size, last update).",
|
|
11611
|
+
usageHint: "CHECK ONCE BEFORE BROAD CODE EXPLORATION:\n\n- Call before broad `tree`, `glob`, or `grep` exploration to see whether index-backed search is available.\n- If it reports no persisted data, run `codebase-index`, then use `codebase-search`.\n- No arguments required.\nLightweight and safe to call frequently.",
|
|
10968
11612
|
permission: "auto",
|
|
10969
11613
|
mutating: false,
|
|
10970
11614
|
capabilities: ["fs.read"],
|
|
10971
|
-
|
|
11615
|
+
// The index host has its own 30s read watchdog. Keep the outer tool budget
|
|
11616
|
+
// longer so the host can return a structured timeout instead of being
|
|
11617
|
+
// pre-empted by the generic tool executor.
|
|
11618
|
+
timeoutMs: 35e3,
|
|
10972
11619
|
inputSchema: {
|
|
10973
11620
|
type: "object",
|
|
10974
11621
|
properties: {},
|
|
@@ -10976,7 +11623,8 @@ var codebaseStatsTool = {
|
|
|
10976
11623
|
},
|
|
10977
11624
|
async execute(_input, ctx, execOpts) {
|
|
10978
11625
|
const idxState = getIndexState();
|
|
10979
|
-
|
|
11626
|
+
const indexPath = resolveIndexDir(ctx.projectRoot, codebaseIndexDirOverride(ctx));
|
|
11627
|
+
if (idxState.indexing) {
|
|
10980
11628
|
return {
|
|
10981
11629
|
totalSymbols: 0,
|
|
10982
11630
|
totalFiles: 0,
|
|
@@ -10984,22 +11632,47 @@ var codebaseStatsTool = {
|
|
|
10984
11632
|
byKind: {},
|
|
10985
11633
|
lastIndexed: null,
|
|
10986
11634
|
sizeBytes: 0,
|
|
10987
|
-
indexPath
|
|
11635
|
+
indexPath,
|
|
10988
11636
|
version: SCHEMA_VERSION,
|
|
10989
|
-
|
|
11637
|
+
statsAvailable: false,
|
|
11638
|
+
indexing: {
|
|
11639
|
+
currentFile: idxState.currentFile,
|
|
11640
|
+
totalFiles: idxState.totalFiles
|
|
11641
|
+
},
|
|
11642
|
+
indexStatus: idxState.ready ? `Index refresh in progress (${idxState.currentFile}/${idxState.totalFiles} files). The persisted snapshot remains available to codebase-search; retry stats after refresh.` : `Startup indexing in progress (${idxState.currentFile}/${idxState.totalFiles} files). Do not start another index; retry stats or codebase-search after completion.`
|
|
10990
11643
|
};
|
|
10991
11644
|
}
|
|
10992
|
-
|
|
10993
|
-
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
|
|
11645
|
+
let stats;
|
|
11646
|
+
try {
|
|
11647
|
+
stats = await codebaseIndexStats(
|
|
11648
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
11649
|
+
{ signal: execOpts?.signal }
|
|
11650
|
+
);
|
|
11651
|
+
} catch (err) {
|
|
11652
|
+
if (!(err instanceof IndexTimeoutError) && err?.name !== "IndexTimeoutError") {
|
|
11653
|
+
throw err;
|
|
11654
|
+
}
|
|
10997
11655
|
return {
|
|
10998
|
-
|
|
10999
|
-
|
|
11656
|
+
totalSymbols: 0,
|
|
11657
|
+
totalFiles: 0,
|
|
11658
|
+
byLang: {},
|
|
11659
|
+
byKind: {},
|
|
11660
|
+
lastIndexed: null,
|
|
11661
|
+
sizeBytes: 0,
|
|
11662
|
+
indexPath,
|
|
11663
|
+
version: SCHEMA_VERSION,
|
|
11664
|
+
statsAvailable: false,
|
|
11665
|
+
indexStatus: "Index statistics timed out. Do not repeatedly retry stats; try codebase-search directly or use the appropriate grep/glob/tree fallback."
|
|
11000
11666
|
};
|
|
11001
11667
|
}
|
|
11002
11668
|
const circuit = idxState.circuit;
|
|
11669
|
+
const hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
11670
|
+
let indexStatus;
|
|
11671
|
+
if (circuit.state === "open") {
|
|
11672
|
+
indexStatus = `${hasPersistedIndex ? "Indexing" : "No persisted index data found, and indexing"} is paused after repeated failures (last: ${circuit.lastFailure ?? "unknown"}); auto-retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s, or run /codebase-reindex. ` + (hasPersistedIndex ? "Stats reflect the last successful build." : "Build the index after recovery.");
|
|
11673
|
+
} else if (!hasPersistedIndex) {
|
|
11674
|
+
indexStatus = "No persisted index data found. Run codebase-index to build it before broad code exploration.";
|
|
11675
|
+
}
|
|
11003
11676
|
return {
|
|
11004
11677
|
totalSymbols: stats.totalSymbols,
|
|
11005
11678
|
totalFiles: stats.totalFiles,
|
|
@@ -11009,9 +11682,8 @@ var codebaseStatsTool = {
|
|
|
11009
11682
|
sizeBytes: stats.sizeBytes,
|
|
11010
11683
|
indexPath: stats.indexPath,
|
|
11011
11684
|
version: stats.version,
|
|
11012
|
-
|
|
11013
|
-
|
|
11014
|
-
} : {}
|
|
11685
|
+
statsAvailable: true,
|
|
11686
|
+
...indexStatus ? { indexStatus } : {}
|
|
11015
11687
|
};
|
|
11016
11688
|
}
|
|
11017
11689
|
};
|
|
@@ -12530,6 +13202,11 @@ Compare this against your old_string and retry with the file's actual text.` : "
|
|
|
12530
13202
|
|
|
12531
13203
|
// src/exec.ts
|
|
12532
13204
|
import { spawn as spawn8 } from "node:child_process";
|
|
13205
|
+
import {
|
|
13206
|
+
emitProcessCompleted as emitProcessCompleted3,
|
|
13207
|
+
emitProcessOutput as emitProcessOutput3,
|
|
13208
|
+
emitProcessStarted as emitProcessStarted3
|
|
13209
|
+
} from "@wrongstack/core";
|
|
12533
13210
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils/error";
|
|
12534
13211
|
init_output_spool();
|
|
12535
13212
|
init_util();
|
|
@@ -13384,12 +14061,30 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13384
14061
|
resolve15(result);
|
|
13385
14062
|
};
|
|
13386
14063
|
const startedAt = Date.now();
|
|
14064
|
+
let stdoutBytes = 0;
|
|
14065
|
+
let stderrBytes = 0;
|
|
14066
|
+
let telemetryCompleted = false;
|
|
14067
|
+
let timedOut = false;
|
|
13387
14068
|
const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
|
|
13388
14069
|
const resolved = resolveWin32Command(cmd);
|
|
13389
14070
|
const needsShell = isWin3 && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
13390
14071
|
const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
|
|
13391
14072
|
const spawnCmd = shim?.command ?? resolved;
|
|
13392
14073
|
const spawnArgs = shim?.args ?? args;
|
|
14074
|
+
const emitCompletedOnce = (exitCode, pid2, signal2) => {
|
|
14075
|
+
if (telemetryCompleted) return;
|
|
14076
|
+
telemetryCompleted = true;
|
|
14077
|
+
emitProcessCompleted3({
|
|
14078
|
+
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
14079
|
+
exitCode,
|
|
14080
|
+
...signal2 ? { signal: signal2 } : {},
|
|
14081
|
+
durationMs: Date.now() - startedAt,
|
|
14082
|
+
stdoutBytes,
|
|
14083
|
+
stderrBytes,
|
|
14084
|
+
timedOut,
|
|
14085
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
14086
|
+
});
|
|
14087
|
+
};
|
|
13393
14088
|
let child;
|
|
13394
14089
|
try {
|
|
13395
14090
|
child = spawn8(spawnCmd, spawnArgs, {
|
|
@@ -13402,6 +14097,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13402
14097
|
});
|
|
13403
14098
|
} catch (err) {
|
|
13404
14099
|
spool.finalize();
|
|
14100
|
+
emitProcessStarted3({
|
|
14101
|
+
parentPid: process.pid,
|
|
14102
|
+
command: redactCommand(`${cmd} ${args.join(" ")}`),
|
|
14103
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
14104
|
+
cwd,
|
|
14105
|
+
background: false,
|
|
14106
|
+
startedAt: new Date(startedAt).toISOString()
|
|
14107
|
+
});
|
|
14108
|
+
emitCompletedOnce(1, void 0);
|
|
13405
14109
|
finish({
|
|
13406
14110
|
command: cmd,
|
|
13407
14111
|
args,
|
|
@@ -13414,6 +14118,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13414
14118
|
});
|
|
13415
14119
|
return;
|
|
13416
14120
|
}
|
|
14121
|
+
emitProcessStarted3({
|
|
14122
|
+
...child.pid !== void 0 ? { pid: child.pid } : {},
|
|
14123
|
+
parentPid: process.pid,
|
|
14124
|
+
command: redactCommand(`${cmd} ${args.join(" ")}`),
|
|
14125
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
14126
|
+
cwd,
|
|
14127
|
+
background: false,
|
|
14128
|
+
startedAt: new Date(startedAt).toISOString()
|
|
14129
|
+
});
|
|
13417
14130
|
child.on("error", (err) => {
|
|
13418
14131
|
const isAbort = err && err.code === "ABORT_ERR";
|
|
13419
14132
|
const stderrText = isAbort ? `Aborted: ${err.message}` : err.message;
|
|
@@ -13422,6 +14135,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13422
14135
|
if (typeof pid === "number") registry.unregister(pid);
|
|
13423
14136
|
registry.afterCall(Date.now() - startedAt, true);
|
|
13424
14137
|
spool.finalize();
|
|
14138
|
+
emitCompletedOnce(isAbort ? 124 : 1, child.pid, isAbort ? "ABORT" : void 0);
|
|
13425
14139
|
finish({
|
|
13426
14140
|
command: cmd,
|
|
13427
14141
|
args,
|
|
@@ -13441,6 +14155,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13441
14155
|
}
|
|
13442
14156
|
const timer = setTimeout(() => {
|
|
13443
14157
|
killed = true;
|
|
14158
|
+
timedOut = true;
|
|
13444
14159
|
if (typeof pid === "number") registry.kill(pid);
|
|
13445
14160
|
else child.kill("SIGTERM");
|
|
13446
14161
|
}, timeout);
|
|
@@ -13455,11 +14170,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13455
14170
|
}
|
|
13456
14171
|
child.stdout?.on("data", (chunk) => {
|
|
13457
14172
|
const text = chunk.toString();
|
|
14173
|
+
stdoutBytes += chunk.byteLength;
|
|
14174
|
+
emitProcessOutput3({ pid, stream: "stdout", chunk });
|
|
13458
14175
|
if (stdout.length < MAX_OUTPUT2) stdout += text;
|
|
13459
14176
|
spool.write(text);
|
|
13460
14177
|
});
|
|
13461
14178
|
child.stderr?.on("data", (chunk) => {
|
|
13462
14179
|
const text = chunk.toString();
|
|
14180
|
+
stderrBytes += chunk.byteLength;
|
|
14181
|
+
emitProcessOutput3({ pid, stream: "stderr", chunk });
|
|
13463
14182
|
if (stderr.length < MAX_OUTPUT2) stderr += text;
|
|
13464
14183
|
spool.write(text);
|
|
13465
14184
|
});
|
|
@@ -13469,6 +14188,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13469
14188
|
if (typeof pid === "number") registry.unregister(pid);
|
|
13470
14189
|
const durationMs = Date.now() - startedAt;
|
|
13471
14190
|
const exitCode = killed ? 124 : code ?? 1;
|
|
14191
|
+
emitCompletedOnce(exitCode, pid);
|
|
13472
14192
|
registry.afterCall(durationMs, exitCode !== 0);
|
|
13473
14193
|
const spooled = spool.finalize();
|
|
13474
14194
|
finish({
|
|
@@ -13544,7 +14264,7 @@ function guardedLookup(hostname4, options, callback) {
|
|
|
13544
14264
|
var pinnedAgent;
|
|
13545
14265
|
function getPinnedDispatcher() {
|
|
13546
14266
|
if (!pinnedAgent) {
|
|
13547
|
-
pinnedAgent = new Agent({ connect: { lookup: guardedLookup } });
|
|
14267
|
+
pinnedAgent = new Agent({ allowH2: false, connect: { lookup: guardedLookup } });
|
|
13548
14268
|
}
|
|
13549
14269
|
return pinnedAgent;
|
|
13550
14270
|
}
|
|
@@ -13646,7 +14366,7 @@ async function assertNotPrivate(hostname4) {
|
|
|
13646
14366
|
}
|
|
13647
14367
|
}
|
|
13648
14368
|
} catch (err) {
|
|
13649
|
-
if (err instanceof
|
|
14369
|
+
if (err instanceof ToolValidationError2) throw err;
|
|
13650
14370
|
}
|
|
13651
14371
|
}
|
|
13652
14372
|
}
|
|
@@ -13994,7 +14714,7 @@ async function detectFixer(cwd) {
|
|
|
13994
14714
|
init_util();
|
|
13995
14715
|
import { spawn as spawn9 } from "node:child_process";
|
|
13996
14716
|
import { statSync as statSync3 } from "node:fs";
|
|
13997
|
-
import { dirname as dirname12, resolve as resolve11, sep as
|
|
14717
|
+
import { dirname as dirname12, resolve as resolve11, sep as sep5 } from "node:path";
|
|
13998
14718
|
import { assessCommitSafety, buildChildEnv as buildChildEnv4 } from "@wrongstack/core";
|
|
13999
14719
|
var TIMEOUT_MS2 = 3e4;
|
|
14000
14720
|
var MAX_OUTPUT3 = 1e5;
|
|
@@ -14139,7 +14859,7 @@ function validateWorktreeInput(input, projectRoot) {
|
|
|
14139
14859
|
if ((input.worktreeAction === "add" || input.worktreeAction === "remove") && input.worktreePath) {
|
|
14140
14860
|
const root = resolve11(projectRoot);
|
|
14141
14861
|
const abs = resolve11(root, input.worktreePath);
|
|
14142
|
-
if (abs !== root && !abs.startsWith(root +
|
|
14862
|
+
if (abs !== root && !abs.startsWith(root + sep5)) {
|
|
14143
14863
|
return reject(`unsafe worktree path (escapes project root): ${input.worktreePath}`);
|
|
14144
14864
|
}
|
|
14145
14865
|
}
|
|
@@ -14302,8 +15022,8 @@ var WALK_CONCURRENCY = 16;
|
|
|
14302
15022
|
var globTool = {
|
|
14303
15023
|
name: "glob",
|
|
14304
15024
|
category: "Filesystem",
|
|
14305
|
-
description: "Find files
|
|
14306
|
-
usageHint: "
|
|
15025
|
+
description: "Find files by path pattern. Use index-backed `codebase-search` first for code symbols or concepts when it is live.",
|
|
15026
|
+
usageHint: "PATH DISCOVERY AND SEARCH SCOPING:\n\n- When `codebase-search` is live, use it first for code concepts; use `glob` for filenames, path patterns, and non-indexed files.\n- Combine with `path` and `limit`.\n- Default ignores common build/dependency directories.\nMuch more efficient than shell `find` for most use cases inside the agent.",
|
|
14307
15027
|
selection: {
|
|
14308
15028
|
doNotUseWhen: "you need to search inside file contents.",
|
|
14309
15029
|
useInstead: ["grep"]
|
|
@@ -14491,8 +15211,8 @@ var NATIVE_MAX_FILE_BYTES = 1e6;
|
|
|
14491
15211
|
var grepTool = {
|
|
14492
15212
|
name: "grep",
|
|
14493
15213
|
category: "Search",
|
|
14494
|
-
description: "Search
|
|
14495
|
-
usageHint: '
|
|
15214
|
+
description: "Search exact file contents using a regular expression. Use index-backed `codebase-search` first for symbol or concept discovery when it is live. Prefers ripgrep for speed and features when available.",
|
|
15215
|
+
usageHint: 'EXACT-TEXT AND REGEX SEARCH:\n\n- When `codebase-search` is live, use it first for symbols and concepts; use `grep` for exact text, regexes, unsupported content, and concrete usage sites.\n- `pattern` is a regular expression.\n- Use `output_mode: "content"` (default) to get matching lines with context.\n- Use `"files_with_matches"` when you only need the list of files.\n- Use `"count"` for quick statistics.\n- `glob` and `path` let you narrow the search scope significantly.\n- Always prefer this over `bash grep` when searching code.',
|
|
14496
15216
|
selection: {
|
|
14497
15217
|
doNotUseWhen: "you only need to locate files by name or path pattern.",
|
|
14498
15218
|
useInstead: ["glob"]
|
|
@@ -15514,6 +16234,7 @@ function toYaml(data, indent = 0) {
|
|
|
15514
16234
|
}
|
|
15515
16235
|
|
|
15516
16236
|
// src/kanban.ts
|
|
16237
|
+
import { randomUUID } from "node:crypto";
|
|
15517
16238
|
import { deserializeTaskGraph as deserializeTaskGraph2, loadTasks as loadTasks2, serializeTaskGraph } from "@wrongstack/core";
|
|
15518
16239
|
import {
|
|
15519
16240
|
addCheckToTask,
|
|
@@ -15553,7 +16274,9 @@ import {
|
|
|
15553
16274
|
setTaskChain,
|
|
15554
16275
|
splitTask,
|
|
15555
16276
|
syncBoardFromTaskGraph as syncBoardFromTaskGraph2,
|
|
16277
|
+
transitionTask,
|
|
15556
16278
|
transferTaskToBoard,
|
|
16279
|
+
touchKanbanPresence as touchKanbanPresence2,
|
|
15557
16280
|
updateBoard as updateBoard2,
|
|
15558
16281
|
updateCheckOnTask,
|
|
15559
16282
|
updateColumn,
|
|
@@ -15564,20 +16287,24 @@ import {
|
|
|
15564
16287
|
|
|
15565
16288
|
// src/session-kanban.ts
|
|
15566
16289
|
import { watch } from "node:fs";
|
|
15567
|
-
import { basename as
|
|
16290
|
+
import { basename as basename9, dirname as dirname13 } from "node:path";
|
|
15568
16291
|
import {
|
|
15569
16292
|
deserializeTaskGraph,
|
|
16293
|
+
GlobalMailbox,
|
|
15570
16294
|
loadPlan,
|
|
15571
16295
|
loadTasks,
|
|
15572
16296
|
mutatePlan,
|
|
15573
16297
|
mutateTasks
|
|
15574
16298
|
} from "@wrongstack/core";
|
|
16299
|
+
import { resolveWstackPaths as resolveWstackPaths3 } from "@wrongstack/core/utils";
|
|
15575
16300
|
import {
|
|
15576
16301
|
createBoard,
|
|
15577
16302
|
getBoard,
|
|
16303
|
+
getKanbanDir,
|
|
15578
16304
|
listBoards,
|
|
15579
16305
|
removeBoard,
|
|
15580
16306
|
syncBoardFromTaskGraph,
|
|
16307
|
+
touchKanbanPresence,
|
|
15581
16308
|
updateBoard
|
|
15582
16309
|
} from "@wrongstack/kanban";
|
|
15583
16310
|
var SESSION_BOARD_TAG = "session-work";
|
|
@@ -15844,6 +16571,42 @@ function fireAndForget(work) {
|
|
|
15844
16571
|
void work.catch(() => {
|
|
15845
16572
|
});
|
|
15846
16573
|
}
|
|
16574
|
+
function broadcastTodoUpdate(context, todos) {
|
|
16575
|
+
const sessionId = context.session?.id ?? "";
|
|
16576
|
+
if (!context.agentId || !sessionId) return;
|
|
16577
|
+
const projectDir = resolveWstackPaths3({ projectRoot: context.projectRoot }).projectDir;
|
|
16578
|
+
const mailbox = new GlobalMailbox(projectDir);
|
|
16579
|
+
void mailbox.send({
|
|
16580
|
+
from: context.agentId,
|
|
16581
|
+
to: "*",
|
|
16582
|
+
type: "status",
|
|
16583
|
+
subject: `Kanban todo list updated (${todos.length} item${todos.length === 1 ? "" : "s"})`,
|
|
16584
|
+
body: JSON.stringify({
|
|
16585
|
+
kind: "kanban.todos.updated",
|
|
16586
|
+
sessionId,
|
|
16587
|
+
revision: context.state.revision,
|
|
16588
|
+
todos
|
|
16589
|
+
}),
|
|
16590
|
+
priority: "normal",
|
|
16591
|
+
senderSessionId: sessionId,
|
|
16592
|
+
ttlMs: 6 * 60 * 60 * 1e3
|
|
16593
|
+
}).catch(() => {
|
|
16594
|
+
});
|
|
16595
|
+
}
|
|
16596
|
+
function notifyTodoUpdate(context, todos) {
|
|
16597
|
+
const summary = todos.length ? todos.map((todo) => `- [${todo.status}] ${todo.content} (${todo.id})`).join("\n") : "- No active todos remain.";
|
|
16598
|
+
const text = `[KANBAN TODO UPDATE]
|
|
16599
|
+
Another Kanban agent reassessed the shared board. The canonical todo list is now:
|
|
16600
|
+
${summary}
|
|
16601
|
+
Reassess your current plan before continuing; do not rely on the initial todo snapshot.`;
|
|
16602
|
+
const state = context.state;
|
|
16603
|
+
if (typeof state.appendBlockToLastUserMessage === "function") {
|
|
16604
|
+
if (state.appendBlockToLastUserMessage({ type: "text", text })) return;
|
|
16605
|
+
}
|
|
16606
|
+
if (typeof state.appendMessage === "function") {
|
|
16607
|
+
state.appendMessage({ role: "user", content: [{ type: "text", text }] });
|
|
16608
|
+
}
|
|
16609
|
+
}
|
|
15847
16610
|
function mirrorSessionTodosToKanban(projectRoot, todos, sessionId) {
|
|
15848
16611
|
fireAndForget(projectSessionTodosToKanban(projectRoot, todos, sessionId));
|
|
15849
16612
|
}
|
|
@@ -15875,6 +16638,10 @@ function attachSessionKanbanMirror(context) {
|
|
|
15875
16638
|
let watcher = null;
|
|
15876
16639
|
let watchedDir = "";
|
|
15877
16640
|
let timer = null;
|
|
16641
|
+
let boardWatcher = null;
|
|
16642
|
+
let watchedBoardId = "";
|
|
16643
|
+
let boardTimer = null;
|
|
16644
|
+
let presenceTimer = null;
|
|
15878
16645
|
const sessionId = () => context.session?.id ?? "";
|
|
15879
16646
|
const refreshFiles = async () => {
|
|
15880
16647
|
const id = sessionId();
|
|
@@ -15890,9 +16657,51 @@ function attachSessionKanbanMirror(context) {
|
|
|
15890
16657
|
if (tasks) await projectSessionTasksToKanban(context.projectRoot, tasks.tasks, id);
|
|
15891
16658
|
}
|
|
15892
16659
|
};
|
|
15893
|
-
const
|
|
15894
|
-
|
|
15895
|
-
const
|
|
16660
|
+
const refreshBoard = async () => {
|
|
16661
|
+
if (!watchedBoardId) return;
|
|
16662
|
+
const board = await getBoard(context.projectRoot, watchedBoardId);
|
|
16663
|
+
if (board) applySessionKanbanBoardToTodos(context, board);
|
|
16664
|
+
};
|
|
16665
|
+
const configureBoardWatcher = async () => {
|
|
16666
|
+
const id = sessionId();
|
|
16667
|
+
const board = id ? await ensureSessionKanbanBoard(context.projectRoot, id) : null;
|
|
16668
|
+
if (!board || board.id === watchedBoardId) return;
|
|
16669
|
+
boardWatcher?.close();
|
|
16670
|
+
boardWatcher = null;
|
|
16671
|
+
watchedBoardId = board.id;
|
|
16672
|
+
try {
|
|
16673
|
+
const boardFileName = `${board.id}.json`;
|
|
16674
|
+
boardWatcher = watch(
|
|
16675
|
+
getKanbanDir(context.projectRoot),
|
|
16676
|
+
{ persistent: false },
|
|
16677
|
+
(_event, filename) => {
|
|
16678
|
+
if (filename?.toString() !== boardFileName) return;
|
|
16679
|
+
if (boardTimer) clearTimeout(boardTimer);
|
|
16680
|
+
boardTimer = setTimeout(() => fireAndForget(refreshBoard()), 60);
|
|
16681
|
+
}
|
|
16682
|
+
);
|
|
16683
|
+
const touchPresence = () => touchKanbanPresence(context.projectRoot, board.id, {
|
|
16684
|
+
sessionId: id,
|
|
16685
|
+
agentId: context.agentId,
|
|
16686
|
+
agentName: context.agentName
|
|
16687
|
+
});
|
|
16688
|
+
fireAndForget(touchPresence());
|
|
16689
|
+
if (presenceTimer) clearInterval(presenceTimer);
|
|
16690
|
+
presenceTimer = setInterval(() => fireAndForget(touchPresence()), 6e4);
|
|
16691
|
+
presenceTimer.unref?.();
|
|
16692
|
+
boardWatcher.on("error", () => {
|
|
16693
|
+
boardWatcher?.close();
|
|
16694
|
+
boardWatcher = null;
|
|
16695
|
+
watchedBoardId = "";
|
|
16696
|
+
});
|
|
16697
|
+
} catch {
|
|
16698
|
+
boardWatcher = null;
|
|
16699
|
+
watchedBoardId = "";
|
|
16700
|
+
}
|
|
16701
|
+
};
|
|
16702
|
+
const configureWatcher = () => {
|
|
16703
|
+
const planPath = context.meta["plan.path"];
|
|
16704
|
+
const taskPath = context.meta["task.path"];
|
|
15896
16705
|
const candidate = typeof planPath === "string" && planPath ? dirname13(planPath) : typeof taskPath === "string" && taskPath ? dirname13(taskPath) : "";
|
|
15897
16706
|
if (!candidate || candidate === watchedDir) return;
|
|
15898
16707
|
watcher?.close();
|
|
@@ -15903,8 +16712,8 @@ function attachSessionKanbanMirror(context) {
|
|
|
15903
16712
|
const name = filename?.toString();
|
|
15904
16713
|
const currentPlanPath = context.meta["plan.path"];
|
|
15905
16714
|
const currentTaskPath = context.meta["task.path"];
|
|
15906
|
-
const planName = typeof currentPlanPath === "string" ?
|
|
15907
|
-
const taskName = typeof currentTaskPath === "string" ?
|
|
16715
|
+
const planName = typeof currentPlanPath === "string" ? basename9(currentPlanPath) : "";
|
|
16716
|
+
const taskName = typeof currentTaskPath === "string" ? basename9(currentTaskPath) : "";
|
|
15908
16717
|
if (name && name !== planName && name !== taskName) return;
|
|
15909
16718
|
if (timer) clearTimeout(timer);
|
|
15910
16719
|
timer = setTimeout(() => fireAndForget(refreshFiles()), 60);
|
|
@@ -15928,14 +16737,19 @@ function attachSessionKanbanMirror(context) {
|
|
|
15928
16737
|
syncActiveSessionRegistration();
|
|
15929
16738
|
configureWatcher();
|
|
15930
16739
|
fireAndForget(ensureSessionKanbanBoard(context.projectRoot, sessionId()));
|
|
16740
|
+
fireAndForget(configureBoardWatcher());
|
|
15931
16741
|
fireAndForget(refreshFiles());
|
|
15932
16742
|
}
|
|
15933
16743
|
});
|
|
15934
16744
|
configureWatcher();
|
|
16745
|
+
fireAndForget(configureBoardWatcher());
|
|
15935
16746
|
const detach = () => {
|
|
15936
16747
|
unsubscribe();
|
|
15937
16748
|
if (timer) clearTimeout(timer);
|
|
16749
|
+
if (boardTimer) clearTimeout(boardTimer);
|
|
16750
|
+
if (presenceTimer) clearInterval(presenceTimer);
|
|
15938
16751
|
watcher?.close();
|
|
16752
|
+
boardWatcher?.close();
|
|
15939
16753
|
bindings.delete(context);
|
|
15940
16754
|
if (attachedProjectRoot && registeredSessionId) {
|
|
15941
16755
|
releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
|
|
@@ -15974,6 +16788,51 @@ function sourceStatus(task) {
|
|
|
15974
16788
|
if (task.status === "failed") return "failed";
|
|
15975
16789
|
return "pending";
|
|
15976
16790
|
}
|
|
16791
|
+
function todoStatus(task) {
|
|
16792
|
+
const status = sourceStatus(task);
|
|
16793
|
+
if (status === "completed") return "completed";
|
|
16794
|
+
if (status === "in_progress" || status === "review") return "in_progress";
|
|
16795
|
+
return "pending";
|
|
16796
|
+
}
|
|
16797
|
+
function sessionTodoFromTask(task) {
|
|
16798
|
+
return {
|
|
16799
|
+
id: task.origin?.taskId ?? task.id,
|
|
16800
|
+
content: task.title,
|
|
16801
|
+
status: todoStatus(task),
|
|
16802
|
+
...task.description ? { activeForm: task.description } : {}
|
|
16803
|
+
};
|
|
16804
|
+
}
|
|
16805
|
+
function sameTodos(left, right) {
|
|
16806
|
+
return left.length === right.length && left.every((todo, index) => {
|
|
16807
|
+
const candidate = right[index];
|
|
16808
|
+
return candidate?.id === todo.id && candidate.content === todo.content && candidate.status === todo.status && candidate.activeForm === todo.activeForm && candidate.promotedFromPlan === todo.promotedFromPlan && candidate.promotedFromTask === todo.promotedFromTask;
|
|
16809
|
+
});
|
|
16810
|
+
}
|
|
16811
|
+
function applySessionKanbanBoardToTodos(context, board) {
|
|
16812
|
+
const sessionId = context.session?.id ?? "";
|
|
16813
|
+
if (!sessionId || sessionIdFromTags(board.tags) !== sessionId || !isOwnedSessionBoard(board.tags)) {
|
|
16814
|
+
return [...context.todos];
|
|
16815
|
+
}
|
|
16816
|
+
const projectedTodos = board.tasks.filter(
|
|
16817
|
+
(task) => task.status !== "archived" && (!task.origin || task.origin.system === "session-todo" || (task.origin.graphId ?? "").startsWith("todo:"))
|
|
16818
|
+
).sort((left, right) => {
|
|
16819
|
+
const leftColumn = board.columns.find((column) => column.id === left.columnId)?.order ?? 0;
|
|
16820
|
+
const rightColumn = board.columns.find((column) => column.id === right.columnId)?.order ?? 0;
|
|
16821
|
+
return leftColumn - rightColumn || left.order - right.order || left.createdAt.localeCompare(right.createdAt);
|
|
16822
|
+
}).map(sessionTodoFromTask);
|
|
16823
|
+
const allCompleted = projectedTodos.length > 0 && projectedTodos.every((todo) => todo.status === "completed");
|
|
16824
|
+
const effectiveTodos = allCompleted ? [] : projectedTodos;
|
|
16825
|
+
if (sameTodos(context.todos, effectiveTodos)) return [...context.todos];
|
|
16826
|
+
suppressedTodoMirrors.add(context);
|
|
16827
|
+
try {
|
|
16828
|
+
context.state.replaceTodos(projectedTodos);
|
|
16829
|
+
} finally {
|
|
16830
|
+
suppressedTodoMirrors.delete(context);
|
|
16831
|
+
}
|
|
16832
|
+
notifyTodoUpdate(context, context.todos);
|
|
16833
|
+
broadcastTodoUpdate(context, context.todos);
|
|
16834
|
+
return [...context.todos];
|
|
16835
|
+
}
|
|
15977
16836
|
async function applySessionKanbanTaskToSource(context, task, options = {}) {
|
|
15978
16837
|
const originId = task.origin?.taskId;
|
|
15979
16838
|
const graphId = task.origin?.graphId ?? "";
|
|
@@ -16037,8 +16896,8 @@ async function applySessionKanbanTaskToSource(context, task, options = {}) {
|
|
|
16037
16896
|
var kanbanTool = {
|
|
16038
16897
|
name: "kanban",
|
|
16039
16898
|
category: "Project",
|
|
16040
|
-
description: "Manage project-scoped multi-kanban boards stored under .wrongstack/kanbans.
|
|
16041
|
-
usageHint: "Use this for durable project kanban state.
|
|
16899
|
+
description: "Manage and audit project-scoped multi-kanban boards stored under .wrongstack/kanbans. Managed cards enforce fully specified details and adjacent Backlog \u2192 Todo \u2192 Running \u2192 Review \u2192 Done transitions with persistent comments and evidence. Successful board access records live agent/session presence.",
|
|
16900
|
+
usageHint: "Use this for durable project kanban state. Reassess with get_board whenever evidence changes; agents may add, update, split, merge, reprioritize, or remove tasks so the board remains a live plan. Presence includes active/last-seen session and agent metadata. For managed boards, fully fill card details, use transition_task after every material step, attach truthful evidence, and never use update_task/move_task to bypass lifecycle guards. Worker completion enters Review; only passed acceptance criteria plus review evidence allow Done.",
|
|
16042
16901
|
permission: "confirm",
|
|
16043
16902
|
mutating: true,
|
|
16044
16903
|
capabilities: ["fs.write"],
|
|
@@ -16075,6 +16934,7 @@ var kanbanTool = {
|
|
|
16075
16934
|
"transfer_task",
|
|
16076
16935
|
"get_task",
|
|
16077
16936
|
"update_task",
|
|
16937
|
+
"transition_task",
|
|
16078
16938
|
"move_task",
|
|
16079
16939
|
"delete_task",
|
|
16080
16940
|
"set_chain",
|
|
@@ -16105,6 +16965,7 @@ var kanbanTool = {
|
|
|
16105
16965
|
targetColumnId: { type: "string" },
|
|
16106
16966
|
title: { type: "string" },
|
|
16107
16967
|
description: { type: "string" },
|
|
16968
|
+
dueDate: { type: "string" },
|
|
16108
16969
|
tags: { type: "array", items: { type: "string" } },
|
|
16109
16970
|
labels: { type: "array", items: { type: "string" } },
|
|
16110
16971
|
priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
|
|
@@ -16151,6 +17012,18 @@ var kanbanTool = {
|
|
|
16151
17012
|
type: "string",
|
|
16152
17013
|
enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
|
|
16153
17014
|
},
|
|
17015
|
+
lifecycleStage: {
|
|
17016
|
+
type: "string",
|
|
17017
|
+
enum: ["backlog", "todo", "running", "review", "done"]
|
|
17018
|
+
},
|
|
17019
|
+
transitionAction: { type: "string" },
|
|
17020
|
+
transitionComment: { type: "string" },
|
|
17021
|
+
attachmentUrl: { type: "string" },
|
|
17022
|
+
attachmentTitle: { type: "string" },
|
|
17023
|
+
attachmentType: {
|
|
17024
|
+
type: "string",
|
|
17025
|
+
enum: ["issue", "pr", "doc", "commit", "design", "file", "url", "other"]
|
|
17026
|
+
},
|
|
16154
17027
|
releaseStatus: { type: "string", enum: ["pending", "ready", "blocked"] },
|
|
16155
17028
|
releaseReason: { type: "string" },
|
|
16156
17029
|
clearAssignee: { type: "boolean" },
|
|
@@ -16221,591 +17094,632 @@ var kanbanTool = {
|
|
|
16221
17094
|
async execute(input, ctx) {
|
|
16222
17095
|
const projectRoot = ctx.projectRoot;
|
|
16223
17096
|
if (!projectRoot) return fail("No project root is available.");
|
|
17097
|
+
const withPresence = async (result) => {
|
|
17098
|
+
const boardId = result.board?.id ?? input.boardId;
|
|
17099
|
+
if (!result.ok || !boardId || !ctx.session?.id || !ctx.agentId) return result;
|
|
17100
|
+
try {
|
|
17101
|
+
const board = await touchKanbanPresence2(projectRoot, boardId, {
|
|
17102
|
+
sessionId: ctx.session.id,
|
|
17103
|
+
agentId: ctx.agentId,
|
|
17104
|
+
agentName: ctx.agentName,
|
|
17105
|
+
taskId: input.taskId ?? result.task?.id,
|
|
17106
|
+
runTaskId: input.runTaskId
|
|
17107
|
+
});
|
|
17108
|
+
return board ? { ...result, board } : result;
|
|
17109
|
+
} catch {
|
|
17110
|
+
return result;
|
|
17111
|
+
}
|
|
17112
|
+
};
|
|
16224
17113
|
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);
|
|
17114
|
+
const result = await (async () => {
|
|
17115
|
+
switch (input.action) {
|
|
17116
|
+
case "list_boards": {
|
|
17117
|
+
const boards = await listBoards2(projectRoot);
|
|
17118
|
+
return { ok: true, message: `${boards.length} board(s).`, boards };
|
|
16283
17119
|
}
|
|
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.");
|
|
17120
|
+
case "get_board": {
|
|
17121
|
+
const board = await requireBoard(projectRoot, input.boardId);
|
|
17122
|
+
return board ? okBoard(board) : fail("Board not found.");
|
|
16316
17123
|
}
|
|
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
|
-
|
|
17124
|
+
case "create_board": {
|
|
17125
|
+
if (!input.title) return fail("create_board requires title.");
|
|
17126
|
+
const board = await createBoard2(projectRoot, {
|
|
17127
|
+
title: input.title,
|
|
17128
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17129
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
17130
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
|
|
17131
|
+
});
|
|
17132
|
+
return { ok: true, message: `Board created: ${board.title}`, board };
|
|
17133
|
+
}
|
|
17134
|
+
case "update_board": {
|
|
17135
|
+
if (!input.boardId) return fail("update_board requires boardId.");
|
|
17136
|
+
const board = await updateBoard2(projectRoot, input.boardId, {
|
|
17137
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17138
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17139
|
+
...input.tags !== void 0 ? { tags: input.tags } : {}
|
|
17140
|
+
});
|
|
17141
|
+
return board ? okBoard(board, "Board updated.") : fail("Board not found.");
|
|
17142
|
+
}
|
|
17143
|
+
case "duplicate_board": {
|
|
17144
|
+
if (!input.boardId) return fail("duplicate_board requires boardId.");
|
|
17145
|
+
const board = await duplicateBoard(projectRoot, input.boardId, {
|
|
17146
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17147
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
17148
|
+
...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
|
|
17149
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
17150
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
|
|
17151
|
+
});
|
|
17152
|
+
return board ? okBoard(board, "Board duplicated.") : fail("Board not found.");
|
|
17153
|
+
}
|
|
17154
|
+
case "delete_board": {
|
|
17155
|
+
if (!input.boardId) return fail("delete_board requires boardId.");
|
|
17156
|
+
const removed = await removeBoard2(projectRoot, input.boardId);
|
|
17157
|
+
return { ok: removed, message: removed ? "Board deleted." : "Board not found." };
|
|
17158
|
+
}
|
|
17159
|
+
case "generate_board": {
|
|
17160
|
+
if (!input.description) return fail("generate_board requires description.");
|
|
17161
|
+
const boardInput = generateBoardFromDescription({
|
|
17162
|
+
description: input.description,
|
|
17163
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17164
|
+
...input.context !== void 0 ? { context: input.context } : {},
|
|
17165
|
+
...input.columns !== void 0 ? { columns: input.columns } : {}
|
|
17166
|
+
});
|
|
17167
|
+
const board = await createBoard2(projectRoot, boardInput);
|
|
17168
|
+
for (const taskInput2 of parseLinesIntoTasks(
|
|
17169
|
+
input.description,
|
|
17170
|
+
board.columns[0]?.id ?? "backlog"
|
|
17171
|
+
)) {
|
|
17172
|
+
await addTask(projectRoot, board.id, taskInput2);
|
|
17173
|
+
}
|
|
17174
|
+
return okBoard(await getBoard2(projectRoot, board.id) ?? board, "Board generated.");
|
|
17175
|
+
}
|
|
17176
|
+
case "export_markdown": {
|
|
17177
|
+
const board = await requireBoard(projectRoot, input.boardId);
|
|
17178
|
+
if (!board) return fail("Board not found.");
|
|
17179
|
+
return {
|
|
17180
|
+
ok: true,
|
|
17181
|
+
message: "Board exported.",
|
|
17182
|
+
board,
|
|
17183
|
+
markdown: exportBoardAsMarkdown(board)
|
|
17184
|
+
};
|
|
17185
|
+
}
|
|
17186
|
+
case "export_task_graph": {
|
|
17187
|
+
if (!input.boardId) return fail("export_task_graph requires boardId.");
|
|
17188
|
+
const exported = await exportBoardToTaskGraph(projectRoot, input.boardId, {
|
|
17189
|
+
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
17190
|
+
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
17191
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17192
|
+
...input.preserveOriginTaskIds !== void 0 ? { preserveOriginTaskIds: input.preserveOriginTaskIds } : {},
|
|
17193
|
+
...input.includeArchived !== void 0 ? { includeArchived: input.includeArchived } : {}
|
|
17194
|
+
});
|
|
17195
|
+
if (!exported) return fail("Board not found.");
|
|
17196
|
+
return {
|
|
17197
|
+
ok: true,
|
|
17198
|
+
message: `Task graph exported with ${exported.graph.nodes.size} node(s).`,
|
|
17199
|
+
board: exported.board,
|
|
17200
|
+
taskGraph: serializeTaskGraph(exported.graph)
|
|
17201
|
+
};
|
|
17202
|
+
}
|
|
17203
|
+
case "sync_task_graph": {
|
|
17204
|
+
if (!input.boardId || !input.taskGraph) {
|
|
17205
|
+
return fail("sync_task_graph requires boardId and taskGraph.");
|
|
17206
|
+
}
|
|
17207
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
17208
|
+
const result2 = await syncBoardFromTaskGraph2(projectRoot, input.boardId, graph, {
|
|
17209
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17210
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17211
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
17212
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
17213
|
+
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
17214
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
17215
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
17216
|
+
...input.archiveMissingTasks !== void 0 ? { archiveMissingTasks: input.archiveMissingTasks } : {},
|
|
17217
|
+
...input.preserveManualDependencies !== void 0 ? { preserveManualDependencies: input.preserveManualDependencies } : {}
|
|
17218
|
+
});
|
|
17219
|
+
return result2 ? {
|
|
17220
|
+
ok: true,
|
|
17221
|
+
message: `Task graph synced: ${result2.createdTaskIds.length} created, ${result2.updatedTaskIds.length} updated, ${result2.archivedTaskIds.length} archived.`,
|
|
17222
|
+
board: result2.board
|
|
17223
|
+
} : fail("Board not found.");
|
|
17224
|
+
}
|
|
17225
|
+
case "create_from_graph": {
|
|
17226
|
+
if (!input.taskGraph) return fail("create_from_graph requires taskGraph.");
|
|
17227
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
17228
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
17229
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17230
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17231
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
17232
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
17233
|
+
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
17234
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
17235
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {}
|
|
17236
|
+
});
|
|
17237
|
+
return {
|
|
17238
|
+
ok: true,
|
|
17239
|
+
message: `Created board "${board.title}" from task graph with ${board.tasks.length} tasks.`,
|
|
17240
|
+
board
|
|
17241
|
+
};
|
|
17242
|
+
}
|
|
17243
|
+
case "import_session_tasks": {
|
|
17244
|
+
const taskPath = ctx.meta?.["task.path"];
|
|
17245
|
+
if (!taskPath) return fail("No session task file for this session.");
|
|
17246
|
+
const file = await loadTasks2(taskPath);
|
|
17247
|
+
if (!file || file.tasks.length === 0) return fail("No session tasks to import.");
|
|
17248
|
+
const sessionId = ctx.session?.id ?? file.sessionId ?? "session";
|
|
17249
|
+
const graph = deserializeTaskGraph2(taskFileToSerializedGraph(file.tasks, sessionId));
|
|
17250
|
+
const tags = ["session", `session:${sessionId}`];
|
|
17251
|
+
const existing = (await listBoards2(projectRoot)).find(
|
|
17252
|
+
(b) => b.tags?.includes(`session:${sessionId}`)
|
|
17253
|
+
);
|
|
17254
|
+
if (existing) {
|
|
17255
|
+
const result2 = await syncBoardFromTaskGraph2(projectRoot, existing.id, graph, {
|
|
17256
|
+
sourceSystem: "session",
|
|
17257
|
+
tags,
|
|
17258
|
+
archiveMissingTasks: true,
|
|
17259
|
+
includeCompletedTasks: true
|
|
17260
|
+
});
|
|
17261
|
+
return result2 ? {
|
|
17262
|
+
ok: true,
|
|
17263
|
+
message: `Synced ${file.tasks.length} session tasks into board "${result2.board.title}".`,
|
|
17264
|
+
board: result2.board
|
|
17265
|
+
} : fail("Session board vanished mid-sync.");
|
|
17266
|
+
}
|
|
17267
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
17268
|
+
title: `Session tasks (${sessionId.slice(0, 8)})`,
|
|
16366
17269
|
sourceSystem: "session",
|
|
16367
|
-
tags
|
|
16368
|
-
archiveMissingTasks: true,
|
|
16369
|
-
includeCompletedTasks: true
|
|
17270
|
+
tags
|
|
16370
17271
|
});
|
|
16371
|
-
return
|
|
17272
|
+
return {
|
|
16372
17273
|
ok: true,
|
|
16373
|
-
message: `
|
|
16374
|
-
board
|
|
16375
|
-
}
|
|
17274
|
+
message: `Imported ${file.tasks.length} session tasks into new board "${board.title}".`,
|
|
17275
|
+
board
|
|
17276
|
+
};
|
|
16376
17277
|
}
|
|
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.");
|
|
17278
|
+
case "search_tasks": {
|
|
17279
|
+
const tasks = await searchKanban(projectRoot, {
|
|
17280
|
+
query: input.query,
|
|
17281
|
+
boardId: input.boardId,
|
|
17282
|
+
assignedAgent: input.agentId,
|
|
17283
|
+
status: input.status,
|
|
17284
|
+
priority: input.priority,
|
|
17285
|
+
label: input.labels?.[0],
|
|
17286
|
+
chainId: input.chainId
|
|
17287
|
+
});
|
|
17288
|
+
return { ok: true, message: `${tasks.length} task(s) matched.`, tasks };
|
|
16462
17289
|
}
|
|
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.");
|
|
17290
|
+
case "ready_tasks": {
|
|
17291
|
+
const tasks = await listReadyTasks(projectRoot, {
|
|
17292
|
+
query: input.query,
|
|
17293
|
+
boardId: input.boardId,
|
|
17294
|
+
assignedAgent: input.agentId,
|
|
17295
|
+
priority: input.priority,
|
|
17296
|
+
label: input.labels?.[0],
|
|
17297
|
+
chainId: input.chainId,
|
|
17298
|
+
limit: input.limit
|
|
17299
|
+
});
|
|
17300
|
+
return { ok: true, message: `${tasks.length} ready task(s).`, tasks };
|
|
16485
17301
|
}
|
|
16486
|
-
|
|
16487
|
-
|
|
16488
|
-
|
|
16489
|
-
|
|
16490
|
-
|
|
16491
|
-
|
|
16492
|
-
|
|
16493
|
-
|
|
16494
|
-
|
|
16495
|
-
|
|
16496
|
-
|
|
16497
|
-
|
|
16498
|
-
|
|
17302
|
+
case "snapshot": {
|
|
17303
|
+
const snapshot = await getKanbanOrchestrationSnapshot(projectRoot, {
|
|
17304
|
+
query: input.query,
|
|
17305
|
+
boardId: input.boardId,
|
|
17306
|
+
assignedAgent: input.agentId,
|
|
17307
|
+
status: input.status,
|
|
17308
|
+
priority: input.priority,
|
|
17309
|
+
label: input.labels?.[0],
|
|
17310
|
+
chainId: input.chainId
|
|
17311
|
+
});
|
|
17312
|
+
return {
|
|
17313
|
+
ok: true,
|
|
17314
|
+
message: `${snapshot.ready.length} ready, ${snapshot.running.length} running, ${snapshot.blocked.length} blocked.`,
|
|
17315
|
+
snapshot
|
|
17316
|
+
};
|
|
16499
17317
|
}
|
|
16500
|
-
|
|
16501
|
-
|
|
16502
|
-
input.boardId,
|
|
16503
|
-
|
|
16504
|
-
|
|
16505
|
-
|
|
16506
|
-
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
17318
|
+
case "add_column": {
|
|
17319
|
+
if (!input.boardId || !input.title) return fail("add_column requires boardId and title.");
|
|
17320
|
+
const result2 = await addColumn(projectRoot, input.boardId, {
|
|
17321
|
+
title: input.title,
|
|
17322
|
+
...input.description !== void 0 ? { description: input.description } : {}
|
|
17323
|
+
});
|
|
17324
|
+
return result2 ? okBoard(result2.board, "Column added.") : fail("Board not found.");
|
|
17325
|
+
}
|
|
17326
|
+
case "update_column": {
|
|
17327
|
+
if (!input.boardId || !input.columnId)
|
|
17328
|
+
return fail("update_column requires boardId and columnId.");
|
|
17329
|
+
const board = await updateColumn(projectRoot, input.boardId, input.columnId, {
|
|
17330
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
17331
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
17332
|
+
...input.order !== void 0 ? { order: input.order } : {}
|
|
17333
|
+
});
|
|
17334
|
+
return board ? okBoard(board, "Column updated.") : fail("Column not found.");
|
|
17335
|
+
}
|
|
17336
|
+
case "delete_column": {
|
|
17337
|
+
if (!input.boardId || !input.columnId)
|
|
17338
|
+
return fail("delete_column requires boardId and columnId.");
|
|
17339
|
+
const board = await removeColumn(projectRoot, input.boardId, input.columnId, {
|
|
17340
|
+
moveTasksToColumnId: input.moveTasksToColumnId
|
|
17341
|
+
});
|
|
17342
|
+
return board ? okBoard(board, "Column deleted.") : fail("Column not found.");
|
|
17343
|
+
}
|
|
17344
|
+
case "add_task": {
|
|
17345
|
+
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
17346
|
+
const result2 = await addTask(projectRoot, input.boardId, taskInput(input));
|
|
17347
|
+
return result2 ? okTask(result2.board, result2.task, "Task added.") : fail("Board not found.");
|
|
17348
|
+
}
|
|
17349
|
+
case "split_task": {
|
|
17350
|
+
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
17351
|
+
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
16510
17352
|
}
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
|
|
16514
|
-
|
|
16515
|
-
|
|
16516
|
-
|
|
17353
|
+
const result2 = await splitTask(projectRoot, input.boardId, input.taskId, {
|
|
17354
|
+
titles: input.childTitles,
|
|
17355
|
+
...input.targetColumnId !== void 0 ? { columnId: input.targetColumnId } : {},
|
|
17356
|
+
...input.inheritAssignment !== void 0 ? { inheritAssignment: input.inheritAssignment } : {},
|
|
17357
|
+
...input.inheritLabels !== void 0 ? { inheritLabels: input.inheritLabels } : {},
|
|
17358
|
+
...input.inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria: input.inheritSuccessCriteria } : {},
|
|
17359
|
+
...input.inheritGoalMetrics !== void 0 ? { inheritGoalMetrics: input.inheritGoalMetrics } : {},
|
|
17360
|
+
...input.inheritDependencies !== void 0 ? { inheritDependencies: input.inheritDependencies } : {},
|
|
17361
|
+
...input.chainChildren !== void 0 ? { chainChildren: input.chainChildren } : {},
|
|
17362
|
+
...input.rewireDependents !== void 0 ? { rewireDependents: input.rewireDependents } : {}
|
|
17363
|
+
});
|
|
17364
|
+
return result2 ? {
|
|
17365
|
+
ok: true,
|
|
17366
|
+
message: `${result2.children.length} child task(s) created.`,
|
|
17367
|
+
board: result2.board,
|
|
17368
|
+
task: result2.parent,
|
|
17369
|
+
children: result2.children
|
|
17370
|
+
} : fail("Task not found.");
|
|
16517
17371
|
}
|
|
16518
|
-
|
|
16519
|
-
|
|
16520
|
-
|
|
16521
|
-
|
|
16522
|
-
input.
|
|
16523
|
-
|
|
17372
|
+
case "merge_tasks": {
|
|
17373
|
+
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
17374
|
+
return fail("merge_tasks requires boardId, taskIds, and title.");
|
|
17375
|
+
}
|
|
17376
|
+
const result2 = await mergeTasks(projectRoot, input.boardId, {
|
|
17377
|
+
taskIds: input.taskIds,
|
|
17378
|
+
title: input.title,
|
|
17379
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16524
17380
|
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
16525
|
-
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
16526
17381
|
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
16527
|
-
...input.
|
|
17382
|
+
...input.closeSourceTasks !== void 0 ? { closeSourceTasks: input.closeSourceTasks } : {}
|
|
17383
|
+
});
|
|
17384
|
+
return result2 ? okTask(result2.board, result2.task, "Tasks merged.") : fail("Board or task not found.");
|
|
17385
|
+
}
|
|
17386
|
+
case "copy_task": {
|
|
17387
|
+
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
17388
|
+
return fail("copy_task requires boardId, taskId, and targetBoardId.");
|
|
16528
17389
|
}
|
|
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.");
|
|
17390
|
+
const result2 = await copyTaskToBoard(
|
|
17391
|
+
projectRoot,
|
|
17392
|
+
input.boardId,
|
|
17393
|
+
input.taskId,
|
|
17394
|
+
input.targetBoardId,
|
|
17395
|
+
{
|
|
17396
|
+
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
17397
|
+
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
17398
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
17399
|
+
...input.preserveDependencies !== void 0 ? { preserveDependencies: input.preserveDependencies } : {}
|
|
17400
|
+
}
|
|
17401
|
+
);
|
|
17402
|
+
return result2 ? okTask(result2.targetBoard, result2.task, "Task copied to target board.") : fail("Board or task not found.");
|
|
16551
17403
|
}
|
|
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.");
|
|
17404
|
+
case "transfer_task": {
|
|
17405
|
+
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
17406
|
+
return fail("transfer_task requires boardId, taskId, and targetBoardId.");
|
|
17407
|
+
}
|
|
17408
|
+
const result2 = await transferTaskToBoard(
|
|
17409
|
+
projectRoot,
|
|
17410
|
+
input.boardId,
|
|
17411
|
+
input.taskId,
|
|
17412
|
+
input.targetBoardId,
|
|
17413
|
+
{
|
|
17414
|
+
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
17415
|
+
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
17416
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
17417
|
+
...input.preserveDependencies !== void 0 ? { preserveDependencies: input.preserveDependencies } : {}
|
|
17418
|
+
}
|
|
17419
|
+
);
|
|
17420
|
+
return result2 ? okTask(result2.targetBoard, result2.task, "Task transferred to target board.") : fail("Board or task not found.");
|
|
16570
17421
|
}
|
|
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.");
|
|
17422
|
+
case "get_task": {
|
|
17423
|
+
if (!input.boardId || !input.taskId) return fail("get_task requires boardId and taskId.");
|
|
17424
|
+
const task = await getTask(projectRoot, input.boardId, input.taskId);
|
|
17425
|
+
return task ? { ok: true, message: "Task loaded.", task } : fail("Task not found.");
|
|
16586
17426
|
}
|
|
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.");
|
|
17427
|
+
case "update_task": {
|
|
17428
|
+
if (!input.boardId || !input.taskId)
|
|
17429
|
+
return fail("update_task requires boardId and taskId.");
|
|
17430
|
+
const board = await updateTask(
|
|
17431
|
+
projectRoot,
|
|
17432
|
+
input.boardId,
|
|
17433
|
+
input.taskId,
|
|
17434
|
+
taskPatch(input)
|
|
17435
|
+
);
|
|
17436
|
+
return board ? okBoard(board, "Task updated.") : fail("Task not found.");
|
|
16611
17437
|
}
|
|
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.");
|
|
17438
|
+
case "transition_task": {
|
|
17439
|
+
if (!input.boardId || !input.taskId || !input.lifecycleStage || !input.author || !input.transitionComment) {
|
|
17440
|
+
return fail(
|
|
17441
|
+
"transition_task requires boardId, taskId, lifecycleStage, author, and transitionComment."
|
|
17442
|
+
);
|
|
17443
|
+
}
|
|
17444
|
+
const result2 = await transitionTask(projectRoot, input.boardId, input.taskId, {
|
|
17445
|
+
to: input.lifecycleStage,
|
|
17446
|
+
actor: input.author,
|
|
17447
|
+
comment: input.transitionComment,
|
|
17448
|
+
...input.transitionAction !== void 0 ? { action: input.transitionAction } : {},
|
|
17449
|
+
...input.attachmentUrl !== void 0 ? {
|
|
17450
|
+
attachment: {
|
|
17451
|
+
url: input.attachmentUrl,
|
|
17452
|
+
type: input.attachmentType ?? "url",
|
|
17453
|
+
...input.attachmentTitle !== void 0 ? { title: input.attachmentTitle } : {}
|
|
17454
|
+
}
|
|
17455
|
+
} : {},
|
|
17456
|
+
patch: taskPatch(input)
|
|
17457
|
+
});
|
|
17458
|
+
return result2 ? okTask(result2.board, result2.task, `Task advanced to ${result2.transition.to}.`) : fail("Board or task not found.");
|
|
16653
17459
|
}
|
|
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.");
|
|
17460
|
+
case "move_task": {
|
|
17461
|
+
if (!input.boardId || !input.taskId || !input.targetColumnId) {
|
|
17462
|
+
return fail("move_task requires boardId, taskId, and targetColumnId.");
|
|
17463
|
+
}
|
|
17464
|
+
const board = await moveTask(
|
|
17465
|
+
projectRoot,
|
|
17466
|
+
input.boardId,
|
|
17467
|
+
input.taskId,
|
|
17468
|
+
input.targetColumnId,
|
|
17469
|
+
input.order
|
|
17470
|
+
);
|
|
17471
|
+
return board ? okBoard(board, "Task moved.") : fail("Move failed.");
|
|
16717
17472
|
}
|
|
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.");
|
|
17473
|
+
case "delete_task": {
|
|
17474
|
+
if (!input.boardId || !input.taskId)
|
|
17475
|
+
return fail("delete_task requires boardId and taskId.");
|
|
17476
|
+
const board = await removeTask(projectRoot, input.boardId, input.taskId);
|
|
17477
|
+
return board ? okBoard(board, "Task deleted.") : fail("Task not found.");
|
|
16729
17478
|
}
|
|
16730
|
-
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
|
|
16734
|
-
|
|
16735
|
-
|
|
16736
|
-
|
|
16737
|
-
|
|
16738
|
-
|
|
16739
|
-
|
|
16740
|
-
|
|
16741
|
-
|
|
16742
|
-
|
|
17479
|
+
case "set_chain": {
|
|
17480
|
+
if (!input.boardId || !input.taskIds?.length) {
|
|
17481
|
+
return fail("set_chain requires boardId and taskIds.");
|
|
17482
|
+
}
|
|
17483
|
+
const result2 = await setTaskChain(projectRoot, input.boardId, {
|
|
17484
|
+
taskIds: input.taskIds,
|
|
17485
|
+
...input.chainId !== void 0 ? { chainId: input.chainId } : {},
|
|
17486
|
+
...input.enforceDependencies !== void 0 ? { enforceDependencies: input.enforceDependencies } : {}
|
|
17487
|
+
});
|
|
17488
|
+
return result2 ? {
|
|
17489
|
+
ok: true,
|
|
17490
|
+
message: `Chain set: ${result2.chainId}`,
|
|
17491
|
+
board: result2.board,
|
|
17492
|
+
chain: result2.tasks
|
|
17493
|
+
} : fail("Board or task not found.");
|
|
16743
17494
|
}
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
|
|
16750
|
-
|
|
17495
|
+
case "get_chain": {
|
|
17496
|
+
if (!input.boardId || !(input.taskId || input.chainId)) {
|
|
17497
|
+
return fail("get_chain requires boardId and taskId or chainId.");
|
|
17498
|
+
}
|
|
17499
|
+
const result2 = await getTaskChain(
|
|
17500
|
+
projectRoot,
|
|
17501
|
+
input.boardId,
|
|
17502
|
+
input.taskId ?? input.chainId ?? ""
|
|
17503
|
+
);
|
|
17504
|
+
return result2 ? {
|
|
17505
|
+
ok: true,
|
|
17506
|
+
message: `Chain loaded: ${result2.chainId}`,
|
|
17507
|
+
board: result2.board,
|
|
17508
|
+
chain: result2.tasks
|
|
17509
|
+
} : fail("Chain not found.");
|
|
17510
|
+
}
|
|
17511
|
+
case "claim_task": {
|
|
17512
|
+
const result2 = await claimReadyTask(projectRoot, {
|
|
17513
|
+
...input.boardId !== void 0 ? { boardId: input.boardId } : {},
|
|
17514
|
+
...input.taskId !== void 0 ? { taskId: input.taskId } : {},
|
|
17515
|
+
...assignmentInput(input),
|
|
17516
|
+
status: input.assignmentStatus ?? "queued"
|
|
17517
|
+
});
|
|
17518
|
+
return result2 ? okTask(result2.board, result2.task, "Task claimed.") : fail("No ready kanban task matched the claim.");
|
|
17519
|
+
}
|
|
17520
|
+
case "release_task": {
|
|
17521
|
+
if (!input.boardId || !input.taskId) {
|
|
17522
|
+
return fail("release_task requires boardId and taskId.");
|
|
17523
|
+
}
|
|
17524
|
+
const board = await releaseTaskClaim(projectRoot, input.boardId, input.taskId, {
|
|
17525
|
+
...input.releaseStatus !== void 0 ? { status: input.releaseStatus } : {},
|
|
17526
|
+
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
17527
|
+
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {}
|
|
17528
|
+
});
|
|
17529
|
+
return board ? okBoard(board, "Task claim released.") : fail("Task not found.");
|
|
17530
|
+
}
|
|
17531
|
+
case "assign_task": {
|
|
17532
|
+
if (!input.boardId || !input.taskId)
|
|
17533
|
+
return fail("assign_task requires boardId and taskId.");
|
|
17534
|
+
const board = await assignTask(
|
|
17535
|
+
projectRoot,
|
|
17536
|
+
input.boardId,
|
|
17537
|
+
input.taskId,
|
|
17538
|
+
assignmentInput(input)
|
|
17539
|
+
);
|
|
17540
|
+
return board ? okBoard(board, "Task assigned.") : fail("Task not found.");
|
|
17541
|
+
}
|
|
17542
|
+
case "mark_assignment": {
|
|
17543
|
+
if (!input.boardId || !input.taskId)
|
|
17544
|
+
return fail("mark_assignment requires boardId and taskId.");
|
|
17545
|
+
const assignmentStatus = input.assignmentStatus ?? (input.status === "completed" ? "completed" : input.error ? "failed" : void 0);
|
|
17546
|
+
const board = await updateTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
17547
|
+
...assignmentStatus !== void 0 ? { status: assignmentStatus } : {},
|
|
17548
|
+
...input.subagentId !== void 0 ? { subagentId: input.subagentId } : {},
|
|
17549
|
+
...input.runTaskId !== void 0 ? { runTaskId: input.runTaskId } : {},
|
|
17550
|
+
...input.lastResult !== void 0 ? { lastResult: input.lastResult } : {},
|
|
17551
|
+
...input.error !== void 0 ? { error: input.error } : {},
|
|
17552
|
+
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
17553
|
+
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
17554
|
+
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
17555
|
+
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
17556
|
+
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
17557
|
+
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
17558
|
+
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {}
|
|
17559
|
+
});
|
|
17560
|
+
return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
|
|
17561
|
+
}
|
|
17562
|
+
case "heartbeat_assignment": {
|
|
17563
|
+
if (!input.boardId || !input.taskId) {
|
|
17564
|
+
return fail("heartbeat_assignment requires boardId and taskId.");
|
|
17565
|
+
}
|
|
17566
|
+
const board = await heartbeatTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
17567
|
+
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
17568
|
+
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {}
|
|
17569
|
+
});
|
|
17570
|
+
return board ? okBoard(board, "Assignment heartbeat updated.") : fail("Task assignment not found.");
|
|
17571
|
+
}
|
|
17572
|
+
case "recover_stale": {
|
|
17573
|
+
if (!input.boardId) return fail("recover_stale requires boardId.");
|
|
17574
|
+
const policyFields = [
|
|
17575
|
+
input.recoveryPolicyFailOnCostCeiling !== void 0,
|
|
17576
|
+
input.recoveryPolicyReleaseOnFailureKinds !== void 0,
|
|
17577
|
+
input.recoveryPolicyReleaseOnHeartbeatDue !== void 0,
|
|
17578
|
+
input.recoveryPolicyRetryPolicyOverride !== void 0
|
|
17579
|
+
].some(Boolean);
|
|
17580
|
+
const result2 = await recoverStaleTaskAssignments(projectRoot, input.boardId, {
|
|
17581
|
+
...input.recoveryMode !== void 0 ? { mode: input.recoveryMode } : {},
|
|
17582
|
+
...input.recoveryNow !== void 0 ? { now: input.recoveryNow } : {},
|
|
17583
|
+
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
17584
|
+
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {},
|
|
17585
|
+
...policyFields ? {
|
|
17586
|
+
policy: {
|
|
17587
|
+
...input.recoveryPolicyFailOnCostCeiling !== void 0 ? { failWhenCostCeilingSet: input.recoveryPolicyFailOnCostCeiling } : {},
|
|
17588
|
+
...input.recoveryPolicyReleaseOnFailureKinds !== void 0 ? {
|
|
17589
|
+
releaseOnFailureKinds: input.recoveryPolicyReleaseOnFailureKinds
|
|
17590
|
+
} : {},
|
|
17591
|
+
...input.recoveryPolicyReleaseOnHeartbeatDue !== void 0 ? {
|
|
17592
|
+
releaseOnHeartbeatDue: input.recoveryPolicyReleaseOnHeartbeatDue
|
|
17593
|
+
} : {},
|
|
17594
|
+
...input.recoveryPolicyRetryPolicyOverride !== void 0 ? {
|
|
17595
|
+
retryPolicyOverride: input.recoveryPolicyRetryPolicyOverride
|
|
17596
|
+
} : {}
|
|
17597
|
+
}
|
|
17598
|
+
} : {}
|
|
17599
|
+
});
|
|
17600
|
+
return result2 ? {
|
|
17601
|
+
ok: true,
|
|
17602
|
+
message: `Recovered ${result2.tasks.length} stale assignment(s).`,
|
|
17603
|
+
board: result2.board,
|
|
17604
|
+
recoveredTasks: result2.tasks
|
|
17605
|
+
} : { ok: true, message: "No stale assignment matched.", recoveredTasks: [] };
|
|
17606
|
+
}
|
|
17607
|
+
case "events": {
|
|
17608
|
+
if (!input.boardId) return fail("events requires boardId.");
|
|
17609
|
+
const eventList = await listKanbanEvents(projectRoot, input.boardId);
|
|
17610
|
+
return {
|
|
17611
|
+
ok: true,
|
|
17612
|
+
message: `${eventList.length} event(s).`,
|
|
17613
|
+
events: eventList
|
|
17614
|
+
};
|
|
17615
|
+
}
|
|
17616
|
+
case "queue_health": {
|
|
17617
|
+
const health = await getKanbanQueueHealth(projectRoot, {
|
|
17618
|
+
...input.boardId !== void 0 ? { boardId: input.boardId } : {}
|
|
17619
|
+
});
|
|
17620
|
+
return {
|
|
17621
|
+
ok: true,
|
|
17622
|
+
message: `Counts: ready=${health.counts.ready}, running=${health.counts.running}, stale=${health.staleAssignments.count}.`,
|
|
17623
|
+
queueHealth: health
|
|
17624
|
+
};
|
|
17625
|
+
}
|
|
17626
|
+
case "add_dependency": {
|
|
17627
|
+
if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
|
|
17628
|
+
return fail("add_dependency requires boardId, taskId, and dependencyTaskId.");
|
|
17629
|
+
}
|
|
17630
|
+
const board = await addDependency(
|
|
17631
|
+
projectRoot,
|
|
17632
|
+
input.boardId,
|
|
17633
|
+
input.taskId,
|
|
17634
|
+
input.dependencyTaskId
|
|
17635
|
+
);
|
|
17636
|
+
return board ? okBoard(board, "Dependency added.") : fail("Task not found.");
|
|
17637
|
+
}
|
|
17638
|
+
case "add_goal_metric": {
|
|
17639
|
+
if (!input.boardId || !input.taskId || !input.metricName) {
|
|
17640
|
+
return fail("add_goal_metric requires boardId, taskId, and metricName.");
|
|
17641
|
+
}
|
|
17642
|
+
const board = await addGoalMetricToTask(projectRoot, input.boardId, input.taskId, {
|
|
17643
|
+
name: input.metricName,
|
|
16751
17644
|
...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
|
|
16752
17645
|
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
16753
17646
|
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
16754
17647
|
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
16755
17648
|
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17649
|
+
});
|
|
17650
|
+
return board ? okBoard(board, "Goal metric added.") : fail("Task not found.");
|
|
17651
|
+
}
|
|
17652
|
+
case "update_goal_metric": {
|
|
17653
|
+
if (!input.boardId || !input.taskId || !input.metricId) {
|
|
17654
|
+
return fail("update_goal_metric requires boardId, taskId, and metricId.");
|
|
16756
17655
|
}
|
|
16757
|
-
|
|
16758
|
-
|
|
16759
|
-
|
|
16760
|
-
|
|
16761
|
-
|
|
16762
|
-
|
|
17656
|
+
const board = await updateGoalMetricOnTask(
|
|
17657
|
+
projectRoot,
|
|
17658
|
+
input.boardId,
|
|
17659
|
+
input.taskId,
|
|
17660
|
+
input.metricId,
|
|
17661
|
+
{
|
|
17662
|
+
...input.metricName !== void 0 ? { name: input.metricName } : {},
|
|
17663
|
+
...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
|
|
17664
|
+
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
17665
|
+
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
17666
|
+
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
17667
|
+
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17668
|
+
}
|
|
17669
|
+
);
|
|
17670
|
+
return board ? okBoard(board, "Goal metric updated.") : fail("Metric not found.");
|
|
16763
17671
|
}
|
|
16764
|
-
|
|
16765
|
-
|
|
16766
|
-
|
|
16767
|
-
|
|
16768
|
-
|
|
16769
|
-
|
|
16770
|
-
|
|
16771
|
-
|
|
16772
|
-
|
|
16773
|
-
return
|
|
17672
|
+
case "add_check": {
|
|
17673
|
+
if (!input.boardId || !input.taskId || !input.checkDescription) {
|
|
17674
|
+
return fail("add_check requires boardId, taskId, and checkDescription.");
|
|
17675
|
+
}
|
|
17676
|
+
const board = await addCheckToTask(projectRoot, input.boardId, input.taskId, {
|
|
17677
|
+
description: input.checkDescription,
|
|
17678
|
+
type: "manual",
|
|
17679
|
+
status: input.checkStatus
|
|
17680
|
+
});
|
|
17681
|
+
return board ? okBoard(board, "Check added.") : fail("Task not found.");
|
|
16774
17682
|
}
|
|
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 } : {}
|
|
17683
|
+
case "update_check": {
|
|
17684
|
+
if (!input.boardId || !input.taskId || !input.checkId) {
|
|
17685
|
+
return fail("update_check requires boardId, taskId, and checkId.");
|
|
16783
17686
|
}
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
|
|
16792
|
-
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
|
|
16797
|
-
|
|
16798
|
-
|
|
16799
|
-
|
|
16800
|
-
|
|
16801
|
-
|
|
16802
|
-
|
|
16803
|
-
|
|
16804
|
-
|
|
17687
|
+
const board = await updateCheckOnTask(
|
|
17688
|
+
projectRoot,
|
|
17689
|
+
input.boardId,
|
|
17690
|
+
input.taskId,
|
|
17691
|
+
input.checkId,
|
|
17692
|
+
{
|
|
17693
|
+
...input.checkDescription !== void 0 ? { description: input.checkDescription } : {},
|
|
17694
|
+
...input.checkStatus !== void 0 ? { status: input.checkStatus } : {}
|
|
17695
|
+
}
|
|
17696
|
+
);
|
|
17697
|
+
return board ? okBoard(board, "Check updated.") : fail("Check not found.");
|
|
17698
|
+
}
|
|
17699
|
+
case "add_note": {
|
|
17700
|
+
if (!input.boardId || !input.taskId || !input.note)
|
|
17701
|
+
return fail("add_note requires boardId, taskId, and note.");
|
|
17702
|
+
const board = await addNoteToTask(projectRoot, input.boardId, input.taskId, {
|
|
17703
|
+
author: input.author ?? "agent",
|
|
17704
|
+
content: input.note
|
|
17705
|
+
});
|
|
17706
|
+
return board ? okBoard(board, "Note added.") : fail("Task not found.");
|
|
17707
|
+
}
|
|
17708
|
+
case "add_link": {
|
|
17709
|
+
if (!input.boardId || !input.taskId || !input.url)
|
|
17710
|
+
return fail("add_link requires boardId, taskId, and url.");
|
|
17711
|
+
const board = await addLinkToTask(projectRoot, input.boardId, input.taskId, {
|
|
17712
|
+
url: input.url,
|
|
17713
|
+
type: input.linkType ?? "url",
|
|
17714
|
+
...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
|
|
17715
|
+
});
|
|
17716
|
+
return board ? okBoard(board, "Link added.") : fail("Task not found.");
|
|
17717
|
+
}
|
|
17718
|
+
default:
|
|
17719
|
+
return fail(`Unknown kanban action: ${input.action}`);
|
|
16805
17720
|
}
|
|
16806
|
-
|
|
16807
|
-
|
|
16808
|
-
}
|
|
17721
|
+
})();
|
|
17722
|
+
return withPresence(result);
|
|
16809
17723
|
} catch (err) {
|
|
16810
17724
|
return fail(err instanceof Error ? err.message : String(err));
|
|
16811
17725
|
}
|
|
@@ -16829,6 +17743,7 @@ function taskInput(input) {
|
|
|
16829
17743
|
title: input.title ?? "",
|
|
16830
17744
|
columnId: input.columnId,
|
|
16831
17745
|
description: input.description,
|
|
17746
|
+
dueDate: input.dueDate,
|
|
16832
17747
|
priority: input.priority,
|
|
16833
17748
|
...input.taskType !== void 0 ? { type: input.taskType } : {},
|
|
16834
17749
|
status: input.status,
|
|
@@ -16837,7 +17752,67 @@ function taskInput(input) {
|
|
|
16837
17752
|
...input.assignee ?? assignment?.name ?? assignment?.agentId ? { assignee: input.assignee ?? assignment?.name ?? assignment?.agentId } : {},
|
|
16838
17753
|
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
16839
17754
|
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
|
|
16840
|
-
...
|
|
17755
|
+
...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {},
|
|
17756
|
+
...assignment ? { assignment } : {},
|
|
17757
|
+
...input.order !== void 0 ? { order: input.order } : {},
|
|
17758
|
+
// Sprint 3 durable task-level fields
|
|
17759
|
+
...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
|
|
17760
|
+
...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
|
|
17761
|
+
// Relationships — chainId alone cannot construct a KanbanTaskChainRef,
|
|
17762
|
+
// so chain/previousTaskId/nextTaskId must be set via update_task.
|
|
17763
|
+
...input.childTitles !== void 0 ? { childTaskIds: input.childTitles } : {},
|
|
17764
|
+
// Detail objects
|
|
17765
|
+
...input.checkDescription !== void 0 ? {
|
|
17766
|
+
successCriteria: [
|
|
17767
|
+
{
|
|
17768
|
+
id: randomUUID(),
|
|
17769
|
+
description: input.checkDescription,
|
|
17770
|
+
type: "manual",
|
|
17771
|
+
status: input.checkStatus ?? "pending"
|
|
17772
|
+
}
|
|
17773
|
+
]
|
|
17774
|
+
} : {},
|
|
17775
|
+
...input.metricName !== void 0 ? {
|
|
17776
|
+
goalMetrics: [
|
|
17777
|
+
{
|
|
17778
|
+
id: randomUUID(),
|
|
17779
|
+
name: input.metricName,
|
|
17780
|
+
status: input.metricStatus ?? "pending",
|
|
17781
|
+
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
17782
|
+
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
17783
|
+
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
17784
|
+
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17785
|
+
}
|
|
17786
|
+
]
|
|
17787
|
+
} : {},
|
|
17788
|
+
...input.url !== void 0 ? {
|
|
17789
|
+
links: [
|
|
17790
|
+
{
|
|
17791
|
+
url: input.url,
|
|
17792
|
+
type: input.linkType ?? "url",
|
|
17793
|
+
...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
|
|
17794
|
+
}
|
|
17795
|
+
]
|
|
17796
|
+
} : {},
|
|
17797
|
+
...input.note !== void 0 ? {
|
|
17798
|
+
notes: [
|
|
17799
|
+
{
|
|
17800
|
+
id: randomUUID(),
|
|
17801
|
+
author: input.author ?? "agent",
|
|
17802
|
+
content: input.note,
|
|
17803
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17804
|
+
}
|
|
17805
|
+
]
|
|
17806
|
+
} : {},
|
|
17807
|
+
// Origin tracking
|
|
17808
|
+
...input.graphId !== void 0 ? {
|
|
17809
|
+
origin: {
|
|
17810
|
+
system: input.sourceSystem ?? "kanban-tool",
|
|
17811
|
+
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
17812
|
+
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
17813
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {}
|
|
17814
|
+
}
|
|
17815
|
+
} : {}
|
|
16841
17816
|
};
|
|
16842
17817
|
}
|
|
16843
17818
|
function mergedDependsOn(input) {
|
|
@@ -16851,6 +17826,7 @@ function taskPatch(input) {
|
|
|
16851
17826
|
return {
|
|
16852
17827
|
title: input.title,
|
|
16853
17828
|
description: input.description,
|
|
17829
|
+
dueDate: input.dueDate,
|
|
16854
17830
|
columnId: input.columnId,
|
|
16855
17831
|
order: input.order,
|
|
16856
17832
|
priority: input.priority,
|
|
@@ -17612,7 +18588,7 @@ import {
|
|
|
17612
18588
|
mutateTasks as mutateTasks2,
|
|
17613
18589
|
formatTaskList
|
|
17614
18590
|
} from "@wrongstack/core";
|
|
17615
|
-
import { randomUUID } from "node:crypto";
|
|
18591
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
17616
18592
|
var planTool = {
|
|
17617
18593
|
name: "plan",
|
|
17618
18594
|
category: "Session",
|
|
@@ -17838,7 +18814,7 @@ var planTool = {
|
|
|
17838
18814
|
try {
|
|
17839
18815
|
const taskFile = await mutateTasks2(taskPath, sessionId, (f) => {
|
|
17840
18816
|
f.tasks.push({
|
|
17841
|
-
id: `task_${
|
|
18817
|
+
id: `task_${randomUUID2()}`,
|
|
17842
18818
|
title: taskifyMeta.title,
|
|
17843
18819
|
description: taskifyMeta.details || void 0,
|
|
17844
18820
|
type: "feature",
|
|
@@ -18980,7 +19956,7 @@ import {
|
|
|
18980
19956
|
mutatePlan as mutatePlan3,
|
|
18981
19957
|
formatPlan as formatPlan2
|
|
18982
19958
|
} from "@wrongstack/core";
|
|
18983
|
-
import { randomUUID as
|
|
19959
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
18984
19960
|
function findTaskIndex(tasks, query) {
|
|
18985
19961
|
const asNum = Number.parseInt(query, 10);
|
|
18986
19962
|
if (!Number.isNaN(asNum)) {
|
|
@@ -19168,7 +20144,7 @@ var taskTool = {
|
|
|
19168
20144
|
}
|
|
19169
20145
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
19170
20146
|
const newTask = {
|
|
19171
|
-
id: `task_${Date.now()}_${
|
|
20147
|
+
id: `task_${Date.now()}_${randomUUID3().slice(0, 8)}`,
|
|
19172
20148
|
title: t.title,
|
|
19173
20149
|
description: t.description,
|
|
19174
20150
|
type: t.type || "feature",
|
|
@@ -19229,7 +20205,7 @@ var taskTool = {
|
|
|
19229
20205
|
});
|
|
19230
20206
|
if (match.description) {
|
|
19231
20207
|
todos.push({
|
|
19232
|
-
id: `todo_${ts2}_${
|
|
20208
|
+
id: `todo_${ts2}_${randomUUID3().slice(0, 6)}`,
|
|
19233
20209
|
content: match.description.slice(0, 200),
|
|
19234
20210
|
status: "pending",
|
|
19235
20211
|
promotedFromTask: match.id
|
|
@@ -19238,7 +20214,7 @@ var taskTool = {
|
|
|
19238
20214
|
if (input.subtasks && input.subtasks.length > 0) {
|
|
19239
20215
|
for (const st of input.subtasks) {
|
|
19240
20216
|
todos.push({
|
|
19241
|
-
id: `todo_${ts2}_${
|
|
20217
|
+
id: `todo_${ts2}_${randomUUID3().slice(0, 6)}`,
|
|
19242
20218
|
content: st,
|
|
19243
20219
|
status: "pending",
|
|
19244
20220
|
promotedFromTask: match.id
|
|
@@ -19944,8 +20920,8 @@ var DEFAULT_IGNORE5 = [
|
|
|
19944
20920
|
var treeTool = {
|
|
19945
20921
|
name: "tree",
|
|
19946
20922
|
category: "Filesystem",
|
|
19947
|
-
description: "Display a
|
|
19948
|
-
usageHint: "
|
|
20923
|
+
description: "Display a project or subpath directory tree. Use it for structural layout; use index-backed `codebase-search` first for code understanding when it is live.",
|
|
20924
|
+
usageHint: "DIRECTORY-LAYOUT EXPLORATION:\n\n- When `codebase-search` is live, use it first for symbols and concepts; use `tree` when the directory hierarchy itself matters.\n- Tune `depth` (default 3) and use `glob`/`exclude` to focus the view.\n- Prefer this over raw `bash find` or `glob` + manual reading when you need a quick structural overview.\nOutput is truncated for very large trees.",
|
|
19949
20925
|
permission: "auto",
|
|
19950
20926
|
mutating: false,
|
|
19951
20927
|
capabilities: ["fs.read"],
|
|
@@ -20348,15 +21324,15 @@ var OPTIONAL_TOOLS = [
|
|
|
20348
21324
|
toolUseTool,
|
|
20349
21325
|
batchToolUseTool,
|
|
20350
21326
|
toolHelpTool,
|
|
20351
|
-
codebaseIndexTool,
|
|
20352
|
-
codebaseSearchTool,
|
|
20353
|
-
codebaseStatsTool,
|
|
20354
21327
|
setWorkingDirTool
|
|
20355
21328
|
];
|
|
20356
21329
|
var TIER1_TOOLS = [
|
|
20357
21330
|
readTool,
|
|
20358
21331
|
writeTool,
|
|
20359
21332
|
editTool,
|
|
21333
|
+
codebaseStatsTool,
|
|
21334
|
+
codebaseSearchTool,
|
|
21335
|
+
codebaseIndexTool,
|
|
20360
21336
|
bashTool,
|
|
20361
21337
|
grepTool,
|
|
20362
21338
|
globTool,
|
|
@@ -20395,9 +21371,6 @@ var TIER3_TOOLS = [
|
|
|
20395
21371
|
toolUseTool,
|
|
20396
21372
|
batchToolUseTool,
|
|
20397
21373
|
toolHelpTool,
|
|
20398
|
-
codebaseIndexTool,
|
|
20399
|
-
codebaseSearchTool,
|
|
20400
|
-
codebaseStatsTool,
|
|
20401
21374
|
setWorkingDirTool
|
|
20402
21375
|
];
|
|
20403
21376
|
var builtinTools = [
|
|
@@ -20406,6 +21379,9 @@ var builtinTools = [
|
|
|
20406
21379
|
readTool,
|
|
20407
21380
|
writeTool,
|
|
20408
21381
|
editTool,
|
|
21382
|
+
codebaseStatsTool,
|
|
21383
|
+
codebaseSearchTool,
|
|
21384
|
+
codebaseIndexTool,
|
|
20409
21385
|
replaceTool,
|
|
20410
21386
|
globTool,
|
|
20411
21387
|
grepTool,
|
|
@@ -20440,9 +21416,6 @@ var builtinTools = [
|
|
|
20440
21416
|
toolUseTool,
|
|
20441
21417
|
batchToolUseTool,
|
|
20442
21418
|
toolHelpTool,
|
|
20443
|
-
codebaseIndexTool,
|
|
20444
|
-
codebaseSearchTool,
|
|
20445
|
-
codebaseStatsTool,
|
|
20446
21419
|
setWorkingDirTool
|
|
20447
21420
|
];
|
|
20448
21421
|
|
|
@@ -21680,6 +22653,7 @@ export {
|
|
|
21680
22653
|
TOOL_ICON_CONFIG,
|
|
21681
22654
|
TOOL_ICON_MAP,
|
|
21682
22655
|
_resetProcessRegistry,
|
|
22656
|
+
applySessionKanbanBoardToTodos,
|
|
21683
22657
|
applySessionKanbanTaskToSource,
|
|
21684
22658
|
assertBrowserUrlAllowed,
|
|
21685
22659
|
attachSessionKanbanMirror,
|
|
@@ -21731,6 +22705,7 @@ export {
|
|
|
21731
22705
|
executeLanguagePlan,
|
|
21732
22706
|
executePackagePlan,
|
|
21733
22707
|
fetchTool,
|
|
22708
|
+
fileGraphService,
|
|
21734
22709
|
forgetTool,
|
|
21735
22710
|
formatGlobalStatus,
|
|
21736
22711
|
formatInstanceList,
|
|
@@ -21770,6 +22745,7 @@ export {
|
|
|
21770
22745
|
normalizeShell,
|
|
21771
22746
|
onIndexStateChange,
|
|
21772
22747
|
outdatedTool,
|
|
22748
|
+
packageGraphService,
|
|
21773
22749
|
parseLanguageDiagnostics,
|
|
21774
22750
|
parsePackageReports,
|
|
21775
22751
|
parsePrivateOriginAllowlist,
|
|
@@ -21800,6 +22776,7 @@ export {
|
|
|
21800
22776
|
shutdownCodebaseIndexHost,
|
|
21801
22777
|
startProcessGuardian,
|
|
21802
22778
|
stopProcessGuardian,
|
|
22779
|
+
symbolGraphService,
|
|
21803
22780
|
testTool,
|
|
21804
22781
|
todoTool,
|
|
21805
22782
|
toolHelpTool,
|