@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/pack.js
CHANGED
|
@@ -6054,6 +6054,9 @@ ${hint}` : ""),
|
|
|
6054
6054
|
};
|
|
6055
6055
|
|
|
6056
6056
|
// src/batch-tool-use.ts
|
|
6057
|
+
import {
|
|
6058
|
+
GOVERNED_TOOL_EXECUTOR_META_KEY
|
|
6059
|
+
} from "@wrongstack/core";
|
|
6057
6060
|
var batchToolUseTool = {
|
|
6058
6061
|
name: "batch_tool_use",
|
|
6059
6062
|
category: "Meta",
|
|
@@ -6090,7 +6093,7 @@ var batchToolUseTool = {
|
|
|
6090
6093
|
},
|
|
6091
6094
|
required: ["calls"]
|
|
6092
6095
|
},
|
|
6093
|
-
async execute(input, ctx,
|
|
6096
|
+
async execute(input, ctx, _opts) {
|
|
6094
6097
|
if (!input?.calls || input.calls.length === 0) {
|
|
6095
6098
|
return {
|
|
6096
6099
|
results: [],
|
|
@@ -6100,18 +6103,33 @@ var batchToolUseTool = {
|
|
|
6100
6103
|
stop_on_error: false
|
|
6101
6104
|
};
|
|
6102
6105
|
}
|
|
6106
|
+
const governedExecute = ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY];
|
|
6107
|
+
if (typeof governedExecute !== "function") {
|
|
6108
|
+
return {
|
|
6109
|
+
results: input.calls.map((call) => ({
|
|
6110
|
+
tool: call.tool,
|
|
6111
|
+
success: false,
|
|
6112
|
+
error: "governed nested execution is unavailable; call the tool directly",
|
|
6113
|
+
executionMs: 0
|
|
6114
|
+
})),
|
|
6115
|
+
total: input.calls.length,
|
|
6116
|
+
succeeded: 0,
|
|
6117
|
+
failed: input.calls.length,
|
|
6118
|
+
stop_on_error: input.stop_on_error ?? false
|
|
6119
|
+
};
|
|
6120
|
+
}
|
|
6103
6121
|
const results = [];
|
|
6104
6122
|
let succeeded = 0;
|
|
6105
6123
|
let failed = 0;
|
|
6106
6124
|
if (input.parallel !== false) {
|
|
6107
|
-
const promises = input.calls.map(async (call) => executeSingle(call, ctx,
|
|
6125
|
+
const promises = input.calls.map(async (call) => executeSingle(call, ctx, governedExecute));
|
|
6108
6126
|
const allResults = await Promise.all(promises);
|
|
6109
6127
|
results.push(...allResults);
|
|
6110
6128
|
succeeded = allResults.filter((r) => r.success).length;
|
|
6111
6129
|
failed = allResults.filter((r) => !r.success).length;
|
|
6112
6130
|
} else {
|
|
6113
6131
|
for (const call of input.calls) {
|
|
6114
|
-
const result = await executeSingle(call, ctx,
|
|
6132
|
+
const result = await executeSingle(call, ctx, governedExecute);
|
|
6115
6133
|
results.push(result);
|
|
6116
6134
|
if (result.success) {
|
|
6117
6135
|
succeeded++;
|
|
@@ -6130,9 +6148,9 @@ var batchToolUseTool = {
|
|
|
6130
6148
|
};
|
|
6131
6149
|
}
|
|
6132
6150
|
};
|
|
6133
|
-
async function executeSingle(call, ctx,
|
|
6151
|
+
async function executeSingle(call, ctx, governedExecute) {
|
|
6134
6152
|
const start = Date.now();
|
|
6135
|
-
const tool = ctx.tools.find((
|
|
6153
|
+
const tool = ctx.tools.find((candidate) => candidate.name === call.tool);
|
|
6136
6154
|
if (!tool) {
|
|
6137
6155
|
return {
|
|
6138
6156
|
tool: call.tool,
|
|
@@ -6142,11 +6160,11 @@ async function executeSingle(call, ctx, opts) {
|
|
|
6142
6160
|
};
|
|
6143
6161
|
}
|
|
6144
6162
|
try {
|
|
6145
|
-
const result = await
|
|
6163
|
+
const result = await governedExecute(call.tool, call.input);
|
|
6146
6164
|
return {
|
|
6147
6165
|
tool: call.tool,
|
|
6148
|
-
success:
|
|
6149
|
-
result,
|
|
6166
|
+
success: result.success,
|
|
6167
|
+
...result.success ? { result: result.result } : { error: result.error ?? "nested tool failed" },
|
|
6150
6168
|
executionMs: Date.now() - start
|
|
6151
6169
|
};
|
|
6152
6170
|
} catch (e) {
|
|
@@ -6556,7 +6574,7 @@ var BrowserSessionManager = class {
|
|
|
6556
6574
|
constructor(options, launcher = defaultLauncher) {
|
|
6557
6575
|
this.launcher = launcher;
|
|
6558
6576
|
this.artifacts = new BrowserArtifactStore(options.artifactRoot);
|
|
6559
|
-
this.allowPrivateHosts = options.allowPrivateHosts ??
|
|
6577
|
+
this.allowPrivateHosts = options.allowPrivateHosts ?? true;
|
|
6560
6578
|
this.allowedPrivateOrigins = options.allowedPrivateOrigins ?? [];
|
|
6561
6579
|
this.networkProxy = new BrowserNetworkGuardProxy({
|
|
6562
6580
|
allowPrivateHosts: this.allowPrivateHosts,
|
|
@@ -8519,6 +8537,7 @@ function detectLang(file) {
|
|
|
8519
8537
|
}
|
|
8520
8538
|
|
|
8521
8539
|
// src/codebase-index/go-parser.ts
|
|
8540
|
+
init_win32_resolve();
|
|
8522
8541
|
import { spawn as spawn4 } from "node:child_process";
|
|
8523
8542
|
import * as os5 from "node:os";
|
|
8524
8543
|
import * as path14 from "node:path";
|
|
@@ -8827,27 +8846,42 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
8827
8846
|
try {
|
|
8828
8847
|
const scriptPath = path14.join(tmpDir, "parse.go");
|
|
8829
8848
|
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
|
-
|
|
8849
|
+
const goBinary = resolveWin32Command("go");
|
|
8850
|
+
const goResult = await new Promise(
|
|
8851
|
+
(resolve14, reject) => {
|
|
8852
|
+
let settled = false;
|
|
8853
|
+
const proc = spawn4(goBinary, ["run", scriptPath], {
|
|
8854
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
8855
|
+
windowsHide: true
|
|
8856
|
+
});
|
|
8857
|
+
proc.on("error", (err) => {
|
|
8858
|
+
if (settled) return;
|
|
8859
|
+
settled = true;
|
|
8860
|
+
reject(err);
|
|
8861
|
+
});
|
|
8862
|
+
let stdout2 = "";
|
|
8863
|
+
proc.stdout?.on("data", (chunk) => {
|
|
8864
|
+
stdout2 += chunk.toString();
|
|
8865
|
+
});
|
|
8866
|
+
proc.stderr?.resume();
|
|
8867
|
+
proc.stdin?.write(content);
|
|
8868
|
+
proc.stdin?.end();
|
|
8869
|
+
const timer = setTimeout(() => {
|
|
8870
|
+
if (settled) return;
|
|
8871
|
+
settled = true;
|
|
8846
8872
|
proc.kill("SIGKILL");
|
|
8847
8873
|
reject(new Error("timeout"));
|
|
8848
|
-
}, 15e3)
|
|
8849
|
-
|
|
8850
|
-
|
|
8874
|
+
}, 15e3);
|
|
8875
|
+
timer.unref?.();
|
|
8876
|
+
proc.on("close", (code2) => {
|
|
8877
|
+
if (settled) return;
|
|
8878
|
+
settled = true;
|
|
8879
|
+
clearTimeout(timer);
|
|
8880
|
+
resolve14({ code: code2, stdout: stdout2 });
|
|
8881
|
+
});
|
|
8882
|
+
}
|
|
8883
|
+
);
|
|
8884
|
+
const { code, stdout } = goResult;
|
|
8851
8885
|
if (code !== 0 || !stdout.trim()) {
|
|
8852
8886
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
8853
8887
|
}
|
|
@@ -8874,7 +8908,8 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
8874
8908
|
}
|
|
8875
8909
|
|
|
8876
8910
|
// src/codebase-index/py-parser.ts
|
|
8877
|
-
|
|
8911
|
+
init_win32_resolve();
|
|
8912
|
+
import { spawn as spawn5, spawnSync } from "node:child_process";
|
|
8878
8913
|
import * as fs11 from "node:fs/promises";
|
|
8879
8914
|
import * as os6 from "node:os";
|
|
8880
8915
|
import * as path15 from "node:path";
|
|
@@ -9089,36 +9124,74 @@ visitor.visit(tree)
|
|
|
9089
9124
|
|
|
9090
9125
|
print(json.dumps([s.to_dict() for s in syms]))
|
|
9091
9126
|
`;
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9127
|
+
function resolvePython() {
|
|
9128
|
+
const candidates = process.platform === "win32" ? ["python3", "python", "py"] : ["python3", "python"];
|
|
9129
|
+
for (const name of candidates) {
|
|
9130
|
+
const resolved = resolveWin32Command(name);
|
|
9131
|
+
const result = spawnSync(resolved, ["--version"], {
|
|
9132
|
+
stdio: "pipe",
|
|
9133
|
+
timeout: 5e3
|
|
9134
|
+
});
|
|
9135
|
+
if (result.error) continue;
|
|
9136
|
+
if (result.status !== 0) continue;
|
|
9137
|
+
return resolved;
|
|
9138
|
+
}
|
|
9139
|
+
return null;
|
|
9140
|
+
}
|
|
9141
|
+
function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
9142
|
+
return new Promise((resolve14, reject) => {
|
|
9143
|
+
let settled = false;
|
|
9144
|
+
const proc = spawn5(pyBinary, [scriptPath, filePath], {
|
|
9102
9145
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9103
9146
|
windowsHide: true
|
|
9104
9147
|
});
|
|
9148
|
+
proc.on("error", (err) => {
|
|
9149
|
+
if (settled) return;
|
|
9150
|
+
settled = true;
|
|
9151
|
+
reject(err);
|
|
9152
|
+
});
|
|
9105
9153
|
proc.stdin?.write(content);
|
|
9106
9154
|
proc.stdin?.end();
|
|
9107
9155
|
let stdout = "";
|
|
9108
9156
|
proc.stdout?.on("data", (chunk) => {
|
|
9109
9157
|
stdout += chunk.toString();
|
|
9110
9158
|
});
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
)
|
|
9121
|
-
|
|
9159
|
+
proc.stderr?.resume();
|
|
9160
|
+
const timer = setTimeout(() => {
|
|
9161
|
+
if (settled) return;
|
|
9162
|
+
settled = true;
|
|
9163
|
+
proc.kill("SIGKILL");
|
|
9164
|
+
reject(new Error("timeout"));
|
|
9165
|
+
}, 15e3);
|
|
9166
|
+
timer.unref?.();
|
|
9167
|
+
proc.on("close", (code) => {
|
|
9168
|
+
if (settled) return;
|
|
9169
|
+
settled = true;
|
|
9170
|
+
clearTimeout(timer);
|
|
9171
|
+
resolve14({ code, stdout });
|
|
9172
|
+
});
|
|
9173
|
+
});
|
|
9174
|
+
}
|
|
9175
|
+
var _cachedScriptPath = null;
|
|
9176
|
+
var _cachedPyBinary = null;
|
|
9177
|
+
async function syncPyParse(filePath, content, lang) {
|
|
9178
|
+
try {
|
|
9179
|
+
if (!_cachedScriptPath) {
|
|
9180
|
+
const tmpDir = path15.join(os6.tmpdir(), "ws-py-parse");
|
|
9181
|
+
await fs11.mkdir(tmpDir, { recursive: true });
|
|
9182
|
+
_cachedScriptPath = path15.join(tmpDir, "parse.py");
|
|
9183
|
+
await fs11.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
9184
|
+
}
|
|
9185
|
+
if (!_cachedPyBinary) {
|
|
9186
|
+
_cachedPyBinary = resolvePython();
|
|
9187
|
+
if (!_cachedPyBinary) return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
9188
|
+
}
|
|
9189
|
+
const { code, stdout } = await spawnPyParser(
|
|
9190
|
+
_cachedPyBinary,
|
|
9191
|
+
_cachedScriptPath,
|
|
9192
|
+
filePath,
|
|
9193
|
+
content
|
|
9194
|
+
);
|
|
9122
9195
|
if (code !== 0 || !stdout.trim()) {
|
|
9123
9196
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
9124
9197
|
}
|
|
@@ -9143,6 +9216,7 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
9143
9216
|
}
|
|
9144
9217
|
|
|
9145
9218
|
// src/codebase-index/rs-parser.ts
|
|
9219
|
+
init_win32_resolve();
|
|
9146
9220
|
import { expectDefined as expectDefined3 } from "@wrongstack/core";
|
|
9147
9221
|
import { execFileSync, spawn as spawn6 } from "node:child_process";
|
|
9148
9222
|
import * as fs12 from "node:fs/promises";
|
|
@@ -9187,30 +9261,45 @@ async function tryNativeParse(file, content) {
|
|
|
9187
9261
|
const crateDir = path16.join(toolsDir, "syn-parser");
|
|
9188
9262
|
const tmpFile = path16.join(crateDir, "src", "input.rs");
|
|
9189
9263
|
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
|
-
|
|
9264
|
+
const cargoBinary = resolveWin32Command("cargo");
|
|
9265
|
+
const result = await new Promise(
|
|
9266
|
+
(resolve14, reject) => {
|
|
9267
|
+
let settled = false;
|
|
9268
|
+
const proc = spawn6(
|
|
9269
|
+
cargoBinary,
|
|
9270
|
+
["run", "--manifest-path", path16.join(toolsDir, "Cargo.toml")],
|
|
9271
|
+
{
|
|
9272
|
+
cwd: process.cwd(),
|
|
9273
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
9274
|
+
windowsHide: true
|
|
9275
|
+
}
|
|
9276
|
+
);
|
|
9277
|
+
proc.on("error", (err) => {
|
|
9278
|
+
if (settled) return;
|
|
9279
|
+
settled = true;
|
|
9280
|
+
reject(err);
|
|
9281
|
+
});
|
|
9282
|
+
let stdout2 = "";
|
|
9283
|
+
proc.stdout?.on("data", (chunk) => {
|
|
9284
|
+
stdout2 += chunk.toString();
|
|
9285
|
+
});
|
|
9286
|
+
proc.stderr?.resume();
|
|
9287
|
+
const timer = setTimeout(() => {
|
|
9288
|
+
if (settled) return;
|
|
9289
|
+
settled = true;
|
|
9209
9290
|
proc.kill("SIGKILL");
|
|
9210
9291
|
reject(new Error("timeout"));
|
|
9211
|
-
}, 15e3)
|
|
9212
|
-
|
|
9213
|
-
|
|
9292
|
+
}, 15e3);
|
|
9293
|
+
timer.unref?.();
|
|
9294
|
+
proc.on("close", (c) => {
|
|
9295
|
+
if (settled) return;
|
|
9296
|
+
settled = true;
|
|
9297
|
+
clearTimeout(timer);
|
|
9298
|
+
resolve14({ code: c, stdout: stdout2 });
|
|
9299
|
+
});
|
|
9300
|
+
}
|
|
9301
|
+
);
|
|
9302
|
+
const { code, stdout } = result;
|
|
9214
9303
|
if (code === 0 && stdout.trim()) {
|
|
9215
9304
|
const symbols = JSON.parse(stdout.trim());
|
|
9216
9305
|
return {
|
|
@@ -11570,25 +11659,46 @@ function findLadderMatches(fileLf, oldLf) {
|
|
|
11570
11659
|
const needleLines = oldLf.split("\n");
|
|
11571
11660
|
if (needleLines.length > fileLines.length) return void 0;
|
|
11572
11661
|
const offsets = lineOffsets(fileLines);
|
|
11662
|
+
const fileTrimEnd = fileLines.map((l) => l.trimEnd());
|
|
11663
|
+
const needleTrimEnd = needleLines.map((l) => l.trimEnd());
|
|
11573
11664
|
const trailing = windowScan(
|
|
11574
|
-
|
|
11575
|
-
|
|
11665
|
+
fileTrimEnd,
|
|
11666
|
+
needleTrimEnd,
|
|
11576
11667
|
offsets,
|
|
11577
|
-
|
|
11668
|
+
fileLines,
|
|
11669
|
+
(a, b) => a === b
|
|
11578
11670
|
);
|
|
11579
11671
|
if (trailing.length > 0) return { tier: "trailing-whitespace", matches: trailing };
|
|
11580
|
-
const normalizedLen =
|
|
11672
|
+
const normalizedLen = needleTrimEnd.reduce((n, l) => n + l.trimStart().length, 0);
|
|
11581
11673
|
if (normalizedLen < MIN_NORMALIZED_NEEDLE_CHARS) return void 0;
|
|
11582
|
-
const
|
|
11674
|
+
const fileTrimmed = fileTrimEnd.map((l) => l.trimStart());
|
|
11675
|
+
const needleTrimmed = needleTrimEnd.map((l) => l.trimStart());
|
|
11676
|
+
const normalized = windowScan(
|
|
11677
|
+
fileTrimmed,
|
|
11678
|
+
needleTrimmed,
|
|
11679
|
+
offsets,
|
|
11680
|
+
fileLines,
|
|
11681
|
+
(a, b) => a === b
|
|
11682
|
+
);
|
|
11583
11683
|
if (normalized.length > 0) return { tier: "whitespace-normalized", matches: normalized };
|
|
11584
11684
|
return fuzzyScan(fileLines, needleLines, offsets);
|
|
11585
11685
|
}
|
|
11586
11686
|
function lineAt(text, pos) {
|
|
11687
|
+
if (pos < 512) {
|
|
11688
|
+
let line2 = 1;
|
|
11689
|
+
for (let i = 0; i < pos; i++) {
|
|
11690
|
+
if (text.charCodeAt(i) === 10) line2++;
|
|
11691
|
+
}
|
|
11692
|
+
return line2;
|
|
11693
|
+
}
|
|
11587
11694
|
let line = 1;
|
|
11588
|
-
|
|
11589
|
-
|
|
11695
|
+
let search = 0;
|
|
11696
|
+
while (true) {
|
|
11697
|
+
const idx = text.indexOf("\n", search);
|
|
11698
|
+
if (idx === -1 || idx >= pos) return line;
|
|
11699
|
+
search = idx + 1;
|
|
11700
|
+
line++;
|
|
11590
11701
|
}
|
|
11591
|
-
return line;
|
|
11592
11702
|
}
|
|
11593
11703
|
function lineOffsets(lines) {
|
|
11594
11704
|
const out = new Array(lines.length);
|
|
@@ -11607,19 +11717,19 @@ function windowToMatch(fileLines, offsets, start, windowLen) {
|
|
|
11607
11717
|
startLine: start + 1
|
|
11608
11718
|
};
|
|
11609
11719
|
}
|
|
11610
|
-
function windowScan(
|
|
11720
|
+
function windowScan(comparisonLines, needleLines, offsets, originalLines, eq) {
|
|
11611
11721
|
const n = needleLines.length;
|
|
11612
11722
|
const out = [];
|
|
11613
|
-
for (let i = 0; i + n <=
|
|
11723
|
+
for (let i = 0; i + n <= comparisonLines.length; i++) {
|
|
11614
11724
|
let all = true;
|
|
11615
11725
|
for (let j = 0; j < n; j++) {
|
|
11616
|
-
if (!eq(
|
|
11726
|
+
if (!eq(comparisonLines[i + j], needleLines[j])) {
|
|
11617
11727
|
all = false;
|
|
11618
11728
|
break;
|
|
11619
11729
|
}
|
|
11620
11730
|
}
|
|
11621
11731
|
if (all) {
|
|
11622
|
-
out.push(windowToMatch(
|
|
11732
|
+
out.push(windowToMatch(originalLines, offsets, i, n));
|
|
11623
11733
|
i += n - 1;
|
|
11624
11734
|
}
|
|
11625
11735
|
}
|
|
@@ -11941,8 +12051,10 @@ var editTool = {
|
|
|
11941
12051
|
note: autoReadNote
|
|
11942
12052
|
};
|
|
11943
12053
|
}
|
|
12054
|
+
opts?.signal?.throwIfAborted();
|
|
11944
12055
|
const ladder = findLadderMatches(fileLf, oldLf);
|
|
11945
12056
|
if (!ladder) {
|
|
12057
|
+
opts?.signal?.throwIfAborted();
|
|
11946
12058
|
const hint = nearestMatchHint(fileLf, oldLf);
|
|
11947
12059
|
throw new ToolValidationError({
|
|
11948
12060
|
message: `edit: no match for old_string in "${input.path}".${hint ? ` Nearest match near line ${hint.line}:
|
|
@@ -12010,6 +12122,7 @@ Compare this against your old_string and retry with the file's actual text.` : "
|
|
|
12010
12122
|
before: original,
|
|
12011
12123
|
after: newFile
|
|
12012
12124
|
});
|
|
12125
|
+
opts?.signal?.throwIfAborted();
|
|
12013
12126
|
const diff = unifiedDiff(original, newFile, {
|
|
12014
12127
|
fromFile: input.path,
|
|
12015
12128
|
toFile: input.path
|
|
@@ -15271,7 +15384,7 @@ function toYaml(data, indent = 0) {
|
|
|
15271
15384
|
}
|
|
15272
15385
|
|
|
15273
15386
|
// src/kanban.ts
|
|
15274
|
-
import { deserializeTaskGraph, serializeTaskGraph } from "@wrongstack/core";
|
|
15387
|
+
import { deserializeTaskGraph as deserializeTaskGraph2, loadTasks as loadTasks2, serializeTaskGraph } from "@wrongstack/core";
|
|
15275
15388
|
import {
|
|
15276
15389
|
addCheckToTask,
|
|
15277
15390
|
addColumn,
|
|
@@ -15283,18 +15396,19 @@ import {
|
|
|
15283
15396
|
assignTask,
|
|
15284
15397
|
claimReadyTask,
|
|
15285
15398
|
copyTaskToBoard,
|
|
15286
|
-
createBoard,
|
|
15399
|
+
createBoard as createBoard2,
|
|
15400
|
+
createBoardFromTaskGraph,
|
|
15287
15401
|
duplicateBoard,
|
|
15288
15402
|
exportBoardAsMarkdown,
|
|
15289
15403
|
exportBoardToTaskGraph,
|
|
15290
15404
|
generateBoardFromDescription,
|
|
15291
|
-
getBoard,
|
|
15405
|
+
getBoard as getBoard2,
|
|
15292
15406
|
getKanbanOrchestrationSnapshot,
|
|
15293
15407
|
getKanbanQueueHealth,
|
|
15294
15408
|
getTask,
|
|
15295
15409
|
getTaskChain,
|
|
15296
15410
|
heartbeatTaskAssignment,
|
|
15297
|
-
listBoards,
|
|
15411
|
+
listBoards as listBoards2,
|
|
15298
15412
|
listKanbanEvents,
|
|
15299
15413
|
listReadyTasks,
|
|
15300
15414
|
mergeTasks,
|
|
@@ -15302,21 +15416,244 @@ import {
|
|
|
15302
15416
|
parseLinesIntoTasks,
|
|
15303
15417
|
recoverStaleTaskAssignments,
|
|
15304
15418
|
releaseTaskClaim,
|
|
15305
|
-
removeBoard,
|
|
15419
|
+
removeBoard as removeBoard2,
|
|
15306
15420
|
removeColumn,
|
|
15307
15421
|
removeTask,
|
|
15308
15422
|
searchKanban,
|
|
15309
15423
|
setTaskChain,
|
|
15310
15424
|
splitTask,
|
|
15311
|
-
syncBoardFromTaskGraph,
|
|
15425
|
+
syncBoardFromTaskGraph as syncBoardFromTaskGraph2,
|
|
15312
15426
|
transferTaskToBoard,
|
|
15313
|
-
updateBoard,
|
|
15427
|
+
updateBoard as updateBoard2,
|
|
15314
15428
|
updateCheckOnTask,
|
|
15315
15429
|
updateColumn,
|
|
15316
15430
|
updateGoalMetricOnTask,
|
|
15317
15431
|
updateTask,
|
|
15318
15432
|
updateTaskAssignment
|
|
15319
15433
|
} from "@wrongstack/kanban";
|
|
15434
|
+
|
|
15435
|
+
// src/session-kanban.ts
|
|
15436
|
+
import {
|
|
15437
|
+
deserializeTaskGraph,
|
|
15438
|
+
loadPlan,
|
|
15439
|
+
loadTasks,
|
|
15440
|
+
mutatePlan,
|
|
15441
|
+
mutateTasks
|
|
15442
|
+
} from "@wrongstack/core";
|
|
15443
|
+
import {
|
|
15444
|
+
createBoard,
|
|
15445
|
+
getBoard,
|
|
15446
|
+
listBoards,
|
|
15447
|
+
removeBoard,
|
|
15448
|
+
syncBoardFromTaskGraph,
|
|
15449
|
+
updateBoard
|
|
15450
|
+
} from "@wrongstack/kanban";
|
|
15451
|
+
var SESSION_BOARD_TAG = "session-work";
|
|
15452
|
+
var MIRROR_DISABLED_ENV = "WRONGSTACK_KANBAN_TASK_MIRROR";
|
|
15453
|
+
var SESSION_KANBAN_COLUMNS = [
|
|
15454
|
+
{ id: "todo", title: "Todo", order: 0, wipLimit: 0, color: "#2563eb" },
|
|
15455
|
+
{ id: "in-progress", title: "Running", order: 1, wipLimit: 1, color: "#d97706" },
|
|
15456
|
+
{ id: "review", title: "Preview", order: 2, wipLimit: 0, color: "#7c3aed" },
|
|
15457
|
+
{ id: "done", title: "Done", order: 3, wipLimit: 0, color: "#16a34a" }
|
|
15458
|
+
];
|
|
15459
|
+
var boardQueue = /* @__PURE__ */ new Map();
|
|
15460
|
+
var boardEnsures = /* @__PURE__ */ new Map();
|
|
15461
|
+
function boardKey(projectRoot, sessionId) {
|
|
15462
|
+
return `${projectRoot}\0${sessionId}`;
|
|
15463
|
+
}
|
|
15464
|
+
function sessionTag(sessionId) {
|
|
15465
|
+
return `session:${sessionId}`;
|
|
15466
|
+
}
|
|
15467
|
+
function sessionBoardTitle(sessionId) {
|
|
15468
|
+
const leaf = sessionId.split(/[\\/]/).filter(Boolean).pop() ?? sessionId;
|
|
15469
|
+
return `Session ${leaf.slice(0, 12)}`;
|
|
15470
|
+
}
|
|
15471
|
+
function sessionBoardTags(sessionId) {
|
|
15472
|
+
return ["session", SESSION_BOARD_TAG, sessionTag(sessionId)];
|
|
15473
|
+
}
|
|
15474
|
+
function sameColumns(columns) {
|
|
15475
|
+
return columns.length === SESSION_KANBAN_COLUMNS.length && columns.every((column, index) => column.id === SESSION_KANBAN_COLUMNS[index]?.id);
|
|
15476
|
+
}
|
|
15477
|
+
async function ensureSessionKanbanBoard(projectRoot, sessionId) {
|
|
15478
|
+
if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return null;
|
|
15479
|
+
const key = boardKey(projectRoot, sessionId);
|
|
15480
|
+
const inFlight = boardEnsures.get(key);
|
|
15481
|
+
if (inFlight) return inFlight;
|
|
15482
|
+
const promise = (async () => {
|
|
15483
|
+
const summary = (await listBoards(projectRoot)).find(
|
|
15484
|
+
(board2) => board2.tags?.includes(sessionTag(sessionId))
|
|
15485
|
+
);
|
|
15486
|
+
let board = summary ? await getBoard(projectRoot, summary.id) : null;
|
|
15487
|
+
if (!board) {
|
|
15488
|
+
return createBoard(projectRoot, {
|
|
15489
|
+
title: sessionBoardTitle(sessionId),
|
|
15490
|
+
description: "Live session work: todos, tasks, and plan items.",
|
|
15491
|
+
tags: sessionBoardTags(sessionId),
|
|
15492
|
+
columns: SESSION_KANBAN_COLUMNS,
|
|
15493
|
+
generatedBy: `session-kanban:${sessionId}`
|
|
15494
|
+
});
|
|
15495
|
+
}
|
|
15496
|
+
if (!sameColumns(board.columns) || !board.tags?.includes(SESSION_BOARD_TAG)) {
|
|
15497
|
+
board = await updateBoard(projectRoot, board.id, {
|
|
15498
|
+
title: sessionBoardTitle(sessionId),
|
|
15499
|
+
description: "Live session work: todos, tasks, and plan items.",
|
|
15500
|
+
tags: [.../* @__PURE__ */ new Set([...board.tags ?? [], ...sessionBoardTags(sessionId)])],
|
|
15501
|
+
columns: SESSION_KANBAN_COLUMNS
|
|
15502
|
+
}) ?? board;
|
|
15503
|
+
}
|
|
15504
|
+
return board;
|
|
15505
|
+
})();
|
|
15506
|
+
boardEnsures.set(key, promise);
|
|
15507
|
+
try {
|
|
15508
|
+
return await promise;
|
|
15509
|
+
} finally {
|
|
15510
|
+
boardEnsures.delete(key);
|
|
15511
|
+
}
|
|
15512
|
+
}
|
|
15513
|
+
function enqueueBoardWork(projectRoot, sessionId, work) {
|
|
15514
|
+
const key = boardKey(projectRoot, sessionId);
|
|
15515
|
+
const previous = boardQueue.get(key) ?? Promise.resolve();
|
|
15516
|
+
const result = previous.catch(() => void 0).then(work);
|
|
15517
|
+
const tail = result.then(
|
|
15518
|
+
() => void 0,
|
|
15519
|
+
() => void 0
|
|
15520
|
+
);
|
|
15521
|
+
boardQueue.set(key, tail);
|
|
15522
|
+
void tail.then(() => {
|
|
15523
|
+
if (boardQueue.get(key) === tail) boardQueue.delete(key);
|
|
15524
|
+
});
|
|
15525
|
+
return result;
|
|
15526
|
+
}
|
|
15527
|
+
async function projectGraph(projectRoot, sessionId, graph, sourceSystem) {
|
|
15528
|
+
if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return null;
|
|
15529
|
+
return enqueueBoardWork(projectRoot, sessionId, async () => {
|
|
15530
|
+
const board = await ensureSessionKanbanBoard(projectRoot, sessionId);
|
|
15531
|
+
if (!board) return null;
|
|
15532
|
+
const result = await syncBoardFromTaskGraph(
|
|
15533
|
+
projectRoot,
|
|
15534
|
+
board.id,
|
|
15535
|
+
deserializeTaskGraph(graph),
|
|
15536
|
+
{
|
|
15537
|
+
sourceSystem,
|
|
15538
|
+
tags: [.../* @__PURE__ */ new Set([...board.tags ?? [], ...sessionBoardTags(sessionId)])],
|
|
15539
|
+
archiveMissingTasks: true,
|
|
15540
|
+
includeCompletedTasks: true
|
|
15541
|
+
}
|
|
15542
|
+
);
|
|
15543
|
+
return result?.board ?? null;
|
|
15544
|
+
});
|
|
15545
|
+
}
|
|
15546
|
+
function todoListToSerializedGraph(todos, sessionId) {
|
|
15547
|
+
const nodes = todos.map((todo, index) => ({
|
|
15548
|
+
id: todo.id,
|
|
15549
|
+
title: todo.content,
|
|
15550
|
+
description: todo.activeForm ?? "",
|
|
15551
|
+
type: "chore",
|
|
15552
|
+
priority: "medium",
|
|
15553
|
+
status: todo.status,
|
|
15554
|
+
createdAt: index,
|
|
15555
|
+
updatedAt: index
|
|
15556
|
+
}));
|
|
15557
|
+
return {
|
|
15558
|
+
id: `todo:${sessionId}`,
|
|
15559
|
+
specId: `todo:${sessionId}`,
|
|
15560
|
+
title: "Session todos",
|
|
15561
|
+
nodes,
|
|
15562
|
+
edges: [],
|
|
15563
|
+
rootNodes: nodes.map((node) => node.id),
|
|
15564
|
+
createdAt: 0,
|
|
15565
|
+
updatedAt: 0
|
|
15566
|
+
};
|
|
15567
|
+
}
|
|
15568
|
+
function taskFileToSerializedGraph(tasks, sessionId) {
|
|
15569
|
+
const ids = new Set(tasks.map((task) => task.id));
|
|
15570
|
+
const nodes = tasks.map((task, index) => ({
|
|
15571
|
+
id: task.id,
|
|
15572
|
+
title: task.title,
|
|
15573
|
+
description: task.description ?? "",
|
|
15574
|
+
type: task.type,
|
|
15575
|
+
priority: task.priority,
|
|
15576
|
+
status: task.status,
|
|
15577
|
+
...task.assignee ? { assignee: task.assignee } : {},
|
|
15578
|
+
...task.estimateHours !== void 0 ? { estimateHours: task.estimateHours } : {},
|
|
15579
|
+
createdAt: index,
|
|
15580
|
+
updatedAt: index
|
|
15581
|
+
}));
|
|
15582
|
+
const edges = tasks.flatMap(
|
|
15583
|
+
(task) => (task.dependsOn ?? []).filter((dependency) => ids.has(dependency)).map((dependency) => ({
|
|
15584
|
+
id: `${dependency}->${task.id}`,
|
|
15585
|
+
from: dependency,
|
|
15586
|
+
to: task.id,
|
|
15587
|
+
type: "depends_on"
|
|
15588
|
+
}))
|
|
15589
|
+
);
|
|
15590
|
+
const hasIncoming = new Set(edges.map((edge) => edge.to));
|
|
15591
|
+
const rootNodes = nodes.filter((node) => !hasIncoming.has(node.id)).map((node) => node.id);
|
|
15592
|
+
return {
|
|
15593
|
+
// Keep the historical graph id so existing mirrored task cards are reused.
|
|
15594
|
+
id: `session:${sessionId}`,
|
|
15595
|
+
specId: `session:${sessionId}`,
|
|
15596
|
+
title: "Session tasks",
|
|
15597
|
+
nodes,
|
|
15598
|
+
edges,
|
|
15599
|
+
rootNodes: rootNodes.length ? rootNodes : nodes[0] ? [nodes[0].id] : [],
|
|
15600
|
+
createdAt: 0,
|
|
15601
|
+
updatedAt: 0
|
|
15602
|
+
};
|
|
15603
|
+
}
|
|
15604
|
+
var PLAN_STATUS_TO_TASK = {
|
|
15605
|
+
open: "pending",
|
|
15606
|
+
in_progress: "in_progress",
|
|
15607
|
+
done: "completed"
|
|
15608
|
+
};
|
|
15609
|
+
function planFileToSerializedGraph(items, sessionId) {
|
|
15610
|
+
const nodes = items.map((item, index) => ({
|
|
15611
|
+
id: item.id,
|
|
15612
|
+
title: item.title,
|
|
15613
|
+
description: item.details ?? "",
|
|
15614
|
+
type: "chore",
|
|
15615
|
+
priority: "medium",
|
|
15616
|
+
status: PLAN_STATUS_TO_TASK[item.status],
|
|
15617
|
+
createdAt: index,
|
|
15618
|
+
updatedAt: index
|
|
15619
|
+
}));
|
|
15620
|
+
return {
|
|
15621
|
+
id: `plan:${sessionId}`,
|
|
15622
|
+
specId: `plan:${sessionId}`,
|
|
15623
|
+
title: "Session plan",
|
|
15624
|
+
nodes,
|
|
15625
|
+
edges: [],
|
|
15626
|
+
rootNodes: nodes.map((node) => node.id),
|
|
15627
|
+
createdAt: 0,
|
|
15628
|
+
updatedAt: 0
|
|
15629
|
+
};
|
|
15630
|
+
}
|
|
15631
|
+
function projectSessionTodosToKanban(projectRoot, todos, sessionId) {
|
|
15632
|
+
return projectGraph(
|
|
15633
|
+
projectRoot,
|
|
15634
|
+
sessionId,
|
|
15635
|
+
todoListToSerializedGraph(todos, sessionId),
|
|
15636
|
+
"session-todo"
|
|
15637
|
+
);
|
|
15638
|
+
}
|
|
15639
|
+
function projectSessionTasksToKanban(projectRoot, tasks, sessionId) {
|
|
15640
|
+
return projectGraph(
|
|
15641
|
+
projectRoot,
|
|
15642
|
+
sessionId,
|
|
15643
|
+
taskFileToSerializedGraph(tasks, sessionId),
|
|
15644
|
+
"session-task"
|
|
15645
|
+
);
|
|
15646
|
+
}
|
|
15647
|
+
function projectSessionPlanToKanban(projectRoot, items, sessionId) {
|
|
15648
|
+
return projectGraph(
|
|
15649
|
+
projectRoot,
|
|
15650
|
+
sessionId,
|
|
15651
|
+
planFileToSerializedGraph(items, sessionId),
|
|
15652
|
+
"session-plan"
|
|
15653
|
+
);
|
|
15654
|
+
}
|
|
15655
|
+
|
|
15656
|
+
// src/kanban.ts
|
|
15320
15657
|
var kanbanTool = {
|
|
15321
15658
|
name: "kanban",
|
|
15322
15659
|
category: "Project",
|
|
@@ -15343,6 +15680,8 @@ var kanbanTool = {
|
|
|
15343
15680
|
"export_markdown",
|
|
15344
15681
|
"export_task_graph",
|
|
15345
15682
|
"sync_task_graph",
|
|
15683
|
+
"create_from_graph",
|
|
15684
|
+
"import_session_tasks",
|
|
15346
15685
|
"search_tasks",
|
|
15347
15686
|
"ready_tasks",
|
|
15348
15687
|
"snapshot",
|
|
@@ -15389,6 +15728,10 @@ var kanbanTool = {
|
|
|
15389
15728
|
tags: { type: "array", items: { type: "string" } },
|
|
15390
15729
|
labels: { type: "array", items: { type: "string" } },
|
|
15391
15730
|
priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
|
|
15731
|
+
taskType: {
|
|
15732
|
+
type: "string",
|
|
15733
|
+
enum: ["feature", "bugfix", "refactor", "docs", "test", "chore"]
|
|
15734
|
+
},
|
|
15392
15735
|
status: {
|
|
15393
15736
|
type: "string",
|
|
15394
15737
|
enum: [
|
|
@@ -15431,8 +15774,21 @@ var kanbanTool = {
|
|
|
15431
15774
|
releaseStatus: { type: "string", enum: ["pending", "ready", "blocked"] },
|
|
15432
15775
|
releaseReason: { type: "string" },
|
|
15433
15776
|
clearAssignee: { type: "boolean" },
|
|
15434
|
-
recoveryMode: { type: "string", enum: ["release", "retry", "fail"] },
|
|
15777
|
+
recoveryMode: { type: "string", enum: ["auto", "release", "retry", "fail"] },
|
|
15435
15778
|
recoveryNow: { type: "string" },
|
|
15779
|
+
recoveryPolicyFailOnCostCeiling: { type: "boolean" },
|
|
15780
|
+
recoveryPolicyReleaseOnFailureKinds: { type: "array", items: { type: "string" } },
|
|
15781
|
+
recoveryPolicyReleaseOnHeartbeatDue: { type: "boolean" },
|
|
15782
|
+
recoveryPolicyRetryPolicyOverride: {
|
|
15783
|
+
type: "string",
|
|
15784
|
+
enum: ["off", "incremental", "exponential"]
|
|
15785
|
+
},
|
|
15786
|
+
assignee: { type: "string" },
|
|
15787
|
+
costCeilingUsd: { type: "number" },
|
|
15788
|
+
retryPolicy: { type: "string", enum: ["off", "incremental", "exponential"] },
|
|
15789
|
+
lastFailureKind: { type: "string" },
|
|
15790
|
+
dependsOn: { type: "array", items: { type: "string" } },
|
|
15791
|
+
estimatedHours: { type: "number" },
|
|
15436
15792
|
taskGraph: { type: "object" },
|
|
15437
15793
|
graphId: { type: "string" },
|
|
15438
15794
|
specId: { type: "string" },
|
|
@@ -15488,7 +15844,7 @@ var kanbanTool = {
|
|
|
15488
15844
|
try {
|
|
15489
15845
|
switch (input.action) {
|
|
15490
15846
|
case "list_boards": {
|
|
15491
|
-
const boards = await
|
|
15847
|
+
const boards = await listBoards2(projectRoot);
|
|
15492
15848
|
return { ok: true, message: `${boards.length} board(s).`, boards };
|
|
15493
15849
|
}
|
|
15494
15850
|
case "get_board": {
|
|
@@ -15497,7 +15853,7 @@ var kanbanTool = {
|
|
|
15497
15853
|
}
|
|
15498
15854
|
case "create_board": {
|
|
15499
15855
|
if (!input.title) return fail("create_board requires title.");
|
|
15500
|
-
const board = await
|
|
15856
|
+
const board = await createBoard2(projectRoot, {
|
|
15501
15857
|
title: input.title,
|
|
15502
15858
|
...input.description !== void 0 ? { description: input.description } : {},
|
|
15503
15859
|
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
@@ -15507,7 +15863,7 @@ var kanbanTool = {
|
|
|
15507
15863
|
}
|
|
15508
15864
|
case "update_board": {
|
|
15509
15865
|
if (!input.boardId) return fail("update_board requires boardId.");
|
|
15510
|
-
const board = await
|
|
15866
|
+
const board = await updateBoard2(projectRoot, input.boardId, {
|
|
15511
15867
|
...input.title !== void 0 ? { title: input.title } : {},
|
|
15512
15868
|
...input.description !== void 0 ? { description: input.description } : {},
|
|
15513
15869
|
...input.tags !== void 0 ? { tags: input.tags } : {}
|
|
@@ -15527,7 +15883,7 @@ var kanbanTool = {
|
|
|
15527
15883
|
}
|
|
15528
15884
|
case "delete_board": {
|
|
15529
15885
|
if (!input.boardId) return fail("delete_board requires boardId.");
|
|
15530
|
-
const removed = await
|
|
15886
|
+
const removed = await removeBoard2(projectRoot, input.boardId);
|
|
15531
15887
|
return { ok: removed, message: removed ? "Board deleted." : "Board not found." };
|
|
15532
15888
|
}
|
|
15533
15889
|
case "generate_board": {
|
|
@@ -15538,14 +15894,14 @@ var kanbanTool = {
|
|
|
15538
15894
|
...input.context !== void 0 ? { context: input.context } : {},
|
|
15539
15895
|
...input.columns !== void 0 ? { columns: input.columns } : {}
|
|
15540
15896
|
});
|
|
15541
|
-
const board = await
|
|
15897
|
+
const board = await createBoard2(projectRoot, boardInput);
|
|
15542
15898
|
for (const taskInput2 of parseLinesIntoTasks(
|
|
15543
15899
|
input.description,
|
|
15544
15900
|
board.columns[0]?.id ?? "backlog"
|
|
15545
15901
|
)) {
|
|
15546
15902
|
await addTask(projectRoot, board.id, taskInput2);
|
|
15547
15903
|
}
|
|
15548
|
-
return okBoard(await
|
|
15904
|
+
return okBoard(await getBoard2(projectRoot, board.id) ?? board, "Board generated.");
|
|
15549
15905
|
}
|
|
15550
15906
|
case "export_markdown": {
|
|
15551
15907
|
const board = await requireBoard(projectRoot, input.boardId);
|
|
@@ -15578,8 +15934,8 @@ var kanbanTool = {
|
|
|
15578
15934
|
if (!input.boardId || !input.taskGraph) {
|
|
15579
15935
|
return fail("sync_task_graph requires boardId and taskGraph.");
|
|
15580
15936
|
}
|
|
15581
|
-
const graph =
|
|
15582
|
-
const result = await
|
|
15937
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
15938
|
+
const result = await syncBoardFromTaskGraph2(projectRoot, input.boardId, graph, {
|
|
15583
15939
|
...input.title !== void 0 ? { title: input.title } : {},
|
|
15584
15940
|
...input.description !== void 0 ? { description: input.description } : {},
|
|
15585
15941
|
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
@@ -15596,6 +15952,59 @@ var kanbanTool = {
|
|
|
15596
15952
|
board: result.board
|
|
15597
15953
|
} : fail("Board not found.");
|
|
15598
15954
|
}
|
|
15955
|
+
case "create_from_graph": {
|
|
15956
|
+
if (!input.taskGraph) return fail("create_from_graph requires taskGraph.");
|
|
15957
|
+
const graph = deserializeTaskGraph2(input.taskGraph);
|
|
15958
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
15959
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
15960
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
15961
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
15962
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
15963
|
+
...input.sourceSystem !== void 0 ? { sourceSystem: input.sourceSystem } : {},
|
|
15964
|
+
...input.phaseId !== void 0 ? { phaseId: input.phaseId } : {},
|
|
15965
|
+
...input.includeCompletedTasks !== void 0 ? { includeCompletedTasks: input.includeCompletedTasks } : {}
|
|
15966
|
+
});
|
|
15967
|
+
return {
|
|
15968
|
+
ok: true,
|
|
15969
|
+
message: `Created board "${board.title}" from task graph with ${board.tasks.length} tasks.`,
|
|
15970
|
+
board
|
|
15971
|
+
};
|
|
15972
|
+
}
|
|
15973
|
+
case "import_session_tasks": {
|
|
15974
|
+
const taskPath = ctx.meta?.["task.path"];
|
|
15975
|
+
if (!taskPath) return fail("No session task file for this session.");
|
|
15976
|
+
const file = await loadTasks2(taskPath);
|
|
15977
|
+
if (!file || file.tasks.length === 0) return fail("No session tasks to import.");
|
|
15978
|
+
const sessionId = ctx.session?.id ?? file.sessionId ?? "session";
|
|
15979
|
+
const graph = deserializeTaskGraph2(taskFileToSerializedGraph(file.tasks, sessionId));
|
|
15980
|
+
const tags = ["session", `session:${sessionId}`];
|
|
15981
|
+
const existing = (await listBoards2(projectRoot)).find(
|
|
15982
|
+
(b) => b.tags?.includes(`session:${sessionId}`)
|
|
15983
|
+
);
|
|
15984
|
+
if (existing) {
|
|
15985
|
+
const result = await syncBoardFromTaskGraph2(projectRoot, existing.id, graph, {
|
|
15986
|
+
sourceSystem: "session",
|
|
15987
|
+
tags,
|
|
15988
|
+
archiveMissingTasks: true,
|
|
15989
|
+
includeCompletedTasks: true
|
|
15990
|
+
});
|
|
15991
|
+
return result ? {
|
|
15992
|
+
ok: true,
|
|
15993
|
+
message: `Synced ${file.tasks.length} session tasks into board "${result.board.title}".`,
|
|
15994
|
+
board: result.board
|
|
15995
|
+
} : fail("Session board vanished mid-sync.");
|
|
15996
|
+
}
|
|
15997
|
+
const { board } = await createBoardFromTaskGraph(projectRoot, graph, {
|
|
15998
|
+
title: `Session tasks (${sessionId.slice(0, 8)})`,
|
|
15999
|
+
sourceSystem: "session",
|
|
16000
|
+
tags
|
|
16001
|
+
});
|
|
16002
|
+
return {
|
|
16003
|
+
ok: true,
|
|
16004
|
+
message: `Imported ${file.tasks.length} session tasks into new board "${board.title}".`,
|
|
16005
|
+
board
|
|
16006
|
+
};
|
|
16007
|
+
}
|
|
15599
16008
|
case "search_tasks": {
|
|
15600
16009
|
const tasks = await searchKanban(projectRoot, {
|
|
15601
16010
|
query: input.query,
|
|
@@ -16032,7 +16441,7 @@ function okTask(board, task, message) {
|
|
|
16032
16441
|
return { ok: true, message, board, task };
|
|
16033
16442
|
}
|
|
16034
16443
|
async function requireBoard(projectRoot, boardId) {
|
|
16035
|
-
return boardId ?
|
|
16444
|
+
return boardId ? getBoard2(projectRoot, boardId) : null;
|
|
16036
16445
|
}
|
|
16037
16446
|
function taskInput(input) {
|
|
16038
16447
|
const assignment = hasAssignmentInput(input) ? assignmentForTaskCreate(input) : void 0;
|
|
@@ -16041,14 +16450,23 @@ function taskInput(input) {
|
|
|
16041
16450
|
columnId: input.columnId,
|
|
16042
16451
|
description: input.description,
|
|
16043
16452
|
priority: input.priority,
|
|
16453
|
+
...input.taskType !== void 0 ? { type: input.taskType } : {},
|
|
16044
16454
|
status: input.status,
|
|
16045
16455
|
labels: input.labels,
|
|
16046
16456
|
...assignment?.agentId ?? assignment?.role ?? assignment?.name ? { assignedAgent: assignment.agentId ?? assignment.role ?? assignment.name } : {},
|
|
16047
16457
|
...input.assignee ?? assignment?.name ?? assignment?.agentId ? { assignee: input.assignee ?? assignment?.name ?? assignment?.agentId } : {},
|
|
16048
|
-
...input
|
|
16458
|
+
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
16459
|
+
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
|
|
16049
16460
|
...assignment ? { assignment } : {}
|
|
16050
16461
|
};
|
|
16051
16462
|
}
|
|
16463
|
+
function mergedDependsOn(input) {
|
|
16464
|
+
const ids = [
|
|
16465
|
+
...input.dependsOn ?? [],
|
|
16466
|
+
...input.dependencyTaskId !== void 0 ? [input.dependencyTaskId] : []
|
|
16467
|
+
].filter((id, i, arr) => id && arr.indexOf(id) === i);
|
|
16468
|
+
return ids.length > 0 ? ids : void 0;
|
|
16469
|
+
}
|
|
16052
16470
|
function taskPatch(input) {
|
|
16053
16471
|
return {
|
|
16054
16472
|
title: input.title,
|
|
@@ -16056,10 +16474,12 @@ function taskPatch(input) {
|
|
|
16056
16474
|
columnId: input.columnId,
|
|
16057
16475
|
order: input.order,
|
|
16058
16476
|
priority: input.priority,
|
|
16477
|
+
...input.taskType !== void 0 ? { type: input.taskType } : {},
|
|
16059
16478
|
status: input.status,
|
|
16060
16479
|
labels: input.labels,
|
|
16061
16480
|
assignedAgent: input.agentId,
|
|
16062
|
-
...input
|
|
16481
|
+
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
16482
|
+
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {}
|
|
16063
16483
|
};
|
|
16064
16484
|
}
|
|
16065
16485
|
function assignmentInput(input) {
|
|
@@ -16804,12 +17224,12 @@ import {
|
|
|
16804
17224
|
deriveTodosFromPlanItem,
|
|
16805
17225
|
formatPlan,
|
|
16806
17226
|
getPlanTemplate,
|
|
16807
|
-
mutatePlan,
|
|
17227
|
+
mutatePlan as mutatePlan2,
|
|
16808
17228
|
removePlanItem,
|
|
16809
17229
|
setPlanItemStatus
|
|
16810
17230
|
} from "@wrongstack/core";
|
|
16811
17231
|
import {
|
|
16812
|
-
mutateTasks,
|
|
17232
|
+
mutateTasks as mutateTasks2,
|
|
16813
17233
|
formatTaskList
|
|
16814
17234
|
} from "@wrongstack/core";
|
|
16815
17235
|
import { randomUUID } from "node:crypto";
|
|
@@ -16896,7 +17316,7 @@ var planTool = {
|
|
|
16896
17316
|
let didTaskify = false;
|
|
16897
17317
|
let plan;
|
|
16898
17318
|
try {
|
|
16899
|
-
plan = await
|
|
17319
|
+
plan = await mutatePlan2(planPath, sessionId, async (p) => {
|
|
16900
17320
|
switch (input.action) {
|
|
16901
17321
|
case "show":
|
|
16902
17322
|
break;
|
|
@@ -17022,6 +17442,7 @@ var planTool = {
|
|
|
17022
17442
|
open: 0
|
|
17023
17443
|
};
|
|
17024
17444
|
}
|
|
17445
|
+
await projectSessionPlanToKanban(ctx.projectRoot, plan.items, sessionId);
|
|
17025
17446
|
if (early) return early;
|
|
17026
17447
|
if (didTaskify) {
|
|
17027
17448
|
const taskPathRaw = ctx.meta["task.path"];
|
|
@@ -17035,7 +17456,7 @@ var planTool = {
|
|
|
17035
17456
|
}
|
|
17036
17457
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
17037
17458
|
try {
|
|
17038
|
-
const taskFile = await
|
|
17459
|
+
const taskFile = await mutateTasks2(taskPath, sessionId, (f) => {
|
|
17039
17460
|
f.tasks.push({
|
|
17040
17461
|
id: `task_${randomUUID()}`,
|
|
17041
17462
|
title: taskifyMeta.title,
|
|
@@ -18172,11 +18593,11 @@ import {
|
|
|
18172
18593
|
formatTaskList as formatTaskList2
|
|
18173
18594
|
} from "@wrongstack/core";
|
|
18174
18595
|
import {
|
|
18175
|
-
mutateTasks as
|
|
18596
|
+
mutateTasks as mutateTasks3
|
|
18176
18597
|
} from "@wrongstack/core";
|
|
18177
18598
|
import {
|
|
18178
18599
|
addPlanItem as addPlanItem2,
|
|
18179
|
-
mutatePlan as
|
|
18600
|
+
mutatePlan as mutatePlan3,
|
|
18180
18601
|
formatPlan as formatPlan2
|
|
18181
18602
|
} from "@wrongstack/core";
|
|
18182
18603
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -18296,7 +18717,7 @@ var taskTool = {
|
|
|
18296
18717
|
let todosToReplace = null;
|
|
18297
18718
|
let file;
|
|
18298
18719
|
try {
|
|
18299
|
-
file = await
|
|
18720
|
+
file = await mutateTasks3(taskPath, sessionId, async (f) => {
|
|
18300
18721
|
switch (input.action) {
|
|
18301
18722
|
case "show":
|
|
18302
18723
|
break;
|
|
@@ -18486,6 +18907,7 @@ var taskTool = {
|
|
|
18486
18907
|
};
|
|
18487
18908
|
}
|
|
18488
18909
|
if (todosToReplace) ctx.state.replaceTodos(todosToReplace);
|
|
18910
|
+
await projectSessionTasksToKanban(ctx.projectRoot, file.tasks, sessionId);
|
|
18489
18911
|
if (early) return early;
|
|
18490
18912
|
if (didPlanify) {
|
|
18491
18913
|
const { title, details } = planifyMeta;
|
|
@@ -18499,7 +18921,7 @@ var taskTool = {
|
|
|
18499
18921
|
}
|
|
18500
18922
|
let formatted = "";
|
|
18501
18923
|
try {
|
|
18502
|
-
await
|
|
18924
|
+
await mutatePlan3(planPath, sessionId, (pf) => {
|
|
18503
18925
|
const { plan: updated } = addPlanItem2(pf, title, details || void 0);
|
|
18504
18926
|
formatted = formatPlan2(updated);
|
|
18505
18927
|
return updated;
|
|
@@ -18731,8 +19153,7 @@ function parseResult(runner, result, duration) {
|
|
|
18731
19153
|
}
|
|
18732
19154
|
|
|
18733
19155
|
// src/todo.ts
|
|
18734
|
-
import { loadPlan, savePlan, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core";
|
|
18735
|
-
import { loadTasks, saveTasks } from "@wrongstack/core";
|
|
19156
|
+
import { loadPlan as loadPlan2, loadTasks as loadTasks3, savePlan, saveTasks, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core";
|
|
18736
19157
|
var todoTool = {
|
|
18737
19158
|
name: "todo",
|
|
18738
19159
|
category: "Session",
|
|
@@ -18741,7 +19162,7 @@ var todoTool = {
|
|
|
18741
19162
|
permission: "auto",
|
|
18742
19163
|
mutating: false,
|
|
18743
19164
|
// mutates only conversation state (ctx.todos), not external state — no confirmation needed
|
|
18744
|
-
timeoutMs:
|
|
19165
|
+
timeoutMs: 5e3,
|
|
18745
19166
|
capabilities: ["session.todo"],
|
|
18746
19167
|
icon: "todo",
|
|
18747
19168
|
inputSchema: {
|
|
@@ -18793,16 +19214,21 @@ var todoTool = {
|
|
|
18793
19214
|
}
|
|
18794
19215
|
}
|
|
18795
19216
|
ctx.state.replaceTodos(items);
|
|
19217
|
+
await projectSessionTodosToKanban(ctx.projectRoot, items, ctx.session?.id ?? "session");
|
|
18796
19218
|
const completedPlanIds = /* @__PURE__ */ new Set();
|
|
18797
19219
|
const completedTaskIds = /* @__PURE__ */ new Set();
|
|
18798
19220
|
const pendingPlanIds = /* @__PURE__ */ new Set();
|
|
18799
19221
|
const pendingTaskIds = /* @__PURE__ */ new Set();
|
|
18800
19222
|
for (const item of items) {
|
|
18801
19223
|
if (item.promotedFromPlan) {
|
|
18802
|
-
(item.status === "completed" ? completedPlanIds : pendingPlanIds).add(
|
|
19224
|
+
(item.status === "completed" ? completedPlanIds : pendingPlanIds).add(
|
|
19225
|
+
item.promotedFromPlan
|
|
19226
|
+
);
|
|
18803
19227
|
}
|
|
18804
19228
|
if (item.promotedFromTask) {
|
|
18805
|
-
(item.status === "completed" ? completedTaskIds : pendingTaskIds).add(
|
|
19229
|
+
(item.status === "completed" ? completedTaskIds : pendingTaskIds).add(
|
|
19230
|
+
item.promotedFromTask
|
|
19231
|
+
);
|
|
18806
19232
|
}
|
|
18807
19233
|
}
|
|
18808
19234
|
for (const planId of completedPlanIds) {
|
|
@@ -18810,7 +19236,7 @@ var todoTool = {
|
|
|
18810
19236
|
const planPath = ctx.meta["plan.path"];
|
|
18811
19237
|
if (typeof planPath !== "string" || !planPath) continue;
|
|
18812
19238
|
try {
|
|
18813
|
-
const plan = await
|
|
19239
|
+
const plan = await loadPlan2(planPath);
|
|
18814
19240
|
if (plan) {
|
|
18815
19241
|
const updated = setPlanItemStatus2(plan, planId, "done");
|
|
18816
19242
|
await savePlan(planPath, updated);
|
|
@@ -18823,7 +19249,7 @@ var todoTool = {
|
|
|
18823
19249
|
const taskPath = ctx.meta["task.path"];
|
|
18824
19250
|
if (typeof taskPath !== "string" || !taskPath) continue;
|
|
18825
19251
|
try {
|
|
18826
|
-
const file = await
|
|
19252
|
+
const file = await loadTasks3(taskPath);
|
|
18827
19253
|
if (file) {
|
|
18828
19254
|
const task = file.tasks.find((t) => t.id === taskId);
|
|
18829
19255
|
if (task && task.status !== "completed") {
|