@wrongstack/tools 0.287.0 → 0.289.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_fetch-guard.d.ts.map +1 -1
- package/dist/_redact-command.d.ts.map +1 -1
- package/dist/_spawn-stream.d.ts.map +1 -1
- package/dist/audit.js +61 -14
- package/dist/audit.js.map +3 -3
- package/dist/bash.d.ts +1 -1
- package/dist/bash.d.ts.map +1 -1
- package/dist/bash.js +97 -19
- package/dist/bash.js.map +3 -3
- package/dist/builtin.d.ts +12 -10
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +1620 -803
- package/dist/builtin.js.map +3 -3
- package/dist/codebase-index/background-indexer.d.ts.map +1 -1
- package/dist/codebase-index/bm25.d.ts +1 -2
- package/dist/codebase-index/bm25.d.ts.map +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts +7 -0
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
- package/dist/codebase-index/index-service.d.ts +11 -1
- package/dist/codebase-index/index-service.d.ts.map +1 -1
- package/dist/codebase-index/index.d.ts +2 -1
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +749 -192
- package/dist/codebase-index/index.js.map +3 -3
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/schema.d.ts +43 -1
- package/dist/codebase-index/schema.d.ts.map +1 -1
- package/dist/codebase-index/ts-parser.d.ts.map +1 -1
- package/dist/codebase-index/worker-protocol.d.ts +1 -0
- package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
- package/dist/codebase-index/worker.js +610 -152
- package/dist/codebase-index/worker.js.map +3 -3
- package/dist/codebase-index/writer.d.ts +27 -1
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/e2e.js +477 -0
- package/dist/e2e.js.map +7 -0
- package/dist/exec.d.ts +1 -1
- package/dist/exec.d.ts.map +1 -1
- package/dist/exec.js +60 -5
- package/dist/exec.js.map +3 -3
- package/dist/fetch.js +2 -2
- package/dist/fetch.js.map +2 -2
- package/dist/format.js +61 -14
- package/dist/format.js.map +3 -3
- package/dist/glob.js +2 -2
- package/dist/glob.js.map +1 -1
- package/dist/grep.d.ts.map +1 -1
- package/dist/grep.js +2 -2
- package/dist/grep.js.map +2 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1786 -809
- package/dist/index.js.map +3 -3
- package/dist/install.js +61 -14
- package/dist/install.js.map +3 -3
- package/dist/kanban.d.ts +11 -2
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +695 -571
- package/dist/kanban.js.map +3 -3
- package/dist/languages/detect.d.ts.map +1 -1
- package/dist/languages/index.js +61 -14
- package/dist/languages/index.js.map +3 -3
- package/dist/languages/types.d.ts +7 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/lint.js +61 -14
- package/dist/lint.js.map +3 -3
- package/dist/outdated.js +61 -14
- package/dist/outdated.js.map +3 -3
- package/dist/pack.js +1617 -800
- package/dist/pack.js.map +3 -3
- package/dist/plan.js +4 -0
- package/dist/plan.js.map +2 -2
- package/dist/process-registry.js +12 -5
- package/dist/process-registry.js.map +2 -2
- package/dist/search.js +2 -2
- package/dist/search.js.map +2 -2
- package/dist/session-kanban.d.ts +9 -0
- package/dist/session-kanban.d.ts.map +1 -1
- package/dist/session-kanban.js +137 -0
- package/dist/session-kanban.js.map +2 -2
- package/dist/task.js +4 -0
- package/dist/task.js.map +2 -2
- package/dist/test.js +61 -14
- package/dist/test.js.map +3 -3
- package/dist/todo.js +4 -0
- package/dist/todo.js.map +2 -2
- package/dist/tree.js +2 -2
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js +61 -14
- package/dist/typecheck.js.map +3 -3
- package/package.json +7 -3
package/dist/pack.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
|
}
|
|
@@ -7678,7 +7820,7 @@ function sleepSync(ms) {
|
|
|
7678
7820
|
} catch {
|
|
7679
7821
|
}
|
|
7680
7822
|
}
|
|
7681
|
-
var IndexStore = class {
|
|
7823
|
+
var IndexStore = class _IndexStore {
|
|
7682
7824
|
db;
|
|
7683
7825
|
/** Absolute path to this project's index directory. */
|
|
7684
7826
|
indexDir;
|
|
@@ -7730,10 +7872,7 @@ var IndexStore = class {
|
|
|
7730
7872
|
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
7731
7873
|
throw new LockError(`SQLite lock conflict after ${MAX_LOCK_RETRIES} retries: ${msg}`);
|
|
7732
7874
|
}
|
|
7733
|
-
const delay = Math.min(
|
|
7734
|
-
LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt,
|
|
7735
|
-
LOCK_RETRY_MAX_DELAY_MS
|
|
7736
|
-
);
|
|
7875
|
+
const delay = Math.min(LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt, LOCK_RETRY_MAX_DELAY_MS);
|
|
7737
7876
|
sleepSync(delay);
|
|
7738
7877
|
}
|
|
7739
7878
|
}
|
|
@@ -7815,8 +7954,24 @@ var IndexStore = class {
|
|
|
7815
7954
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)");
|
|
7816
7955
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)");
|
|
7817
7956
|
try {
|
|
7818
|
-
this.db.exec(
|
|
7957
|
+
this.db.exec(
|
|
7958
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')"
|
|
7959
|
+
);
|
|
7819
7960
|
this.ftsAvailable = true;
|
|
7961
|
+
const symbolCount = Number(
|
|
7962
|
+
this.db.prepare("SELECT COUNT(*) AS n FROM symbols").get()?.n ?? 0
|
|
7963
|
+
);
|
|
7964
|
+
const ftsCount = Number(
|
|
7965
|
+
this.db.prepare("SELECT COUNT(*) AS n FROM symbols_fts").get()?.n ?? 0
|
|
7966
|
+
);
|
|
7967
|
+
if (symbolCount !== ftsCount) {
|
|
7968
|
+
this.db.exec("DELETE FROM symbols_fts");
|
|
7969
|
+
const insert = this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)");
|
|
7970
|
+
const rows = this.db.prepare("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
|
|
7971
|
+
for (const row of rows) {
|
|
7972
|
+
insert.run(row.id, buildIndexableText(row.name, row.signature, row.doc_comment));
|
|
7973
|
+
}
|
|
7974
|
+
}
|
|
7820
7975
|
} catch {
|
|
7821
7976
|
this.ftsAvailable = false;
|
|
7822
7977
|
}
|
|
@@ -7874,7 +8029,9 @@ var IndexStore = class {
|
|
|
7874
8029
|
deleteSymbolsForFile(file) {
|
|
7875
8030
|
this.runWithRetry(() => {
|
|
7876
8031
|
if (this.ftsAvailable) {
|
|
7877
|
-
this.stmt(
|
|
8032
|
+
this.stmt(
|
|
8033
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8034
|
+
).run(file);
|
|
7878
8035
|
}
|
|
7879
8036
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
7880
8037
|
});
|
|
@@ -7886,9 +8043,21 @@ var IndexStore = class {
|
|
|
7886
8043
|
*/
|
|
7887
8044
|
deleteFile(file) {
|
|
7888
8045
|
this.runWithRetry(() => {
|
|
7889
|
-
this.
|
|
7890
|
-
|
|
7891
|
-
|
|
8046
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
8047
|
+
try {
|
|
8048
|
+
if (this.ftsAvailable) {
|
|
8049
|
+
this.db.prepare(
|
|
8050
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8051
|
+
).run(file);
|
|
8052
|
+
}
|
|
8053
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)").run(file);
|
|
8054
|
+
this.db.prepare("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
8055
|
+
this.db.prepare("DELETE FROM files WHERE file = ?").run(file);
|
|
8056
|
+
this.db.exec("COMMIT");
|
|
8057
|
+
} catch (err) {
|
|
8058
|
+
this.db.exec("ROLLBACK");
|
|
8059
|
+
throw err;
|
|
8060
|
+
}
|
|
7892
8061
|
});
|
|
7893
8062
|
}
|
|
7894
8063
|
// ─── File metadata ──────────────────────────────────────────────────────────
|
|
@@ -7911,14 +8080,22 @@ var IndexStore = class {
|
|
|
7911
8080
|
).all(file);
|
|
7912
8081
|
if (!rows.length) return null;
|
|
7913
8082
|
const r = expectDefined2(rows[0]);
|
|
7914
|
-
return {
|
|
8083
|
+
return {
|
|
8084
|
+
file: r.file,
|
|
8085
|
+
lang: r.lang,
|
|
8086
|
+
mtimeMs: r.mtime_ms,
|
|
8087
|
+
symbolCount: r.symbol_count,
|
|
8088
|
+
lastIndexed: r.last_indexed
|
|
8089
|
+
};
|
|
7915
8090
|
}
|
|
7916
8091
|
getAllFileMetas() {
|
|
7917
|
-
return this.db.prepare(
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
8092
|
+
return this.db.prepare("SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files").all().map((r) => ({
|
|
8093
|
+
file: r.file,
|
|
8094
|
+
lang: r.lang,
|
|
8095
|
+
mtimeMs: r.mtime_ms,
|
|
8096
|
+
symbolCount: r.symbol_count,
|
|
8097
|
+
lastIndexed: r.last_indexed
|
|
8098
|
+
}));
|
|
7922
8099
|
}
|
|
7923
8100
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
7924
8101
|
search(query, filter) {
|
|
@@ -7942,8 +8119,8 @@ var IndexStore = class {
|
|
|
7942
8119
|
values.push(filter.lang);
|
|
7943
8120
|
}
|
|
7944
8121
|
if (filter?.file) {
|
|
7945
|
-
conditions.push("file LIKE ?");
|
|
7946
|
-
values.push(`%${filter.file}%`);
|
|
8122
|
+
conditions.push("replace(file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
8123
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
7947
8124
|
}
|
|
7948
8125
|
if (query.trim()) {
|
|
7949
8126
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
@@ -7981,9 +8158,11 @@ var IndexStore = class {
|
|
|
7981
8158
|
* "users", camelCase-split text makes "complex" find "complexOperation").
|
|
7982
8159
|
*/
|
|
7983
8160
|
searchRanked(query, filter, limit) {
|
|
8161
|
+
const rawLimit = Number.isFinite(limit) ? Math.trunc(limit) : 20;
|
|
8162
|
+
const safeLimit = Math.max(1, Math.min(rawLimit, 100));
|
|
7984
8163
|
const tokens = tokenise(query);
|
|
7985
8164
|
if (tokens.length === 0 || !this.ftsAvailable) {
|
|
7986
|
-
return this.searchRankedFallback(query, filter,
|
|
8165
|
+
return this.searchRankedFallback(query, filter, safeLimit);
|
|
7987
8166
|
}
|
|
7988
8167
|
let effectiveKind = filter?.kind;
|
|
7989
8168
|
if (filter?.lspKind !== void 0) {
|
|
@@ -8003,11 +8182,13 @@ var IndexStore = class {
|
|
|
8003
8182
|
values.push(filter.lang);
|
|
8004
8183
|
}
|
|
8005
8184
|
if (filter?.file) {
|
|
8006
|
-
conditions.push("s.file LIKE ?");
|
|
8007
|
-
values.push(`%${filter.file}%`);
|
|
8185
|
+
conditions.push("replace(s.file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
8186
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
8008
8187
|
}
|
|
8009
8188
|
const where = conditions.join(" AND ");
|
|
8010
|
-
const countRows = this.db.prepare(
|
|
8189
|
+
const countRows = this.db.prepare(
|
|
8190
|
+
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
8191
|
+
).all(...values);
|
|
8011
8192
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
8012
8193
|
if (total === 0) return { results: [], total: 0 };
|
|
8013
8194
|
const rows = this.db.prepare(
|
|
@@ -8016,9 +8197,13 @@ var IndexStore = class {
|
|
|
8016
8197
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
8017
8198
|
FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
8018
8199
|
WHERE ${where}
|
|
8019
|
-
ORDER BY
|
|
8200
|
+
ORDER BY
|
|
8201
|
+
CASE WHEN lower(s.name) = lower(?) THEN 0
|
|
8202
|
+
WHEN lower(s.name) LIKE lower(?) ESCAPE '\\' THEN 1
|
|
8203
|
+
ELSE 2 END,
|
|
8204
|
+
bm25(symbols_fts), lower(s.name), s.file, s.line, s.col, s.id
|
|
8020
8205
|
LIMIT ?`
|
|
8021
|
-
).all(...values,
|
|
8206
|
+
).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
8022
8207
|
return {
|
|
8023
8208
|
results: rows.map((r) => ({
|
|
8024
8209
|
id: r.id,
|
|
@@ -8048,10 +8233,28 @@ var IndexStore = class {
|
|
|
8048
8233
|
}
|
|
8049
8234
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
8050
8235
|
const bm25 = buildBm25Index(
|
|
8051
|
-
candidates.map((c) => ({
|
|
8236
|
+
candidates.map((c) => ({
|
|
8237
|
+
id: c.id,
|
|
8238
|
+
text: buildIndexableText(c.name, c.signature, c.docComment)
|
|
8239
|
+
}))
|
|
8052
8240
|
);
|
|
8053
8241
|
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
8054
|
-
|
|
8242
|
+
const q = query.trim().toLowerCase();
|
|
8243
|
+
const rank = (id) => {
|
|
8244
|
+
const name = candidateById.get(id)?.name.toLowerCase() ?? "";
|
|
8245
|
+
if (name === q) return 0;
|
|
8246
|
+
if (name.startsWith(q)) return 1;
|
|
8247
|
+
return 2;
|
|
8248
|
+
};
|
|
8249
|
+
scored.sort((a, b) => {
|
|
8250
|
+
const rankDiff = rank(a.id) - rank(b.id);
|
|
8251
|
+
if (rankDiff !== 0) return rankDiff;
|
|
8252
|
+
const scoreDiff = b.score - a.score;
|
|
8253
|
+
if (scoreDiff !== 0) return scoreDiff;
|
|
8254
|
+
const left = expectDefined2(candidateById.get(a.id));
|
|
8255
|
+
const right = expectDefined2(candidateById.get(b.id));
|
|
8256
|
+
return left.name.localeCompare(right.name) || left.file.localeCompare(right.file) || left.line - right.line || left.col - right.col || left.id - right.id;
|
|
8257
|
+
});
|
|
8055
8258
|
const qTokens = tokenise(query);
|
|
8056
8259
|
const results = scored.slice(0, limit).map(({ id, score }) => {
|
|
8057
8260
|
const c = expectDefined2(candidateById.get(id));
|
|
@@ -8060,9 +8263,7 @@ var IndexStore = class {
|
|
|
8060
8263
|
return { results, total: candidates.length };
|
|
8061
8264
|
}
|
|
8062
8265
|
getAllIndexable() {
|
|
8063
|
-
return this.db.prepare("SELECT id, text FROM symbols").all().map(
|
|
8064
|
-
({ id, text }) => ({ id, text })
|
|
8065
|
-
);
|
|
8266
|
+
return this.db.prepare("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
|
|
8066
8267
|
}
|
|
8067
8268
|
/**
|
|
8068
8269
|
* Largest symbol id currently in the table (0 when empty). New ids must be
|
|
@@ -8078,22 +8279,16 @@ var IndexStore = class {
|
|
|
8078
8279
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
8079
8280
|
getStats() {
|
|
8080
8281
|
const sizeBytes = this.sizeBytes();
|
|
8081
|
-
const lastRows = this.db.prepare(
|
|
8082
|
-
"SELECT value FROM metadata WHERE key = 'last_indexed'"
|
|
8083
|
-
).all();
|
|
8282
|
+
const lastRows = this.db.prepare("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
8084
8283
|
const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
|
|
8085
8284
|
const totalRows = this.db.prepare("SELECT COUNT(*) FROM symbols").all();
|
|
8086
8285
|
const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
|
|
8087
8286
|
const fileRows = this.db.prepare("SELECT COUNT(*) FROM files").all();
|
|
8088
8287
|
const totalFiles = fileRows[0] ? Number(fileRows[0]["COUNT(*)"]) : 0;
|
|
8089
|
-
const langRows = this.db.prepare(
|
|
8090
|
-
"SELECT lang, COUNT(*) FROM symbols GROUP BY lang"
|
|
8091
|
-
).all();
|
|
8288
|
+
const langRows = this.db.prepare("SELECT lang, COUNT(*) FROM symbols GROUP BY lang").all();
|
|
8092
8289
|
const byLang = {};
|
|
8093
8290
|
for (const row of langRows) byLang[row.lang] = Number(row["COUNT(*)"]);
|
|
8094
|
-
const kindRows = this.db.prepare(
|
|
8095
|
-
"SELECT kind, COUNT(*) FROM symbols GROUP BY kind"
|
|
8096
|
-
).all();
|
|
8291
|
+
const kindRows = this.db.prepare("SELECT kind, COUNT(*) FROM symbols GROUP BY kind").all();
|
|
8097
8292
|
const byKind = {};
|
|
8098
8293
|
for (const row of kindRows) byKind[row.kind] = Number(row["COUNT(*)"]);
|
|
8099
8294
|
return {
|
|
@@ -8109,17 +8304,31 @@ var IndexStore = class {
|
|
|
8109
8304
|
}
|
|
8110
8305
|
setLastIndexed(ts2) {
|
|
8111
8306
|
this.runWithRetry(() => {
|
|
8112
|
-
this.db.prepare(
|
|
8113
|
-
|
|
8114
|
-
|
|
8307
|
+
this.db.prepare("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
|
|
8308
|
+
});
|
|
8309
|
+
}
|
|
8310
|
+
getMetadata(key) {
|
|
8311
|
+
const rows = this.db.prepare("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
8312
|
+
return rows[0]?.value;
|
|
8313
|
+
}
|
|
8314
|
+
setMetadata(key, value) {
|
|
8315
|
+
this.runWithRetry(() => {
|
|
8316
|
+
this.db.prepare("INSERT OR REPLACE INTO metadata(key, value) VALUES(?, ?)").run(key, value);
|
|
8115
8317
|
});
|
|
8116
8318
|
}
|
|
8117
8319
|
clearAll() {
|
|
8118
8320
|
this.runWithRetry(() => {
|
|
8119
|
-
this.db.exec("
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8321
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
8322
|
+
try {
|
|
8323
|
+
this.db.exec("DELETE FROM refs");
|
|
8324
|
+
this.db.exec("DELETE FROM symbols");
|
|
8325
|
+
this.db.exec("DELETE FROM files");
|
|
8326
|
+
if (this.ftsAvailable) this.db.exec("DELETE FROM symbols_fts");
|
|
8327
|
+
this.db.exec("COMMIT");
|
|
8328
|
+
} catch (err) {
|
|
8329
|
+
this.db.exec("ROLLBACK");
|
|
8330
|
+
throw err;
|
|
8331
|
+
}
|
|
8123
8332
|
});
|
|
8124
8333
|
}
|
|
8125
8334
|
// ─── Ref CRUD ────────────────────────────────────────────────────────────────
|
|
@@ -8210,6 +8419,7 @@ var IndexStore = class {
|
|
|
8210
8419
|
const allInserted = [];
|
|
8211
8420
|
const refsToInsert = [];
|
|
8212
8421
|
for (const entry of entries) {
|
|
8422
|
+
const insertedForEntry = [];
|
|
8213
8423
|
for (const s of entry.symbols) {
|
|
8214
8424
|
const id = nextId++;
|
|
8215
8425
|
symStmt.run(
|
|
@@ -8227,9 +8437,11 @@ var IndexStore = class {
|
|
|
8227
8437
|
s.file
|
|
8228
8438
|
);
|
|
8229
8439
|
ftsStmt?.run(id, buildIndexableText(s.name, s.signature, s.docComment));
|
|
8230
|
-
|
|
8440
|
+
const inserted = { ...s, id };
|
|
8441
|
+
allInserted.push(inserted);
|
|
8442
|
+
insertedForEntry.push(inserted);
|
|
8231
8443
|
}
|
|
8232
|
-
|
|
8444
|
+
refsToInsert.push(...assignRefsToSymbols(entry.refs, insertedForEntry));
|
|
8233
8445
|
}
|
|
8234
8446
|
if (refsToInsert.length > 0) {
|
|
8235
8447
|
const refStmt = this.db.prepare(
|
|
@@ -8267,9 +8479,7 @@ var IndexStore = class {
|
|
|
8267
8479
|
*/
|
|
8268
8480
|
deleteRefsForFile(file) {
|
|
8269
8481
|
this.runWithRetry(() => {
|
|
8270
|
-
this.db.prepare(
|
|
8271
|
-
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
8272
|
-
).run(file);
|
|
8482
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
|
|
8273
8483
|
});
|
|
8274
8484
|
}
|
|
8275
8485
|
/**
|
|
@@ -8310,9 +8520,7 @@ var IndexStore = class {
|
|
|
8310
8520
|
* Find all references FROM a given symbol (what does this symbol call/use?).
|
|
8311
8521
|
*/
|
|
8312
8522
|
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) => ({
|
|
8523
|
+
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
8524
|
id: r.id,
|
|
8317
8525
|
fromId: r.from_id,
|
|
8318
8526
|
toName: r.to_name,
|
|
@@ -8329,6 +8537,325 @@ var IndexStore = class {
|
|
|
8329
8537
|
return 0;
|
|
8330
8538
|
}
|
|
8331
8539
|
}
|
|
8540
|
+
// ─── CodeMap graph aggregation ──────────────────────────────────────────────
|
|
8541
|
+
/**
|
|
8542
|
+
* Derive a monorepo package name from an absolute file path.
|
|
8543
|
+
* Handles both `packages/<name>/...` (workspace) and `src/<name>/...` layouts.
|
|
8544
|
+
* Returns `undefined` for files outside any recognisable package structure.
|
|
8545
|
+
*/
|
|
8546
|
+
static derivePackage(filePath) {
|
|
8547
|
+
const f = filePath.replace(/\\/g, "/");
|
|
8548
|
+
const pkgsIdx = f.indexOf("/packages/");
|
|
8549
|
+
if (pkgsIdx !== -1) {
|
|
8550
|
+
const rest = f.slice(pkgsIdx + "/packages/".length);
|
|
8551
|
+
const seg = rest.split("/")[0];
|
|
8552
|
+
return seg ? `@wrongstack/${seg}` : void 0;
|
|
8553
|
+
}
|
|
8554
|
+
const appsIdx = f.indexOf("/apps/");
|
|
8555
|
+
if (appsIdx !== -1) {
|
|
8556
|
+
const rest = f.slice(appsIdx + "/apps/".length);
|
|
8557
|
+
const seg = rest.split("/")[0];
|
|
8558
|
+
return seg ? `app:${seg}` : void 0;
|
|
8559
|
+
}
|
|
8560
|
+
return void 0;
|
|
8561
|
+
}
|
|
8562
|
+
static packageFromImport(moduleName) {
|
|
8563
|
+
if (!moduleName.startsWith("@wrongstack/")) return void 0;
|
|
8564
|
+
const parts = moduleName.split("/");
|
|
8565
|
+
return parts[1] ? `@wrongstack/${parts[1]}` : void 0;
|
|
8566
|
+
}
|
|
8567
|
+
static resolveRelativeImport(fromFile, moduleName, indexedFiles) {
|
|
8568
|
+
if (!moduleName.startsWith(".")) return void 0;
|
|
8569
|
+
const normalizedFrom = fromFile.replace(/\\/g, "/");
|
|
8570
|
+
const absolute = path13.posix.normalize(
|
|
8571
|
+
path13.posix.join(path13.posix.dirname(normalizedFrom), moduleName)
|
|
8572
|
+
);
|
|
8573
|
+
const extension = path13.posix.extname(absolute);
|
|
8574
|
+
const base = extension ? absolute.slice(0, -extension.length) : absolute;
|
|
8575
|
+
const candidates = [
|
|
8576
|
+
absolute,
|
|
8577
|
+
...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
|
|
8578
|
+
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path13.posix.join(absolute, `index${ext}`)),
|
|
8579
|
+
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path13.posix.join(base, `index${ext}`))
|
|
8580
|
+
];
|
|
8581
|
+
const indexedByPortablePath = new Map(
|
|
8582
|
+
[...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
|
|
8583
|
+
);
|
|
8584
|
+
for (const candidate of candidates) {
|
|
8585
|
+
const indexed = indexedByPortablePath.get(candidate.toLocaleLowerCase());
|
|
8586
|
+
if (indexed) return indexed;
|
|
8587
|
+
}
|
|
8588
|
+
return void 0;
|
|
8589
|
+
}
|
|
8590
|
+
/**
|
|
8591
|
+
* Package-level graph: each workspace package is a node; edges are derived
|
|
8592
|
+
* from cross-package symbol references (a symbol in package A references a
|
|
8593
|
+
* symbol resolved in package B). Node metadata includes symbol/file counts.
|
|
8594
|
+
*/
|
|
8595
|
+
getPackageGraph() {
|
|
8596
|
+
const symbols = this.db.prepare("SELECT file, id, name, kind, lang, line FROM symbols ORDER BY id").all();
|
|
8597
|
+
const pkgNodes = /* @__PURE__ */ new Map();
|
|
8598
|
+
const fileToPkg = /* @__PURE__ */ new Map();
|
|
8599
|
+
const symbolToPkg = /* @__PURE__ */ new Map();
|
|
8600
|
+
for (const s of symbols) {
|
|
8601
|
+
const pkg = _IndexStore.derivePackage(s.file) ?? "(root)";
|
|
8602
|
+
fileToPkg.set(s.file, pkg);
|
|
8603
|
+
symbolToPkg.set(s.id, pkg);
|
|
8604
|
+
const node = pkgNodes.get(pkg);
|
|
8605
|
+
if (node) {
|
|
8606
|
+
node.symbolCount = (node.symbolCount ?? 0) + 1;
|
|
8607
|
+
} else {
|
|
8608
|
+
pkgNodes.set(pkg, {
|
|
8609
|
+
id: `pkg:${pkg}`,
|
|
8610
|
+
label: pkg,
|
|
8611
|
+
kind: "package",
|
|
8612
|
+
package: pkg,
|
|
8613
|
+
symbolCount: 1,
|
|
8614
|
+
fileCount: 0
|
|
8615
|
+
});
|
|
8616
|
+
}
|
|
8617
|
+
}
|
|
8618
|
+
const files = this.db.prepare("SELECT DISTINCT file FROM files").all();
|
|
8619
|
+
for (const { file } of files) {
|
|
8620
|
+
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
8621
|
+
const node = pkgNodes.get(pkg);
|
|
8622
|
+
if (node) node.fileCount = (node.fileCount ?? 0) + 1;
|
|
8623
|
+
}
|
|
8624
|
+
const refRows = this.db.prepare(
|
|
8625
|
+
`SELECT r.from_id, r.to_id, r.call_type
|
|
8626
|
+
FROM refs r
|
|
8627
|
+
WHERE r.to_id IS NOT NULL`
|
|
8628
|
+
).all();
|
|
8629
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
8630
|
+
for (const r of refRows) {
|
|
8631
|
+
if (r.call_type === "import") continue;
|
|
8632
|
+
const fromPkg = symbolToPkg.get(r.from_id);
|
|
8633
|
+
const toPkg = symbolToPkg.get(r.to_id);
|
|
8634
|
+
if (!fromPkg || !toPkg || fromPkg === toPkg) continue;
|
|
8635
|
+
const key = `${fromPkg}\0${toPkg}`;
|
|
8636
|
+
let e = edgeMap.get(key);
|
|
8637
|
+
if (!e) {
|
|
8638
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8639
|
+
edgeMap.set(key, e);
|
|
8640
|
+
}
|
|
8641
|
+
e.weight++;
|
|
8642
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
8643
|
+
}
|
|
8644
|
+
const importRows = this.db.prepare(
|
|
8645
|
+
`SELECT r.from_id, r.to_name
|
|
8646
|
+
FROM refs r
|
|
8647
|
+
WHERE r.call_type = 'import'`
|
|
8648
|
+
).all();
|
|
8649
|
+
for (const r of importRows) {
|
|
8650
|
+
const fromPkg = symbolToPkg.get(r.from_id);
|
|
8651
|
+
const toPkg = _IndexStore.packageFromImport(r.to_name);
|
|
8652
|
+
if (!fromPkg || !toPkg || fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
8653
|
+
const key = `${fromPkg}\0${toPkg}`;
|
|
8654
|
+
let edge = edgeMap.get(key);
|
|
8655
|
+
if (!edge) {
|
|
8656
|
+
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8657
|
+
edgeMap.set(key, edge);
|
|
8658
|
+
}
|
|
8659
|
+
edge.weight++;
|
|
8660
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
|
|
8661
|
+
}
|
|
8662
|
+
const edges = [];
|
|
8663
|
+
for (const [key, e] of edgeMap) {
|
|
8664
|
+
const [source, target] = key.split("\0");
|
|
8665
|
+
let bestType = "call";
|
|
8666
|
+
let bestCount = 0;
|
|
8667
|
+
for (const [t, c] of e.types) {
|
|
8668
|
+
if (c > bestCount) {
|
|
8669
|
+
bestType = t;
|
|
8670
|
+
bestCount = c;
|
|
8671
|
+
}
|
|
8672
|
+
}
|
|
8673
|
+
edges.push({
|
|
8674
|
+
source: `pkg:${source}`,
|
|
8675
|
+
target: `pkg:${target}`,
|
|
8676
|
+
weight: e.weight,
|
|
8677
|
+
refType: bestType
|
|
8678
|
+
});
|
|
8679
|
+
}
|
|
8680
|
+
return { nodes: [...pkgNodes.values()], edges };
|
|
8681
|
+
}
|
|
8682
|
+
/**
|
|
8683
|
+
* File-level graph for a single package: each file is a node; edges are
|
|
8684
|
+
* derived from cross-file symbol references within the package.
|
|
8685
|
+
*/
|
|
8686
|
+
getFileGraph(packageFilter) {
|
|
8687
|
+
const allSymbols = this.db.prepare("SELECT file, id, name, kind, lang, line FROM symbols ORDER BY id").all();
|
|
8688
|
+
const pkgSyms = allSymbols.filter(
|
|
8689
|
+
(s) => (_IndexStore.derivePackage(s.file) ?? "(root)") === packageFilter
|
|
8690
|
+
);
|
|
8691
|
+
if (pkgSyms.length === 0) return { nodes: [], edges: [] };
|
|
8692
|
+
const fileNodes = /* @__PURE__ */ new Map();
|
|
8693
|
+
const symToFile = /* @__PURE__ */ new Map();
|
|
8694
|
+
const fileStats = /* @__PURE__ */ new Map();
|
|
8695
|
+
for (const s of allSymbols) {
|
|
8696
|
+
symToFile.set(s.id, s.file);
|
|
8697
|
+
const current = fileStats.get(s.file);
|
|
8698
|
+
fileStats.set(s.file, {
|
|
8699
|
+
count: (current?.count ?? 0) + 1,
|
|
8700
|
+
lang: current?.lang ?? s.lang
|
|
8701
|
+
});
|
|
8702
|
+
}
|
|
8703
|
+
const localFiles = new Set(pkgSyms.map((s) => s.file));
|
|
8704
|
+
const indexedFiles = new Set(allSymbols.map((s) => s.file));
|
|
8705
|
+
const ensureFileNode = (file) => {
|
|
8706
|
+
if (fileNodes.has(file)) return;
|
|
8707
|
+
const stats = fileStats.get(file);
|
|
8708
|
+
fileNodes.set(file, {
|
|
8709
|
+
id: `file:${file}`,
|
|
8710
|
+
label: file.replace(/\\/g, "/").split("/").pop() ?? file,
|
|
8711
|
+
kind: "file",
|
|
8712
|
+
package: _IndexStore.derivePackage(file) ?? "(root)",
|
|
8713
|
+
file,
|
|
8714
|
+
symbolCount: stats?.count ?? 0,
|
|
8715
|
+
lang: stats?.lang,
|
|
8716
|
+
external: !localFiles.has(file)
|
|
8717
|
+
});
|
|
8718
|
+
};
|
|
8719
|
+
for (const file of localFiles) {
|
|
8720
|
+
ensureFileNode(file);
|
|
8721
|
+
}
|
|
8722
|
+
const refRows = this.db.prepare(
|
|
8723
|
+
`SELECT r.from_id, r.to_id, r.call_type
|
|
8724
|
+
FROM refs r
|
|
8725
|
+
WHERE r.to_id IS NOT NULL`
|
|
8726
|
+
).all();
|
|
8727
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
8728
|
+
for (const r of refRows) {
|
|
8729
|
+
if (r.call_type === "import") continue;
|
|
8730
|
+
const fromFile = symToFile.get(r.from_id);
|
|
8731
|
+
const toFile = symToFile.get(r.to_id);
|
|
8732
|
+
if (!fromFile || !toFile || fromFile === toFile) continue;
|
|
8733
|
+
if (!localFiles.has(fromFile) && !localFiles.has(toFile)) continue;
|
|
8734
|
+
ensureFileNode(fromFile);
|
|
8735
|
+
ensureFileNode(toFile);
|
|
8736
|
+
const key = `${fromFile}\0${toFile}`;
|
|
8737
|
+
let e = edgeMap.get(key);
|
|
8738
|
+
if (!e) {
|
|
8739
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8740
|
+
edgeMap.set(key, e);
|
|
8741
|
+
}
|
|
8742
|
+
e.weight++;
|
|
8743
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
8744
|
+
}
|
|
8745
|
+
const importRows = this.db.prepare(
|
|
8746
|
+
`SELECT r.from_id, r.to_name
|
|
8747
|
+
FROM refs r
|
|
8748
|
+
WHERE r.call_type = 'import'`
|
|
8749
|
+
).all();
|
|
8750
|
+
for (const r of importRows) {
|
|
8751
|
+
const fromFile = symToFile.get(r.from_id);
|
|
8752
|
+
if (!fromFile || !localFiles.has(fromFile)) continue;
|
|
8753
|
+
const toFile = _IndexStore.resolveRelativeImport(fromFile, r.to_name, indexedFiles);
|
|
8754
|
+
if (!toFile || fromFile === toFile) continue;
|
|
8755
|
+
ensureFileNode(fromFile);
|
|
8756
|
+
ensureFileNode(toFile);
|
|
8757
|
+
const key = `${fromFile}\0${toFile}`;
|
|
8758
|
+
let edge = edgeMap.get(key);
|
|
8759
|
+
if (!edge) {
|
|
8760
|
+
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8761
|
+
edgeMap.set(key, edge);
|
|
8762
|
+
}
|
|
8763
|
+
edge.weight++;
|
|
8764
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + 1);
|
|
8765
|
+
}
|
|
8766
|
+
const edges = [];
|
|
8767
|
+
for (const [key, e] of edgeMap) {
|
|
8768
|
+
const [source, target] = key.split("\0");
|
|
8769
|
+
let bestType = "call";
|
|
8770
|
+
let bestCount = 0;
|
|
8771
|
+
for (const [t, c] of e.types) {
|
|
8772
|
+
if (c > bestCount) {
|
|
8773
|
+
bestType = t;
|
|
8774
|
+
bestCount = c;
|
|
8775
|
+
}
|
|
8776
|
+
}
|
|
8777
|
+
edges.push({
|
|
8778
|
+
source: `file:${source}`,
|
|
8779
|
+
target: `file:${target}`,
|
|
8780
|
+
weight: e.weight,
|
|
8781
|
+
refType: bestType
|
|
8782
|
+
});
|
|
8783
|
+
}
|
|
8784
|
+
return { nodes: [...fileNodes.values()], edges };
|
|
8785
|
+
}
|
|
8786
|
+
/**
|
|
8787
|
+
* Symbol-level graph for a single file: each symbol is a node; edges are
|
|
8788
|
+
* derived from intra-file and cross-file symbol references (who calls whom).
|
|
8789
|
+
*/
|
|
8790
|
+
getSymbolGraph(fileFilter) {
|
|
8791
|
+
const allSymbols = this.db.prepare(
|
|
8792
|
+
"SELECT id, name, kind, lang, file, line, signature, scope FROM symbols ORDER BY file, line, id"
|
|
8793
|
+
).all();
|
|
8794
|
+
const syms = allSymbols.filter((symbol) => symbol.file === fileFilter);
|
|
8795
|
+
if (syms.length === 0) return { nodes: [], edges: [] };
|
|
8796
|
+
const symById = new Map(allSymbols.map((symbol) => [symbol.id, symbol]));
|
|
8797
|
+
const relatedIds = new Set(syms.map((symbol) => symbol.id));
|
|
8798
|
+
const toGraphNode = (s) => ({
|
|
8799
|
+
id: `sym:${s.id}`,
|
|
8800
|
+
label: s.name,
|
|
8801
|
+
kind: "symbol",
|
|
8802
|
+
symbolId: s.id,
|
|
8803
|
+
symbolKind: s.kind,
|
|
8804
|
+
file: s.file,
|
|
8805
|
+
package: _IndexStore.derivePackage(s.file) ?? "(root)",
|
|
8806
|
+
lang: s.lang,
|
|
8807
|
+
line: s.line,
|
|
8808
|
+
signature: s.signature,
|
|
8809
|
+
scope: s.scope,
|
|
8810
|
+
external: s.file !== fileFilter
|
|
8811
|
+
});
|
|
8812
|
+
const symIds = new Set(syms.map((s) => s.id));
|
|
8813
|
+
const refRows = this.db.prepare(
|
|
8814
|
+
`SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
8815
|
+
FROM refs r
|
|
8816
|
+
WHERE r.from_id IN (SELECT id FROM symbols WHERE file = ?)
|
|
8817
|
+
OR r.to_id IN (SELECT id FROM symbols WHERE file = ?)`
|
|
8818
|
+
).all(fileFilter, fileFilter);
|
|
8819
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
8820
|
+
for (const r of refRows) {
|
|
8821
|
+
if (r.to_id === null) continue;
|
|
8822
|
+
if (!symIds.has(r.from_id) && !symIds.has(r.to_id)) continue;
|
|
8823
|
+
relatedIds.add(r.from_id);
|
|
8824
|
+
relatedIds.add(r.to_id);
|
|
8825
|
+
const key = `${r.from_id}\0${r.to_id}`;
|
|
8826
|
+
let e = edgeMap.get(key);
|
|
8827
|
+
if (!e) {
|
|
8828
|
+
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8829
|
+
edgeMap.set(key, e);
|
|
8830
|
+
}
|
|
8831
|
+
e.weight++;
|
|
8832
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
8833
|
+
}
|
|
8834
|
+
const edges = [];
|
|
8835
|
+
for (const [key, e] of edgeMap) {
|
|
8836
|
+
const [fromId, toId] = key.split("\0").map(Number);
|
|
8837
|
+
let bestType = "call";
|
|
8838
|
+
let bestCount = 0;
|
|
8839
|
+
for (const [t, c] of e.types) {
|
|
8840
|
+
if (c > bestCount) {
|
|
8841
|
+
bestType = t;
|
|
8842
|
+
bestCount = c;
|
|
8843
|
+
}
|
|
8844
|
+
}
|
|
8845
|
+
edges.push({
|
|
8846
|
+
source: `sym:${fromId}`,
|
|
8847
|
+
target: `sym:${toId}`,
|
|
8848
|
+
weight: e.weight,
|
|
8849
|
+
refType: bestType
|
|
8850
|
+
});
|
|
8851
|
+
}
|
|
8852
|
+
const nodes = [...relatedIds].map((id) => symById.get(id)).filter((symbol) => symbol !== void 0).sort((a, b) => {
|
|
8853
|
+
const aExternal = a.file === fileFilter ? 0 : 1;
|
|
8854
|
+
const bExternal = b.file === fileFilter ? 0 : 1;
|
|
8855
|
+
return aExternal - bExternal || a.file.localeCompare(b.file) || a.line - b.line || a.id - b.id;
|
|
8856
|
+
}).map(toGraphNode);
|
|
8857
|
+
return { nodes, edges };
|
|
8858
|
+
}
|
|
8332
8859
|
close() {
|
|
8333
8860
|
this.stmtCache.clear();
|
|
8334
8861
|
try {
|
|
@@ -8423,6 +8950,14 @@ function getJsDoc(node, sourceFile) {
|
|
|
8423
8950
|
}
|
|
8424
8951
|
return "";
|
|
8425
8952
|
}
|
|
8953
|
+
function hasFunctionLikeAncestor(node) {
|
|
8954
|
+
let current = node.parent;
|
|
8955
|
+
while (current) {
|
|
8956
|
+
if (ts.isFunctionLike(current)) return true;
|
|
8957
|
+
current = current.parent;
|
|
8958
|
+
}
|
|
8959
|
+
return false;
|
|
8960
|
+
}
|
|
8426
8961
|
function buildScope(node) {
|
|
8427
8962
|
const parts = [];
|
|
8428
8963
|
let current = node.parent;
|
|
@@ -8450,6 +8985,10 @@ function parseSymbols(opts) {
|
|
|
8450
8985
|
function visit(node) {
|
|
8451
8986
|
const kind = kindOf(node);
|
|
8452
8987
|
if (kind) {
|
|
8988
|
+
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && hasFunctionLikeAncestor(node)) {
|
|
8989
|
+
ts.forEachChild(node, visit);
|
|
8990
|
+
return;
|
|
8991
|
+
}
|
|
8453
8992
|
const nameNode = node.name;
|
|
8454
8993
|
if (!nameNode || !ts.isIdentifier(nameNode)) return;
|
|
8455
8994
|
const name = nameNode.text;
|
|
@@ -9394,9 +9933,9 @@ function parseSymbols5(opts) {
|
|
|
9394
9933
|
function regexParse2(opts) {
|
|
9395
9934
|
const { file, content, lang } = opts;
|
|
9396
9935
|
const symbols = [];
|
|
9397
|
-
const
|
|
9398
|
-
const isPackageJson =
|
|
9399
|
-
const isTsconfig =
|
|
9936
|
+
const basename9 = path17.basename(file).toLowerCase();
|
|
9937
|
+
const isPackageJson = basename9 === "package.json";
|
|
9938
|
+
const isTsconfig = basename9 === "tsconfig.json" || basename9 === "tsconfig.build.json";
|
|
9400
9939
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
9401
9940
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
9402
9941
|
const lines = content.split("\n");
|
|
@@ -9798,9 +10337,7 @@ function yieldEventLoop() {
|
|
|
9798
10337
|
function throwIfAborted(signal) {
|
|
9799
10338
|
if (!signal?.aborted) return;
|
|
9800
10339
|
if (signal.reason instanceof Error) throw signal.reason;
|
|
9801
|
-
throw new Error(
|
|
9802
|
-
typeof signal.reason === "string" ? signal.reason : "Indexing cancelled"
|
|
9803
|
-
);
|
|
10340
|
+
throw new Error(typeof signal.reason === "string" ? signal.reason : "Indexing cancelled");
|
|
9804
10341
|
}
|
|
9805
10342
|
function isAbortError(err) {
|
|
9806
10343
|
return err instanceof DOMException && err.name === "AbortError";
|
|
@@ -9816,8 +10353,20 @@ var DEFAULT_IGNORE = [
|
|
|
9816
10353
|
"__snapshots__",
|
|
9817
10354
|
".nyc_output"
|
|
9818
10355
|
];
|
|
10356
|
+
var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
|
|
10357
|
+
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
10358
|
+
function isWithinProject(projectRoot, file) {
|
|
10359
|
+
const rel = path19.relative(projectRoot, file);
|
|
10360
|
+
return rel !== "" && !rel.startsWith(`..${path19.sep}`) && rel !== ".." && !path19.isAbsolute(rel);
|
|
10361
|
+
}
|
|
10362
|
+
function isMissingPathError(err) {
|
|
10363
|
+
const code = err?.code;
|
|
10364
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
10365
|
+
}
|
|
9819
10366
|
async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
9820
10367
|
const results = [];
|
|
10368
|
+
const errors = [];
|
|
10369
|
+
let complete = true;
|
|
9821
10370
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
9822
10371
|
const globs = [
|
|
9823
10372
|
{ ext: ".ts", pat: compileGlob2("**/*.ts") },
|
|
@@ -9841,7 +10390,9 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
9841
10390
|
let entries;
|
|
9842
10391
|
try {
|
|
9843
10392
|
entries = await fs14.readdir(dir, { withFileTypes: true });
|
|
9844
|
-
} catch {
|
|
10393
|
+
} catch (err) {
|
|
10394
|
+
complete = false;
|
|
10395
|
+
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9845
10396
|
return;
|
|
9846
10397
|
}
|
|
9847
10398
|
dirCount++;
|
|
@@ -9853,8 +10404,8 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
9853
10404
|
if (isGitIgnored(rel, true)) continue;
|
|
9854
10405
|
await walk(full);
|
|
9855
10406
|
} else if (e.isFile()) {
|
|
9856
|
-
if (isGitIgnored(rel, false)) continue;
|
|
9857
|
-
const ext = path19.extname(e.name);
|
|
10407
|
+
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
10408
|
+
const ext = path19.extname(e.name).toLowerCase();
|
|
9858
10409
|
for (const { ext: extName, pat } of globs) {
|
|
9859
10410
|
if (ext === extName && (pat.test(rel) || pat.test(e.name))) {
|
|
9860
10411
|
results.push(full);
|
|
@@ -9865,7 +10416,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
9865
10416
|
}
|
|
9866
10417
|
};
|
|
9867
10418
|
await walk(projectRoot);
|
|
9868
|
-
return results;
|
|
10419
|
+
return { files: results, complete, errors };
|
|
9869
10420
|
}
|
|
9870
10421
|
async function parseFile(file, content, lang) {
|
|
9871
10422
|
switch (lang) {
|
|
@@ -9888,6 +10439,26 @@ async function parseFile(file, content, lang) {
|
|
|
9888
10439
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
9889
10440
|
}
|
|
9890
10441
|
}
|
|
10442
|
+
function assignRefsToSymbols2(refs, symbols) {
|
|
10443
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
10444
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
10445
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10446
|
+
const assigned = [];
|
|
10447
|
+
for (const ref of refs) {
|
|
10448
|
+
let owner2;
|
|
10449
|
+
for (const symbol of ordered) {
|
|
10450
|
+
if (symbol.line > ref.line) break;
|
|
10451
|
+
owner2 = symbol;
|
|
10452
|
+
}
|
|
10453
|
+
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
10454
|
+
if (!owner2 || owner2.id <= 0) continue;
|
|
10455
|
+
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
10456
|
+
if (seen.has(key)) continue;
|
|
10457
|
+
seen.add(key);
|
|
10458
|
+
assigned.push({ ...ref, fromId: owner2.id });
|
|
10459
|
+
}
|
|
10460
|
+
return assigned;
|
|
10461
|
+
}
|
|
9891
10462
|
async function runIndexer(_ctx, opts) {
|
|
9892
10463
|
const store = new IndexStore(opts.projectRoot, { indexDir: opts.indexDir });
|
|
9893
10464
|
try {
|
|
@@ -9900,7 +10471,9 @@ async function runIndexer(_ctx, opts) {
|
|
|
9900
10471
|
}
|
|
9901
10472
|
}
|
|
9902
10473
|
async function runIndexerWithStore(store, opts) {
|
|
9903
|
-
const { projectRoot,
|
|
10474
|
+
const { projectRoot, langs, ignore = [], signal } = opts;
|
|
10475
|
+
const relationGraphVersion = "2";
|
|
10476
|
+
const force = (opts.force ?? false) || store.getMetadata("relation_graph_version") !== relationGraphVersion;
|
|
9904
10477
|
const startMs = Date.now();
|
|
9905
10478
|
const errors = [];
|
|
9906
10479
|
const langStats = {};
|
|
@@ -9909,10 +10482,18 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9909
10482
|
const isGitIgnored = await loadGitignoreMatcher(projectRoot);
|
|
9910
10483
|
let files;
|
|
9911
10484
|
let discoveredFiles = null;
|
|
10485
|
+
let discoveryComplete = true;
|
|
9912
10486
|
if (opts.files && opts.files.length > 0) {
|
|
9913
|
-
files = opts.files.map((f) => path19.resolve(projectRoot, f)).filter((f) =>
|
|
10487
|
+
files = opts.files.map((f) => path19.resolve(projectRoot, f)).filter((f) => {
|
|
10488
|
+
if (!isWithinProject(projectRoot, f)) return false;
|
|
10489
|
+
const rel = path19.relative(projectRoot, f).replace(/\\/g, "/");
|
|
10490
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path19.basename(f)) && !isGitIgnored(rel, false);
|
|
10491
|
+
});
|
|
9914
10492
|
} else {
|
|
9915
|
-
|
|
10493
|
+
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
10494
|
+
files = discovery.files;
|
|
10495
|
+
errors.push(...discovery.errors);
|
|
10496
|
+
discoveryComplete = discovery.complete;
|
|
9916
10497
|
discoveredFiles = new Set(files);
|
|
9917
10498
|
}
|
|
9918
10499
|
if (langs && langs.length > 0) {
|
|
@@ -9937,36 +10518,66 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9937
10518
|
}
|
|
9938
10519
|
const statOpts = signal ? { signal } : {};
|
|
9939
10520
|
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
|
-
|
|
10521
|
+
batchFiles.map(
|
|
10522
|
+
async (file) => {
|
|
10523
|
+
let stat17;
|
|
10524
|
+
try {
|
|
10525
|
+
stat17 = await fs14.stat(file, statOpts);
|
|
10526
|
+
} catch (e) {
|
|
10527
|
+
if (isAbortError(e)) throw e;
|
|
10528
|
+
return {
|
|
10529
|
+
file,
|
|
10530
|
+
stat: null,
|
|
10531
|
+
lang: "",
|
|
10532
|
+
parsed: null,
|
|
10533
|
+
error: `stat error: ${e instanceof Error ? e.message : String(e)}`,
|
|
10534
|
+
missing: isMissingPathError(e)
|
|
10535
|
+
};
|
|
10536
|
+
}
|
|
10537
|
+
if (!stat17.isFile()) return { file, stat: stat17, lang: "", parsed: null };
|
|
10538
|
+
const lang = detectLang(file);
|
|
10539
|
+
if (!lang) return { file, stat: stat17, lang: "", parsed: null };
|
|
10540
|
+
if (stat17.size > MAX_INDEX_FILE_BYTES) {
|
|
10541
|
+
return {
|
|
10542
|
+
file,
|
|
10543
|
+
stat: stat17,
|
|
10544
|
+
lang,
|
|
10545
|
+
parsed: null,
|
|
10546
|
+
error: `file too large (${stat17.size} bytes; max ${MAX_INDEX_FILE_BYTES})`
|
|
10547
|
+
};
|
|
10548
|
+
}
|
|
10549
|
+
const meta = existingMeta.get(file);
|
|
10550
|
+
if (!force && meta && meta.mtimeMs === Math.floor(stat17.mtimeMs)) {
|
|
10551
|
+
return { file, stat: stat17, lang, parsed: null, skippedMeta: meta };
|
|
10552
|
+
}
|
|
10553
|
+
let content;
|
|
10554
|
+
try {
|
|
10555
|
+
content = await fs14.readFile(file, { encoding: "utf8", signal });
|
|
10556
|
+
} catch (e) {
|
|
10557
|
+
if (isAbortError(e)) throw e;
|
|
10558
|
+
return {
|
|
10559
|
+
file,
|
|
10560
|
+
stat: stat17,
|
|
10561
|
+
lang,
|
|
10562
|
+
parsed: null,
|
|
10563
|
+
error: `read error: ${e instanceof Error ? e.message : String(e)}`
|
|
10564
|
+
};
|
|
10565
|
+
}
|
|
10566
|
+
let parsed;
|
|
10567
|
+
try {
|
|
10568
|
+
parsed = await parseFile(file, content, lang);
|
|
10569
|
+
} catch (e) {
|
|
10570
|
+
return {
|
|
10571
|
+
file,
|
|
10572
|
+
stat: stat17,
|
|
10573
|
+
lang,
|
|
10574
|
+
parsed: null,
|
|
10575
|
+
error: `parse error: ${e instanceof Error ? e.message : String(e)}`
|
|
10576
|
+
};
|
|
10577
|
+
}
|
|
10578
|
+
return { file, stat: stat17, lang, parsed, content };
|
|
9967
10579
|
}
|
|
9968
|
-
|
|
9969
|
-
})
|
|
10580
|
+
)
|
|
9970
10581
|
);
|
|
9971
10582
|
const batchEntries = [];
|
|
9972
10583
|
const deleteForFiles = [];
|
|
@@ -9981,8 +10592,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9981
10592
|
}
|
|
9982
10593
|
const result = settled.value;
|
|
9983
10594
|
if (result.error) {
|
|
9984
|
-
if (result.
|
|
9985
|
-
|
|
10595
|
+
if (result.missing) store.deleteFile(file);
|
|
10596
|
+
errors.push(`${file}: ${result.error}`);
|
|
9986
10597
|
continue;
|
|
9987
10598
|
}
|
|
9988
10599
|
const { stat: stat17, lang, parsed } = result;
|
|
@@ -10006,6 +10617,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10006
10617
|
continue;
|
|
10007
10618
|
}
|
|
10008
10619
|
if (parsed.symbols.length === 0) {
|
|
10620
|
+
store.deleteRefsForFile(file);
|
|
10621
|
+
store.deleteSymbolsForFile(file);
|
|
10009
10622
|
store.upsertFile({
|
|
10010
10623
|
file,
|
|
10011
10624
|
lang,
|
|
@@ -10016,15 +10629,11 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10016
10629
|
filesIndexed++;
|
|
10017
10630
|
continue;
|
|
10018
10631
|
}
|
|
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
10632
|
batchEntries.push({
|
|
10024
10633
|
file,
|
|
10025
10634
|
lang,
|
|
10026
10635
|
symbols: parsed.symbols,
|
|
10027
|
-
refs,
|
|
10636
|
+
refs: parsed.refs ?? [],
|
|
10028
10637
|
mtimeMs: Math.floor(stat17.mtimeMs),
|
|
10029
10638
|
symbolCount: parsed.symbols.length
|
|
10030
10639
|
});
|
|
@@ -10032,34 +10641,12 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10032
10641
|
}
|
|
10033
10642
|
if (batchEntries.length > 0) {
|
|
10034
10643
|
try {
|
|
10035
|
-
|
|
10036
|
-
let cursor = 0;
|
|
10644
|
+
store.commitBatch(batchEntries, { deleteForFiles });
|
|
10037
10645
|
for (const entry of batchEntries) {
|
|
10038
10646
|
const count = entry.symbols.length;
|
|
10039
|
-
const symbolsWithIds = inserted.slice(cursor, cursor + count);
|
|
10040
|
-
cursor += count;
|
|
10041
10647
|
symbolsIndexed += count;
|
|
10042
10648
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
|
|
10043
10649
|
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
10650
|
}
|
|
10064
10651
|
} catch (err) {
|
|
10065
10652
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -10073,23 +10660,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10073
10660
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
|
|
10074
10661
|
filesIndexed++;
|
|
10075
10662
|
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
|
-
}
|
|
10663
|
+
const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
|
|
10093
10664
|
if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
|
|
10094
10665
|
}
|
|
10095
10666
|
store.upsertFile({
|
|
@@ -10100,19 +10671,23 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10100
10671
|
lastIndexed: Date.now()
|
|
10101
10672
|
});
|
|
10102
10673
|
} catch (innerErr) {
|
|
10103
|
-
errors.push(
|
|
10674
|
+
errors.push(
|
|
10675
|
+
`fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
|
|
10676
|
+
);
|
|
10104
10677
|
}
|
|
10105
10678
|
}
|
|
10106
10679
|
}
|
|
10107
10680
|
}
|
|
10108
10681
|
}
|
|
10109
|
-
if (discoveredFiles) {
|
|
10682
|
+
if (discoveredFiles && discoveryComplete) {
|
|
10110
10683
|
for (const [file_] of existingMeta) {
|
|
10111
10684
|
if (!discoveredFiles.has(file_)) {
|
|
10112
10685
|
store.deleteFile(file_);
|
|
10113
10686
|
}
|
|
10114
10687
|
}
|
|
10115
10688
|
}
|
|
10689
|
+
store.resolveRefs();
|
|
10690
|
+
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
10116
10691
|
const durationMs = Date.now() - startMs;
|
|
10117
10692
|
store.setLastIndexed(Date.now());
|
|
10118
10693
|
return {
|
|
@@ -10175,7 +10750,7 @@ function statsService(args) {
|
|
|
10175
10750
|
|
|
10176
10751
|
// src/codebase-index/background-indexer.ts
|
|
10177
10752
|
var DEFAULT_FULL_INDEX_TIMEOUT_MS = 24e4;
|
|
10178
|
-
var DEFAULT_QUERY_TIMEOUT_MS =
|
|
10753
|
+
var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
10179
10754
|
var _ready = false;
|
|
10180
10755
|
var _indexing = false;
|
|
10181
10756
|
var _currentFile = 0;
|
|
@@ -10244,14 +10819,22 @@ function ensureWorker() {
|
|
|
10244
10819
|
if (!entry) return;
|
|
10245
10820
|
pending.delete(msg.id);
|
|
10246
10821
|
if (msg.ok) entry.resolve(msg.result);
|
|
10247
|
-
else
|
|
10822
|
+
else {
|
|
10823
|
+
const error = msg.errorName === "LockError" ? new LockError(msg.error) : new Error(msg.error);
|
|
10824
|
+
if (msg.errorName && msg.errorName !== "Error") {
|
|
10825
|
+
error.name = msg.errorName;
|
|
10826
|
+
}
|
|
10827
|
+
entry.reject(error);
|
|
10828
|
+
}
|
|
10248
10829
|
});
|
|
10249
10830
|
w.on("error", (err) => {
|
|
10831
|
+
if (worker !== w) return;
|
|
10250
10832
|
worker = null;
|
|
10251
10833
|
failAllPending(err);
|
|
10252
10834
|
});
|
|
10253
10835
|
w.on("exit", () => {
|
|
10254
|
-
if (worker
|
|
10836
|
+
if (worker !== w) return;
|
|
10837
|
+
worker = null;
|
|
10255
10838
|
failAllPending(new Error("codebase-index worker exited"));
|
|
10256
10839
|
});
|
|
10257
10840
|
worker = w;
|
|
@@ -10271,6 +10854,11 @@ function terminateWorker(reason) {
|
|
|
10271
10854
|
function callIndexOp(op, args, opts) {
|
|
10272
10855
|
const w = ensureWorker();
|
|
10273
10856
|
if (!w) return callInline(op, args, opts);
|
|
10857
|
+
if (opts.signal?.aborted) {
|
|
10858
|
+
return Promise.reject(
|
|
10859
|
+
opts.signal.reason instanceof Error ? opts.signal.reason : new Error("Indexing cancelled")
|
|
10860
|
+
);
|
|
10861
|
+
}
|
|
10274
10862
|
return new Promise((resolve14, reject) => {
|
|
10275
10863
|
const id = nextRpcId++;
|
|
10276
10864
|
const timer = setTimeout(() => {
|
|
@@ -10285,8 +10873,7 @@ function callIndexOp(op, args, opts) {
|
|
|
10285
10873
|
const onAbort = () => {
|
|
10286
10874
|
w.postMessage({ type: "cancel", id });
|
|
10287
10875
|
};
|
|
10288
|
-
|
|
10289
|
-
else opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
10876
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
10290
10877
|
const cleanup = () => {
|
|
10291
10878
|
clearTimeout(timer);
|
|
10292
10879
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
@@ -10430,7 +11017,7 @@ var codebaseIndexTool = {
|
|
|
10430
11017
|
category: "Project",
|
|
10431
11018
|
icon: "index",
|
|
10432
11019
|
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: "
|
|
11020
|
+
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
11021
|
permission: "confirm",
|
|
10435
11022
|
mutating: true,
|
|
10436
11023
|
capabilities: ["fs.write.outside-project"],
|
|
@@ -10486,12 +11073,14 @@ var codebaseSearchTool = {
|
|
|
10486
11073
|
name: "codebase-search",
|
|
10487
11074
|
category: "Project",
|
|
10488
11075
|
icon: "index",
|
|
10489
|
-
description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback.
|
|
10490
|
-
usageHint: "
|
|
11076
|
+
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).",
|
|
11077
|
+
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
11078
|
permission: "auto",
|
|
10492
11079
|
mutating: false,
|
|
10493
11080
|
capabilities: ["fs.read"],
|
|
10494
|
-
|
|
11081
|
+
// The index host has its own 30s read watchdog. Leave enough headroom for
|
|
11082
|
+
// worker teardown and structured timeout reporting.
|
|
11083
|
+
timeoutMs: 35e3,
|
|
10495
11084
|
inputSchema: {
|
|
10496
11085
|
type: "object",
|
|
10497
11086
|
properties: {
|
|
@@ -10501,11 +11090,34 @@ var codebaseSearchTool = {
|
|
|
10501
11090
|
},
|
|
10502
11091
|
kind: {
|
|
10503
11092
|
type: "string",
|
|
10504
|
-
|
|
11093
|
+
enum: [
|
|
11094
|
+
"class",
|
|
11095
|
+
"interface",
|
|
11096
|
+
"enum",
|
|
11097
|
+
"type",
|
|
11098
|
+
"function",
|
|
11099
|
+
"method",
|
|
11100
|
+
"var",
|
|
11101
|
+
"const",
|
|
11102
|
+
"let",
|
|
11103
|
+
"property",
|
|
11104
|
+
"parameter",
|
|
11105
|
+
"namespace",
|
|
11106
|
+
"object",
|
|
11107
|
+
"literal",
|
|
11108
|
+
"schema",
|
|
11109
|
+
"struct",
|
|
11110
|
+
"trait",
|
|
11111
|
+
"impl",
|
|
11112
|
+
"static",
|
|
11113
|
+
"mod"
|
|
11114
|
+
],
|
|
11115
|
+
description: "Filter by indexed symbol kind"
|
|
10505
11116
|
},
|
|
10506
11117
|
lang: {
|
|
10507
11118
|
type: "string",
|
|
10508
|
-
|
|
11119
|
+
enum: ["ts", "tsx", "js", "jsx", "go", "py", "rs", "json", "yaml"],
|
|
11120
|
+
description: "Filter by indexed language"
|
|
10509
11121
|
},
|
|
10510
11122
|
lspKind: {
|
|
10511
11123
|
type: "integer",
|
|
@@ -10530,20 +11142,12 @@ var codebaseSearchTool = {
|
|
|
10530
11142
|
},
|
|
10531
11143
|
async execute(input, ctx, execOpts) {
|
|
10532
11144
|
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) {
|
|
11145
|
+
if (state.indexing && !state.ready) {
|
|
10542
11146
|
return {
|
|
10543
11147
|
results: [],
|
|
10544
11148
|
total: 0,
|
|
10545
11149
|
query: input.query,
|
|
10546
|
-
indexStatus: `
|
|
11150
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
10547
11151
|
};
|
|
10548
11152
|
}
|
|
10549
11153
|
if (state.lastError) {
|
|
@@ -10556,21 +11160,37 @@ var codebaseSearchTool = {
|
|
|
10556
11160
|
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
10557
11161
|
};
|
|
10558
11162
|
}
|
|
10559
|
-
const limit = Math.min(input.limit ?? 20, 100);
|
|
11163
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 20), 100));
|
|
10560
11164
|
const { results, total } = await searchCodebaseIndex(
|
|
10561
11165
|
{
|
|
10562
11166
|
projectRoot: ctx.projectRoot,
|
|
10563
11167
|
indexDir: codebaseIndexDirOverride(ctx),
|
|
10564
11168
|
query: input.query,
|
|
10565
|
-
kind: input.kind,
|
|
10566
|
-
lang: input.lang,
|
|
11169
|
+
kind: input.kind?.toLowerCase(),
|
|
11170
|
+
lang: input.lang?.toLowerCase(),
|
|
10567
11171
|
file: input.file,
|
|
10568
11172
|
lspKind: input.lspKind,
|
|
10569
11173
|
limit
|
|
10570
11174
|
},
|
|
10571
11175
|
{ signal: execOpts?.signal }
|
|
10572
11176
|
);
|
|
10573
|
-
|
|
11177
|
+
let hasPersistedIndex = state.ready || total > 0;
|
|
11178
|
+
if (!hasPersistedIndex) {
|
|
11179
|
+
try {
|
|
11180
|
+
const stats = await codebaseIndexStats(
|
|
11181
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
11182
|
+
{ signal: execOpts?.signal }
|
|
11183
|
+
);
|
|
11184
|
+
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
11185
|
+
} catch {
|
|
11186
|
+
}
|
|
11187
|
+
}
|
|
11188
|
+
return {
|
|
11189
|
+
results,
|
|
11190
|
+
total,
|
|
11191
|
+
query: input.query,
|
|
11192
|
+
...hasPersistedIndex ? {} : { indexStatus: "No persisted index data found. Run codebase-index to build it." }
|
|
11193
|
+
};
|
|
10574
11194
|
}
|
|
10575
11195
|
};
|
|
10576
11196
|
|
|
@@ -10579,12 +11199,15 @@ var codebaseStatsTool = {
|
|
|
10579
11199
|
name: "codebase-stats",
|
|
10580
11200
|
category: "Project",
|
|
10581
11201
|
icon: "index",
|
|
10582
|
-
description: "
|
|
10583
|
-
usageHint: "
|
|
11202
|
+
description: "Check whether a persisted codebase index exists and report its health and statistics (symbols, files, language/kind breakdown, size, last update).",
|
|
11203
|
+
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
11204
|
permission: "auto",
|
|
10585
11205
|
mutating: false,
|
|
10586
11206
|
capabilities: ["fs.read"],
|
|
10587
|
-
|
|
11207
|
+
// The index host has its own 30s read watchdog. Keep the outer tool budget
|
|
11208
|
+
// longer so the host can return a structured timeout instead of being
|
|
11209
|
+
// pre-empted by the generic tool executor.
|
|
11210
|
+
timeoutMs: 35e3,
|
|
10588
11211
|
inputSchema: {
|
|
10589
11212
|
type: "object",
|
|
10590
11213
|
properties: {},
|
|
@@ -10592,7 +11215,8 @@ var codebaseStatsTool = {
|
|
|
10592
11215
|
},
|
|
10593
11216
|
async execute(_input, ctx, execOpts) {
|
|
10594
11217
|
const idxState = getIndexState();
|
|
10595
|
-
|
|
11218
|
+
const indexPath = resolveIndexDir(ctx.projectRoot, codebaseIndexDirOverride(ctx));
|
|
11219
|
+
if (idxState.indexing) {
|
|
10596
11220
|
return {
|
|
10597
11221
|
totalSymbols: 0,
|
|
10598
11222
|
totalFiles: 0,
|
|
@@ -10600,22 +11224,47 @@ var codebaseStatsTool = {
|
|
|
10600
11224
|
byKind: {},
|
|
10601
11225
|
lastIndexed: null,
|
|
10602
11226
|
sizeBytes: 0,
|
|
10603
|
-
indexPath
|
|
11227
|
+
indexPath,
|
|
10604
11228
|
version: SCHEMA_VERSION,
|
|
10605
|
-
|
|
11229
|
+
statsAvailable: false,
|
|
11230
|
+
indexing: {
|
|
11231
|
+
currentFile: idxState.currentFile,
|
|
11232
|
+
totalFiles: idxState.totalFiles
|
|
11233
|
+
},
|
|
11234
|
+
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
11235
|
};
|
|
10607
11236
|
}
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
11237
|
+
let stats;
|
|
11238
|
+
try {
|
|
11239
|
+
stats = await codebaseIndexStats(
|
|
11240
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
11241
|
+
{ signal: execOpts?.signal }
|
|
11242
|
+
);
|
|
11243
|
+
} catch (err) {
|
|
11244
|
+
if (!(err instanceof IndexTimeoutError) && err?.name !== "IndexTimeoutError") {
|
|
11245
|
+
throw err;
|
|
11246
|
+
}
|
|
10613
11247
|
return {
|
|
10614
|
-
|
|
10615
|
-
|
|
11248
|
+
totalSymbols: 0,
|
|
11249
|
+
totalFiles: 0,
|
|
11250
|
+
byLang: {},
|
|
11251
|
+
byKind: {},
|
|
11252
|
+
lastIndexed: null,
|
|
11253
|
+
sizeBytes: 0,
|
|
11254
|
+
indexPath,
|
|
11255
|
+
version: SCHEMA_VERSION,
|
|
11256
|
+
statsAvailable: false,
|
|
11257
|
+
indexStatus: "Index statistics timed out. Do not repeatedly retry stats; try codebase-search directly or use the appropriate grep/glob/tree fallback."
|
|
10616
11258
|
};
|
|
10617
11259
|
}
|
|
10618
11260
|
const circuit = idxState.circuit;
|
|
11261
|
+
const hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
11262
|
+
let indexStatus;
|
|
11263
|
+
if (circuit.state === "open") {
|
|
11264
|
+
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.");
|
|
11265
|
+
} else if (!hasPersistedIndex) {
|
|
11266
|
+
indexStatus = "No persisted index data found. Run codebase-index to build it before broad code exploration.";
|
|
11267
|
+
}
|
|
10619
11268
|
return {
|
|
10620
11269
|
totalSymbols: stats.totalSymbols,
|
|
10621
11270
|
totalFiles: stats.totalFiles,
|
|
@@ -10625,9 +11274,8 @@ var codebaseStatsTool = {
|
|
|
10625
11274
|
sizeBytes: stats.sizeBytes,
|
|
10626
11275
|
indexPath: stats.indexPath,
|
|
10627
11276
|
version: stats.version,
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
} : {}
|
|
11277
|
+
statsAvailable: true,
|
|
11278
|
+
...indexStatus ? { indexStatus } : {}
|
|
10631
11279
|
};
|
|
10632
11280
|
}
|
|
10633
11281
|
};
|
|
@@ -12146,6 +12794,11 @@ Compare this against your old_string and retry with the file's actual text.` : "
|
|
|
12146
12794
|
|
|
12147
12795
|
// src/exec.ts
|
|
12148
12796
|
import { spawn as spawn8 } from "node:child_process";
|
|
12797
|
+
import {
|
|
12798
|
+
emitProcessCompleted as emitProcessCompleted3,
|
|
12799
|
+
emitProcessOutput as emitProcessOutput3,
|
|
12800
|
+
emitProcessStarted as emitProcessStarted3
|
|
12801
|
+
} from "@wrongstack/core";
|
|
12149
12802
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils/error";
|
|
12150
12803
|
init_output_spool();
|
|
12151
12804
|
init_util();
|
|
@@ -13254,12 +13907,30 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13254
13907
|
resolve14(result);
|
|
13255
13908
|
};
|
|
13256
13909
|
const startedAt = Date.now();
|
|
13910
|
+
let stdoutBytes = 0;
|
|
13911
|
+
let stderrBytes = 0;
|
|
13912
|
+
let telemetryCompleted = false;
|
|
13913
|
+
let timedOut = false;
|
|
13257
13914
|
const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
|
|
13258
13915
|
const resolved = resolveWin32Command(cmd);
|
|
13259
13916
|
const needsShell = isWin3 && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
13260
13917
|
const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
|
|
13261
13918
|
const spawnCmd = shim?.command ?? resolved;
|
|
13262
13919
|
const spawnArgs = shim?.args ?? args;
|
|
13920
|
+
const emitCompletedOnce = (exitCode, pid2, signal2) => {
|
|
13921
|
+
if (telemetryCompleted) return;
|
|
13922
|
+
telemetryCompleted = true;
|
|
13923
|
+
emitProcessCompleted3({
|
|
13924
|
+
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
13925
|
+
exitCode,
|
|
13926
|
+
...signal2 ? { signal: signal2 } : {},
|
|
13927
|
+
durationMs: Date.now() - startedAt,
|
|
13928
|
+
stdoutBytes,
|
|
13929
|
+
stderrBytes,
|
|
13930
|
+
timedOut,
|
|
13931
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13932
|
+
});
|
|
13933
|
+
};
|
|
13263
13934
|
let child;
|
|
13264
13935
|
try {
|
|
13265
13936
|
child = spawn8(spawnCmd, spawnArgs, {
|
|
@@ -13272,6 +13943,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13272
13943
|
});
|
|
13273
13944
|
} catch (err) {
|
|
13274
13945
|
spool.finalize();
|
|
13946
|
+
emitProcessStarted3({
|
|
13947
|
+
parentPid: process.pid,
|
|
13948
|
+
command: redactCommand(`${cmd} ${args.join(" ")}`),
|
|
13949
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
13950
|
+
cwd,
|
|
13951
|
+
background: false,
|
|
13952
|
+
startedAt: new Date(startedAt).toISOString()
|
|
13953
|
+
});
|
|
13954
|
+
emitCompletedOnce(1, void 0);
|
|
13275
13955
|
finish({
|
|
13276
13956
|
command: cmd,
|
|
13277
13957
|
args,
|
|
@@ -13284,6 +13964,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13284
13964
|
});
|
|
13285
13965
|
return;
|
|
13286
13966
|
}
|
|
13967
|
+
emitProcessStarted3({
|
|
13968
|
+
...child.pid !== void 0 ? { pid: child.pid } : {},
|
|
13969
|
+
parentPid: process.pid,
|
|
13970
|
+
command: redactCommand(`${cmd} ${args.join(" ")}`),
|
|
13971
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
13972
|
+
cwd,
|
|
13973
|
+
background: false,
|
|
13974
|
+
startedAt: new Date(startedAt).toISOString()
|
|
13975
|
+
});
|
|
13287
13976
|
child.on("error", (err) => {
|
|
13288
13977
|
const isAbort = err && err.code === "ABORT_ERR";
|
|
13289
13978
|
const stderrText = isAbort ? `Aborted: ${err.message}` : err.message;
|
|
@@ -13292,6 +13981,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13292
13981
|
if (typeof pid === "number") registry.unregister(pid);
|
|
13293
13982
|
registry.afterCall(Date.now() - startedAt, true);
|
|
13294
13983
|
spool.finalize();
|
|
13984
|
+
emitCompletedOnce(isAbort ? 124 : 1, child.pid, isAbort ? "ABORT" : void 0);
|
|
13295
13985
|
finish({
|
|
13296
13986
|
command: cmd,
|
|
13297
13987
|
args,
|
|
@@ -13311,6 +14001,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13311
14001
|
}
|
|
13312
14002
|
const timer = setTimeout(() => {
|
|
13313
14003
|
killed = true;
|
|
14004
|
+
timedOut = true;
|
|
13314
14005
|
if (typeof pid === "number") registry.kill(pid);
|
|
13315
14006
|
else child.kill("SIGTERM");
|
|
13316
14007
|
}, timeout);
|
|
@@ -13325,11 +14016,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13325
14016
|
}
|
|
13326
14017
|
child.stdout?.on("data", (chunk) => {
|
|
13327
14018
|
const text = chunk.toString();
|
|
14019
|
+
stdoutBytes += chunk.byteLength;
|
|
14020
|
+
emitProcessOutput3({ pid, stream: "stdout", chunk });
|
|
13328
14021
|
if (stdout.length < MAX_OUTPUT2) stdout += text;
|
|
13329
14022
|
spool.write(text);
|
|
13330
14023
|
});
|
|
13331
14024
|
child.stderr?.on("data", (chunk) => {
|
|
13332
14025
|
const text = chunk.toString();
|
|
14026
|
+
stderrBytes += chunk.byteLength;
|
|
14027
|
+
emitProcessOutput3({ pid, stream: "stderr", chunk });
|
|
13333
14028
|
if (stderr.length < MAX_OUTPUT2) stderr += text;
|
|
13334
14029
|
spool.write(text);
|
|
13335
14030
|
});
|
|
@@ -13339,6 +14034,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13339
14034
|
if (typeof pid === "number") registry.unregister(pid);
|
|
13340
14035
|
const durationMs = Date.now() - startedAt;
|
|
13341
14036
|
const exitCode = killed ? 124 : code ?? 1;
|
|
14037
|
+
emitCompletedOnce(exitCode, pid);
|
|
13342
14038
|
registry.afterCall(durationMs, exitCode !== 0);
|
|
13343
14039
|
const spooled = spool.finalize();
|
|
13344
14040
|
finish({
|
|
@@ -13414,7 +14110,7 @@ function guardedLookup(hostname2, options, callback) {
|
|
|
13414
14110
|
var pinnedAgent;
|
|
13415
14111
|
function getPinnedDispatcher() {
|
|
13416
14112
|
if (!pinnedAgent) {
|
|
13417
|
-
pinnedAgent = new Agent({ connect: { lookup: guardedLookup } });
|
|
14113
|
+
pinnedAgent = new Agent({ allowH2: false, connect: { lookup: guardedLookup } });
|
|
13418
14114
|
}
|
|
13419
14115
|
return pinnedAgent;
|
|
13420
14116
|
}
|
|
@@ -13516,7 +14212,7 @@ async function assertNotPrivate(hostname2) {
|
|
|
13516
14212
|
}
|
|
13517
14213
|
}
|
|
13518
14214
|
} catch (err) {
|
|
13519
|
-
if (err instanceof
|
|
14215
|
+
if (err instanceof ToolValidationError2) throw err;
|
|
13520
14216
|
}
|
|
13521
14217
|
}
|
|
13522
14218
|
}
|
|
@@ -13864,7 +14560,7 @@ async function detectFixer(cwd) {
|
|
|
13864
14560
|
init_util();
|
|
13865
14561
|
import { spawn as spawn9 } from "node:child_process";
|
|
13866
14562
|
import { statSync as statSync3 } from "node:fs";
|
|
13867
|
-
import { dirname as dirname12, resolve as resolve11, sep as
|
|
14563
|
+
import { dirname as dirname12, resolve as resolve11, sep as sep5 } from "node:path";
|
|
13868
14564
|
import { assessCommitSafety, buildChildEnv as buildChildEnv4 } from "@wrongstack/core";
|
|
13869
14565
|
var TIMEOUT_MS2 = 3e4;
|
|
13870
14566
|
var MAX_OUTPUT3 = 1e5;
|
|
@@ -14009,7 +14705,7 @@ function validateWorktreeInput(input, projectRoot) {
|
|
|
14009
14705
|
if ((input.worktreeAction === "add" || input.worktreeAction === "remove") && input.worktreePath) {
|
|
14010
14706
|
const root = resolve11(projectRoot);
|
|
14011
14707
|
const abs = resolve11(root, input.worktreePath);
|
|
14012
|
-
if (abs !== root && !abs.startsWith(root +
|
|
14708
|
+
if (abs !== root && !abs.startsWith(root + sep5)) {
|
|
14013
14709
|
return reject(`unsafe worktree path (escapes project root): ${input.worktreePath}`);
|
|
14014
14710
|
}
|
|
14015
14711
|
}
|
|
@@ -14172,8 +14868,8 @@ var WALK_CONCURRENCY = 16;
|
|
|
14172
14868
|
var globTool = {
|
|
14173
14869
|
name: "glob",
|
|
14174
14870
|
category: "Filesystem",
|
|
14175
|
-
description: "Find files
|
|
14176
|
-
usageHint: "
|
|
14871
|
+
description: "Find files by path pattern. Use index-backed `codebase-search` first for code symbols or concepts when it is live.",
|
|
14872
|
+
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
14873
|
selection: {
|
|
14178
14874
|
doNotUseWhen: "you need to search inside file contents.",
|
|
14179
14875
|
useInstead: ["grep"]
|
|
@@ -14361,8 +15057,8 @@ var NATIVE_MAX_FILE_BYTES = 1e6;
|
|
|
14361
15057
|
var grepTool = {
|
|
14362
15058
|
name: "grep",
|
|
14363
15059
|
category: "Search",
|
|
14364
|
-
description: "Search
|
|
14365
|
-
usageHint: '
|
|
15060
|
+
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.",
|
|
15061
|
+
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
15062
|
selection: {
|
|
14367
15063
|
doNotUseWhen: "you only need to locate files by name or path pattern.",
|
|
14368
15064
|
useInstead: ["glob"]
|
|
@@ -15384,6 +16080,7 @@ function toYaml(data, indent = 0) {
|
|
|
15384
16080
|
}
|
|
15385
16081
|
|
|
15386
16082
|
// src/kanban.ts
|
|
16083
|
+
import { randomUUID } from "node:crypto";
|
|
15387
16084
|
import { deserializeTaskGraph as deserializeTaskGraph2, loadTasks as loadTasks2, serializeTaskGraph } from "@wrongstack/core";
|
|
15388
16085
|
import {
|
|
15389
16086
|
addCheckToTask,
|
|
@@ -15423,7 +16120,9 @@ import {
|
|
|
15423
16120
|
setTaskChain,
|
|
15424
16121
|
splitTask,
|
|
15425
16122
|
syncBoardFromTaskGraph as syncBoardFromTaskGraph2,
|
|
16123
|
+
transitionTask,
|
|
15426
16124
|
transferTaskToBoard,
|
|
16125
|
+
touchKanbanPresence as touchKanbanPresence2,
|
|
15427
16126
|
updateBoard as updateBoard2,
|
|
15428
16127
|
updateCheckOnTask,
|
|
15429
16128
|
updateColumn,
|
|
@@ -15435,17 +16134,21 @@ import {
|
|
|
15435
16134
|
// src/session-kanban.ts
|
|
15436
16135
|
import {
|
|
15437
16136
|
deserializeTaskGraph,
|
|
16137
|
+
GlobalMailbox,
|
|
15438
16138
|
loadPlan,
|
|
15439
16139
|
loadTasks,
|
|
15440
16140
|
mutatePlan,
|
|
15441
16141
|
mutateTasks
|
|
15442
16142
|
} from "@wrongstack/core";
|
|
16143
|
+
import { resolveWstackPaths as resolveWstackPaths3 } from "@wrongstack/core/utils";
|
|
15443
16144
|
import {
|
|
15444
16145
|
createBoard,
|
|
15445
16146
|
getBoard,
|
|
16147
|
+
getKanbanDir,
|
|
15446
16148
|
listBoards,
|
|
15447
16149
|
removeBoard,
|
|
15448
16150
|
syncBoardFromTaskGraph,
|
|
16151
|
+
touchKanbanPresence,
|
|
15449
16152
|
updateBoard
|
|
15450
16153
|
} from "@wrongstack/kanban";
|
|
15451
16154
|
var SESSION_BOARD_TAG = "session-work";
|
|
@@ -15657,8 +16360,8 @@ function projectSessionPlanToKanban(projectRoot, items, sessionId) {
|
|
|
15657
16360
|
var kanbanTool = {
|
|
15658
16361
|
name: "kanban",
|
|
15659
16362
|
category: "Project",
|
|
15660
|
-
description: "Manage project-scoped multi-kanban boards stored under .wrongstack/kanbans.
|
|
15661
|
-
usageHint: "Use this for durable project kanban state.
|
|
16363
|
+
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.",
|
|
16364
|
+
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
16365
|
permission: "confirm",
|
|
15663
16366
|
mutating: true,
|
|
15664
16367
|
capabilities: ["fs.write"],
|
|
@@ -15695,6 +16398,7 @@ var kanbanTool = {
|
|
|
15695
16398
|
"transfer_task",
|
|
15696
16399
|
"get_task",
|
|
15697
16400
|
"update_task",
|
|
16401
|
+
"transition_task",
|
|
15698
16402
|
"move_task",
|
|
15699
16403
|
"delete_task",
|
|
15700
16404
|
"set_chain",
|
|
@@ -15725,6 +16429,7 @@ var kanbanTool = {
|
|
|
15725
16429
|
targetColumnId: { type: "string" },
|
|
15726
16430
|
title: { type: "string" },
|
|
15727
16431
|
description: { type: "string" },
|
|
16432
|
+
dueDate: { type: "string" },
|
|
15728
16433
|
tags: { type: "array", items: { type: "string" } },
|
|
15729
16434
|
labels: { type: "array", items: { type: "string" } },
|
|
15730
16435
|
priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
|
|
@@ -15771,6 +16476,18 @@ var kanbanTool = {
|
|
|
15771
16476
|
type: "string",
|
|
15772
16477
|
enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
|
|
15773
16478
|
},
|
|
16479
|
+
lifecycleStage: {
|
|
16480
|
+
type: "string",
|
|
16481
|
+
enum: ["backlog", "todo", "running", "review", "done"]
|
|
16482
|
+
},
|
|
16483
|
+
transitionAction: { type: "string" },
|
|
16484
|
+
transitionComment: { type: "string" },
|
|
16485
|
+
attachmentUrl: { type: "string" },
|
|
16486
|
+
attachmentTitle: { type: "string" },
|
|
16487
|
+
attachmentType: {
|
|
16488
|
+
type: "string",
|
|
16489
|
+
enum: ["issue", "pr", "doc", "commit", "design", "file", "url", "other"]
|
|
16490
|
+
},
|
|
15774
16491
|
releaseStatus: { type: "string", enum: ["pending", "ready", "blocked"] },
|
|
15775
16492
|
releaseReason: { type: "string" },
|
|
15776
16493
|
clearAssignee: { type: "boolean" },
|
|
@@ -15841,591 +16558,632 @@ var kanbanTool = {
|
|
|
15841
16558
|
async execute(input, ctx) {
|
|
15842
16559
|
const projectRoot = ctx.projectRoot;
|
|
15843
16560
|
if (!projectRoot) return fail("No project root is available.");
|
|
16561
|
+
const withPresence = async (result) => {
|
|
16562
|
+
const boardId = result.board?.id ?? input.boardId;
|
|
16563
|
+
if (!result.ok || !boardId || !ctx.session?.id || !ctx.agentId) return result;
|
|
16564
|
+
try {
|
|
16565
|
+
const board = await touchKanbanPresence2(projectRoot, boardId, {
|
|
16566
|
+
sessionId: ctx.session.id,
|
|
16567
|
+
agentId: ctx.agentId,
|
|
16568
|
+
agentName: ctx.agentName,
|
|
16569
|
+
taskId: input.taskId ?? result.task?.id,
|
|
16570
|
+
runTaskId: input.runTaskId
|
|
16571
|
+
});
|
|
16572
|
+
return board ? { ...result, board } : result;
|
|
16573
|
+
} catch {
|
|
16574
|
+
return result;
|
|
16575
|
+
}
|
|
16576
|
+
};
|
|
15844
16577
|
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);
|
|
16578
|
+
const result = await (async () => {
|
|
16579
|
+
switch (input.action) {
|
|
16580
|
+
case "list_boards": {
|
|
16581
|
+
const boards = await listBoards2(projectRoot);
|
|
16582
|
+
return { ok: true, message: `${boards.length} board(s).`, boards };
|
|
15903
16583
|
}
|
|
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.");
|
|
16584
|
+
case "get_board": {
|
|
16585
|
+
const board = await requireBoard(projectRoot, input.boardId);
|
|
16586
|
+
return board ? okBoard(board) : fail("Board not found.");
|
|
15936
16587
|
}
|
|
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
|
-
|
|
16588
|
+
case "create_board": {
|
|
16589
|
+
if (!input.title) return fail("create_board requires title.");
|
|
16590
|
+
const board = await createBoard2(projectRoot, {
|
|
16591
|
+
title: input.title,
|
|
16592
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16593
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
16594
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
|
|
16595
|
+
});
|
|
16596
|
+
return { ok: true, message: `Board created: ${board.title}`, board };
|
|
16597
|
+
}
|
|
16598
|
+
case "update_board": {
|
|
16599
|
+
if (!input.boardId) return fail("update_board requires boardId.");
|
|
16600
|
+
const board = await updateBoard2(projectRoot, input.boardId, {
|
|
16601
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16602
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16603
|
+
...input.tags !== void 0 ? { tags: input.tags } : {}
|
|
16604
|
+
});
|
|
16605
|
+
return board ? okBoard(board, "Board updated.") : fail("Board not found.");
|
|
16606
|
+
}
|
|
16607
|
+
case "duplicate_board": {
|
|
16608
|
+
if (!input.boardId) return fail("duplicate_board requires boardId.");
|
|
16609
|
+
const board = await duplicateBoard(projectRoot, input.boardId, {
|
|
16610
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16611
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
16612
|
+
...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
|
|
16613
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
16614
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
|
|
16615
|
+
});
|
|
16616
|
+
return board ? okBoard(board, "Board duplicated.") : fail("Board not found.");
|
|
16617
|
+
}
|
|
16618
|
+
case "delete_board": {
|
|
16619
|
+
if (!input.boardId) return fail("delete_board requires boardId.");
|
|
16620
|
+
const removed = await removeBoard2(projectRoot, input.boardId);
|
|
16621
|
+
return { ok: removed, message: removed ? "Board deleted." : "Board not found." };
|
|
16622
|
+
}
|
|
16623
|
+
case "generate_board": {
|
|
16624
|
+
if (!input.description) return fail("generate_board requires description.");
|
|
16625
|
+
const boardInput = generateBoardFromDescription({
|
|
16626
|
+
description: input.description,
|
|
16627
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16628
|
+
...input.context !== void 0 ? { context: input.context } : {},
|
|
16629
|
+
...input.columns !== void 0 ? { columns: input.columns } : {}
|
|
16630
|
+
});
|
|
16631
|
+
const board = await createBoard2(projectRoot, boardInput);
|
|
16632
|
+
for (const taskInput2 of parseLinesIntoTasks(
|
|
16633
|
+
input.description,
|
|
16634
|
+
board.columns[0]?.id ?? "backlog"
|
|
16635
|
+
)) {
|
|
16636
|
+
await addTask(projectRoot, board.id, taskInput2);
|
|
16637
|
+
}
|
|
16638
|
+
return okBoard(await getBoard2(projectRoot, board.id) ?? board, "Board generated.");
|
|
16639
|
+
}
|
|
16640
|
+
case "export_markdown": {
|
|
16641
|
+
const board = await requireBoard(projectRoot, input.boardId);
|
|
16642
|
+
if (!board) return fail("Board not found.");
|
|
16643
|
+
return {
|
|
16644
|
+
ok: true,
|
|
16645
|
+
message: "Board exported.",
|
|
16646
|
+
board,
|
|
16647
|
+
markdown: exportBoardAsMarkdown(board)
|
|
16648
|
+
};
|
|
16649
|
+
}
|
|
16650
|
+
case "export_task_graph": {
|
|
16651
|
+
if (!input.boardId) return fail("export_task_graph requires boardId.");
|
|
16652
|
+
const exported = await exportBoardToTaskGraph(projectRoot, input.boardId, {
|
|
16653
|
+
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
16654
|
+
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
16655
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16656
|
+
...input.preserveOriginTaskIds !== void 0 ? { preserveOriginTaskIds: input.preserveOriginTaskIds } : {},
|
|
16657
|
+
...input.includeArchived !== void 0 ? { includeArchived: input.includeArchived } : {}
|
|
16658
|
+
});
|
|
16659
|
+
if (!exported) return fail("Board not found.");
|
|
16660
|
+
return {
|
|
16661
|
+
ok: true,
|
|
16662
|
+
message: `Task graph exported with ${exported.graph.nodes.size} node(s).`,
|
|
16663
|
+
board: exported.board,
|
|
16664
|
+
taskGraph: serializeTaskGraph(exported.graph)
|
|
16665
|
+
};
|
|
16666
|
+
}
|
|
16667
|
+
case "sync_task_graph": {
|
|
16668
|
+
if (!input.boardId || !input.taskGraph) {
|
|
16669
|
+
return fail("sync_task_graph requires boardId and taskGraph.");
|
|
16670
|
+
}
|
|
16671
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
16672
|
+
const result2 = await syncBoardFromTaskGraph2(projectRoot, input.boardId, graph, {
|
|
16673
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16674
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16675
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
16676
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
16677
|
+
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
16678
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
16679
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
16680
|
+
...input.archiveMissingTasks !== void 0 ? { archiveMissingTasks: input.archiveMissingTasks } : {},
|
|
16681
|
+
...input.preserveManualDependencies !== void 0 ? { preserveManualDependencies: input.preserveManualDependencies } : {}
|
|
16682
|
+
});
|
|
16683
|
+
return result2 ? {
|
|
16684
|
+
ok: true,
|
|
16685
|
+
message: `Task graph synced: ${result2.createdTaskIds.length} created, ${result2.updatedTaskIds.length} updated, ${result2.archivedTaskIds.length} archived.`,
|
|
16686
|
+
board: result2.board
|
|
16687
|
+
} : fail("Board not found.");
|
|
16688
|
+
}
|
|
16689
|
+
case "create_from_graph": {
|
|
16690
|
+
if (!input.taskGraph) return fail("create_from_graph requires taskGraph.");
|
|
16691
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
16692
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
16693
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16694
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16695
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
16696
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
16697
|
+
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
16698
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
16699
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {}
|
|
16700
|
+
});
|
|
16701
|
+
return {
|
|
16702
|
+
ok: true,
|
|
16703
|
+
message: `Created board "${board.title}" from task graph with ${board.tasks.length} tasks.`,
|
|
16704
|
+
board
|
|
16705
|
+
};
|
|
16706
|
+
}
|
|
16707
|
+
case "import_session_tasks": {
|
|
16708
|
+
const taskPath = ctx.meta?.["task.path"];
|
|
16709
|
+
if (!taskPath) return fail("No session task file for this session.");
|
|
16710
|
+
const file = await loadTasks2(taskPath);
|
|
16711
|
+
if (!file || file.tasks.length === 0) return fail("No session tasks to import.");
|
|
16712
|
+
const sessionId = ctx.session?.id ?? file.sessionId ?? "session";
|
|
16713
|
+
const graph = deserializeTaskGraph2(taskFileToSerializedGraph(file.tasks, sessionId));
|
|
16714
|
+
const tags = ["session", `session:${sessionId}`];
|
|
16715
|
+
const existing = (await listBoards2(projectRoot)).find(
|
|
16716
|
+
(b) => b.tags?.includes(`session:${sessionId}`)
|
|
16717
|
+
);
|
|
16718
|
+
if (existing) {
|
|
16719
|
+
const result2 = await syncBoardFromTaskGraph2(projectRoot, existing.id, graph, {
|
|
16720
|
+
sourceSystem: "session",
|
|
16721
|
+
tags,
|
|
16722
|
+
archiveMissingTasks: true,
|
|
16723
|
+
includeCompletedTasks: true
|
|
16724
|
+
});
|
|
16725
|
+
return result2 ? {
|
|
16726
|
+
ok: true,
|
|
16727
|
+
message: `Synced ${file.tasks.length} session tasks into board "${result2.board.title}".`,
|
|
16728
|
+
board: result2.board
|
|
16729
|
+
} : fail("Session board vanished mid-sync.");
|
|
16730
|
+
}
|
|
16731
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
16732
|
+
title: `Session tasks (${sessionId.slice(0, 8)})`,
|
|
15986
16733
|
sourceSystem: "session",
|
|
15987
|
-
tags
|
|
15988
|
-
archiveMissingTasks: true,
|
|
15989
|
-
includeCompletedTasks: true
|
|
16734
|
+
tags
|
|
15990
16735
|
});
|
|
15991
|
-
return
|
|
16736
|
+
return {
|
|
15992
16737
|
ok: true,
|
|
15993
|
-
message: `
|
|
15994
|
-
board
|
|
15995
|
-
}
|
|
16738
|
+
message: `Imported ${file.tasks.length} session tasks into new board "${board.title}".`,
|
|
16739
|
+
board
|
|
16740
|
+
};
|
|
15996
16741
|
}
|
|
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.");
|
|
16742
|
+
case "search_tasks": {
|
|
16743
|
+
const tasks = await searchKanban(projectRoot, {
|
|
16744
|
+
query: input.query,
|
|
16745
|
+
boardId: input.boardId,
|
|
16746
|
+
assignedAgent: input.agentId,
|
|
16747
|
+
status: input.status,
|
|
16748
|
+
priority: input.priority,
|
|
16749
|
+
label: input.labels?.[0],
|
|
16750
|
+
chainId: input.chainId
|
|
16751
|
+
});
|
|
16752
|
+
return { ok: true, message: `${tasks.length} task(s) matched.`, tasks };
|
|
16082
16753
|
}
|
|
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.");
|
|
16754
|
+
case "ready_tasks": {
|
|
16755
|
+
const tasks = await listReadyTasks(projectRoot, {
|
|
16756
|
+
query: input.query,
|
|
16757
|
+
boardId: input.boardId,
|
|
16758
|
+
assignedAgent: input.agentId,
|
|
16759
|
+
priority: input.priority,
|
|
16760
|
+
label: input.labels?.[0],
|
|
16761
|
+
chainId: input.chainId,
|
|
16762
|
+
limit: input.limit
|
|
16763
|
+
});
|
|
16764
|
+
return { ok: true, message: `${tasks.length} ready task(s).`, tasks };
|
|
16105
16765
|
}
|
|
16106
|
-
|
|
16107
|
-
|
|
16108
|
-
|
|
16109
|
-
|
|
16110
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
16114
|
-
|
|
16115
|
-
|
|
16116
|
-
|
|
16117
|
-
|
|
16118
|
-
|
|
16766
|
+
case "snapshot": {
|
|
16767
|
+
const snapshot = await getKanbanOrchestrationSnapshot(projectRoot, {
|
|
16768
|
+
query: input.query,
|
|
16769
|
+
boardId: input.boardId,
|
|
16770
|
+
assignedAgent: input.agentId,
|
|
16771
|
+
status: input.status,
|
|
16772
|
+
priority: input.priority,
|
|
16773
|
+
label: input.labels?.[0],
|
|
16774
|
+
chainId: input.chainId
|
|
16775
|
+
});
|
|
16776
|
+
return {
|
|
16777
|
+
ok: true,
|
|
16778
|
+
message: `${snapshot.ready.length} ready, ${snapshot.running.length} running, ${snapshot.blocked.length} blocked.`,
|
|
16779
|
+
snapshot
|
|
16780
|
+
};
|
|
16119
16781
|
}
|
|
16120
|
-
|
|
16121
|
-
|
|
16122
|
-
input.boardId,
|
|
16123
|
-
|
|
16124
|
-
|
|
16125
|
-
|
|
16126
|
-
|
|
16127
|
-
|
|
16128
|
-
|
|
16129
|
-
|
|
16782
|
+
case "add_column": {
|
|
16783
|
+
if (!input.boardId || !input.title) return fail("add_column requires boardId and title.");
|
|
16784
|
+
const result2 = await addColumn(projectRoot, input.boardId, {
|
|
16785
|
+
title: input.title,
|
|
16786
|
+
...input.description !== void 0 ? { description: input.description } : {}
|
|
16787
|
+
});
|
|
16788
|
+
return result2 ? okBoard(result2.board, "Column added.") : fail("Board not found.");
|
|
16789
|
+
}
|
|
16790
|
+
case "update_column": {
|
|
16791
|
+
if (!input.boardId || !input.columnId)
|
|
16792
|
+
return fail("update_column requires boardId and columnId.");
|
|
16793
|
+
const board = await updateColumn(projectRoot, input.boardId, input.columnId, {
|
|
16794
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16795
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16796
|
+
...input.order !== void 0 ? { order: input.order } : {}
|
|
16797
|
+
});
|
|
16798
|
+
return board ? okBoard(board, "Column updated.") : fail("Column not found.");
|
|
16799
|
+
}
|
|
16800
|
+
case "delete_column": {
|
|
16801
|
+
if (!input.boardId || !input.columnId)
|
|
16802
|
+
return fail("delete_column requires boardId and columnId.");
|
|
16803
|
+
const board = await removeColumn(projectRoot, input.boardId, input.columnId, {
|
|
16804
|
+
moveTasksToColumnId: input.moveTasksToColumnId
|
|
16805
|
+
});
|
|
16806
|
+
return board ? okBoard(board, "Column deleted.") : fail("Column not found.");
|
|
16807
|
+
}
|
|
16808
|
+
case "add_task": {
|
|
16809
|
+
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
16810
|
+
const result2 = await addTask(projectRoot, input.boardId, taskInput(input));
|
|
16811
|
+
return result2 ? okTask(result2.board, result2.task, "Task added.") : fail("Board not found.");
|
|
16812
|
+
}
|
|
16813
|
+
case "split_task": {
|
|
16814
|
+
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
16815
|
+
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
16130
16816
|
}
|
|
16131
|
-
|
|
16132
|
-
|
|
16133
|
-
|
|
16134
|
-
|
|
16135
|
-
|
|
16136
|
-
|
|
16817
|
+
const result2 = await splitTask(projectRoot, input.boardId, input.taskId, {
|
|
16818
|
+
titles: input.childTitles,
|
|
16819
|
+
...input.targetColumnId !== void 0 ? { columnId: input.targetColumnId } : {},
|
|
16820
|
+
...input.inheritAssignment !== void 0 ? { inheritAssignment: input.inheritAssignment } : {},
|
|
16821
|
+
...input.inheritLabels !== void 0 ? { inheritLabels: input.inheritLabels } : {},
|
|
16822
|
+
...input.inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria: input.inheritSuccessCriteria } : {},
|
|
16823
|
+
...input.inheritGoalMetrics !== void 0 ? { inheritGoalMetrics: input.inheritGoalMetrics } : {},
|
|
16824
|
+
...input.inheritDependencies !== void 0 ? { inheritDependencies: input.inheritDependencies } : {},
|
|
16825
|
+
...input.chainChildren !== void 0 ? { chainChildren: input.chainChildren } : {},
|
|
16826
|
+
...input.rewireDependents !== void 0 ? { rewireDependents: input.rewireDependents } : {}
|
|
16827
|
+
});
|
|
16828
|
+
return result2 ? {
|
|
16829
|
+
ok: true,
|
|
16830
|
+
message: `${result2.children.length} child task(s) created.`,
|
|
16831
|
+
board: result2.board,
|
|
16832
|
+
task: result2.parent,
|
|
16833
|
+
children: result2.children
|
|
16834
|
+
} : fail("Task not found.");
|
|
16137
16835
|
}
|
|
16138
|
-
|
|
16139
|
-
|
|
16140
|
-
|
|
16141
|
-
|
|
16142
|
-
input.
|
|
16143
|
-
|
|
16836
|
+
case "merge_tasks": {
|
|
16837
|
+
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
16838
|
+
return fail("merge_tasks requires boardId, taskIds, and title.");
|
|
16839
|
+
}
|
|
16840
|
+
const result2 = await mergeTasks(projectRoot, input.boardId, {
|
|
16841
|
+
taskIds: input.taskIds,
|
|
16842
|
+
title: input.title,
|
|
16843
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16144
16844
|
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
16145
|
-
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
16146
16845
|
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
16147
|
-
...input.
|
|
16846
|
+
...input.closeSourceTasks !== void 0 ? { closeSourceTasks: input.closeSourceTasks } : {}
|
|
16847
|
+
});
|
|
16848
|
+
return result2 ? okTask(result2.board, result2.task, "Tasks merged.") : fail("Board or task not found.");
|
|
16849
|
+
}
|
|
16850
|
+
case "copy_task": {
|
|
16851
|
+
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
16852
|
+
return fail("copy_task requires boardId, taskId, and targetBoardId.");
|
|
16148
16853
|
}
|
|
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.");
|
|
16854
|
+
const result2 = await copyTaskToBoard(
|
|
16855
|
+
projectRoot,
|
|
16856
|
+
input.boardId,
|
|
16857
|
+
input.taskId,
|
|
16858
|
+
input.targetBoardId,
|
|
16859
|
+
{
|
|
16860
|
+
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
16861
|
+
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
16862
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
16863
|
+
...input.preserveDependencies !== void 0 ? { preserveDependencies: input.preserveDependencies } : {}
|
|
16864
|
+
}
|
|
16865
|
+
);
|
|
16866
|
+
return result2 ? okTask(result2.targetBoard, result2.task, "Task copied to target board.") : fail("Board or task not found.");
|
|
16171
16867
|
}
|
|
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.");
|
|
16868
|
+
case "transfer_task": {
|
|
16869
|
+
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
16870
|
+
return fail("transfer_task requires boardId, taskId, and targetBoardId.");
|
|
16871
|
+
}
|
|
16872
|
+
const result2 = await transferTaskToBoard(
|
|
16873
|
+
projectRoot,
|
|
16874
|
+
input.boardId,
|
|
16875
|
+
input.taskId,
|
|
16876
|
+
input.targetBoardId,
|
|
16877
|
+
{
|
|
16878
|
+
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
16879
|
+
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
16880
|
+
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
16881
|
+
...input.preserveDependencies !== void 0 ? { preserveDependencies: input.preserveDependencies } : {}
|
|
16882
|
+
}
|
|
16883
|
+
);
|
|
16884
|
+
return result2 ? okTask(result2.targetBoard, result2.task, "Task transferred to target board.") : fail("Board or task not found.");
|
|
16190
16885
|
}
|
|
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.");
|
|
16886
|
+
case "get_task": {
|
|
16887
|
+
if (!input.boardId || !input.taskId) return fail("get_task requires boardId and taskId.");
|
|
16888
|
+
const task = await getTask(projectRoot, input.boardId, input.taskId);
|
|
16889
|
+
return task ? { ok: true, message: "Task loaded.", task } : fail("Task not found.");
|
|
16206
16890
|
}
|
|
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.");
|
|
16891
|
+
case "update_task": {
|
|
16892
|
+
if (!input.boardId || !input.taskId)
|
|
16893
|
+
return fail("update_task requires boardId and taskId.");
|
|
16894
|
+
const board = await updateTask(
|
|
16895
|
+
projectRoot,
|
|
16896
|
+
input.boardId,
|
|
16897
|
+
input.taskId,
|
|
16898
|
+
taskPatch(input)
|
|
16899
|
+
);
|
|
16900
|
+
return board ? okBoard(board, "Task updated.") : fail("Task not found.");
|
|
16231
16901
|
}
|
|
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.");
|
|
16902
|
+
case "transition_task": {
|
|
16903
|
+
if (!input.boardId || !input.taskId || !input.lifecycleStage || !input.author || !input.transitionComment) {
|
|
16904
|
+
return fail(
|
|
16905
|
+
"transition_task requires boardId, taskId, lifecycleStage, author, and transitionComment."
|
|
16906
|
+
);
|
|
16907
|
+
}
|
|
16908
|
+
const result2 = await transitionTask(projectRoot, input.boardId, input.taskId, {
|
|
16909
|
+
to: input.lifecycleStage,
|
|
16910
|
+
actor: input.author,
|
|
16911
|
+
comment: input.transitionComment,
|
|
16912
|
+
...input.transitionAction !== void 0 ? { action: input.transitionAction } : {},
|
|
16913
|
+
...input.attachmentUrl !== void 0 ? {
|
|
16914
|
+
attachment: {
|
|
16915
|
+
url: input.attachmentUrl,
|
|
16916
|
+
type: input.attachmentType ?? "url",
|
|
16917
|
+
...input.attachmentTitle !== void 0 ? { title: input.attachmentTitle } : {}
|
|
16918
|
+
}
|
|
16919
|
+
} : {},
|
|
16920
|
+
patch: taskPatch(input)
|
|
16921
|
+
});
|
|
16922
|
+
return result2 ? okTask(result2.board, result2.task, `Task advanced to ${result2.transition.to}.`) : fail("Board or task not found.");
|
|
16273
16923
|
}
|
|
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.");
|
|
16924
|
+
case "move_task": {
|
|
16925
|
+
if (!input.boardId || !input.taskId || !input.targetColumnId) {
|
|
16926
|
+
return fail("move_task requires boardId, taskId, and targetColumnId.");
|
|
16927
|
+
}
|
|
16928
|
+
const board = await moveTask(
|
|
16929
|
+
projectRoot,
|
|
16930
|
+
input.boardId,
|
|
16931
|
+
input.taskId,
|
|
16932
|
+
input.targetColumnId,
|
|
16933
|
+
input.order
|
|
16934
|
+
);
|
|
16935
|
+
return board ? okBoard(board, "Task moved.") : fail("Move failed.");
|
|
16337
16936
|
}
|
|
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.");
|
|
16937
|
+
case "delete_task": {
|
|
16938
|
+
if (!input.boardId || !input.taskId)
|
|
16939
|
+
return fail("delete_task requires boardId and taskId.");
|
|
16940
|
+
const board = await removeTask(projectRoot, input.boardId, input.taskId);
|
|
16941
|
+
return board ? okBoard(board, "Task deleted.") : fail("Task not found.");
|
|
16349
16942
|
}
|
|
16350
|
-
|
|
16351
|
-
|
|
16352
|
-
|
|
16353
|
-
|
|
16354
|
-
|
|
16355
|
-
|
|
16356
|
-
|
|
16357
|
-
|
|
16358
|
-
|
|
16359
|
-
|
|
16360
|
-
|
|
16361
|
-
|
|
16362
|
-
|
|
16943
|
+
case "set_chain": {
|
|
16944
|
+
if (!input.boardId || !input.taskIds?.length) {
|
|
16945
|
+
return fail("set_chain requires boardId and taskIds.");
|
|
16946
|
+
}
|
|
16947
|
+
const result2 = await setTaskChain(projectRoot, input.boardId, {
|
|
16948
|
+
taskIds: input.taskIds,
|
|
16949
|
+
...input.chainId !== void 0 ? { chainId: input.chainId } : {},
|
|
16950
|
+
...input.enforceDependencies !== void 0 ? { enforceDependencies: input.enforceDependencies } : {}
|
|
16951
|
+
});
|
|
16952
|
+
return result2 ? {
|
|
16953
|
+
ok: true,
|
|
16954
|
+
message: `Chain set: ${result2.chainId}`,
|
|
16955
|
+
board: result2.board,
|
|
16956
|
+
chain: result2.tasks
|
|
16957
|
+
} : fail("Board or task not found.");
|
|
16363
16958
|
}
|
|
16364
|
-
|
|
16365
|
-
|
|
16366
|
-
|
|
16367
|
-
|
|
16368
|
-
|
|
16369
|
-
|
|
16370
|
-
|
|
16959
|
+
case "get_chain": {
|
|
16960
|
+
if (!input.boardId || !(input.taskId || input.chainId)) {
|
|
16961
|
+
return fail("get_chain requires boardId and taskId or chainId.");
|
|
16962
|
+
}
|
|
16963
|
+
const result2 = await getTaskChain(
|
|
16964
|
+
projectRoot,
|
|
16965
|
+
input.boardId,
|
|
16966
|
+
input.taskId ?? input.chainId ?? ""
|
|
16967
|
+
);
|
|
16968
|
+
return result2 ? {
|
|
16969
|
+
ok: true,
|
|
16970
|
+
message: `Chain loaded: ${result2.chainId}`,
|
|
16971
|
+
board: result2.board,
|
|
16972
|
+
chain: result2.tasks
|
|
16973
|
+
} : fail("Chain not found.");
|
|
16974
|
+
}
|
|
16975
|
+
case "claim_task": {
|
|
16976
|
+
const result2 = await claimReadyTask(projectRoot, {
|
|
16977
|
+
...input.boardId !== void 0 ? { boardId: input.boardId } : {},
|
|
16978
|
+
...input.taskId !== void 0 ? { taskId: input.taskId } : {},
|
|
16979
|
+
...assignmentInput(input),
|
|
16980
|
+
status: input.assignmentStatus ?? "queued"
|
|
16981
|
+
});
|
|
16982
|
+
return result2 ? okTask(result2.board, result2.task, "Task claimed.") : fail("No ready kanban task matched the claim.");
|
|
16983
|
+
}
|
|
16984
|
+
case "release_task": {
|
|
16985
|
+
if (!input.boardId || !input.taskId) {
|
|
16986
|
+
return fail("release_task requires boardId and taskId.");
|
|
16987
|
+
}
|
|
16988
|
+
const board = await releaseTaskClaim(projectRoot, input.boardId, input.taskId, {
|
|
16989
|
+
...input.releaseStatus !== void 0 ? { status: input.releaseStatus } : {},
|
|
16990
|
+
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
16991
|
+
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {}
|
|
16992
|
+
});
|
|
16993
|
+
return board ? okBoard(board, "Task claim released.") : fail("Task not found.");
|
|
16994
|
+
}
|
|
16995
|
+
case "assign_task": {
|
|
16996
|
+
if (!input.boardId || !input.taskId)
|
|
16997
|
+
return fail("assign_task requires boardId and taskId.");
|
|
16998
|
+
const board = await assignTask(
|
|
16999
|
+
projectRoot,
|
|
17000
|
+
input.boardId,
|
|
17001
|
+
input.taskId,
|
|
17002
|
+
assignmentInput(input)
|
|
17003
|
+
);
|
|
17004
|
+
return board ? okBoard(board, "Task assigned.") : fail("Task not found.");
|
|
17005
|
+
}
|
|
17006
|
+
case "mark_assignment": {
|
|
17007
|
+
if (!input.boardId || !input.taskId)
|
|
17008
|
+
return fail("mark_assignment requires boardId and taskId.");
|
|
17009
|
+
const assignmentStatus = input.assignmentStatus ?? (input.status === "completed" ? "completed" : input.error ? "failed" : void 0);
|
|
17010
|
+
const board = await updateTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
17011
|
+
...assignmentStatus !== void 0 ? { status: assignmentStatus } : {},
|
|
17012
|
+
...input.subagentId !== void 0 ? { subagentId: input.subagentId } : {},
|
|
17013
|
+
...input.runTaskId !== void 0 ? { runTaskId: input.runTaskId } : {},
|
|
17014
|
+
...input.lastResult !== void 0 ? { lastResult: input.lastResult } : {},
|
|
17015
|
+
...input.error !== void 0 ? { error: input.error } : {},
|
|
17016
|
+
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
17017
|
+
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
17018
|
+
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
17019
|
+
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
17020
|
+
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
17021
|
+
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
17022
|
+
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {}
|
|
17023
|
+
});
|
|
17024
|
+
return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
|
|
17025
|
+
}
|
|
17026
|
+
case "heartbeat_assignment": {
|
|
17027
|
+
if (!input.boardId || !input.taskId) {
|
|
17028
|
+
return fail("heartbeat_assignment requires boardId and taskId.");
|
|
17029
|
+
}
|
|
17030
|
+
const board = await heartbeatTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
17031
|
+
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
17032
|
+
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {}
|
|
17033
|
+
});
|
|
17034
|
+
return board ? okBoard(board, "Assignment heartbeat updated.") : fail("Task assignment not found.");
|
|
17035
|
+
}
|
|
17036
|
+
case "recover_stale": {
|
|
17037
|
+
if (!input.boardId) return fail("recover_stale requires boardId.");
|
|
17038
|
+
const policyFields = [
|
|
17039
|
+
input.recoveryPolicyFailOnCostCeiling !== void 0,
|
|
17040
|
+
input.recoveryPolicyReleaseOnFailureKinds !== void 0,
|
|
17041
|
+
input.recoveryPolicyReleaseOnHeartbeatDue !== void 0,
|
|
17042
|
+
input.recoveryPolicyRetryPolicyOverride !== void 0
|
|
17043
|
+
].some(Boolean);
|
|
17044
|
+
const result2 = await recoverStaleTaskAssignments(projectRoot, input.boardId, {
|
|
17045
|
+
...input.recoveryMode !== void 0 ? { mode: input.recoveryMode } : {},
|
|
17046
|
+
...input.recoveryNow !== void 0 ? { now: input.recoveryNow } : {},
|
|
17047
|
+
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
17048
|
+
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {},
|
|
17049
|
+
...policyFields ? {
|
|
17050
|
+
policy: {
|
|
17051
|
+
...input.recoveryPolicyFailOnCostCeiling !== void 0 ? { failWhenCostCeilingSet: input.recoveryPolicyFailOnCostCeiling } : {},
|
|
17052
|
+
...input.recoveryPolicyReleaseOnFailureKinds !== void 0 ? {
|
|
17053
|
+
releaseOnFailureKinds: input.recoveryPolicyReleaseOnFailureKinds
|
|
17054
|
+
} : {},
|
|
17055
|
+
...input.recoveryPolicyReleaseOnHeartbeatDue !== void 0 ? {
|
|
17056
|
+
releaseOnHeartbeatDue: input.recoveryPolicyReleaseOnHeartbeatDue
|
|
17057
|
+
} : {},
|
|
17058
|
+
...input.recoveryPolicyRetryPolicyOverride !== void 0 ? {
|
|
17059
|
+
retryPolicyOverride: input.recoveryPolicyRetryPolicyOverride
|
|
17060
|
+
} : {}
|
|
17061
|
+
}
|
|
17062
|
+
} : {}
|
|
17063
|
+
});
|
|
17064
|
+
return result2 ? {
|
|
17065
|
+
ok: true,
|
|
17066
|
+
message: `Recovered ${result2.tasks.length} stale assignment(s).`,
|
|
17067
|
+
board: result2.board,
|
|
17068
|
+
recoveredTasks: result2.tasks
|
|
17069
|
+
} : { ok: true, message: "No stale assignment matched.", recoveredTasks: [] };
|
|
17070
|
+
}
|
|
17071
|
+
case "events": {
|
|
17072
|
+
if (!input.boardId) return fail("events requires boardId.");
|
|
17073
|
+
const eventList = await listKanbanEvents(projectRoot, input.boardId);
|
|
17074
|
+
return {
|
|
17075
|
+
ok: true,
|
|
17076
|
+
message: `${eventList.length} event(s).`,
|
|
17077
|
+
events: eventList
|
|
17078
|
+
};
|
|
17079
|
+
}
|
|
17080
|
+
case "queue_health": {
|
|
17081
|
+
const health = await getKanbanQueueHealth(projectRoot, {
|
|
17082
|
+
...input.boardId !== void 0 ? { boardId: input.boardId } : {}
|
|
17083
|
+
});
|
|
17084
|
+
return {
|
|
17085
|
+
ok: true,
|
|
17086
|
+
message: `Counts: ready=${health.counts.ready}, running=${health.counts.running}, stale=${health.staleAssignments.count}.`,
|
|
17087
|
+
queueHealth: health
|
|
17088
|
+
};
|
|
17089
|
+
}
|
|
17090
|
+
case "add_dependency": {
|
|
17091
|
+
if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
|
|
17092
|
+
return fail("add_dependency requires boardId, taskId, and dependencyTaskId.");
|
|
17093
|
+
}
|
|
17094
|
+
const board = await addDependency(
|
|
17095
|
+
projectRoot,
|
|
17096
|
+
input.boardId,
|
|
17097
|
+
input.taskId,
|
|
17098
|
+
input.dependencyTaskId
|
|
17099
|
+
);
|
|
17100
|
+
return board ? okBoard(board, "Dependency added.") : fail("Task not found.");
|
|
17101
|
+
}
|
|
17102
|
+
case "add_goal_metric": {
|
|
17103
|
+
if (!input.boardId || !input.taskId || !input.metricName) {
|
|
17104
|
+
return fail("add_goal_metric requires boardId, taskId, and metricName.");
|
|
17105
|
+
}
|
|
17106
|
+
const board = await addGoalMetricToTask(projectRoot, input.boardId, input.taskId, {
|
|
17107
|
+
name: input.metricName,
|
|
16371
17108
|
...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
|
|
16372
17109
|
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
16373
17110
|
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
16374
17111
|
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
16375
17112
|
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17113
|
+
});
|
|
17114
|
+
return board ? okBoard(board, "Goal metric added.") : fail("Task not found.");
|
|
17115
|
+
}
|
|
17116
|
+
case "update_goal_metric": {
|
|
17117
|
+
if (!input.boardId || !input.taskId || !input.metricId) {
|
|
17118
|
+
return fail("update_goal_metric requires boardId, taskId, and metricId.");
|
|
16376
17119
|
}
|
|
16377
|
-
|
|
16378
|
-
|
|
16379
|
-
|
|
16380
|
-
|
|
16381
|
-
|
|
16382
|
-
|
|
17120
|
+
const board = await updateGoalMetricOnTask(
|
|
17121
|
+
projectRoot,
|
|
17122
|
+
input.boardId,
|
|
17123
|
+
input.taskId,
|
|
17124
|
+
input.metricId,
|
|
17125
|
+
{
|
|
17126
|
+
...input.metricName !== void 0 ? { name: input.metricName } : {},
|
|
17127
|
+
...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
|
|
17128
|
+
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
17129
|
+
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
17130
|
+
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
17131
|
+
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17132
|
+
}
|
|
17133
|
+
);
|
|
17134
|
+
return board ? okBoard(board, "Goal metric updated.") : fail("Metric not found.");
|
|
16383
17135
|
}
|
|
16384
|
-
|
|
16385
|
-
|
|
16386
|
-
|
|
16387
|
-
|
|
16388
|
-
|
|
16389
|
-
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
|
|
16393
|
-
return
|
|
17136
|
+
case "add_check": {
|
|
17137
|
+
if (!input.boardId || !input.taskId || !input.checkDescription) {
|
|
17138
|
+
return fail("add_check requires boardId, taskId, and checkDescription.");
|
|
17139
|
+
}
|
|
17140
|
+
const board = await addCheckToTask(projectRoot, input.boardId, input.taskId, {
|
|
17141
|
+
description: input.checkDescription,
|
|
17142
|
+
type: "manual",
|
|
17143
|
+
status: input.checkStatus
|
|
17144
|
+
});
|
|
17145
|
+
return board ? okBoard(board, "Check added.") : fail("Task not found.");
|
|
16394
17146
|
}
|
|
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 } : {}
|
|
17147
|
+
case "update_check": {
|
|
17148
|
+
if (!input.boardId || !input.taskId || !input.checkId) {
|
|
17149
|
+
return fail("update_check requires boardId, taskId, and checkId.");
|
|
16403
17150
|
}
|
|
16404
|
-
|
|
16405
|
-
|
|
16406
|
-
|
|
16407
|
-
|
|
16408
|
-
|
|
16409
|
-
|
|
16410
|
-
|
|
16411
|
-
|
|
16412
|
-
|
|
16413
|
-
|
|
16414
|
-
|
|
16415
|
-
|
|
16416
|
-
|
|
16417
|
-
|
|
16418
|
-
|
|
16419
|
-
|
|
16420
|
-
|
|
16421
|
-
|
|
16422
|
-
|
|
16423
|
-
|
|
16424
|
-
|
|
17151
|
+
const board = await updateCheckOnTask(
|
|
17152
|
+
projectRoot,
|
|
17153
|
+
input.boardId,
|
|
17154
|
+
input.taskId,
|
|
17155
|
+
input.checkId,
|
|
17156
|
+
{
|
|
17157
|
+
...input.checkDescription !== void 0 ? { description: input.checkDescription } : {},
|
|
17158
|
+
...input.checkStatus !== void 0 ? { status: input.checkStatus } : {}
|
|
17159
|
+
}
|
|
17160
|
+
);
|
|
17161
|
+
return board ? okBoard(board, "Check updated.") : fail("Check not found.");
|
|
17162
|
+
}
|
|
17163
|
+
case "add_note": {
|
|
17164
|
+
if (!input.boardId || !input.taskId || !input.note)
|
|
17165
|
+
return fail("add_note requires boardId, taskId, and note.");
|
|
17166
|
+
const board = await addNoteToTask(projectRoot, input.boardId, input.taskId, {
|
|
17167
|
+
author: input.author ?? "agent",
|
|
17168
|
+
content: input.note
|
|
17169
|
+
});
|
|
17170
|
+
return board ? okBoard(board, "Note added.") : fail("Task not found.");
|
|
17171
|
+
}
|
|
17172
|
+
case "add_link": {
|
|
17173
|
+
if (!input.boardId || !input.taskId || !input.url)
|
|
17174
|
+
return fail("add_link requires boardId, taskId, and url.");
|
|
17175
|
+
const board = await addLinkToTask(projectRoot, input.boardId, input.taskId, {
|
|
17176
|
+
url: input.url,
|
|
17177
|
+
type: input.linkType ?? "url",
|
|
17178
|
+
...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
|
|
17179
|
+
});
|
|
17180
|
+
return board ? okBoard(board, "Link added.") : fail("Task not found.");
|
|
17181
|
+
}
|
|
17182
|
+
default:
|
|
17183
|
+
return fail(`Unknown kanban action: ${input.action}`);
|
|
16425
17184
|
}
|
|
16426
|
-
|
|
16427
|
-
|
|
16428
|
-
}
|
|
17185
|
+
})();
|
|
17186
|
+
return withPresence(result);
|
|
16429
17187
|
} catch (err) {
|
|
16430
17188
|
return fail(err instanceof Error ? err.message : String(err));
|
|
16431
17189
|
}
|
|
@@ -16449,6 +17207,7 @@ function taskInput(input) {
|
|
|
16449
17207
|
title: input.title ?? "",
|
|
16450
17208
|
columnId: input.columnId,
|
|
16451
17209
|
description: input.description,
|
|
17210
|
+
dueDate: input.dueDate,
|
|
16452
17211
|
priority: input.priority,
|
|
16453
17212
|
...input.taskType !== void 0 ? { type: input.taskType } : {},
|
|
16454
17213
|
status: input.status,
|
|
@@ -16457,7 +17216,67 @@ function taskInput(input) {
|
|
|
16457
17216
|
...input.assignee ?? assignment?.name ?? assignment?.agentId ? { assignee: input.assignee ?? assignment?.name ?? assignment?.agentId } : {},
|
|
16458
17217
|
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
16459
17218
|
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
|
|
16460
|
-
...
|
|
17219
|
+
...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {},
|
|
17220
|
+
...assignment ? { assignment } : {},
|
|
17221
|
+
...input.order !== void 0 ? { order: input.order } : {},
|
|
17222
|
+
// Sprint 3 durable task-level fields
|
|
17223
|
+
...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
|
|
17224
|
+
...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
|
|
17225
|
+
// Relationships — chainId alone cannot construct a KanbanTaskChainRef,
|
|
17226
|
+
// so chain/previousTaskId/nextTaskId must be set via update_task.
|
|
17227
|
+
...input.childTitles !== void 0 ? { childTaskIds: input.childTitles } : {},
|
|
17228
|
+
// Detail objects
|
|
17229
|
+
...input.checkDescription !== void 0 ? {
|
|
17230
|
+
successCriteria: [
|
|
17231
|
+
{
|
|
17232
|
+
id: randomUUID(),
|
|
17233
|
+
description: input.checkDescription,
|
|
17234
|
+
type: "manual",
|
|
17235
|
+
status: input.checkStatus ?? "pending"
|
|
17236
|
+
}
|
|
17237
|
+
]
|
|
17238
|
+
} : {},
|
|
17239
|
+
...input.metricName !== void 0 ? {
|
|
17240
|
+
goalMetrics: [
|
|
17241
|
+
{
|
|
17242
|
+
id: randomUUID(),
|
|
17243
|
+
name: input.metricName,
|
|
17244
|
+
status: input.metricStatus ?? "pending",
|
|
17245
|
+
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
17246
|
+
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
17247
|
+
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
17248
|
+
...input.metricNotes !== void 0 ? { notes: input.metricNotes } : {}
|
|
17249
|
+
}
|
|
17250
|
+
]
|
|
17251
|
+
} : {},
|
|
17252
|
+
...input.url !== void 0 ? {
|
|
17253
|
+
links: [
|
|
17254
|
+
{
|
|
17255
|
+
url: input.url,
|
|
17256
|
+
type: input.linkType ?? "url",
|
|
17257
|
+
...input.linkTitle !== void 0 ? { title: input.linkTitle } : {}
|
|
17258
|
+
}
|
|
17259
|
+
]
|
|
17260
|
+
} : {},
|
|
17261
|
+
...input.note !== void 0 ? {
|
|
17262
|
+
notes: [
|
|
17263
|
+
{
|
|
17264
|
+
id: randomUUID(),
|
|
17265
|
+
author: input.author ?? "agent",
|
|
17266
|
+
content: input.note,
|
|
17267
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17268
|
+
}
|
|
17269
|
+
]
|
|
17270
|
+
} : {},
|
|
17271
|
+
// Origin tracking
|
|
17272
|
+
...input.graphId !== void 0 ? {
|
|
17273
|
+
origin: {
|
|
17274
|
+
system: input.sourceSystem ?? "kanban-tool",
|
|
17275
|
+
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
17276
|
+
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
17277
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {}
|
|
17278
|
+
}
|
|
17279
|
+
} : {}
|
|
16461
17280
|
};
|
|
16462
17281
|
}
|
|
16463
17282
|
function mergedDependsOn(input) {
|
|
@@ -16471,6 +17290,7 @@ function taskPatch(input) {
|
|
|
16471
17290
|
return {
|
|
16472
17291
|
title: input.title,
|
|
16473
17292
|
description: input.description,
|
|
17293
|
+
dueDate: input.dueDate,
|
|
16474
17294
|
columnId: input.columnId,
|
|
16475
17295
|
order: input.order,
|
|
16476
17296
|
priority: input.priority,
|
|
@@ -17232,7 +18052,7 @@ import {
|
|
|
17232
18052
|
mutateTasks as mutateTasks2,
|
|
17233
18053
|
formatTaskList
|
|
17234
18054
|
} from "@wrongstack/core";
|
|
17235
|
-
import { randomUUID } from "node:crypto";
|
|
18055
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
17236
18056
|
var planTool = {
|
|
17237
18057
|
name: "plan",
|
|
17238
18058
|
category: "Session",
|
|
@@ -17458,7 +18278,7 @@ var planTool = {
|
|
|
17458
18278
|
try {
|
|
17459
18279
|
const taskFile = await mutateTasks2(taskPath, sessionId, (f) => {
|
|
17460
18280
|
f.tasks.push({
|
|
17461
|
-
id: `task_${
|
|
18281
|
+
id: `task_${randomUUID2()}`,
|
|
17462
18282
|
title: taskifyMeta.title,
|
|
17463
18283
|
description: taskifyMeta.details || void 0,
|
|
17464
18284
|
type: "feature",
|
|
@@ -18600,7 +19420,7 @@ import {
|
|
|
18600
19420
|
mutatePlan as mutatePlan3,
|
|
18601
19421
|
formatPlan as formatPlan2
|
|
18602
19422
|
} from "@wrongstack/core";
|
|
18603
|
-
import { randomUUID as
|
|
19423
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
18604
19424
|
function findTaskIndex(tasks, query) {
|
|
18605
19425
|
const asNum = Number.parseInt(query, 10);
|
|
18606
19426
|
if (!Number.isNaN(asNum)) {
|
|
@@ -18788,7 +19608,7 @@ var taskTool = {
|
|
|
18788
19608
|
}
|
|
18789
19609
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18790
19610
|
const newTask = {
|
|
18791
|
-
id: `task_${Date.now()}_${
|
|
19611
|
+
id: `task_${Date.now()}_${randomUUID3().slice(0, 8)}`,
|
|
18792
19612
|
title: t.title,
|
|
18793
19613
|
description: t.description,
|
|
18794
19614
|
type: t.type || "feature",
|
|
@@ -18849,7 +19669,7 @@ var taskTool = {
|
|
|
18849
19669
|
});
|
|
18850
19670
|
if (match.description) {
|
|
18851
19671
|
todos.push({
|
|
18852
|
-
id: `todo_${ts2}_${
|
|
19672
|
+
id: `todo_${ts2}_${randomUUID3().slice(0, 6)}`,
|
|
18853
19673
|
content: match.description.slice(0, 200),
|
|
18854
19674
|
status: "pending",
|
|
18855
19675
|
promotedFromTask: match.id
|
|
@@ -18858,7 +19678,7 @@ var taskTool = {
|
|
|
18858
19678
|
if (input.subtasks && input.subtasks.length > 0) {
|
|
18859
19679
|
for (const st of input.subtasks) {
|
|
18860
19680
|
todos.push({
|
|
18861
|
-
id: `todo_${ts2}_${
|
|
19681
|
+
id: `todo_${ts2}_${randomUUID3().slice(0, 6)}`,
|
|
18862
19682
|
content: st,
|
|
18863
19683
|
status: "pending",
|
|
18864
19684
|
promotedFromTask: match.id
|
|
@@ -19564,8 +20384,8 @@ var DEFAULT_IGNORE5 = [
|
|
|
19564
20384
|
var treeTool = {
|
|
19565
20385
|
name: "tree",
|
|
19566
20386
|
category: "Filesystem",
|
|
19567
|
-
description: "Display a
|
|
19568
|
-
usageHint: "
|
|
20387
|
+
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.",
|
|
20388
|
+
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
20389
|
permission: "auto",
|
|
19570
20390
|
mutating: false,
|
|
19571
20391
|
capabilities: ["fs.read"],
|
|
@@ -19968,9 +20788,6 @@ var OPTIONAL_TOOLS = [
|
|
|
19968
20788
|
toolUseTool,
|
|
19969
20789
|
batchToolUseTool,
|
|
19970
20790
|
toolHelpTool,
|
|
19971
|
-
codebaseIndexTool,
|
|
19972
|
-
codebaseSearchTool,
|
|
19973
|
-
codebaseStatsTool,
|
|
19974
20791
|
setWorkingDirTool
|
|
19975
20792
|
];
|
|
19976
20793
|
var builtinTools = [
|
|
@@ -19979,6 +20796,9 @@ var builtinTools = [
|
|
|
19979
20796
|
readTool,
|
|
19980
20797
|
writeTool,
|
|
19981
20798
|
editTool,
|
|
20799
|
+
codebaseStatsTool,
|
|
20800
|
+
codebaseSearchTool,
|
|
20801
|
+
codebaseIndexTool,
|
|
19982
20802
|
replaceTool,
|
|
19983
20803
|
globTool,
|
|
19984
20804
|
grepTool,
|
|
@@ -20013,9 +20833,6 @@ var builtinTools = [
|
|
|
20013
20833
|
toolUseTool,
|
|
20014
20834
|
batchToolUseTool,
|
|
20015
20835
|
toolHelpTool,
|
|
20016
|
-
codebaseIndexTool,
|
|
20017
|
-
codebaseSearchTool,
|
|
20018
|
-
codebaseStatsTool,
|
|
20019
20836
|
setWorkingDirTool
|
|
20020
20837
|
];
|
|
20021
20838
|
|