@wrongstack/tools 0.286.0 → 0.287.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/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.js +544 -118
- package/dist/builtin.js.map +4 -4
- package/dist/codebase-index/go-parser.d.ts.map +1 -1
- package/dist/codebase-index/index.js +190 -96
- package/dist/codebase-index/index.js.map +4 -4
- 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/worker.js +188 -94
- package/dist/codebase-index/worker.js.map +4 -4
- package/dist/edit.d.ts.map +1 -1
- package/dist/edit.js +36 -12
- package/dist/edit.js.map +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +818 -125
- package/dist/index.js.map +4 -4
- package/dist/kanban.d.ts +7 -2
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +158 -19
- package/dist/kanban.js.map +4 -4
- 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/pack.js +544 -118
- package/dist/pack.js.map +4 -4
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +154 -4
- package/dist/plan.js.map +4 -4
- package/dist/session-kanban.d.ts +38 -0
- package/dist/session-kanban.d.ts.map +1 -0
- package/dist/session-kanban.js +489 -0
- package/dist/session-kanban.js.map +7 -0
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +163 -4
- package/dist/task.js.map +4 -4
- package/dist/todo.d.ts.map +1 -1
- package/dist/todo.js +155 -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/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -2561,8 +2561,8 @@ async function scanDirectory(directory, depth, profiles, limits, state, signal)
|
|
|
2561
2561
|
collectFileEvidence(directory, fullPath, entry.name, profiles, state);
|
|
2562
2562
|
}
|
|
2563
2563
|
}
|
|
2564
|
-
function collectFileEvidence(directory, fullPath,
|
|
2565
|
-
const lower =
|
|
2564
|
+
function collectFileEvidence(directory, fullPath, basename9, profiles, state) {
|
|
2565
|
+
const lower = basename9.toLowerCase();
|
|
2566
2566
|
const extension = path4.extname(lower);
|
|
2567
2567
|
for (const profile of profiles) {
|
|
2568
2568
|
const detector = profile.detectors.find(
|
|
@@ -2573,7 +2573,7 @@ function collectFileEvidence(directory, fullPath, basename8, profiles, state) {
|
|
|
2573
2573
|
candidate.evidence.push({
|
|
2574
2574
|
kind: detector.kind,
|
|
2575
2575
|
path: fullPath,
|
|
2576
|
-
value:
|
|
2576
|
+
value: basename9,
|
|
2577
2577
|
weight: detector.weight
|
|
2578
2578
|
});
|
|
2579
2579
|
if (detector.kind === "manifest" || detector.kind === "config") {
|
|
@@ -6373,6 +6373,9 @@ ${hint}` : ""),
|
|
|
6373
6373
|
};
|
|
6374
6374
|
|
|
6375
6375
|
// src/batch-tool-use.ts
|
|
6376
|
+
import {
|
|
6377
|
+
GOVERNED_TOOL_EXECUTOR_META_KEY
|
|
6378
|
+
} from "@wrongstack/core";
|
|
6376
6379
|
var batchToolUseTool = {
|
|
6377
6380
|
name: "batch_tool_use",
|
|
6378
6381
|
category: "Meta",
|
|
@@ -6409,7 +6412,7 @@ var batchToolUseTool = {
|
|
|
6409
6412
|
},
|
|
6410
6413
|
required: ["calls"]
|
|
6411
6414
|
},
|
|
6412
|
-
async execute(input, ctx,
|
|
6415
|
+
async execute(input, ctx, _opts) {
|
|
6413
6416
|
if (!input?.calls || input.calls.length === 0) {
|
|
6414
6417
|
return {
|
|
6415
6418
|
results: [],
|
|
@@ -6419,18 +6422,33 @@ var batchToolUseTool = {
|
|
|
6419
6422
|
stop_on_error: false
|
|
6420
6423
|
};
|
|
6421
6424
|
}
|
|
6425
|
+
const governedExecute = ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY];
|
|
6426
|
+
if (typeof governedExecute !== "function") {
|
|
6427
|
+
return {
|
|
6428
|
+
results: input.calls.map((call) => ({
|
|
6429
|
+
tool: call.tool,
|
|
6430
|
+
success: false,
|
|
6431
|
+
error: "governed nested execution is unavailable; call the tool directly",
|
|
6432
|
+
executionMs: 0
|
|
6433
|
+
})),
|
|
6434
|
+
total: input.calls.length,
|
|
6435
|
+
succeeded: 0,
|
|
6436
|
+
failed: input.calls.length,
|
|
6437
|
+
stop_on_error: input.stop_on_error ?? false
|
|
6438
|
+
};
|
|
6439
|
+
}
|
|
6422
6440
|
const results = [];
|
|
6423
6441
|
let succeeded = 0;
|
|
6424
6442
|
let failed = 0;
|
|
6425
6443
|
if (input.parallel !== false) {
|
|
6426
|
-
const promises = input.calls.map(async (call) => executeSingle(call, ctx,
|
|
6444
|
+
const promises = input.calls.map(async (call) => executeSingle(call, ctx, governedExecute));
|
|
6427
6445
|
const allResults = await Promise.all(promises);
|
|
6428
6446
|
results.push(...allResults);
|
|
6429
6447
|
succeeded = allResults.filter((r) => r.success).length;
|
|
6430
6448
|
failed = allResults.filter((r) => !r.success).length;
|
|
6431
6449
|
} else {
|
|
6432
6450
|
for (const call of input.calls) {
|
|
6433
|
-
const result = await executeSingle(call, ctx,
|
|
6451
|
+
const result = await executeSingle(call, ctx, governedExecute);
|
|
6434
6452
|
results.push(result);
|
|
6435
6453
|
if (result.success) {
|
|
6436
6454
|
succeeded++;
|
|
@@ -6449,9 +6467,9 @@ var batchToolUseTool = {
|
|
|
6449
6467
|
};
|
|
6450
6468
|
}
|
|
6451
6469
|
};
|
|
6452
|
-
async function executeSingle(call, ctx,
|
|
6470
|
+
async function executeSingle(call, ctx, governedExecute) {
|
|
6453
6471
|
const start = Date.now();
|
|
6454
|
-
const tool = ctx.tools.find((
|
|
6472
|
+
const tool = ctx.tools.find((candidate) => candidate.name === call.tool);
|
|
6455
6473
|
if (!tool) {
|
|
6456
6474
|
return {
|
|
6457
6475
|
tool: call.tool,
|
|
@@ -6461,11 +6479,11 @@ async function executeSingle(call, ctx, opts) {
|
|
|
6461
6479
|
};
|
|
6462
6480
|
}
|
|
6463
6481
|
try {
|
|
6464
|
-
const result = await
|
|
6482
|
+
const result = await governedExecute(call.tool, call.input);
|
|
6465
6483
|
return {
|
|
6466
6484
|
tool: call.tool,
|
|
6467
|
-
success:
|
|
6468
|
-
result,
|
|
6485
|
+
success: result.success,
|
|
6486
|
+
...result.success ? { result: result.result } : { error: result.error ?? "nested tool failed" },
|
|
6469
6487
|
executionMs: Date.now() - start
|
|
6470
6488
|
};
|
|
6471
6489
|
} catch (e) {
|
|
@@ -6870,7 +6888,7 @@ var BrowserSessionManager = class {
|
|
|
6870
6888
|
constructor(options, launcher = defaultLauncher) {
|
|
6871
6889
|
this.launcher = launcher;
|
|
6872
6890
|
this.artifacts = new BrowserArtifactStore(options.artifactRoot);
|
|
6873
|
-
this.allowPrivateHosts = options.allowPrivateHosts ??
|
|
6891
|
+
this.allowPrivateHosts = options.allowPrivateHosts ?? true;
|
|
6874
6892
|
this.allowedPrivateOrigins = options.allowedPrivateOrigins ?? [];
|
|
6875
6893
|
this.networkProxy = new BrowserNetworkGuardProxy({
|
|
6876
6894
|
allowPrivateHosts: this.allowPrivateHosts,
|
|
@@ -8844,6 +8862,7 @@ function detectLang(file) {
|
|
|
8844
8862
|
}
|
|
8845
8863
|
|
|
8846
8864
|
// src/codebase-index/go-parser.ts
|
|
8865
|
+
init_win32_resolve();
|
|
8847
8866
|
import { spawn as spawn4 } from "node:child_process";
|
|
8848
8867
|
import * as os5 from "node:os";
|
|
8849
8868
|
import * as path14 from "node:path";
|
|
@@ -9152,27 +9171,42 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
9152
9171
|
try {
|
|
9153
9172
|
const scriptPath = path14.join(tmpDir, "parse.go");
|
|
9154
9173
|
await fs10.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
9155
|
-
const
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9174
|
+
const goBinary = resolveWin32Command("go");
|
|
9175
|
+
const goResult = await new Promise(
|
|
9176
|
+
(resolve15, reject) => {
|
|
9177
|
+
let settled = false;
|
|
9178
|
+
const proc = spawn4(goBinary, ["run", scriptPath], {
|
|
9179
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
9180
|
+
windowsHide: true
|
|
9181
|
+
});
|
|
9182
|
+
proc.on("error", (err) => {
|
|
9183
|
+
if (settled) return;
|
|
9184
|
+
settled = true;
|
|
9185
|
+
reject(err);
|
|
9186
|
+
});
|
|
9187
|
+
let stdout2 = "";
|
|
9188
|
+
proc.stdout?.on("data", (chunk) => {
|
|
9189
|
+
stdout2 += chunk.toString();
|
|
9190
|
+
});
|
|
9191
|
+
proc.stderr?.resume();
|
|
9192
|
+
proc.stdin?.write(content);
|
|
9193
|
+
proc.stdin?.end();
|
|
9194
|
+
const timer = setTimeout(() => {
|
|
9195
|
+
if (settled) return;
|
|
9196
|
+
settled = true;
|
|
9171
9197
|
proc.kill("SIGKILL");
|
|
9172
9198
|
reject(new Error("timeout"));
|
|
9173
|
-
}, 15e3)
|
|
9174
|
-
|
|
9175
|
-
|
|
9199
|
+
}, 15e3);
|
|
9200
|
+
timer.unref?.();
|
|
9201
|
+
proc.on("close", (code2) => {
|
|
9202
|
+
if (settled) return;
|
|
9203
|
+
settled = true;
|
|
9204
|
+
clearTimeout(timer);
|
|
9205
|
+
resolve15({ code: code2, stdout: stdout2 });
|
|
9206
|
+
});
|
|
9207
|
+
}
|
|
9208
|
+
);
|
|
9209
|
+
const { code, stdout } = goResult;
|
|
9176
9210
|
if (code !== 0 || !stdout.trim()) {
|
|
9177
9211
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
9178
9212
|
}
|
|
@@ -9199,7 +9233,8 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
9199
9233
|
}
|
|
9200
9234
|
|
|
9201
9235
|
// src/codebase-index/py-parser.ts
|
|
9202
|
-
|
|
9236
|
+
init_win32_resolve();
|
|
9237
|
+
import { spawn as spawn5, spawnSync } from "node:child_process";
|
|
9203
9238
|
import * as fs11 from "node:fs/promises";
|
|
9204
9239
|
import * as os6 from "node:os";
|
|
9205
9240
|
import * as path15 from "node:path";
|
|
@@ -9414,36 +9449,74 @@ visitor.visit(tree)
|
|
|
9414
9449
|
|
|
9415
9450
|
print(json.dumps([s.to_dict() for s in syms]))
|
|
9416
9451
|
`;
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9452
|
+
function resolvePython() {
|
|
9453
|
+
const candidates = process.platform === "win32" ? ["python3", "python", "py"] : ["python3", "python"];
|
|
9454
|
+
for (const name of candidates) {
|
|
9455
|
+
const resolved = resolveWin32Command(name);
|
|
9456
|
+
const result = spawnSync(resolved, ["--version"], {
|
|
9457
|
+
stdio: "pipe",
|
|
9458
|
+
timeout: 5e3
|
|
9459
|
+
});
|
|
9460
|
+
if (result.error) continue;
|
|
9461
|
+
if (result.status !== 0) continue;
|
|
9462
|
+
return resolved;
|
|
9463
|
+
}
|
|
9464
|
+
return null;
|
|
9465
|
+
}
|
|
9466
|
+
function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
9467
|
+
return new Promise((resolve15, reject) => {
|
|
9468
|
+
let settled = false;
|
|
9469
|
+
const proc = spawn5(pyBinary, [scriptPath, filePath], {
|
|
9427
9470
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9428
9471
|
windowsHide: true
|
|
9429
9472
|
});
|
|
9473
|
+
proc.on("error", (err) => {
|
|
9474
|
+
if (settled) return;
|
|
9475
|
+
settled = true;
|
|
9476
|
+
reject(err);
|
|
9477
|
+
});
|
|
9430
9478
|
proc.stdin?.write(content);
|
|
9431
9479
|
proc.stdin?.end();
|
|
9432
9480
|
let stdout = "";
|
|
9433
9481
|
proc.stdout?.on("data", (chunk) => {
|
|
9434
9482
|
stdout += chunk.toString();
|
|
9435
9483
|
});
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
)
|
|
9446
|
-
|
|
9484
|
+
proc.stderr?.resume();
|
|
9485
|
+
const timer = setTimeout(() => {
|
|
9486
|
+
if (settled) return;
|
|
9487
|
+
settled = true;
|
|
9488
|
+
proc.kill("SIGKILL");
|
|
9489
|
+
reject(new Error("timeout"));
|
|
9490
|
+
}, 15e3);
|
|
9491
|
+
timer.unref?.();
|
|
9492
|
+
proc.on("close", (code) => {
|
|
9493
|
+
if (settled) return;
|
|
9494
|
+
settled = true;
|
|
9495
|
+
clearTimeout(timer);
|
|
9496
|
+
resolve15({ code, stdout });
|
|
9497
|
+
});
|
|
9498
|
+
});
|
|
9499
|
+
}
|
|
9500
|
+
var _cachedScriptPath = null;
|
|
9501
|
+
var _cachedPyBinary = null;
|
|
9502
|
+
async function syncPyParse(filePath, content, lang) {
|
|
9503
|
+
try {
|
|
9504
|
+
if (!_cachedScriptPath) {
|
|
9505
|
+
const tmpDir = path15.join(os6.tmpdir(), "ws-py-parse");
|
|
9506
|
+
await fs11.mkdir(tmpDir, { recursive: true });
|
|
9507
|
+
_cachedScriptPath = path15.join(tmpDir, "parse.py");
|
|
9508
|
+
await fs11.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
9509
|
+
}
|
|
9510
|
+
if (!_cachedPyBinary) {
|
|
9511
|
+
_cachedPyBinary = resolvePython();
|
|
9512
|
+
if (!_cachedPyBinary) return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
9513
|
+
}
|
|
9514
|
+
const { code, stdout } = await spawnPyParser(
|
|
9515
|
+
_cachedPyBinary,
|
|
9516
|
+
_cachedScriptPath,
|
|
9517
|
+
filePath,
|
|
9518
|
+
content
|
|
9519
|
+
);
|
|
9447
9520
|
if (code !== 0 || !stdout.trim()) {
|
|
9448
9521
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
9449
9522
|
}
|
|
@@ -9468,6 +9541,7 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
9468
9541
|
}
|
|
9469
9542
|
|
|
9470
9543
|
// src/codebase-index/rs-parser.ts
|
|
9544
|
+
init_win32_resolve();
|
|
9471
9545
|
import { expectDefined as expectDefined3 } from "@wrongstack/core";
|
|
9472
9546
|
import { execFileSync, spawn as spawn6 } from "node:child_process";
|
|
9473
9547
|
import * as fs12 from "node:fs/promises";
|
|
@@ -9512,30 +9586,45 @@ async function tryNativeParse(file, content) {
|
|
|
9512
9586
|
const crateDir = path16.join(toolsDir, "syn-parser");
|
|
9513
9587
|
const tmpFile = path16.join(crateDir, "src", "input.rs");
|
|
9514
9588
|
await fs12.writeFile(tmpFile, content, "utf8");
|
|
9515
|
-
const
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9589
|
+
const cargoBinary = resolveWin32Command("cargo");
|
|
9590
|
+
const result = await new Promise(
|
|
9591
|
+
(resolve15, reject) => {
|
|
9592
|
+
let settled = false;
|
|
9593
|
+
const proc = spawn6(
|
|
9594
|
+
cargoBinary,
|
|
9595
|
+
["run", "--manifest-path", path16.join(toolsDir, "Cargo.toml")],
|
|
9596
|
+
{
|
|
9597
|
+
cwd: process.cwd(),
|
|
9598
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
9599
|
+
windowsHide: true
|
|
9600
|
+
}
|
|
9601
|
+
);
|
|
9602
|
+
proc.on("error", (err) => {
|
|
9603
|
+
if (settled) return;
|
|
9604
|
+
settled = true;
|
|
9605
|
+
reject(err);
|
|
9606
|
+
});
|
|
9607
|
+
let stdout2 = "";
|
|
9608
|
+
proc.stdout?.on("data", (chunk) => {
|
|
9609
|
+
stdout2 += chunk.toString();
|
|
9610
|
+
});
|
|
9611
|
+
proc.stderr?.resume();
|
|
9612
|
+
const timer = setTimeout(() => {
|
|
9613
|
+
if (settled) return;
|
|
9614
|
+
settled = true;
|
|
9534
9615
|
proc.kill("SIGKILL");
|
|
9535
9616
|
reject(new Error("timeout"));
|
|
9536
|
-
}, 15e3)
|
|
9537
|
-
|
|
9538
|
-
|
|
9617
|
+
}, 15e3);
|
|
9618
|
+
timer.unref?.();
|
|
9619
|
+
proc.on("close", (c) => {
|
|
9620
|
+
if (settled) return;
|
|
9621
|
+
settled = true;
|
|
9622
|
+
clearTimeout(timer);
|
|
9623
|
+
resolve15({ code: c, stdout: stdout2 });
|
|
9624
|
+
});
|
|
9625
|
+
}
|
|
9626
|
+
);
|
|
9627
|
+
const { code, stdout } = result;
|
|
9539
9628
|
if (code === 0 && stdout.trim()) {
|
|
9540
9629
|
const symbols = JSON.parse(stdout.trim());
|
|
9541
9630
|
return {
|
|
@@ -9630,9 +9719,9 @@ function parseSymbols5(opts) {
|
|
|
9630
9719
|
function regexParse2(opts) {
|
|
9631
9720
|
const { file, content, lang } = opts;
|
|
9632
9721
|
const symbols = [];
|
|
9633
|
-
const
|
|
9634
|
-
const isPackageJson =
|
|
9635
|
-
const isTsconfig =
|
|
9722
|
+
const basename9 = path17.basename(file).toLowerCase();
|
|
9723
|
+
const isPackageJson = basename9 === "package.json";
|
|
9724
|
+
const isTsconfig = basename9 === "tsconfig.json" || basename9 === "tsconfig.build.json";
|
|
9636
9725
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
9637
9726
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
9638
9727
|
const lines = content.split("\n");
|
|
@@ -11954,25 +12043,46 @@ function findLadderMatches(fileLf, oldLf) {
|
|
|
11954
12043
|
const needleLines = oldLf.split("\n");
|
|
11955
12044
|
if (needleLines.length > fileLines.length) return void 0;
|
|
11956
12045
|
const offsets = lineOffsets(fileLines);
|
|
12046
|
+
const fileTrimEnd = fileLines.map((l) => l.trimEnd());
|
|
12047
|
+
const needleTrimEnd = needleLines.map((l) => l.trimEnd());
|
|
11957
12048
|
const trailing = windowScan(
|
|
11958
|
-
|
|
11959
|
-
|
|
12049
|
+
fileTrimEnd,
|
|
12050
|
+
needleTrimEnd,
|
|
11960
12051
|
offsets,
|
|
11961
|
-
|
|
12052
|
+
fileLines,
|
|
12053
|
+
(a, b) => a === b
|
|
11962
12054
|
);
|
|
11963
12055
|
if (trailing.length > 0) return { tier: "trailing-whitespace", matches: trailing };
|
|
11964
|
-
const normalizedLen =
|
|
12056
|
+
const normalizedLen = needleTrimEnd.reduce((n, l) => n + l.trimStart().length, 0);
|
|
11965
12057
|
if (normalizedLen < MIN_NORMALIZED_NEEDLE_CHARS) return void 0;
|
|
11966
|
-
const
|
|
12058
|
+
const fileTrimmed = fileTrimEnd.map((l) => l.trimStart());
|
|
12059
|
+
const needleTrimmed = needleTrimEnd.map((l) => l.trimStart());
|
|
12060
|
+
const normalized = windowScan(
|
|
12061
|
+
fileTrimmed,
|
|
12062
|
+
needleTrimmed,
|
|
12063
|
+
offsets,
|
|
12064
|
+
fileLines,
|
|
12065
|
+
(a, b) => a === b
|
|
12066
|
+
);
|
|
11967
12067
|
if (normalized.length > 0) return { tier: "whitespace-normalized", matches: normalized };
|
|
11968
12068
|
return fuzzyScan(fileLines, needleLines, offsets);
|
|
11969
12069
|
}
|
|
11970
12070
|
function lineAt(text, pos) {
|
|
12071
|
+
if (pos < 512) {
|
|
12072
|
+
let line2 = 1;
|
|
12073
|
+
for (let i = 0; i < pos; i++) {
|
|
12074
|
+
if (text.charCodeAt(i) === 10) line2++;
|
|
12075
|
+
}
|
|
12076
|
+
return line2;
|
|
12077
|
+
}
|
|
11971
12078
|
let line = 1;
|
|
11972
|
-
|
|
11973
|
-
|
|
12079
|
+
let search = 0;
|
|
12080
|
+
while (true) {
|
|
12081
|
+
const idx = text.indexOf("\n", search);
|
|
12082
|
+
if (idx === -1 || idx >= pos) return line;
|
|
12083
|
+
search = idx + 1;
|
|
12084
|
+
line++;
|
|
11974
12085
|
}
|
|
11975
|
-
return line;
|
|
11976
12086
|
}
|
|
11977
12087
|
function lineOffsets(lines) {
|
|
11978
12088
|
const out = new Array(lines.length);
|
|
@@ -11991,19 +12101,19 @@ function windowToMatch(fileLines, offsets, start, windowLen) {
|
|
|
11991
12101
|
startLine: start + 1
|
|
11992
12102
|
};
|
|
11993
12103
|
}
|
|
11994
|
-
function windowScan(
|
|
12104
|
+
function windowScan(comparisonLines, needleLines, offsets, originalLines, eq) {
|
|
11995
12105
|
const n = needleLines.length;
|
|
11996
12106
|
const out = [];
|
|
11997
|
-
for (let i = 0; i + n <=
|
|
12107
|
+
for (let i = 0; i + n <= comparisonLines.length; i++) {
|
|
11998
12108
|
let all = true;
|
|
11999
12109
|
for (let j = 0; j < n; j++) {
|
|
12000
|
-
if (!eq(
|
|
12110
|
+
if (!eq(comparisonLines[i + j], needleLines[j])) {
|
|
12001
12111
|
all = false;
|
|
12002
12112
|
break;
|
|
12003
12113
|
}
|
|
12004
12114
|
}
|
|
12005
12115
|
if (all) {
|
|
12006
|
-
out.push(windowToMatch(
|
|
12116
|
+
out.push(windowToMatch(originalLines, offsets, i, n));
|
|
12007
12117
|
i += n - 1;
|
|
12008
12118
|
}
|
|
12009
12119
|
}
|
|
@@ -12325,8 +12435,10 @@ var editTool = {
|
|
|
12325
12435
|
note: autoReadNote
|
|
12326
12436
|
};
|
|
12327
12437
|
}
|
|
12438
|
+
opts?.signal?.throwIfAborted();
|
|
12328
12439
|
const ladder = findLadderMatches(fileLf, oldLf);
|
|
12329
12440
|
if (!ladder) {
|
|
12441
|
+
opts?.signal?.throwIfAborted();
|
|
12330
12442
|
const hint = nearestMatchHint(fileLf, oldLf);
|
|
12331
12443
|
throw new ToolValidationError({
|
|
12332
12444
|
message: `edit: no match for old_string in "${input.path}".${hint ? ` Nearest match near line ${hint.line}:
|
|
@@ -12394,6 +12506,7 @@ Compare this against your old_string and retry with the file's actual text.` : "
|
|
|
12394
12506
|
before: original,
|
|
12395
12507
|
after: newFile
|
|
12396
12508
|
});
|
|
12509
|
+
opts?.signal?.throwIfAborted();
|
|
12397
12510
|
const diff = unifiedDiff(original, newFile, {
|
|
12398
12511
|
fromFile: input.path,
|
|
12399
12512
|
toFile: input.path
|
|
@@ -15401,7 +15514,7 @@ function toYaml(data, indent = 0) {
|
|
|
15401
15514
|
}
|
|
15402
15515
|
|
|
15403
15516
|
// src/kanban.ts
|
|
15404
|
-
import { deserializeTaskGraph, serializeTaskGraph } from "@wrongstack/core";
|
|
15517
|
+
import { deserializeTaskGraph as deserializeTaskGraph2, loadTasks as loadTasks2, serializeTaskGraph } from "@wrongstack/core";
|
|
15405
15518
|
import {
|
|
15406
15519
|
addCheckToTask,
|
|
15407
15520
|
addColumn,
|
|
@@ -15413,18 +15526,19 @@ import {
|
|
|
15413
15526
|
assignTask,
|
|
15414
15527
|
claimReadyTask,
|
|
15415
15528
|
copyTaskToBoard,
|
|
15416
|
-
createBoard,
|
|
15529
|
+
createBoard as createBoard2,
|
|
15530
|
+
createBoardFromTaskGraph,
|
|
15417
15531
|
duplicateBoard,
|
|
15418
15532
|
exportBoardAsMarkdown,
|
|
15419
15533
|
exportBoardToTaskGraph,
|
|
15420
15534
|
generateBoardFromDescription,
|
|
15421
|
-
getBoard,
|
|
15535
|
+
getBoard as getBoard2,
|
|
15422
15536
|
getKanbanOrchestrationSnapshot,
|
|
15423
15537
|
getKanbanQueueHealth,
|
|
15424
15538
|
getTask,
|
|
15425
15539
|
getTaskChain,
|
|
15426
15540
|
heartbeatTaskAssignment,
|
|
15427
|
-
listBoards,
|
|
15541
|
+
listBoards as listBoards2,
|
|
15428
15542
|
listKanbanEvents,
|
|
15429
15543
|
listReadyTasks,
|
|
15430
15544
|
mergeTasks,
|
|
@@ -15432,21 +15546,494 @@ import {
|
|
|
15432
15546
|
parseLinesIntoTasks,
|
|
15433
15547
|
recoverStaleTaskAssignments,
|
|
15434
15548
|
releaseTaskClaim,
|
|
15435
|
-
removeBoard,
|
|
15549
|
+
removeBoard as removeBoard2,
|
|
15436
15550
|
removeColumn,
|
|
15437
15551
|
removeTask,
|
|
15438
15552
|
searchKanban,
|
|
15439
15553
|
setTaskChain,
|
|
15440
15554
|
splitTask,
|
|
15441
|
-
syncBoardFromTaskGraph,
|
|
15555
|
+
syncBoardFromTaskGraph as syncBoardFromTaskGraph2,
|
|
15442
15556
|
transferTaskToBoard,
|
|
15443
|
-
updateBoard,
|
|
15557
|
+
updateBoard as updateBoard2,
|
|
15444
15558
|
updateCheckOnTask,
|
|
15445
15559
|
updateColumn,
|
|
15446
15560
|
updateGoalMetricOnTask,
|
|
15447
15561
|
updateTask,
|
|
15448
15562
|
updateTaskAssignment
|
|
15449
15563
|
} from "@wrongstack/kanban";
|
|
15564
|
+
|
|
15565
|
+
// src/session-kanban.ts
|
|
15566
|
+
import { watch } from "node:fs";
|
|
15567
|
+
import { basename as basename8, dirname as dirname13 } from "node:path";
|
|
15568
|
+
import {
|
|
15569
|
+
deserializeTaskGraph,
|
|
15570
|
+
loadPlan,
|
|
15571
|
+
loadTasks,
|
|
15572
|
+
mutatePlan,
|
|
15573
|
+
mutateTasks
|
|
15574
|
+
} from "@wrongstack/core";
|
|
15575
|
+
import {
|
|
15576
|
+
createBoard,
|
|
15577
|
+
getBoard,
|
|
15578
|
+
listBoards,
|
|
15579
|
+
removeBoard,
|
|
15580
|
+
syncBoardFromTaskGraph,
|
|
15581
|
+
updateBoard
|
|
15582
|
+
} from "@wrongstack/kanban";
|
|
15583
|
+
var SESSION_BOARD_TAG = "session-work";
|
|
15584
|
+
var MIRROR_DISABLED_ENV = "WRONGSTACK_KANBAN_TASK_MIRROR";
|
|
15585
|
+
var SESSION_KANBAN_COLUMNS = [
|
|
15586
|
+
{ id: "todo", title: "Todo", order: 0, wipLimit: 0, color: "#2563eb" },
|
|
15587
|
+
{ id: "in-progress", title: "Running", order: 1, wipLimit: 1, color: "#d97706" },
|
|
15588
|
+
{ id: "review", title: "Preview", order: 2, wipLimit: 0, color: "#7c3aed" },
|
|
15589
|
+
{ id: "done", title: "Done", order: 3, wipLimit: 0, color: "#16a34a" }
|
|
15590
|
+
];
|
|
15591
|
+
var boardQueue = /* @__PURE__ */ new Map();
|
|
15592
|
+
var boardEnsures = /* @__PURE__ */ new Map();
|
|
15593
|
+
var bindings = /* @__PURE__ */ new WeakMap();
|
|
15594
|
+
var suppressedTodoMirrors = /* @__PURE__ */ new WeakSet();
|
|
15595
|
+
var activeSessionBoards = /* @__PURE__ */ new Map();
|
|
15596
|
+
function boardKey(projectRoot, sessionId) {
|
|
15597
|
+
return `${projectRoot}\0${sessionId}`;
|
|
15598
|
+
}
|
|
15599
|
+
function sessionTag(sessionId) {
|
|
15600
|
+
return `session:${sessionId}`;
|
|
15601
|
+
}
|
|
15602
|
+
function sessionBoardTitle(sessionId) {
|
|
15603
|
+
const leaf = sessionId.split(/[\\/]/).filter(Boolean).pop() ?? sessionId;
|
|
15604
|
+
return `Session ${leaf.slice(0, 12)}`;
|
|
15605
|
+
}
|
|
15606
|
+
function sessionBoardTags(sessionId) {
|
|
15607
|
+
return ["session", SESSION_BOARD_TAG, sessionTag(sessionId)];
|
|
15608
|
+
}
|
|
15609
|
+
function sessionIdFromTags(tags) {
|
|
15610
|
+
const tag = tags?.find((candidate) => candidate.startsWith("session:"));
|
|
15611
|
+
return tag?.slice("session:".length) || null;
|
|
15612
|
+
}
|
|
15613
|
+
function isOwnedSessionBoard(tags) {
|
|
15614
|
+
return Boolean(tags?.includes(SESSION_BOARD_TAG) && sessionIdFromTags(tags));
|
|
15615
|
+
}
|
|
15616
|
+
function retainActiveSessionBoard(projectRoot, sessionId) {
|
|
15617
|
+
const key = boardKey(projectRoot, sessionId);
|
|
15618
|
+
activeSessionBoards.set(key, (activeSessionBoards.get(key) ?? 0) + 1);
|
|
15619
|
+
}
|
|
15620
|
+
function releaseActiveSessionBoard(projectRoot, sessionId) {
|
|
15621
|
+
const key = boardKey(projectRoot, sessionId);
|
|
15622
|
+
const remaining = (activeSessionBoards.get(key) ?? 0) - 1;
|
|
15623
|
+
if (remaining > 0) activeSessionBoards.set(key, remaining);
|
|
15624
|
+
else activeSessionBoards.delete(key);
|
|
15625
|
+
}
|
|
15626
|
+
function isSessionBoardActive(projectRoot, sessionId) {
|
|
15627
|
+
return (activeSessionBoards.get(boardKey(projectRoot, sessionId)) ?? 0) > 0;
|
|
15628
|
+
}
|
|
15629
|
+
function sameColumns(columns) {
|
|
15630
|
+
return columns.length === SESSION_KANBAN_COLUMNS.length && columns.every((column, index) => column.id === SESSION_KANBAN_COLUMNS[index]?.id);
|
|
15631
|
+
}
|
|
15632
|
+
async function ensureSessionKanbanBoard(projectRoot, sessionId) {
|
|
15633
|
+
if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return null;
|
|
15634
|
+
const key = boardKey(projectRoot, sessionId);
|
|
15635
|
+
const inFlight = boardEnsures.get(key);
|
|
15636
|
+
if (inFlight) return inFlight;
|
|
15637
|
+
const promise = (async () => {
|
|
15638
|
+
const summary = (await listBoards(projectRoot)).find(
|
|
15639
|
+
(board2) => board2.tags?.includes(sessionTag(sessionId))
|
|
15640
|
+
);
|
|
15641
|
+
let board = summary ? await getBoard(projectRoot, summary.id) : null;
|
|
15642
|
+
if (!board) {
|
|
15643
|
+
return createBoard(projectRoot, {
|
|
15644
|
+
title: sessionBoardTitle(sessionId),
|
|
15645
|
+
description: "Live session work: todos, tasks, and plan items.",
|
|
15646
|
+
tags: sessionBoardTags(sessionId),
|
|
15647
|
+
columns: SESSION_KANBAN_COLUMNS,
|
|
15648
|
+
generatedBy: `session-kanban:${sessionId}`
|
|
15649
|
+
});
|
|
15650
|
+
}
|
|
15651
|
+
if (!sameColumns(board.columns) || !board.tags?.includes(SESSION_BOARD_TAG)) {
|
|
15652
|
+
board = await updateBoard(projectRoot, board.id, {
|
|
15653
|
+
title: sessionBoardTitle(sessionId),
|
|
15654
|
+
description: "Live session work: todos, tasks, and plan items.",
|
|
15655
|
+
tags: [.../* @__PURE__ */ new Set([...board.tags ?? [], ...sessionBoardTags(sessionId)])],
|
|
15656
|
+
columns: SESSION_KANBAN_COLUMNS
|
|
15657
|
+
}) ?? board;
|
|
15658
|
+
}
|
|
15659
|
+
return board;
|
|
15660
|
+
})();
|
|
15661
|
+
boardEnsures.set(key, promise);
|
|
15662
|
+
try {
|
|
15663
|
+
return await promise;
|
|
15664
|
+
} finally {
|
|
15665
|
+
boardEnsures.delete(key);
|
|
15666
|
+
}
|
|
15667
|
+
}
|
|
15668
|
+
function enqueueBoardWork(projectRoot, sessionId, work) {
|
|
15669
|
+
const key = boardKey(projectRoot, sessionId);
|
|
15670
|
+
const previous = boardQueue.get(key) ?? Promise.resolve();
|
|
15671
|
+
const result = previous.catch(() => void 0).then(work);
|
|
15672
|
+
const tail = result.then(
|
|
15673
|
+
() => void 0,
|
|
15674
|
+
() => void 0
|
|
15675
|
+
);
|
|
15676
|
+
boardQueue.set(key, tail);
|
|
15677
|
+
void tail.then(() => {
|
|
15678
|
+
if (boardQueue.get(key) === tail) boardQueue.delete(key);
|
|
15679
|
+
});
|
|
15680
|
+
return result;
|
|
15681
|
+
}
|
|
15682
|
+
async function removeEmptySessionBoard(projectRoot, boardId, sessionId) {
|
|
15683
|
+
return enqueueBoardWork(projectRoot, sessionId, async () => {
|
|
15684
|
+
if (isSessionBoardActive(projectRoot, sessionId)) return null;
|
|
15685
|
+
const board = await getBoard(projectRoot, boardId);
|
|
15686
|
+
if (!board || board.tasks.length > 0 || !isOwnedSessionBoard(board.tags)) return null;
|
|
15687
|
+
if (sessionIdFromTags(board.tags) !== sessionId) return null;
|
|
15688
|
+
return await removeBoard(projectRoot, board.id) ? board.id : null;
|
|
15689
|
+
});
|
|
15690
|
+
}
|
|
15691
|
+
async function cleanupSessionKanbanBoardIfEmpty(projectRoot, sessionId) {
|
|
15692
|
+
if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return [];
|
|
15693
|
+
if (isSessionBoardActive(projectRoot, sessionId)) return [];
|
|
15694
|
+
const candidates = (await listBoards(projectRoot)).filter(
|
|
15695
|
+
(board) => board.taskCount === 0 && isOwnedSessionBoard(board.tags) && sessionIdFromTags(board.tags) === sessionId
|
|
15696
|
+
);
|
|
15697
|
+
const removed = await Promise.all(
|
|
15698
|
+
candidates.map((board) => removeEmptySessionBoard(projectRoot, board.id, sessionId))
|
|
15699
|
+
);
|
|
15700
|
+
return removed.filter((boardId) => Boolean(boardId));
|
|
15701
|
+
}
|
|
15702
|
+
async function cleanupEmptySessionKanbanBoards(projectRoot, activeSessionId = "") {
|
|
15703
|
+
if (!projectRoot || process.env[MIRROR_DISABLED_ENV] === "0") return [];
|
|
15704
|
+
const candidates = (await listBoards(projectRoot)).flatMap((board) => {
|
|
15705
|
+
const ownerSessionId = sessionIdFromTags(board.tags);
|
|
15706
|
+
return board.taskCount === 0 && isOwnedSessionBoard(board.tags) && ownerSessionId && ownerSessionId !== activeSessionId && !isSessionBoardActive(projectRoot, ownerSessionId) ? [{ boardId: board.id, sessionId: ownerSessionId }] : [];
|
|
15707
|
+
});
|
|
15708
|
+
const removed = await Promise.all(
|
|
15709
|
+
candidates.map(
|
|
15710
|
+
({ boardId, sessionId }) => removeEmptySessionBoard(projectRoot, boardId, sessionId)
|
|
15711
|
+
)
|
|
15712
|
+
);
|
|
15713
|
+
return removed.filter((boardId) => Boolean(boardId));
|
|
15714
|
+
}
|
|
15715
|
+
async function projectGraph(projectRoot, sessionId, graph, sourceSystem) {
|
|
15716
|
+
if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return null;
|
|
15717
|
+
return enqueueBoardWork(projectRoot, sessionId, async () => {
|
|
15718
|
+
const board = await ensureSessionKanbanBoard(projectRoot, sessionId);
|
|
15719
|
+
if (!board) return null;
|
|
15720
|
+
const result = await syncBoardFromTaskGraph(
|
|
15721
|
+
projectRoot,
|
|
15722
|
+
board.id,
|
|
15723
|
+
deserializeTaskGraph(graph),
|
|
15724
|
+
{
|
|
15725
|
+
sourceSystem,
|
|
15726
|
+
tags: [.../* @__PURE__ */ new Set([...board.tags ?? [], ...sessionBoardTags(sessionId)])],
|
|
15727
|
+
archiveMissingTasks: true,
|
|
15728
|
+
includeCompletedTasks: true
|
|
15729
|
+
}
|
|
15730
|
+
);
|
|
15731
|
+
return result?.board ?? null;
|
|
15732
|
+
});
|
|
15733
|
+
}
|
|
15734
|
+
function todoListToSerializedGraph(todos, sessionId) {
|
|
15735
|
+
const nodes = todos.map((todo, index) => ({
|
|
15736
|
+
id: todo.id,
|
|
15737
|
+
title: todo.content,
|
|
15738
|
+
description: todo.activeForm ?? "",
|
|
15739
|
+
type: "chore",
|
|
15740
|
+
priority: "medium",
|
|
15741
|
+
status: todo.status,
|
|
15742
|
+
createdAt: index,
|
|
15743
|
+
updatedAt: index
|
|
15744
|
+
}));
|
|
15745
|
+
return {
|
|
15746
|
+
id: `todo:${sessionId}`,
|
|
15747
|
+
specId: `todo:${sessionId}`,
|
|
15748
|
+
title: "Session todos",
|
|
15749
|
+
nodes,
|
|
15750
|
+
edges: [],
|
|
15751
|
+
rootNodes: nodes.map((node) => node.id),
|
|
15752
|
+
createdAt: 0,
|
|
15753
|
+
updatedAt: 0
|
|
15754
|
+
};
|
|
15755
|
+
}
|
|
15756
|
+
function taskFileToSerializedGraph(tasks, sessionId) {
|
|
15757
|
+
const ids = new Set(tasks.map((task) => task.id));
|
|
15758
|
+
const nodes = tasks.map((task, index) => ({
|
|
15759
|
+
id: task.id,
|
|
15760
|
+
title: task.title,
|
|
15761
|
+
description: task.description ?? "",
|
|
15762
|
+
type: task.type,
|
|
15763
|
+
priority: task.priority,
|
|
15764
|
+
status: task.status,
|
|
15765
|
+
...task.assignee ? { assignee: task.assignee } : {},
|
|
15766
|
+
...task.estimateHours !== void 0 ? { estimateHours: task.estimateHours } : {},
|
|
15767
|
+
createdAt: index,
|
|
15768
|
+
updatedAt: index
|
|
15769
|
+
}));
|
|
15770
|
+
const edges = tasks.flatMap(
|
|
15771
|
+
(task) => (task.dependsOn ?? []).filter((dependency) => ids.has(dependency)).map((dependency) => ({
|
|
15772
|
+
id: `${dependency}->${task.id}`,
|
|
15773
|
+
from: dependency,
|
|
15774
|
+
to: task.id,
|
|
15775
|
+
type: "depends_on"
|
|
15776
|
+
}))
|
|
15777
|
+
);
|
|
15778
|
+
const hasIncoming = new Set(edges.map((edge) => edge.to));
|
|
15779
|
+
const rootNodes = nodes.filter((node) => !hasIncoming.has(node.id)).map((node) => node.id);
|
|
15780
|
+
return {
|
|
15781
|
+
// Keep the historical graph id so existing mirrored task cards are reused.
|
|
15782
|
+
id: `session:${sessionId}`,
|
|
15783
|
+
specId: `session:${sessionId}`,
|
|
15784
|
+
title: "Session tasks",
|
|
15785
|
+
nodes,
|
|
15786
|
+
edges,
|
|
15787
|
+
rootNodes: rootNodes.length ? rootNodes : nodes[0] ? [nodes[0].id] : [],
|
|
15788
|
+
createdAt: 0,
|
|
15789
|
+
updatedAt: 0
|
|
15790
|
+
};
|
|
15791
|
+
}
|
|
15792
|
+
var PLAN_STATUS_TO_TASK = {
|
|
15793
|
+
open: "pending",
|
|
15794
|
+
in_progress: "in_progress",
|
|
15795
|
+
done: "completed"
|
|
15796
|
+
};
|
|
15797
|
+
function planFileToSerializedGraph(items, sessionId) {
|
|
15798
|
+
const nodes = items.map((item, index) => ({
|
|
15799
|
+
id: item.id,
|
|
15800
|
+
title: item.title,
|
|
15801
|
+
description: item.details ?? "",
|
|
15802
|
+
type: "chore",
|
|
15803
|
+
priority: "medium",
|
|
15804
|
+
status: PLAN_STATUS_TO_TASK[item.status],
|
|
15805
|
+
createdAt: index,
|
|
15806
|
+
updatedAt: index
|
|
15807
|
+
}));
|
|
15808
|
+
return {
|
|
15809
|
+
id: `plan:${sessionId}`,
|
|
15810
|
+
specId: `plan:${sessionId}`,
|
|
15811
|
+
title: "Session plan",
|
|
15812
|
+
nodes,
|
|
15813
|
+
edges: [],
|
|
15814
|
+
rootNodes: nodes.map((node) => node.id),
|
|
15815
|
+
createdAt: 0,
|
|
15816
|
+
updatedAt: 0
|
|
15817
|
+
};
|
|
15818
|
+
}
|
|
15819
|
+
function projectSessionTodosToKanban(projectRoot, todos, sessionId) {
|
|
15820
|
+
return projectGraph(
|
|
15821
|
+
projectRoot,
|
|
15822
|
+
sessionId,
|
|
15823
|
+
todoListToSerializedGraph(todos, sessionId),
|
|
15824
|
+
"session-todo"
|
|
15825
|
+
);
|
|
15826
|
+
}
|
|
15827
|
+
function projectSessionTasksToKanban(projectRoot, tasks, sessionId) {
|
|
15828
|
+
return projectGraph(
|
|
15829
|
+
projectRoot,
|
|
15830
|
+
sessionId,
|
|
15831
|
+
taskFileToSerializedGraph(tasks, sessionId),
|
|
15832
|
+
"session-task"
|
|
15833
|
+
);
|
|
15834
|
+
}
|
|
15835
|
+
function projectSessionPlanToKanban(projectRoot, items, sessionId) {
|
|
15836
|
+
return projectGraph(
|
|
15837
|
+
projectRoot,
|
|
15838
|
+
sessionId,
|
|
15839
|
+
planFileToSerializedGraph(items, sessionId),
|
|
15840
|
+
"session-plan"
|
|
15841
|
+
);
|
|
15842
|
+
}
|
|
15843
|
+
function fireAndForget(work) {
|
|
15844
|
+
void work.catch(() => {
|
|
15845
|
+
});
|
|
15846
|
+
}
|
|
15847
|
+
function mirrorSessionTodosToKanban(projectRoot, todos, sessionId) {
|
|
15848
|
+
fireAndForget(projectSessionTodosToKanban(projectRoot, todos, sessionId));
|
|
15849
|
+
}
|
|
15850
|
+
function mirrorSessionTasksToKanban(projectRoot, tasks, sessionId) {
|
|
15851
|
+
fireAndForget(projectSessionTasksToKanban(projectRoot, tasks, sessionId));
|
|
15852
|
+
}
|
|
15853
|
+
function mirrorSessionPlanToKanban(projectRoot, items, sessionId) {
|
|
15854
|
+
fireAndForget(projectSessionPlanToKanban(projectRoot, items, sessionId));
|
|
15855
|
+
}
|
|
15856
|
+
function attachSessionKanbanMirror(context) {
|
|
15857
|
+
const existing = bindings.get(context);
|
|
15858
|
+
if (existing) return existing;
|
|
15859
|
+
const attachedProjectRoot = context.projectRoot ?? "";
|
|
15860
|
+
let registeredSessionId = "";
|
|
15861
|
+
const syncActiveSessionRegistration = () => {
|
|
15862
|
+
if (!attachedProjectRoot) return;
|
|
15863
|
+
const currentSessionId = context.session?.id ?? "";
|
|
15864
|
+
if (currentSessionId === registeredSessionId) return;
|
|
15865
|
+
if (registeredSessionId) {
|
|
15866
|
+
releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
|
|
15867
|
+
fireAndForget(cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId));
|
|
15868
|
+
}
|
|
15869
|
+
registeredSessionId = currentSessionId;
|
|
15870
|
+
if (registeredSessionId) {
|
|
15871
|
+
retainActiveSessionBoard(attachedProjectRoot, registeredSessionId);
|
|
15872
|
+
}
|
|
15873
|
+
};
|
|
15874
|
+
syncActiveSessionRegistration();
|
|
15875
|
+
let watcher = null;
|
|
15876
|
+
let watchedDir = "";
|
|
15877
|
+
let timer = null;
|
|
15878
|
+
const sessionId = () => context.session?.id ?? "";
|
|
15879
|
+
const refreshFiles = async () => {
|
|
15880
|
+
const id = sessionId();
|
|
15881
|
+
if (!id) return;
|
|
15882
|
+
const planPath = context.meta["plan.path"];
|
|
15883
|
+
if (typeof planPath === "string" && planPath) {
|
|
15884
|
+
const plan = await loadPlan(planPath);
|
|
15885
|
+
if (plan) await projectSessionPlanToKanban(context.projectRoot, plan.items, id);
|
|
15886
|
+
}
|
|
15887
|
+
const taskPath = context.meta["task.path"];
|
|
15888
|
+
if (typeof taskPath === "string" && taskPath) {
|
|
15889
|
+
const tasks = await loadTasks(taskPath);
|
|
15890
|
+
if (tasks) await projectSessionTasksToKanban(context.projectRoot, tasks.tasks, id);
|
|
15891
|
+
}
|
|
15892
|
+
};
|
|
15893
|
+
const configureWatcher = () => {
|
|
15894
|
+
const planPath = context.meta["plan.path"];
|
|
15895
|
+
const taskPath = context.meta["task.path"];
|
|
15896
|
+
const candidate = typeof planPath === "string" && planPath ? dirname13(planPath) : typeof taskPath === "string" && taskPath ? dirname13(taskPath) : "";
|
|
15897
|
+
if (!candidate || candidate === watchedDir) return;
|
|
15898
|
+
watcher?.close();
|
|
15899
|
+
watcher = null;
|
|
15900
|
+
watchedDir = candidate;
|
|
15901
|
+
try {
|
|
15902
|
+
watcher = watch(candidate, { persistent: false }, (_event, filename) => {
|
|
15903
|
+
const name = filename?.toString();
|
|
15904
|
+
const currentPlanPath = context.meta["plan.path"];
|
|
15905
|
+
const currentTaskPath = context.meta["task.path"];
|
|
15906
|
+
const planName = typeof currentPlanPath === "string" ? basename8(currentPlanPath) : "";
|
|
15907
|
+
const taskName = typeof currentTaskPath === "string" ? basename8(currentTaskPath) : "";
|
|
15908
|
+
if (name && name !== planName && name !== taskName) return;
|
|
15909
|
+
if (timer) clearTimeout(timer);
|
|
15910
|
+
timer = setTimeout(() => fireAndForget(refreshFiles()), 60);
|
|
15911
|
+
});
|
|
15912
|
+
watcher.on("error", () => watcher?.close());
|
|
15913
|
+
} catch {
|
|
15914
|
+
watcher = null;
|
|
15915
|
+
watchedDir = "";
|
|
15916
|
+
}
|
|
15917
|
+
};
|
|
15918
|
+
const unsubscribe = context.state.onChange((change) => {
|
|
15919
|
+
if (change.kind === "todos_replaced" && !suppressedTodoMirrors.has(context)) {
|
|
15920
|
+
mirrorSessionTodosToKanban(
|
|
15921
|
+
context.projectRoot,
|
|
15922
|
+
change.completedSnapshot ?? change.todos,
|
|
15923
|
+
sessionId()
|
|
15924
|
+
);
|
|
15925
|
+
return;
|
|
15926
|
+
}
|
|
15927
|
+
if (change.kind === "meta_set" && (change.key === "plan.path" || change.key === "task.path")) {
|
|
15928
|
+
syncActiveSessionRegistration();
|
|
15929
|
+
configureWatcher();
|
|
15930
|
+
fireAndForget(ensureSessionKanbanBoard(context.projectRoot, sessionId()));
|
|
15931
|
+
fireAndForget(refreshFiles());
|
|
15932
|
+
}
|
|
15933
|
+
});
|
|
15934
|
+
configureWatcher();
|
|
15935
|
+
const detach = () => {
|
|
15936
|
+
unsubscribe();
|
|
15937
|
+
if (timer) clearTimeout(timer);
|
|
15938
|
+
watcher?.close();
|
|
15939
|
+
bindings.delete(context);
|
|
15940
|
+
if (attachedProjectRoot && registeredSessionId) {
|
|
15941
|
+
releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
|
|
15942
|
+
fireAndForget(cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId));
|
|
15943
|
+
registeredSessionId = "";
|
|
15944
|
+
}
|
|
15945
|
+
};
|
|
15946
|
+
bindings.set(context, detach);
|
|
15947
|
+
return detach;
|
|
15948
|
+
}
|
|
15949
|
+
async function hydrateSessionKanban(context) {
|
|
15950
|
+
const id = context.session?.id ?? "";
|
|
15951
|
+
if (!id) return null;
|
|
15952
|
+
await cleanupEmptySessionKanbanBoards(context.projectRoot, id);
|
|
15953
|
+
let board = await ensureSessionKanbanBoard(context.projectRoot, id);
|
|
15954
|
+
if (context.todos.length) {
|
|
15955
|
+
board = await projectSessionTodosToKanban(context.projectRoot, context.todos, id);
|
|
15956
|
+
}
|
|
15957
|
+
const planPath = context.meta["plan.path"];
|
|
15958
|
+
if (typeof planPath === "string" && planPath) {
|
|
15959
|
+
const plan = await loadPlan(planPath);
|
|
15960
|
+
if (plan) board = await projectSessionPlanToKanban(context.projectRoot, plan.items, id);
|
|
15961
|
+
}
|
|
15962
|
+
const taskPath = context.meta["task.path"];
|
|
15963
|
+
if (typeof taskPath === "string" && taskPath) {
|
|
15964
|
+
const tasks = await loadTasks(taskPath);
|
|
15965
|
+
if (tasks) board = await projectSessionTasksToKanban(context.projectRoot, tasks.tasks, id);
|
|
15966
|
+
}
|
|
15967
|
+
return board;
|
|
15968
|
+
}
|
|
15969
|
+
function sourceStatus(task) {
|
|
15970
|
+
if (task.status === "completed") return "completed";
|
|
15971
|
+
if (task.status === "in_progress") return "in_progress";
|
|
15972
|
+
if (task.status === "review") return "review";
|
|
15973
|
+
if (task.status === "blocked") return "blocked";
|
|
15974
|
+
if (task.status === "failed") return "failed";
|
|
15975
|
+
return "pending";
|
|
15976
|
+
}
|
|
15977
|
+
async function applySessionKanbanTaskToSource(context, task, options = {}) {
|
|
15978
|
+
const originId = task.origin?.taskId;
|
|
15979
|
+
const graphId = task.origin?.graphId ?? "";
|
|
15980
|
+
if (!originId) return { source: null };
|
|
15981
|
+
if (task.origin?.system === "session-todo" || graphId.startsWith("todo:")) {
|
|
15982
|
+
const next = options.remove ? context.todos.filter((todo) => todo.id !== originId) : context.todos.map(
|
|
15983
|
+
(todo) => todo.id === originId ? {
|
|
15984
|
+
...todo,
|
|
15985
|
+
content: task.title,
|
|
15986
|
+
status: sourceStatus(task) === "completed" ? "completed" : sourceStatus(task) === "in_progress" || sourceStatus(task) === "review" ? "in_progress" : "pending"
|
|
15987
|
+
} : todo
|
|
15988
|
+
);
|
|
15989
|
+
suppressedTodoMirrors.add(context);
|
|
15990
|
+
try {
|
|
15991
|
+
context.state.replaceTodos(next);
|
|
15992
|
+
} finally {
|
|
15993
|
+
suppressedTodoMirrors.delete(context);
|
|
15994
|
+
}
|
|
15995
|
+
return { source: "todo", todos: [...context.todos] };
|
|
15996
|
+
}
|
|
15997
|
+
const id = context.session?.id ?? "";
|
|
15998
|
+
if (task.origin?.system === "session-plan" || graphId.startsWith("plan:")) {
|
|
15999
|
+
const planPath = context.meta["plan.path"];
|
|
16000
|
+
if (typeof planPath !== "string" || !planPath) return { source: "plan" };
|
|
16001
|
+
const plan = await mutatePlan(planPath, id, (file) => ({
|
|
16002
|
+
...file,
|
|
16003
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16004
|
+
items: options.remove ? file.items.filter((item) => item.id !== originId) : file.items.map(
|
|
16005
|
+
(item) => item.id === originId ? {
|
|
16006
|
+
...item,
|
|
16007
|
+
title: task.title,
|
|
16008
|
+
details: task.description,
|
|
16009
|
+
status: task.status === "completed" ? "done" : task.status === "in_progress" || task.status === "review" ? "in_progress" : "open",
|
|
16010
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16011
|
+
} : item
|
|
16012
|
+
)
|
|
16013
|
+
}));
|
|
16014
|
+
return { source: "plan", plan };
|
|
16015
|
+
}
|
|
16016
|
+
if (task.origin?.system === "session-task" || task.origin?.system === "session" || graphId.startsWith("session:")) {
|
|
16017
|
+
const taskPath = context.meta["task.path"];
|
|
16018
|
+
if (typeof taskPath !== "string" || !taskPath) return { source: "task" };
|
|
16019
|
+
const tasks = await mutateTasks(taskPath, id, (file) => ({
|
|
16020
|
+
...file,
|
|
16021
|
+
tasks: options.remove ? file.tasks.filter((item) => item.id !== originId) : file.tasks.map(
|
|
16022
|
+
(item) => item.id === originId ? {
|
|
16023
|
+
...item,
|
|
16024
|
+
title: task.title,
|
|
16025
|
+
description: task.description,
|
|
16026
|
+
status: sourceStatus(task),
|
|
16027
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16028
|
+
} : item
|
|
16029
|
+
)
|
|
16030
|
+
}));
|
|
16031
|
+
return { source: "task", tasks };
|
|
16032
|
+
}
|
|
16033
|
+
return { source: null };
|
|
16034
|
+
}
|
|
16035
|
+
|
|
16036
|
+
// src/kanban.ts
|
|
15450
16037
|
var kanbanTool = {
|
|
15451
16038
|
name: "kanban",
|
|
15452
16039
|
category: "Project",
|
|
@@ -15473,6 +16060,8 @@ var kanbanTool = {
|
|
|
15473
16060
|
"export_markdown",
|
|
15474
16061
|
"export_task_graph",
|
|
15475
16062
|
"sync_task_graph",
|
|
16063
|
+
"create_from_graph",
|
|
16064
|
+
"import_session_tasks",
|
|
15476
16065
|
"search_tasks",
|
|
15477
16066
|
"ready_tasks",
|
|
15478
16067
|
"snapshot",
|
|
@@ -15519,6 +16108,10 @@ var kanbanTool = {
|
|
|
15519
16108
|
tags: { type: "array", items: { type: "string" } },
|
|
15520
16109
|
labels: { type: "array", items: { type: "string" } },
|
|
15521
16110
|
priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
|
|
16111
|
+
taskType: {
|
|
16112
|
+
type: "string",
|
|
16113
|
+
enum: ["feature", "bugfix", "refactor", "docs", "test", "chore"]
|
|
16114
|
+
},
|
|
15522
16115
|
status: {
|
|
15523
16116
|
type: "string",
|
|
15524
16117
|
enum: [
|
|
@@ -15561,8 +16154,21 @@ var kanbanTool = {
|
|
|
15561
16154
|
releaseStatus: { type: "string", enum: ["pending", "ready", "blocked"] },
|
|
15562
16155
|
releaseReason: { type: "string" },
|
|
15563
16156
|
clearAssignee: { type: "boolean" },
|
|
15564
|
-
recoveryMode: { type: "string", enum: ["release", "retry", "fail"] },
|
|
16157
|
+
recoveryMode: { type: "string", enum: ["auto", "release", "retry", "fail"] },
|
|
15565
16158
|
recoveryNow: { type: "string" },
|
|
16159
|
+
recoveryPolicyFailOnCostCeiling: { type: "boolean" },
|
|
16160
|
+
recoveryPolicyReleaseOnFailureKinds: { type: "array", items: { type: "string" } },
|
|
16161
|
+
recoveryPolicyReleaseOnHeartbeatDue: { type: "boolean" },
|
|
16162
|
+
recoveryPolicyRetryPolicyOverride: {
|
|
16163
|
+
type: "string",
|
|
16164
|
+
enum: ["off", "incremental", "exponential"]
|
|
16165
|
+
},
|
|
16166
|
+
assignee: { type: "string" },
|
|
16167
|
+
costCeilingUsd: { type: "number" },
|
|
16168
|
+
retryPolicy: { type: "string", enum: ["off", "incremental", "exponential"] },
|
|
16169
|
+
lastFailureKind: { type: "string" },
|
|
16170
|
+
dependsOn: { type: "array", items: { type: "string" } },
|
|
16171
|
+
estimatedHours: { type: "number" },
|
|
15566
16172
|
taskGraph: { type: "object" },
|
|
15567
16173
|
graphId: { type: "string" },
|
|
15568
16174
|
specId: { type: "string" },
|
|
@@ -15618,7 +16224,7 @@ var kanbanTool = {
|
|
|
15618
16224
|
try {
|
|
15619
16225
|
switch (input.action) {
|
|
15620
16226
|
case "list_boards": {
|
|
15621
|
-
const boards = await
|
|
16227
|
+
const boards = await listBoards2(projectRoot);
|
|
15622
16228
|
return { ok: true, message: `${boards.length} board(s).`, boards };
|
|
15623
16229
|
}
|
|
15624
16230
|
case "get_board": {
|
|
@@ -15627,7 +16233,7 @@ var kanbanTool = {
|
|
|
15627
16233
|
}
|
|
15628
16234
|
case "create_board": {
|
|
15629
16235
|
if (!input.title) return fail("create_board requires title.");
|
|
15630
|
-
const board = await
|
|
16236
|
+
const board = await createBoard2(projectRoot, {
|
|
15631
16237
|
title: input.title,
|
|
15632
16238
|
...input.description !== void 0 ? { description: input.description } : {},
|
|
15633
16239
|
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
@@ -15637,7 +16243,7 @@ var kanbanTool = {
|
|
|
15637
16243
|
}
|
|
15638
16244
|
case "update_board": {
|
|
15639
16245
|
if (!input.boardId) return fail("update_board requires boardId.");
|
|
15640
|
-
const board = await
|
|
16246
|
+
const board = await updateBoard2(projectRoot, input.boardId, {
|
|
15641
16247
|
...input.title !== void 0 ? { title: input.title } : {},
|
|
15642
16248
|
...input.description !== void 0 ? { description: input.description } : {},
|
|
15643
16249
|
...input.tags !== void 0 ? { tags: input.tags } : {}
|
|
@@ -15657,7 +16263,7 @@ var kanbanTool = {
|
|
|
15657
16263
|
}
|
|
15658
16264
|
case "delete_board": {
|
|
15659
16265
|
if (!input.boardId) return fail("delete_board requires boardId.");
|
|
15660
|
-
const removed = await
|
|
16266
|
+
const removed = await removeBoard2(projectRoot, input.boardId);
|
|
15661
16267
|
return { ok: removed, message: removed ? "Board deleted." : "Board not found." };
|
|
15662
16268
|
}
|
|
15663
16269
|
case "generate_board": {
|
|
@@ -15668,14 +16274,14 @@ var kanbanTool = {
|
|
|
15668
16274
|
...input.context !== void 0 ? { context: input.context } : {},
|
|
15669
16275
|
...input.columns !== void 0 ? { columns: input.columns } : {}
|
|
15670
16276
|
});
|
|
15671
|
-
const board = await
|
|
16277
|
+
const board = await createBoard2(projectRoot, boardInput);
|
|
15672
16278
|
for (const taskInput2 of parseLinesIntoTasks(
|
|
15673
16279
|
input.description,
|
|
15674
16280
|
board.columns[0]?.id ?? "backlog"
|
|
15675
16281
|
)) {
|
|
15676
16282
|
await addTask(projectRoot, board.id, taskInput2);
|
|
15677
16283
|
}
|
|
15678
|
-
return okBoard(await
|
|
16284
|
+
return okBoard(await getBoard2(projectRoot, board.id) ?? board, "Board generated.");
|
|
15679
16285
|
}
|
|
15680
16286
|
case "export_markdown": {
|
|
15681
16287
|
const board = await requireBoard(projectRoot, input.boardId);
|
|
@@ -15708,8 +16314,8 @@ var kanbanTool = {
|
|
|
15708
16314
|
if (!input.boardId || !input.taskGraph) {
|
|
15709
16315
|
return fail("sync_task_graph requires boardId and taskGraph.");
|
|
15710
16316
|
}
|
|
15711
|
-
const graph =
|
|
15712
|
-
const result = await
|
|
16317
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
16318
|
+
const result = await syncBoardFromTaskGraph2(projectRoot, input.boardId, graph, {
|
|
15713
16319
|
...input.title !== void 0 ? { title: input.title } : {},
|
|
15714
16320
|
...input.description !== void 0 ? { description: input.description } : {},
|
|
15715
16321
|
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
@@ -15726,6 +16332,59 @@ var kanbanTool = {
|
|
|
15726
16332
|
board: result.board
|
|
15727
16333
|
} : fail("Board not found.");
|
|
15728
16334
|
}
|
|
16335
|
+
case "create_from_graph": {
|
|
16336
|
+
if (!input.taskGraph) return fail("create_from_graph requires taskGraph.");
|
|
16337
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
16338
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
16339
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
16340
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
16341
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
16342
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
16343
|
+
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
16344
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
16345
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {}
|
|
16346
|
+
});
|
|
16347
|
+
return {
|
|
16348
|
+
ok: true,
|
|
16349
|
+
message: `Created board "${board.title}" from task graph with ${board.tasks.length} tasks.`,
|
|
16350
|
+
board
|
|
16351
|
+
};
|
|
16352
|
+
}
|
|
16353
|
+
case "import_session_tasks": {
|
|
16354
|
+
const taskPath = ctx.meta?.["task.path"];
|
|
16355
|
+
if (!taskPath) return fail("No session task file for this session.");
|
|
16356
|
+
const file = await loadTasks2(taskPath);
|
|
16357
|
+
if (!file || file.tasks.length === 0) return fail("No session tasks to import.");
|
|
16358
|
+
const sessionId = ctx.session?.id ?? file.sessionId ?? "session";
|
|
16359
|
+
const graph = deserializeTaskGraph2(taskFileToSerializedGraph(file.tasks, sessionId));
|
|
16360
|
+
const tags = ["session", `session:${sessionId}`];
|
|
16361
|
+
const existing = (await listBoards2(projectRoot)).find(
|
|
16362
|
+
(b) => b.tags?.includes(`session:${sessionId}`)
|
|
16363
|
+
);
|
|
16364
|
+
if (existing) {
|
|
16365
|
+
const result = await syncBoardFromTaskGraph2(projectRoot, existing.id, graph, {
|
|
16366
|
+
sourceSystem: "session",
|
|
16367
|
+
tags,
|
|
16368
|
+
archiveMissingTasks: true,
|
|
16369
|
+
includeCompletedTasks: true
|
|
16370
|
+
});
|
|
16371
|
+
return result ? {
|
|
16372
|
+
ok: true,
|
|
16373
|
+
message: `Synced ${file.tasks.length} session tasks into board "${result.board.title}".`,
|
|
16374
|
+
board: result.board
|
|
16375
|
+
} : fail("Session board vanished mid-sync.");
|
|
16376
|
+
}
|
|
16377
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
16378
|
+
title: `Session tasks (${sessionId.slice(0, 8)})`,
|
|
16379
|
+
sourceSystem: "session",
|
|
16380
|
+
tags
|
|
16381
|
+
});
|
|
16382
|
+
return {
|
|
16383
|
+
ok: true,
|
|
16384
|
+
message: `Imported ${file.tasks.length} session tasks into new board "${board.title}".`,
|
|
16385
|
+
board
|
|
16386
|
+
};
|
|
16387
|
+
}
|
|
15729
16388
|
case "search_tasks": {
|
|
15730
16389
|
const tasks = await searchKanban(projectRoot, {
|
|
15731
16390
|
query: input.query,
|
|
@@ -16162,7 +16821,7 @@ function okTask(board, task, message) {
|
|
|
16162
16821
|
return { ok: true, message, board, task };
|
|
16163
16822
|
}
|
|
16164
16823
|
async function requireBoard(projectRoot, boardId) {
|
|
16165
|
-
return boardId ?
|
|
16824
|
+
return boardId ? getBoard2(projectRoot, boardId) : null;
|
|
16166
16825
|
}
|
|
16167
16826
|
function taskInput(input) {
|
|
16168
16827
|
const assignment = hasAssignmentInput(input) ? assignmentForTaskCreate(input) : void 0;
|
|
@@ -16171,14 +16830,23 @@ function taskInput(input) {
|
|
|
16171
16830
|
columnId: input.columnId,
|
|
16172
16831
|
description: input.description,
|
|
16173
16832
|
priority: input.priority,
|
|
16833
|
+
...input.taskType !== void 0 ? { type: input.taskType } : {},
|
|
16174
16834
|
status: input.status,
|
|
16175
16835
|
labels: input.labels,
|
|
16176
16836
|
...assignment?.agentId ?? assignment?.role ?? assignment?.name ? { assignedAgent: assignment.agentId ?? assignment.role ?? assignment.name } : {},
|
|
16177
16837
|
...input.assignee ?? assignment?.name ?? assignment?.agentId ? { assignee: input.assignee ?? assignment?.name ?? assignment?.agentId } : {},
|
|
16178
|
-
...input
|
|
16838
|
+
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
16839
|
+
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
|
|
16179
16840
|
...assignment ? { assignment } : {}
|
|
16180
16841
|
};
|
|
16181
16842
|
}
|
|
16843
|
+
function mergedDependsOn(input) {
|
|
16844
|
+
const ids = [
|
|
16845
|
+
...input.dependsOn ?? [],
|
|
16846
|
+
...input.dependencyTaskId !== void 0 ? [input.dependencyTaskId] : []
|
|
16847
|
+
].filter((id, i, arr) => id && arr.indexOf(id) === i);
|
|
16848
|
+
return ids.length > 0 ? ids : void 0;
|
|
16849
|
+
}
|
|
16182
16850
|
function taskPatch(input) {
|
|
16183
16851
|
return {
|
|
16184
16852
|
title: input.title,
|
|
@@ -16186,10 +16854,12 @@ function taskPatch(input) {
|
|
|
16186
16854
|
columnId: input.columnId,
|
|
16187
16855
|
order: input.order,
|
|
16188
16856
|
priority: input.priority,
|
|
16857
|
+
...input.taskType !== void 0 ? { type: input.taskType } : {},
|
|
16189
16858
|
status: input.status,
|
|
16190
16859
|
labels: input.labels,
|
|
16191
16860
|
assignedAgent: input.agentId,
|
|
16192
|
-
...input
|
|
16861
|
+
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
16862
|
+
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {}
|
|
16193
16863
|
};
|
|
16194
16864
|
}
|
|
16195
16865
|
function assignmentInput(input) {
|
|
@@ -16934,12 +17604,12 @@ import {
|
|
|
16934
17604
|
deriveTodosFromPlanItem,
|
|
16935
17605
|
formatPlan,
|
|
16936
17606
|
getPlanTemplate,
|
|
16937
|
-
mutatePlan,
|
|
17607
|
+
mutatePlan as mutatePlan2,
|
|
16938
17608
|
removePlanItem,
|
|
16939
17609
|
setPlanItemStatus
|
|
16940
17610
|
} from "@wrongstack/core";
|
|
16941
17611
|
import {
|
|
16942
|
-
mutateTasks,
|
|
17612
|
+
mutateTasks as mutateTasks2,
|
|
16943
17613
|
formatTaskList
|
|
16944
17614
|
} from "@wrongstack/core";
|
|
16945
17615
|
import { randomUUID } from "node:crypto";
|
|
@@ -17026,7 +17696,7 @@ var planTool = {
|
|
|
17026
17696
|
let didTaskify = false;
|
|
17027
17697
|
let plan;
|
|
17028
17698
|
try {
|
|
17029
|
-
plan = await
|
|
17699
|
+
plan = await mutatePlan2(planPath, sessionId, async (p) => {
|
|
17030
17700
|
switch (input.action) {
|
|
17031
17701
|
case "show":
|
|
17032
17702
|
break;
|
|
@@ -17152,6 +17822,7 @@ var planTool = {
|
|
|
17152
17822
|
open: 0
|
|
17153
17823
|
};
|
|
17154
17824
|
}
|
|
17825
|
+
await projectSessionPlanToKanban(ctx.projectRoot, plan.items, sessionId);
|
|
17155
17826
|
if (early) return early;
|
|
17156
17827
|
if (didTaskify) {
|
|
17157
17828
|
const taskPathRaw = ctx.meta["task.path"];
|
|
@@ -17165,7 +17836,7 @@ var planTool = {
|
|
|
17165
17836
|
}
|
|
17166
17837
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
17167
17838
|
try {
|
|
17168
|
-
const taskFile = await
|
|
17839
|
+
const taskFile = await mutateTasks2(taskPath, sessionId, (f) => {
|
|
17169
17840
|
f.tasks.push({
|
|
17170
17841
|
id: `task_${randomUUID()}`,
|
|
17171
17842
|
title: taskifyMeta.title,
|
|
@@ -18302,11 +18973,11 @@ import {
|
|
|
18302
18973
|
formatTaskList as formatTaskList2
|
|
18303
18974
|
} from "@wrongstack/core";
|
|
18304
18975
|
import {
|
|
18305
|
-
mutateTasks as
|
|
18976
|
+
mutateTasks as mutateTasks3
|
|
18306
18977
|
} from "@wrongstack/core";
|
|
18307
18978
|
import {
|
|
18308
18979
|
addPlanItem as addPlanItem2,
|
|
18309
|
-
mutatePlan as
|
|
18980
|
+
mutatePlan as mutatePlan3,
|
|
18310
18981
|
formatPlan as formatPlan2
|
|
18311
18982
|
} from "@wrongstack/core";
|
|
18312
18983
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -18426,7 +19097,7 @@ var taskTool = {
|
|
|
18426
19097
|
let todosToReplace = null;
|
|
18427
19098
|
let file;
|
|
18428
19099
|
try {
|
|
18429
|
-
file = await
|
|
19100
|
+
file = await mutateTasks3(taskPath, sessionId, async (f) => {
|
|
18430
19101
|
switch (input.action) {
|
|
18431
19102
|
case "show":
|
|
18432
19103
|
break;
|
|
@@ -18616,6 +19287,7 @@ var taskTool = {
|
|
|
18616
19287
|
};
|
|
18617
19288
|
}
|
|
18618
19289
|
if (todosToReplace) ctx.state.replaceTodos(todosToReplace);
|
|
19290
|
+
await projectSessionTasksToKanban(ctx.projectRoot, file.tasks, sessionId);
|
|
18619
19291
|
if (early) return early;
|
|
18620
19292
|
if (didPlanify) {
|
|
18621
19293
|
const { title, details } = planifyMeta;
|
|
@@ -18629,7 +19301,7 @@ var taskTool = {
|
|
|
18629
19301
|
}
|
|
18630
19302
|
let formatted = "";
|
|
18631
19303
|
try {
|
|
18632
|
-
await
|
|
19304
|
+
await mutatePlan3(planPath, sessionId, (pf) => {
|
|
18633
19305
|
const { plan: updated } = addPlanItem2(pf, title, details || void 0);
|
|
18634
19306
|
formatted = formatPlan2(updated);
|
|
18635
19307
|
return updated;
|
|
@@ -18861,8 +19533,7 @@ function parseResult(runner, result, duration) {
|
|
|
18861
19533
|
}
|
|
18862
19534
|
|
|
18863
19535
|
// src/todo.ts
|
|
18864
|
-
import { loadPlan, savePlan, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core";
|
|
18865
|
-
import { loadTasks, saveTasks } from "@wrongstack/core";
|
|
19536
|
+
import { loadPlan as loadPlan2, loadTasks as loadTasks3, savePlan, saveTasks, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core";
|
|
18866
19537
|
var todoTool = {
|
|
18867
19538
|
name: "todo",
|
|
18868
19539
|
category: "Session",
|
|
@@ -18871,7 +19542,7 @@ var todoTool = {
|
|
|
18871
19542
|
permission: "auto",
|
|
18872
19543
|
mutating: false,
|
|
18873
19544
|
// mutates only conversation state (ctx.todos), not external state — no confirmation needed
|
|
18874
|
-
timeoutMs:
|
|
19545
|
+
timeoutMs: 5e3,
|
|
18875
19546
|
capabilities: ["session.todo"],
|
|
18876
19547
|
icon: "todo",
|
|
18877
19548
|
inputSchema: {
|
|
@@ -18923,16 +19594,21 @@ var todoTool = {
|
|
|
18923
19594
|
}
|
|
18924
19595
|
}
|
|
18925
19596
|
ctx.state.replaceTodos(items);
|
|
19597
|
+
await projectSessionTodosToKanban(ctx.projectRoot, items, ctx.session?.id ?? "session");
|
|
18926
19598
|
const completedPlanIds = /* @__PURE__ */ new Set();
|
|
18927
19599
|
const completedTaskIds = /* @__PURE__ */ new Set();
|
|
18928
19600
|
const pendingPlanIds = /* @__PURE__ */ new Set();
|
|
18929
19601
|
const pendingTaskIds = /* @__PURE__ */ new Set();
|
|
18930
19602
|
for (const item of items) {
|
|
18931
19603
|
if (item.promotedFromPlan) {
|
|
18932
|
-
(item.status === "completed" ? completedPlanIds : pendingPlanIds).add(
|
|
19604
|
+
(item.status === "completed" ? completedPlanIds : pendingPlanIds).add(
|
|
19605
|
+
item.promotedFromPlan
|
|
19606
|
+
);
|
|
18933
19607
|
}
|
|
18934
19608
|
if (item.promotedFromTask) {
|
|
18935
|
-
(item.status === "completed" ? completedTaskIds : pendingTaskIds).add(
|
|
19609
|
+
(item.status === "completed" ? completedTaskIds : pendingTaskIds).add(
|
|
19610
|
+
item.promotedFromTask
|
|
19611
|
+
);
|
|
18936
19612
|
}
|
|
18937
19613
|
}
|
|
18938
19614
|
for (const planId of completedPlanIds) {
|
|
@@ -18940,7 +19616,7 @@ var todoTool = {
|
|
|
18940
19616
|
const planPath = ctx.meta["plan.path"];
|
|
18941
19617
|
if (typeof planPath !== "string" || !planPath) continue;
|
|
18942
19618
|
try {
|
|
18943
|
-
const plan = await
|
|
19619
|
+
const plan = await loadPlan2(planPath);
|
|
18944
19620
|
if (plan) {
|
|
18945
19621
|
const updated = setPlanItemStatus2(plan, planId, "done");
|
|
18946
19622
|
await savePlan(planPath, updated);
|
|
@@ -18953,7 +19629,7 @@ var todoTool = {
|
|
|
18953
19629
|
const taskPath = ctx.meta["task.path"];
|
|
18954
19630
|
if (typeof taskPath !== "string" || !taskPath) continue;
|
|
18955
19631
|
try {
|
|
18956
|
-
const file = await
|
|
19632
|
+
const file = await loadTasks3(taskPath);
|
|
18957
19633
|
if (file) {
|
|
18958
19634
|
const task = file.tasks.find((t) => t.id === taskId);
|
|
18959
19635
|
if (task && task.status !== "completed") {
|
|
@@ -20922,7 +21598,13 @@ var TOOL_ICON_MAP = {
|
|
|
20922
21598
|
think: "brain",
|
|
20923
21599
|
reason: "brain",
|
|
20924
21600
|
analyze: "brain",
|
|
20925
|
-
reasoning: "brain"
|
|
21601
|
+
reasoning: "brain",
|
|
21602
|
+
// Language intelligence
|
|
21603
|
+
lsp_diagnostics: "code",
|
|
21604
|
+
lsp_definition: "search",
|
|
21605
|
+
lsp_completion: "code",
|
|
21606
|
+
lsp_rename: "edit",
|
|
21607
|
+
"codebase-lsp-search": "index"
|
|
20926
21608
|
};
|
|
20927
21609
|
function getToolIcon(toolName) {
|
|
20928
21610
|
return TOOL_ICON_MAP[toolName.toLowerCase()] ?? "fallback";
|
|
@@ -20991,13 +21673,16 @@ export {
|
|
|
20991
21673
|
LanguageProfileRegistry,
|
|
20992
21674
|
OPTIONAL_TOOLS,
|
|
20993
21675
|
PRIMARY_LANGUAGE_PROFILES,
|
|
21676
|
+
SESSION_KANBAN_COLUMNS,
|
|
20994
21677
|
TIER1_TOOLS,
|
|
20995
21678
|
TIER2_TOOLS,
|
|
20996
21679
|
TIER3_TOOLS,
|
|
20997
21680
|
TOOL_ICON_CONFIG,
|
|
20998
21681
|
TOOL_ICON_MAP,
|
|
20999
21682
|
_resetProcessRegistry,
|
|
21683
|
+
applySessionKanbanTaskToSource,
|
|
21000
21684
|
assertBrowserUrlAllowed,
|
|
21685
|
+
attachSessionKanbanMirror,
|
|
21001
21686
|
auditTool,
|
|
21002
21687
|
bashTool,
|
|
21003
21688
|
batchToolUseTool,
|
|
@@ -21040,6 +21725,7 @@ export {
|
|
|
21040
21725
|
e2ePlanTool,
|
|
21041
21726
|
editTool,
|
|
21042
21727
|
enqueueReindex,
|
|
21728
|
+
ensureSessionKanbanBoard,
|
|
21043
21729
|
ensureSessionShell,
|
|
21044
21730
|
execTool,
|
|
21045
21731
|
executeLanguagePlan,
|
|
@@ -21061,6 +21747,7 @@ export {
|
|
|
21061
21747
|
gitTool,
|
|
21062
21748
|
globTool,
|
|
21063
21749
|
grepTool,
|
|
21750
|
+
hydrateSessionKanban,
|
|
21064
21751
|
indexCircuitBreaker,
|
|
21065
21752
|
installTool,
|
|
21066
21753
|
isExecCommandAllowed,
|
|
@@ -21077,6 +21764,9 @@ export {
|
|
|
21077
21764
|
listInstances,
|
|
21078
21765
|
logsTool,
|
|
21079
21766
|
makeSkillTool,
|
|
21767
|
+
mirrorSessionPlanToKanban,
|
|
21768
|
+
mirrorSessionTasksToKanban,
|
|
21769
|
+
mirrorSessionTodosToKanban,
|
|
21080
21770
|
normalizeShell,
|
|
21081
21771
|
onIndexStateChange,
|
|
21082
21772
|
outdatedTool,
|
|
@@ -21086,6 +21776,9 @@ export {
|
|
|
21086
21776
|
patchTool,
|
|
21087
21777
|
planLanguageOperation,
|
|
21088
21778
|
planTool,
|
|
21779
|
+
projectSessionPlanToKanban,
|
|
21780
|
+
projectSessionTasksToKanban,
|
|
21781
|
+
projectSessionTodosToKanban,
|
|
21089
21782
|
readTool,
|
|
21090
21783
|
redactBrowserText,
|
|
21091
21784
|
relatedMemoryTool,
|