@wrongstack/tools 0.286.0 → 0.289.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/_edit-match.d.ts.map +1 -1
- package/dist/_fetch-guard.d.ts.map +1 -1
- package/dist/_redact-command.d.ts.map +1 -1
- package/dist/_spawn-stream.d.ts.map +1 -1
- package/dist/audit.js +61 -14
- package/dist/audit.js.map +3 -3
- package/dist/bash.d.ts +1 -1
- package/dist/bash.d.ts.map +1 -1
- package/dist/bash.js +97 -19
- package/dist/bash.js.map +3 -3
- package/dist/batch-tool-use.d.ts +1 -1
- package/dist/batch-tool-use.d.ts.map +1 -1
- package/dist/batch-tool-use.js +26 -8
- package/dist/batch-tool-use.js.map +2 -2
- package/dist/browser/index.js +1 -1
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/types.d.ts +1 -1
- package/dist/browser/types.d.ts.map +1 -1
- package/dist/builtin.d.ts +12 -10
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +2107 -864
- package/dist/builtin.js.map +4 -4
- package/dist/codebase-index/background-indexer.d.ts.map +1 -1
- package/dist/codebase-index/bm25.d.ts +1 -2
- package/dist/codebase-index/bm25.d.ts.map +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts +7 -0
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
- package/dist/codebase-index/go-parser.d.ts.map +1 -1
- package/dist/codebase-index/index-service.d.ts +11 -1
- package/dist/codebase-index/index-service.d.ts.map +1 -1
- package/dist/codebase-index/index.d.ts +2 -1
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +934 -283
- package/dist/codebase-index/index.js.map +4 -4
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/py-parser.d.ts.map +1 -1
- package/dist/codebase-index/rs-parser.d.ts.map +1 -1
- package/dist/codebase-index/schema.d.ts +43 -1
- package/dist/codebase-index/schema.d.ts.map +1 -1
- package/dist/codebase-index/ts-parser.d.ts.map +1 -1
- package/dist/codebase-index/worker-protocol.d.ts +1 -0
- package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
- package/dist/codebase-index/worker.js +793 -241
- package/dist/codebase-index/worker.js.map +4 -4
- package/dist/codebase-index/writer.d.ts +27 -1
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/e2e.js +477 -0
- package/dist/e2e.js.map +7 -0
- package/dist/edit.d.ts.map +1 -1
- package/dist/edit.js +36 -12
- package/dist/edit.js.map +3 -3
- package/dist/exec.d.ts +1 -1
- package/dist/exec.d.ts.map +1 -1
- package/dist/exec.js +60 -5
- package/dist/exec.js.map +3 -3
- package/dist/fetch.js +2 -2
- package/dist/fetch.js.map +2 -2
- package/dist/format.js +61 -14
- package/dist/format.js.map +3 -3
- package/dist/glob.js +2 -2
- package/dist/glob.js.map +1 -1
- package/dist/grep.d.ts.map +1 -1
- package/dist/grep.js +2 -2
- package/dist/grep.js.map +2 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2535 -865
- package/dist/index.js.map +4 -4
- package/dist/install.js +61 -14
- package/dist/install.js.map +3 -3
- package/dist/kanban.d.ts +16 -2
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +795 -532
- package/dist/kanban.js.map +4 -4
- package/dist/languages/detect.d.ts.map +1 -1
- package/dist/languages/index.js +61 -14
- package/dist/languages/index.js.map +3 -3
- package/dist/languages/types.d.ts +7 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/lint.js +61 -14
- package/dist/lint.js.map +3 -3
- package/dist/next-steps.d.ts +6 -2
- package/dist/next-steps.d.ts.map +1 -1
- package/dist/next-steps.js +3 -3
- package/dist/next-steps.js.map +2 -2
- package/dist/outdated.js +61 -14
- package/dist/outdated.js.map +3 -3
- package/dist/pack.js +2104 -861
- package/dist/pack.js.map +4 -4
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +158 -4
- package/dist/plan.js.map +4 -4
- package/dist/process-registry.js +12 -5
- package/dist/process-registry.js.map +2 -2
- package/dist/search.js +2 -2
- package/dist/search.js.map +2 -2
- package/dist/session-kanban.d.ts +47 -0
- package/dist/session-kanban.d.ts.map +1 -0
- package/dist/session-kanban.js +626 -0
- package/dist/session-kanban.js.map +7 -0
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +167 -4
- package/dist/task.js.map +4 -4
- package/dist/test.js +61 -14
- package/dist/test.js.map +3 -3
- package/dist/todo.d.ts.map +1 -1
- package/dist/todo.js +159 -7
- package/dist/todo.js.map +4 -4
- package/dist/tool-icon-map.d.ts.map +1 -1
- package/dist/tool-icons.d.ts.map +1 -1
- package/dist/tool-icons.js +7 -1
- package/dist/tool-icons.js.map +2 -2
- package/dist/tree.js +2 -2
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js +61 -14
- package/dist/typecheck.js.map +3 -3
- package/package.json +11 -3
package/dist/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) {
|
|
@@ -6054,6 +6172,9 @@ ${hint}` : ""),
|
|
|
6054
6172
|
};
|
|
6055
6173
|
|
|
6056
6174
|
// src/batch-tool-use.ts
|
|
6175
|
+
import {
|
|
6176
|
+
GOVERNED_TOOL_EXECUTOR_META_KEY
|
|
6177
|
+
} from "@wrongstack/core";
|
|
6057
6178
|
var batchToolUseTool = {
|
|
6058
6179
|
name: "batch_tool_use",
|
|
6059
6180
|
category: "Meta",
|
|
@@ -6090,7 +6211,7 @@ var batchToolUseTool = {
|
|
|
6090
6211
|
},
|
|
6091
6212
|
required: ["calls"]
|
|
6092
6213
|
},
|
|
6093
|
-
async execute(input, ctx,
|
|
6214
|
+
async execute(input, ctx, _opts) {
|
|
6094
6215
|
if (!input?.calls || input.calls.length === 0) {
|
|
6095
6216
|
return {
|
|
6096
6217
|
results: [],
|
|
@@ -6100,18 +6221,33 @@ var batchToolUseTool = {
|
|
|
6100
6221
|
stop_on_error: false
|
|
6101
6222
|
};
|
|
6102
6223
|
}
|
|
6224
|
+
const governedExecute = ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY];
|
|
6225
|
+
if (typeof governedExecute !== "function") {
|
|
6226
|
+
return {
|
|
6227
|
+
results: input.calls.map((call) => ({
|
|
6228
|
+
tool: call.tool,
|
|
6229
|
+
success: false,
|
|
6230
|
+
error: "governed nested execution is unavailable; call the tool directly",
|
|
6231
|
+
executionMs: 0
|
|
6232
|
+
})),
|
|
6233
|
+
total: input.calls.length,
|
|
6234
|
+
succeeded: 0,
|
|
6235
|
+
failed: input.calls.length,
|
|
6236
|
+
stop_on_error: input.stop_on_error ?? false
|
|
6237
|
+
};
|
|
6238
|
+
}
|
|
6103
6239
|
const results = [];
|
|
6104
6240
|
let succeeded = 0;
|
|
6105
6241
|
let failed = 0;
|
|
6106
6242
|
if (input.parallel !== false) {
|
|
6107
|
-
const promises = input.calls.map(async (call) => executeSingle(call, ctx,
|
|
6243
|
+
const promises = input.calls.map(async (call) => executeSingle(call, ctx, governedExecute));
|
|
6108
6244
|
const allResults = await Promise.all(promises);
|
|
6109
6245
|
results.push(...allResults);
|
|
6110
6246
|
succeeded = allResults.filter((r) => r.success).length;
|
|
6111
6247
|
failed = allResults.filter((r) => !r.success).length;
|
|
6112
6248
|
} else {
|
|
6113
6249
|
for (const call of input.calls) {
|
|
6114
|
-
const result = await executeSingle(call, ctx,
|
|
6250
|
+
const result = await executeSingle(call, ctx, governedExecute);
|
|
6115
6251
|
results.push(result);
|
|
6116
6252
|
if (result.success) {
|
|
6117
6253
|
succeeded++;
|
|
@@ -6130,9 +6266,9 @@ var batchToolUseTool = {
|
|
|
6130
6266
|
};
|
|
6131
6267
|
}
|
|
6132
6268
|
};
|
|
6133
|
-
async function executeSingle(call, ctx,
|
|
6269
|
+
async function executeSingle(call, ctx, governedExecute) {
|
|
6134
6270
|
const start = Date.now();
|
|
6135
|
-
const tool = ctx.tools.find((
|
|
6271
|
+
const tool = ctx.tools.find((candidate) => candidate.name === call.tool);
|
|
6136
6272
|
if (!tool) {
|
|
6137
6273
|
return {
|
|
6138
6274
|
tool: call.tool,
|
|
@@ -6142,11 +6278,11 @@ async function executeSingle(call, ctx, opts) {
|
|
|
6142
6278
|
};
|
|
6143
6279
|
}
|
|
6144
6280
|
try {
|
|
6145
|
-
const result = await
|
|
6281
|
+
const result = await governedExecute(call.tool, call.input);
|
|
6146
6282
|
return {
|
|
6147
6283
|
tool: call.tool,
|
|
6148
|
-
success:
|
|
6149
|
-
result,
|
|
6284
|
+
success: result.success,
|
|
6285
|
+
...result.success ? { result: result.result } : { error: result.error ?? "nested tool failed" },
|
|
6150
6286
|
executionMs: Date.now() - start
|
|
6151
6287
|
};
|
|
6152
6288
|
} catch (e) {
|
|
@@ -6556,7 +6692,7 @@ var BrowserSessionManager = class {
|
|
|
6556
6692
|
constructor(options, launcher = defaultLauncher) {
|
|
6557
6693
|
this.launcher = launcher;
|
|
6558
6694
|
this.artifacts = new BrowserArtifactStore(options.artifactRoot);
|
|
6559
|
-
this.allowPrivateHosts = options.allowPrivateHosts ??
|
|
6695
|
+
this.allowPrivateHosts = options.allowPrivateHosts ?? true;
|
|
6560
6696
|
this.allowedPrivateOrigins = options.allowedPrivateOrigins ?? [];
|
|
6561
6697
|
this.networkProxy = new BrowserNetworkGuardProxy({
|
|
6562
6698
|
allowPrivateHosts: this.allowPrivateHosts,
|
|
@@ -7482,7 +7618,7 @@ import * as path13 from "node:path";
|
|
|
7482
7618
|
import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core";
|
|
7483
7619
|
|
|
7484
7620
|
// src/codebase-index/schema.ts
|
|
7485
|
-
var SCHEMA_VERSION =
|
|
7621
|
+
var SCHEMA_VERSION = 3;
|
|
7486
7622
|
|
|
7487
7623
|
// src/codebase-index/lsp-kind.ts
|
|
7488
7624
|
function lspKindToInternalKind(k) {
|
|
@@ -7525,7 +7661,7 @@ function tokenise(text) {
|
|
|
7525
7661
|
return sanitised.toLowerCase().split(" ").filter(Boolean);
|
|
7526
7662
|
}
|
|
7527
7663
|
function splitName(name) {
|
|
7528
|
-
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();
|
|
7529
7665
|
}
|
|
7530
7666
|
function buildIndexableText(name, signature, docComment) {
|
|
7531
7667
|
return [splitName(name), name, signature, docComment].filter(Boolean).join(" ");
|
|
@@ -7551,14 +7687,12 @@ function buildBm25Index(docs) {
|
|
|
7551
7687
|
return new Bm25Index(documents, df, N, avgLen);
|
|
7552
7688
|
}
|
|
7553
7689
|
var Bm25Index = class {
|
|
7554
|
-
constructor(documents,
|
|
7690
|
+
constructor(documents, _df, N, avgLen) {
|
|
7555
7691
|
this.documents = documents;
|
|
7556
|
-
this.df = df;
|
|
7557
7692
|
this.N = N;
|
|
7558
7693
|
this.safeAvgLen = avgLen === 0 ? 1 : avgLen;
|
|
7559
7694
|
}
|
|
7560
7695
|
documents;
|
|
7561
|
-
df;
|
|
7562
7696
|
N;
|
|
7563
7697
|
safeAvgLen;
|
|
7564
7698
|
score(query, filter) {
|
|
@@ -7571,10 +7705,13 @@ var Bm25Index = class {
|
|
|
7571
7705
|
for (const qTerm of qTokens) {
|
|
7572
7706
|
let tf = 0;
|
|
7573
7707
|
for (const t of doc.tokens) {
|
|
7574
|
-
if (t
|
|
7708
|
+
if (t.startsWith(qTerm)) tf++;
|
|
7575
7709
|
}
|
|
7576
7710
|
if (tf === 0) continue;
|
|
7577
|
-
|
|
7711
|
+
let dfVal = 0;
|
|
7712
|
+
for (const candidate of this.documents) {
|
|
7713
|
+
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
7714
|
+
}
|
|
7578
7715
|
if (dfVal === 0) continue;
|
|
7579
7716
|
const idf = Math.log((this.N - dfVal + 0.5) / (dfVal + 0.5) + 1);
|
|
7580
7717
|
const lenRatio = B * (doc.len / this.safeAvgLen);
|
|
@@ -7607,6 +7744,29 @@ var Bm25Index = class {
|
|
|
7607
7744
|
|
|
7608
7745
|
// src/codebase-index/writer.ts
|
|
7609
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
|
+
}
|
|
7610
7770
|
function resolveIndexDir(projectRoot, override) {
|
|
7611
7771
|
return override ?? resolveWstackPaths2({ projectRoot }).projectCodebaseIndex;
|
|
7612
7772
|
}
|
|
@@ -7660,7 +7820,7 @@ function sleepSync(ms) {
|
|
|
7660
7820
|
} catch {
|
|
7661
7821
|
}
|
|
7662
7822
|
}
|
|
7663
|
-
var IndexStore = class {
|
|
7823
|
+
var IndexStore = class _IndexStore {
|
|
7664
7824
|
db;
|
|
7665
7825
|
/** Absolute path to this project's index directory. */
|
|
7666
7826
|
indexDir;
|
|
@@ -7712,10 +7872,7 @@ var IndexStore = class {
|
|
|
7712
7872
|
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
7713
7873
|
throw new LockError(`SQLite lock conflict after ${MAX_LOCK_RETRIES} retries: ${msg}`);
|
|
7714
7874
|
}
|
|
7715
|
-
const delay = Math.min(
|
|
7716
|
-
LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt,
|
|
7717
|
-
LOCK_RETRY_MAX_DELAY_MS
|
|
7718
|
-
);
|
|
7875
|
+
const delay = Math.min(LOCK_RETRY_BASE_DELAY_MS * 2 ** attempt, LOCK_RETRY_MAX_DELAY_MS);
|
|
7719
7876
|
sleepSync(delay);
|
|
7720
7877
|
}
|
|
7721
7878
|
}
|
|
@@ -7797,8 +7954,24 @@ var IndexStore = class {
|
|
|
7797
7954
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)");
|
|
7798
7955
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)");
|
|
7799
7956
|
try {
|
|
7800
|
-
this.db.exec(
|
|
7957
|
+
this.db.exec(
|
|
7958
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')"
|
|
7959
|
+
);
|
|
7801
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
|
+
}
|
|
7802
7975
|
} catch {
|
|
7803
7976
|
this.ftsAvailable = false;
|
|
7804
7977
|
}
|
|
@@ -7856,7 +8029,9 @@ var IndexStore = class {
|
|
|
7856
8029
|
deleteSymbolsForFile(file) {
|
|
7857
8030
|
this.runWithRetry(() => {
|
|
7858
8031
|
if (this.ftsAvailable) {
|
|
7859
|
-
this.stmt(
|
|
8032
|
+
this.stmt(
|
|
8033
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8034
|
+
).run(file);
|
|
7860
8035
|
}
|
|
7861
8036
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
7862
8037
|
});
|
|
@@ -7868,9 +8043,21 @@ var IndexStore = class {
|
|
|
7868
8043
|
*/
|
|
7869
8044
|
deleteFile(file) {
|
|
7870
8045
|
this.runWithRetry(() => {
|
|
7871
|
-
this.
|
|
7872
|
-
|
|
7873
|
-
|
|
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
|
+
}
|
|
7874
8061
|
});
|
|
7875
8062
|
}
|
|
7876
8063
|
// ─── File metadata ──────────────────────────────────────────────────────────
|
|
@@ -7893,14 +8080,22 @@ var IndexStore = class {
|
|
|
7893
8080
|
).all(file);
|
|
7894
8081
|
if (!rows.length) return null;
|
|
7895
8082
|
const r = expectDefined2(rows[0]);
|
|
7896
|
-
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
|
+
};
|
|
7897
8090
|
}
|
|
7898
8091
|
getAllFileMetas() {
|
|
7899
|
-
return this.db.prepare(
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
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
|
+
}));
|
|
7904
8099
|
}
|
|
7905
8100
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
7906
8101
|
search(query, filter) {
|
|
@@ -7924,8 +8119,8 @@ var IndexStore = class {
|
|
|
7924
8119
|
values.push(filter.lang);
|
|
7925
8120
|
}
|
|
7926
8121
|
if (filter?.file) {
|
|
7927
|
-
conditions.push("file LIKE ?");
|
|
7928
|
-
values.push(`%${filter.file}%`);
|
|
8122
|
+
conditions.push("replace(file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
8123
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
7929
8124
|
}
|
|
7930
8125
|
if (query.trim()) {
|
|
7931
8126
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
@@ -7963,9 +8158,11 @@ var IndexStore = class {
|
|
|
7963
8158
|
* "users", camelCase-split text makes "complex" find "complexOperation").
|
|
7964
8159
|
*/
|
|
7965
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));
|
|
7966
8163
|
const tokens = tokenise(query);
|
|
7967
8164
|
if (tokens.length === 0 || !this.ftsAvailable) {
|
|
7968
|
-
return this.searchRankedFallback(query, filter,
|
|
8165
|
+
return this.searchRankedFallback(query, filter, safeLimit);
|
|
7969
8166
|
}
|
|
7970
8167
|
let effectiveKind = filter?.kind;
|
|
7971
8168
|
if (filter?.lspKind !== void 0) {
|
|
@@ -7985,11 +8182,13 @@ var IndexStore = class {
|
|
|
7985
8182
|
values.push(filter.lang);
|
|
7986
8183
|
}
|
|
7987
8184
|
if (filter?.file) {
|
|
7988
|
-
conditions.push("s.file LIKE ?");
|
|
7989
|
-
values.push(`%${filter.file}%`);
|
|
8185
|
+
conditions.push("replace(s.file, '\\', '/') LIKE ? ESCAPE '\\'");
|
|
8186
|
+
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
7990
8187
|
}
|
|
7991
8188
|
const where = conditions.join(" AND ");
|
|
7992
|
-
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);
|
|
7993
8192
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
7994
8193
|
if (total === 0) return { results: [], total: 0 };
|
|
7995
8194
|
const rows = this.db.prepare(
|
|
@@ -7998,9 +8197,13 @@ var IndexStore = class {
|
|
|
7998
8197
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
7999
8198
|
FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
8000
8199
|
WHERE ${where}
|
|
8001
|
-
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
|
|
8002
8205
|
LIMIT ?`
|
|
8003
|
-
).all(...values,
|
|
8206
|
+
).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
8004
8207
|
return {
|
|
8005
8208
|
results: rows.map((r) => ({
|
|
8006
8209
|
id: r.id,
|
|
@@ -8030,10 +8233,28 @@ var IndexStore = class {
|
|
|
8030
8233
|
}
|
|
8031
8234
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
8032
8235
|
const bm25 = buildBm25Index(
|
|
8033
|
-
candidates.map((c) => ({
|
|
8236
|
+
candidates.map((c) => ({
|
|
8237
|
+
id: c.id,
|
|
8238
|
+
text: buildIndexableText(c.name, c.signature, c.docComment)
|
|
8239
|
+
}))
|
|
8034
8240
|
);
|
|
8035
8241
|
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
8036
|
-
|
|
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
|
+
});
|
|
8037
8258
|
const qTokens = tokenise(query);
|
|
8038
8259
|
const results = scored.slice(0, limit).map(({ id, score }) => {
|
|
8039
8260
|
const c = expectDefined2(candidateById.get(id));
|
|
@@ -8042,9 +8263,7 @@ var IndexStore = class {
|
|
|
8042
8263
|
return { results, total: candidates.length };
|
|
8043
8264
|
}
|
|
8044
8265
|
getAllIndexable() {
|
|
8045
|
-
return this.db.prepare("SELECT id, text FROM symbols").all().map(
|
|
8046
|
-
({ id, text }) => ({ id, text })
|
|
8047
|
-
);
|
|
8266
|
+
return this.db.prepare("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
|
|
8048
8267
|
}
|
|
8049
8268
|
/**
|
|
8050
8269
|
* Largest symbol id currently in the table (0 when empty). New ids must be
|
|
@@ -8060,22 +8279,16 @@ var IndexStore = class {
|
|
|
8060
8279
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
8061
8280
|
getStats() {
|
|
8062
8281
|
const sizeBytes = this.sizeBytes();
|
|
8063
|
-
const lastRows = this.db.prepare(
|
|
8064
|
-
"SELECT value FROM metadata WHERE key = 'last_indexed'"
|
|
8065
|
-
).all();
|
|
8282
|
+
const lastRows = this.db.prepare("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
8066
8283
|
const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
|
|
8067
8284
|
const totalRows = this.db.prepare("SELECT COUNT(*) FROM symbols").all();
|
|
8068
8285
|
const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
|
|
8069
8286
|
const fileRows = this.db.prepare("SELECT COUNT(*) FROM files").all();
|
|
8070
8287
|
const totalFiles = fileRows[0] ? Number(fileRows[0]["COUNT(*)"]) : 0;
|
|
8071
|
-
const langRows = this.db.prepare(
|
|
8072
|
-
"SELECT lang, COUNT(*) FROM symbols GROUP BY lang"
|
|
8073
|
-
).all();
|
|
8288
|
+
const langRows = this.db.prepare("SELECT lang, COUNT(*) FROM symbols GROUP BY lang").all();
|
|
8074
8289
|
const byLang = {};
|
|
8075
8290
|
for (const row of langRows) byLang[row.lang] = Number(row["COUNT(*)"]);
|
|
8076
|
-
const kindRows = this.db.prepare(
|
|
8077
|
-
"SELECT kind, COUNT(*) FROM symbols GROUP BY kind"
|
|
8078
|
-
).all();
|
|
8291
|
+
const kindRows = this.db.prepare("SELECT kind, COUNT(*) FROM symbols GROUP BY kind").all();
|
|
8079
8292
|
const byKind = {};
|
|
8080
8293
|
for (const row of kindRows) byKind[row.kind] = Number(row["COUNT(*)"]);
|
|
8081
8294
|
return {
|
|
@@ -8091,17 +8304,31 @@ var IndexStore = class {
|
|
|
8091
8304
|
}
|
|
8092
8305
|
setLastIndexed(ts2) {
|
|
8093
8306
|
this.runWithRetry(() => {
|
|
8094
|
-
this.db.prepare(
|
|
8095
|
-
|
|
8096
|
-
|
|
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);
|
|
8097
8317
|
});
|
|
8098
8318
|
}
|
|
8099
8319
|
clearAll() {
|
|
8100
8320
|
this.runWithRetry(() => {
|
|
8101
|
-
this.db.exec("
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
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
|
+
}
|
|
8105
8332
|
});
|
|
8106
8333
|
}
|
|
8107
8334
|
// ─── Ref CRUD ────────────────────────────────────────────────────────────────
|
|
@@ -8192,6 +8419,7 @@ var IndexStore = class {
|
|
|
8192
8419
|
const allInserted = [];
|
|
8193
8420
|
const refsToInsert = [];
|
|
8194
8421
|
for (const entry of entries) {
|
|
8422
|
+
const insertedForEntry = [];
|
|
8195
8423
|
for (const s of entry.symbols) {
|
|
8196
8424
|
const id = nextId++;
|
|
8197
8425
|
symStmt.run(
|
|
@@ -8209,9 +8437,11 @@ var IndexStore = class {
|
|
|
8209
8437
|
s.file
|
|
8210
8438
|
);
|
|
8211
8439
|
ftsStmt?.run(id, buildIndexableText(s.name, s.signature, s.docComment));
|
|
8212
|
-
|
|
8440
|
+
const inserted = { ...s, id };
|
|
8441
|
+
allInserted.push(inserted);
|
|
8442
|
+
insertedForEntry.push(inserted);
|
|
8213
8443
|
}
|
|
8214
|
-
|
|
8444
|
+
refsToInsert.push(...assignRefsToSymbols(entry.refs, insertedForEntry));
|
|
8215
8445
|
}
|
|
8216
8446
|
if (refsToInsert.length > 0) {
|
|
8217
8447
|
const refStmt = this.db.prepare(
|
|
@@ -8249,9 +8479,7 @@ var IndexStore = class {
|
|
|
8249
8479
|
*/
|
|
8250
8480
|
deleteRefsForFile(file) {
|
|
8251
8481
|
this.runWithRetry(() => {
|
|
8252
|
-
this.db.prepare(
|
|
8253
|
-
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
8254
|
-
).run(file);
|
|
8482
|
+
this.db.prepare("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
|
|
8255
8483
|
});
|
|
8256
8484
|
}
|
|
8257
8485
|
/**
|
|
@@ -8292,9 +8520,7 @@ var IndexStore = class {
|
|
|
8292
8520
|
* Find all references FROM a given symbol (what does this symbol call/use?).
|
|
8293
8521
|
*/
|
|
8294
8522
|
findRefsFrom(symbolId) {
|
|
8295
|
-
return this.db.prepare(
|
|
8296
|
-
"SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?"
|
|
8297
|
-
).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) => ({
|
|
8298
8524
|
id: r.id,
|
|
8299
8525
|
fromId: r.from_id,
|
|
8300
8526
|
toName: r.to_name,
|
|
@@ -8311,6 +8537,325 @@ var IndexStore = class {
|
|
|
8311
8537
|
return 0;
|
|
8312
8538
|
}
|
|
8313
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
|
+
}
|
|
8314
8859
|
close() {
|
|
8315
8860
|
this.stmtCache.clear();
|
|
8316
8861
|
try {
|
|
@@ -8405,6 +8950,14 @@ function getJsDoc(node, sourceFile) {
|
|
|
8405
8950
|
}
|
|
8406
8951
|
return "";
|
|
8407
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
|
+
}
|
|
8408
8961
|
function buildScope(node) {
|
|
8409
8962
|
const parts = [];
|
|
8410
8963
|
let current = node.parent;
|
|
@@ -8432,6 +8985,10 @@ function parseSymbols(opts) {
|
|
|
8432
8985
|
function visit(node) {
|
|
8433
8986
|
const kind = kindOf(node);
|
|
8434
8987
|
if (kind) {
|
|
8988
|
+
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && hasFunctionLikeAncestor(node)) {
|
|
8989
|
+
ts.forEachChild(node, visit);
|
|
8990
|
+
return;
|
|
8991
|
+
}
|
|
8435
8992
|
const nameNode = node.name;
|
|
8436
8993
|
if (!nameNode || !ts.isIdentifier(nameNode)) return;
|
|
8437
8994
|
const name = nameNode.text;
|
|
@@ -8519,6 +9076,7 @@ function detectLang(file) {
|
|
|
8519
9076
|
}
|
|
8520
9077
|
|
|
8521
9078
|
// src/codebase-index/go-parser.ts
|
|
9079
|
+
init_win32_resolve();
|
|
8522
9080
|
import { spawn as spawn4 } from "node:child_process";
|
|
8523
9081
|
import * as os5 from "node:os";
|
|
8524
9082
|
import * as path14 from "node:path";
|
|
@@ -8827,27 +9385,42 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
8827
9385
|
try {
|
|
8828
9386
|
const scriptPath = path14.join(tmpDir, "parse.go");
|
|
8829
9387
|
await fs10.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
8830
|
-
const
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
9388
|
+
const goBinary = resolveWin32Command("go");
|
|
9389
|
+
const goResult = await new Promise(
|
|
9390
|
+
(resolve14, reject) => {
|
|
9391
|
+
let settled = false;
|
|
9392
|
+
const proc = spawn4(goBinary, ["run", scriptPath], {
|
|
9393
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
9394
|
+
windowsHide: true
|
|
9395
|
+
});
|
|
9396
|
+
proc.on("error", (err) => {
|
|
9397
|
+
if (settled) return;
|
|
9398
|
+
settled = true;
|
|
9399
|
+
reject(err);
|
|
9400
|
+
});
|
|
9401
|
+
let stdout2 = "";
|
|
9402
|
+
proc.stdout?.on("data", (chunk) => {
|
|
9403
|
+
stdout2 += chunk.toString();
|
|
9404
|
+
});
|
|
9405
|
+
proc.stderr?.resume();
|
|
9406
|
+
proc.stdin?.write(content);
|
|
9407
|
+
proc.stdin?.end();
|
|
9408
|
+
const timer = setTimeout(() => {
|
|
9409
|
+
if (settled) return;
|
|
9410
|
+
settled = true;
|
|
8846
9411
|
proc.kill("SIGKILL");
|
|
8847
9412
|
reject(new Error("timeout"));
|
|
8848
|
-
}, 15e3)
|
|
8849
|
-
|
|
8850
|
-
|
|
9413
|
+
}, 15e3);
|
|
9414
|
+
timer.unref?.();
|
|
9415
|
+
proc.on("close", (code2) => {
|
|
9416
|
+
if (settled) return;
|
|
9417
|
+
settled = true;
|
|
9418
|
+
clearTimeout(timer);
|
|
9419
|
+
resolve14({ code: code2, stdout: stdout2 });
|
|
9420
|
+
});
|
|
9421
|
+
}
|
|
9422
|
+
);
|
|
9423
|
+
const { code, stdout } = goResult;
|
|
8851
9424
|
if (code !== 0 || !stdout.trim()) {
|
|
8852
9425
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
8853
9426
|
}
|
|
@@ -8874,7 +9447,8 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
8874
9447
|
}
|
|
8875
9448
|
|
|
8876
9449
|
// src/codebase-index/py-parser.ts
|
|
8877
|
-
|
|
9450
|
+
init_win32_resolve();
|
|
9451
|
+
import { spawn as spawn5, spawnSync } from "node:child_process";
|
|
8878
9452
|
import * as fs11 from "node:fs/promises";
|
|
8879
9453
|
import * as os6 from "node:os";
|
|
8880
9454
|
import * as path15 from "node:path";
|
|
@@ -9089,36 +9663,74 @@ visitor.visit(tree)
|
|
|
9089
9663
|
|
|
9090
9664
|
print(json.dumps([s.to_dict() for s in syms]))
|
|
9091
9665
|
`;
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9666
|
+
function resolvePython() {
|
|
9667
|
+
const candidates = process.platform === "win32" ? ["python3", "python", "py"] : ["python3", "python"];
|
|
9668
|
+
for (const name of candidates) {
|
|
9669
|
+
const resolved = resolveWin32Command(name);
|
|
9670
|
+
const result = spawnSync(resolved, ["--version"], {
|
|
9671
|
+
stdio: "pipe",
|
|
9672
|
+
timeout: 5e3
|
|
9673
|
+
});
|
|
9674
|
+
if (result.error) continue;
|
|
9675
|
+
if (result.status !== 0) continue;
|
|
9676
|
+
return resolved;
|
|
9677
|
+
}
|
|
9678
|
+
return null;
|
|
9679
|
+
}
|
|
9680
|
+
function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
9681
|
+
return new Promise((resolve14, reject) => {
|
|
9682
|
+
let settled = false;
|
|
9683
|
+
const proc = spawn5(pyBinary, [scriptPath, filePath], {
|
|
9102
9684
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9103
9685
|
windowsHide: true
|
|
9104
9686
|
});
|
|
9687
|
+
proc.on("error", (err) => {
|
|
9688
|
+
if (settled) return;
|
|
9689
|
+
settled = true;
|
|
9690
|
+
reject(err);
|
|
9691
|
+
});
|
|
9105
9692
|
proc.stdin?.write(content);
|
|
9106
9693
|
proc.stdin?.end();
|
|
9107
9694
|
let stdout = "";
|
|
9108
9695
|
proc.stdout?.on("data", (chunk) => {
|
|
9109
9696
|
stdout += chunk.toString();
|
|
9110
9697
|
});
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
)
|
|
9121
|
-
|
|
9698
|
+
proc.stderr?.resume();
|
|
9699
|
+
const timer = setTimeout(() => {
|
|
9700
|
+
if (settled) return;
|
|
9701
|
+
settled = true;
|
|
9702
|
+
proc.kill("SIGKILL");
|
|
9703
|
+
reject(new Error("timeout"));
|
|
9704
|
+
}, 15e3);
|
|
9705
|
+
timer.unref?.();
|
|
9706
|
+
proc.on("close", (code) => {
|
|
9707
|
+
if (settled) return;
|
|
9708
|
+
settled = true;
|
|
9709
|
+
clearTimeout(timer);
|
|
9710
|
+
resolve14({ code, stdout });
|
|
9711
|
+
});
|
|
9712
|
+
});
|
|
9713
|
+
}
|
|
9714
|
+
var _cachedScriptPath = null;
|
|
9715
|
+
var _cachedPyBinary = null;
|
|
9716
|
+
async function syncPyParse(filePath, content, lang) {
|
|
9717
|
+
try {
|
|
9718
|
+
if (!_cachedScriptPath) {
|
|
9719
|
+
const tmpDir = path15.join(os6.tmpdir(), "ws-py-parse");
|
|
9720
|
+
await fs11.mkdir(tmpDir, { recursive: true });
|
|
9721
|
+
_cachedScriptPath = path15.join(tmpDir, "parse.py");
|
|
9722
|
+
await fs11.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
9723
|
+
}
|
|
9724
|
+
if (!_cachedPyBinary) {
|
|
9725
|
+
_cachedPyBinary = resolvePython();
|
|
9726
|
+
if (!_cachedPyBinary) return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
9727
|
+
}
|
|
9728
|
+
const { code, stdout } = await spawnPyParser(
|
|
9729
|
+
_cachedPyBinary,
|
|
9730
|
+
_cachedScriptPath,
|
|
9731
|
+
filePath,
|
|
9732
|
+
content
|
|
9733
|
+
);
|
|
9122
9734
|
if (code !== 0 || !stdout.trim()) {
|
|
9123
9735
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
9124
9736
|
}
|
|
@@ -9143,6 +9755,7 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
9143
9755
|
}
|
|
9144
9756
|
|
|
9145
9757
|
// src/codebase-index/rs-parser.ts
|
|
9758
|
+
init_win32_resolve();
|
|
9146
9759
|
import { expectDefined as expectDefined3 } from "@wrongstack/core";
|
|
9147
9760
|
import { execFileSync, spawn as spawn6 } from "node:child_process";
|
|
9148
9761
|
import * as fs12 from "node:fs/promises";
|
|
@@ -9187,30 +9800,45 @@ async function tryNativeParse(file, content) {
|
|
|
9187
9800
|
const crateDir = path16.join(toolsDir, "syn-parser");
|
|
9188
9801
|
const tmpFile = path16.join(crateDir, "src", "input.rs");
|
|
9189
9802
|
await fs12.writeFile(tmpFile, content, "utf8");
|
|
9190
|
-
const
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
|
|
9199
|
-
|
|
9200
|
-
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9803
|
+
const cargoBinary = resolveWin32Command("cargo");
|
|
9804
|
+
const result = await new Promise(
|
|
9805
|
+
(resolve14, reject) => {
|
|
9806
|
+
let settled = false;
|
|
9807
|
+
const proc = spawn6(
|
|
9808
|
+
cargoBinary,
|
|
9809
|
+
["run", "--manifest-path", path16.join(toolsDir, "Cargo.toml")],
|
|
9810
|
+
{
|
|
9811
|
+
cwd: process.cwd(),
|
|
9812
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
9813
|
+
windowsHide: true
|
|
9814
|
+
}
|
|
9815
|
+
);
|
|
9816
|
+
proc.on("error", (err) => {
|
|
9817
|
+
if (settled) return;
|
|
9818
|
+
settled = true;
|
|
9819
|
+
reject(err);
|
|
9820
|
+
});
|
|
9821
|
+
let stdout2 = "";
|
|
9822
|
+
proc.stdout?.on("data", (chunk) => {
|
|
9823
|
+
stdout2 += chunk.toString();
|
|
9824
|
+
});
|
|
9825
|
+
proc.stderr?.resume();
|
|
9826
|
+
const timer = setTimeout(() => {
|
|
9827
|
+
if (settled) return;
|
|
9828
|
+
settled = true;
|
|
9209
9829
|
proc.kill("SIGKILL");
|
|
9210
9830
|
reject(new Error("timeout"));
|
|
9211
|
-
}, 15e3)
|
|
9212
|
-
|
|
9213
|
-
|
|
9831
|
+
}, 15e3);
|
|
9832
|
+
timer.unref?.();
|
|
9833
|
+
proc.on("close", (c) => {
|
|
9834
|
+
if (settled) return;
|
|
9835
|
+
settled = true;
|
|
9836
|
+
clearTimeout(timer);
|
|
9837
|
+
resolve14({ code: c, stdout: stdout2 });
|
|
9838
|
+
});
|
|
9839
|
+
}
|
|
9840
|
+
);
|
|
9841
|
+
const { code, stdout } = result;
|
|
9214
9842
|
if (code === 0 && stdout.trim()) {
|
|
9215
9843
|
const symbols = JSON.parse(stdout.trim());
|
|
9216
9844
|
return {
|
|
@@ -9305,9 +9933,9 @@ function parseSymbols5(opts) {
|
|
|
9305
9933
|
function regexParse2(opts) {
|
|
9306
9934
|
const { file, content, lang } = opts;
|
|
9307
9935
|
const symbols = [];
|
|
9308
|
-
const
|
|
9309
|
-
const isPackageJson =
|
|
9310
|
-
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";
|
|
9311
9939
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
9312
9940
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
9313
9941
|
const lines = content.split("\n");
|
|
@@ -9709,9 +10337,7 @@ function yieldEventLoop() {
|
|
|
9709
10337
|
function throwIfAborted(signal) {
|
|
9710
10338
|
if (!signal?.aborted) return;
|
|
9711
10339
|
if (signal.reason instanceof Error) throw signal.reason;
|
|
9712
|
-
throw new Error(
|
|
9713
|
-
typeof signal.reason === "string" ? signal.reason : "Indexing cancelled"
|
|
9714
|
-
);
|
|
10340
|
+
throw new Error(typeof signal.reason === "string" ? signal.reason : "Indexing cancelled");
|
|
9715
10341
|
}
|
|
9716
10342
|
function isAbortError(err) {
|
|
9717
10343
|
return err instanceof DOMException && err.name === "AbortError";
|
|
@@ -9727,8 +10353,20 @@ var DEFAULT_IGNORE = [
|
|
|
9727
10353
|
"__snapshots__",
|
|
9728
10354
|
".nyc_output"
|
|
9729
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
|
+
}
|
|
9730
10366
|
async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
9731
10367
|
const results = [];
|
|
10368
|
+
const errors = [];
|
|
10369
|
+
let complete = true;
|
|
9732
10370
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
9733
10371
|
const globs = [
|
|
9734
10372
|
{ ext: ".ts", pat: compileGlob2("**/*.ts") },
|
|
@@ -9752,7 +10390,9 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
9752
10390
|
let entries;
|
|
9753
10391
|
try {
|
|
9754
10392
|
entries = await fs14.readdir(dir, { withFileTypes: true });
|
|
9755
|
-
} catch {
|
|
10393
|
+
} catch (err) {
|
|
10394
|
+
complete = false;
|
|
10395
|
+
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9756
10396
|
return;
|
|
9757
10397
|
}
|
|
9758
10398
|
dirCount++;
|
|
@@ -9764,8 +10404,8 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
9764
10404
|
if (isGitIgnored(rel, true)) continue;
|
|
9765
10405
|
await walk(full);
|
|
9766
10406
|
} else if (e.isFile()) {
|
|
9767
|
-
if (isGitIgnored(rel, false)) continue;
|
|
9768
|
-
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();
|
|
9769
10409
|
for (const { ext: extName, pat } of globs) {
|
|
9770
10410
|
if (ext === extName && (pat.test(rel) || pat.test(e.name))) {
|
|
9771
10411
|
results.push(full);
|
|
@@ -9776,7 +10416,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
9776
10416
|
}
|
|
9777
10417
|
};
|
|
9778
10418
|
await walk(projectRoot);
|
|
9779
|
-
return results;
|
|
10419
|
+
return { files: results, complete, errors };
|
|
9780
10420
|
}
|
|
9781
10421
|
async function parseFile(file, content, lang) {
|
|
9782
10422
|
switch (lang) {
|
|
@@ -9799,6 +10439,26 @@ async function parseFile(file, content, lang) {
|
|
|
9799
10439
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
9800
10440
|
}
|
|
9801
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
|
+
}
|
|
9802
10462
|
async function runIndexer(_ctx, opts) {
|
|
9803
10463
|
const store = new IndexStore(opts.projectRoot, { indexDir: opts.indexDir });
|
|
9804
10464
|
try {
|
|
@@ -9811,7 +10471,9 @@ async function runIndexer(_ctx, opts) {
|
|
|
9811
10471
|
}
|
|
9812
10472
|
}
|
|
9813
10473
|
async function runIndexerWithStore(store, opts) {
|
|
9814
|
-
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;
|
|
9815
10477
|
const startMs = Date.now();
|
|
9816
10478
|
const errors = [];
|
|
9817
10479
|
const langStats = {};
|
|
@@ -9820,10 +10482,18 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9820
10482
|
const isGitIgnored = await loadGitignoreMatcher(projectRoot);
|
|
9821
10483
|
let files;
|
|
9822
10484
|
let discoveredFiles = null;
|
|
10485
|
+
let discoveryComplete = true;
|
|
9823
10486
|
if (opts.files && opts.files.length > 0) {
|
|
9824
|
-
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
|
+
});
|
|
9825
10492
|
} else {
|
|
9826
|
-
|
|
10493
|
+
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
10494
|
+
files = discovery.files;
|
|
10495
|
+
errors.push(...discovery.errors);
|
|
10496
|
+
discoveryComplete = discovery.complete;
|
|
9827
10497
|
discoveredFiles = new Set(files);
|
|
9828
10498
|
}
|
|
9829
10499
|
if (langs && langs.length > 0) {
|
|
@@ -9848,36 +10518,66 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9848
10518
|
}
|
|
9849
10519
|
const statOpts = signal ? { signal } : {};
|
|
9850
10520
|
const statReadParse = await Promise.allSettled(
|
|
9851
|
-
batchFiles.map(
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9855
|
-
|
|
9856
|
-
|
|
9857
|
-
|
|
9858
|
-
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
if (
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
|
|
9877
|
-
|
|
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 };
|
|
9878
10579
|
}
|
|
9879
|
-
|
|
9880
|
-
})
|
|
10580
|
+
)
|
|
9881
10581
|
);
|
|
9882
10582
|
const batchEntries = [];
|
|
9883
10583
|
const deleteForFiles = [];
|
|
@@ -9892,8 +10592,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9892
10592
|
}
|
|
9893
10593
|
const result = settled.value;
|
|
9894
10594
|
if (result.error) {
|
|
9895
|
-
if (result.
|
|
9896
|
-
|
|
10595
|
+
if (result.missing) store.deleteFile(file);
|
|
10596
|
+
errors.push(`${file}: ${result.error}`);
|
|
9897
10597
|
continue;
|
|
9898
10598
|
}
|
|
9899
10599
|
const { stat: stat17, lang, parsed } = result;
|
|
@@ -9917,6 +10617,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9917
10617
|
continue;
|
|
9918
10618
|
}
|
|
9919
10619
|
if (parsed.symbols.length === 0) {
|
|
10620
|
+
store.deleteRefsForFile(file);
|
|
10621
|
+
store.deleteSymbolsForFile(file);
|
|
9920
10622
|
store.upsertFile({
|
|
9921
10623
|
file,
|
|
9922
10624
|
lang,
|
|
@@ -9927,15 +10629,11 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9927
10629
|
filesIndexed++;
|
|
9928
10630
|
continue;
|
|
9929
10631
|
}
|
|
9930
|
-
const refs = [];
|
|
9931
|
-
if (parsed.refs && parsed.refs.length > 0) {
|
|
9932
|
-
for (const r of parsed.refs) refs.push({ ...r, fromId: 0 });
|
|
9933
|
-
}
|
|
9934
10632
|
batchEntries.push({
|
|
9935
10633
|
file,
|
|
9936
10634
|
lang,
|
|
9937
10635
|
symbols: parsed.symbols,
|
|
9938
|
-
refs,
|
|
10636
|
+
refs: parsed.refs ?? [],
|
|
9939
10637
|
mtimeMs: Math.floor(stat17.mtimeMs),
|
|
9940
10638
|
symbolCount: parsed.symbols.length
|
|
9941
10639
|
});
|
|
@@ -9943,34 +10641,12 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9943
10641
|
}
|
|
9944
10642
|
if (batchEntries.length > 0) {
|
|
9945
10643
|
try {
|
|
9946
|
-
|
|
9947
|
-
let cursor = 0;
|
|
10644
|
+
store.commitBatch(batchEntries, { deleteForFiles });
|
|
9948
10645
|
for (const entry of batchEntries) {
|
|
9949
10646
|
const count = entry.symbols.length;
|
|
9950
|
-
const symbolsWithIds = inserted.slice(cursor, cursor + count);
|
|
9951
|
-
cursor += count;
|
|
9952
10647
|
symbolsIndexed += count;
|
|
9953
10648
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
|
|
9954
10649
|
filesIndexed++;
|
|
9955
|
-
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
9956
|
-
const refsByLine = /* @__PURE__ */ new Map();
|
|
9957
|
-
for (let i = 0; i < symbolsWithIds.length; i++) {
|
|
9958
|
-
const sym = symbolsWithIds[i];
|
|
9959
|
-
let arr = refsByLine.get(sym.line);
|
|
9960
|
-
if (!arr) {
|
|
9961
|
-
arr = [];
|
|
9962
|
-
refsByLine.set(sym.line, arr);
|
|
9963
|
-
}
|
|
9964
|
-
arr.push(i);
|
|
9965
|
-
}
|
|
9966
|
-
for (const ref of entry.refs) {
|
|
9967
|
-
const indices = refsByLine.get(ref.line);
|
|
9968
|
-
if (indices && indices.length > 0) {
|
|
9969
|
-
const idx = indices.shift();
|
|
9970
|
-
ref.fromId = symbolsWithIds[idx].id;
|
|
9971
|
-
}
|
|
9972
|
-
}
|
|
9973
|
-
}
|
|
9974
10650
|
}
|
|
9975
10651
|
} catch (err) {
|
|
9976
10652
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -9984,23 +10660,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9984
10660
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
|
|
9985
10661
|
filesIndexed++;
|
|
9986
10662
|
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
9987
|
-
const
|
|
9988
|
-
for (const sym of symbolsWithIds) {
|
|
9989
|
-
let arr = refsByLine.get(sym.line);
|
|
9990
|
-
if (!arr) {
|
|
9991
|
-
arr = [];
|
|
9992
|
-
refsByLine.set(sym.line, arr);
|
|
9993
|
-
}
|
|
9994
|
-
arr.push(sym);
|
|
9995
|
-
}
|
|
9996
|
-
const fallbackBatch = [];
|
|
9997
|
-
for (const ref of entry.refs) {
|
|
9998
|
-
const syms = refsByLine.get(ref.line);
|
|
9999
|
-
if (syms && syms.length > 0) {
|
|
10000
|
-
const sym = syms.shift();
|
|
10001
|
-
fallbackBatch.push({ ...ref, fromId: sym.id });
|
|
10002
|
-
}
|
|
10003
|
-
}
|
|
10663
|
+
const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
|
|
10004
10664
|
if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
|
|
10005
10665
|
}
|
|
10006
10666
|
store.upsertFile({
|
|
@@ -10011,19 +10671,23 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10011
10671
|
lastIndexed: Date.now()
|
|
10012
10672
|
});
|
|
10013
10673
|
} catch (innerErr) {
|
|
10014
|
-
errors.push(
|
|
10674
|
+
errors.push(
|
|
10675
|
+
`fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
|
|
10676
|
+
);
|
|
10015
10677
|
}
|
|
10016
10678
|
}
|
|
10017
10679
|
}
|
|
10018
10680
|
}
|
|
10019
10681
|
}
|
|
10020
|
-
if (discoveredFiles) {
|
|
10682
|
+
if (discoveredFiles && discoveryComplete) {
|
|
10021
10683
|
for (const [file_] of existingMeta) {
|
|
10022
10684
|
if (!discoveredFiles.has(file_)) {
|
|
10023
10685
|
store.deleteFile(file_);
|
|
10024
10686
|
}
|
|
10025
10687
|
}
|
|
10026
10688
|
}
|
|
10689
|
+
store.resolveRefs();
|
|
10690
|
+
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
10027
10691
|
const durationMs = Date.now() - startMs;
|
|
10028
10692
|
store.setLastIndexed(Date.now());
|
|
10029
10693
|
return {
|
|
@@ -10086,7 +10750,7 @@ function statsService(args) {
|
|
|
10086
10750
|
|
|
10087
10751
|
// src/codebase-index/background-indexer.ts
|
|
10088
10752
|
var DEFAULT_FULL_INDEX_TIMEOUT_MS = 24e4;
|
|
10089
|
-
var DEFAULT_QUERY_TIMEOUT_MS =
|
|
10753
|
+
var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
10090
10754
|
var _ready = false;
|
|
10091
10755
|
var _indexing = false;
|
|
10092
10756
|
var _currentFile = 0;
|
|
@@ -10155,14 +10819,22 @@ function ensureWorker() {
|
|
|
10155
10819
|
if (!entry) return;
|
|
10156
10820
|
pending.delete(msg.id);
|
|
10157
10821
|
if (msg.ok) entry.resolve(msg.result);
|
|
10158
|
-
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
|
+
}
|
|
10159
10829
|
});
|
|
10160
10830
|
w.on("error", (err) => {
|
|
10831
|
+
if (worker !== w) return;
|
|
10161
10832
|
worker = null;
|
|
10162
10833
|
failAllPending(err);
|
|
10163
10834
|
});
|
|
10164
10835
|
w.on("exit", () => {
|
|
10165
|
-
if (worker
|
|
10836
|
+
if (worker !== w) return;
|
|
10837
|
+
worker = null;
|
|
10166
10838
|
failAllPending(new Error("codebase-index worker exited"));
|
|
10167
10839
|
});
|
|
10168
10840
|
worker = w;
|
|
@@ -10182,6 +10854,11 @@ function terminateWorker(reason) {
|
|
|
10182
10854
|
function callIndexOp(op, args, opts) {
|
|
10183
10855
|
const w = ensureWorker();
|
|
10184
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
|
+
}
|
|
10185
10862
|
return new Promise((resolve14, reject) => {
|
|
10186
10863
|
const id = nextRpcId++;
|
|
10187
10864
|
const timer = setTimeout(() => {
|
|
@@ -10196,8 +10873,7 @@ function callIndexOp(op, args, opts) {
|
|
|
10196
10873
|
const onAbort = () => {
|
|
10197
10874
|
w.postMessage({ type: "cancel", id });
|
|
10198
10875
|
};
|
|
10199
|
-
|
|
10200
|
-
else opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
10876
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
10201
10877
|
const cleanup = () => {
|
|
10202
10878
|
clearTimeout(timer);
|
|
10203
10879
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
@@ -10341,7 +11017,7 @@ var codebaseIndexTool = {
|
|
|
10341
11017
|
category: "Project",
|
|
10342
11018
|
icon: "index",
|
|
10343
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.",
|
|
10344
|
-
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.",
|
|
10345
11021
|
permission: "confirm",
|
|
10346
11022
|
mutating: true,
|
|
10347
11023
|
capabilities: ["fs.write.outside-project"],
|
|
@@ -10397,12 +11073,14 @@ var codebaseSearchTool = {
|
|
|
10397
11073
|
name: "codebase-search",
|
|
10398
11074
|
category: "Project",
|
|
10399
11075
|
icon: "index",
|
|
10400
|
-
description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback.
|
|
10401
|
-
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.",
|
|
10402
11078
|
permission: "auto",
|
|
10403
11079
|
mutating: false,
|
|
10404
11080
|
capabilities: ["fs.read"],
|
|
10405
|
-
|
|
11081
|
+
// The index host has its own 30s read watchdog. Leave enough headroom for
|
|
11082
|
+
// worker teardown and structured timeout reporting.
|
|
11083
|
+
timeoutMs: 35e3,
|
|
10406
11084
|
inputSchema: {
|
|
10407
11085
|
type: "object",
|
|
10408
11086
|
properties: {
|
|
@@ -10412,11 +11090,34 @@ var codebaseSearchTool = {
|
|
|
10412
11090
|
},
|
|
10413
11091
|
kind: {
|
|
10414
11092
|
type: "string",
|
|
10415
|
-
|
|
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"
|
|
10416
11116
|
},
|
|
10417
11117
|
lang: {
|
|
10418
11118
|
type: "string",
|
|
10419
|
-
|
|
11119
|
+
enum: ["ts", "tsx", "js", "jsx", "go", "py", "rs", "json", "yaml"],
|
|
11120
|
+
description: "Filter by indexed language"
|
|
10420
11121
|
},
|
|
10421
11122
|
lspKind: {
|
|
10422
11123
|
type: "integer",
|
|
@@ -10441,20 +11142,12 @@ var codebaseSearchTool = {
|
|
|
10441
11142
|
},
|
|
10442
11143
|
async execute(input, ctx, execOpts) {
|
|
10443
11144
|
const state = getIndexState();
|
|
10444
|
-
if (!state.ready) {
|
|
10445
|
-
return {
|
|
10446
|
-
results: [],
|
|
10447
|
-
total: 0,
|
|
10448
|
-
query: input.query,
|
|
10449
|
-
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."
|
|
10450
|
-
};
|
|
10451
|
-
}
|
|
10452
|
-
if (state.indexing) {
|
|
11145
|
+
if (state.indexing && !state.ready) {
|
|
10453
11146
|
return {
|
|
10454
11147
|
results: [],
|
|
10455
11148
|
total: 0,
|
|
10456
11149
|
query: input.query,
|
|
10457
|
-
indexStatus: `
|
|
11150
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
10458
11151
|
};
|
|
10459
11152
|
}
|
|
10460
11153
|
if (state.lastError) {
|
|
@@ -10467,21 +11160,37 @@ var codebaseSearchTool = {
|
|
|
10467
11160
|
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
10468
11161
|
};
|
|
10469
11162
|
}
|
|
10470
|
-
const limit = Math.min(input.limit ?? 20, 100);
|
|
11163
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 20), 100));
|
|
10471
11164
|
const { results, total } = await searchCodebaseIndex(
|
|
10472
11165
|
{
|
|
10473
11166
|
projectRoot: ctx.projectRoot,
|
|
10474
11167
|
indexDir: codebaseIndexDirOverride(ctx),
|
|
10475
11168
|
query: input.query,
|
|
10476
|
-
kind: input.kind,
|
|
10477
|
-
lang: input.lang,
|
|
11169
|
+
kind: input.kind?.toLowerCase(),
|
|
11170
|
+
lang: input.lang?.toLowerCase(),
|
|
10478
11171
|
file: input.file,
|
|
10479
11172
|
lspKind: input.lspKind,
|
|
10480
11173
|
limit
|
|
10481
11174
|
},
|
|
10482
11175
|
{ signal: execOpts?.signal }
|
|
10483
11176
|
);
|
|
10484
|
-
|
|
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
|
+
};
|
|
10485
11194
|
}
|
|
10486
11195
|
};
|
|
10487
11196
|
|
|
@@ -10490,12 +11199,15 @@ var codebaseStatsTool = {
|
|
|
10490
11199
|
name: "codebase-stats",
|
|
10491
11200
|
category: "Project",
|
|
10492
11201
|
icon: "index",
|
|
10493
|
-
description: "
|
|
10494
|
-
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.",
|
|
10495
11204
|
permission: "auto",
|
|
10496
11205
|
mutating: false,
|
|
10497
11206
|
capabilities: ["fs.read"],
|
|
10498
|
-
|
|
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,
|
|
10499
11211
|
inputSchema: {
|
|
10500
11212
|
type: "object",
|
|
10501
11213
|
properties: {},
|
|
@@ -10503,7 +11215,8 @@ var codebaseStatsTool = {
|
|
|
10503
11215
|
},
|
|
10504
11216
|
async execute(_input, ctx, execOpts) {
|
|
10505
11217
|
const idxState = getIndexState();
|
|
10506
|
-
|
|
11218
|
+
const indexPath = resolveIndexDir(ctx.projectRoot, codebaseIndexDirOverride(ctx));
|
|
11219
|
+
if (idxState.indexing) {
|
|
10507
11220
|
return {
|
|
10508
11221
|
totalSymbols: 0,
|
|
10509
11222
|
totalFiles: 0,
|
|
@@ -10511,22 +11224,47 @@ var codebaseStatsTool = {
|
|
|
10511
11224
|
byKind: {},
|
|
10512
11225
|
lastIndexed: null,
|
|
10513
11226
|
sizeBytes: 0,
|
|
10514
|
-
indexPath
|
|
11227
|
+
indexPath,
|
|
10515
11228
|
version: SCHEMA_VERSION,
|
|
10516
|
-
|
|
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.`
|
|
10517
11235
|
};
|
|
10518
11236
|
}
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
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
|
+
}
|
|
10524
11247
|
return {
|
|
10525
|
-
|
|
10526
|
-
|
|
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."
|
|
10527
11258
|
};
|
|
10528
11259
|
}
|
|
10529
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
|
+
}
|
|
10530
11268
|
return {
|
|
10531
11269
|
totalSymbols: stats.totalSymbols,
|
|
10532
11270
|
totalFiles: stats.totalFiles,
|
|
@@ -10536,9 +11274,8 @@ var codebaseStatsTool = {
|
|
|
10536
11274
|
sizeBytes: stats.sizeBytes,
|
|
10537
11275
|
indexPath: stats.indexPath,
|
|
10538
11276
|
version: stats.version,
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
} : {}
|
|
11277
|
+
statsAvailable: true,
|
|
11278
|
+
...indexStatus ? { indexStatus } : {}
|
|
10542
11279
|
};
|
|
10543
11280
|
}
|
|
10544
11281
|
};
|
|
@@ -11570,25 +12307,46 @@ function findLadderMatches(fileLf, oldLf) {
|
|
|
11570
12307
|
const needleLines = oldLf.split("\n");
|
|
11571
12308
|
if (needleLines.length > fileLines.length) return void 0;
|
|
11572
12309
|
const offsets = lineOffsets(fileLines);
|
|
12310
|
+
const fileTrimEnd = fileLines.map((l) => l.trimEnd());
|
|
12311
|
+
const needleTrimEnd = needleLines.map((l) => l.trimEnd());
|
|
11573
12312
|
const trailing = windowScan(
|
|
11574
|
-
|
|
11575
|
-
|
|
12313
|
+
fileTrimEnd,
|
|
12314
|
+
needleTrimEnd,
|
|
11576
12315
|
offsets,
|
|
11577
|
-
|
|
12316
|
+
fileLines,
|
|
12317
|
+
(a, b) => a === b
|
|
11578
12318
|
);
|
|
11579
12319
|
if (trailing.length > 0) return { tier: "trailing-whitespace", matches: trailing };
|
|
11580
|
-
const normalizedLen =
|
|
12320
|
+
const normalizedLen = needleTrimEnd.reduce((n, l) => n + l.trimStart().length, 0);
|
|
11581
12321
|
if (normalizedLen < MIN_NORMALIZED_NEEDLE_CHARS) return void 0;
|
|
11582
|
-
const
|
|
12322
|
+
const fileTrimmed = fileTrimEnd.map((l) => l.trimStart());
|
|
12323
|
+
const needleTrimmed = needleTrimEnd.map((l) => l.trimStart());
|
|
12324
|
+
const normalized = windowScan(
|
|
12325
|
+
fileTrimmed,
|
|
12326
|
+
needleTrimmed,
|
|
12327
|
+
offsets,
|
|
12328
|
+
fileLines,
|
|
12329
|
+
(a, b) => a === b
|
|
12330
|
+
);
|
|
11583
12331
|
if (normalized.length > 0) return { tier: "whitespace-normalized", matches: normalized };
|
|
11584
12332
|
return fuzzyScan(fileLines, needleLines, offsets);
|
|
11585
12333
|
}
|
|
11586
12334
|
function lineAt(text, pos) {
|
|
12335
|
+
if (pos < 512) {
|
|
12336
|
+
let line2 = 1;
|
|
12337
|
+
for (let i = 0; i < pos; i++) {
|
|
12338
|
+
if (text.charCodeAt(i) === 10) line2++;
|
|
12339
|
+
}
|
|
12340
|
+
return line2;
|
|
12341
|
+
}
|
|
11587
12342
|
let line = 1;
|
|
11588
|
-
|
|
11589
|
-
|
|
12343
|
+
let search = 0;
|
|
12344
|
+
while (true) {
|
|
12345
|
+
const idx = text.indexOf("\n", search);
|
|
12346
|
+
if (idx === -1 || idx >= pos) return line;
|
|
12347
|
+
search = idx + 1;
|
|
12348
|
+
line++;
|
|
11590
12349
|
}
|
|
11591
|
-
return line;
|
|
11592
12350
|
}
|
|
11593
12351
|
function lineOffsets(lines) {
|
|
11594
12352
|
const out = new Array(lines.length);
|
|
@@ -11607,19 +12365,19 @@ function windowToMatch(fileLines, offsets, start, windowLen) {
|
|
|
11607
12365
|
startLine: start + 1
|
|
11608
12366
|
};
|
|
11609
12367
|
}
|
|
11610
|
-
function windowScan(
|
|
12368
|
+
function windowScan(comparisonLines, needleLines, offsets, originalLines, eq) {
|
|
11611
12369
|
const n = needleLines.length;
|
|
11612
12370
|
const out = [];
|
|
11613
|
-
for (let i = 0; i + n <=
|
|
12371
|
+
for (let i = 0; i + n <= comparisonLines.length; i++) {
|
|
11614
12372
|
let all = true;
|
|
11615
12373
|
for (let j = 0; j < n; j++) {
|
|
11616
|
-
if (!eq(
|
|
12374
|
+
if (!eq(comparisonLines[i + j], needleLines[j])) {
|
|
11617
12375
|
all = false;
|
|
11618
12376
|
break;
|
|
11619
12377
|
}
|
|
11620
12378
|
}
|
|
11621
12379
|
if (all) {
|
|
11622
|
-
out.push(windowToMatch(
|
|
12380
|
+
out.push(windowToMatch(originalLines, offsets, i, n));
|
|
11623
12381
|
i += n - 1;
|
|
11624
12382
|
}
|
|
11625
12383
|
}
|
|
@@ -11941,8 +12699,10 @@ var editTool = {
|
|
|
11941
12699
|
note: autoReadNote
|
|
11942
12700
|
};
|
|
11943
12701
|
}
|
|
12702
|
+
opts?.signal?.throwIfAborted();
|
|
11944
12703
|
const ladder = findLadderMatches(fileLf, oldLf);
|
|
11945
12704
|
if (!ladder) {
|
|
12705
|
+
opts?.signal?.throwIfAborted();
|
|
11946
12706
|
const hint = nearestMatchHint(fileLf, oldLf);
|
|
11947
12707
|
throw new ToolValidationError({
|
|
11948
12708
|
message: `edit: no match for old_string in "${input.path}".${hint ? ` Nearest match near line ${hint.line}:
|
|
@@ -12010,6 +12770,7 @@ Compare this against your old_string and retry with the file's actual text.` : "
|
|
|
12010
12770
|
before: original,
|
|
12011
12771
|
after: newFile
|
|
12012
12772
|
});
|
|
12773
|
+
opts?.signal?.throwIfAborted();
|
|
12013
12774
|
const diff = unifiedDiff(original, newFile, {
|
|
12014
12775
|
fromFile: input.path,
|
|
12015
12776
|
toFile: input.path
|
|
@@ -12033,6 +12794,11 @@ Compare this against your old_string and retry with the file's actual text.` : "
|
|
|
12033
12794
|
|
|
12034
12795
|
// src/exec.ts
|
|
12035
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";
|
|
12036
12802
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils/error";
|
|
12037
12803
|
init_output_spool();
|
|
12038
12804
|
init_util();
|
|
@@ -13141,12 +13907,30 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13141
13907
|
resolve14(result);
|
|
13142
13908
|
};
|
|
13143
13909
|
const startedAt = Date.now();
|
|
13910
|
+
let stdoutBytes = 0;
|
|
13911
|
+
let stderrBytes = 0;
|
|
13912
|
+
let telemetryCompleted = false;
|
|
13913
|
+
let timedOut = false;
|
|
13144
13914
|
const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
|
|
13145
13915
|
const resolved = resolveWin32Command(cmd);
|
|
13146
13916
|
const needsShell = isWin3 && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
13147
13917
|
const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
|
|
13148
13918
|
const spawnCmd = shim?.command ?? resolved;
|
|
13149
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
|
+
};
|
|
13150
13934
|
let child;
|
|
13151
13935
|
try {
|
|
13152
13936
|
child = spawn8(spawnCmd, spawnArgs, {
|
|
@@ -13159,6 +13943,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13159
13943
|
});
|
|
13160
13944
|
} catch (err) {
|
|
13161
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);
|
|
13162
13955
|
finish({
|
|
13163
13956
|
command: cmd,
|
|
13164
13957
|
args,
|
|
@@ -13171,6 +13964,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13171
13964
|
});
|
|
13172
13965
|
return;
|
|
13173
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
|
+
});
|
|
13174
13976
|
child.on("error", (err) => {
|
|
13175
13977
|
const isAbort = err && err.code === "ABORT_ERR";
|
|
13176
13978
|
const stderrText = isAbort ? `Aborted: ${err.message}` : err.message;
|
|
@@ -13179,6 +13981,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13179
13981
|
if (typeof pid === "number") registry.unregister(pid);
|
|
13180
13982
|
registry.afterCall(Date.now() - startedAt, true);
|
|
13181
13983
|
spool.finalize();
|
|
13984
|
+
emitCompletedOnce(isAbort ? 124 : 1, child.pid, isAbort ? "ABORT" : void 0);
|
|
13182
13985
|
finish({
|
|
13183
13986
|
command: cmd,
|
|
13184
13987
|
args,
|
|
@@ -13198,6 +14001,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13198
14001
|
}
|
|
13199
14002
|
const timer = setTimeout(() => {
|
|
13200
14003
|
killed = true;
|
|
14004
|
+
timedOut = true;
|
|
13201
14005
|
if (typeof pid === "number") registry.kill(pid);
|
|
13202
14006
|
else child.kill("SIGTERM");
|
|
13203
14007
|
}, timeout);
|
|
@@ -13212,11 +14016,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13212
14016
|
}
|
|
13213
14017
|
child.stdout?.on("data", (chunk) => {
|
|
13214
14018
|
const text = chunk.toString();
|
|
14019
|
+
stdoutBytes += chunk.byteLength;
|
|
14020
|
+
emitProcessOutput3({ pid, stream: "stdout", chunk });
|
|
13215
14021
|
if (stdout.length < MAX_OUTPUT2) stdout += text;
|
|
13216
14022
|
spool.write(text);
|
|
13217
14023
|
});
|
|
13218
14024
|
child.stderr?.on("data", (chunk) => {
|
|
13219
14025
|
const text = chunk.toString();
|
|
14026
|
+
stderrBytes += chunk.byteLength;
|
|
14027
|
+
emitProcessOutput3({ pid, stream: "stderr", chunk });
|
|
13220
14028
|
if (stderr.length < MAX_OUTPUT2) stderr += text;
|
|
13221
14029
|
spool.write(text);
|
|
13222
14030
|
});
|
|
@@ -13226,6 +14034,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
13226
14034
|
if (typeof pid === "number") registry.unregister(pid);
|
|
13227
14035
|
const durationMs = Date.now() - startedAt;
|
|
13228
14036
|
const exitCode = killed ? 124 : code ?? 1;
|
|
14037
|
+
emitCompletedOnce(exitCode, pid);
|
|
13229
14038
|
registry.afterCall(durationMs, exitCode !== 0);
|
|
13230
14039
|
const spooled = spool.finalize();
|
|
13231
14040
|
finish({
|
|
@@ -13301,7 +14110,7 @@ function guardedLookup(hostname2, options, callback) {
|
|
|
13301
14110
|
var pinnedAgent;
|
|
13302
14111
|
function getPinnedDispatcher() {
|
|
13303
14112
|
if (!pinnedAgent) {
|
|
13304
|
-
pinnedAgent = new Agent({ connect: { lookup: guardedLookup } });
|
|
14113
|
+
pinnedAgent = new Agent({ allowH2: false, connect: { lookup: guardedLookup } });
|
|
13305
14114
|
}
|
|
13306
14115
|
return pinnedAgent;
|
|
13307
14116
|
}
|
|
@@ -13403,7 +14212,7 @@ async function assertNotPrivate(hostname2) {
|
|
|
13403
14212
|
}
|
|
13404
14213
|
}
|
|
13405
14214
|
} catch (err) {
|
|
13406
|
-
if (err instanceof
|
|
14215
|
+
if (err instanceof ToolValidationError2) throw err;
|
|
13407
14216
|
}
|
|
13408
14217
|
}
|
|
13409
14218
|
}
|
|
@@ -13751,7 +14560,7 @@ async function detectFixer(cwd) {
|
|
|
13751
14560
|
init_util();
|
|
13752
14561
|
import { spawn as spawn9 } from "node:child_process";
|
|
13753
14562
|
import { statSync as statSync3 } from "node:fs";
|
|
13754
|
-
import { dirname as dirname12, resolve as resolve11, sep as
|
|
14563
|
+
import { dirname as dirname12, resolve as resolve11, sep as sep5 } from "node:path";
|
|
13755
14564
|
import { assessCommitSafety, buildChildEnv as buildChildEnv4 } from "@wrongstack/core";
|
|
13756
14565
|
var TIMEOUT_MS2 = 3e4;
|
|
13757
14566
|
var MAX_OUTPUT3 = 1e5;
|
|
@@ -13896,7 +14705,7 @@ function validateWorktreeInput(input, projectRoot) {
|
|
|
13896
14705
|
if ((input.worktreeAction === "add" || input.worktreeAction === "remove") && input.worktreePath) {
|
|
13897
14706
|
const root = resolve11(projectRoot);
|
|
13898
14707
|
const abs = resolve11(root, input.worktreePath);
|
|
13899
|
-
if (abs !== root && !abs.startsWith(root +
|
|
14708
|
+
if (abs !== root && !abs.startsWith(root + sep5)) {
|
|
13900
14709
|
return reject(`unsafe worktree path (escapes project root): ${input.worktreePath}`);
|
|
13901
14710
|
}
|
|
13902
14711
|
}
|
|
@@ -14059,8 +14868,8 @@ var WALK_CONCURRENCY = 16;
|
|
|
14059
14868
|
var globTool = {
|
|
14060
14869
|
name: "glob",
|
|
14061
14870
|
category: "Filesystem",
|
|
14062
|
-
description: "Find files
|
|
14063
|
-
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.",
|
|
14064
14873
|
selection: {
|
|
14065
14874
|
doNotUseWhen: "you need to search inside file contents.",
|
|
14066
14875
|
useInstead: ["grep"]
|
|
@@ -14248,8 +15057,8 @@ var NATIVE_MAX_FILE_BYTES = 1e6;
|
|
|
14248
15057
|
var grepTool = {
|
|
14249
15058
|
name: "grep",
|
|
14250
15059
|
category: "Search",
|
|
14251
|
-
description: "Search
|
|
14252
|
-
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.',
|
|
14253
15062
|
selection: {
|
|
14254
15063
|
doNotUseWhen: "you only need to locate files by name or path pattern.",
|
|
14255
15064
|
useInstead: ["glob"]
|
|
@@ -15271,7 +16080,8 @@ function toYaml(data, indent = 0) {
|
|
|
15271
16080
|
}
|
|
15272
16081
|
|
|
15273
16082
|
// src/kanban.ts
|
|
15274
|
-
import {
|
|
16083
|
+
import { randomUUID } from "node:crypto";
|
|
16084
|
+
import { deserializeTaskGraph as deserializeTaskGraph2, loadTasks as loadTasks2, serializeTaskGraph } from "@wrongstack/core";
|
|
15275
16085
|
import {
|
|
15276
16086
|
addCheckToTask,
|
|
15277
16087
|
addColumn,
|
|
@@ -15283,18 +16093,19 @@ import {
|
|
|
15283
16093
|
assignTask,
|
|
15284
16094
|
claimReadyTask,
|
|
15285
16095
|
copyTaskToBoard,
|
|
15286
|
-
createBoard,
|
|
16096
|
+
createBoard as createBoard2,
|
|
16097
|
+
createBoardFromTaskGraph,
|
|
15287
16098
|
duplicateBoard,
|
|
15288
16099
|
exportBoardAsMarkdown,
|
|
15289
16100
|
exportBoardToTaskGraph,
|
|
15290
16101
|
generateBoardFromDescription,
|
|
15291
|
-
getBoard,
|
|
16102
|
+
getBoard as getBoard2,
|
|
15292
16103
|
getKanbanOrchestrationSnapshot,
|
|
15293
16104
|
getKanbanQueueHealth,
|
|
15294
16105
|
getTask,
|
|
15295
16106
|
getTaskChain,
|
|
15296
16107
|
heartbeatTaskAssignment,
|
|
15297
|
-
listBoards,
|
|
16108
|
+
listBoards as listBoards2,
|
|
15298
16109
|
listKanbanEvents,
|
|
15299
16110
|
listReadyTasks,
|
|
15300
16111
|
mergeTasks,
|
|
@@ -15302,26 +16113,255 @@ import {
|
|
|
15302
16113
|
parseLinesIntoTasks,
|
|
15303
16114
|
recoverStaleTaskAssignments,
|
|
15304
16115
|
releaseTaskClaim,
|
|
15305
|
-
removeBoard,
|
|
16116
|
+
removeBoard as removeBoard2,
|
|
15306
16117
|
removeColumn,
|
|
15307
16118
|
removeTask,
|
|
15308
16119
|
searchKanban,
|
|
15309
16120
|
setTaskChain,
|
|
15310
16121
|
splitTask,
|
|
15311
|
-
syncBoardFromTaskGraph,
|
|
16122
|
+
syncBoardFromTaskGraph as syncBoardFromTaskGraph2,
|
|
16123
|
+
transitionTask,
|
|
15312
16124
|
transferTaskToBoard,
|
|
15313
|
-
|
|
16125
|
+
touchKanbanPresence as touchKanbanPresence2,
|
|
16126
|
+
updateBoard as updateBoard2,
|
|
15314
16127
|
updateCheckOnTask,
|
|
15315
16128
|
updateColumn,
|
|
15316
16129
|
updateGoalMetricOnTask,
|
|
15317
16130
|
updateTask,
|
|
15318
16131
|
updateTaskAssignment
|
|
15319
16132
|
} from "@wrongstack/kanban";
|
|
16133
|
+
|
|
16134
|
+
// src/session-kanban.ts
|
|
16135
|
+
import {
|
|
16136
|
+
deserializeTaskGraph,
|
|
16137
|
+
GlobalMailbox,
|
|
16138
|
+
loadPlan,
|
|
16139
|
+
loadTasks,
|
|
16140
|
+
mutatePlan,
|
|
16141
|
+
mutateTasks
|
|
16142
|
+
} from "@wrongstack/core";
|
|
16143
|
+
import { resolveWstackPaths as resolveWstackPaths3 } from "@wrongstack/core/utils";
|
|
16144
|
+
import {
|
|
16145
|
+
createBoard,
|
|
16146
|
+
getBoard,
|
|
16147
|
+
getKanbanDir,
|
|
16148
|
+
listBoards,
|
|
16149
|
+
removeBoard,
|
|
16150
|
+
syncBoardFromTaskGraph,
|
|
16151
|
+
touchKanbanPresence,
|
|
16152
|
+
updateBoard
|
|
16153
|
+
} from "@wrongstack/kanban";
|
|
16154
|
+
var SESSION_BOARD_TAG = "session-work";
|
|
16155
|
+
var MIRROR_DISABLED_ENV = "WRONGSTACK_KANBAN_TASK_MIRROR";
|
|
16156
|
+
var SESSION_KANBAN_COLUMNS = [
|
|
16157
|
+
{ id: "todo", title: "Todo", order: 0, wipLimit: 0, color: "#2563eb" },
|
|
16158
|
+
{ id: "in-progress", title: "Running", order: 1, wipLimit: 1, color: "#d97706" },
|
|
16159
|
+
{ id: "review", title: "Preview", order: 2, wipLimit: 0, color: "#7c3aed" },
|
|
16160
|
+
{ id: "done", title: "Done", order: 3, wipLimit: 0, color: "#16a34a" }
|
|
16161
|
+
];
|
|
16162
|
+
var boardQueue = /* @__PURE__ */ new Map();
|
|
16163
|
+
var boardEnsures = /* @__PURE__ */ new Map();
|
|
16164
|
+
function boardKey(projectRoot, sessionId) {
|
|
16165
|
+
return `${projectRoot}\0${sessionId}`;
|
|
16166
|
+
}
|
|
16167
|
+
function sessionTag(sessionId) {
|
|
16168
|
+
return `session:${sessionId}`;
|
|
16169
|
+
}
|
|
16170
|
+
function sessionBoardTitle(sessionId) {
|
|
16171
|
+
const leaf = sessionId.split(/[\\/]/).filter(Boolean).pop() ?? sessionId;
|
|
16172
|
+
return `Session ${leaf.slice(0, 12)}`;
|
|
16173
|
+
}
|
|
16174
|
+
function sessionBoardTags(sessionId) {
|
|
16175
|
+
return ["session", SESSION_BOARD_TAG, sessionTag(sessionId)];
|
|
16176
|
+
}
|
|
16177
|
+
function sameColumns(columns) {
|
|
16178
|
+
return columns.length === SESSION_KANBAN_COLUMNS.length && columns.every((column, index) => column.id === SESSION_KANBAN_COLUMNS[index]?.id);
|
|
16179
|
+
}
|
|
16180
|
+
async function ensureSessionKanbanBoard(projectRoot, sessionId) {
|
|
16181
|
+
if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return null;
|
|
16182
|
+
const key = boardKey(projectRoot, sessionId);
|
|
16183
|
+
const inFlight = boardEnsures.get(key);
|
|
16184
|
+
if (inFlight) return inFlight;
|
|
16185
|
+
const promise = (async () => {
|
|
16186
|
+
const summary = (await listBoards(projectRoot)).find(
|
|
16187
|
+
(board2) => board2.tags?.includes(sessionTag(sessionId))
|
|
16188
|
+
);
|
|
16189
|
+
let board = summary ? await getBoard(projectRoot, summary.id) : null;
|
|
16190
|
+
if (!board) {
|
|
16191
|
+
return createBoard(projectRoot, {
|
|
16192
|
+
title: sessionBoardTitle(sessionId),
|
|
16193
|
+
description: "Live session work: todos, tasks, and plan items.",
|
|
16194
|
+
tags: sessionBoardTags(sessionId),
|
|
16195
|
+
columns: SESSION_KANBAN_COLUMNS,
|
|
16196
|
+
generatedBy: `session-kanban:${sessionId}`
|
|
16197
|
+
});
|
|
16198
|
+
}
|
|
16199
|
+
if (!sameColumns(board.columns) || !board.tags?.includes(SESSION_BOARD_TAG)) {
|
|
16200
|
+
board = await updateBoard(projectRoot, board.id, {
|
|
16201
|
+
title: sessionBoardTitle(sessionId),
|
|
16202
|
+
description: "Live session work: todos, tasks, and plan items.",
|
|
16203
|
+
tags: [.../* @__PURE__ */ new Set([...board.tags ?? [], ...sessionBoardTags(sessionId)])],
|
|
16204
|
+
columns: SESSION_KANBAN_COLUMNS
|
|
16205
|
+
}) ?? board;
|
|
16206
|
+
}
|
|
16207
|
+
return board;
|
|
16208
|
+
})();
|
|
16209
|
+
boardEnsures.set(key, promise);
|
|
16210
|
+
try {
|
|
16211
|
+
return await promise;
|
|
16212
|
+
} finally {
|
|
16213
|
+
boardEnsures.delete(key);
|
|
16214
|
+
}
|
|
16215
|
+
}
|
|
16216
|
+
function enqueueBoardWork(projectRoot, sessionId, work) {
|
|
16217
|
+
const key = boardKey(projectRoot, sessionId);
|
|
16218
|
+
const previous = boardQueue.get(key) ?? Promise.resolve();
|
|
16219
|
+
const result = previous.catch(() => void 0).then(work);
|
|
16220
|
+
const tail = result.then(
|
|
16221
|
+
() => void 0,
|
|
16222
|
+
() => void 0
|
|
16223
|
+
);
|
|
16224
|
+
boardQueue.set(key, tail);
|
|
16225
|
+
void tail.then(() => {
|
|
16226
|
+
if (boardQueue.get(key) === tail) boardQueue.delete(key);
|
|
16227
|
+
});
|
|
16228
|
+
return result;
|
|
16229
|
+
}
|
|
16230
|
+
async function projectGraph(projectRoot, sessionId, graph, sourceSystem) {
|
|
16231
|
+
if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return null;
|
|
16232
|
+
return enqueueBoardWork(projectRoot, sessionId, async () => {
|
|
16233
|
+
const board = await ensureSessionKanbanBoard(projectRoot, sessionId);
|
|
16234
|
+
if (!board) return null;
|
|
16235
|
+
const result = await syncBoardFromTaskGraph(
|
|
16236
|
+
projectRoot,
|
|
16237
|
+
board.id,
|
|
16238
|
+
deserializeTaskGraph(graph),
|
|
16239
|
+
{
|
|
16240
|
+
sourceSystem,
|
|
16241
|
+
tags: [.../* @__PURE__ */ new Set([...board.tags ?? [], ...sessionBoardTags(sessionId)])],
|
|
16242
|
+
archiveMissingTasks: true,
|
|
16243
|
+
includeCompletedTasks: true
|
|
16244
|
+
}
|
|
16245
|
+
);
|
|
16246
|
+
return result?.board ?? null;
|
|
16247
|
+
});
|
|
16248
|
+
}
|
|
16249
|
+
function todoListToSerializedGraph(todos, sessionId) {
|
|
16250
|
+
const nodes = todos.map((todo, index) => ({
|
|
16251
|
+
id: todo.id,
|
|
16252
|
+
title: todo.content,
|
|
16253
|
+
description: todo.activeForm ?? "",
|
|
16254
|
+
type: "chore",
|
|
16255
|
+
priority: "medium",
|
|
16256
|
+
status: todo.status,
|
|
16257
|
+
createdAt: index,
|
|
16258
|
+
updatedAt: index
|
|
16259
|
+
}));
|
|
16260
|
+
return {
|
|
16261
|
+
id: `todo:${sessionId}`,
|
|
16262
|
+
specId: `todo:${sessionId}`,
|
|
16263
|
+
title: "Session todos",
|
|
16264
|
+
nodes,
|
|
16265
|
+
edges: [],
|
|
16266
|
+
rootNodes: nodes.map((node) => node.id),
|
|
16267
|
+
createdAt: 0,
|
|
16268
|
+
updatedAt: 0
|
|
16269
|
+
};
|
|
16270
|
+
}
|
|
16271
|
+
function taskFileToSerializedGraph(tasks, sessionId) {
|
|
16272
|
+
const ids = new Set(tasks.map((task) => task.id));
|
|
16273
|
+
const nodes = tasks.map((task, index) => ({
|
|
16274
|
+
id: task.id,
|
|
16275
|
+
title: task.title,
|
|
16276
|
+
description: task.description ?? "",
|
|
16277
|
+
type: task.type,
|
|
16278
|
+
priority: task.priority,
|
|
16279
|
+
status: task.status,
|
|
16280
|
+
...task.assignee ? { assignee: task.assignee } : {},
|
|
16281
|
+
...task.estimateHours !== void 0 ? { estimateHours: task.estimateHours } : {},
|
|
16282
|
+
createdAt: index,
|
|
16283
|
+
updatedAt: index
|
|
16284
|
+
}));
|
|
16285
|
+
const edges = tasks.flatMap(
|
|
16286
|
+
(task) => (task.dependsOn ?? []).filter((dependency) => ids.has(dependency)).map((dependency) => ({
|
|
16287
|
+
id: `${dependency}->${task.id}`,
|
|
16288
|
+
from: dependency,
|
|
16289
|
+
to: task.id,
|
|
16290
|
+
type: "depends_on"
|
|
16291
|
+
}))
|
|
16292
|
+
);
|
|
16293
|
+
const hasIncoming = new Set(edges.map((edge) => edge.to));
|
|
16294
|
+
const rootNodes = nodes.filter((node) => !hasIncoming.has(node.id)).map((node) => node.id);
|
|
16295
|
+
return {
|
|
16296
|
+
// Keep the historical graph id so existing mirrored task cards are reused.
|
|
16297
|
+
id: `session:${sessionId}`,
|
|
16298
|
+
specId: `session:${sessionId}`,
|
|
16299
|
+
title: "Session tasks",
|
|
16300
|
+
nodes,
|
|
16301
|
+
edges,
|
|
16302
|
+
rootNodes: rootNodes.length ? rootNodes : nodes[0] ? [nodes[0].id] : [],
|
|
16303
|
+
createdAt: 0,
|
|
16304
|
+
updatedAt: 0
|
|
16305
|
+
};
|
|
16306
|
+
}
|
|
16307
|
+
var PLAN_STATUS_TO_TASK = {
|
|
16308
|
+
open: "pending",
|
|
16309
|
+
in_progress: "in_progress",
|
|
16310
|
+
done: "completed"
|
|
16311
|
+
};
|
|
16312
|
+
function planFileToSerializedGraph(items, sessionId) {
|
|
16313
|
+
const nodes = items.map((item, index) => ({
|
|
16314
|
+
id: item.id,
|
|
16315
|
+
title: item.title,
|
|
16316
|
+
description: item.details ?? "",
|
|
16317
|
+
type: "chore",
|
|
16318
|
+
priority: "medium",
|
|
16319
|
+
status: PLAN_STATUS_TO_TASK[item.status],
|
|
16320
|
+
createdAt: index,
|
|
16321
|
+
updatedAt: index
|
|
16322
|
+
}));
|
|
16323
|
+
return {
|
|
16324
|
+
id: `plan:${sessionId}`,
|
|
16325
|
+
specId: `plan:${sessionId}`,
|
|
16326
|
+
title: "Session plan",
|
|
16327
|
+
nodes,
|
|
16328
|
+
edges: [],
|
|
16329
|
+
rootNodes: nodes.map((node) => node.id),
|
|
16330
|
+
createdAt: 0,
|
|
16331
|
+
updatedAt: 0
|
|
16332
|
+
};
|
|
16333
|
+
}
|
|
16334
|
+
function projectSessionTodosToKanban(projectRoot, todos, sessionId) {
|
|
16335
|
+
return projectGraph(
|
|
16336
|
+
projectRoot,
|
|
16337
|
+
sessionId,
|
|
16338
|
+
todoListToSerializedGraph(todos, sessionId),
|
|
16339
|
+
"session-todo"
|
|
16340
|
+
);
|
|
16341
|
+
}
|
|
16342
|
+
function projectSessionTasksToKanban(projectRoot, tasks, sessionId) {
|
|
16343
|
+
return projectGraph(
|
|
16344
|
+
projectRoot,
|
|
16345
|
+
sessionId,
|
|
16346
|
+
taskFileToSerializedGraph(tasks, sessionId),
|
|
16347
|
+
"session-task"
|
|
16348
|
+
);
|
|
16349
|
+
}
|
|
16350
|
+
function projectSessionPlanToKanban(projectRoot, items, sessionId) {
|
|
16351
|
+
return projectGraph(
|
|
16352
|
+
projectRoot,
|
|
16353
|
+
sessionId,
|
|
16354
|
+
planFileToSerializedGraph(items, sessionId),
|
|
16355
|
+
"session-plan"
|
|
16356
|
+
);
|
|
16357
|
+
}
|
|
16358
|
+
|
|
16359
|
+
// src/kanban.ts
|
|
15320
16360
|
var kanbanTool = {
|
|
15321
16361
|
name: "kanban",
|
|
15322
16362
|
category: "Project",
|
|
15323
|
-
description: "Manage project-scoped multi-kanban boards stored under .wrongstack/kanbans.
|
|
15324
|
-
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.",
|
|
15325
16365
|
permission: "confirm",
|
|
15326
16366
|
mutating: true,
|
|
15327
16367
|
capabilities: ["fs.write"],
|
|
@@ -15343,6 +16383,8 @@ var kanbanTool = {
|
|
|
15343
16383
|
"export_markdown",
|
|
15344
16384
|
"export_task_graph",
|
|
15345
16385
|
"sync_task_graph",
|
|
16386
|
+
"create_from_graph",
|
|
16387
|
+
"import_session_tasks",
|
|
15346
16388
|
"search_tasks",
|
|
15347
16389
|
"ready_tasks",
|
|
15348
16390
|
"snapshot",
|
|
@@ -15356,6 +16398,7 @@ var kanbanTool = {
|
|
|
15356
16398
|
"transfer_task",
|
|
15357
16399
|
"get_task",
|
|
15358
16400
|
"update_task",
|
|
16401
|
+
"transition_task",
|
|
15359
16402
|
"move_task",
|
|
15360
16403
|
"delete_task",
|
|
15361
16404
|
"set_chain",
|
|
@@ -15386,9 +16429,14 @@ var kanbanTool = {
|
|
|
15386
16429
|
targetColumnId: { type: "string" },
|
|
15387
16430
|
title: { type: "string" },
|
|
15388
16431
|
description: { type: "string" },
|
|
16432
|
+
dueDate: { type: "string" },
|
|
15389
16433
|
tags: { type: "array", items: { type: "string" } },
|
|
15390
16434
|
labels: { type: "array", items: { type: "string" } },
|
|
15391
16435
|
priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
|
|
16436
|
+
taskType: {
|
|
16437
|
+
type: "string",
|
|
16438
|
+
enum: ["feature", "bugfix", "refactor", "docs", "test", "chore"]
|
|
16439
|
+
},
|
|
15392
16440
|
status: {
|
|
15393
16441
|
type: "string",
|
|
15394
16442
|
enum: [
|
|
@@ -15428,11 +16476,36 @@ var kanbanTool = {
|
|
|
15428
16476
|
type: "string",
|
|
15429
16477
|
enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
|
|
15430
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
|
+
},
|
|
15431
16491
|
releaseStatus: { type: "string", enum: ["pending", "ready", "blocked"] },
|
|
15432
16492
|
releaseReason: { type: "string" },
|
|
15433
16493
|
clearAssignee: { type: "boolean" },
|
|
15434
|
-
recoveryMode: { type: "string", enum: ["release", "retry", "fail"] },
|
|
16494
|
+
recoveryMode: { type: "string", enum: ["auto", "release", "retry", "fail"] },
|
|
15435
16495
|
recoveryNow: { type: "string" },
|
|
16496
|
+
recoveryPolicyFailOnCostCeiling: { type: "boolean" },
|
|
16497
|
+
recoveryPolicyReleaseOnFailureKinds: { type: "array", items: { type: "string" } },
|
|
16498
|
+
recoveryPolicyReleaseOnHeartbeatDue: { type: "boolean" },
|
|
16499
|
+
recoveryPolicyRetryPolicyOverride: {
|
|
16500
|
+
type: "string",
|
|
16501
|
+
enum: ["off", "incremental", "exponential"]
|
|
16502
|
+
},
|
|
16503
|
+
assignee: { type: "string" },
|
|
16504
|
+
costCeilingUsd: { type: "number" },
|
|
16505
|
+
retryPolicy: { type: "string", enum: ["off", "incremental", "exponential"] },
|
|
16506
|
+
lastFailureKind: { type: "string" },
|
|
16507
|
+
dependsOn: { type: "array", items: { type: "string" } },
|
|
16508
|
+
estimatedHours: { type: "number" },
|
|
15436
16509
|
taskGraph: { type: "object" },
|
|
15437
16510
|
graphId: { type: "string" },
|
|
15438
16511
|
specId: { type: "string" },
|
|
@@ -15485,538 +16558,632 @@ var kanbanTool = {
|
|
|
15485
16558
|
async execute(input, ctx) {
|
|
15486
16559
|
const projectRoot = ctx.projectRoot;
|
|
15487
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
|
+
};
|
|
15488
16577
|
try {
|
|
15489
|
-
|
|
15490
|
-
|
|
15491
|
-
|
|
15492
|
-
|
|
15493
|
-
|
|
15494
|
-
case "get_board": {
|
|
15495
|
-
const board = await requireBoard(projectRoot, input.boardId);
|
|
15496
|
-
return board ? okBoard(board) : fail("Board not found.");
|
|
15497
|
-
}
|
|
15498
|
-
case "create_board": {
|
|
15499
|
-
if (!input.title) return fail("create_board requires title.");
|
|
15500
|
-
const board = await createBoard(projectRoot, {
|
|
15501
|
-
title: input.title,
|
|
15502
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
15503
|
-
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
15504
|
-
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
|
|
15505
|
-
});
|
|
15506
|
-
return { ok: true, message: `Board created: ${board.title}`, board };
|
|
15507
|
-
}
|
|
15508
|
-
case "update_board": {
|
|
15509
|
-
if (!input.boardId) return fail("update_board requires boardId.");
|
|
15510
|
-
const board = await updateBoard(projectRoot, input.boardId, {
|
|
15511
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
15512
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
15513
|
-
...input.tags !== void 0 ? { tags: input.tags } : {}
|
|
15514
|
-
});
|
|
15515
|
-
return board ? okBoard(board, "Board updated.") : fail("Board not found.");
|
|
15516
|
-
}
|
|
15517
|
-
case "duplicate_board": {
|
|
15518
|
-
if (!input.boardId) return fail("duplicate_board requires boardId.");
|
|
15519
|
-
const board = await duplicateBoard(projectRoot, input.boardId, {
|
|
15520
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
15521
|
-
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
15522
|
-
...input.includeTasks !== void 0 ? { includeTasks: input.includeTasks } : {},
|
|
15523
|
-
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {},
|
|
15524
|
-
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {}
|
|
15525
|
-
});
|
|
15526
|
-
return board ? okBoard(board, "Board duplicated.") : fail("Board not found.");
|
|
15527
|
-
}
|
|
15528
|
-
case "delete_board": {
|
|
15529
|
-
if (!input.boardId) return fail("delete_board requires boardId.");
|
|
15530
|
-
const removed = await removeBoard(projectRoot, input.boardId);
|
|
15531
|
-
return { ok: removed, message: removed ? "Board deleted." : "Board not found." };
|
|
15532
|
-
}
|
|
15533
|
-
case "generate_board": {
|
|
15534
|
-
if (!input.description) return fail("generate_board requires description.");
|
|
15535
|
-
const boardInput = generateBoardFromDescription({
|
|
15536
|
-
description: input.description,
|
|
15537
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
15538
|
-
...input.context !== void 0 ? { context: input.context } : {},
|
|
15539
|
-
...input.columns !== void 0 ? { columns: input.columns } : {}
|
|
15540
|
-
});
|
|
15541
|
-
const board = await createBoard(projectRoot, boardInput);
|
|
15542
|
-
for (const taskInput2 of parseLinesIntoTasks(
|
|
15543
|
-
input.description,
|
|
15544
|
-
board.columns[0]?.id ?? "backlog"
|
|
15545
|
-
)) {
|
|
15546
|
-
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 };
|
|
15547
16583
|
}
|
|
15548
|
-
|
|
15549
|
-
|
|
15550
|
-
|
|
15551
|
-
const board = await requireBoard(projectRoot, input.boardId);
|
|
15552
|
-
if (!board) return fail("Board not found.");
|
|
15553
|
-
return {
|
|
15554
|
-
ok: true,
|
|
15555
|
-
message: "Board exported.",
|
|
15556
|
-
board,
|
|
15557
|
-
markdown: exportBoardAsMarkdown(board)
|
|
15558
|
-
};
|
|
15559
|
-
}
|
|
15560
|
-
case "export_task_graph": {
|
|
15561
|
-
if (!input.boardId) return fail("export_task_graph requires boardId.");
|
|
15562
|
-
const exported = await exportBoardToTaskGraph(projectRoot, input.boardId, {
|
|
15563
|
-
...input.graphId !== void 0 ? { graphId: input.graphId } : {},
|
|
15564
|
-
...input.specId !== void 0 ? { specId: input.specId } : {},
|
|
15565
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
15566
|
-
...input.preserveOriginTaskIds !== void 0 ? { preserveOriginTaskIds: input.preserveOriginTaskIds } : {},
|
|
15567
|
-
...input.includeArchived !== void 0 ? { includeArchived: input.includeArchived } : {}
|
|
15568
|
-
});
|
|
15569
|
-
if (!exported) return fail("Board not found.");
|
|
15570
|
-
return {
|
|
15571
|
-
ok: true,
|
|
15572
|
-
message: `Task graph exported with ${exported.graph.nodes.size} node(s).`,
|
|
15573
|
-
board: exported.board,
|
|
15574
|
-
taskGraph: serializeTaskGraph(exported.graph)
|
|
15575
|
-
};
|
|
15576
|
-
}
|
|
15577
|
-
case "sync_task_graph": {
|
|
15578
|
-
if (!input.boardId || !input.taskGraph) {
|
|
15579
|
-
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.");
|
|
15580
16587
|
}
|
|
15581
|
-
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15586
|
-
|
|
15587
|
-
|
|
15588
|
-
|
|
15589
|
-
|
|
15590
|
-
...input.archiveMissingTasks !== void 0 ? { archiveMissingTasks: input.archiveMissingTasks } : {},
|
|
15591
|
-
...input.preserveManualDependencies !== void 0 ? { preserveManualDependencies: input.preserveManualDependencies } : {}
|
|
15592
|
-
});
|
|
15593
|
-
return result ? {
|
|
15594
|
-
ok: true,
|
|
15595
|
-
message: `Task graph synced: ${result.createdTaskIds.length} created, ${result.updatedTaskIds.length} updated, ${result.archivedTaskIds.length} archived.`,
|
|
15596
|
-
board: result.board
|
|
15597
|
-
} : fail("Board not found.");
|
|
15598
|
-
}
|
|
15599
|
-
case "search_tasks": {
|
|
15600
|
-
const tasks = await searchKanban(projectRoot, {
|
|
15601
|
-
query: input.query,
|
|
15602
|
-
boardId: input.boardId,
|
|
15603
|
-
assignedAgent: input.agentId,
|
|
15604
|
-
status: input.status,
|
|
15605
|
-
priority: input.priority,
|
|
15606
|
-
label: input.labels?.[0],
|
|
15607
|
-
chainId: input.chainId
|
|
15608
|
-
});
|
|
15609
|
-
return { ok: true, message: `${tasks.length} task(s) matched.`, tasks };
|
|
15610
|
-
}
|
|
15611
|
-
case "ready_tasks": {
|
|
15612
|
-
const tasks = await listReadyTasks(projectRoot, {
|
|
15613
|
-
query: input.query,
|
|
15614
|
-
boardId: input.boardId,
|
|
15615
|
-
assignedAgent: input.agentId,
|
|
15616
|
-
priority: input.priority,
|
|
15617
|
-
label: input.labels?.[0],
|
|
15618
|
-
chainId: input.chainId,
|
|
15619
|
-
limit: input.limit
|
|
15620
|
-
});
|
|
15621
|
-
return { ok: true, message: `${tasks.length} ready task(s).`, tasks };
|
|
15622
|
-
}
|
|
15623
|
-
case "snapshot": {
|
|
15624
|
-
const snapshot = await getKanbanOrchestrationSnapshot(projectRoot, {
|
|
15625
|
-
query: input.query,
|
|
15626
|
-
boardId: input.boardId,
|
|
15627
|
-
assignedAgent: input.agentId,
|
|
15628
|
-
status: input.status,
|
|
15629
|
-
priority: input.priority,
|
|
15630
|
-
label: input.labels?.[0],
|
|
15631
|
-
chainId: input.chainId
|
|
15632
|
-
});
|
|
15633
|
-
return {
|
|
15634
|
-
ok: true,
|
|
15635
|
-
message: `${snapshot.ready.length} ready, ${snapshot.running.length} running, ${snapshot.blocked.length} blocked.`,
|
|
15636
|
-
snapshot
|
|
15637
|
-
};
|
|
15638
|
-
}
|
|
15639
|
-
case "add_column": {
|
|
15640
|
-
if (!input.boardId || !input.title) return fail("add_column requires boardId and title.");
|
|
15641
|
-
const result = await addColumn(projectRoot, input.boardId, {
|
|
15642
|
-
title: input.title,
|
|
15643
|
-
...input.description !== void 0 ? { description: input.description } : {}
|
|
15644
|
-
});
|
|
15645
|
-
return result ? okBoard(result.board, "Column added.") : fail("Board not found.");
|
|
15646
|
-
}
|
|
15647
|
-
case "update_column": {
|
|
15648
|
-
if (!input.boardId || !input.columnId)
|
|
15649
|
-
return fail("update_column requires boardId and columnId.");
|
|
15650
|
-
const board = await updateColumn(projectRoot, input.boardId, input.columnId, {
|
|
15651
|
-
...input.title !== void 0 ? { title: input.title } : {},
|
|
15652
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
15653
|
-
...input.order !== void 0 ? { order: input.order } : {}
|
|
15654
|
-
});
|
|
15655
|
-
return board ? okBoard(board, "Column updated.") : fail("Column not found.");
|
|
15656
|
-
}
|
|
15657
|
-
case "delete_column": {
|
|
15658
|
-
if (!input.boardId || !input.columnId)
|
|
15659
|
-
return fail("delete_column requires boardId and columnId.");
|
|
15660
|
-
const board = await removeColumn(projectRoot, input.boardId, input.columnId, {
|
|
15661
|
-
moveTasksToColumnId: input.moveTasksToColumnId
|
|
15662
|
-
});
|
|
15663
|
-
return board ? okBoard(board, "Column deleted.") : fail("Column not found.");
|
|
15664
|
-
}
|
|
15665
|
-
case "add_task": {
|
|
15666
|
-
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
15667
|
-
const result = await addTask(projectRoot, input.boardId, taskInput(input));
|
|
15668
|
-
return result ? okTask(result.board, result.task, "Task added.") : fail("Board not found.");
|
|
15669
|
-
}
|
|
15670
|
-
case "split_task": {
|
|
15671
|
-
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
15672
|
-
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
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 };
|
|
15673
16597
|
}
|
|
15674
|
-
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
...input.chainChildren !== void 0 ? { chainChildren: input.chainChildren } : {},
|
|
15683
|
-
...input.rewireDependents !== void 0 ? { rewireDependents: input.rewireDependents } : {}
|
|
15684
|
-
});
|
|
15685
|
-
return result ? {
|
|
15686
|
-
ok: true,
|
|
15687
|
-
message: `${result.children.length} child task(s) created.`,
|
|
15688
|
-
board: result.board,
|
|
15689
|
-
task: result.parent,
|
|
15690
|
-
children: result.children
|
|
15691
|
-
} : fail("Task not found.");
|
|
15692
|
-
}
|
|
15693
|
-
case "merge_tasks": {
|
|
15694
|
-
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
15695
|
-
return fail("merge_tasks requires boardId, taskIds, and title.");
|
|
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.");
|
|
15696
16606
|
}
|
|
15697
|
-
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15706
|
-
|
|
15707
|
-
case "copy_task": {
|
|
15708
|
-
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
15709
|
-
return fail("copy_task requires boardId, taskId, and targetBoardId.");
|
|
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.");
|
|
15710
16617
|
}
|
|
15711
|
-
|
|
15712
|
-
|
|
15713
|
-
input.boardId
|
|
15714
|
-
|
|
15715
|
-
|
|
15716
|
-
|
|
15717
|
-
|
|
15718
|
-
|
|
15719
|
-
|
|
15720
|
-
...input.
|
|
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);
|
|
15721
16637
|
}
|
|
15722
|
-
|
|
15723
|
-
return result ? okTask(result.targetBoard, result.task, "Task copied to target board.") : fail("Board or task not found.");
|
|
15724
|
-
}
|
|
15725
|
-
case "transfer_task": {
|
|
15726
|
-
if (!input.boardId || !input.taskId || !input.targetBoardId) {
|
|
15727
|
-
return fail("transfer_task requires boardId, taskId, and targetBoardId.");
|
|
16638
|
+
return okBoard(await getBoard2(projectRoot, board.id) ?? board, "Board generated.");
|
|
15728
16639
|
}
|
|
15729
|
-
|
|
15730
|
-
projectRoot,
|
|
15731
|
-
|
|
15732
|
-
|
|
15733
|
-
|
|
15734
|
-
|
|
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)})`,
|
|
16733
|
+
sourceSystem: "session",
|
|
16734
|
+
tags
|
|
16735
|
+
});
|
|
16736
|
+
return {
|
|
16737
|
+
ok: true,
|
|
16738
|
+
message: `Imported ${file.tasks.length} session tasks into new board "${board.title}".`,
|
|
16739
|
+
board
|
|
16740
|
+
};
|
|
16741
|
+
}
|
|
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 };
|
|
16753
|
+
}
|
|
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 };
|
|
16765
|
+
}
|
|
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
|
+
};
|
|
16781
|
+
}
|
|
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.");
|
|
16816
|
+
}
|
|
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.");
|
|
16835
|
+
}
|
|
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 } : {},
|
|
15735
16844
|
...input.targetColumnId !== void 0 ? { targetColumnId: input.targetColumnId } : {},
|
|
15736
|
-
...input.order !== void 0 ? { targetOrder: input.order } : {},
|
|
15737
16845
|
...input.preserveAssignment !== void 0 ? { preserveAssignment: input.preserveAssignment } : {},
|
|
15738
|
-
...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.");
|
|
15739
16853
|
}
|
|
15740
|
-
|
|
15741
|
-
|
|
15742
|
-
|
|
15743
|
-
|
|
15744
|
-
|
|
15745
|
-
|
|
15746
|
-
|
|
15747
|
-
|
|
15748
|
-
|
|
15749
|
-
|
|
15750
|
-
|
|
15751
|
-
|
|
15752
|
-
|
|
15753
|
-
input.boardId,
|
|
15754
|
-
input.taskId,
|
|
15755
|
-
taskPatch(input)
|
|
15756
|
-
);
|
|
15757
|
-
return board ? okBoard(board, "Task updated.") : fail("Task not found.");
|
|
15758
|
-
}
|
|
15759
|
-
case "move_task": {
|
|
15760
|
-
if (!input.boardId || !input.taskId || !input.targetColumnId) {
|
|
15761
|
-
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.");
|
|
15762
16867
|
}
|
|
15763
|
-
|
|
15764
|
-
|
|
15765
|
-
|
|
15766
|
-
|
|
15767
|
-
|
|
15768
|
-
|
|
15769
|
-
|
|
15770
|
-
|
|
15771
|
-
|
|
15772
|
-
|
|
15773
|
-
|
|
15774
|
-
|
|
15775
|
-
|
|
15776
|
-
|
|
15777
|
-
|
|
15778
|
-
|
|
15779
|
-
|
|
15780
|
-
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.");
|
|
15781
16885
|
}
|
|
15782
|
-
|
|
15783
|
-
|
|
15784
|
-
|
|
15785
|
-
|
|
15786
|
-
});
|
|
15787
|
-
return result ? {
|
|
15788
|
-
ok: true,
|
|
15789
|
-
message: `Chain set: ${result.chainId}`,
|
|
15790
|
-
board: result.board,
|
|
15791
|
-
chain: result.tasks
|
|
15792
|
-
} : fail("Board or task not found.");
|
|
15793
|
-
}
|
|
15794
|
-
case "get_chain": {
|
|
15795
|
-
if (!input.boardId || !(input.taskId || input.chainId)) {
|
|
15796
|
-
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.");
|
|
15797
16890
|
}
|
|
15798
|
-
|
|
15799
|
-
|
|
15800
|
-
|
|
15801
|
-
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
} : fail("Chain not found.");
|
|
15809
|
-
}
|
|
15810
|
-
case "claim_task": {
|
|
15811
|
-
const result = await claimReadyTask(projectRoot, {
|
|
15812
|
-
...input.boardId !== void 0 ? { boardId: input.boardId } : {},
|
|
15813
|
-
...input.taskId !== void 0 ? { taskId: input.taskId } : {},
|
|
15814
|
-
...assignmentInput(input),
|
|
15815
|
-
status: input.assignmentStatus ?? "queued"
|
|
15816
|
-
});
|
|
15817
|
-
return result ? okTask(result.board, result.task, "Task claimed.") : fail("No ready kanban task matched the claim.");
|
|
15818
|
-
}
|
|
15819
|
-
case "release_task": {
|
|
15820
|
-
if (!input.boardId || !input.taskId) {
|
|
15821
|
-
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.");
|
|
15822
16901
|
}
|
|
15823
|
-
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
15833
|
-
|
|
15834
|
-
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
|
|
15838
|
-
|
|
15839
|
-
|
|
15840
|
-
|
|
15841
|
-
|
|
15842
|
-
|
|
15843
|
-
return fail("
|
|
15844
|
-
const assignmentStatus = input.assignmentStatus ?? (input.status === "completed" ? "completed" : input.error ? "failed" : void 0);
|
|
15845
|
-
const board = await updateTaskAssignment(projectRoot, input.boardId, input.taskId, {
|
|
15846
|
-
...assignmentStatus !== void 0 ? { status: assignmentStatus } : {},
|
|
15847
|
-
...input.subagentId !== void 0 ? { subagentId: input.subagentId } : {},
|
|
15848
|
-
...input.runTaskId !== void 0 ? { runTaskId: input.runTaskId } : {},
|
|
15849
|
-
...input.lastResult !== void 0 ? { lastResult: input.lastResult } : {},
|
|
15850
|
-
...input.error !== void 0 ? { error: input.error } : {},
|
|
15851
|
-
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
15852
|
-
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
15853
|
-
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
15854
|
-
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
15855
|
-
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
15856
|
-
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
15857
|
-
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {}
|
|
15858
|
-
});
|
|
15859
|
-
return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
|
|
15860
|
-
}
|
|
15861
|
-
case "heartbeat_assignment": {
|
|
15862
|
-
if (!input.boardId || !input.taskId) {
|
|
15863
|
-
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.");
|
|
15864
16923
|
}
|
|
15865
|
-
|
|
15866
|
-
|
|
15867
|
-
|
|
15868
|
-
|
|
15869
|
-
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
15875
|
-
|
|
15876
|
-
|
|
15877
|
-
input.recoveryPolicyRetryPolicyOverride !== void 0
|
|
15878
|
-
].some(Boolean);
|
|
15879
|
-
const result = await recoverStaleTaskAssignments(projectRoot, input.boardId, {
|
|
15880
|
-
...input.recoveryMode !== void 0 ? { mode: input.recoveryMode } : {},
|
|
15881
|
-
...input.recoveryNow !== void 0 ? { now: input.recoveryNow } : {},
|
|
15882
|
-
...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
|
|
15883
|
-
...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {},
|
|
15884
|
-
...policyFields ? {
|
|
15885
|
-
policy: {
|
|
15886
|
-
...input.recoveryPolicyFailOnCostCeiling !== void 0 ? { failWhenCostCeilingSet: input.recoveryPolicyFailOnCostCeiling } : {},
|
|
15887
|
-
...input.recoveryPolicyReleaseOnFailureKinds !== void 0 ? {
|
|
15888
|
-
releaseOnFailureKinds: input.recoveryPolicyReleaseOnFailureKinds
|
|
15889
|
-
} : {},
|
|
15890
|
-
...input.recoveryPolicyReleaseOnHeartbeatDue !== void 0 ? {
|
|
15891
|
-
releaseOnHeartbeatDue: input.recoveryPolicyReleaseOnHeartbeatDue
|
|
15892
|
-
} : {},
|
|
15893
|
-
...input.recoveryPolicyRetryPolicyOverride !== void 0 ? {
|
|
15894
|
-
retryPolicyOverride: input.recoveryPolicyRetryPolicyOverride
|
|
15895
|
-
} : {}
|
|
15896
|
-
}
|
|
15897
|
-
} : {}
|
|
15898
|
-
});
|
|
15899
|
-
return result ? {
|
|
15900
|
-
ok: true,
|
|
15901
|
-
message: `Recovered ${result.tasks.length} stale assignment(s).`,
|
|
15902
|
-
board: result.board,
|
|
15903
|
-
recoveredTasks: result.tasks
|
|
15904
|
-
} : { ok: true, message: "No stale assignment matched.", recoveredTasks: [] };
|
|
15905
|
-
}
|
|
15906
|
-
case "events": {
|
|
15907
|
-
if (!input.boardId) return fail("events requires boardId.");
|
|
15908
|
-
const eventList = await listKanbanEvents(projectRoot, input.boardId);
|
|
15909
|
-
return {
|
|
15910
|
-
ok: true,
|
|
15911
|
-
message: `${eventList.length} event(s).`,
|
|
15912
|
-
events: eventList
|
|
15913
|
-
};
|
|
15914
|
-
}
|
|
15915
|
-
case "queue_health": {
|
|
15916
|
-
const health = await getKanbanQueueHealth(projectRoot, {
|
|
15917
|
-
...input.boardId !== void 0 ? { boardId: input.boardId } : {}
|
|
15918
|
-
});
|
|
15919
|
-
return {
|
|
15920
|
-
ok: true,
|
|
15921
|
-
message: `Counts: ready=${health.counts.ready}, running=${health.counts.running}, stale=${health.staleAssignments.count}.`,
|
|
15922
|
-
queueHealth: health
|
|
15923
|
-
};
|
|
15924
|
-
}
|
|
15925
|
-
case "add_dependency": {
|
|
15926
|
-
if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
|
|
15927
|
-
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.");
|
|
15928
16936
|
}
|
|
15929
|
-
|
|
15930
|
-
|
|
15931
|
-
|
|
15932
|
-
input.taskId
|
|
15933
|
-
|
|
15934
|
-
);
|
|
15935
|
-
return board ? okBoard(board, "Dependency added.") : fail("Task not found.");
|
|
15936
|
-
}
|
|
15937
|
-
case "add_goal_metric": {
|
|
15938
|
-
if (!input.boardId || !input.taskId || !input.metricName) {
|
|
15939
|
-
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.");
|
|
15940
16942
|
}
|
|
15941
|
-
|
|
15942
|
-
|
|
15943
|
-
|
|
15944
|
-
|
|
15945
|
-
|
|
15946
|
-
|
|
15947
|
-
|
|
15948
|
-
|
|
15949
|
-
|
|
15950
|
-
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
|
|
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.");
|
|
15954
16958
|
}
|
|
15955
|
-
|
|
15956
|
-
|
|
15957
|
-
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
|
|
15961
|
-
|
|
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,
|
|
15962
17108
|
...input.metricStatus !== void 0 ? { status: input.metricStatus } : {},
|
|
15963
17109
|
...input.metricTarget !== void 0 ? { target: input.metricTarget } : {},
|
|
15964
17110
|
...input.metricCurrent !== void 0 ? { current: input.metricCurrent } : {},
|
|
15965
17111
|
...input.metricUnit !== void 0 ? { unit: input.metricUnit } : {},
|
|
15966
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.");
|
|
15967
17119
|
}
|
|
15968
|
-
|
|
15969
|
-
|
|
15970
|
-
|
|
15971
|
-
|
|
15972
|
-
|
|
15973
|
-
|
|
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.");
|
|
15974
17135
|
}
|
|
15975
|
-
|
|
15976
|
-
|
|
15977
|
-
|
|
15978
|
-
|
|
15979
|
-
|
|
15980
|
-
|
|
15981
|
-
|
|
15982
|
-
|
|
15983
|
-
|
|
15984
|
-
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.");
|
|
15985
17146
|
}
|
|
15986
|
-
|
|
15987
|
-
|
|
15988
|
-
|
|
15989
|
-
input.taskId,
|
|
15990
|
-
input.checkId,
|
|
15991
|
-
{
|
|
15992
|
-
...input.checkDescription !== void 0 ? { description: input.checkDescription } : {},
|
|
15993
|
-
...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.");
|
|
15994
17150
|
}
|
|
15995
|
-
|
|
15996
|
-
|
|
15997
|
-
|
|
15998
|
-
|
|
15999
|
-
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
|
|
16009
|
-
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
|
|
16015
|
-
|
|
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}`);
|
|
16016
17184
|
}
|
|
16017
|
-
|
|
16018
|
-
|
|
16019
|
-
}
|
|
17185
|
+
})();
|
|
17186
|
+
return withPresence(result);
|
|
16020
17187
|
} catch (err) {
|
|
16021
17188
|
return fail(err instanceof Error ? err.message : String(err));
|
|
16022
17189
|
}
|
|
@@ -16032,7 +17199,7 @@ function okTask(board, task, message) {
|
|
|
16032
17199
|
return { ok: true, message, board, task };
|
|
16033
17200
|
}
|
|
16034
17201
|
async function requireBoard(projectRoot, boardId) {
|
|
16035
|
-
return boardId ?
|
|
17202
|
+
return boardId ? getBoard2(projectRoot, boardId) : null;
|
|
16036
17203
|
}
|
|
16037
17204
|
function taskInput(input) {
|
|
16038
17205
|
const assignment = hasAssignmentInput(input) ? assignmentForTaskCreate(input) : void 0;
|
|
@@ -16040,26 +17207,99 @@ function taskInput(input) {
|
|
|
16040
17207
|
title: input.title ?? "",
|
|
16041
17208
|
columnId: input.columnId,
|
|
16042
17209
|
description: input.description,
|
|
17210
|
+
dueDate: input.dueDate,
|
|
16043
17211
|
priority: input.priority,
|
|
17212
|
+
...input.taskType !== void 0 ? { type: input.taskType } : {},
|
|
16044
17213
|
status: input.status,
|
|
16045
17214
|
labels: input.labels,
|
|
16046
17215
|
...assignment?.agentId ?? assignment?.role ?? assignment?.name ? { assignedAgent: assignment.agentId ?? assignment.role ?? assignment.name } : {},
|
|
16047
17216
|
...input.assignee ?? assignment?.name ?? assignment?.agentId ? { assignee: input.assignee ?? assignment?.name ?? assignment?.agentId } : {},
|
|
16048
|
-
...input
|
|
16049
|
-
...
|
|
17217
|
+
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
17218
|
+
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
|
|
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
|
+
} : {}
|
|
16050
17280
|
};
|
|
16051
17281
|
}
|
|
17282
|
+
function mergedDependsOn(input) {
|
|
17283
|
+
const ids = [
|
|
17284
|
+
...input.dependsOn ?? [],
|
|
17285
|
+
...input.dependencyTaskId !== void 0 ? [input.dependencyTaskId] : []
|
|
17286
|
+
].filter((id, i, arr) => id && arr.indexOf(id) === i);
|
|
17287
|
+
return ids.length > 0 ? ids : void 0;
|
|
17288
|
+
}
|
|
16052
17289
|
function taskPatch(input) {
|
|
16053
17290
|
return {
|
|
16054
17291
|
title: input.title,
|
|
16055
17292
|
description: input.description,
|
|
17293
|
+
dueDate: input.dueDate,
|
|
16056
17294
|
columnId: input.columnId,
|
|
16057
17295
|
order: input.order,
|
|
16058
17296
|
priority: input.priority,
|
|
17297
|
+
...input.taskType !== void 0 ? { type: input.taskType } : {},
|
|
16059
17298
|
status: input.status,
|
|
16060
17299
|
labels: input.labels,
|
|
16061
17300
|
assignedAgent: input.agentId,
|
|
16062
|
-
...input
|
|
17301
|
+
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
17302
|
+
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {}
|
|
16063
17303
|
};
|
|
16064
17304
|
}
|
|
16065
17305
|
function assignmentInput(input) {
|
|
@@ -16804,15 +18044,15 @@ import {
|
|
|
16804
18044
|
deriveTodosFromPlanItem,
|
|
16805
18045
|
formatPlan,
|
|
16806
18046
|
getPlanTemplate,
|
|
16807
|
-
mutatePlan,
|
|
18047
|
+
mutatePlan as mutatePlan2,
|
|
16808
18048
|
removePlanItem,
|
|
16809
18049
|
setPlanItemStatus
|
|
16810
18050
|
} from "@wrongstack/core";
|
|
16811
18051
|
import {
|
|
16812
|
-
mutateTasks,
|
|
18052
|
+
mutateTasks as mutateTasks2,
|
|
16813
18053
|
formatTaskList
|
|
16814
18054
|
} from "@wrongstack/core";
|
|
16815
|
-
import { randomUUID } from "node:crypto";
|
|
18055
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
16816
18056
|
var planTool = {
|
|
16817
18057
|
name: "plan",
|
|
16818
18058
|
category: "Session",
|
|
@@ -16896,7 +18136,7 @@ var planTool = {
|
|
|
16896
18136
|
let didTaskify = false;
|
|
16897
18137
|
let plan;
|
|
16898
18138
|
try {
|
|
16899
|
-
plan = await
|
|
18139
|
+
plan = await mutatePlan2(planPath, sessionId, async (p) => {
|
|
16900
18140
|
switch (input.action) {
|
|
16901
18141
|
case "show":
|
|
16902
18142
|
break;
|
|
@@ -17022,6 +18262,7 @@ var planTool = {
|
|
|
17022
18262
|
open: 0
|
|
17023
18263
|
};
|
|
17024
18264
|
}
|
|
18265
|
+
await projectSessionPlanToKanban(ctx.projectRoot, plan.items, sessionId);
|
|
17025
18266
|
if (early) return early;
|
|
17026
18267
|
if (didTaskify) {
|
|
17027
18268
|
const taskPathRaw = ctx.meta["task.path"];
|
|
@@ -17035,9 +18276,9 @@ var planTool = {
|
|
|
17035
18276
|
}
|
|
17036
18277
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
17037
18278
|
try {
|
|
17038
|
-
const taskFile = await
|
|
18279
|
+
const taskFile = await mutateTasks2(taskPath, sessionId, (f) => {
|
|
17039
18280
|
f.tasks.push({
|
|
17040
|
-
id: `task_${
|
|
18281
|
+
id: `task_${randomUUID2()}`,
|
|
17041
18282
|
title: taskifyMeta.title,
|
|
17042
18283
|
description: taskifyMeta.details || void 0,
|
|
17043
18284
|
type: "feature",
|
|
@@ -18172,14 +19413,14 @@ import {
|
|
|
18172
19413
|
formatTaskList as formatTaskList2
|
|
18173
19414
|
} from "@wrongstack/core";
|
|
18174
19415
|
import {
|
|
18175
|
-
mutateTasks as
|
|
19416
|
+
mutateTasks as mutateTasks3
|
|
18176
19417
|
} from "@wrongstack/core";
|
|
18177
19418
|
import {
|
|
18178
19419
|
addPlanItem as addPlanItem2,
|
|
18179
|
-
mutatePlan as
|
|
19420
|
+
mutatePlan as mutatePlan3,
|
|
18180
19421
|
formatPlan as formatPlan2
|
|
18181
19422
|
} from "@wrongstack/core";
|
|
18182
|
-
import { randomUUID as
|
|
19423
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
18183
19424
|
function findTaskIndex(tasks, query) {
|
|
18184
19425
|
const asNum = Number.parseInt(query, 10);
|
|
18185
19426
|
if (!Number.isNaN(asNum)) {
|
|
@@ -18296,7 +19537,7 @@ var taskTool = {
|
|
|
18296
19537
|
let todosToReplace = null;
|
|
18297
19538
|
let file;
|
|
18298
19539
|
try {
|
|
18299
|
-
file = await
|
|
19540
|
+
file = await mutateTasks3(taskPath, sessionId, async (f) => {
|
|
18300
19541
|
switch (input.action) {
|
|
18301
19542
|
case "show":
|
|
18302
19543
|
break;
|
|
@@ -18367,7 +19608,7 @@ var taskTool = {
|
|
|
18367
19608
|
}
|
|
18368
19609
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18369
19610
|
const newTask = {
|
|
18370
|
-
id: `task_${Date.now()}_${
|
|
19611
|
+
id: `task_${Date.now()}_${randomUUID3().slice(0, 8)}`,
|
|
18371
19612
|
title: t.title,
|
|
18372
19613
|
description: t.description,
|
|
18373
19614
|
type: t.type || "feature",
|
|
@@ -18428,7 +19669,7 @@ var taskTool = {
|
|
|
18428
19669
|
});
|
|
18429
19670
|
if (match.description) {
|
|
18430
19671
|
todos.push({
|
|
18431
|
-
id: `todo_${ts2}_${
|
|
19672
|
+
id: `todo_${ts2}_${randomUUID3().slice(0, 6)}`,
|
|
18432
19673
|
content: match.description.slice(0, 200),
|
|
18433
19674
|
status: "pending",
|
|
18434
19675
|
promotedFromTask: match.id
|
|
@@ -18437,7 +19678,7 @@ var taskTool = {
|
|
|
18437
19678
|
if (input.subtasks && input.subtasks.length > 0) {
|
|
18438
19679
|
for (const st of input.subtasks) {
|
|
18439
19680
|
todos.push({
|
|
18440
|
-
id: `todo_${ts2}_${
|
|
19681
|
+
id: `todo_${ts2}_${randomUUID3().slice(0, 6)}`,
|
|
18441
19682
|
content: st,
|
|
18442
19683
|
status: "pending",
|
|
18443
19684
|
promotedFromTask: match.id
|
|
@@ -18486,6 +19727,7 @@ var taskTool = {
|
|
|
18486
19727
|
};
|
|
18487
19728
|
}
|
|
18488
19729
|
if (todosToReplace) ctx.state.replaceTodos(todosToReplace);
|
|
19730
|
+
await projectSessionTasksToKanban(ctx.projectRoot, file.tasks, sessionId);
|
|
18489
19731
|
if (early) return early;
|
|
18490
19732
|
if (didPlanify) {
|
|
18491
19733
|
const { title, details } = planifyMeta;
|
|
@@ -18499,7 +19741,7 @@ var taskTool = {
|
|
|
18499
19741
|
}
|
|
18500
19742
|
let formatted = "";
|
|
18501
19743
|
try {
|
|
18502
|
-
await
|
|
19744
|
+
await mutatePlan3(planPath, sessionId, (pf) => {
|
|
18503
19745
|
const { plan: updated } = addPlanItem2(pf, title, details || void 0);
|
|
18504
19746
|
formatted = formatPlan2(updated);
|
|
18505
19747
|
return updated;
|
|
@@ -18731,8 +19973,7 @@ function parseResult(runner, result, duration) {
|
|
|
18731
19973
|
}
|
|
18732
19974
|
|
|
18733
19975
|
// src/todo.ts
|
|
18734
|
-
import { loadPlan, savePlan, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core";
|
|
18735
|
-
import { loadTasks, saveTasks } from "@wrongstack/core";
|
|
19976
|
+
import { loadPlan as loadPlan2, loadTasks as loadTasks3, savePlan, saveTasks, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core";
|
|
18736
19977
|
var todoTool = {
|
|
18737
19978
|
name: "todo",
|
|
18738
19979
|
category: "Session",
|
|
@@ -18741,7 +19982,7 @@ var todoTool = {
|
|
|
18741
19982
|
permission: "auto",
|
|
18742
19983
|
mutating: false,
|
|
18743
19984
|
// mutates only conversation state (ctx.todos), not external state — no confirmation needed
|
|
18744
|
-
timeoutMs:
|
|
19985
|
+
timeoutMs: 5e3,
|
|
18745
19986
|
capabilities: ["session.todo"],
|
|
18746
19987
|
icon: "todo",
|
|
18747
19988
|
inputSchema: {
|
|
@@ -18793,16 +20034,21 @@ var todoTool = {
|
|
|
18793
20034
|
}
|
|
18794
20035
|
}
|
|
18795
20036
|
ctx.state.replaceTodos(items);
|
|
20037
|
+
await projectSessionTodosToKanban(ctx.projectRoot, items, ctx.session?.id ?? "session");
|
|
18796
20038
|
const completedPlanIds = /* @__PURE__ */ new Set();
|
|
18797
20039
|
const completedTaskIds = /* @__PURE__ */ new Set();
|
|
18798
20040
|
const pendingPlanIds = /* @__PURE__ */ new Set();
|
|
18799
20041
|
const pendingTaskIds = /* @__PURE__ */ new Set();
|
|
18800
20042
|
for (const item of items) {
|
|
18801
20043
|
if (item.promotedFromPlan) {
|
|
18802
|
-
(item.status === "completed" ? completedPlanIds : pendingPlanIds).add(
|
|
20044
|
+
(item.status === "completed" ? completedPlanIds : pendingPlanIds).add(
|
|
20045
|
+
item.promotedFromPlan
|
|
20046
|
+
);
|
|
18803
20047
|
}
|
|
18804
20048
|
if (item.promotedFromTask) {
|
|
18805
|
-
(item.status === "completed" ? completedTaskIds : pendingTaskIds).add(
|
|
20049
|
+
(item.status === "completed" ? completedTaskIds : pendingTaskIds).add(
|
|
20050
|
+
item.promotedFromTask
|
|
20051
|
+
);
|
|
18806
20052
|
}
|
|
18807
20053
|
}
|
|
18808
20054
|
for (const planId of completedPlanIds) {
|
|
@@ -18810,7 +20056,7 @@ var todoTool = {
|
|
|
18810
20056
|
const planPath = ctx.meta["plan.path"];
|
|
18811
20057
|
if (typeof planPath !== "string" || !planPath) continue;
|
|
18812
20058
|
try {
|
|
18813
|
-
const plan = await
|
|
20059
|
+
const plan = await loadPlan2(planPath);
|
|
18814
20060
|
if (plan) {
|
|
18815
20061
|
const updated = setPlanItemStatus2(plan, planId, "done");
|
|
18816
20062
|
await savePlan(planPath, updated);
|
|
@@ -18823,7 +20069,7 @@ var todoTool = {
|
|
|
18823
20069
|
const taskPath = ctx.meta["task.path"];
|
|
18824
20070
|
if (typeof taskPath !== "string" || !taskPath) continue;
|
|
18825
20071
|
try {
|
|
18826
|
-
const file = await
|
|
20072
|
+
const file = await loadTasks3(taskPath);
|
|
18827
20073
|
if (file) {
|
|
18828
20074
|
const task = file.tasks.find((t) => t.id === taskId);
|
|
18829
20075
|
if (task && task.status !== "completed") {
|
|
@@ -19138,8 +20384,8 @@ var DEFAULT_IGNORE5 = [
|
|
|
19138
20384
|
var treeTool = {
|
|
19139
20385
|
name: "tree",
|
|
19140
20386
|
category: "Filesystem",
|
|
19141
|
-
description: "Display a
|
|
19142
|
-
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.",
|
|
19143
20389
|
permission: "auto",
|
|
19144
20390
|
mutating: false,
|
|
19145
20391
|
capabilities: ["fs.read"],
|
|
@@ -19542,9 +20788,6 @@ var OPTIONAL_TOOLS = [
|
|
|
19542
20788
|
toolUseTool,
|
|
19543
20789
|
batchToolUseTool,
|
|
19544
20790
|
toolHelpTool,
|
|
19545
|
-
codebaseIndexTool,
|
|
19546
|
-
codebaseSearchTool,
|
|
19547
|
-
codebaseStatsTool,
|
|
19548
20791
|
setWorkingDirTool
|
|
19549
20792
|
];
|
|
19550
20793
|
var builtinTools = [
|
|
@@ -19553,6 +20796,9 @@ var builtinTools = [
|
|
|
19553
20796
|
readTool,
|
|
19554
20797
|
writeTool,
|
|
19555
20798
|
editTool,
|
|
20799
|
+
codebaseStatsTool,
|
|
20800
|
+
codebaseSearchTool,
|
|
20801
|
+
codebaseIndexTool,
|
|
19556
20802
|
replaceTool,
|
|
19557
20803
|
globTool,
|
|
19558
20804
|
grepTool,
|
|
@@ -19587,9 +20833,6 @@ var builtinTools = [
|
|
|
19587
20833
|
toolUseTool,
|
|
19588
20834
|
batchToolUseTool,
|
|
19589
20835
|
toolHelpTool,
|
|
19590
|
-
codebaseIndexTool,
|
|
19591
|
-
codebaseSearchTool,
|
|
19592
|
-
codebaseStatsTool,
|
|
19593
20836
|
setWorkingDirTool
|
|
19594
20837
|
];
|
|
19595
20838
|
|