@wrongstack/tools 0.309.0 → 0.310.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_regex.d.ts +6 -34
- package/dist/audit.js +3 -3
- package/dist/bash.js +4 -4
- package/dist/builtin.js +3020 -1680
- package/dist/codebase-index/binary-frame.d.ts +57 -8
- package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +6 -0
- package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +6 -0
- package/dist/codebase-index/codebase-search-tool.d.ts +15 -5
- package/dist/codebase-index/index-service.d.ts +3 -19
- package/dist/codebase-index/index.js +2784 -1464
- package/dist/codebase-index/indexer.d.ts +3 -0
- package/dist/codebase-index/parser-batch.d.ts +53 -0
- package/dist/codebase-index/parser-dispatch.d.ts +32 -0
- package/dist/codebase-index/parser-output.d.ts +14 -0
- package/dist/codebase-index/parser-worker-pool.d.ts +57 -4
- package/dist/codebase-index/parser-worker-script.d.ts +5 -2
- package/dist/codebase-index/parser-worker-script.js +4042 -0
- package/dist/codebase-index/project-server-cache.d.ts +16 -0
- package/dist/codebase-index/project-server-client.d.ts +2 -2
- package/dist/codebase-index/project-server-query-cache.d.ts +88 -0
- package/dist/codebase-index/project-server.js +2846 -1308
- package/dist/codebase-index/py-parser.d.ts +5 -0
- package/dist/codebase-index/schema.d.ts +14 -1
- package/dist/codebase-index/sqlite-runtime.d.ts +2 -2
- package/dist/codebase-index/tree-sitter/queries.d.ts +30 -3
- package/dist/codebase-index/tree-sitter/visitor.d.ts +2 -1
- package/dist/codebase-index/vector-search.d.ts +12 -0
- package/dist/codebase-index/wal-maintenance.d.ts +58 -0
- package/dist/codebase-index/worker-protocol/contracts.d.ts +44 -0
- package/dist/codebase-index/worker-protocol.d.ts +17 -1
- package/dist/codebase-index/worker.js +2300 -1020
- package/dist/codebase-index/writer-admin.d.ts +11 -0
- package/dist/codebase-index/writer-helpers.d.ts +31 -1
- package/dist/codebase-index/writer-mutations.d.ts +0 -6
- package/dist/codebase-index/writer-schema.d.ts +2 -2
- package/dist/codebase-index/writer.d.ts +15 -0
- package/dist/edit.js +2511 -1203
- package/dist/exec.js +8 -6
- package/dist/format.js +3 -3
- package/dist/git.js +6 -0
- package/dist/grep.js +5 -124
- package/dist/index.js +3016 -1739
- package/dist/install.js +3 -3
- package/dist/json.js +5 -124
- package/dist/kanban.js +130 -0
- package/dist/languages/index.js +3 -3
- package/dist/lint.js +3 -3
- package/dist/logs.js +5 -121
- package/dist/outdated.js +3 -3
- package/dist/pack.js +3020 -1680
- package/dist/patch.js +2524 -1216
- package/dist/plan.js +106 -0
- package/dist/process-registry.js +1 -1
- package/dist/read.js +2506 -1198
- package/dist/replace.js +2487 -1295
- package/dist/search.js +6 -2
- package/dist/session-kanban.js +24 -16
- package/dist/task.js +106 -0
- package/dist/test.js +3 -3
- package/dist/todo.js +106 -0
- package/dist/tool-tier.d.ts +11 -0
- package/dist/tool-tier.js +3028 -1680
- package/dist/tree.js +14 -3
- package/dist/typecheck.js +3 -3
- package/dist/win32.js +5 -5
- package/dist/write.js +2513 -1205
- package/package.json +5 -4
package/dist/search.js
CHANGED
|
@@ -421,10 +421,14 @@ function parseDuckDuckGo(html, num) {
|
|
|
421
421
|
}
|
|
422
422
|
return results;
|
|
423
423
|
}
|
|
424
|
+
function escapeRegex(s) {
|
|
425
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
426
|
+
}
|
|
424
427
|
function getHtmlAttr(attrs, name) {
|
|
425
|
-
const
|
|
428
|
+
const escaped = escapeRegex(name);
|
|
429
|
+
const quoted = new RegExp(`\\b${escaped}\\s*=\\s*(["'])(.*?)\\1`, "i").exec(attrs);
|
|
426
430
|
if (quoted?.[2]) return decodeHtmlEntities(quoted[2]);
|
|
427
|
-
const unquoted = new RegExp(`\\b${
|
|
431
|
+
const unquoted = new RegExp(`\\b${escaped}\\s*=\\s*([^\\s>]+)`, "i").exec(attrs);
|
|
428
432
|
return unquoted?.[1] ? decodeHtmlEntities(unquoted[1]) : void 0;
|
|
429
433
|
}
|
|
430
434
|
function normalizeDuckDuckGoUrl(raw) {
|
package/dist/session-kanban.js
CHANGED
|
@@ -293,24 +293,32 @@ function applyManagedKanbanBoardToTodos(context, board, suppressedTodoMirrors2,
|
|
|
293
293
|
async function applySessionKanbanTaskToSource(context, task, suppressedTodoMirrors2, options = {}) {
|
|
294
294
|
const originId = task.origin?.taskId;
|
|
295
295
|
const graphId = task.origin?.graphId ?? "";
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
296
|
+
const isTodoOrigin = task.origin?.system === "session-todo" || graphId.startsWith("todo:");
|
|
297
|
+
const targetTodo = context.todos?.find(
|
|
298
|
+
(todo) => originId !== void 0 && todo.id === originId || todo.kanbanTaskId === task.id || todo.id === task.id && !todo.promotedFromPlan && !todo.promotedFromTask
|
|
299
|
+
);
|
|
300
|
+
if (isTodoOrigin || targetTodo) {
|
|
301
|
+
const matchedId = targetTodo?.id ?? originId;
|
|
302
|
+
if (matchedId) {
|
|
303
|
+
const mappedStatus = todoStatus(task);
|
|
304
|
+
const next = options.remove ? context.todos.filter((todo) => todo.id !== matchedId) : context.todos.map(
|
|
305
|
+
(todo) => todo.id === matchedId ? {
|
|
306
|
+
...todo,
|
|
307
|
+
content: task.title,
|
|
308
|
+
status: mappedStatus,
|
|
309
|
+
kanbanTaskId: task.id
|
|
310
|
+
} : todo
|
|
311
|
+
);
|
|
312
|
+
suppressedTodoMirrors2.add(context);
|
|
313
|
+
try {
|
|
314
|
+
context.state.replaceTodos(next);
|
|
315
|
+
} finally {
|
|
316
|
+
suppressedTodoMirrors2.delete(context);
|
|
317
|
+
}
|
|
318
|
+
return { source: "todo", todos: [...context.todos] };
|
|
311
319
|
}
|
|
312
|
-
return { source: "todo", todos: [...context.todos] };
|
|
313
320
|
}
|
|
321
|
+
if (!originId) return { source: null };
|
|
314
322
|
const id = context.session?.id ?? "";
|
|
315
323
|
if (task.origin?.system === "session-plan" || graphId.startsWith("plan:")) {
|
|
316
324
|
const planPath = context.meta["plan.path"];
|
package/dist/task.js
CHANGED
|
@@ -234,6 +234,81 @@ function applyManagedKanbanBoardToTodos(context, board, suppressedTodoMirrors2,
|
|
|
234
234
|
broadcastTodoUpdate(context, context.todos);
|
|
235
235
|
return [...context.todos];
|
|
236
236
|
}
|
|
237
|
+
async function applySessionKanbanTaskToSource(context, task, suppressedTodoMirrors2, options = {}) {
|
|
238
|
+
const originId = task.origin?.taskId;
|
|
239
|
+
const graphId = task.origin?.graphId ?? "";
|
|
240
|
+
const isTodoOrigin = task.origin?.system === "session-todo" || graphId.startsWith("todo:");
|
|
241
|
+
const targetTodo = context.todos?.find(
|
|
242
|
+
(todo) => originId !== void 0 && todo.id === originId || todo.kanbanTaskId === task.id || todo.id === task.id && !todo.promotedFromPlan && !todo.promotedFromTask
|
|
243
|
+
);
|
|
244
|
+
if (isTodoOrigin || targetTodo) {
|
|
245
|
+
const matchedId = targetTodo?.id ?? originId;
|
|
246
|
+
if (matchedId) {
|
|
247
|
+
const mappedStatus = todoStatus(task);
|
|
248
|
+
const next = options.remove ? context.todos.filter((todo) => todo.id !== matchedId) : context.todos.map(
|
|
249
|
+
(todo) => todo.id === matchedId ? {
|
|
250
|
+
...todo,
|
|
251
|
+
content: task.title,
|
|
252
|
+
status: mappedStatus,
|
|
253
|
+
kanbanTaskId: task.id
|
|
254
|
+
} : todo
|
|
255
|
+
);
|
|
256
|
+
suppressedTodoMirrors2.add(context);
|
|
257
|
+
try {
|
|
258
|
+
context.state.replaceTodos(next);
|
|
259
|
+
} finally {
|
|
260
|
+
suppressedTodoMirrors2.delete(context);
|
|
261
|
+
}
|
|
262
|
+
return { source: "todo", todos: [...context.todos] };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (!originId) return { source: null };
|
|
266
|
+
const id = context.session?.id ?? "";
|
|
267
|
+
if (task.origin?.system === "session-plan" || graphId.startsWith("plan:")) {
|
|
268
|
+
const planPath = context.meta["plan.path"];
|
|
269
|
+
if (typeof planPath !== "string" || !planPath) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
"Cannot reflect this Kanban edit back to its plan source: the session has no plan.path configured. The board mutation already succeeded; the plan file is now out of sync."
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
const plan = await mutatePlan(planPath, id, (file) => ({
|
|
275
|
+
...file,
|
|
276
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
277
|
+
items: options.remove ? file.items.filter((item) => item.id !== originId) : file.items.map(
|
|
278
|
+
(item) => item.id === originId ? {
|
|
279
|
+
...item,
|
|
280
|
+
title: task.title,
|
|
281
|
+
details: task.description,
|
|
282
|
+
status: task.status === "completed" ? "done" : task.status === "in_progress" || task.status === "review" ? "in_progress" : "open",
|
|
283
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
284
|
+
} : item
|
|
285
|
+
)
|
|
286
|
+
}));
|
|
287
|
+
return { source: "plan", plan };
|
|
288
|
+
}
|
|
289
|
+
if (task.origin?.system === "session-task" || task.origin?.system === "session" || graphId.startsWith("session:")) {
|
|
290
|
+
const taskPath = context.meta["task.path"];
|
|
291
|
+
if (typeof taskPath !== "string" || !taskPath) {
|
|
292
|
+
throw new Error(
|
|
293
|
+
"Cannot reflect this Kanban edit back to its task source: the session has no task.path configured. The board mutation already succeeded; the task file is now out of sync."
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
const tasks = await mutateTasks(taskPath, id, (file) => ({
|
|
297
|
+
...file,
|
|
298
|
+
tasks: options.remove ? file.tasks.filter((item) => item.id !== originId) : file.tasks.map(
|
|
299
|
+
(item) => item.id === originId ? {
|
|
300
|
+
...item,
|
|
301
|
+
title: task.title,
|
|
302
|
+
description: task.description,
|
|
303
|
+
status: sourceStatus(task),
|
|
304
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
305
|
+
} : item
|
|
306
|
+
)
|
|
307
|
+
}));
|
|
308
|
+
return { source: "task", tasks };
|
|
309
|
+
}
|
|
310
|
+
return { source: null };
|
|
311
|
+
}
|
|
237
312
|
|
|
238
313
|
// src/session-kanban.ts
|
|
239
314
|
var SESSION_BOARD_TAG = "session-work";
|
|
@@ -477,6 +552,9 @@ function applyManagedKanbanBoardToTodos2(context, board) {
|
|
|
477
552
|
sessionOwnerFromTags: sessionIdFromTags
|
|
478
553
|
});
|
|
479
554
|
}
|
|
555
|
+
function applySessionKanbanTaskToSource2(context, task, options = {}) {
|
|
556
|
+
return applySessionKanbanTaskToSource(context, task, suppressedTodoMirrors, options);
|
|
557
|
+
}
|
|
480
558
|
|
|
481
559
|
// src/todo.ts
|
|
482
560
|
import {
|
|
@@ -1453,12 +1531,20 @@ function assignmentForTaskCreate(input) {
|
|
|
1453
1531
|
}
|
|
1454
1532
|
|
|
1455
1533
|
// src/kanban-lifecycle-actions.ts
|
|
1534
|
+
async function syncContextTask(ctx, task, options = {}) {
|
|
1535
|
+
if (!ctx?.state || !task) return;
|
|
1536
|
+
try {
|
|
1537
|
+
await applySessionKanbanTaskToSource2(ctx, task, options);
|
|
1538
|
+
} catch {
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1456
1541
|
async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
1457
1542
|
switch (input.action) {
|
|
1458
1543
|
case "add_task": {
|
|
1459
1544
|
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
1460
1545
|
const result = await addTask2(projectRoot, input.boardId, taskInput(input));
|
|
1461
1546
|
if (!result) return fail("Board not found.");
|
|
1547
|
+
await syncContextTask(ctx, result.task);
|
|
1462
1548
|
return okTask(result.board, result.task, `Task added.${atomicityNudge(result.task)}`);
|
|
1463
1549
|
}
|
|
1464
1550
|
case "split_task": {
|
|
@@ -1603,6 +1689,7 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1603
1689
|
);
|
|
1604
1690
|
}
|
|
1605
1691
|
ctx.setCurrentKanbanTask(task.id, board.id);
|
|
1692
|
+
await syncContextTask(ctx, task);
|
|
1606
1693
|
return okTask(
|
|
1607
1694
|
board,
|
|
1608
1695
|
task,
|
|
@@ -1612,6 +1699,9 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1612
1699
|
case "update_task": {
|
|
1613
1700
|
if (!input.boardId || !input.taskId) return fail("update_task requires boardId and taskId.");
|
|
1614
1701
|
const board = await updateTask2(projectRoot, input.boardId, input.taskId, taskPatch(input));
|
|
1702
|
+
if (board) {
|
|
1703
|
+
await syncContextTask(ctx, board.tasks.find((t) => t.id === input.taskId));
|
|
1704
|
+
}
|
|
1615
1705
|
return board ? okBoard(board, "Task updated.") : fail("Task not found.");
|
|
1616
1706
|
}
|
|
1617
1707
|
case "transition_task": {
|
|
@@ -1651,6 +1741,9 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1651
1741
|
if (result && input.lifecycleStage === "done" && result.task.verificationReport) {
|
|
1652
1742
|
recordKanbanVerificationEvidence(ctx, result.task.verificationReport);
|
|
1653
1743
|
}
|
|
1744
|
+
if (result?.task) {
|
|
1745
|
+
await syncContextTask(ctx, result.task);
|
|
1746
|
+
}
|
|
1654
1747
|
return result ? okTask(result.board, result.task, `Task advanced to ${result.transition.to}.`) : fail("Board or task not found.");
|
|
1655
1748
|
}
|
|
1656
1749
|
case "repair_managed_projection": {
|
|
@@ -1663,6 +1756,9 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1663
1756
|
actor: input.author,
|
|
1664
1757
|
comment: input.transitionComment
|
|
1665
1758
|
});
|
|
1759
|
+
if (result?.task) {
|
|
1760
|
+
await syncContextTask(ctx, result.task);
|
|
1761
|
+
}
|
|
1666
1762
|
return result ? okTask(
|
|
1667
1763
|
result.board,
|
|
1668
1764
|
result.task,
|
|
@@ -1680,14 +1776,22 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1680
1776
|
input.targetColumnId,
|
|
1681
1777
|
input.order
|
|
1682
1778
|
);
|
|
1779
|
+
if (board) {
|
|
1780
|
+
await syncContextTask(ctx, board.tasks.find((t) => t.id === input.taskId));
|
|
1781
|
+
}
|
|
1683
1782
|
return board ? okBoard(board, "Task moved.") : fail("Move failed.");
|
|
1684
1783
|
}
|
|
1685
1784
|
case "delete_task": {
|
|
1686
1785
|
if (!input.boardId || !input.taskId) return fail("delete_task requires boardId and taskId.");
|
|
1786
|
+
const boardBefore = await getBoard4(projectRoot, input.boardId);
|
|
1787
|
+
const taskToDelete = boardBefore?.tasks.find((t) => t.id === input.taskId);
|
|
1687
1788
|
const board = await removeTask(projectRoot, input.boardId, input.taskId);
|
|
1688
1789
|
if (board && ctx.currentKanbanTaskId === input.taskId) {
|
|
1689
1790
|
ctx.setCurrentKanbanTask?.(void 0, ctx.currentKanbanBoardId);
|
|
1690
1791
|
}
|
|
1792
|
+
if (board && taskToDelete) {
|
|
1793
|
+
await syncContextTask(ctx, taskToDelete, { remove: true });
|
|
1794
|
+
}
|
|
1691
1795
|
return board ? okBoard(board, "Task deleted.") : fail("Task not found.");
|
|
1692
1796
|
}
|
|
1693
1797
|
case "set_chain": {
|
|
@@ -1794,6 +1898,7 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1794
1898
|
issues: finalized.gate.issues.map((issue) => issue.message)
|
|
1795
1899
|
};
|
|
1796
1900
|
const gateMessage = finalized.gate.allowed ? `Completion gate ${finalized.gate.verdict === "skipped" ? "passed" : "passed"}; task completed.` : finalized.gate.enforcement === "strict" ? `Completion gate BLOCKED (verdict: ${finalized.gate.verdict}); task parked in review. Issues: ${gateSummary.issues.join(" | ")}` : `Completion gate failed softly (verdict: ${finalized.gate.verdict}); task completed with warnings. Issues: ${gateSummary.issues.join(" | ")}`;
|
|
1901
|
+
await syncContextTask(ctx, finalized.task);
|
|
1797
1902
|
return {
|
|
1798
1903
|
...okTask(finalized.board, finalized.task, `Assignment updated. ${gateMessage}`),
|
|
1799
1904
|
gate: gateSummary
|
|
@@ -1899,6 +2004,7 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1899
2004
|
msgParts.push(`Card advanced to ${transitionResult.transition.to}.`);
|
|
1900
2005
|
}
|
|
1901
2006
|
for (const w of lifecycleWarnings) msgParts.push(`Warning: ${w}`);
|
|
2007
|
+
await syncContextTask(ctx, responseTask);
|
|
1902
2008
|
return okTask(responseBoard, responseTask, msgParts.join(" "));
|
|
1903
2009
|
}
|
|
1904
2010
|
return okBoard(board, "Assignment updated.");
|
package/dist/test.js
CHANGED
|
@@ -141,12 +141,12 @@ function resolveWin32Command(cmd) {
|
|
|
141
141
|
}
|
|
142
142
|
return cmd;
|
|
143
143
|
}
|
|
144
|
-
var WIN32_SHELL_META = /[&|<>"
|
|
144
|
+
var WIN32_SHELL_META = /[&|<>"%\r\n\0]/;
|
|
145
145
|
function assertSafeWin32ShellArgs(args) {
|
|
146
146
|
for (const arg of args) {
|
|
147
147
|
if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
|
|
148
148
|
throw new Error(
|
|
149
|
-
'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > "
|
|
149
|
+
'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > " %, or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
|
|
150
150
|
);
|
|
151
151
|
}
|
|
152
152
|
}
|
|
@@ -377,7 +377,7 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
377
377
|
// redaction function. Synced with core copy.
|
|
378
378
|
/(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
|
|
379
379
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
380
|
-
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
380
|
+
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD|PASSPHRASE)\s*[=:]\s*[^\s,]+/gi,
|
|
381
381
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
382
382
|
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
383
383
|
// every such flag in the command line is redacted, not just the first.
|
package/dist/todo.js
CHANGED
|
@@ -383,6 +383,81 @@ function applyManagedKanbanBoardToTodos(context, board, suppressedTodoMirrors2,
|
|
|
383
383
|
broadcastTodoUpdate(context, context.todos);
|
|
384
384
|
return [...context.todos];
|
|
385
385
|
}
|
|
386
|
+
async function applySessionKanbanTaskToSource(context, task, suppressedTodoMirrors2, options = {}) {
|
|
387
|
+
const originId = task.origin?.taskId;
|
|
388
|
+
const graphId = task.origin?.graphId ?? "";
|
|
389
|
+
const isTodoOrigin = task.origin?.system === "session-todo" || graphId.startsWith("todo:");
|
|
390
|
+
const targetTodo = context.todos?.find(
|
|
391
|
+
(todo) => originId !== void 0 && todo.id === originId || todo.kanbanTaskId === task.id || todo.id === task.id && !todo.promotedFromPlan && !todo.promotedFromTask
|
|
392
|
+
);
|
|
393
|
+
if (isTodoOrigin || targetTodo) {
|
|
394
|
+
const matchedId = targetTodo?.id ?? originId;
|
|
395
|
+
if (matchedId) {
|
|
396
|
+
const mappedStatus = todoStatus(task);
|
|
397
|
+
const next = options.remove ? context.todos.filter((todo) => todo.id !== matchedId) : context.todos.map(
|
|
398
|
+
(todo) => todo.id === matchedId ? {
|
|
399
|
+
...todo,
|
|
400
|
+
content: task.title,
|
|
401
|
+
status: mappedStatus,
|
|
402
|
+
kanbanTaskId: task.id
|
|
403
|
+
} : todo
|
|
404
|
+
);
|
|
405
|
+
suppressedTodoMirrors2.add(context);
|
|
406
|
+
try {
|
|
407
|
+
context.state.replaceTodos(next);
|
|
408
|
+
} finally {
|
|
409
|
+
suppressedTodoMirrors2.delete(context);
|
|
410
|
+
}
|
|
411
|
+
return { source: "todo", todos: [...context.todos] };
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (!originId) return { source: null };
|
|
415
|
+
const id = context.session?.id ?? "";
|
|
416
|
+
if (task.origin?.system === "session-plan" || graphId.startsWith("plan:")) {
|
|
417
|
+
const planPath = context.meta["plan.path"];
|
|
418
|
+
if (typeof planPath !== "string" || !planPath) {
|
|
419
|
+
throw new Error(
|
|
420
|
+
"Cannot reflect this Kanban edit back to its plan source: the session has no plan.path configured. The board mutation already succeeded; the plan file is now out of sync."
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
const plan = await mutatePlan(planPath, id, (file) => ({
|
|
424
|
+
...file,
|
|
425
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
426
|
+
items: options.remove ? file.items.filter((item) => item.id !== originId) : file.items.map(
|
|
427
|
+
(item) => item.id === originId ? {
|
|
428
|
+
...item,
|
|
429
|
+
title: task.title,
|
|
430
|
+
details: task.description,
|
|
431
|
+
status: task.status === "completed" ? "done" : task.status === "in_progress" || task.status === "review" ? "in_progress" : "open",
|
|
432
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
433
|
+
} : item
|
|
434
|
+
)
|
|
435
|
+
}));
|
|
436
|
+
return { source: "plan", plan };
|
|
437
|
+
}
|
|
438
|
+
if (task.origin?.system === "session-task" || task.origin?.system === "session" || graphId.startsWith("session:")) {
|
|
439
|
+
const taskPath = context.meta["task.path"];
|
|
440
|
+
if (typeof taskPath !== "string" || !taskPath) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
"Cannot reflect this Kanban edit back to its task source: the session has no task.path configured. The board mutation already succeeded; the task file is now out of sync."
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
const tasks = await mutateTasks(taskPath, id, (file) => ({
|
|
446
|
+
...file,
|
|
447
|
+
tasks: options.remove ? file.tasks.filter((item) => item.id !== originId) : file.tasks.map(
|
|
448
|
+
(item) => item.id === originId ? {
|
|
449
|
+
...item,
|
|
450
|
+
title: task.title,
|
|
451
|
+
description: task.description,
|
|
452
|
+
status: sourceStatus(task),
|
|
453
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
454
|
+
} : item
|
|
455
|
+
)
|
|
456
|
+
}));
|
|
457
|
+
return { source: "task", tasks };
|
|
458
|
+
}
|
|
459
|
+
return { source: null };
|
|
460
|
+
}
|
|
386
461
|
|
|
387
462
|
// src/session-kanban.ts
|
|
388
463
|
var SESSION_BOARD_TAG = "session-work";
|
|
@@ -618,6 +693,9 @@ function applyManagedKanbanBoardToTodos2(context, board) {
|
|
|
618
693
|
sessionOwnerFromTags: sessionIdFromTags
|
|
619
694
|
});
|
|
620
695
|
}
|
|
696
|
+
function applySessionKanbanTaskToSource2(context, task, options = {}) {
|
|
697
|
+
return applySessionKanbanTaskToSource(context, task, suppressedTodoMirrors, options);
|
|
698
|
+
}
|
|
621
699
|
|
|
622
700
|
// src/kanban-board-actions.ts
|
|
623
701
|
async function handleKanbanBoardAction(projectRoot, input, ctx) {
|
|
@@ -1440,12 +1518,20 @@ function assignmentForTaskCreate(input) {
|
|
|
1440
1518
|
}
|
|
1441
1519
|
|
|
1442
1520
|
// src/kanban-lifecycle-actions.ts
|
|
1521
|
+
async function syncContextTask(ctx, task, options = {}) {
|
|
1522
|
+
if (!ctx?.state || !task) return;
|
|
1523
|
+
try {
|
|
1524
|
+
await applySessionKanbanTaskToSource2(ctx, task, options);
|
|
1525
|
+
} catch {
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1443
1528
|
async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
1444
1529
|
switch (input.action) {
|
|
1445
1530
|
case "add_task": {
|
|
1446
1531
|
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
1447
1532
|
const result = await addTask2(projectRoot, input.boardId, taskInput(input));
|
|
1448
1533
|
if (!result) return fail("Board not found.");
|
|
1534
|
+
await syncContextTask(ctx, result.task);
|
|
1449
1535
|
return okTask(result.board, result.task, `Task added.${atomicityNudge(result.task)}`);
|
|
1450
1536
|
}
|
|
1451
1537
|
case "split_task": {
|
|
@@ -1590,6 +1676,7 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1590
1676
|
);
|
|
1591
1677
|
}
|
|
1592
1678
|
ctx.setCurrentKanbanTask(task.id, board.id);
|
|
1679
|
+
await syncContextTask(ctx, task);
|
|
1593
1680
|
return okTask(
|
|
1594
1681
|
board,
|
|
1595
1682
|
task,
|
|
@@ -1599,6 +1686,9 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1599
1686
|
case "update_task": {
|
|
1600
1687
|
if (!input.boardId || !input.taskId) return fail("update_task requires boardId and taskId.");
|
|
1601
1688
|
const board = await updateTask2(projectRoot, input.boardId, input.taskId, taskPatch(input));
|
|
1689
|
+
if (board) {
|
|
1690
|
+
await syncContextTask(ctx, board.tasks.find((t) => t.id === input.taskId));
|
|
1691
|
+
}
|
|
1602
1692
|
return board ? okBoard(board, "Task updated.") : fail("Task not found.");
|
|
1603
1693
|
}
|
|
1604
1694
|
case "transition_task": {
|
|
@@ -1638,6 +1728,9 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1638
1728
|
if (result && input.lifecycleStage === "done" && result.task.verificationReport) {
|
|
1639
1729
|
recordKanbanVerificationEvidence(ctx, result.task.verificationReport);
|
|
1640
1730
|
}
|
|
1731
|
+
if (result?.task) {
|
|
1732
|
+
await syncContextTask(ctx, result.task);
|
|
1733
|
+
}
|
|
1641
1734
|
return result ? okTask(result.board, result.task, `Task advanced to ${result.transition.to}.`) : fail("Board or task not found.");
|
|
1642
1735
|
}
|
|
1643
1736
|
case "repair_managed_projection": {
|
|
@@ -1650,6 +1743,9 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1650
1743
|
actor: input.author,
|
|
1651
1744
|
comment: input.transitionComment
|
|
1652
1745
|
});
|
|
1746
|
+
if (result?.task) {
|
|
1747
|
+
await syncContextTask(ctx, result.task);
|
|
1748
|
+
}
|
|
1653
1749
|
return result ? okTask(
|
|
1654
1750
|
result.board,
|
|
1655
1751
|
result.task,
|
|
@@ -1667,14 +1763,22 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1667
1763
|
input.targetColumnId,
|
|
1668
1764
|
input.order
|
|
1669
1765
|
);
|
|
1766
|
+
if (board) {
|
|
1767
|
+
await syncContextTask(ctx, board.tasks.find((t) => t.id === input.taskId));
|
|
1768
|
+
}
|
|
1670
1769
|
return board ? okBoard(board, "Task moved.") : fail("Move failed.");
|
|
1671
1770
|
}
|
|
1672
1771
|
case "delete_task": {
|
|
1673
1772
|
if (!input.boardId || !input.taskId) return fail("delete_task requires boardId and taskId.");
|
|
1773
|
+
const boardBefore = await getBoard4(projectRoot, input.boardId);
|
|
1774
|
+
const taskToDelete = boardBefore?.tasks.find((t) => t.id === input.taskId);
|
|
1674
1775
|
const board = await removeTask(projectRoot, input.boardId, input.taskId);
|
|
1675
1776
|
if (board && ctx.currentKanbanTaskId === input.taskId) {
|
|
1676
1777
|
ctx.setCurrentKanbanTask?.(void 0, ctx.currentKanbanBoardId);
|
|
1677
1778
|
}
|
|
1779
|
+
if (board && taskToDelete) {
|
|
1780
|
+
await syncContextTask(ctx, taskToDelete, { remove: true });
|
|
1781
|
+
}
|
|
1678
1782
|
return board ? okBoard(board, "Task deleted.") : fail("Task not found.");
|
|
1679
1783
|
}
|
|
1680
1784
|
case "set_chain": {
|
|
@@ -1781,6 +1885,7 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1781
1885
|
issues: finalized.gate.issues.map((issue) => issue.message)
|
|
1782
1886
|
};
|
|
1783
1887
|
const gateMessage = finalized.gate.allowed ? `Completion gate ${finalized.gate.verdict === "skipped" ? "passed" : "passed"}; task completed.` : finalized.gate.enforcement === "strict" ? `Completion gate BLOCKED (verdict: ${finalized.gate.verdict}); task parked in review. Issues: ${gateSummary.issues.join(" | ")}` : `Completion gate failed softly (verdict: ${finalized.gate.verdict}); task completed with warnings. Issues: ${gateSummary.issues.join(" | ")}`;
|
|
1888
|
+
await syncContextTask(ctx, finalized.task);
|
|
1784
1889
|
return {
|
|
1785
1890
|
...okTask(finalized.board, finalized.task, `Assignment updated. ${gateMessage}`),
|
|
1786
1891
|
gate: gateSummary
|
|
@@ -1886,6 +1991,7 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1886
1991
|
msgParts.push(`Card advanced to ${transitionResult.transition.to}.`);
|
|
1887
1992
|
}
|
|
1888
1993
|
for (const w of lifecycleWarnings) msgParts.push(`Warning: ${w}`);
|
|
1994
|
+
await syncContextTask(ctx, responseTask);
|
|
1889
1995
|
return okTask(responseBoard, responseTask, msgParts.join(" "));
|
|
1890
1996
|
}
|
|
1891
1997
|
return okBoard(board, "Assignment updated.");
|
package/dist/tool-tier.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { ToolRegistry } from '@wrongstack/core/registry';
|
|
2
2
|
import type { ConcreteTokenSavingTier, Tool } from '@wrongstack/core/types';
|
|
3
|
+
/**
|
|
4
|
+
* Live count of tools each concrete token-saving tier exposes to the provider.
|
|
5
|
+
* This is the runtime source of truth — `off` counts every registered tool,
|
|
6
|
+
* the other tiers count the result of `selectBuiltinToolsForTier(tier, …)`.
|
|
7
|
+
*
|
|
8
|
+
* Website (`website/src/pages/ToolsPage.tsx`) and any other consumer that
|
|
9
|
+
* wants to render tier counts without actually loading the catalog should
|
|
10
|
+
* mirror this map. When `TIER1_TOOLS` / `TIER2_TOOLS` / `TIER3_TOOLS` change,
|
|
11
|
+
* regenerate these numbers from a smoke test (or update both together).
|
|
12
|
+
*/
|
|
13
|
+
export declare const BUILTIN_TIER_COUNTS: Readonly<Record<ConcreteTokenSavingTier, number>>;
|
|
3
14
|
/**
|
|
4
15
|
* Select built-in tools for a concrete token-saving tier while preserving the
|
|
5
16
|
* order and instances supplied by the caller.
|