@wrongstack/tools 0.287.0 → 0.291.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_fetch-guard.d.ts.map +1 -1
- package/dist/_redact-command.d.ts.map +1 -1
- package/dist/_spawn-stream.d.ts.map +1 -1
- package/dist/audit.js +61 -14
- package/dist/audit.js.map +3 -3
- package/dist/auto-proceed-loop-guard.d.ts +128 -0
- package/dist/auto-proceed-loop-guard.d.ts.map +1 -0
- package/dist/auto-proceed-loop-guard.js +46 -0
- package/dist/auto-proceed-loop-guard.js.map +7 -0
- package/dist/bash.d.ts +1 -1
- package/dist/bash.d.ts.map +1 -1
- package/dist/bash.js +97 -19
- package/dist/bash.js.map +3 -3
- package/dist/builtin.d.ts +12 -10
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +1766 -864
- package/dist/builtin.js.map +3 -3
- package/dist/codebase-index/background-indexer.d.ts +1 -1
- package/dist/codebase-index/background-indexer.d.ts.map +1 -1
- package/dist/codebase-index/bm25.d.ts +1 -2
- package/dist/codebase-index/bm25.d.ts.map +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts +7 -0
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
- package/dist/codebase-index/index-service.d.ts +11 -1
- package/dist/codebase-index/index-service.d.ts.map +1 -1
- package/dist/codebase-index/index.d.ts +2 -1
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +888 -254
- package/dist/codebase-index/index.js.map +3 -3
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/refs-extractor.d.ts +2 -17
- package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
- package/dist/codebase-index/schema.d.ts +43 -1
- package/dist/codebase-index/schema.d.ts.map +1 -1
- package/dist/codebase-index/ts-parser.d.ts.map +1 -1
- package/dist/codebase-index/worker-protocol.d.ts +1 -0
- package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
- package/dist/codebase-index/worker.js +738 -212
- package/dist/codebase-index/worker.js.map +3 -3
- package/dist/codebase-index/writer.d.ts +60 -1
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/e2e.js +477 -0
- package/dist/e2e.js.map +7 -0
- package/dist/exec.d.ts +1 -1
- package/dist/exec.d.ts.map +1 -1
- package/dist/exec.js +60 -5
- package/dist/exec.js.map +3 -3
- package/dist/fetch.js +2 -2
- package/dist/fetch.js.map +2 -2
- package/dist/format.js +61 -14
- package/dist/format.js.map +3 -3
- package/dist/glob.js +2 -2
- package/dist/glob.js.map +1 -1
- package/dist/grep.d.ts.map +1 -1
- package/dist/grep.js +2 -2
- package/dist/grep.js.map +2 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2001 -873
- package/dist/index.js.map +3 -3
- package/dist/install.js +61 -14
- package/dist/install.js.map +3 -3
- package/dist/kanban.d.ts +21 -2
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +713 -572
- package/dist/kanban.js.map +3 -3
- package/dist/languages/detect.d.ts.map +1 -1
- package/dist/languages/index.js +61 -14
- package/dist/languages/index.js.map +3 -3
- package/dist/languages/types.d.ts +7 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/lint.js +61 -14
- package/dist/lint.js.map +3 -3
- package/dist/outdated.js +61 -14
- package/dist/outdated.js.map +3 -3
- package/dist/pack.js +1763 -861
- package/dist/pack.js.map +3 -3
- package/dist/plan.js +4 -0
- package/dist/plan.js.map +2 -2
- package/dist/process-registry.js +12 -5
- package/dist/process-registry.js.map +2 -2
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js.map +2 -2
- package/dist/search.js +2 -2
- package/dist/search.js.map +2 -2
- package/dist/session-kanban.d.ts +9 -0
- package/dist/session-kanban.d.ts.map +1 -1
- package/dist/session-kanban.js +197 -3
- package/dist/session-kanban.js.map +2 -2
- package/dist/task.js +4 -0
- package/dist/task.js.map +2 -2
- package/dist/test.js +61 -14
- package/dist/test.js.map +3 -3
- package/dist/todo.js +4 -0
- package/dist/todo.js.map +2 -2
- package/dist/tree.js +2 -2
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js +61 -14
- package/dist/typecheck.js.map +3 -3
- package/package.json +13 -5
package/dist/builtin.js
CHANGED
|
@@ -337,8 +337,10 @@ function redactCommand(cmd) {
|
|
|
337
337
|
const flag = match.slice(0, match.indexOf(expectDefined(delim)) + 1);
|
|
338
338
|
return `${flag}[REDACTED]`;
|
|
339
339
|
}
|
|
340
|
-
|
|
341
|
-
|
|
340
|
+
if (match.startsWith("--")) {
|
|
341
|
+
return match;
|
|
342
|
+
}
|
|
343
|
+
return `${match.slice(0, 2)}[REDACTED]`;
|
|
342
344
|
});
|
|
343
345
|
}
|
|
344
346
|
return result;
|
|
@@ -350,9 +352,14 @@ var init_redact_command = __esm({
|
|
|
350
352
|
SENSITIVE_FLAG_PATTERNS = [
|
|
351
353
|
// --flag=value or --flag "value" (value captured up to next space or comma)
|
|
352
354
|
/--(?: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,
|
|
353
|
-
// -
|
|
354
|
-
|
|
355
|
-
/
|
|
355
|
+
// -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
|
|
356
|
+
// (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
|
|
357
|
+
// NOTE: synced with @wrongstack/core observability/redact-command.ts.
|
|
358
|
+
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]+/,
|
|
359
|
+
// -p|-password|-a (redis auth) short flags: attached + separated + =value.
|
|
360
|
+
// Same token-start anchor; over-redaction is an accepted tradeoff for a
|
|
361
|
+
// redaction function. Synced with core copy.
|
|
362
|
+
/(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
|
|
356
363
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
357
364
|
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
358
365
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
@@ -799,7 +806,12 @@ var init_win32_resolve = __esm({
|
|
|
799
806
|
|
|
800
807
|
// src/_spawn-stream.ts
|
|
801
808
|
import { spawn as spawn2 } from "node:child_process";
|
|
802
|
-
import {
|
|
809
|
+
import {
|
|
810
|
+
buildChildEnv,
|
|
811
|
+
emitProcessCompleted,
|
|
812
|
+
emitProcessOutput,
|
|
813
|
+
emitProcessStarted
|
|
814
|
+
} from "@wrongstack/core";
|
|
803
815
|
async function* spawnStream(opts) {
|
|
804
816
|
const max = opts.maxBytes ?? 2e5;
|
|
805
817
|
const flushAt = opts.flushBytes ?? 4 * 1024;
|
|
@@ -824,6 +836,19 @@ async function* spawnStream(opts) {
|
|
|
824
836
|
});
|
|
825
837
|
const registry = getProcessRegistry();
|
|
826
838
|
const pid = child.pid;
|
|
839
|
+
const processStartedAt = Date.now();
|
|
840
|
+
let stdoutBytes = 0;
|
|
841
|
+
let stderrBytes = 0;
|
|
842
|
+
let telemetryCompleted = false;
|
|
843
|
+
emitProcessStarted({
|
|
844
|
+
...pid !== void 0 ? { pid } : {},
|
|
845
|
+
parentPid: process.pid,
|
|
846
|
+
command: redactCommand(`${opts.cmd} ${opts.args.join(" ")}`),
|
|
847
|
+
args: redactCommand(opts.args.join(" ")).split(" ").filter(Boolean),
|
|
848
|
+
cwd: opts.cwd,
|
|
849
|
+
background: false,
|
|
850
|
+
startedAt: new Date(processStartedAt).toISOString()
|
|
851
|
+
});
|
|
827
852
|
if (typeof pid === "number") {
|
|
828
853
|
registry.register({
|
|
829
854
|
pid,
|
|
@@ -852,6 +877,8 @@ async function* spawnStream(opts) {
|
|
|
852
877
|
};
|
|
853
878
|
const onOut = (c) => {
|
|
854
879
|
const s = c.toString();
|
|
880
|
+
stdoutBytes += c.byteLength;
|
|
881
|
+
emitProcessOutput({ pid, stream: "stdout", chunk: c });
|
|
855
882
|
if (stdout.length < max) stdout += s;
|
|
856
883
|
spool.write(s);
|
|
857
884
|
queue.push({ kind: "out", data: s });
|
|
@@ -864,6 +891,8 @@ async function* spawnStream(opts) {
|
|
|
864
891
|
};
|
|
865
892
|
const onErr = (c) => {
|
|
866
893
|
const s = c.toString();
|
|
894
|
+
stderrBytes += c.byteLength;
|
|
895
|
+
emitProcessOutput({ pid, stream: "stderr", chunk: c });
|
|
867
896
|
if (stderr.length < max) stderr += s;
|
|
868
897
|
spool.write(s);
|
|
869
898
|
queue.push({ kind: "err", data: s });
|
|
@@ -881,9 +910,25 @@ async function* spawnStream(opts) {
|
|
|
881
910
|
queue.push({ kind: "error", data: e.message });
|
|
882
911
|
wake();
|
|
883
912
|
});
|
|
884
|
-
|
|
913
|
+
const completeTelemetry = (code, signal, timedOut = false) => {
|
|
914
|
+
if (telemetryCompleted) return;
|
|
915
|
+
telemetryCompleted = true;
|
|
916
|
+
emitProcessCompleted({
|
|
917
|
+
...pid !== void 0 ? { pid } : {},
|
|
918
|
+
exitCode: code,
|
|
919
|
+
...signal ? { signal } : {},
|
|
920
|
+
durationMs: Date.now() - processStartedAt,
|
|
921
|
+
stdoutBytes,
|
|
922
|
+
stderrBytes,
|
|
923
|
+
timedOut,
|
|
924
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
925
|
+
});
|
|
926
|
+
};
|
|
927
|
+
child.on("close", (code, signal) => {
|
|
885
928
|
if (typeof pid === "number") registry.unregister(pid);
|
|
886
|
-
|
|
929
|
+
const exitCode2 = code ?? (signal ? 1 : 0);
|
|
930
|
+
completeTelemetry(exitCode2, signal ?? void 0);
|
|
931
|
+
queue.push({ kind: "close", data: "", code: exitCode2, ...signal ? { signal } : {} });
|
|
887
932
|
wake();
|
|
888
933
|
});
|
|
889
934
|
const onAbort = () => {
|
|
@@ -896,6 +941,7 @@ async function* spawnStream(opts) {
|
|
|
896
941
|
}
|
|
897
942
|
}
|
|
898
943
|
queue.push({ kind: "close", data: "", code: 124 });
|
|
944
|
+
completeTelemetry(124, "SIGKILL", true);
|
|
899
945
|
wake();
|
|
900
946
|
};
|
|
901
947
|
if (isWin) {
|
|
@@ -2503,13 +2549,14 @@ async function detectLanguageWorkspaces(options) {
|
|
|
2503
2549
|
const target = options.target ? await canonicalInside(resolveFrom(cwd, options.target), projectRoot, "target") : void 0;
|
|
2504
2550
|
const profiles = (options.profiles ?? languageProfileRegistry.list()).filter((profile) => !options.language || profile.id === options.language).slice().sort((a, b) => a.id.localeCompare(b.id));
|
|
2505
2551
|
const limits = normalizeLimits(options.limits);
|
|
2552
|
+
const extraIgnores = new Set(options.ignoredDirectories ?? []);
|
|
2506
2553
|
const state = {
|
|
2507
2554
|
entries: 0,
|
|
2508
2555
|
truncated: false,
|
|
2509
2556
|
sourcePaths: /* @__PURE__ */ new Map(),
|
|
2510
2557
|
candidates: /* @__PURE__ */ new Map()
|
|
2511
2558
|
};
|
|
2512
|
-
await scanDirectory(projectRoot, 0, profiles, limits, state, options.signal);
|
|
2559
|
+
await scanDirectory(projectRoot, 0, profiles, limits, state, extraIgnores, options.signal);
|
|
2513
2560
|
addSourceFallbacks(projectRoot, profiles, state);
|
|
2514
2561
|
if (target) addTargetEvidence(target, projectRoot, profiles, state);
|
|
2515
2562
|
const workspaces = await Promise.all(
|
|
@@ -2523,7 +2570,7 @@ async function detectLanguageWorkspaces(options) {
|
|
|
2523
2570
|
workspaces
|
|
2524
2571
|
};
|
|
2525
2572
|
}
|
|
2526
|
-
async function scanDirectory(directory, depth, profiles, limits, state, signal) {
|
|
2573
|
+
async function scanDirectory(directory, depth, profiles, limits, state, extraIgnores, signal) {
|
|
2527
2574
|
signal?.throwIfAborted();
|
|
2528
2575
|
if (depth > limits.maxDepth || state.entries >= limits.maxEntries) {
|
|
2529
2576
|
state.truncated = true;
|
|
@@ -2546,20 +2593,20 @@ async function scanDirectory(directory, depth, profiles, limits, state, signal)
|
|
|
2546
2593
|
const fullPath = path4.join(directory, entry.name);
|
|
2547
2594
|
if (entry.isSymbolicLink()) continue;
|
|
2548
2595
|
if (entry.isDirectory()) {
|
|
2549
|
-
if (shouldIgnoreDirectory(entry.name, profiles)) continue;
|
|
2596
|
+
if (shouldIgnoreDirectory(entry.name, profiles, extraIgnores)) continue;
|
|
2550
2597
|
if (depth >= limits.maxDepth) {
|
|
2551
2598
|
state.truncated = true;
|
|
2552
2599
|
continue;
|
|
2553
2600
|
}
|
|
2554
|
-
await scanDirectory(fullPath, depth + 1, profiles, limits, state, signal);
|
|
2601
|
+
await scanDirectory(fullPath, depth + 1, profiles, limits, state, extraIgnores, signal);
|
|
2555
2602
|
continue;
|
|
2556
2603
|
}
|
|
2557
2604
|
if (!entry.isFile()) continue;
|
|
2558
2605
|
collectFileEvidence(directory, fullPath, entry.name, profiles, state);
|
|
2559
2606
|
}
|
|
2560
2607
|
}
|
|
2561
|
-
function collectFileEvidence(directory, fullPath,
|
|
2562
|
-
const lower =
|
|
2608
|
+
function collectFileEvidence(directory, fullPath, basename9, profiles, state) {
|
|
2609
|
+
const lower = basename9.toLowerCase();
|
|
2563
2610
|
const extension = path4.extname(lower);
|
|
2564
2611
|
for (const profile of profiles) {
|
|
2565
2612
|
const detector = profile.detectors.find(
|
|
@@ -2570,7 +2617,7 @@ function collectFileEvidence(directory, fullPath, basename8, profiles, state) {
|
|
|
2570
2617
|
candidate.evidence.push({
|
|
2571
2618
|
kind: detector.kind,
|
|
2572
2619
|
path: fullPath,
|
|
2573
|
-
value:
|
|
2620
|
+
value: basename9,
|
|
2574
2621
|
weight: detector.weight
|
|
2575
2622
|
});
|
|
2576
2623
|
if (detector.kind === "manifest" || detector.kind === "config") {
|
|
@@ -2680,8 +2727,8 @@ function getCandidate(state, profile, root) {
|
|
|
2680
2727
|
}
|
|
2681
2728
|
return candidate;
|
|
2682
2729
|
}
|
|
2683
|
-
function shouldIgnoreDirectory(name, profiles) {
|
|
2684
|
-
if (GLOBAL_IGNORES.has(name) || name.startsWith(".")) return true;
|
|
2730
|
+
function shouldIgnoreDirectory(name, profiles, extraIgnores) {
|
|
2731
|
+
if (GLOBAL_IGNORES.has(name) || extraIgnores.has(name) || name.startsWith(".")) return true;
|
|
2685
2732
|
return profiles.some((profile) => profile.ignoredDirectories.includes(name));
|
|
2686
2733
|
}
|
|
2687
2734
|
function normalizeLimits(input) {
|
|
@@ -4897,6 +4944,11 @@ function parseAuditOutput(json2, exitCode) {
|
|
|
4897
4944
|
// src/bash.ts
|
|
4898
4945
|
import { spawn as spawn3 } from "node:child_process";
|
|
4899
4946
|
import * as os4 from "node:os";
|
|
4947
|
+
import {
|
|
4948
|
+
emitProcessCompleted as emitProcessCompleted2,
|
|
4949
|
+
emitProcessOutput as emitProcessOutput2,
|
|
4950
|
+
emitProcessStarted as emitProcessStarted2
|
|
4951
|
+
} from "@wrongstack/core";
|
|
4900
4952
|
|
|
4901
4953
|
// src/_env.ts
|
|
4902
4954
|
import { buildChildEnv as buildChildEnv2 } from "@wrongstack/core";
|
|
@@ -5766,6 +5818,32 @@ var bashTool = {
|
|
|
5766
5818
|
}
|
|
5767
5819
|
}
|
|
5768
5820
|
const pid2 = child2.pid;
|
|
5821
|
+
let stdoutBytes2 = 0;
|
|
5822
|
+
let stderrBytes2 = 0;
|
|
5823
|
+
let telemetryCompleted2 = false;
|
|
5824
|
+
emitProcessStarted2({
|
|
5825
|
+
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
5826
|
+
parentPid: process.pid,
|
|
5827
|
+
command: redactCommand(`${shell} ${args.join(" ")}`),
|
|
5828
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
5829
|
+
cwd: ctx.projectRoot,
|
|
5830
|
+
background: true,
|
|
5831
|
+
startedAt: new Date(startedAt).toISOString()
|
|
5832
|
+
});
|
|
5833
|
+
const completeBackground = (exitCode, signal) => {
|
|
5834
|
+
if (telemetryCompleted2) return;
|
|
5835
|
+
telemetryCompleted2 = true;
|
|
5836
|
+
emitProcessCompleted2({
|
|
5837
|
+
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
5838
|
+
exitCode,
|
|
5839
|
+
...signal ? { signal } : {},
|
|
5840
|
+
durationMs: Date.now() - startedAt,
|
|
5841
|
+
stdoutBytes: stdoutBytes2,
|
|
5842
|
+
stderrBytes: stderrBytes2,
|
|
5843
|
+
timedOut: false,
|
|
5844
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5845
|
+
});
|
|
5846
|
+
};
|
|
5769
5847
|
if (typeof pid2 === "number") {
|
|
5770
5848
|
registry.register({
|
|
5771
5849
|
pid: pid2,
|
|
@@ -5778,7 +5856,10 @@ var bashTool = {
|
|
|
5778
5856
|
});
|
|
5779
5857
|
child2.on("close", () => registry.unregister(pid2));
|
|
5780
5858
|
}
|
|
5781
|
-
const onBgData = (chunk) => {
|
|
5859
|
+
const onBgData = (chunk, stream) => {
|
|
5860
|
+
if (stream === "stdout") stdoutBytes2 += chunk.byteLength;
|
|
5861
|
+
else stderrBytes2 += chunk.byteLength;
|
|
5862
|
+
emitProcessOutput2({ pid: pid2, stream, chunk });
|
|
5782
5863
|
if (truncated) return;
|
|
5783
5864
|
const remain = MAX_OUTPUT - buf2.length;
|
|
5784
5865
|
if (remain > 0) {
|
|
@@ -5786,24 +5867,28 @@ var bashTool = {
|
|
|
5786
5867
|
}
|
|
5787
5868
|
if (buf2.length >= MAX_OUTPUT) {
|
|
5788
5869
|
truncated = true;
|
|
5789
|
-
child2.stdout?.off("data",
|
|
5790
|
-
child2.stderr?.off("data",
|
|
5870
|
+
child2.stdout?.off("data", onBgStdout);
|
|
5871
|
+
child2.stderr?.off("data", onBgStderr);
|
|
5791
5872
|
}
|
|
5792
5873
|
};
|
|
5793
|
-
|
|
5794
|
-
|
|
5874
|
+
const onBgStdout = (chunk) => onBgData(chunk, "stdout");
|
|
5875
|
+
const onBgStderr = (chunk) => onBgData(chunk, "stderr");
|
|
5876
|
+
child2.stdout?.on("data", onBgStdout);
|
|
5877
|
+
child2.stderr?.on("data", onBgStderr);
|
|
5795
5878
|
const cleanupBackground = () => {
|
|
5796
|
-
child2.stdout?.off("data",
|
|
5797
|
-
child2.stderr?.off("data",
|
|
5879
|
+
child2.stdout?.off("data", onBgStdout);
|
|
5880
|
+
child2.stderr?.off("data", onBgStderr);
|
|
5798
5881
|
};
|
|
5799
5882
|
child2.on("error", () => {
|
|
5800
5883
|
cleanupBackground();
|
|
5801
5884
|
if (typeof pid2 === "number") registry.unregister(pid2);
|
|
5802
5885
|
registry.afterCall(Date.now() - startedAt, true, bypassBreaker);
|
|
5886
|
+
completeBackground(1);
|
|
5803
5887
|
});
|
|
5804
|
-
child2.on("close", () => {
|
|
5888
|
+
child2.on("close", (code, signal) => {
|
|
5805
5889
|
cleanupBackground();
|
|
5806
5890
|
registry.afterCall(Date.now() - startedAt, false, bypassBreaker);
|
|
5891
|
+
completeBackground(code ?? (signal ? 1 : 0), signal ?? void 0);
|
|
5807
5892
|
});
|
|
5808
5893
|
if (typeof pid2 === "number") child2.unref();
|
|
5809
5894
|
yield {
|
|
@@ -5843,6 +5928,32 @@ var bashTool = {
|
|
|
5843
5928
|
}
|
|
5844
5929
|
}
|
|
5845
5930
|
const pid = child.pid;
|
|
5931
|
+
let stdoutBytes = 0;
|
|
5932
|
+
let stderrBytes = 0;
|
|
5933
|
+
let telemetryCompleted = false;
|
|
5934
|
+
emitProcessStarted2({
|
|
5935
|
+
...pid !== void 0 ? { pid } : {},
|
|
5936
|
+
parentPid: process.pid,
|
|
5937
|
+
command: redactCommand(`${shell} ${args.join(" ")}`),
|
|
5938
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
5939
|
+
cwd: ctx.projectRoot,
|
|
5940
|
+
background: false,
|
|
5941
|
+
startedAt: new Date(startedAt).toISOString()
|
|
5942
|
+
});
|
|
5943
|
+
const completeForeground = (exitCode, signal) => {
|
|
5944
|
+
if (telemetryCompleted) return;
|
|
5945
|
+
telemetryCompleted = true;
|
|
5946
|
+
emitProcessCompleted2({
|
|
5947
|
+
...pid !== void 0 ? { pid } : {},
|
|
5948
|
+
exitCode,
|
|
5949
|
+
...signal ? { signal } : {},
|
|
5950
|
+
durationMs: Date.now() - startedAt,
|
|
5951
|
+
stdoutBytes,
|
|
5952
|
+
stderrBytes,
|
|
5953
|
+
timedOut,
|
|
5954
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5955
|
+
});
|
|
5956
|
+
};
|
|
5846
5957
|
if (typeof pid === "number") {
|
|
5847
5958
|
registry.register({
|
|
5848
5959
|
pid,
|
|
@@ -5944,8 +6055,11 @@ var bashTool = {
|
|
|
5944
6055
|
child.stderr?.resume();
|
|
5945
6056
|
}
|
|
5946
6057
|
};
|
|
5947
|
-
const onData = (chunk) => {
|
|
6058
|
+
const onData = (chunk, stream) => {
|
|
5948
6059
|
const text = chunk.toString();
|
|
6060
|
+
if (stream === "stdout") stdoutBytes += chunk.byteLength;
|
|
6061
|
+
else stderrBytes += chunk.byteLength;
|
|
6062
|
+
emitProcessOutput2({ pid, stream, chunk });
|
|
5949
6063
|
if (buf.length < MAX_OUTPUT) {
|
|
5950
6064
|
buf += text.slice(0, MAX_OUTPUT - buf.length);
|
|
5951
6065
|
}
|
|
@@ -5954,17 +6068,21 @@ var bashTool = {
|
|
|
5954
6068
|
push({ kind: "data", text });
|
|
5955
6069
|
pauseIfFlooded();
|
|
5956
6070
|
};
|
|
5957
|
-
|
|
5958
|
-
|
|
6071
|
+
const onStdoutData = (chunk) => onData(chunk, "stdout");
|
|
6072
|
+
const onStderrData = (chunk) => onData(chunk, "stderr");
|
|
6073
|
+
child.stdout?.on("data", onStdoutData);
|
|
6074
|
+
child.stderr?.on("data", onStderrData);
|
|
5959
6075
|
child.on("error", (err) => {
|
|
5960
6076
|
for (const t of timers) clearTimeout(t);
|
|
5961
6077
|
registry.afterCall(Date.now() - startedAt, true);
|
|
6078
|
+
completeForeground(1);
|
|
5962
6079
|
push({ kind: "error", err });
|
|
5963
6080
|
});
|
|
5964
|
-
child.on("close", (code) => {
|
|
6081
|
+
child.on("close", (code, signal) => {
|
|
5965
6082
|
for (const t of timers) clearTimeout(t);
|
|
5966
6083
|
if (typeof pid === "number") registry.unregister(pid);
|
|
5967
6084
|
registry.afterCall(Date.now() - startedAt, code !== 0 && code !== null);
|
|
6085
|
+
completeForeground(timedOut ? 124 : code ?? (signal ? 1 : 0), signal ?? void 0);
|
|
5968
6086
|
push({ kind: "end", code });
|
|
5969
6087
|
});
|
|
5970
6088
|
try {
|
|
@@ -6009,8 +6127,8 @@ ${hint}` : ""),
|
|
|
6009
6127
|
for (const t of timers) clearTimeout(t);
|
|
6010
6128
|
spool.finalize();
|
|
6011
6129
|
if (isWin4) opts.signal.removeEventListener("abort", onAbort);
|
|
6012
|
-
child.stdout?.off("data",
|
|
6013
|
-
child.stderr?.off("data",
|
|
6130
|
+
child.stdout?.off("data", onStdoutData);
|
|
6131
|
+
child.stderr?.off("data", onStderrData);
|
|
6014
6132
|
child.stdout?.destroy();
|
|
6015
6133
|
child.stderr?.destroy();
|
|
6016
6134
|
if (child.exitCode === null && !child.killed) {
|
|
@@ -7500,7 +7618,7 @@ import * as path13 from "node:path";
|
|
|
7500
7618
|
import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core";
|
|
7501
7619
|
|
|
7502
7620
|
// src/codebase-index/schema.ts
|
|
7503
|
-
var SCHEMA_VERSION =
|
|
7621
|
+
var SCHEMA_VERSION = 3;
|
|
7504
7622
|
|
|
7505
7623
|
// src/codebase-index/lsp-kind.ts
|
|
7506
7624
|
function lspKindToInternalKind(k) {
|
|
@@ -7543,7 +7661,7 @@ function tokenise(text) {
|
|
|
7543
7661
|
return sanitised.toLowerCase().split(" ").filter(Boolean);
|
|
7544
7662
|
}
|
|
7545
7663
|
function splitName(name) {
|
|
7546
|
-
return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
7664
|
+
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();
|
|
7547
7665
|
}
|
|
7548
7666
|
function buildIndexableText(name, signature, docComment) {
|
|
7549
7667
|
return [splitName(name), name, signature, docComment].filter(Boolean).join(" ");
|
|
@@ -7569,14 +7687,12 @@ function buildBm25Index(docs) {
|
|
|
7569
7687
|
return new Bm25Index(documents, df, N, avgLen);
|
|
7570
7688
|
}
|
|
7571
7689
|
var Bm25Index = class {
|
|
7572
|
-
constructor(documents,
|
|
7690
|
+
constructor(documents, _df, N, avgLen) {
|
|
7573
7691
|
this.documents = documents;
|
|
7574
|
-
this.df = df;
|
|
7575
7692
|
this.N = N;
|
|
7576
7693
|
this.safeAvgLen = avgLen === 0 ? 1 : avgLen;
|
|
7577
7694
|
}
|
|
7578
7695
|
documents;
|
|
7579
|
-
df;
|
|
7580
7696
|
N;
|
|
7581
7697
|
safeAvgLen;
|
|
7582
7698
|
score(query, filter) {
|
|
@@ -7589,10 +7705,13 @@ var Bm25Index = class {
|
|
|
7589
7705
|
for (const qTerm of qTokens) {
|
|
7590
7706
|
let tf = 0;
|
|
7591
7707
|
for (const t of doc.tokens) {
|
|
7592
|
-
if (t
|
|
7708
|
+
if (t.startsWith(qTerm)) tf++;
|
|
7593
7709
|
}
|
|
7594
7710
|
if (tf === 0) continue;
|
|
7595
|
-
|
|
7711
|
+
let dfVal = 0;
|
|
7712
|
+
for (const candidate of this.documents) {
|
|
7713
|
+
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
7714
|
+
}
|
|
7596
7715
|
if (dfVal === 0) continue;
|
|
7597
7716
|
const idf = Math.log((this.N - dfVal + 0.5) / (dfVal + 0.5) + 1);
|
|
7598
7717
|
const lenRatio = B * (doc.len / this.safeAvgLen);
|
|
@@ -7625,6 +7744,29 @@ var Bm25Index = class {
|
|
|
7625
7744
|
|
|
7626
7745
|
// src/codebase-index/writer.ts
|
|
7627
7746
|
var DB_FILE = "index.db";
|
|
7747
|
+
function escapeLike(value) {
|
|
7748
|
+
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
7749
|
+
}
|
|
7750
|
+
function assignRefsToSymbols(refs, symbols) {
|
|
7751
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
7752
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
7753
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7754
|
+
const assigned = [];
|
|
7755
|
+
for (const ref of refs) {
|
|
7756
|
+
let owner2;
|
|
7757
|
+
for (const symbol of ordered) {
|
|
7758
|
+
if (symbol.line > ref.line) break;
|
|
7759
|
+
owner2 = symbol;
|
|
7760
|
+
}
|
|
7761
|
+
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
7762
|
+
if (!owner2 || owner2.id <= 0) continue;
|
|
7763
|
+
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
7764
|
+
if (seen.has(key)) continue;
|
|
7765
|
+
seen.add(key);
|
|
7766
|
+
assigned.push({ ...ref, fromId: owner2.id });
|
|
7767
|
+
}
|
|
7768
|
+
return assigned;
|
|
7769
|
+
}
|
|
7628
7770
|
function resolveIndexDir(projectRoot, override) {
|
|
7629
7771
|
return override ?? resolveWstackPaths2({ projectRoot }).projectCodebaseIndex;
|
|
7630
7772
|
}
|
|
@@ -7632,6 +7774,53 @@ function codebaseIndexDirOverride(ctx) {
|
|
|
7632
7774
|
const v = ctx.meta?.["codebaseIndexDir"];
|
|
7633
7775
|
return typeof v === "string" ? v : void 0;
|
|
7634
7776
|
}
|
|
7777
|
+
var StorePool = class {
|
|
7778
|
+
stores = /* @__PURE__ */ new Map();
|
|
7779
|
+
key(projectRoot, indexDir) {
|
|
7780
|
+
return `${projectRoot}\0${indexDir ?? ""}`;
|
|
7781
|
+
}
|
|
7782
|
+
/** Borrow a store. Creates it on first access for this key. */
|
|
7783
|
+
acquire(projectRoot, opts) {
|
|
7784
|
+
const k = this.key(projectRoot, opts?.indexDir);
|
|
7785
|
+
let store = this.stores.get(k);
|
|
7786
|
+
if (!store) {
|
|
7787
|
+
store = new IndexStore(projectRoot, { indexDir: opts?.indexDir });
|
|
7788
|
+
this.stores.set(k, store);
|
|
7789
|
+
}
|
|
7790
|
+
return store;
|
|
7791
|
+
}
|
|
7792
|
+
/** Return the store to the pool. The connection stays warm for subsequent
|
|
7793
|
+
* operations on the same (projectRoot, indexDir). */
|
|
7794
|
+
release(_store) {
|
|
7795
|
+
}
|
|
7796
|
+
/** Close every pooled connection and drain the pool. Call on shutdown. */
|
|
7797
|
+
closeAll() {
|
|
7798
|
+
for (const store of this.stores.values()) {
|
|
7799
|
+
try {
|
|
7800
|
+
store.close();
|
|
7801
|
+
} catch {
|
|
7802
|
+
}
|
|
7803
|
+
}
|
|
7804
|
+
this.stores.clear();
|
|
7805
|
+
}
|
|
7806
|
+
/** Remove one store from the pool. Used by tests that need isolation. */
|
|
7807
|
+
evict(projectRoot, indexDir) {
|
|
7808
|
+
const k = this.key(projectRoot, indexDir);
|
|
7809
|
+
const store = this.stores.get(k);
|
|
7810
|
+
if (store) {
|
|
7811
|
+
try {
|
|
7812
|
+
store.close();
|
|
7813
|
+
} catch {
|
|
7814
|
+
}
|
|
7815
|
+
this.stores.delete(k);
|
|
7816
|
+
}
|
|
7817
|
+
}
|
|
7818
|
+
/** True when the pool holds a connection for the given key. */
|
|
7819
|
+
has(projectRoot, indexDir) {
|
|
7820
|
+
return this.stores.has(this.key(projectRoot, indexDir));
|
|
7821
|
+
}
|
|
7822
|
+
};
|
|
7823
|
+
var indexStorePool = new StorePool();
|
|
7635
7824
|
var warningSilenced = false;
|
|
7636
7825
|
function silenceSqliteExperimentalWarning() {
|
|
7637
7826
|
if (warningSilenced) return;
|
|
@@ -7678,7 +7867,7 @@ function sleepSync(ms) {
|
|
|
7678
7867
|
} catch {
|
|
7679
7868
|
}
|
|
7680
7869
|
}
|
|
7681
|
-
var IndexStore = class {
|
|
7870
|
+
var IndexStore = class _IndexStore {
|
|
7682
7871
|
db;
|
|
7683
7872
|
/** Absolute path to this project's index directory. */
|
|
7684
7873
|
indexDir;
|
|
@@ -7730,10 +7919,7 @@ var IndexStore = class {
|
|
|
7730
7919
|
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
7731
7920
|
throw new LockError(`SQLite lock conflict after ${MAX_LOCK_RETRIES} retries: ${msg}`);
|
|
7732
7921
|
}
|
|
7733
|
-
const delay = Math.min(
|
|
7734
|
-
LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt,
|
|
7735
|
-
LOCK_RETRY_MAX_DELAY_MS
|
|
7736
|
-
);
|
|
7922
|
+
const delay = Math.min(LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt, LOCK_RETRY_MAX_DELAY_MS);
|
|
7737
7923
|
sleepSync(delay);
|
|
7738
7924
|
}
|
|
7739
7925
|
}
|
|
@@ -7815,8 +8001,24 @@ var IndexStore = class {
|
|
|
7815
8001
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)");
|
|
7816
8002
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)");
|
|
7817
8003
|
try {
|
|
7818
|
-
this.db.exec(
|
|
8004
|
+
this.db.exec(
|
|
8005
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')"
|
|
8006
|
+
);
|
|
7819
8007
|
this.ftsAvailable = true;
|
|
8008
|
+
const symbolCount = Number(
|
|
8009
|
+
this.db.prepare("SELECT COUNT(*) AS n FROM symbols").get()?.n ?? 0
|
|
8010
|
+
);
|
|
8011
|
+
const ftsCount = Number(
|
|
8012
|
+
this.db.prepare("SELECT COUNT(*) AS n FROM symbols_fts").get()?.n ?? 0
|
|
8013
|
+
);
|
|
8014
|
+
if (symbolCount !== ftsCount) {
|
|
8015
|
+
this.db.exec("DELETE FROM symbols_fts");
|
|
8016
|
+
const insert = this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)");
|
|
8017
|
+
const rows = this.db.prepare("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
|
|
8018
|
+
for (const row of rows) {
|
|
8019
|
+
insert.run(row.id, buildIndexableText(row.name, row.signature, row.doc_comment));
|
|
8020
|
+
}
|
|
8021
|
+
}
|
|
7820
8022
|
} catch {
|
|
7821
8023
|
this.ftsAvailable = false;
|
|
7822
8024
|
}
|
|
@@ -7836,7 +8038,7 @@ var IndexStore = class {
|
|
|
7836
8038
|
return this.runWithRetry(() => {
|
|
7837
8039
|
this.db.exec("BEGIN IMMEDIATE");
|
|
7838
8040
|
try {
|
|
7839
|
-
const maxRows = this.
|
|
8041
|
+
const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
7840
8042
|
let nextId = (maxRows[0]?.m ?? 0) + 1;
|
|
7841
8043
|
const stmt = this.db.prepare(
|
|
7842
8044
|
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
@@ -7874,7 +8076,9 @@ var IndexStore = class {
|
|
|
7874
8076
|
deleteSymbolsForFile(file) {
|
|
7875
8077
|
this.runWithRetry(() => {
|
|
7876
8078
|
if (this.ftsAvailable) {
|
|
7877
|
-
this.stmt(
|
|
8079
|
+
this.stmt(
|
|
8080
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8081
|
+
).run(file);
|
|
7878
8082
|
}
|
|
7879
8083
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
7880
8084
|
});
|
|
@@ -7886,9 +8090,21 @@ var IndexStore = class {
|
|
|
7886
8090
|
*/
|
|
7887
8091
|
deleteFile(file) {
|
|
7888
8092
|
this.runWithRetry(() => {
|
|
7889
|
-
this.
|
|
7890
|
-
|
|
7891
|
-
|
|
8093
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
8094
|
+
try {
|
|
8095
|
+
if (this.ftsAvailable) {
|
|
8096
|
+
this.db.prepare(
|
|
8097
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8098
|
+
).run(file);
|
|
8099
|
+
}
|
|
8100
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)").run(file);
|
|
8101
|
+
this.db.prepare("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
8102
|
+
this.db.prepare("DELETE FROM files WHERE file = ?").run(file);
|
|
8103
|
+
this.db.exec("COMMIT");
|
|
8104
|
+
} catch (err) {
|
|
8105
|
+
this.db.exec("ROLLBACK");
|
|
8106
|
+
throw err;
|
|
8107
|
+
}
|
|
7892
8108
|
});
|
|
7893
8109
|
}
|
|
7894
8110
|
// ─── File metadata ──────────────────────────────────────────────────────────
|
|
@@ -7911,14 +8127,22 @@ var IndexStore = class {
|
|
|
7911
8127
|
).all(file);
|
|
7912
8128
|
if (!rows.length) return null;
|
|
7913
8129
|
const r = expectDefined2(rows[0]);
|
|
7914
|
-
return {
|
|
8130
|
+
return {
|
|
8131
|
+
file: r.file,
|
|
8132
|
+
lang: r.lang,
|
|
8133
|
+
mtimeMs: r.mtime_ms,
|
|
8134
|
+
symbolCount: r.symbol_count,
|
|
8135
|
+
lastIndexed: r.last_indexed
|
|
8136
|
+
};
|
|
7915
8137
|
}
|
|
7916
8138
|
getAllFileMetas() {
|
|
7917
|
-
return this.db.prepare(
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
8139
|
+
return this.db.prepare("SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files").all().map((r) => ({
|
|
8140
|
+
file: r.file,
|
|
8141
|
+
lang: r.lang,
|
|
8142
|
+
mtimeMs: r.mtime_ms,
|
|
8143
|
+
symbolCount: r.symbol_count,
|
|
8144
|
+
lastIndexed: r.last_indexed
|
|
8145
|
+
}));
|
|
7922
8146
|
}
|
|
7923
8147
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
7924
8148
|
search(query, filter) {
|
|
@@ -7942,8 +8166,8 @@ var IndexStore = class {
|
|
|
7942
8166
|
values.push(filter.lang);
|
|
7943
8167
|
}
|
|
7944
8168
|
if (filter?.file) {
|
|
7945
|
-
conditions.push("file LIKE ?");
|
|
7946
|
-
values.push(`%${filter.file}%`);
|
|
8169
|
+
conditions.push("replace(file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
8170
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
7947
8171
|
}
|
|
7948
8172
|
if (query.trim()) {
|
|
7949
8173
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
@@ -7981,9 +8205,11 @@ var IndexStore = class {
|
|
|
7981
8205
|
* "users", camelCase-split text makes "complex" find "complexOperation").
|
|
7982
8206
|
*/
|
|
7983
8207
|
searchRanked(query, filter, limit) {
|
|
8208
|
+
const rawLimit = Number.isFinite(limit) ? Math.trunc(limit) : 20;
|
|
8209
|
+
const safeLimit = Math.max(1, Math.min(rawLimit, 100));
|
|
7984
8210
|
const tokens = tokenise(query);
|
|
7985
8211
|
if (tokens.length === 0 || !this.ftsAvailable) {
|
|
7986
|
-
return this.searchRankedFallback(query, filter,
|
|
8212
|
+
return this.searchRankedFallback(query, filter, safeLimit);
|
|
7987
8213
|
}
|
|
7988
8214
|
let effectiveKind = filter?.kind;
|
|
7989
8215
|
if (filter?.lspKind !== void 0) {
|
|
@@ -8003,11 +8229,13 @@ var IndexStore = class {
|
|
|
8003
8229
|
values.push(filter.lang);
|
|
8004
8230
|
}
|
|
8005
8231
|
if (filter?.file) {
|
|
8006
|
-
conditions.push("s.file LIKE ?");
|
|
8007
|
-
values.push(`%${filter.file}%`);
|
|
8232
|
+
conditions.push("replace(s.file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
8233
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
8008
8234
|
}
|
|
8009
8235
|
const where = conditions.join(" AND ");
|
|
8010
|
-
const countRows = this.db.prepare(
|
|
8236
|
+
const countRows = this.db.prepare(
|
|
8237
|
+
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
8238
|
+
).all(...values);
|
|
8011
8239
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
8012
8240
|
if (total === 0) return { results: [], total: 0 };
|
|
8013
8241
|
const rows = this.db.prepare(
|
|
@@ -8016,9 +8244,13 @@ var IndexStore = class {
|
|
|
8016
8244
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
8017
8245
|
FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
8018
8246
|
WHERE ${where}
|
|
8019
|
-
ORDER BY
|
|
8247
|
+
ORDER BY
|
|
8248
|
+
CASE WHEN lower(s.name) = lower(?) THEN 0
|
|
8249
|
+
WHEN lower(s.name) LIKE lower(?) ESCAPE '\\' THEN 1
|
|
8250
|
+
ELSE 2 END,
|
|
8251
|
+
bm25(symbols_fts), lower(s.name), s.file, s.line, s.col, s.id
|
|
8020
8252
|
LIMIT ?`
|
|
8021
|
-
).all(...values,
|
|
8253
|
+
).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
8022
8254
|
return {
|
|
8023
8255
|
results: rows.map((r) => ({
|
|
8024
8256
|
id: r.id,
|
|
@@ -8048,10 +8280,28 @@ var IndexStore = class {
|
|
|
8048
8280
|
}
|
|
8049
8281
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
8050
8282
|
const bm25 = buildBm25Index(
|
|
8051
|
-
candidates.map((c) => ({
|
|
8283
|
+
candidates.map((c) => ({
|
|
8284
|
+
id: c.id,
|
|
8285
|
+
text: buildIndexableText(c.name, c.signature, c.docComment)
|
|
8286
|
+
}))
|
|
8052
8287
|
);
|
|
8053
8288
|
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
8054
|
-
|
|
8289
|
+
const q = query.trim().toLowerCase();
|
|
8290
|
+
const rank = (id) => {
|
|
8291
|
+
const name = candidateById.get(id)?.name.toLowerCase() ?? "";
|
|
8292
|
+
if (name === q) return 0;
|
|
8293
|
+
if (name.startsWith(q)) return 1;
|
|
8294
|
+
return 2;
|
|
8295
|
+
};
|
|
8296
|
+
scored.sort((a, b) => {
|
|
8297
|
+
const rankDiff = rank(a.id) - rank(b.id);
|
|
8298
|
+
if (rankDiff !== 0) return rankDiff;
|
|
8299
|
+
const scoreDiff = b.score - a.score;
|
|
8300
|
+
if (scoreDiff !== 0) return scoreDiff;
|
|
8301
|
+
const left = expectDefined2(candidateById.get(a.id));
|
|
8302
|
+
const right = expectDefined2(candidateById.get(b.id));
|
|
8303
|
+
return left.name.localeCompare(right.name) || left.file.localeCompare(right.file) || left.line - right.line || left.col - right.col || left.id - right.id;
|
|
8304
|
+
});
|
|
8055
8305
|
const qTokens = tokenise(query);
|
|
8056
8306
|
const results = scored.slice(0, limit).map(({ id, score }) => {
|
|
8057
8307
|
const c = expectDefined2(candidateById.get(id));
|
|
@@ -8060,9 +8310,7 @@ var IndexStore = class {
|
|
|
8060
8310
|
return { results, total: candidates.length };
|
|
8061
8311
|
}
|
|
8062
8312
|
getAllIndexable() {
|
|
8063
|
-
return this.
|
|
8064
|
-
({ id, text }) => ({ id, text })
|
|
8065
|
-
);
|
|
8313
|
+
return this.stmt("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
|
|
8066
8314
|
}
|
|
8067
8315
|
/**
|
|
8068
8316
|
* Largest symbol id currently in the table (0 when empty). New ids must be
|
|
@@ -8072,28 +8320,22 @@ var IndexStore = class {
|
|
|
8072
8320
|
* `symbols.id`). Ids may have gaps — that is fine.
|
|
8073
8321
|
*/
|
|
8074
8322
|
getMaxSymbolId() {
|
|
8075
|
-
const rows = this.
|
|
8323
|
+
const rows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
8076
8324
|
return rows[0]?.m ?? 0;
|
|
8077
8325
|
}
|
|
8078
8326
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
8079
8327
|
getStats() {
|
|
8080
8328
|
const sizeBytes = this.sizeBytes();
|
|
8081
|
-
const lastRows = this.db.prepare(
|
|
8082
|
-
"SELECT value FROM metadata WHERE key = 'last_indexed'"
|
|
8083
|
-
).all();
|
|
8329
|
+
const lastRows = this.db.prepare("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
8084
8330
|
const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
|
|
8085
|
-
const totalRows = this.
|
|
8331
|
+
const totalRows = this.stmt("SELECT COUNT(*) FROM symbols").all();
|
|
8086
8332
|
const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
|
|
8087
|
-
const fileRows = this.
|
|
8333
|
+
const fileRows = this.stmt("SELECT COUNT(*) FROM files").all();
|
|
8088
8334
|
const totalFiles = fileRows[0] ? Number(fileRows[0]["COUNT(*)"]) : 0;
|
|
8089
|
-
const langRows = this.
|
|
8090
|
-
"SELECT lang, COUNT(*) FROM symbols GROUP BY lang"
|
|
8091
|
-
).all();
|
|
8335
|
+
const langRows = this.stmt("SELECT lang, COUNT(*) FROM symbols GROUP BY lang").all();
|
|
8092
8336
|
const byLang = {};
|
|
8093
8337
|
for (const row of langRows) byLang[row.lang] = Number(row["COUNT(*)"]);
|
|
8094
|
-
const kindRows = this.
|
|
8095
|
-
"SELECT kind, COUNT(*) FROM symbols GROUP BY kind"
|
|
8096
|
-
).all();
|
|
8338
|
+
const kindRows = this.stmt("SELECT kind, COUNT(*) FROM symbols GROUP BY kind").all();
|
|
8097
8339
|
const byKind = {};
|
|
8098
8340
|
for (const row of kindRows) byKind[row.kind] = Number(row["COUNT(*)"]);
|
|
8099
8341
|
return {
|
|
@@ -8109,17 +8351,34 @@ var IndexStore = class {
|
|
|
8109
8351
|
}
|
|
8110
8352
|
setLastIndexed(ts2) {
|
|
8111
8353
|
this.runWithRetry(() => {
|
|
8112
|
-
this.db.prepare(
|
|
8113
|
-
|
|
8114
|
-
|
|
8354
|
+
this.db.prepare("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
|
|
8355
|
+
});
|
|
8356
|
+
}
|
|
8357
|
+
getMetadata(key) {
|
|
8358
|
+
const rows = this.db.prepare("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
8359
|
+
return rows[0]?.value;
|
|
8360
|
+
}
|
|
8361
|
+
setMetadata(key, value) {
|
|
8362
|
+
this.runWithRetry(() => {
|
|
8363
|
+
this.db.prepare("INSERT OR REPLACE INTO metadata(key, value) VALUES(?, ?)").run(key, value);
|
|
8115
8364
|
});
|
|
8116
8365
|
}
|
|
8117
8366
|
clearAll() {
|
|
8118
8367
|
this.runWithRetry(() => {
|
|
8119
|
-
this.db.exec("
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8368
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
8369
|
+
try {
|
|
8370
|
+
this.db.exec("DROP TABLE IF EXISTS refs");
|
|
8371
|
+
this.db.exec("DROP TABLE IF EXISTS symbols");
|
|
8372
|
+
this.db.exec("DROP TABLE IF EXISTS files");
|
|
8373
|
+
this.db.exec("DROP TABLE IF EXISTS metadata");
|
|
8374
|
+
if (this.ftsAvailable) this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
8375
|
+
this.db.exec("COMMIT");
|
|
8376
|
+
this.stmtCache.clear();
|
|
8377
|
+
this.initSchema();
|
|
8378
|
+
} catch (err) {
|
|
8379
|
+
this.db.exec("ROLLBACK");
|
|
8380
|
+
throw err;
|
|
8381
|
+
}
|
|
8123
8382
|
});
|
|
8124
8383
|
}
|
|
8125
8384
|
// ─── Ref CRUD ────────────────────────────────────────────────────────────────
|
|
@@ -8200,7 +8459,7 @@ var IndexStore = class {
|
|
|
8200
8459
|
).run(...options.deleteForFiles);
|
|
8201
8460
|
this.db.prepare(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
8202
8461
|
}
|
|
8203
|
-
const maxRows = this.
|
|
8462
|
+
const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
8204
8463
|
let nextId = (maxRows[0]?.m ?? 0) + 1;
|
|
8205
8464
|
const symStmt = this.db.prepare(
|
|
8206
8465
|
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
@@ -8210,6 +8469,7 @@ var IndexStore = class {
|
|
|
8210
8469
|
const allInserted = [];
|
|
8211
8470
|
const refsToInsert = [];
|
|
8212
8471
|
for (const entry of entries) {
|
|
8472
|
+
const insertedForEntry = [];
|
|
8213
8473
|
for (const s of entry.symbols) {
|
|
8214
8474
|
const id = nextId++;
|
|
8215
8475
|
symStmt.run(
|
|
@@ -8227,9 +8487,11 @@ var IndexStore = class {
|
|
|
8227
8487
|
s.file
|
|
8228
8488
|
);
|
|
8229
8489
|
ftsStmt?.run(id, buildIndexableText(s.name, s.signature, s.docComment));
|
|
8230
|
-
|
|
8490
|
+
const inserted = { ...s, id };
|
|
8491
|
+
allInserted.push(inserted);
|
|
8492
|
+
insertedForEntry.push(inserted);
|
|
8231
8493
|
}
|
|
8232
|
-
|
|
8494
|
+
refsToInsert.push(...assignRefsToSymbols(entry.refs, insertedForEntry));
|
|
8233
8495
|
}
|
|
8234
8496
|
if (refsToInsert.length > 0) {
|
|
8235
8497
|
const refStmt = this.db.prepare(
|
|
@@ -8267,9 +8529,7 @@ var IndexStore = class {
|
|
|
8267
8529
|
*/
|
|
8268
8530
|
deleteRefsForFile(file) {
|
|
8269
8531
|
this.runWithRetry(() => {
|
|
8270
|
-
this.db.prepare(
|
|
8271
|
-
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
8272
|
-
).run(file);
|
|
8532
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
|
|
8273
8533
|
});
|
|
8274
8534
|
}
|
|
8275
8535
|
/**
|
|
@@ -8310,9 +8570,7 @@ var IndexStore = class {
|
|
|
8310
8570
|
* Find all references FROM a given symbol (what does this symbol call/use?).
|
|
8311
8571
|
*/
|
|
8312
8572
|
findRefsFrom(symbolId) {
|
|
8313
|
-
return this.db.prepare(
|
|
8314
|
-
"SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?"
|
|
8315
|
-
).all(symbolId).map((r) => ({
|
|
8573
|
+
return this.db.prepare("SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?").all(symbolId).map((r) => ({
|
|
8316
8574
|
id: r.id,
|
|
8317
8575
|
fromId: r.from_id,
|
|
8318
8576
|
toName: r.to_name,
|
|
@@ -8329,6 +8587,356 @@ var IndexStore = class {
|
|
|
8329
8587
|
return 0;
|
|
8330
8588
|
}
|
|
8331
8589
|
}
|
|
8590
|
+
// ─── CodeMap graph aggregation ──────────────────────────────────────────────
|
|
8591
|
+
/**
|
|
8592
|
+
* Derive a monorepo package name from an absolute file path.
|
|
8593
|
+
* Handles both `packages/<name>/...` (workspace) and `src/<name>/...` layouts.
|
|
8594
|
+
* Returns `undefined` for files outside any recognisable package structure.
|
|
8595
|
+
*/
|
|
8596
|
+
static derivePackage(filePath) {
|
|
8597
|
+
const f = filePath.replace(/\\/g, "/");
|
|
8598
|
+
const pkgsIdx = f.indexOf("/packages/");
|
|
8599
|
+
if (pkgsIdx !== -1) {
|
|
8600
|
+
const rest = f.slice(pkgsIdx + "/packages/".length);
|
|
8601
|
+
const seg = rest.split("/")[0];
|
|
8602
|
+
return seg ? `@wrongstack/${seg}` : void 0;
|
|
8603
|
+
}
|
|
8604
|
+
const appsIdx = f.indexOf("/apps/");
|
|
8605
|
+
if (appsIdx !== -1) {
|
|
8606
|
+
const rest = f.slice(appsIdx + "/apps/".length);
|
|
8607
|
+
const seg = rest.split("/")[0];
|
|
8608
|
+
return seg ? `app:${seg}` : void 0;
|
|
8609
|
+
}
|
|
8610
|
+
return void 0;
|
|
8611
|
+
}
|
|
8612
|
+
static packageFromImport(moduleName) {
|
|
8613
|
+
if (!moduleName.startsWith("@wrongstack/")) return void 0;
|
|
8614
|
+
const parts = moduleName.split("/");
|
|
8615
|
+
return parts[1] ? `@wrongstack/${parts[1]}` : void 0;
|
|
8616
|
+
}
|
|
8617
|
+
static resolveRelativeImport(fromFile, moduleName, indexedFiles) {
|
|
8618
|
+
if (!moduleName.startsWith(".")) return void 0;
|
|
8619
|
+
const normalizedFrom = fromFile.replace(/\\/g, "/");
|
|
8620
|
+
const absolute = path13.posix.normalize(
|
|
8621
|
+
path13.posix.join(path13.posix.dirname(normalizedFrom), moduleName)
|
|
8622
|
+
);
|
|
8623
|
+
const extension = path13.posix.extname(absolute);
|
|
8624
|
+
const base = extension ? absolute.slice(0, -extension.length) : absolute;
|
|
8625
|
+
const candidates = [
|
|
8626
|
+
absolute,
|
|
8627
|
+
...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
|
|
8628
|
+
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path13.posix.join(absolute, `index${ext}`)),
|
|
8629
|
+
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path13.posix.join(base, `index${ext}`))
|
|
8630
|
+
];
|
|
8631
|
+
const indexedByPortablePath = new Map(
|
|
8632
|
+
[...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
|
|
8633
|
+
);
|
|
8634
|
+
for (const candidate of candidates) {
|
|
8635
|
+
const indexed = indexedByPortablePath.get(candidate.toLocaleLowerCase());
|
|
8636
|
+
if (indexed) return indexed;
|
|
8637
|
+
}
|
|
8638
|
+
return void 0;
|
|
8639
|
+
}
|
|
8640
|
+
/**
|
|
8641
|
+
* Package-level graph: each workspace package is a node; edges are derived
|
|
8642
|
+
* from cross-package symbol references (a symbol in package A references a
|
|
8643
|
+
* symbol resolved in package B). Node metadata includes symbol/file counts.
|
|
8644
|
+
*/
|
|
8645
|
+
getPackageGraph() {
|
|
8646
|
+
const symbols = this.db.prepare("SELECT file, id FROM symbols ORDER BY id").all();
|
|
8647
|
+
const pkgNodes = /* @__PURE__ */ new Map();
|
|
8648
|
+
const fileToPkg = /* @__PURE__ */ new Map();
|
|
8649
|
+
const symbolToPkg = /* @__PURE__ */ new Map();
|
|
8650
|
+
for (const s of symbols) {
|
|
8651
|
+
const pkg = _IndexStore.derivePackage(s.file) ?? "(root)";
|
|
8652
|
+
fileToPkg.set(s.file, pkg);
|
|
8653
|
+
symbolToPkg.set(s.id, pkg);
|
|
8654
|
+
const node = pkgNodes.get(pkg);
|
|
8655
|
+
if (node) {
|
|
8656
|
+
node.symbolCount = (node.symbolCount ?? 0) + 1;
|
|
8657
|
+
} else {
|
|
8658
|
+
pkgNodes.set(pkg, {
|
|
8659
|
+
id: `pkg:${pkg}`,
|
|
8660
|
+
label: pkg,
|
|
8661
|
+
kind: "package",
|
|
8662
|
+
package: pkg,
|
|
8663
|
+
symbolCount: 1,
|
|
8664
|
+
fileCount: 0
|
|
8665
|
+
});
|
|
8666
|
+
}
|
|
8667
|
+
}
|
|
8668
|
+
const files = this.db.prepare("SELECT DISTINCT file FROM files").all();
|
|
8669
|
+
for (const { file } of files) {
|
|
8670
|
+
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
8671
|
+
const node = pkgNodes.get(pkg);
|
|
8672
|
+
if (node) node.fileCount = (node.fileCount ?? 0) + 1;
|
|
8673
|
+
}
|
|
8674
|
+
const refRows = this.db.prepare(
|
|
8675
|
+
`SELECT r.from_id, r.to_id, r.call_type
|
|
8676
|
+
FROM refs r
|
|
8677
|
+
WHERE r.to_id IS NOT NULL`
|
|
8678
|
+
).all();
|
|
8679
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
8680
|
+
for (const r of refRows) {
|
|
8681
|
+
if (r.call_type === "import") continue;
|
|
8682
|
+
const fromPkg = symbolToPkg.get(r.from_id);
|
|
8683
|
+
const toPkg = symbolToPkg.get(r.to_id);
|
|
8684
|
+
if (!fromPkg || !toPkg || fromPkg === toPkg) continue;
|
|
8685
|
+
const key = `${fromPkg}\0${toPkg}`;
|
|
8686
|
+
let e = edgeMap.get(key);
|
|
8687
|
+
if (!e) {
|
|
8688
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8689
|
+
edgeMap.set(key, e);
|
|
8690
|
+
}
|
|
8691
|
+
e.weight++;
|
|
8692
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
8693
|
+
}
|
|
8694
|
+
const importRows = this.db.prepare(
|
|
8695
|
+
`SELECT r.from_id, r.to_name
|
|
8696
|
+
FROM refs r
|
|
8697
|
+
WHERE r.call_type = 'import'`
|
|
8698
|
+
).all();
|
|
8699
|
+
for (const r of importRows) {
|
|
8700
|
+
const fromPkg = symbolToPkg.get(r.from_id);
|
|
8701
|
+
const toPkg = _IndexStore.packageFromImport(r.to_name);
|
|
8702
|
+
if (!fromPkg || !toPkg || fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
8703
|
+
const key = `${fromPkg}\0${toPkg}`;
|
|
8704
|
+
let edge = edgeMap.get(key);
|
|
8705
|
+
if (!edge) {
|
|
8706
|
+
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8707
|
+
edgeMap.set(key, edge);
|
|
8708
|
+
}
|
|
8709
|
+
edge.weight++;
|
|
8710
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
|
|
8711
|
+
}
|
|
8712
|
+
const edges = [];
|
|
8713
|
+
for (const [key, e] of edgeMap) {
|
|
8714
|
+
const [source, target] = key.split("\0");
|
|
8715
|
+
let bestType = "call";
|
|
8716
|
+
let bestCount = 0;
|
|
8717
|
+
for (const [t, c] of e.types) {
|
|
8718
|
+
if (c > bestCount) {
|
|
8719
|
+
bestType = t;
|
|
8720
|
+
bestCount = c;
|
|
8721
|
+
}
|
|
8722
|
+
}
|
|
8723
|
+
edges.push({
|
|
8724
|
+
source: `pkg:${source}`,
|
|
8725
|
+
target: `pkg:${target}`,
|
|
8726
|
+
weight: e.weight,
|
|
8727
|
+
refType: bestType
|
|
8728
|
+
});
|
|
8729
|
+
}
|
|
8730
|
+
return { nodes: [...pkgNodes.values()], edges };
|
|
8731
|
+
}
|
|
8732
|
+
/**
|
|
8733
|
+
* File-level graph for a single package: each file is a node; edges are
|
|
8734
|
+
* derived from cross-file symbol references within the package.
|
|
8735
|
+
*/
|
|
8736
|
+
getFileGraph(packageFilter) {
|
|
8737
|
+
const allFiles = this.db.prepare("SELECT DISTINCT file FROM symbols").all();
|
|
8738
|
+
const pkgFilePaths = allFiles.filter((f) => (_IndexStore.derivePackage(f.file) ?? "(root)") === packageFilter).map((f) => f.file);
|
|
8739
|
+
const localFiles = new Set(pkgFilePaths);
|
|
8740
|
+
if (localFiles.size === 0) return { nodes: [], edges: [] };
|
|
8741
|
+
const filePlaceholders = [...localFiles].map(() => "?").join(",");
|
|
8742
|
+
const pkgSyms = this.db.prepare(
|
|
8743
|
+
`SELECT file, id, name, kind, lang, line FROM symbols WHERE file IN (${filePlaceholders}) ORDER BY id`
|
|
8744
|
+
).all(...pkgFilePaths);
|
|
8745
|
+
const fileNodes = /* @__PURE__ */ new Map();
|
|
8746
|
+
const symToFile = /* @__PURE__ */ new Map();
|
|
8747
|
+
const fileStats = /* @__PURE__ */ new Map();
|
|
8748
|
+
for (const s of pkgSyms) {
|
|
8749
|
+
symToFile.set(s.id, s.file);
|
|
8750
|
+
const current = fileStats.get(s.file);
|
|
8751
|
+
fileStats.set(s.file, {
|
|
8752
|
+
count: (current?.count ?? 0) + 1,
|
|
8753
|
+
lang: current?.lang ?? s.lang
|
|
8754
|
+
});
|
|
8755
|
+
}
|
|
8756
|
+
const ensureFileNode = (file) => {
|
|
8757
|
+
if (fileNodes.has(file)) return;
|
|
8758
|
+
const stats = fileStats.get(file);
|
|
8759
|
+
fileNodes.set(file, {
|
|
8760
|
+
id: `file:${file}`,
|
|
8761
|
+
label: file.replace(/\\/g, "/").split("/").pop() ?? file,
|
|
8762
|
+
kind: "file",
|
|
8763
|
+
package: _IndexStore.derivePackage(file) ?? "(root)",
|
|
8764
|
+
file,
|
|
8765
|
+
symbolCount: stats?.count ?? 0,
|
|
8766
|
+
lang: stats?.lang,
|
|
8767
|
+
external: !localFiles.has(file)
|
|
8768
|
+
});
|
|
8769
|
+
};
|
|
8770
|
+
for (const file of localFiles) {
|
|
8771
|
+
ensureFileNode(file);
|
|
8772
|
+
}
|
|
8773
|
+
const indexedFiles = new Set(allFiles.map((f) => f.file));
|
|
8774
|
+
const refRows = this.db.prepare(
|
|
8775
|
+
`SELECT r.from_id, r.to_id, r.call_type
|
|
8776
|
+
FROM refs r
|
|
8777
|
+
WHERE (r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
8778
|
+
OR r.to_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders})))
|
|
8779
|
+
AND r.to_id IS NOT NULL`
|
|
8780
|
+
).all(...pkgFilePaths, ...pkgFilePaths);
|
|
8781
|
+
const knownSymIds = new Set(pkgSyms.map((s) => s.id));
|
|
8782
|
+
const crossRefIds = /* @__PURE__ */ new Set();
|
|
8783
|
+
for (const r of refRows) {
|
|
8784
|
+
if (!knownSymIds.has(r.from_id)) crossRefIds.add(r.from_id);
|
|
8785
|
+
if (!knownSymIds.has(r.to_id)) crossRefIds.add(r.to_id);
|
|
8786
|
+
}
|
|
8787
|
+
if (crossRefIds.size > 0) {
|
|
8788
|
+
const crossPlaceholders = [...crossRefIds].map(() => "?").join(",");
|
|
8789
|
+
const extras = this.db.prepare(
|
|
8790
|
+
`SELECT id, file FROM symbols WHERE id IN (${crossPlaceholders})`
|
|
8791
|
+
).all(...crossRefIds);
|
|
8792
|
+
for (const x of extras) {
|
|
8793
|
+
symToFile.set(x.id, x.file);
|
|
8794
|
+
if (!fileStats.has(x.file)) {
|
|
8795
|
+
fileStats.set(x.file, { count: 0, lang: "ts" });
|
|
8796
|
+
}
|
|
8797
|
+
}
|
|
8798
|
+
}
|
|
8799
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
8800
|
+
for (const r of refRows) {
|
|
8801
|
+
if (r.call_type === "import") continue;
|
|
8802
|
+
const fromFile = symToFile.get(r.from_id);
|
|
8803
|
+
const toFile = symToFile.get(r.to_id);
|
|
8804
|
+
if (!fromFile || !toFile || fromFile === toFile) continue;
|
|
8805
|
+
if (!localFiles.has(fromFile) && !localFiles.has(toFile)) continue;
|
|
8806
|
+
ensureFileNode(fromFile);
|
|
8807
|
+
ensureFileNode(toFile);
|
|
8808
|
+
const key = `${fromFile}\0${toFile}`;
|
|
8809
|
+
let e = edgeMap.get(key);
|
|
8810
|
+
if (!e) {
|
|
8811
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8812
|
+
edgeMap.set(key, e);
|
|
8813
|
+
}
|
|
8814
|
+
e.weight++;
|
|
8815
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
8816
|
+
}
|
|
8817
|
+
const importRows = this.db.prepare(
|
|
8818
|
+
`SELECT r.from_id, r.to_name
|
|
8819
|
+
FROM refs r
|
|
8820
|
+
WHERE r.call_type = 'import'
|
|
8821
|
+
AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))`
|
|
8822
|
+
).all(...pkgFilePaths);
|
|
8823
|
+
for (const r of importRows) {
|
|
8824
|
+
const fromFile = symToFile.get(r.from_id);
|
|
8825
|
+
if (!fromFile || !localFiles.has(fromFile)) continue;
|
|
8826
|
+
const toFile = _IndexStore.resolveRelativeImport(fromFile, r.to_name, indexedFiles);
|
|
8827
|
+
if (!toFile || fromFile === toFile) continue;
|
|
8828
|
+
ensureFileNode(fromFile);
|
|
8829
|
+
ensureFileNode(toFile);
|
|
8830
|
+
const key = `${fromFile}\0${toFile}`;
|
|
8831
|
+
let edge = edgeMap.get(key);
|
|
8832
|
+
if (!edge) {
|
|
8833
|
+
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8834
|
+
edgeMap.set(key, edge);
|
|
8835
|
+
}
|
|
8836
|
+
edge.weight++;
|
|
8837
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
|
|
8838
|
+
}
|
|
8839
|
+
const edges = [];
|
|
8840
|
+
for (const [key, e] of edgeMap) {
|
|
8841
|
+
const [source, target] = key.split("\0");
|
|
8842
|
+
let bestType = "call";
|
|
8843
|
+
let bestCount = 0;
|
|
8844
|
+
for (const [t, c] of e.types) {
|
|
8845
|
+
if (c > bestCount) {
|
|
8846
|
+
bestType = t;
|
|
8847
|
+
bestCount = c;
|
|
8848
|
+
}
|
|
8849
|
+
}
|
|
8850
|
+
edges.push({
|
|
8851
|
+
source: `file:${source}`,
|
|
8852
|
+
target: `file:${target}`,
|
|
8853
|
+
weight: e.weight,
|
|
8854
|
+
refType: bestType
|
|
8855
|
+
});
|
|
8856
|
+
}
|
|
8857
|
+
return { nodes: [...fileNodes.values()], edges };
|
|
8858
|
+
}
|
|
8859
|
+
/**
|
|
8860
|
+
* Symbol-level graph for a single file: each symbol is a node; edges are
|
|
8861
|
+
* derived from intra-file and cross-file symbol references (who calls whom).
|
|
8862
|
+
*/
|
|
8863
|
+
getSymbolGraph(fileFilter) {
|
|
8864
|
+
const syms = this.db.prepare(
|
|
8865
|
+
"SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE file = ? ORDER BY line, id"
|
|
8866
|
+
).all(fileFilter);
|
|
8867
|
+
if (syms.length === 0) return { nodes: [], edges: [] };
|
|
8868
|
+
const symById = new Map(syms.map((symbol) => [symbol.id, symbol]));
|
|
8869
|
+
const relatedIds = new Set(syms.map((symbol) => symbol.id));
|
|
8870
|
+
const toGraphNode = (s) => ({
|
|
8871
|
+
id: `sym:${s.id}`,
|
|
8872
|
+
label: s.name,
|
|
8873
|
+
kind: "symbol",
|
|
8874
|
+
symbolId: s.id,
|
|
8875
|
+
symbolKind: s.kind,
|
|
8876
|
+
file: s.file,
|
|
8877
|
+
package: _IndexStore.derivePackage(s.file) ?? "(root)",
|
|
8878
|
+
lang: s.lang,
|
|
8879
|
+
line: s.line,
|
|
8880
|
+
signature: s.signature,
|
|
8881
|
+
scope: s.scope,
|
|
8882
|
+
external: s.file !== fileFilter
|
|
8883
|
+
});
|
|
8884
|
+
const symIds = new Set(syms.map((s) => s.id));
|
|
8885
|
+
const refRows = this.db.prepare(
|
|
8886
|
+
`SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
8887
|
+
FROM refs r
|
|
8888
|
+
WHERE r.from_id IN (SELECT id FROM symbols WHERE file = ?)
|
|
8889
|
+
OR r.to_id IN (SELECT id FROM symbols WHERE file = ?)`
|
|
8890
|
+
).all(fileFilter, fileFilter);
|
|
8891
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
8892
|
+
for (const r of refRows) {
|
|
8893
|
+
if (r.to_id === null) continue;
|
|
8894
|
+
if (!symIds.has(r.from_id) && !symIds.has(r.to_id)) continue;
|
|
8895
|
+
relatedIds.add(r.from_id);
|
|
8896
|
+
relatedIds.add(r.to_id);
|
|
8897
|
+
const key = `${r.from_id}\0${r.to_id}`;
|
|
8898
|
+
let e = edgeMap.get(key);
|
|
8899
|
+
if (!e) {
|
|
8900
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8901
|
+
edgeMap.set(key, e);
|
|
8902
|
+
}
|
|
8903
|
+
e.weight++;
|
|
8904
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
8905
|
+
}
|
|
8906
|
+
const edges = [];
|
|
8907
|
+
for (const [key, e] of edgeMap) {
|
|
8908
|
+
const [fromId, toId] = key.split("\0").map(Number);
|
|
8909
|
+
let bestType = "call";
|
|
8910
|
+
let bestCount = 0;
|
|
8911
|
+
for (const [t, c] of e.types) {
|
|
8912
|
+
if (c > bestCount) {
|
|
8913
|
+
bestType = t;
|
|
8914
|
+
bestCount = c;
|
|
8915
|
+
}
|
|
8916
|
+
}
|
|
8917
|
+
edges.push({
|
|
8918
|
+
source: `sym:${fromId}`,
|
|
8919
|
+
target: `sym:${toId}`,
|
|
8920
|
+
weight: e.weight,
|
|
8921
|
+
refType: bestType
|
|
8922
|
+
});
|
|
8923
|
+
}
|
|
8924
|
+
const loadedIds = new Set(syms.map((s) => s.id));
|
|
8925
|
+
const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
|
|
8926
|
+
if (missingIds.length > 0) {
|
|
8927
|
+
const placeholders = missingIds.map(() => "?").join(",");
|
|
8928
|
+
const extras = this.db.prepare(
|
|
8929
|
+
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders})`
|
|
8930
|
+
).all(...missingIds);
|
|
8931
|
+
for (const s of extras) symById.set(s.id, s);
|
|
8932
|
+
}
|
|
8933
|
+
const nodes = [...relatedIds].map((id) => symById.get(id)).filter((symbol) => symbol !== void 0).sort((a, b) => {
|
|
8934
|
+
const aExternal = a.file === fileFilter ? 0 : 1;
|
|
8935
|
+
const bExternal = b.file === fileFilter ? 0 : 1;
|
|
8936
|
+
return aExternal - bExternal || a.file.localeCompare(b.file) || a.line - b.line || a.id - b.id;
|
|
8937
|
+
}).map(toGraphNode);
|
|
8938
|
+
return { nodes, edges };
|
|
8939
|
+
}
|
|
8332
8940
|
close() {
|
|
8333
8941
|
this.stmtCache.clear();
|
|
8334
8942
|
try {
|
|
@@ -8347,6 +8955,7 @@ import { Worker } from "node:worker_threads";
|
|
|
8347
8955
|
import { expectDefined as expectDefined6 } from "@wrongstack/core";
|
|
8348
8956
|
import * as fs14 from "node:fs/promises";
|
|
8349
8957
|
import * as path19 from "node:path";
|
|
8958
|
+
import { availableParallelism } from "node:os";
|
|
8350
8959
|
import { compileGlob as compileGlob2 } from "@wrongstack/core";
|
|
8351
8960
|
|
|
8352
8961
|
// src/codebase-index/ts-parser.ts
|
|
@@ -8403,8 +9012,7 @@ function extToLang(ext) {
|
|
|
8403
9012
|
return null;
|
|
8404
9013
|
}
|
|
8405
9014
|
}
|
|
8406
|
-
function getSignature(node, sourceFile) {
|
|
8407
|
-
const printer = ts.createPrinter({});
|
|
9015
|
+
function getSignature(printer, node, sourceFile) {
|
|
8408
9016
|
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
8409
9017
|
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
8410
9018
|
}
|
|
@@ -8423,20 +9031,14 @@ function getJsDoc(node, sourceFile) {
|
|
|
8423
9031
|
}
|
|
8424
9032
|
return "";
|
|
8425
9033
|
}
|
|
8426
|
-
function
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
if (
|
|
8431
|
-
parts.
|
|
8432
|
-
} else if (ts.isMethodDeclaration(current) || ts.isGetAccessor(current) || ts.isSetAccessor(current) || ts.isPropertyDeclaration(current) || ts.isFunctionDeclaration(current)) {
|
|
8433
|
-
if (current.name && ts.isIdentifier(current.name)) {
|
|
8434
|
-
parts.unshift(current.name.text);
|
|
8435
|
-
}
|
|
9034
|
+
function pushScopeName(node, parts) {
|
|
9035
|
+
if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
|
|
9036
|
+
parts.push(node.name?.text ?? "Anon");
|
|
9037
|
+
} else if (ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node) || ts.isPropertyDeclaration(node) || ts.isFunctionDeclaration(node)) {
|
|
9038
|
+
if (node.name && ts.isIdentifier(node.name)) {
|
|
9039
|
+
parts.push(node.name.text);
|
|
8436
9040
|
}
|
|
8437
|
-
current = current.parent;
|
|
8438
9041
|
}
|
|
8439
|
-
return parts.join(".");
|
|
8440
9042
|
}
|
|
8441
9043
|
function parseSymbols(opts) {
|
|
8442
9044
|
const { file, content, lang } = opts;
|
|
@@ -8447,41 +9049,39 @@ function parseSymbols(opts) {
|
|
|
8447
9049
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
8448
9050
|
}
|
|
8449
9051
|
const symbols = [];
|
|
8450
|
-
|
|
9052
|
+
const refs = [];
|
|
9053
|
+
const printer = ts.createPrinter({});
|
|
9054
|
+
function visit(node, funcDepth, scopeParts) {
|
|
8451
9055
|
const kind = kindOf(node);
|
|
8452
9056
|
if (kind) {
|
|
8453
|
-
const
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
9057
|
+
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && funcDepth > 0) {
|
|
9058
|
+
} else {
|
|
9059
|
+
const nameNode = node.name;
|
|
9060
|
+
if (!nameNode || !ts.isIdentifier(nameNode)) {
|
|
9061
|
+
return;
|
|
9062
|
+
}
|
|
9063
|
+
const name = nameNode.text;
|
|
9064
|
+
const pos2 = nameNode.getStart(sourceFile);
|
|
9065
|
+
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(pos2);
|
|
9066
|
+
const scope = scopeParts.join(".");
|
|
9067
|
+
const signature = getSignature(printer, node, sourceFile);
|
|
9068
|
+
const docComment = getJsDoc(node, sourceFile);
|
|
9069
|
+
const text = [name, signature, docComment].filter(Boolean).join(" | ");
|
|
9070
|
+
symbols.push({
|
|
9071
|
+
id: 0,
|
|
9072
|
+
lang,
|
|
9073
|
+
kind,
|
|
9074
|
+
name,
|
|
9075
|
+
file,
|
|
9076
|
+
line: line2 + 1,
|
|
9077
|
+
col: character,
|
|
9078
|
+
signature,
|
|
9079
|
+
docComment,
|
|
9080
|
+
scope,
|
|
9081
|
+
text
|
|
9082
|
+
});
|
|
9083
|
+
}
|
|
8475
9084
|
}
|
|
8476
|
-
ts.forEachChild(node, visit);
|
|
8477
|
-
}
|
|
8478
|
-
visit(sourceFile);
|
|
8479
|
-
const refs = extractRefs(sourceFile);
|
|
8480
|
-
return { file, lang, symbols, refs, mtimeMs: Date.now() };
|
|
8481
|
-
}
|
|
8482
|
-
function extractRefs(sourceFile) {
|
|
8483
|
-
const refs = [];
|
|
8484
|
-
function visit(node) {
|
|
8485
9085
|
const pos = node.getStart(sourceFile);
|
|
8486
9086
|
const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
|
|
8487
9087
|
const lineNum = line + 1;
|
|
@@ -8506,10 +9106,14 @@ function extractRefs(sourceFile) {
|
|
|
8506
9106
|
const moduleName = getModuleName(node);
|
|
8507
9107
|
if (moduleName) refs.push({ fromId: 0, toName: moduleName, callType: "import", line: lineNum });
|
|
8508
9108
|
}
|
|
8509
|
-
|
|
9109
|
+
const scopeIdx = scopeParts.length;
|
|
9110
|
+
pushScopeName(node, scopeParts);
|
|
9111
|
+
const childFuncDepth = ts.isFunctionLike(node) ? funcDepth + 1 : funcDepth;
|
|
9112
|
+
ts.forEachChild(node, (child) => visit(child, childFuncDepth, scopeParts));
|
|
9113
|
+
scopeParts.length = scopeIdx;
|
|
8510
9114
|
}
|
|
8511
|
-
visit(sourceFile);
|
|
8512
|
-
return deduplicateRefs(refs);
|
|
9115
|
+
visit(sourceFile, 0, []);
|
|
9116
|
+
return { file, lang, symbols, refs: deduplicateRefs(refs), mtimeMs: Date.now() };
|
|
8513
9117
|
}
|
|
8514
9118
|
function getTypeName(name) {
|
|
8515
9119
|
if (ts.isIdentifier(name)) return name.text;
|
|
@@ -9394,9 +9998,9 @@ function parseSymbols5(opts) {
|
|
|
9394
9998
|
function regexParse2(opts) {
|
|
9395
9999
|
const { file, content, lang } = opts;
|
|
9396
10000
|
const symbols = [];
|
|
9397
|
-
const
|
|
9398
|
-
const isPackageJson =
|
|
9399
|
-
const isTsconfig =
|
|
10001
|
+
const basename9 = path17.basename(file).toLowerCase();
|
|
10002
|
+
const isPackageJson = basename9 === "package.json";
|
|
10003
|
+
const isTsconfig = basename9 === "tsconfig.json" || basename9 === "tsconfig.build.json";
|
|
9400
10004
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
9401
10005
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
9402
10006
|
const lines = content.split("\n");
|
|
@@ -9791,16 +10395,14 @@ async function loadGitignoreMatcher(projectRoot) {
|
|
|
9791
10395
|
|
|
9792
10396
|
// src/codebase-index/indexer.ts
|
|
9793
10397
|
var YIELD_EVERY_N = 50;
|
|
9794
|
-
var PARALLEL_BATCH =
|
|
10398
|
+
var PARALLEL_BATCH = Math.min(availableParallelism() * 4, 40);
|
|
9795
10399
|
function yieldEventLoop() {
|
|
9796
10400
|
return new Promise((resolve14) => setImmediate(resolve14));
|
|
9797
10401
|
}
|
|
9798
10402
|
function throwIfAborted(signal) {
|
|
9799
10403
|
if (!signal?.aborted) return;
|
|
9800
10404
|
if (signal.reason instanceof Error) throw signal.reason;
|
|
9801
|
-
throw new Error(
|
|
9802
|
-
typeof signal.reason === "string" ? signal.reason : "Indexing cancelled"
|
|
9803
|
-
);
|
|
10405
|
+
throw new Error(typeof signal.reason === "string" ? signal.reason : "Indexing cancelled");
|
|
9804
10406
|
}
|
|
9805
10407
|
function isAbortError(err) {
|
|
9806
10408
|
return err instanceof DOMException && err.name === "AbortError";
|
|
@@ -9816,8 +10418,20 @@ var DEFAULT_IGNORE = [
|
|
|
9816
10418
|
"__snapshots__",
|
|
9817
10419
|
".nyc_output"
|
|
9818
10420
|
];
|
|
10421
|
+
var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
|
|
10422
|
+
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
10423
|
+
function isWithinProject(projectRoot, file) {
|
|
10424
|
+
const rel = path19.relative(projectRoot, file);
|
|
10425
|
+
return rel !== "" && !rel.startsWith(`..${path19.sep}`) && rel !== ".." && !path19.isAbsolute(rel);
|
|
10426
|
+
}
|
|
10427
|
+
function isMissingPathError(err) {
|
|
10428
|
+
const code = err?.code;
|
|
10429
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
10430
|
+
}
|
|
9819
10431
|
async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
9820
10432
|
const results = [];
|
|
10433
|
+
const errors = [];
|
|
10434
|
+
let complete = true;
|
|
9821
10435
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
9822
10436
|
const globs = [
|
|
9823
10437
|
{ ext: ".ts", pat: compileGlob2("**/*.ts") },
|
|
@@ -9841,7 +10455,9 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
9841
10455
|
let entries;
|
|
9842
10456
|
try {
|
|
9843
10457
|
entries = await fs14.readdir(dir, { withFileTypes: true });
|
|
9844
|
-
} catch {
|
|
10458
|
+
} catch (err) {
|
|
10459
|
+
complete = false;
|
|
10460
|
+
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9845
10461
|
return;
|
|
9846
10462
|
}
|
|
9847
10463
|
dirCount++;
|
|
@@ -9853,8 +10469,8 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
9853
10469
|
if (isGitIgnored(rel, true)) continue;
|
|
9854
10470
|
await walk(full);
|
|
9855
10471
|
} else if (e.isFile()) {
|
|
9856
|
-
if (isGitIgnored(rel, false)) continue;
|
|
9857
|
-
const ext = path19.extname(e.name);
|
|
10472
|
+
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
10473
|
+
const ext = path19.extname(e.name).toLowerCase();
|
|
9858
10474
|
for (const { ext: extName, pat } of globs) {
|
|
9859
10475
|
if (ext === extName && (pat.test(rel) || pat.test(e.name))) {
|
|
9860
10476
|
results.push(full);
|
|
@@ -9865,7 +10481,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
9865
10481
|
}
|
|
9866
10482
|
};
|
|
9867
10483
|
await walk(projectRoot);
|
|
9868
|
-
return results;
|
|
10484
|
+
return { files: results, complete, errors };
|
|
9869
10485
|
}
|
|
9870
10486
|
async function parseFile(file, content, lang) {
|
|
9871
10487
|
switch (lang) {
|
|
@@ -9888,6 +10504,26 @@ async function parseFile(file, content, lang) {
|
|
|
9888
10504
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
9889
10505
|
}
|
|
9890
10506
|
}
|
|
10507
|
+
function assignRefsToSymbols2(refs, symbols) {
|
|
10508
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
10509
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
10510
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10511
|
+
const assigned = [];
|
|
10512
|
+
for (const ref of refs) {
|
|
10513
|
+
let owner2;
|
|
10514
|
+
for (const symbol of ordered) {
|
|
10515
|
+
if (symbol.line > ref.line) break;
|
|
10516
|
+
owner2 = symbol;
|
|
10517
|
+
}
|
|
10518
|
+
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
10519
|
+
if (!owner2 || owner2.id <= 0) continue;
|
|
10520
|
+
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
10521
|
+
if (seen.has(key)) continue;
|
|
10522
|
+
seen.add(key);
|
|
10523
|
+
assigned.push({ ...ref, fromId: owner2.id });
|
|
10524
|
+
}
|
|
10525
|
+
return assigned;
|
|
10526
|
+
}
|
|
9891
10527
|
async function runIndexer(_ctx, opts) {
|
|
9892
10528
|
const store = new IndexStore(opts.projectRoot, { indexDir: opts.indexDir });
|
|
9893
10529
|
try {
|
|
@@ -9900,7 +10536,9 @@ async function runIndexer(_ctx, opts) {
|
|
|
9900
10536
|
}
|
|
9901
10537
|
}
|
|
9902
10538
|
async function runIndexerWithStore(store, opts) {
|
|
9903
|
-
const { projectRoot,
|
|
10539
|
+
const { projectRoot, langs, ignore = [], signal } = opts;
|
|
10540
|
+
const relationGraphVersion = "2";
|
|
10541
|
+
const force = (opts.force ?? false) || store.getMetadata("relation_graph_version") !== relationGraphVersion;
|
|
9904
10542
|
const startMs = Date.now();
|
|
9905
10543
|
const errors = [];
|
|
9906
10544
|
const langStats = {};
|
|
@@ -9909,10 +10547,18 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9909
10547
|
const isGitIgnored = await loadGitignoreMatcher(projectRoot);
|
|
9910
10548
|
let files;
|
|
9911
10549
|
let discoveredFiles = null;
|
|
10550
|
+
let discoveryComplete = true;
|
|
9912
10551
|
if (opts.files && opts.files.length > 0) {
|
|
9913
|
-
files = opts.files.map((f) => path19.resolve(projectRoot, f)).filter((f) =>
|
|
10552
|
+
files = opts.files.map((f) => path19.resolve(projectRoot, f)).filter((f) => {
|
|
10553
|
+
if (!isWithinProject(projectRoot, f)) return false;
|
|
10554
|
+
const rel = path19.relative(projectRoot, f).replace(/\\/g, "/");
|
|
10555
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path19.basename(f)) && !isGitIgnored(rel, false);
|
|
10556
|
+
});
|
|
9914
10557
|
} else {
|
|
9915
|
-
|
|
10558
|
+
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
10559
|
+
files = discovery.files;
|
|
10560
|
+
errors.push(...discovery.errors);
|
|
10561
|
+
discoveryComplete = discovery.complete;
|
|
9916
10562
|
discoveredFiles = new Set(files);
|
|
9917
10563
|
}
|
|
9918
10564
|
if (langs && langs.length > 0) {
|
|
@@ -9927,46 +10573,79 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9927
10573
|
if (!force) {
|
|
9928
10574
|
for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
|
|
9929
10575
|
}
|
|
10576
|
+
let filesSinceLastYield = 0;
|
|
9930
10577
|
for (let batchStart = 0; batchStart < files.length; batchStart += PARALLEL_BATCH) {
|
|
9931
10578
|
const batchEnd = Math.min(batchStart + PARALLEL_BATCH, files.length);
|
|
9932
10579
|
const batchFiles = files.slice(batchStart, batchEnd);
|
|
9933
10580
|
opts.onProgress?.(batchEnd, files.length);
|
|
9934
|
-
|
|
10581
|
+
filesSinceLastYield += batchFiles.length;
|
|
10582
|
+
if (filesSinceLastYield >= YIELD_EVERY_N) {
|
|
10583
|
+
filesSinceLastYield = 0;
|
|
9935
10584
|
await yieldEventLoop();
|
|
9936
10585
|
throwIfAborted(signal);
|
|
9937
10586
|
}
|
|
9938
10587
|
const statOpts = signal ? { signal } : {};
|
|
9939
10588
|
const statReadParse = await Promise.allSettled(
|
|
9940
|
-
batchFiles.map(
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
if (
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
|
|
10589
|
+
batchFiles.map(
|
|
10590
|
+
async (file) => {
|
|
10591
|
+
let stat17;
|
|
10592
|
+
try {
|
|
10593
|
+
stat17 = await fs14.stat(file, statOpts);
|
|
10594
|
+
} catch (e) {
|
|
10595
|
+
if (isAbortError(e)) throw e;
|
|
10596
|
+
return {
|
|
10597
|
+
file,
|
|
10598
|
+
stat: null,
|
|
10599
|
+
lang: "",
|
|
10600
|
+
parsed: null,
|
|
10601
|
+
error: `stat error: ${e instanceof Error ? e.message : String(e)}`,
|
|
10602
|
+
missing: isMissingPathError(e)
|
|
10603
|
+
};
|
|
10604
|
+
}
|
|
10605
|
+
if (!stat17.isFile()) return { file, stat: stat17, lang: "", parsed: null };
|
|
10606
|
+
const lang = detectLang(file);
|
|
10607
|
+
if (!lang) return { file, stat: stat17, lang: "", parsed: null };
|
|
10608
|
+
if (stat17.size > MAX_INDEX_FILE_BYTES) {
|
|
10609
|
+
return {
|
|
10610
|
+
file,
|
|
10611
|
+
stat: stat17,
|
|
10612
|
+
lang,
|
|
10613
|
+
parsed: null,
|
|
10614
|
+
error: `file too large (${stat17.size} bytes; max ${MAX_INDEX_FILE_BYTES})`
|
|
10615
|
+
};
|
|
10616
|
+
}
|
|
10617
|
+
const meta = existingMeta.get(file);
|
|
10618
|
+
if (!force && meta && meta.mtimeMs === Math.floor(stat17.mtimeMs)) {
|
|
10619
|
+
return { file, stat: stat17, lang, parsed: null, skippedMeta: meta };
|
|
10620
|
+
}
|
|
10621
|
+
let content;
|
|
10622
|
+
try {
|
|
10623
|
+
content = await fs14.readFile(file, { encoding: "utf8", signal });
|
|
10624
|
+
} catch (e) {
|
|
10625
|
+
if (isAbortError(e)) throw e;
|
|
10626
|
+
return {
|
|
10627
|
+
file,
|
|
10628
|
+
stat: stat17,
|
|
10629
|
+
lang,
|
|
10630
|
+
parsed: null,
|
|
10631
|
+
error: `read error: ${e instanceof Error ? e.message : String(e)}`
|
|
10632
|
+
};
|
|
10633
|
+
}
|
|
10634
|
+
let parsed;
|
|
10635
|
+
try {
|
|
10636
|
+
parsed = await parseFile(file, content, lang);
|
|
10637
|
+
} catch (e) {
|
|
10638
|
+
return {
|
|
10639
|
+
file,
|
|
10640
|
+
stat: stat17,
|
|
10641
|
+
lang,
|
|
10642
|
+
parsed: null,
|
|
10643
|
+
error: `parse error: ${e instanceof Error ? e.message : String(e)}`
|
|
10644
|
+
};
|
|
10645
|
+
}
|
|
10646
|
+
return { file, stat: stat17, lang, parsed, content };
|
|
9967
10647
|
}
|
|
9968
|
-
|
|
9969
|
-
})
|
|
10648
|
+
)
|
|
9970
10649
|
);
|
|
9971
10650
|
const batchEntries = [];
|
|
9972
10651
|
const deleteForFiles = [];
|
|
@@ -9981,8 +10660,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9981
10660
|
}
|
|
9982
10661
|
const result = settled.value;
|
|
9983
10662
|
if (result.error) {
|
|
9984
|
-
if (result.
|
|
9985
|
-
|
|
10663
|
+
if (result.missing) store.deleteFile(file);
|
|
10664
|
+
errors.push(`${file}: ${result.error}`);
|
|
9986
10665
|
continue;
|
|
9987
10666
|
}
|
|
9988
10667
|
const { stat: stat17, lang, parsed } = result;
|
|
@@ -10006,6 +10685,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10006
10685
|
continue;
|
|
10007
10686
|
}
|
|
10008
10687
|
if (parsed.symbols.length === 0) {
|
|
10688
|
+
store.deleteRefsForFile(file);
|
|
10689
|
+
store.deleteSymbolsForFile(file);
|
|
10009
10690
|
store.upsertFile({
|
|
10010
10691
|
file,
|
|
10011
10692
|
lang,
|
|
@@ -10016,15 +10697,11 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10016
10697
|
filesIndexed++;
|
|
10017
10698
|
continue;
|
|
10018
10699
|
}
|
|
10019
|
-
const refs = [];
|
|
10020
|
-
if (parsed.refs && parsed.refs.length > 0) {
|
|
10021
|
-
for (const r of parsed.refs) refs.push({ ...r, fromId: 0 });
|
|
10022
|
-
}
|
|
10023
10700
|
batchEntries.push({
|
|
10024
10701
|
file,
|
|
10025
10702
|
lang,
|
|
10026
10703
|
symbols: parsed.symbols,
|
|
10027
|
-
refs,
|
|
10704
|
+
refs: parsed.refs ?? [],
|
|
10028
10705
|
mtimeMs: Math.floor(stat17.mtimeMs),
|
|
10029
10706
|
symbolCount: parsed.symbols.length
|
|
10030
10707
|
});
|
|
@@ -10032,34 +10709,12 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10032
10709
|
}
|
|
10033
10710
|
if (batchEntries.length > 0) {
|
|
10034
10711
|
try {
|
|
10035
|
-
|
|
10036
|
-
let cursor = 0;
|
|
10712
|
+
store.commitBatch(batchEntries, { deleteForFiles });
|
|
10037
10713
|
for (const entry of batchEntries) {
|
|
10038
10714
|
const count = entry.symbols.length;
|
|
10039
|
-
const symbolsWithIds = inserted.slice(cursor, cursor + count);
|
|
10040
|
-
cursor += count;
|
|
10041
10715
|
symbolsIndexed += count;
|
|
10042
10716
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
|
|
10043
10717
|
filesIndexed++;
|
|
10044
|
-
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
10045
|
-
const refsByLine = /* @__PURE__ */ new Map();
|
|
10046
|
-
for (let i = 0; i < symbolsWithIds.length; i++) {
|
|
10047
|
-
const sym = symbolsWithIds[i];
|
|
10048
|
-
let arr = refsByLine.get(sym.line);
|
|
10049
|
-
if (!arr) {
|
|
10050
|
-
arr = [];
|
|
10051
|
-
refsByLine.set(sym.line, arr);
|
|
10052
|
-
}
|
|
10053
|
-
arr.push(i);
|
|
10054
|
-
}
|
|
10055
|
-
for (const ref of entry.refs) {
|
|
10056
|
-
const indices = refsByLine.get(ref.line);
|
|
10057
|
-
if (indices && indices.length > 0) {
|
|
10058
|
-
const idx = indices.shift();
|
|
10059
|
-
ref.fromId = symbolsWithIds[idx].id;
|
|
10060
|
-
}
|
|
10061
|
-
}
|
|
10062
|
-
}
|
|
10063
10718
|
}
|
|
10064
10719
|
} catch (err) {
|
|
10065
10720
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -10073,23 +10728,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10073
10728
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
|
|
10074
10729
|
filesIndexed++;
|
|
10075
10730
|
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
10076
|
-
const
|
|
10077
|
-
for (const sym of symbolsWithIds) {
|
|
10078
|
-
let arr = refsByLine.get(sym.line);
|
|
10079
|
-
if (!arr) {
|
|
10080
|
-
arr = [];
|
|
10081
|
-
refsByLine.set(sym.line, arr);
|
|
10082
|
-
}
|
|
10083
|
-
arr.push(sym);
|
|
10084
|
-
}
|
|
10085
|
-
const fallbackBatch = [];
|
|
10086
|
-
for (const ref of entry.refs) {
|
|
10087
|
-
const syms = refsByLine.get(ref.line);
|
|
10088
|
-
if (syms && syms.length > 0) {
|
|
10089
|
-
const sym = syms.shift();
|
|
10090
|
-
fallbackBatch.push({ ...ref, fromId: sym.id });
|
|
10091
|
-
}
|
|
10092
|
-
}
|
|
10731
|
+
const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
|
|
10093
10732
|
if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
|
|
10094
10733
|
}
|
|
10095
10734
|
store.upsertFile({
|
|
@@ -10100,19 +10739,23 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10100
10739
|
lastIndexed: Date.now()
|
|
10101
10740
|
});
|
|
10102
10741
|
} catch (innerErr) {
|
|
10103
|
-
errors.push(
|
|
10742
|
+
errors.push(
|
|
10743
|
+
`fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
|
|
10744
|
+
);
|
|
10104
10745
|
}
|
|
10105
10746
|
}
|
|
10106
10747
|
}
|
|
10107
10748
|
}
|
|
10108
10749
|
}
|
|
10109
|
-
if (discoveredFiles) {
|
|
10750
|
+
if (discoveredFiles && discoveryComplete) {
|
|
10110
10751
|
for (const [file_] of existingMeta) {
|
|
10111
10752
|
if (!discoveredFiles.has(file_)) {
|
|
10112
10753
|
store.deleteFile(file_);
|
|
10113
10754
|
}
|
|
10114
10755
|
}
|
|
10115
10756
|
}
|
|
10757
|
+
store.resolveRefs();
|
|
10758
|
+
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
10116
10759
|
const durationMs = Date.now() - startMs;
|
|
10117
10760
|
store.setLastIndexed(Date.now());
|
|
10118
10761
|
return {
|
|
@@ -10148,7 +10791,7 @@ async function indexService(args, hooks = {}) {
|
|
|
10148
10791
|
});
|
|
10149
10792
|
}
|
|
10150
10793
|
function searchService(args) {
|
|
10151
|
-
const store =
|
|
10794
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
10152
10795
|
try {
|
|
10153
10796
|
return store.searchRanked(
|
|
10154
10797
|
args.query,
|
|
@@ -10161,21 +10804,21 @@ function searchService(args) {
|
|
|
10161
10804
|
args.limit
|
|
10162
10805
|
);
|
|
10163
10806
|
} finally {
|
|
10164
|
-
|
|
10807
|
+
indexStorePool.release(store);
|
|
10165
10808
|
}
|
|
10166
10809
|
}
|
|
10167
10810
|
function statsService(args) {
|
|
10168
|
-
const store =
|
|
10811
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
10169
10812
|
try {
|
|
10170
10813
|
return store.getStats();
|
|
10171
10814
|
} finally {
|
|
10172
|
-
|
|
10815
|
+
indexStorePool.release(store);
|
|
10173
10816
|
}
|
|
10174
10817
|
}
|
|
10175
10818
|
|
|
10176
10819
|
// src/codebase-index/background-indexer.ts
|
|
10177
10820
|
var DEFAULT_FULL_INDEX_TIMEOUT_MS = 24e4;
|
|
10178
|
-
var DEFAULT_QUERY_TIMEOUT_MS =
|
|
10821
|
+
var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
10179
10822
|
var _ready = false;
|
|
10180
10823
|
var _indexing = false;
|
|
10181
10824
|
var _currentFile = 0;
|
|
@@ -10244,14 +10887,22 @@ function ensureWorker() {
|
|
|
10244
10887
|
if (!entry) return;
|
|
10245
10888
|
pending.delete(msg.id);
|
|
10246
10889
|
if (msg.ok) entry.resolve(msg.result);
|
|
10247
|
-
else
|
|
10890
|
+
else {
|
|
10891
|
+
const error = msg.errorName === "LockError" ? new LockError(msg.error) : new Error(msg.error);
|
|
10892
|
+
if (msg.errorName && msg.errorName !== "Error") {
|
|
10893
|
+
error.name = msg.errorName;
|
|
10894
|
+
}
|
|
10895
|
+
entry.reject(error);
|
|
10896
|
+
}
|
|
10248
10897
|
});
|
|
10249
10898
|
w.on("error", (err) => {
|
|
10899
|
+
if (worker !== w) return;
|
|
10250
10900
|
worker = null;
|
|
10251
10901
|
failAllPending(err);
|
|
10252
10902
|
});
|
|
10253
10903
|
w.on("exit", () => {
|
|
10254
|
-
if (worker
|
|
10904
|
+
if (worker !== w) return;
|
|
10905
|
+
worker = null;
|
|
10255
10906
|
failAllPending(new Error("codebase-index worker exited"));
|
|
10256
10907
|
});
|
|
10257
10908
|
worker = w;
|
|
@@ -10271,6 +10922,11 @@ function terminateWorker(reason) {
|
|
|
10271
10922
|
function callIndexOp(op, args, opts) {
|
|
10272
10923
|
const w = ensureWorker();
|
|
10273
10924
|
if (!w) return callInline(op, args, opts);
|
|
10925
|
+
if (opts.signal?.aborted) {
|
|
10926
|
+
return Promise.reject(
|
|
10927
|
+
opts.signal.reason instanceof Error ? opts.signal.reason : new Error("Indexing cancelled")
|
|
10928
|
+
);
|
|
10929
|
+
}
|
|
10274
10930
|
return new Promise((resolve14, reject) => {
|
|
10275
10931
|
const id = nextRpcId++;
|
|
10276
10932
|
const timer = setTimeout(() => {
|
|
@@ -10285,8 +10941,7 @@ function callIndexOp(op, args, opts) {
|
|
|
10285
10941
|
const onAbort = () => {
|
|
10286
10942
|
w.postMessage({ type: "cancel", id });
|
|
10287
10943
|
};
|
|
10288
|
-
|
|
10289
|
-
else opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
10944
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
10290
10945
|
const cleanup = () => {
|
|
10291
10946
|
clearTimeout(timer);
|
|
10292
10947
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
@@ -10430,7 +11085,7 @@ var codebaseIndexTool = {
|
|
|
10430
11085
|
category: "Project",
|
|
10431
11086
|
icon: "index",
|
|
10432
11087
|
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.",
|
|
10433
|
-
usageHint: "
|
|
11088
|
+
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.",
|
|
10434
11089
|
permission: "confirm",
|
|
10435
11090
|
mutating: true,
|
|
10436
11091
|
capabilities: ["fs.write.outside-project"],
|
|
@@ -10486,12 +11141,14 @@ var codebaseSearchTool = {
|
|
|
10486
11141
|
name: "codebase-search",
|
|
10487
11142
|
category: "Project",
|
|
10488
11143
|
icon: "index",
|
|
10489
|
-
description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback.
|
|
10490
|
-
usageHint: "
|
|
11144
|
+
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).",
|
|
11145
|
+
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.",
|
|
10491
11146
|
permission: "auto",
|
|
10492
11147
|
mutating: false,
|
|
10493
11148
|
capabilities: ["fs.read"],
|
|
10494
|
-
|
|
11149
|
+
// The index host has its own 30s read watchdog. Leave enough headroom for
|
|
11150
|
+
// worker teardown and structured timeout reporting.
|
|
11151
|
+
timeoutMs: 35e3,
|
|
10495
11152
|
inputSchema: {
|
|
10496
11153
|
type: "object",
|
|
10497
11154
|
properties: {
|
|
@@ -10501,11 +11158,34 @@ var codebaseSearchTool = {
|
|
|
10501
11158
|
},
|
|
10502
11159
|
kind: {
|
|
10503
11160
|
type: "string",
|
|
10504
|
-
|
|
11161
|
+
enum: [
|
|
11162
|
+
"class",
|
|
11163
|
+
"interface",
|
|
11164
|
+
"enum",
|
|
11165
|
+
"type",
|
|
11166
|
+
"function",
|
|
11167
|
+
"method",
|
|
11168
|
+
"var",
|
|
11169
|
+
"const",
|
|
11170
|
+
"let",
|
|
11171
|
+
"property",
|
|
11172
|
+
"parameter",
|
|
11173
|
+
"namespace",
|
|
11174
|
+
"object",
|
|
11175
|
+
"literal",
|
|
11176
|
+
"schema",
|
|
11177
|
+
"struct",
|
|
11178
|
+
"trait",
|
|
11179
|
+
"impl",
|
|
11180
|
+
"static",
|
|
11181
|
+
"mod"
|
|
11182
|
+
],
|
|
11183
|
+
description: "Filter by indexed symbol kind"
|
|
10505
11184
|
},
|
|
10506
11185
|
lang: {
|
|
10507
11186
|
type: "string",
|
|
10508
|
-
|
|
11187
|
+
enum: ["ts", "tsx", "js", "jsx", "go", "py", "rs", "json", "yaml"],
|
|
11188
|
+
description: "Filter by indexed language"
|
|
10509
11189
|
},
|
|
10510
11190
|
lspKind: {
|
|
10511
11191
|
type: "integer",
|
|
@@ -10530,20 +11210,12 @@ var codebaseSearchTool = {
|
|
|
10530
11210
|
},
|
|
10531
11211
|
async execute(input, ctx, execOpts) {
|
|
10532
11212
|
const state = getIndexState();
|
|
10533
|
-
if (!state.ready) {
|
|
10534
|
-
return {
|
|
10535
|
-
results: [],
|
|
10536
|
-
total: 0,
|
|
10537
|
-
query: input.query,
|
|
10538
|
-
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."
|
|
10539
|
-
};
|
|
10540
|
-
}
|
|
10541
|
-
if (state.indexing) {
|
|
11213
|
+
if (state.indexing && !state.ready) {
|
|
10542
11214
|
return {
|
|
10543
11215
|
results: [],
|
|
10544
11216
|
total: 0,
|
|
10545
11217
|
query: input.query,
|
|
10546
|
-
indexStatus: `
|
|
11218
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
10547
11219
|
};
|
|
10548
11220
|
}
|
|
10549
11221
|
if (state.lastError) {
|
|
@@ -10556,21 +11228,37 @@ var codebaseSearchTool = {
|
|
|
10556
11228
|
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
10557
11229
|
};
|
|
10558
11230
|
}
|
|
10559
|
-
const limit = Math.min(input.limit ?? 20, 100);
|
|
11231
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 20), 100));
|
|
10560
11232
|
const { results, total } = await searchCodebaseIndex(
|
|
10561
11233
|
{
|
|
10562
11234
|
projectRoot: ctx.projectRoot,
|
|
10563
11235
|
indexDir: codebaseIndexDirOverride(ctx),
|
|
10564
11236
|
query: input.query,
|
|
10565
|
-
kind: input.kind,
|
|
10566
|
-
lang: input.lang,
|
|
11237
|
+
kind: input.kind?.toLowerCase(),
|
|
11238
|
+
lang: input.lang?.toLowerCase(),
|
|
10567
11239
|
file: input.file,
|
|
10568
11240
|
lspKind: input.lspKind,
|
|
10569
11241
|
limit
|
|
10570
11242
|
},
|
|
10571
11243
|
{ signal: execOpts?.signal }
|
|
10572
11244
|
);
|
|
10573
|
-
|
|
11245
|
+
let hasPersistedIndex = state.ready || total > 0;
|
|
11246
|
+
if (!hasPersistedIndex) {
|
|
11247
|
+
try {
|
|
11248
|
+
const stats = await codebaseIndexStats(
|
|
11249
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
11250
|
+
{ signal: execOpts?.signal }
|
|
11251
|
+
);
|
|
11252
|
+
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
11253
|
+
} catch {
|
|
11254
|
+
}
|
|
11255
|
+
}
|
|
11256
|
+
return {
|
|
11257
|
+
results,
|
|
11258
|
+
total,
|
|
11259
|
+
query: input.query,
|
|
11260
|
+
...hasPersistedIndex ? {} : { indexStatus: "No persisted index data found. Run codebase-index to build it." }
|
|
11261
|
+
};
|
|
10574
11262
|
}
|
|
10575
11263
|
};
|
|
10576
11264
|
|
|
@@ -10579,12 +11267,15 @@ var codebaseStatsTool = {
|
|
|
10579
11267
|
name: "codebase-stats",
|
|
10580
11268
|
category: "Project",
|
|
10581
11269
|
icon: "index",
|
|
10582
|
-
description: "
|
|
10583
|
-
usageHint: "
|
|
11270
|
+
description: "Check whether a persisted codebase index exists and report its health and statistics (symbols, files, language/kind breakdown, size, last update).",
|
|
11271
|
+
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.",
|
|
10584
11272
|
permission: "auto",
|
|
10585
11273
|
mutating: false,
|
|
10586
11274
|
capabilities: ["fs.read"],
|
|
10587
|
-
|
|
11275
|
+
// The index host has its own 30s read watchdog. Keep the outer tool budget
|
|
11276
|
+
// longer so the host can return a structured timeout instead of being
|
|
11277
|
+
// pre-empted by the generic tool executor.
|
|
11278
|
+
timeoutMs: 35e3,
|
|
10588
11279
|
inputSchema: {
|
|
10589
11280
|
type: "object",
|
|
10590
11281
|
properties: {},
|
|
@@ -10592,7 +11283,8 @@ var codebaseStatsTool = {
|
|
|
10592
11283
|
},
|
|
10593
11284
|
async execute(_input, ctx, execOpts) {
|
|
10594
11285
|
const idxState = getIndexState();
|
|
10595
|
-
|
|
11286
|
+
const indexPath = resolveIndexDir(ctx.projectRoot, codebaseIndexDirOverride(ctx));
|
|
11287
|
+
if (idxState.indexing) {
|
|
10596
11288
|
return {
|
|
10597
11289
|
totalSymbols: 0,
|
|
10598
11290
|
totalFiles: 0,
|
|
@@ -10600,22 +11292,47 @@ var codebaseStatsTool = {
|
|
|
10600
11292
|
byKind: {},
|
|
10601
11293
|
lastIndexed: null,
|
|
10602
11294
|
sizeBytes: 0,
|
|
10603
|
-
indexPath
|
|
11295
|
+
indexPath,
|
|
10604
11296
|
version: SCHEMA_VERSION,
|
|
10605
|
-
|
|
11297
|
+
statsAvailable: false,
|
|
11298
|
+
indexing: {
|
|
11299
|
+
currentFile: idxState.currentFile,
|
|
11300
|
+
totalFiles: idxState.totalFiles
|
|
11301
|
+
},
|
|
11302
|
+
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.`
|
|
10606
11303
|
};
|
|
10607
11304
|
}
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
11305
|
+
let stats;
|
|
11306
|
+
try {
|
|
11307
|
+
stats = await codebaseIndexStats(
|
|
11308
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
11309
|
+
{ signal: execOpts?.signal }
|
|
11310
|
+
);
|
|
11311
|
+
} catch (err) {
|
|
11312
|
+
if (!(err instanceof IndexTimeoutError) && err?.name !== "IndexTimeoutError") {
|
|
11313
|
+
throw err;
|
|
11314
|
+
}
|
|
10613
11315
|
return {
|
|
10614
|
-
|
|
10615
|
-
|
|
11316
|
+
totalSymbols: 0,
|
|
11317
|
+
totalFiles: 0,
|
|
11318
|
+
byLang: {},
|
|
11319
|
+
byKind: {},
|
|
11320
|
+
lastIndexed: null,
|
|
11321
|
+
sizeBytes: 0,
|
|
11322
|
+
indexPath,
|
|
11323
|
+
version: SCHEMA_VERSION,
|
|
11324
|
+
statsAvailable: false,
|
|
11325
|
+
indexStatus: "Index statistics timed out. Do not repeatedly retry stats; try codebase-search directly or use the appropriate grep/glob/tree fallback."
|
|
10616
11326
|
};
|
|
10617
11327
|
}
|
|
10618
11328
|
const circuit = idxState.circuit;
|
|
11329
|
+
const hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
11330
|
+
let indexStatus;
|
|
11331
|
+
if (circuit.state === "open") {
|
|
11332
|
+
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.");
|
|
11333
|
+
} else if (!hasPersistedIndex) {
|
|
11334
|
+
indexStatus = "No persisted index data found. Run codebase-index to build it before broad code exploration.";
|
|
11335
|
+
}
|
|
10619
11336
|
return {
|
|
10620
11337
|
totalSymbols: stats.totalSymbols,
|
|
10621
11338
|
totalFiles: stats.totalFiles,
|
|
@@ -10625,9 +11342,8 @@ var codebaseStatsTool = {
|
|
|
10625
11342
|
sizeBytes: stats.sizeBytes,
|
|
10626
11343
|
indexPath: stats.indexPath,
|
|
10627
11344
|
version: stats.version,
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
} : {}
|
|
11345
|
+
statsAvailable: true,
|
|
11346
|
+
...indexStatus ? { indexStatus } : {}
|
|
10631
11347
|
};
|
|
10632
11348
|
}
|
|
10633
11349
|
};
|
|
@@ -12146,6 +12862,11 @@ Compare this against your old_string and retry with the file's actual text.` : "
|
|
|
12146
12862
|
|
|
12147
12863
|
// src/exec.ts
|
|
12148
12864
|
import { spawn as spawn8 } from "node:child_process";
|
|
12865
|
+
import {
|
|
12866
|
+
emitProcessCompleted as emitProcessCompleted3,
|
|
12867
|
+
emitProcessOutput as emitProcessOutput3,
|
|
12868
|
+
emitProcessStarted as emitProcessStarted3
|
|
12869
|
+
} from "@wrongstack/core";
|
|
12149
12870
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils/error";
|
|
12150
12871
|
init_output_spool();
|
|
12151
12872
|
init_util();
|
|
@@ -13254,12 +13975,30 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13254
13975
|
resolve14(result);
|
|
13255
13976
|
};
|
|
13256
13977
|
const startedAt = Date.now();
|
|
13978
|
+
let stdoutBytes = 0;
|
|
13979
|
+
let stderrBytes = 0;
|
|
13980
|
+
let telemetryCompleted = false;
|
|
13981
|
+
let timedOut = false;
|
|
13257
13982
|
const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
|
|
13258
13983
|
const resolved = resolveWin32Command(cmd);
|
|
13259
13984
|
const needsShell = isWin3 && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
13260
13985
|
const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
|
|
13261
13986
|
const spawnCmd = shim?.command ?? resolved;
|
|
13262
13987
|
const spawnArgs = shim?.args ?? args;
|
|
13988
|
+
const emitCompletedOnce = (exitCode, pid2, signal2) => {
|
|
13989
|
+
if (telemetryCompleted) return;
|
|
13990
|
+
telemetryCompleted = true;
|
|
13991
|
+
emitProcessCompleted3({
|
|
13992
|
+
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
13993
|
+
exitCode,
|
|
13994
|
+
...signal2 ? { signal: signal2 } : {},
|
|
13995
|
+
durationMs: Date.now() - startedAt,
|
|
13996
|
+
stdoutBytes,
|
|
13997
|
+
stderrBytes,
|
|
13998
|
+
timedOut,
|
|
13999
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
14000
|
+
});
|
|
14001
|
+
};
|
|
13263
14002
|
let child;
|
|
13264
14003
|
try {
|
|
13265
14004
|
child = spawn8(spawnCmd, spawnArgs, {
|
|
@@ -13272,6 +14011,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13272
14011
|
});
|
|
13273
14012
|
} catch (err) {
|
|
13274
14013
|
spool.finalize();
|
|
14014
|
+
emitProcessStarted3({
|
|
14015
|
+
parentPid: process.pid,
|
|
14016
|
+
command: redactCommand(`${cmd} ${args.join(" ")}`),
|
|
14017
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
14018
|
+
cwd,
|
|
14019
|
+
background: false,
|
|
14020
|
+
startedAt: new Date(startedAt).toISOString()
|
|
14021
|
+
});
|
|
14022
|
+
emitCompletedOnce(1, void 0);
|
|
13275
14023
|
finish({
|
|
13276
14024
|
command: cmd,
|
|
13277
14025
|
args,
|
|
@@ -13284,6 +14032,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13284
14032
|
});
|
|
13285
14033
|
return;
|
|
13286
14034
|
}
|
|
14035
|
+
emitProcessStarted3({
|
|
14036
|
+
...child.pid !== void 0 ? { pid: child.pid } : {},
|
|
14037
|
+
parentPid: process.pid,
|
|
14038
|
+
command: redactCommand(`${cmd} ${args.join(" ")}`),
|
|
14039
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
14040
|
+
cwd,
|
|
14041
|
+
background: false,
|
|
14042
|
+
startedAt: new Date(startedAt).toISOString()
|
|
14043
|
+
});
|
|
13287
14044
|
child.on("error", (err) => {
|
|
13288
14045
|
const isAbort = err && err.code === "ABORT_ERR";
|
|
13289
14046
|
const stderrText = isAbort ? `Aborted: ${err.message}` : err.message;
|
|
@@ -13292,6 +14049,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13292
14049
|
if (typeof pid === "number") registry.unregister(pid);
|
|
13293
14050
|
registry.afterCall(Date.now() - startedAt, true);
|
|
13294
14051
|
spool.finalize();
|
|
14052
|
+
emitCompletedOnce(isAbort ? 124 : 1, child.pid, isAbort ? "ABORT" : void 0);
|
|
13295
14053
|
finish({
|
|
13296
14054
|
command: cmd,
|
|
13297
14055
|
args,
|
|
@@ -13311,6 +14069,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13311
14069
|
}
|
|
13312
14070
|
const timer = setTimeout(() => {
|
|
13313
14071
|
killed = true;
|
|
14072
|
+
timedOut = true;
|
|
13314
14073
|
if (typeof pid === "number") registry.kill(pid);
|
|
13315
14074
|
else child.kill("SIGTERM");
|
|
13316
14075
|
}, timeout);
|
|
@@ -13325,11 +14084,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13325
14084
|
}
|
|
13326
14085
|
child.stdout?.on("data", (chunk) => {
|
|
13327
14086
|
const text = chunk.toString();
|
|
14087
|
+
stdoutBytes += chunk.byteLength;
|
|
14088
|
+
emitProcessOutput3({ pid, stream: "stdout", chunk });
|
|
13328
14089
|
if (stdout.length < MAX_OUTPUT2) stdout += text;
|
|
13329
14090
|
spool.write(text);
|
|
13330
14091
|
});
|
|
13331
14092
|
child.stderr?.on("data", (chunk) => {
|
|
13332
14093
|
const text = chunk.toString();
|
|
14094
|
+
stderrBytes += chunk.byteLength;
|
|
14095
|
+
emitProcessOutput3({ pid, stream: "stderr", chunk });
|
|
13333
14096
|
if (stderr.length < MAX_OUTPUT2) stderr += text;
|
|
13334
14097
|
spool.write(text);
|
|
13335
14098
|
});
|
|
@@ -13339,6 +14102,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13339
14102
|
if (typeof pid === "number") registry.unregister(pid);
|
|
13340
14103
|
const durationMs = Date.now() - startedAt;
|
|
13341
14104
|
const exitCode = killed ? 124 : code ?? 1;
|
|
14105
|
+
emitCompletedOnce(exitCode, pid);
|
|
13342
14106
|
registry.afterCall(durationMs, exitCode !== 0);
|
|
13343
14107
|
const spooled = spool.finalize();
|
|
13344
14108
|
finish({
|
|
@@ -13414,7 +14178,7 @@ function guardedLookup(hostname2, options, callback) {
|
|
|
13414
14178
|
var pinnedAgent;
|
|
13415
14179
|
function getPinnedDispatcher() {
|
|
13416
14180
|
if (!pinnedAgent) {
|
|
13417
|
-
pinnedAgent = new Agent({ connect: { lookup: guardedLookup } });
|
|
14181
|
+
pinnedAgent = new Agent({ allowH2: false, connect: { lookup: guardedLookup } });
|
|
13418
14182
|
}
|
|
13419
14183
|
return pinnedAgent;
|
|
13420
14184
|
}
|
|
@@ -13516,7 +14280,7 @@ async function assertNotPrivate(hostname2) {
|
|
|
13516
14280
|
}
|
|
13517
14281
|
}
|
|
13518
14282
|
} catch (err) {
|
|
13519
|
-
if (err instanceof
|
|
14283
|
+
if (err instanceof ToolValidationError2) throw err;
|
|
13520
14284
|
}
|
|
13521
14285
|
}
|
|
13522
14286
|
}
|
|
@@ -13864,7 +14628,7 @@ async function detectFixer(cwd) {
|
|
|
13864
14628
|
init_util();
|
|
13865
14629
|
import { spawn as spawn9 } from "node:child_process";
|
|
13866
14630
|
import { statSync as statSync3 } from "node:fs";
|
|
13867
|
-
import { dirname as dirname12, resolve as resolve11, sep as
|
|
14631
|
+
import { dirname as dirname12, resolve as resolve11, sep as sep5 } from "node:path";
|
|
13868
14632
|
import { assessCommitSafety, buildChildEnv as buildChildEnv4 } from "@wrongstack/core";
|
|
13869
14633
|
var TIMEOUT_MS2 = 3e4;
|
|
13870
14634
|
var MAX_OUTPUT3 = 1e5;
|
|
@@ -14009,7 +14773,7 @@ function validateWorktreeInput(input, projectRoot) {
|
|
|
14009
14773
|
if ((input.worktreeAction === "add" || input.worktreeAction === "remove") && input.worktreePath) {
|
|
14010
14774
|
const root = resolve11(projectRoot);
|
|
14011
14775
|
const abs = resolve11(root, input.worktreePath);
|
|
14012
|
-
if (abs !== root && !abs.startsWith(root +
|
|
14776
|
+
if (abs !== root && !abs.startsWith(root + sep5)) {
|
|
14013
14777
|
return reject(`unsafe worktree path (escapes project root): ${input.worktreePath}`);
|
|
14014
14778
|
}
|
|
14015
14779
|
}
|
|
@@ -14172,8 +14936,8 @@ var WALK_CONCURRENCY = 16;
|
|
|
14172
14936
|
var globTool = {
|
|
14173
14937
|
name: "glob",
|
|
14174
14938
|
category: "Filesystem",
|
|
14175
|
-
description: "Find files
|
|
14176
|
-
usageHint: "
|
|
14939
|
+
description: "Find files by path pattern. Use index-backed `codebase-search` first for code symbols or concepts when it is live.",
|
|
14940
|
+
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.",
|
|
14177
14941
|
selection: {
|
|
14178
14942
|
doNotUseWhen: "you need to search inside file contents.",
|
|
14179
14943
|
useInstead: ["grep"]
|
|
@@ -14361,8 +15125,8 @@ var NATIVE_MAX_FILE_BYTES = 1e6;
|
|
|
14361
15125
|
var grepTool = {
|
|
14362
15126
|
name: "grep",
|
|
14363
15127
|
category: "Search",
|
|
14364
|
-
description: "Search
|
|
14365
|
-
usageHint: '
|
|
15128
|
+
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.",
|
|
15129
|
+
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.',
|
|
14366
15130
|
selection: {
|
|
14367
15131
|
doNotUseWhen: "you only need to locate files by name or path pattern.",
|
|
14368
15132
|
useInstead: ["glob"]
|
|
@@ -15384,6 +16148,7 @@ function toYaml(data, indent = 0) {
|
|
|
15384
16148
|
}
|
|
15385
16149
|
|
|
15386
16150
|
// src/kanban.ts
|
|
16151
|
+
import { randomUUID } from "node:crypto";
|
|
15387
16152
|
import { deserializeTaskGraph as deserializeTaskGraph2, loadTasks as loadTasks2, serializeTaskGraph } from "@wrongstack/core";
|
|
15388
16153
|
import {
|
|
15389
16154
|
addCheckToTask,
|
|
@@ -15401,7 +16166,7 @@ import {
|
|
|
15401
16166
|
duplicateBoard,
|
|
15402
16167
|
exportBoardAsMarkdown,
|
|
15403
16168
|
exportBoardToTaskGraph,
|
|
15404
|
-
|
|
16169
|
+
createBoardFromText,
|
|
15405
16170
|
getBoard as getBoard2,
|
|
15406
16171
|
getKanbanOrchestrationSnapshot,
|
|
15407
16172
|
getKanbanQueueHealth,
|
|
@@ -15423,7 +16188,9 @@ import {
|
|
|
15423
16188
|
setTaskChain,
|
|
15424
16189
|
splitTask,
|
|
15425
16190
|
syncBoardFromTaskGraph as syncBoardFromTaskGraph2,
|
|
16191
|
+
transitionTask,
|
|
15426
16192
|
transferTaskToBoard,
|
|
16193
|
+
touchKanbanPresence as touchKanbanPresence2,
|
|
15427
16194
|
updateBoard as updateBoard2,
|
|
15428
16195
|
updateCheckOnTask,
|
|
15429
16196
|
updateColumn,
|
|
@@ -15435,17 +16202,21 @@ import {
|
|
|
15435
16202
|
// src/session-kanban.ts
|
|
15436
16203
|
import {
|
|
15437
16204
|
deserializeTaskGraph,
|
|
16205
|
+
GlobalMailbox,
|
|
15438
16206
|
loadPlan,
|
|
15439
16207
|
loadTasks,
|
|
15440
16208
|
mutatePlan,
|
|
15441
16209
|
mutateTasks
|
|
15442
16210
|
} from "@wrongstack/core";
|
|
16211
|
+
import { resolveWstackPaths as resolveWstackPaths3 } from "@wrongstack/core/utils";
|
|
15443
16212
|
import {
|
|
15444
16213
|
createBoard,
|
|
15445
16214
|
getBoard,
|
|
16215
|
+
getKanbanDir,
|
|
15446
16216
|
listBoards,
|
|
15447
16217
|
removeBoard,
|
|
15448
16218
|
syncBoardFromTaskGraph,
|
|
16219
|
+
touchKanbanPresence,
|
|
15449
16220
|
updateBoard
|
|
15450
16221
|
} from "@wrongstack/kanban";
|
|
15451
16222
|
var SESSION_BOARD_TAG = "session-work";
|
|
@@ -15657,8 +16428,8 @@ function projectSessionPlanToKanban(projectRoot, items, sessionId) {
|
|
|
15657
16428
|
var kanbanTool = {
|
|
15658
16429
|
name: "kanban",
|
|
15659
16430
|
category: "Project",
|
|
15660
|
-
description: "Manage project-scoped multi-kanban boards stored under .wrongstack/kanbans.
|
|
15661
|
-
usageHint: "Use this for durable project kanban state.
|
|
16431
|
+
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.",
|
|
16432
|
+
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.",
|
|
15662
16433
|
permission: "confirm",
|
|
15663
16434
|
mutating: true,
|
|
15664
16435
|
capabilities: ["fs.write"],
|
|
@@ -15695,6 +16466,7 @@ var kanbanTool = {
|
|
|
15695
16466
|
"transfer_task",
|
|
15696
16467
|
"get_task",
|
|
15697
16468
|
"update_task",
|
|
16469
|
+
"transition_task",
|
|
15698
16470
|
"move_task",
|
|
15699
16471
|
"delete_task",
|
|
15700
16472
|
"set_chain",
|
|
@@ -15725,6 +16497,7 @@ var kanbanTool = {
|
|
|
15725
16497
|
targetColumnId: { type: "string" },
|
|
15726
16498
|
title: { type: "string" },
|
|
15727
16499
|
description: { type: "string" },
|
|
16500
|
+
dueDate: { type: "string" },
|
|
15728
16501
|
tags: { type: "array", items: { type: "string" } },
|
|
15729
16502
|
labels: { type: "array", items: { type: "string" } },
|
|
15730
16503
|
priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
|
|
@@ -15771,6 +16544,18 @@ var kanbanTool = {
|
|
|
15771
16544
|
type: "string",
|
|
15772
16545
|
enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
|
|
15773
16546
|
},
|
|
16547
|
+
lifecycleStage: {
|
|
16548
|
+
type: "string",
|
|
16549
|
+
enum: ["backlog", "todo", "running", "review", "done"]
|
|
16550
|
+
},
|
|
16551
|
+
transitionAction: { type: "string" },
|
|
16552
|
+
transitionComment: { type: "string" },
|
|
16553
|
+
attachmentUrl: { type: "string" },
|
|
16554
|
+
attachmentTitle: { type: "string" },
|
|
16555
|
+
attachmentType: {
|
|
16556
|
+
type: "string",
|
|
16557
|
+
enum: ["issue", "pr", "doc", "commit", "design", "file", "url", "other"]
|
|
16558
|
+
},
|
|
15774
16559
|
releaseStatus: { type: "string", enum: ["pending", "ready", "blocked"] },
|
|
15775
16560
|
releaseReason: { type: "string" },
|
|
15776
16561
|
clearAssignee: { type: "boolean" },
|
|
@@ -15841,591 +16626,649 @@ var kanbanTool = {
|
|
|
15841
16626
|
async execute(input, ctx) {
|
|
15842
16627
|
const projectRoot = ctx.projectRoot;
|
|
15843
16628
|
if (!projectRoot) return fail("No project root is available.");
|
|
16629
|
+
const withPresence = async (result) => {
|
|
16630
|
+
const boardId = result.board?.id ?? input.boardId;
|
|
16631
|
+
if (!result.ok || !boardId || !ctx.session?.id || !ctx.agentId) return result;
|
|
16632
|
+
try {
|
|
16633
|
+
const board = await touchKanbanPresence2(projectRoot, boardId, {
|
|
16634
|
+
sessionId: ctx.session.id,
|
|
16635
|
+
agentId: ctx.agentId,
|
|
16636
|
+
agentName: ctx.agentName,
|
|
16637
|
+
taskId: input.taskId ?? result.task?.id,
|
|
16638
|
+
runTaskId: input.runTaskId
|
|
16639
|
+
});
|
|
16640
|
+
return board ? { ...result, board } : result;
|
|
16641
|
+
} catch {
|
|
16642
|
+
return result;
|
|
16643
|
+
}
|
|
16644
|
+
};
|
|
15844
16645
|
try {
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
|
|
15850
|
-
case "get_board": {
|
|
15851
|
-
const board = await requireBoard(projectRoot, input.boardId);
|
|
15852
|
-
return board ? okBoard(board) : fail("Board not found.");
|
|
15853
|
-
}
|
|
15854
|
-
case "create_board": {
|
|
15855
|
-
if (!input.title) return fail("create_board requires title.");
|
|
15856
|
-
const board = await createBoard2(projectRoot, {
|
|
15857
|
-
title: input.title,
|
|
15858
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
15859
|
-
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
15860
|
-
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
|
|
15861
|
-
});
|
|
15862
|
-
return { ok: true, message: `Board created: ${board.title}`, board };
|
|
15863
|
-
}
|
|
15864
|
-
case "update_board": {
|
|
15865
|
-
if (!input.boardId) return fail("update_board requires boardId.");
|
|
15866
|
-
const board = await updateBoard2(projectRoot, input.boardId, {
|
|
15867
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
15868
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
15869
|
-
...input.tags !== void 0 ? { tags: input.tags } : {}
|
|
15870
|
-
});
|
|
15871
|
-
return board ? okBoard(board, "Board updated.") : fail("Board not found.");
|
|
15872
|
-
}
|
|
15873
|
-
case "duplicate_board": {
|
|
15874
|
-
if (!input.boardId) return fail("duplicate_board requires boardId.");
|
|
15875
|
-
const board = await duplicateBoard(projectRoot, input.boardId, {
|
|
15876
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
15877
|
-
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
15878
|
-
...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
|
|
15879
|
-
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
15880
|
-
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
|
|
15881
|
-
});
|
|
15882
|
-
return board ? okBoard(board, "Board duplicated.") : fail("Board not found.");
|
|
15883
|
-
}
|
|
15884
|
-
case "delete_board": {
|
|
15885
|
-
if (!input.boardId) return fail("delete_board requires boardId.");
|
|
15886
|
-
const removed = await removeBoard2(projectRoot, input.boardId);
|
|
15887
|
-
return { ok: removed, message: removed ? "Board deleted." : "Board not found." };
|
|
15888
|
-
}
|
|
15889
|
-
case "generate_board": {
|
|
15890
|
-
if (!input.description) return fail("generate_board requires description.");
|
|
15891
|
-
const boardInput = generateBoardFromDescription({
|
|
15892
|
-
description: input.description,
|
|
15893
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
15894
|
-
...input.context !== void 0 ? { context: input.context } : {},
|
|
15895
|
-
...input.columns !== void 0 ? { columns: input.columns } : {}
|
|
15896
|
-
});
|
|
15897
|
-
const board = await createBoard2(projectRoot, boardInput);
|
|
15898
|
-
for (const taskInput2 of parseLinesIntoTasks(
|
|
15899
|
-
input.description,
|
|
15900
|
-
board.columns[0]?.id ?? "backlog"
|
|
15901
|
-
)) {
|
|
15902
|
-
await addTask(projectRoot, board.id, taskInput2);
|
|
16646
|
+
const result = await (async () => {
|
|
16647
|
+
switch (input.action) {
|
|
16648
|
+
case "list_boards": {
|
|
16649
|
+
const boards = await listBoards2(projectRoot);
|
|
16650
|
+
return { ok: true, message: `${boards.length} board(s).`, boards };
|
|
15903
16651
|
}
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
const board = await requireBoard(projectRoot, input.boardId);
|
|
15908
|
-
if (!board) return fail("Board not found.");
|
|
15909
|
-
return {
|
|
15910
|
-
ok: true,
|
|
15911
|
-
message: "Board exported.",
|
|
15912
|
-
board,
|
|
15913
|
-
markdown: exportBoardAsMarkdown(board)
|
|
15914
|
-
};
|
|
15915
|
-
}
|
|
15916
|
-
case "export_task_graph": {
|
|
15917
|
-
if (!input.boardId) return fail("export_task_graph requires boardId.");
|
|
15918
|
-
const exported = await exportBoardToTaskGraph(projectRoot, input.boardId, {
|
|
15919
|
-
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
15920
|
-
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
15921
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
15922
|
-
...input.preserveOriginTaskIds !== void 0 ? { preserveOriginTaskIds: input.preserveOriginTaskIds } : {},
|
|
15923
|
-
...input.includeArchived !== void 0 ? { includeArchived: input.includeArchived } : {}
|
|
15924
|
-
});
|
|
15925
|
-
if (!exported) return fail("Board not found.");
|
|
15926
|
-
return {
|
|
15927
|
-
ok: true,
|
|
15928
|
-
message: `Task graph exported with ${exported.graph.nodes.size} node(s).`,
|
|
15929
|
-
board: exported.board,
|
|
15930
|
-
taskGraph: serializeTaskGraph(exported.graph)
|
|
15931
|
-
};
|
|
15932
|
-
}
|
|
15933
|
-
case "sync_task_graph": {
|
|
15934
|
-
if (!input.boardId || !input.taskGraph) {
|
|
15935
|
-
return fail("sync_task_graph requires boardId and taskGraph.");
|
|
16652
|
+
case "get_board": {
|
|
16653
|
+
const board = await requireBoard(projectRoot, input.boardId);
|
|
16654
|
+
return board ? okBoard(board) : fail("Board not found.");
|
|
15936
16655
|
}
|
|
15937
|
-
|
|
15938
|
-
|
|
15939
|
-
|
|
15940
|
-
|
|
15941
|
-
|
|
15942
|
-
|
|
15943
|
-
|
|
15944
|
-
|
|
15945
|
-
|
|
15946
|
-
|
|
15947
|
-
|
|
15948
|
-
|
|
15949
|
-
|
|
15950
|
-
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
|
|
15955
|
-
|
|
15956
|
-
|
|
15957
|
-
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
|
|
15961
|
-
|
|
15962
|
-
|
|
15963
|
-
|
|
15964
|
-
|
|
15965
|
-
|
|
15966
|
-
}
|
|
15967
|
-
|
|
15968
|
-
|
|
15969
|
-
|
|
15970
|
-
|
|
15971
|
-
}
|
|
15972
|
-
|
|
15973
|
-
|
|
15974
|
-
|
|
15975
|
-
|
|
15976
|
-
|
|
15977
|
-
|
|
15978
|
-
|
|
15979
|
-
|
|
15980
|
-
|
|
15981
|
-
|
|
15982
|
-
|
|
15983
|
-
|
|
15984
|
-
|
|
15985
|
-
|
|
16656
|
+
case "create_board": {
|
|
16657
|
+
if (!input.title) return fail("create_board requires title.");
|
|
16658
|
+
const board = await createBoard2(projectRoot, {
|
|
16659
|
+
title: input.title,
|
|
16660
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16661
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
16662
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
|
|
16663
|
+
});
|
|
16664
|
+
return { ok: true, message: `Board created: ${board.title}`, board };
|
|
16665
|
+
}
|
|
16666
|
+
case "update_board": {
|
|
16667
|
+
if (!input.boardId) return fail("update_board requires boardId.");
|
|
16668
|
+
const board = await updateBoard2(projectRoot, input.boardId, {
|
|
16669
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16670
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16671
|
+
...input.tags !== void 0 ? { tags: input.tags } : {}
|
|
16672
|
+
});
|
|
16673
|
+
return board ? okBoard(board, "Board updated.") : fail("Board not found.");
|
|
16674
|
+
}
|
|
16675
|
+
case "duplicate_board": {
|
|
16676
|
+
if (!input.boardId) return fail("duplicate_board requires boardId.");
|
|
16677
|
+
const board = await duplicateBoard(projectRoot, input.boardId, {
|
|
16678
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16679
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
16680
|
+
...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
|
|
16681
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
16682
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
|
|
16683
|
+
});
|
|
16684
|
+
return board ? okBoard(board, "Board duplicated.") : fail("Board not found.");
|
|
16685
|
+
}
|
|
16686
|
+
case "delete_board": {
|
|
16687
|
+
if (!input.boardId) return fail("delete_board requires boardId.");
|
|
16688
|
+
const removed = await removeBoard2(projectRoot, input.boardId);
|
|
16689
|
+
return { ok: removed, message: removed ? "Board deleted." : "Board not found." };
|
|
16690
|
+
}
|
|
16691
|
+
case "generate_board": {
|
|
16692
|
+
if (!input.description) return fail("generate_board requires description.");
|
|
16693
|
+
const boardInput = createBoardFromText({
|
|
16694
|
+
description: input.description,
|
|
16695
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16696
|
+
...input.context !== void 0 ? { context: input.context } : {},
|
|
16697
|
+
...input.columns !== void 0 ? { columns: input.columns } : {}
|
|
16698
|
+
});
|
|
16699
|
+
const board = await createBoard2(projectRoot, boardInput);
|
|
16700
|
+
for (const taskInput2 of parseLinesIntoTasks(
|
|
16701
|
+
input.description,
|
|
16702
|
+
board.columns[0]?.id ?? "backlog"
|
|
16703
|
+
)) {
|
|
16704
|
+
await addTask(projectRoot, board.id, taskInput2);
|
|
16705
|
+
}
|
|
16706
|
+
return okBoard(await getBoard2(projectRoot, board.id) ?? board, "Board generated.");
|
|
16707
|
+
}
|
|
16708
|
+
case "export_markdown": {
|
|
16709
|
+
const board = await requireBoard(projectRoot, input.boardId);
|
|
16710
|
+
if (!board) return fail("Board not found.");
|
|
16711
|
+
return {
|
|
16712
|
+
ok: true,
|
|
16713
|
+
message: "Board exported.",
|
|
16714
|
+
board,
|
|
16715
|
+
markdown: exportBoardAsMarkdown(board)
|
|
16716
|
+
};
|
|
16717
|
+
}
|
|
16718
|
+
case "export_task_graph": {
|
|
16719
|
+
if (!input.boardId) return fail("export_task_graph requires boardId.");
|
|
16720
|
+
const exported = await exportBoardToTaskGraph(projectRoot, input.boardId, {
|
|
16721
|
+
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
16722
|
+
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
16723
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16724
|
+
...input.preserveOriginTaskIds !== void 0 ? { preserveOriginTaskIds: input.preserveOriginTaskIds } : {},
|
|
16725
|
+
...input.includeArchived !== void 0 ? { includeArchived: input.includeArchived } : {}
|
|
16726
|
+
});
|
|
16727
|
+
if (!exported) return fail("Board not found.");
|
|
16728
|
+
return {
|
|
16729
|
+
ok: true,
|
|
16730
|
+
message: `Task graph exported with ${exported.graph.nodes.size} node(s).`,
|
|
16731
|
+
board: exported.board,
|
|
16732
|
+
taskGraph: serializeTaskGraph(exported.graph)
|
|
16733
|
+
};
|
|
16734
|
+
}
|
|
16735
|
+
case "sync_task_graph": {
|
|
16736
|
+
if (!input.boardId || !input.taskGraph) {
|
|
16737
|
+
return fail("sync_task_graph requires boardId and taskGraph.");
|
|
16738
|
+
}
|
|
16739
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
16740
|
+
const result2 = await syncBoardFromTaskGraph2(projectRoot, input.boardId, graph, {
|
|
16741
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16742
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16743
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
16744
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
16745
|
+
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
16746
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
16747
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
16748
|
+
...input.archiveMissingTasks !== void 0 ? { archiveMissingTasks: input.archiveMissingTasks } : {},
|
|
16749
|
+
...input.preserveManualDependencies !== void 0 ? { preserveManualDependencies: input.preserveManualDependencies } : {}
|
|
16750
|
+
});
|
|
16751
|
+
return result2 ? {
|
|
16752
|
+
ok: true,
|
|
16753
|
+
message: `Task graph synced: ${result2.createdTaskIds.length} created, ${result2.updatedTaskIds.length} updated, ${result2.archivedTaskIds.length} archived.`,
|
|
16754
|
+
board: result2.board
|
|
16755
|
+
} : fail("Board not found.");
|
|
16756
|
+
}
|
|
16757
|
+
case "create_from_graph": {
|
|
16758
|
+
if (!input.taskGraph) return fail("create_from_graph requires taskGraph.");
|
|
16759
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
16760
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
16761
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16762
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16763
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
16764
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
16765
|
+
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
16766
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
16767
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {}
|
|
16768
|
+
});
|
|
16769
|
+
return {
|
|
16770
|
+
ok: true,
|
|
16771
|
+
message: `Created board "${board.title}" from task graph with ${board.tasks.length} tasks.`,
|
|
16772
|
+
board
|
|
16773
|
+
};
|
|
16774
|
+
}
|
|
16775
|
+
case "import_session_tasks": {
|
|
16776
|
+
const taskPath = ctx.meta?.["task.path"];
|
|
16777
|
+
if (!taskPath) return fail("No session task file for this session.");
|
|
16778
|
+
const file = await loadTasks2(taskPath);
|
|
16779
|
+
if (!file || file.tasks.length === 0) return fail("No session tasks to import.");
|
|
16780
|
+
const sessionId = ctx.session?.id ?? file.sessionId ?? "session";
|
|
16781
|
+
const graph = deserializeTaskGraph2(taskFileToSerializedGraph(file.tasks, sessionId));
|
|
16782
|
+
const tags = ["session", `session:${sessionId}`];
|
|
16783
|
+
const existing = (await listBoards2(projectRoot)).find(
|
|
16784
|
+
(b) => b.tags?.includes(`session:${sessionId}`)
|
|
16785
|
+
);
|
|
16786
|
+
if (existing) {
|
|
16787
|
+
const result2 = await syncBoardFromTaskGraph2(projectRoot, existing.id, graph, {
|
|
16788
|
+
sourceSystem: "session",
|
|
16789
|
+
tags,
|
|
16790
|
+
archiveMissingTasks: true,
|
|
16791
|
+
includeCompletedTasks: true
|
|
16792
|
+
});
|
|
16793
|
+
return result2 ? {
|
|
16794
|
+
ok: true,
|
|
16795
|
+
message: `Synced ${file.tasks.length} session tasks into board "${result2.board.title}".`,
|
|
16796
|
+
board: result2.board
|
|
16797
|
+
} : fail("Session board vanished mid-sync.");
|
|
16798
|
+
}
|
|
16799
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
16800
|
+
title: `Session tasks (${sessionId.slice(0, 8)})`,
|
|
15986
16801
|
sourceSystem: "session",
|
|
15987
|
-
tags
|
|
15988
|
-
archiveMissingTasks: true,
|
|
15989
|
-
includeCompletedTasks: true
|
|
16802
|
+
tags
|
|
15990
16803
|
});
|
|
15991
|
-
return
|
|
16804
|
+
return {
|
|
15992
16805
|
ok: true,
|
|
15993
|
-
message: `
|
|
15994
|
-
board
|
|
15995
|
-
}
|
|
16806
|
+
message: `Imported ${file.tasks.length} session tasks into new board "${board.title}".`,
|
|
16807
|
+
board
|
|
16808
|
+
};
|
|
15996
16809
|
}
|
|
15997
|
-
|
|
15998
|
-
|
|
15999
|
-
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
case "search_tasks": {
|
|
16009
|
-
const tasks = await searchKanban(projectRoot, {
|
|
16010
|
-
query: input.query,
|
|
16011
|
-
boardId: input.boardId,
|
|
16012
|
-
assignedAgent: input.agentId,
|
|
16013
|
-
status: input.status,
|
|
16014
|
-
priority: input.priority,
|
|
16015
|
-
label: input.labels?.[0],
|
|
16016
|
-
chainId: input.chainId
|
|
16017
|
-
});
|
|
16018
|
-
return { ok: true, message: `${tasks.length} task(s) matched.`, tasks };
|
|
16019
|
-
}
|
|
16020
|
-
case "ready_tasks": {
|
|
16021
|
-
const tasks = await listReadyTasks(projectRoot, {
|
|
16022
|
-
query: input.query,
|
|
16023
|
-
boardId: input.boardId,
|
|
16024
|
-
assignedAgent: input.agentId,
|
|
16025
|
-
priority: input.priority,
|
|
16026
|
-
label: input.labels?.[0],
|
|
16027
|
-
chainId: input.chainId,
|
|
16028
|
-
limit: input.limit
|
|
16029
|
-
});
|
|
16030
|
-
return { ok: true, message: `${tasks.length} ready task(s).`, tasks };
|
|
16031
|
-
}
|
|
16032
|
-
case "snapshot": {
|
|
16033
|
-
const snapshot = await getKanbanOrchestrationSnapshot(projectRoot, {
|
|
16034
|
-
query: input.query,
|
|
16035
|
-
boardId: input.boardId,
|
|
16036
|
-
assignedAgent: input.agentId,
|
|
16037
|
-
status: input.status,
|
|
16038
|
-
priority: input.priority,
|
|
16039
|
-
label: input.labels?.[0],
|
|
16040
|
-
chainId: input.chainId
|
|
16041
|
-
});
|
|
16042
|
-
return {
|
|
16043
|
-
ok: true,
|
|
16044
|
-
message: `${snapshot.ready.length} ready, ${snapshot.running.length} running, ${snapshot.blocked.length} blocked.`,
|
|
16045
|
-
snapshot
|
|
16046
|
-
};
|
|
16047
|
-
}
|
|
16048
|
-
case "add_column": {
|
|
16049
|
-
if (!input.boardId || !input.title) return fail("add_column requires boardId and title.");
|
|
16050
|
-
const result = await addColumn(projectRoot, input.boardId, {
|
|
16051
|
-
title: input.title,
|
|
16052
|
-
...input.description !== void 0 ? { description: input.description } : {}
|
|
16053
|
-
});
|
|
16054
|
-
return result ? okBoard(result.board, "Column added.") : fail("Board not found.");
|
|
16055
|
-
}
|
|
16056
|
-
case "update_column": {
|
|
16057
|
-
if (!input.boardId || !input.columnId)
|
|
16058
|
-
return fail("update_column requires boardId and columnId.");
|
|
16059
|
-
const board = await updateColumn(projectRoot, input.boardId, input.columnId, {
|
|
16060
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
16061
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
16062
|
-
...input.order !== void 0 ? { order: input.order } : {}
|
|
16063
|
-
});
|
|
16064
|
-
return board ? okBoard(board, "Column updated.") : fail("Column not found.");
|
|
16065
|
-
}
|
|
16066
|
-
case "delete_column": {
|
|
16067
|
-
if (!input.boardId || !input.columnId)
|
|
16068
|
-
return fail("delete_column requires boardId and columnId.");
|
|
16069
|
-
const board = await removeColumn(projectRoot, input.boardId, input.columnId, {
|
|
16070
|
-
moveTasksToColumnId: input.moveTasksToColumnId
|
|
16071
|
-
});
|
|
16072
|
-
return board ? okBoard(board, "Column deleted.") : fail("Column not found.");
|
|
16073
|
-
}
|
|
16074
|
-
case "add_task": {
|
|
16075
|
-
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
16076
|
-
const result = await addTask(projectRoot, input.boardId, taskInput(input));
|
|
16077
|
-
return result ? okTask(result.board, result.task, "Task added.") : fail("Board not found.");
|
|
16078
|
-
}
|
|
16079
|
-
case "split_task": {
|
|
16080
|
-
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
16081
|
-
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
16810
|
+
case "search_tasks": {
|
|
16811
|
+
const tasks = await searchKanban(projectRoot, {
|
|
16812
|
+
query: input.query,
|
|
16813
|
+
boardId: input.boardId,
|
|
16814
|
+
assignedAgent: input.agentId,
|
|
16815
|
+
status: input.status,
|
|
16816
|
+
priority: input.priority,
|
|
16817
|
+
label: input.labels?.[0],
|
|
16818
|
+
chainId: input.chainId
|
|
16819
|
+
});
|
|
16820
|
+
return { ok: true, message: `${tasks.length} task(s) matched.`, tasks };
|
|
16082
16821
|
}
|
|
16083
|
-
|
|
16084
|
-
|
|
16085
|
-
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
16092
|
-
|
|
16093
|
-
|
|
16094
|
-
return result ? {
|
|
16095
|
-
ok: true,
|
|
16096
|
-
message: `${result.children.length} child task(s) created.`,
|
|
16097
|
-
board: result.board,
|
|
16098
|
-
task: result.parent,
|
|
16099
|
-
children: result.children
|
|
16100
|
-
} : fail("Task not found.");
|
|
16101
|
-
}
|
|
16102
|
-
case "merge_tasks": {
|
|
16103
|
-
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
16104
|
-
return fail("merge_tasks requires boardId, taskIds, and title.");
|
|
16822
|
+
case "ready_tasks": {
|
|
16823
|
+
const tasks = await listReadyTasks(projectRoot, {
|
|
16824
|
+
query: input.query,
|
|
16825
|
+
boardId: input.boardId,
|
|
16826
|
+
assignedAgent: input.agentId,
|
|
16827
|
+
priority: input.priority,
|
|
16828
|
+
label: input.labels?.[0],
|
|
16829
|
+
chainId: input.chainId,
|
|
16830
|
+
limit: input.limit
|
|
16831
|
+
});
|
|
16832
|
+
return { ok: true, message: `${tasks.length} ready task(s).`, tasks };
|
|
16105
16833
|
}
|
|
16106
|
-
|
|
16107
|
-
|
|
16108
|
-
|
|
16109
|
-
|
|
16110
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
16114
|
-
|
|
16115
|
-
|
|
16116
|
-
|
|
16117
|
-
|
|
16118
|
-
|
|
16834
|
+
case "snapshot": {
|
|
16835
|
+
const snapshot = await getKanbanOrchestrationSnapshot(projectRoot, {
|
|
16836
|
+
query: input.query,
|
|
16837
|
+
boardId: input.boardId,
|
|
16838
|
+
assignedAgent: input.agentId,
|
|
16839
|
+
status: input.status,
|
|
16840
|
+
priority: input.priority,
|
|
16841
|
+
label: input.labels?.[0],
|
|
16842
|
+
chainId: input.chainId
|
|
16843
|
+
});
|
|
16844
|
+
return {
|
|
16845
|
+
ok: true,
|
|
16846
|
+
message: `${snapshot.ready.length} ready, ${snapshot.running.length} running, ${snapshot.blocked.length} blocked.`,
|
|
16847
|
+
snapshot
|
|
16848
|
+
};
|
|
16119
16849
|
}
|
|
16120
|
-
|
|
16121
|
-
|
|
16122
|
-
input.boardId,
|
|
16123
|
-
|
|
16124
|
-
|
|
16125
|
-
|
|
16126
|
-
|
|
16127
|
-
|
|
16128
|
-
|
|
16129
|
-
|
|
16850
|
+
case "add_column": {
|
|
16851
|
+
if (!input.boardId || !input.title) return fail("add_column requires boardId and title.");
|
|
16852
|
+
const result2 = await addColumn(projectRoot, input.boardId, {
|
|
16853
|
+
title: input.title,
|
|
16854
|
+
...input.description !== void 0 ? { description: input.description } : {}
|
|
16855
|
+
});
|
|
16856
|
+
return result2 ? okBoard(result2.board, "Column added.") : fail("Board not found.");
|
|
16857
|
+
}
|
|
16858
|
+
case "update_column": {
|
|
16859
|
+
if (!input.boardId || !input.columnId)
|
|
16860
|
+
return fail("update_column requires boardId and columnId.");
|
|
16861
|
+
const board = await updateColumn(projectRoot, input.boardId, input.columnId, {
|
|
16862
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16863
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16864
|
+
...input.order !== void 0 ? { order: input.order } : {}
|
|
16865
|
+
});
|
|
16866
|
+
return board ? okBoard(board, "Column updated.") : fail("Column not found.");
|
|
16867
|
+
}
|
|
16868
|
+
case "delete_column": {
|
|
16869
|
+
if (!input.boardId || !input.columnId)
|
|
16870
|
+
return fail("delete_column requires boardId and columnId.");
|
|
16871
|
+
const board = await removeColumn(projectRoot, input.boardId, input.columnId, {
|
|
16872
|
+
moveTasksToColumnId: input.moveTasksToColumnId
|
|
16873
|
+
});
|
|
16874
|
+
return board ? okBoard(board, "Column deleted.") : fail("Column not found.");
|
|
16875
|
+
}
|
|
16876
|
+
case "add_task": {
|
|
16877
|
+
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
16878
|
+
const result2 = await addTask(projectRoot, input.boardId, taskInput(input));
|
|
16879
|
+
return result2 ? okTask(result2.board, result2.task, "Task added.") : fail("Board not found.");
|
|
16880
|
+
}
|
|
16881
|
+
case "split_task": {
|
|
16882
|
+
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
16883
|
+
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
16130
16884
|
}
|
|
16131
|
-
|
|
16132
|
-
|
|
16133
|
-
|
|
16134
|
-
|
|
16135
|
-
|
|
16136
|
-
|
|
16885
|
+
const result2 = await splitTask(projectRoot, input.boardId, input.taskId, {
|
|
16886
|
+
titles: input.childTitles,
|
|
16887
|
+
...input.targetColumnId !== void 0 ? { columnId: input.targetColumnId } : {},
|
|
16888
|
+
...input.inheritAssignment !== void 0 ? { inheritAssignment: input.inheritAssignment } : {},
|
|
16889
|
+
...input.inheritLabels !== void 0 ? { inheritLabels: input.inheritLabels } : {},
|
|
16890
|
+
...input.inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria: input.inheritSuccessCriteria } : {},
|
|
16891
|
+
...input.inheritGoalMetrics !== void 0 ? { inheritGoalMetrics: input.inheritGoalMetrics } : {},
|
|
16892
|
+
...input.inheritDependencies !== void 0 ? { inheritDependencies: input.inheritDependencies } : {},
|
|
16893
|
+
...input.chainChildren !== void 0 ? { chainChildren: input.chainChildren } : {},
|
|
16894
|
+
...input.rewireDependents !== void 0 ? { rewireDependents: input.rewireDependents } : {}
|
|
16895
|
+
});
|
|
16896
|
+
return result2 ? {
|
|
16897
|
+
ok: true,
|
|
16898
|
+
message: `${result2.children.length} child task(s) created.`,
|
|
16899
|
+
board: result2.board,
|
|
16900
|
+
task: result2.parent,
|
|
16901
|
+
children: result2.children
|
|
16902
|
+
} : fail("Task not found.");
|
|
16137
16903
|
}
|
|
16138
|
-
|
|
16139
|
-
|
|
16140
|
-
|
|
16141
|
-
|
|
16142
|
-
input.
|
|
16143
|
-
|
|
16904
|
+
case "merge_tasks": {
|
|
16905
|
+
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
16906
|
+
return fail("merge_tasks requires boardId, taskIds, and title.");
|
|
16907
|
+
}
|
|
16908
|
+
const result2 = await mergeTasks(projectRoot, input.boardId, {
|
|
16909
|
+
taskIds: input.taskIds,
|
|
16910
|
+
title: input.title,
|
|
16911
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16144
16912
|
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
16145
|
-
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
16146
16913
|
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
16147
|
-
...input.
|
|
16914
|
+
...input.closeSourceTasks !== void 0 ? { closeSourceTasks: input.closeSourceTasks } : {}
|
|
16915
|
+
});
|
|
16916
|
+
return result2 ? okTask(result2.board, result2.task, "Tasks merged.") : fail("Board or task not found.");
|
|
16917
|
+
}
|
|
16918
|
+
case "copy_task": {
|
|
16919
|
+
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
16920
|
+
return fail("copy_task requires boardId, taskId, and targetBoardId.");
|
|
16148
16921
|
}
|
|
16149
|
-
|
|
16150
|
-
|
|
16151
|
-
|
|
16152
|
-
|
|
16153
|
-
|
|
16154
|
-
|
|
16155
|
-
|
|
16156
|
-
|
|
16157
|
-
|
|
16158
|
-
|
|
16159
|
-
|
|
16160
|
-
|
|
16161
|
-
|
|
16162
|
-
input.boardId,
|
|
16163
|
-
input.taskId,
|
|
16164
|
-
taskPatch(input)
|
|
16165
|
-
);
|
|
16166
|
-
return board ? okBoard(board, "Task updated.") : fail("Task not found.");
|
|
16167
|
-
}
|
|
16168
|
-
case "move_task": {
|
|
16169
|
-
if (!input.boardId || !input.taskId || !input.targetColumnId) {
|
|
16170
|
-
return fail("move_task requires boardId, taskId, and targetColumnId.");
|
|
16922
|
+
const result2 = await copyTaskToBoard(
|
|
16923
|
+
projectRoot,
|
|
16924
|
+
input.boardId,
|
|
16925
|
+
input.taskId,
|
|
16926
|
+
input.targetBoardId,
|
|
16927
|
+
{
|
|
16928
|
+
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
16929
|
+
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
16930
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
16931
|
+
...input.preserveDependencies !== void 0 ? { preserveDependencies: input.preserveDependencies } : {}
|
|
16932
|
+
}
|
|
16933
|
+
);
|
|
16934
|
+
return result2 ? okTask(result2.targetBoard, result2.task, "Task copied to target board.") : fail("Board or task not found.");
|
|
16171
16935
|
}
|
|
16172
|
-
|
|
16173
|
-
|
|
16174
|
-
|
|
16175
|
-
|
|
16176
|
-
|
|
16177
|
-
|
|
16178
|
-
|
|
16179
|
-
|
|
16180
|
-
|
|
16181
|
-
|
|
16182
|
-
|
|
16183
|
-
|
|
16184
|
-
|
|
16185
|
-
|
|
16186
|
-
|
|
16187
|
-
|
|
16188
|
-
|
|
16189
|
-
return fail("set_chain requires boardId and taskIds.");
|
|
16936
|
+
case "transfer_task": {
|
|
16937
|
+
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
16938
|
+
return fail("transfer_task requires boardId, taskId, and targetBoardId.");
|
|
16939
|
+
}
|
|
16940
|
+
const result2 = await transferTaskToBoard(
|
|
16941
|
+
projectRoot,
|
|
16942
|
+
input.boardId,
|
|
16943
|
+
input.taskId,
|
|
16944
|
+
input.targetBoardId,
|
|
16945
|
+
{
|
|
16946
|
+
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
16947
|
+
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
16948
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
16949
|
+
...input.preserveDependencies !== void 0 ? { preserveDependencies: input.preserveDependencies } : {}
|
|
16950
|
+
}
|
|
16951
|
+
);
|
|
16952
|
+
return result2 ? okTask(result2.targetBoard, result2.task, "Task transferred to target board.") : fail("Board or task not found.");
|
|
16190
16953
|
}
|
|
16191
|
-
|
|
16192
|
-
|
|
16193
|
-
|
|
16194
|
-
|
|
16195
|
-
});
|
|
16196
|
-
return result ? {
|
|
16197
|
-
ok: true,
|
|
16198
|
-
message: `Chain set: ${result.chainId}`,
|
|
16199
|
-
board: result.board,
|
|
16200
|
-
chain: result.tasks
|
|
16201
|
-
} : fail("Board or task not found.");
|
|
16202
|
-
}
|
|
16203
|
-
case "get_chain": {
|
|
16204
|
-
if (!input.boardId || !(input.taskId || input.chainId)) {
|
|
16205
|
-
return fail("get_chain requires boardId and taskId or chainId.");
|
|
16954
|
+
case "get_task": {
|
|
16955
|
+
if (!input.boardId || !input.taskId) return fail("get_task requires boardId and taskId.");
|
|
16956
|
+
const task = await getTask(projectRoot, input.boardId, input.taskId);
|
|
16957
|
+
return task ? { ok: true, message: "Task loaded.", task } : fail("Task not found.");
|
|
16206
16958
|
}
|
|
16207
|
-
|
|
16208
|
-
|
|
16209
|
-
|
|
16210
|
-
|
|
16211
|
-
|
|
16212
|
-
|
|
16213
|
-
|
|
16214
|
-
|
|
16215
|
-
|
|
16216
|
-
|
|
16217
|
-
} : fail("Chain not found.");
|
|
16218
|
-
}
|
|
16219
|
-
case "claim_task": {
|
|
16220
|
-
const result = await claimReadyTask(projectRoot, {
|
|
16221
|
-
...input.boardId !== void 0 ? { boardId: input.boardId } : {},
|
|
16222
|
-
...input.taskId !== void 0 ? { taskId: input.taskId } : {},
|
|
16223
|
-
...assignmentInput(input),
|
|
16224
|
-
status: input.assignmentStatus ?? "queued"
|
|
16225
|
-
});
|
|
16226
|
-
return result ? okTask(result.board, result.task, "Task claimed.") : fail("No ready kanban task matched the claim.");
|
|
16227
|
-
}
|
|
16228
|
-
case "release_task": {
|
|
16229
|
-
if (!input.boardId || !input.taskId) {
|
|
16230
|
-
return fail("release_task requires boardId and taskId.");
|
|
16959
|
+
case "update_task": {
|
|
16960
|
+
if (!input.boardId || !input.taskId)
|
|
16961
|
+
return fail("update_task requires boardId and taskId.");
|
|
16962
|
+
const board = await updateTask(
|
|
16963
|
+
projectRoot,
|
|
16964
|
+
input.boardId,
|
|
16965
|
+
input.taskId,
|
|
16966
|
+
taskPatch(input)
|
|
16967
|
+
);
|
|
16968
|
+
return board ? okBoard(board, "Task updated.") : fail("Task not found.");
|
|
16231
16969
|
}
|
|
16232
|
-
|
|
16233
|
-
|
|
16234
|
-
|
|
16235
|
-
|
|
16236
|
-
|
|
16237
|
-
|
|
16238
|
-
|
|
16239
|
-
|
|
16240
|
-
|
|
16241
|
-
|
|
16242
|
-
|
|
16243
|
-
|
|
16244
|
-
|
|
16245
|
-
|
|
16246
|
-
|
|
16247
|
-
|
|
16248
|
-
|
|
16249
|
-
|
|
16250
|
-
|
|
16251
|
-
|
|
16252
|
-
return fail("
|
|
16253
|
-
const assignmentStatus = input.assignmentStatus ?? (input.status === "completed" ? "completed" : input.error ? "failed" : void 0);
|
|
16254
|
-
const board = await updateTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
16255
|
-
...assignmentStatus !== void 0 ? { status: assignmentStatus } : {},
|
|
16256
|
-
...input.subagentId !== void 0 ? { subagentId: input.subagentId } : {},
|
|
16257
|
-
...input.runTaskId !== void 0 ? { runTaskId: input.runTaskId } : {},
|
|
16258
|
-
...input.lastResult !== void 0 ? { lastResult: input.lastResult } : {},
|
|
16259
|
-
...input.error !== void 0 ? { error: input.error } : {},
|
|
16260
|
-
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
16261
|
-
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
16262
|
-
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
16263
|
-
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
16264
|
-
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
16265
|
-
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
16266
|
-
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {}
|
|
16267
|
-
});
|
|
16268
|
-
return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
|
|
16269
|
-
}
|
|
16270
|
-
case "heartbeat_assignment": {
|
|
16271
|
-
if (!input.boardId || !input.taskId) {
|
|
16272
|
-
return fail("heartbeat_assignment requires boardId and taskId.");
|
|
16970
|
+
case "transition_task": {
|
|
16971
|
+
if (!input.boardId || !input.taskId || !input.lifecycleStage || !input.author || !input.transitionComment) {
|
|
16972
|
+
return fail(
|
|
16973
|
+
"transition_task requires boardId, taskId, lifecycleStage, author, and transitionComment."
|
|
16974
|
+
);
|
|
16975
|
+
}
|
|
16976
|
+
const result2 = await transitionTask(projectRoot, input.boardId, input.taskId, {
|
|
16977
|
+
to: input.lifecycleStage,
|
|
16978
|
+
actor: input.author,
|
|
16979
|
+
comment: input.transitionComment,
|
|
16980
|
+
...input.transitionAction !== void 0 ? { action: input.transitionAction } : {},
|
|
16981
|
+
...input.attachmentUrl !== void 0 ? {
|
|
16982
|
+
attachment: {
|
|
16983
|
+
url: input.attachmentUrl,
|
|
16984
|
+
type: input.attachmentType ?? "url",
|
|
16985
|
+
...input.attachmentTitle !== void 0 ? { title: input.attachmentTitle } : {}
|
|
16986
|
+
}
|
|
16987
|
+
} : {},
|
|
16988
|
+
patch: taskPatch(input)
|
|
16989
|
+
});
|
|
16990
|
+
return result2 ? okTask(result2.board, result2.task, `Task advanced to ${result2.transition.to}.`) : fail("Board or task not found.");
|
|
16273
16991
|
}
|
|
16274
|
-
|
|
16275
|
-
|
|
16276
|
-
|
|
16277
|
-
|
|
16278
|
-
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
|
|
16282
|
-
|
|
16283
|
-
|
|
16284
|
-
|
|
16285
|
-
|
|
16286
|
-
input.recoveryPolicyRetryPolicyOverride !== void 0
|
|
16287
|
-
].some(Boolean);
|
|
16288
|
-
const result = await recoverStaleTaskAssignments(projectRoot, input.boardId, {
|
|
16289
|
-
...input.recoveryMode !== void 0 ? { mode: input.recoveryMode } : {},
|
|
16290
|
-
...input.recoveryNow !== void 0 ? { now: input.recoveryNow } : {},
|
|
16291
|
-
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
16292
|
-
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {},
|
|
16293
|
-
...policyFields ? {
|
|
16294
|
-
policy: {
|
|
16295
|
-
...input.recoveryPolicyFailOnCostCeiling !== void 0 ? { failWhenCostCeilingSet: input.recoveryPolicyFailOnCostCeiling } : {},
|
|
16296
|
-
...input.recoveryPolicyReleaseOnFailureKinds !== void 0 ? {
|
|
16297
|
-
releaseOnFailureKinds: input.recoveryPolicyReleaseOnFailureKinds
|
|
16298
|
-
} : {},
|
|
16299
|
-
...input.recoveryPolicyReleaseOnHeartbeatDue !== void 0 ? {
|
|
16300
|
-
releaseOnHeartbeatDue: input.recoveryPolicyReleaseOnHeartbeatDue
|
|
16301
|
-
} : {},
|
|
16302
|
-
...input.recoveryPolicyRetryPolicyOverride !== void 0 ? {
|
|
16303
|
-
retryPolicyOverride: input.recoveryPolicyRetryPolicyOverride
|
|
16304
|
-
} : {}
|
|
16305
|
-
}
|
|
16306
|
-
} : {}
|
|
16307
|
-
});
|
|
16308
|
-
return result ? {
|
|
16309
|
-
ok: true,
|
|
16310
|
-
message: `Recovered ${result.tasks.length} stale assignment(s).`,
|
|
16311
|
-
board: result.board,
|
|
16312
|
-
recoveredTasks: result.tasks
|
|
16313
|
-
} : { ok: true, message: "No stale assignment matched.", recoveredTasks: [] };
|
|
16314
|
-
}
|
|
16315
|
-
case "events": {
|
|
16316
|
-
if (!input.boardId) return fail("events requires boardId.");
|
|
16317
|
-
const eventList = await listKanbanEvents(projectRoot, input.boardId);
|
|
16318
|
-
return {
|
|
16319
|
-
ok: true,
|
|
16320
|
-
message: `${eventList.length} event(s).`,
|
|
16321
|
-
events: eventList
|
|
16322
|
-
};
|
|
16323
|
-
}
|
|
16324
|
-
case "queue_health": {
|
|
16325
|
-
const health = await getKanbanQueueHealth(projectRoot, {
|
|
16326
|
-
...input.boardId !== void 0 ? { boardId: input.boardId } : {}
|
|
16327
|
-
});
|
|
16328
|
-
return {
|
|
16329
|
-
ok: true,
|
|
16330
|
-
message: `Counts: ready=${health.counts.ready}, running=${health.counts.running}, stale=${health.staleAssignments.count}.`,
|
|
16331
|
-
queueHealth: health
|
|
16332
|
-
};
|
|
16333
|
-
}
|
|
16334
|
-
case "add_dependency": {
|
|
16335
|
-
if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
|
|
16336
|
-
return fail("add_dependency requires boardId, taskId, and dependencyTaskId.");
|
|
16992
|
+
case "move_task": {
|
|
16993
|
+
if (!input.boardId || !input.taskId || !input.targetColumnId) {
|
|
16994
|
+
return fail("move_task requires boardId, taskId, and targetColumnId.");
|
|
16995
|
+
}
|
|
16996
|
+
const board = await moveTask(
|
|
16997
|
+
projectRoot,
|
|
16998
|
+
input.boardId,
|
|
16999
|
+
input.taskId,
|
|
17000
|
+
input.targetColumnId,
|
|
17001
|
+
input.order
|
|
17002
|
+
);
|
|
17003
|
+
return board ? okBoard(board, "Task moved.") : fail("Move failed.");
|
|
16337
17004
|
}
|
|
16338
|
-
|
|
16339
|
-
|
|
16340
|
-
|
|
16341
|
-
input.taskId
|
|
16342
|
-
|
|
16343
|
-
);
|
|
16344
|
-
return board ? okBoard(board, "Dependency added.") : fail("Task not found.");
|
|
16345
|
-
}
|
|
16346
|
-
case "add_goal_metric": {
|
|
16347
|
-
if (!input.boardId || !input.taskId || !input.metricName) {
|
|
16348
|
-
return fail("add_goal_metric requires boardId, taskId, and metricName.");
|
|
17005
|
+
case "delete_task": {
|
|
17006
|
+
if (!input.boardId || !input.taskId)
|
|
17007
|
+
return fail("delete_task requires boardId and taskId.");
|
|
17008
|
+
const board = await removeTask(projectRoot, input.boardId, input.taskId);
|
|
17009
|
+
return board ? okBoard(board, "Task deleted.") : fail("Task not found.");
|
|
16349
17010
|
}
|
|
16350
|
-
|
|
16351
|
-
|
|
16352
|
-
|
|
16353
|
-
|
|
16354
|
-
|
|
16355
|
-
|
|
16356
|
-
|
|
16357
|
-
|
|
16358
|
-
|
|
16359
|
-
|
|
16360
|
-
|
|
16361
|
-
|
|
16362
|
-
|
|
17011
|
+
case "set_chain": {
|
|
17012
|
+
if (!input.boardId || !input.taskIds?.length) {
|
|
17013
|
+
return fail("set_chain requires boardId and taskIds.");
|
|
17014
|
+
}
|
|
17015
|
+
const result2 = await setTaskChain(projectRoot, input.boardId, {
|
|
17016
|
+
taskIds: input.taskIds,
|
|
17017
|
+
...input.chainId !== void 0 ? { chainId: input.chainId } : {},
|
|
17018
|
+
...input.enforceDependencies !== void 0 ? { enforceDependencies: input.enforceDependencies } : {}
|
|
17019
|
+
});
|
|
17020
|
+
return result2 ? {
|
|
17021
|
+
ok: true,
|
|
17022
|
+
message: `Chain set: ${result2.chainId}`,
|
|
17023
|
+
board: result2.board,
|
|
17024
|
+
chain: result2.tasks
|
|
17025
|
+
} : fail("Board or task not found.");
|
|
16363
17026
|
}
|
|
16364
|
-
|
|
16365
|
-
|
|
16366
|
-
|
|
16367
|
-
|
|
16368
|
-
|
|
16369
|
-
|
|
16370
|
-
|
|
17027
|
+
case "get_chain": {
|
|
17028
|
+
if (!input.boardId || !(input.taskId || input.chainId)) {
|
|
17029
|
+
return fail("get_chain requires boardId and taskId or chainId.");
|
|
17030
|
+
}
|
|
17031
|
+
const result2 = await getTaskChain(
|
|
17032
|
+
projectRoot,
|
|
17033
|
+
input.boardId,
|
|
17034
|
+
input.taskId ?? input.chainId ?? ""
|
|
17035
|
+
);
|
|
17036
|
+
return result2 ? {
|
|
17037
|
+
ok: true,
|
|
17038
|
+
message: `Chain loaded: ${result2.chainId}`,
|
|
17039
|
+
board: result2.board,
|
|
17040
|
+
chain: result2.tasks
|
|
17041
|
+
} : fail("Chain not found.");
|
|
17042
|
+
}
|
|
17043
|
+
case "claim_task": {
|
|
17044
|
+
const result2 = await claimReadyTask(projectRoot, {
|
|
17045
|
+
...input.boardId !== void 0 ? { boardId: input.boardId } : {},
|
|
17046
|
+
...input.taskId !== void 0 ? { taskId: input.taskId } : {},
|
|
17047
|
+
...assignmentInput(input),
|
|
17048
|
+
status: input.assignmentStatus ?? "queued"
|
|
17049
|
+
});
|
|
17050
|
+
return result2 ? okTask(result2.board, result2.task, "Task claimed.") : fail("No ready kanban task matched the claim.");
|
|
17051
|
+
}
|
|
17052
|
+
case "release_task": {
|
|
17053
|
+
if (!input.boardId || !input.taskId) {
|
|
17054
|
+
return fail("release_task requires boardId and taskId.");
|
|
17055
|
+
}
|
|
17056
|
+
const board = await releaseTaskClaim(projectRoot, input.boardId, input.taskId, {
|
|
17057
|
+
...input.releaseStatus !== void 0 ? { status: input.releaseStatus } : {},
|
|
17058
|
+
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
17059
|
+
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {}
|
|
17060
|
+
});
|
|
17061
|
+
return board ? okBoard(board, "Task claim released.") : fail("Task not found.");
|
|
17062
|
+
}
|
|
17063
|
+
case "assign_task": {
|
|
17064
|
+
if (!input.boardId || !input.taskId)
|
|
17065
|
+
return fail("assign_task requires boardId and taskId.");
|
|
17066
|
+
const board = await assignTask(
|
|
17067
|
+
projectRoot,
|
|
17068
|
+
input.boardId,
|
|
17069
|
+
input.taskId,
|
|
17070
|
+
assignmentInput(input)
|
|
17071
|
+
);
|
|
17072
|
+
return board ? okBoard(board, "Task assigned.") : fail("Task not found.");
|
|
17073
|
+
}
|
|
17074
|
+
case "mark_assignment": {
|
|
17075
|
+
if (!input.boardId || !input.taskId)
|
|
17076
|
+
return fail("mark_assignment requires boardId and taskId.");
|
|
17077
|
+
const assignmentStatus = input.assignmentStatus ?? (input.status === "completed" ? "completed" : input.error ? "failed" : void 0);
|
|
17078
|
+
const board = await updateTaskAssignment(
|
|
17079
|
+
projectRoot,
|
|
17080
|
+
input.boardId,
|
|
17081
|
+
input.taskId,
|
|
17082
|
+
{
|
|
17083
|
+
...assignmentStatus !== void 0 ? { status: assignmentStatus } : {},
|
|
17084
|
+
...input.subagentId !== void 0 ? { subagentId: input.subagentId } : {},
|
|
17085
|
+
...input.runTaskId !== void 0 ? { runTaskId: input.runTaskId } : {},
|
|
17086
|
+
...input.lastResult !== void 0 ? { lastResult: input.lastResult } : {},
|
|
17087
|
+
...input.error !== void 0 ? { error: input.error } : {},
|
|
17088
|
+
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
17089
|
+
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
17090
|
+
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
17091
|
+
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
17092
|
+
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
17093
|
+
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
17094
|
+
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {}
|
|
17095
|
+
},
|
|
17096
|
+
// Ownership fence: when expectedLeaseId is supplied, the write is
|
|
17097
|
+
// applied only if the current assignment still holds this lease.
|
|
17098
|
+
// This prevents a recovered+reassigned stale worker's terminal
|
|
17099
|
+
// mark_assignment from overwriting the successor's state. The check
|
|
17100
|
+
// is atomic inside updateTaskAssignment's mutateBoard lock.
|
|
17101
|
+
input.expectedLeaseId !== void 0 ? { expectedLeaseId: input.expectedLeaseId } : {}
|
|
17102
|
+
);
|
|
17103
|
+
return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
|
|
17104
|
+
}
|
|
17105
|
+
case "heartbeat_assignment": {
|
|
17106
|
+
if (!input.boardId || !input.taskId) {
|
|
17107
|
+
return fail("heartbeat_assignment requires boardId and taskId.");
|
|
17108
|
+
}
|
|
17109
|
+
const board = await heartbeatTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
17110
|
+
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
17111
|
+
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
17112
|
+
// Ownership fence: when expectedLeaseId is supplied, the renewal
|
|
17113
|
+
// is applied only if the current assignment still holds this lease.
|
|
17114
|
+
// This prevents a recovered+reassigned stale worker's heartbeat
|
|
17115
|
+
// from renewing the successor's lease. The check is atomic inside
|
|
17116
|
+
// heartbeatTaskAssignment's mutateBoard lock.
|
|
17117
|
+
...input.expectedLeaseId !== void 0 ? { expectedLeaseId: input.expectedLeaseId } : {}
|
|
17118
|
+
});
|
|
17119
|
+
return board ? okBoard(board, "Assignment heartbeat updated.") : fail("Task assignment not found.");
|
|
17120
|
+
}
|
|
17121
|
+
case "recover_stale": {
|
|
17122
|
+
if (!input.boardId) return fail("recover_stale requires boardId.");
|
|
17123
|
+
const policyFields = [
|
|
17124
|
+
input.recoveryPolicyFailOnCostCeiling !== void 0,
|
|
17125
|
+
input.recoveryPolicyReleaseOnFailureKinds !== void 0,
|
|
17126
|
+
input.recoveryPolicyReleaseOnHeartbeatDue !== void 0,
|
|
17127
|
+
input.recoveryPolicyRetryPolicyOverride !== void 0
|
|
17128
|
+
].some(Boolean);
|
|
17129
|
+
const result2 = await recoverStaleTaskAssignments(projectRoot, input.boardId, {
|
|
17130
|
+
...input.recoveryMode !== void 0 ? { mode: input.recoveryMode } : {},
|
|
17131
|
+
...input.recoveryNow !== void 0 ? { now: input.recoveryNow } : {},
|
|
17132
|
+
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
17133
|
+
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {},
|
|
17134
|
+
...policyFields ? {
|
|
17135
|
+
policy: {
|
|
17136
|
+
...input.recoveryPolicyFailOnCostCeiling !== void 0 ? { failWhenCostCeilingSet: input.recoveryPolicyFailOnCostCeiling } : {},
|
|
17137
|
+
...input.recoveryPolicyReleaseOnFailureKinds !== void 0 ? {
|
|
17138
|
+
releaseOnFailureKinds: input.recoveryPolicyReleaseOnFailureKinds
|
|
17139
|
+
} : {},
|
|
17140
|
+
...input.recoveryPolicyReleaseOnHeartbeatDue !== void 0 ? {
|
|
17141
|
+
releaseOnHeartbeatDue: input.recoveryPolicyReleaseOnHeartbeatDue
|
|
17142
|
+
} : {},
|
|
17143
|
+
...input.recoveryPolicyRetryPolicyOverride !== void 0 ? {
|
|
17144
|
+
retryPolicyOverride: input.recoveryPolicyRetryPolicyOverride
|
|
17145
|
+
} : {}
|
|
17146
|
+
}
|
|
17147
|
+
} : {}
|
|
17148
|
+
});
|
|
17149
|
+
return result2 ? {
|
|
17150
|
+
ok: true,
|
|
17151
|
+
message: `Recovered ${result2.tasks.length} stale assignment(s).`,
|
|
17152
|
+
board: result2.board,
|
|
17153
|
+
recoveredTasks: result2.tasks
|
|
17154
|
+
} : { ok: true, message: "No stale assignment matched.", recoveredTasks: [] };
|
|
17155
|
+
}
|
|
17156
|
+
case "events": {
|
|
17157
|
+
if (!input.boardId) return fail("events requires boardId.");
|
|
17158
|
+
const eventList = await listKanbanEvents(projectRoot, input.boardId);
|
|
17159
|
+
return {
|
|
17160
|
+
ok: true,
|
|
17161
|
+
message: `${eventList.length} event(s).`,
|
|
17162
|
+
events: eventList
|
|
17163
|
+
};
|
|
17164
|
+
}
|
|
17165
|
+
case "queue_health": {
|
|
17166
|
+
const health = await getKanbanQueueHealth(projectRoot, {
|
|
17167
|
+
...input.boardId !== void 0 ? { boardId: input.boardId } : {}
|
|
17168
|
+
});
|
|
17169
|
+
return {
|
|
17170
|
+
ok: true,
|
|
17171
|
+
message: `Counts: ready=${health.counts.ready}, running=${health.counts.running}, stale=${health.staleAssignments.count}.`,
|
|
17172
|
+
queueHealth: health
|
|
17173
|
+
};
|
|
17174
|
+
}
|
|
17175
|
+
case "add_dependency": {
|
|
17176
|
+
if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
|
|
17177
|
+
return fail("add_dependency requires boardId, taskId, and dependencyTaskId.");
|
|
17178
|
+
}
|
|
17179
|
+
const board = await addDependency(
|
|
17180
|
+
projectRoot,
|
|
17181
|
+
input.boardId,
|
|
17182
|
+
input.taskId,
|
|
17183
|
+
input.dependencyTaskId
|
|
17184
|
+
);
|
|
17185
|
+
return board ? okBoard(board, "Dependency added.") : fail("Task not found.");
|
|
17186
|
+
}
|
|
17187
|
+
case "add_goal_metric": {
|
|
17188
|
+
if (!input.boardId || !input.taskId || !input.metricName) {
|
|
17189
|
+
return fail("add_goal_metric requires boardId, taskId, and metricName.");
|
|
17190
|
+
}
|
|
17191
|
+
const board = await addGoalMetricToTask(projectRoot, input.boardId, input.taskId, {
|
|
17192
|
+
name: input.metricName,
|
|
16371
17193
|
...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
|
|
16372
17194
|
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
16373
17195
|
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
16374
17196
|
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
16375
17197
|
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17198
|
+
});
|
|
17199
|
+
return board ? okBoard(board, "Goal metric added.") : fail("Task not found.");
|
|
17200
|
+
}
|
|
17201
|
+
case "update_goal_metric": {
|
|
17202
|
+
if (!input.boardId || !input.taskId || !input.metricId) {
|
|
17203
|
+
return fail("update_goal_metric requires boardId, taskId, and metricId.");
|
|
16376
17204
|
}
|
|
16377
|
-
|
|
16378
|
-
|
|
16379
|
-
|
|
16380
|
-
|
|
16381
|
-
|
|
16382
|
-
|
|
17205
|
+
const board = await updateGoalMetricOnTask(
|
|
17206
|
+
projectRoot,
|
|
17207
|
+
input.boardId,
|
|
17208
|
+
input.taskId,
|
|
17209
|
+
input.metricId,
|
|
17210
|
+
{
|
|
17211
|
+
...input.metricName !== void 0 ? { name: input.metricName } : {},
|
|
17212
|
+
...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
|
|
17213
|
+
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
17214
|
+
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
17215
|
+
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
17216
|
+
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17217
|
+
}
|
|
17218
|
+
);
|
|
17219
|
+
return board ? okBoard(board, "Goal metric updated.") : fail("Metric not found.");
|
|
16383
17220
|
}
|
|
16384
|
-
|
|
16385
|
-
|
|
16386
|
-
|
|
16387
|
-
|
|
16388
|
-
|
|
16389
|
-
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
|
|
16393
|
-
return
|
|
17221
|
+
case "add_check": {
|
|
17222
|
+
if (!input.boardId || !input.taskId || !input.checkDescription) {
|
|
17223
|
+
return fail("add_check requires boardId, taskId, and checkDescription.");
|
|
17224
|
+
}
|
|
17225
|
+
const board = await addCheckToTask(projectRoot, input.boardId, input.taskId, {
|
|
17226
|
+
description: input.checkDescription,
|
|
17227
|
+
type: "manual",
|
|
17228
|
+
status: input.checkStatus
|
|
17229
|
+
});
|
|
17230
|
+
return board ? okBoard(board, "Check added.") : fail("Task not found.");
|
|
16394
17231
|
}
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
16398
|
-
input.taskId,
|
|
16399
|
-
input.checkId,
|
|
16400
|
-
{
|
|
16401
|
-
...input.checkDescription !== void 0 ? { description: input.checkDescription } : {},
|
|
16402
|
-
...input.checkStatus !== void 0 ? { status: input.checkStatus } : {}
|
|
17232
|
+
case "update_check": {
|
|
17233
|
+
if (!input.boardId || !input.taskId || !input.checkId) {
|
|
17234
|
+
return fail("update_check requires boardId, taskId, and checkId.");
|
|
16403
17235
|
}
|
|
16404
|
-
|
|
16405
|
-
|
|
16406
|
-
|
|
16407
|
-
|
|
16408
|
-
|
|
16409
|
-
|
|
16410
|
-
|
|
16411
|
-
|
|
16412
|
-
|
|
16413
|
-
|
|
16414
|
-
|
|
16415
|
-
|
|
16416
|
-
|
|
16417
|
-
|
|
16418
|
-
|
|
16419
|
-
|
|
16420
|
-
|
|
16421
|
-
|
|
16422
|
-
|
|
16423
|
-
|
|
16424
|
-
|
|
17236
|
+
const board = await updateCheckOnTask(
|
|
17237
|
+
projectRoot,
|
|
17238
|
+
input.boardId,
|
|
17239
|
+
input.taskId,
|
|
17240
|
+
input.checkId,
|
|
17241
|
+
{
|
|
17242
|
+
...input.checkDescription !== void 0 ? { description: input.checkDescription } : {},
|
|
17243
|
+
...input.checkStatus !== void 0 ? { status: input.checkStatus } : {}
|
|
17244
|
+
}
|
|
17245
|
+
);
|
|
17246
|
+
return board ? okBoard(board, "Check updated.") : fail("Check not found.");
|
|
17247
|
+
}
|
|
17248
|
+
case "add_note": {
|
|
17249
|
+
if (!input.boardId || !input.taskId || !input.note)
|
|
17250
|
+
return fail("add_note requires boardId, taskId, and note.");
|
|
17251
|
+
const board = await addNoteToTask(projectRoot, input.boardId, input.taskId, {
|
|
17252
|
+
author: input.author ?? "agent",
|
|
17253
|
+
content: input.note
|
|
17254
|
+
});
|
|
17255
|
+
return board ? okBoard(board, "Note added.") : fail("Task not found.");
|
|
17256
|
+
}
|
|
17257
|
+
case "add_link": {
|
|
17258
|
+
if (!input.boardId || !input.taskId || !input.url)
|
|
17259
|
+
return fail("add_link requires boardId, taskId, and url.");
|
|
17260
|
+
const board = await addLinkToTask(projectRoot, input.boardId, input.taskId, {
|
|
17261
|
+
url: input.url,
|
|
17262
|
+
type: input.linkType ?? "url",
|
|
17263
|
+
...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
|
|
17264
|
+
});
|
|
17265
|
+
return board ? okBoard(board, "Link added.") : fail("Task not found.");
|
|
17266
|
+
}
|
|
17267
|
+
default:
|
|
17268
|
+
return fail(`Unknown kanban action: ${input.action}`);
|
|
16425
17269
|
}
|
|
16426
|
-
|
|
16427
|
-
|
|
16428
|
-
}
|
|
17270
|
+
})();
|
|
17271
|
+
return withPresence(result);
|
|
16429
17272
|
} catch (err) {
|
|
16430
17273
|
return fail(err instanceof Error ? err.message : String(err));
|
|
16431
17274
|
}
|
|
@@ -16449,6 +17292,7 @@ function taskInput(input) {
|
|
|
16449
17292
|
title: input.title ?? "",
|
|
16450
17293
|
columnId: input.columnId,
|
|
16451
17294
|
description: input.description,
|
|
17295
|
+
dueDate: input.dueDate,
|
|
16452
17296
|
priority: input.priority,
|
|
16453
17297
|
...input.taskType !== void 0 ? { type: input.taskType } : {},
|
|
16454
17298
|
status: input.status,
|
|
@@ -16457,7 +17301,67 @@ function taskInput(input) {
|
|
|
16457
17301
|
...input.assignee ?? assignment?.name ?? assignment?.agentId ? { assignee: input.assignee ?? assignment?.name ?? assignment?.agentId } : {},
|
|
16458
17302
|
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
16459
17303
|
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
|
|
16460
|
-
...
|
|
17304
|
+
...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {},
|
|
17305
|
+
...assignment ? { assignment } : {},
|
|
17306
|
+
...input.order !== void 0 ? { order: input.order } : {},
|
|
17307
|
+
// Sprint 3 durable task-level fields
|
|
17308
|
+
...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
|
|
17309
|
+
...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
|
|
17310
|
+
// Relationships — chainId alone cannot construct a KanbanTaskChainRef,
|
|
17311
|
+
// so chain/previousTaskId/nextTaskId must be set via update_task.
|
|
17312
|
+
...input.childTitles !== void 0 ? { childTaskIds: input.childTitles } : {},
|
|
17313
|
+
// Detail objects
|
|
17314
|
+
...input.checkDescription !== void 0 ? {
|
|
17315
|
+
successCriteria: [
|
|
17316
|
+
{
|
|
17317
|
+
id: randomUUID(),
|
|
17318
|
+
description: input.checkDescription,
|
|
17319
|
+
type: "manual",
|
|
17320
|
+
status: input.checkStatus ?? "pending"
|
|
17321
|
+
}
|
|
17322
|
+
]
|
|
17323
|
+
} : {},
|
|
17324
|
+
...input.metricName !== void 0 ? {
|
|
17325
|
+
goalMetrics: [
|
|
17326
|
+
{
|
|
17327
|
+
id: randomUUID(),
|
|
17328
|
+
name: input.metricName,
|
|
17329
|
+
status: input.metricStatus ?? "pending",
|
|
17330
|
+
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
17331
|
+
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
17332
|
+
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
17333
|
+
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17334
|
+
}
|
|
17335
|
+
]
|
|
17336
|
+
} : {},
|
|
17337
|
+
...input.url !== void 0 ? {
|
|
17338
|
+
links: [
|
|
17339
|
+
{
|
|
17340
|
+
url: input.url,
|
|
17341
|
+
type: input.linkType ?? "url",
|
|
17342
|
+
...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
|
|
17343
|
+
}
|
|
17344
|
+
]
|
|
17345
|
+
} : {},
|
|
17346
|
+
...input.note !== void 0 ? {
|
|
17347
|
+
notes: [
|
|
17348
|
+
{
|
|
17349
|
+
id: randomUUID(),
|
|
17350
|
+
author: input.author ?? "agent",
|
|
17351
|
+
content: input.note,
|
|
17352
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17353
|
+
}
|
|
17354
|
+
]
|
|
17355
|
+
} : {},
|
|
17356
|
+
// Origin tracking
|
|
17357
|
+
...input.graphId !== void 0 ? {
|
|
17358
|
+
origin: {
|
|
17359
|
+
system: input.sourceSystem ?? "kanban-tool",
|
|
17360
|
+
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
17361
|
+
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
17362
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {}
|
|
17363
|
+
}
|
|
17364
|
+
} : {}
|
|
16461
17365
|
};
|
|
16462
17366
|
}
|
|
16463
17367
|
function mergedDependsOn(input) {
|
|
@@ -16471,6 +17375,7 @@ function taskPatch(input) {
|
|
|
16471
17375
|
return {
|
|
16472
17376
|
title: input.title,
|
|
16473
17377
|
description: input.description,
|
|
17378
|
+
dueDate: input.dueDate,
|
|
16474
17379
|
columnId: input.columnId,
|
|
16475
17380
|
order: input.order,
|
|
16476
17381
|
priority: input.priority,
|
|
@@ -17232,7 +18137,7 @@ import {
|
|
|
17232
18137
|
mutateTasks as mutateTasks2,
|
|
17233
18138
|
formatTaskList
|
|
17234
18139
|
} from "@wrongstack/core";
|
|
17235
|
-
import { randomUUID } from "node:crypto";
|
|
18140
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
17236
18141
|
var planTool = {
|
|
17237
18142
|
name: "plan",
|
|
17238
18143
|
category: "Session",
|
|
@@ -17458,7 +18363,7 @@ var planTool = {
|
|
|
17458
18363
|
try {
|
|
17459
18364
|
const taskFile = await mutateTasks2(taskPath, sessionId, (f) => {
|
|
17460
18365
|
f.tasks.push({
|
|
17461
|
-
id: `task_${
|
|
18366
|
+
id: `task_${randomUUID2()}`,
|
|
17462
18367
|
title: taskifyMeta.title,
|
|
17463
18368
|
description: taskifyMeta.details || void 0,
|
|
17464
18369
|
type: "feature",
|
|
@@ -18600,7 +19505,7 @@ import {
|
|
|
18600
19505
|
mutatePlan as mutatePlan3,
|
|
18601
19506
|
formatPlan as formatPlan2
|
|
18602
19507
|
} from "@wrongstack/core";
|
|
18603
|
-
import { randomUUID as
|
|
19508
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
18604
19509
|
function findTaskIndex(tasks, query) {
|
|
18605
19510
|
const asNum = Number.parseInt(query, 10);
|
|
18606
19511
|
if (!Number.isNaN(asNum)) {
|
|
@@ -18788,7 +19693,7 @@ var taskTool = {
|
|
|
18788
19693
|
}
|
|
18789
19694
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18790
19695
|
const newTask = {
|
|
18791
|
-
id: `task_${Date.now()}_${
|
|
19696
|
+
id: `task_${Date.now()}_${randomUUID3().slice(0, 8)}`,
|
|
18792
19697
|
title: t.title,
|
|
18793
19698
|
description: t.description,
|
|
18794
19699
|
type: t.type || "feature",
|
|
@@ -18849,7 +19754,7 @@ var taskTool = {
|
|
|
18849
19754
|
});
|
|
18850
19755
|
if (match.description) {
|
|
18851
19756
|
todos.push({
|
|
18852
|
-
id: `todo_${ts2}_${
|
|
19757
|
+
id: `todo_${ts2}_${randomUUID3().slice(0, 6)}`,
|
|
18853
19758
|
content: match.description.slice(0, 200),
|
|
18854
19759
|
status: "pending",
|
|
18855
19760
|
promotedFromTask: match.id
|
|
@@ -18858,7 +19763,7 @@ var taskTool = {
|
|
|
18858
19763
|
if (input.subtasks && input.subtasks.length > 0) {
|
|
18859
19764
|
for (const st of input.subtasks) {
|
|
18860
19765
|
todos.push({
|
|
18861
|
-
id: `todo_${ts2}_${
|
|
19766
|
+
id: `todo_${ts2}_${randomUUID3().slice(0, 6)}`,
|
|
18862
19767
|
content: st,
|
|
18863
19768
|
status: "pending",
|
|
18864
19769
|
promotedFromTask: match.id
|
|
@@ -19564,8 +20469,8 @@ var DEFAULT_IGNORE5 = [
|
|
|
19564
20469
|
var treeTool = {
|
|
19565
20470
|
name: "tree",
|
|
19566
20471
|
category: "Filesystem",
|
|
19567
|
-
description: "Display a
|
|
19568
|
-
usageHint: "
|
|
20472
|
+
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.",
|
|
20473
|
+
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.",
|
|
19569
20474
|
permission: "auto",
|
|
19570
20475
|
mutating: false,
|
|
19571
20476
|
capabilities: ["fs.read"],
|
|
@@ -19968,15 +20873,15 @@ var OPTIONAL_TOOLS = [
|
|
|
19968
20873
|
toolUseTool,
|
|
19969
20874
|
batchToolUseTool,
|
|
19970
20875
|
toolHelpTool,
|
|
19971
|
-
codebaseIndexTool,
|
|
19972
|
-
codebaseSearchTool,
|
|
19973
|
-
codebaseStatsTool,
|
|
19974
20876
|
setWorkingDirTool
|
|
19975
20877
|
];
|
|
19976
20878
|
var TIER1_TOOLS = [
|
|
19977
20879
|
readTool,
|
|
19978
20880
|
writeTool,
|
|
19979
20881
|
editTool,
|
|
20882
|
+
codebaseStatsTool,
|
|
20883
|
+
codebaseSearchTool,
|
|
20884
|
+
codebaseIndexTool,
|
|
19980
20885
|
bashTool,
|
|
19981
20886
|
grepTool,
|
|
19982
20887
|
globTool,
|
|
@@ -20015,9 +20920,6 @@ var TIER3_TOOLS = [
|
|
|
20015
20920
|
toolUseTool,
|
|
20016
20921
|
batchToolUseTool,
|
|
20017
20922
|
toolHelpTool,
|
|
20018
|
-
codebaseIndexTool,
|
|
20019
|
-
codebaseSearchTool,
|
|
20020
|
-
codebaseStatsTool,
|
|
20021
20923
|
setWorkingDirTool
|
|
20022
20924
|
];
|
|
20023
20925
|
var builtinTools = [
|
|
@@ -20026,6 +20928,9 @@ var builtinTools = [
|
|
|
20026
20928
|
readTool,
|
|
20027
20929
|
writeTool,
|
|
20028
20930
|
editTool,
|
|
20931
|
+
codebaseStatsTool,
|
|
20932
|
+
codebaseSearchTool,
|
|
20933
|
+
codebaseIndexTool,
|
|
20029
20934
|
replaceTool,
|
|
20030
20935
|
globTool,
|
|
20031
20936
|
grepTool,
|
|
@@ -20060,9 +20965,6 @@ var builtinTools = [
|
|
|
20060
20965
|
toolUseTool,
|
|
20061
20966
|
batchToolUseTool,
|
|
20062
20967
|
toolHelpTool,
|
|
20063
|
-
codebaseIndexTool,
|
|
20064
|
-
codebaseSearchTool,
|
|
20065
|
-
codebaseStatsTool,
|
|
20066
20968
|
setWorkingDirTool
|
|
20067
20969
|
];
|
|
20068
20970
|
export {
|