@wrongstack/core 0.282.1 → 0.283.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/{agent-subagent-runner-DCczSoQj.d.ts → agent-subagent-runner-BsuWhB28.d.ts} +2 -2
- package/dist/coordination/index.d.ts +12 -12
- package/dist/coordination/index.js +315 -41
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +12 -12
- package/dist/defaults/index.js +316 -53
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-BOv8h6I1.d.ts → events-KmxSmvho.d.ts} +5 -0
- package/dist/execution/index.d.ts +7 -7
- package/dist/extension/index.d.ts +2 -2
- package/dist/{global-mailbox-MDDFhLYh.d.ts → global-mailbox-rWVt8YlO.d.ts} +70 -7
- package/dist/{goal-store-BEmDmSKF.d.ts → goal-store-8owBXJT2.d.ts} +1 -1
- package/dist/hq/index.d.ts +3 -3
- package/dist/hq/index.js +106 -32
- package/dist/hq/index.js.map +1 -1
- package/dist/{index-Dd-PJJ8A.d.ts → index-BhCteHAF.d.ts} +1 -1
- package/dist/index.d.ts +289 -19
- package/dist/index.js +919 -110
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +1 -1
- package/dist/kernel/index.d.ts +4 -4
- package/dist/kernel/index.js.map +1 -1
- package/dist/models/index.js +105 -42
- package/dist/models/index.js.map +1 -1
- package/dist/{multi-agent-coordinator-BRqtpn-a.d.ts → multi-agent-coordinator-CrjeTB9i.d.ts} +1 -1
- package/dist/{null-fleet-bus-BkptvIVo.d.ts → null-fleet-bus-D4e9R7Qc.d.ts} +9 -4
- package/dist/observability/index.d.ts +1 -1
- package/dist/{parallel-eternal-engine-BXECuOVG.d.ts → parallel-eternal-engine-iY2uxocj.d.ts} +4 -4
- package/dist/{provider-runner-CJtCs1Rw.d.ts → provider-runner-oQhDaZmH.d.ts} +1 -1
- package/dist/sdd/index.d.ts +4 -4
- package/dist/sdd/index.js.map +1 -1
- package/dist/storage/index.d.ts +5 -5
- package/dist/storage/index.js.map +1 -1
- package/dist/{todos-checkpoint-Bw83WMh9.d.ts → todos-checkpoint-BwCrj4Cb.d.ts} +1 -1
- package/dist/{tool-executor-4mWN2vHW.d.ts → tool-executor-kElSEgO0.d.ts} +4 -4
- package/dist/types/index.d.ts +8 -8
- package/dist/types/index.js +105 -45
- package/dist/types/index.js.map +1 -1
- package/dist/{worktree-manager-BrtjUFYk.d.ts → worktree-manager-BjyAW30D.d.ts} +1 -1
- package/instructions/modes/audit-lite.md +13 -0
- package/instructions/modes/debug-lite.md +13 -0
- package/instructions/modes/plan-lite.md +14 -0
- package/instructions/modes/refactor-lite.md +13 -0
- package/instructions/modes/research-lite.md +13 -0
- package/instructions/modes/review-lite.md +14 -0
- package/instructions/modes/test-lite.md +13 -0
- package/package.json +1 -1
package/dist/defaults/index.js
CHANGED
|
@@ -6689,6 +6689,16 @@ function getKanbanPath(projectRoot, boardId) {
|
|
|
6689
6689
|
}
|
|
6690
6690
|
return resolved;
|
|
6691
6691
|
}
|
|
6692
|
+
function getKanbanEventsPath(projectRoot, boardId) {
|
|
6693
|
+
assertValidBoardId(boardId);
|
|
6694
|
+
const dir = path4.resolve(getKanbanDir(projectRoot));
|
|
6695
|
+
const resolved = path4.resolve(dir, `${boardId}.events.jsonl`);
|
|
6696
|
+
const rel = path4.relative(dir, resolved);
|
|
6697
|
+
if (rel.startsWith("..") || path4.isAbsolute(rel)) {
|
|
6698
|
+
throw invalidBoardId(boardId);
|
|
6699
|
+
}
|
|
6700
|
+
return resolved;
|
|
6701
|
+
}
|
|
6692
6702
|
function isValidBoardId(boardId) {
|
|
6693
6703
|
return BOARD_ID_RE.test(boardId) && !boardId.includes("..");
|
|
6694
6704
|
}
|
|
@@ -6741,6 +6751,14 @@ async function readBoard(projectRoot, boardRef) {
|
|
|
6741
6751
|
throw err;
|
|
6742
6752
|
}
|
|
6743
6753
|
}
|
|
6754
|
+
async function appendKanbanEvent(projectRoot, boardId, event) {
|
|
6755
|
+
const filePath = getKanbanEventsPath(projectRoot, boardId);
|
|
6756
|
+
await withFileLock(filePath, async () => {
|
|
6757
|
+
await fsp7.mkdir(path4.dirname(filePath), { recursive: true });
|
|
6758
|
+
await fsp7.appendFile(filePath, `${JSON.stringify(event)}
|
|
6759
|
+
`, "utf8");
|
|
6760
|
+
});
|
|
6761
|
+
}
|
|
6744
6762
|
async function mutateBoard(projectRoot, boardRef, mutator) {
|
|
6745
6763
|
const boardId = await resolveBoardRef(projectRoot, boardRef);
|
|
6746
6764
|
if (!boardId) return null;
|
|
@@ -6839,9 +6857,12 @@ async function listBoards(projectRoot) {
|
|
|
6839
6857
|
return listBoardSummaries(projectRoot);
|
|
6840
6858
|
}
|
|
6841
6859
|
async function updateTaskAssignment(projectRoot, boardId, taskId, patch) {
|
|
6860
|
+
let event;
|
|
6842
6861
|
const updated = await mutateBoard(projectRoot, boardId, (board) => {
|
|
6843
6862
|
const task = findTask(board, taskId);
|
|
6844
6863
|
if (!task) return null;
|
|
6864
|
+
const previousColumnId = task.columnId;
|
|
6865
|
+
const beforeAssignment = task.assignment ? { ...task.assignment } : void 0;
|
|
6845
6866
|
const nextAssignment = {
|
|
6846
6867
|
...task.assignment ?? { status: "assigned" }
|
|
6847
6868
|
};
|
|
@@ -6879,10 +6900,17 @@ async function updateTaskAssignment(projectRoot, boardId, taskId, patch) {
|
|
|
6879
6900
|
}
|
|
6880
6901
|
delete task.completedAt;
|
|
6881
6902
|
}
|
|
6903
|
+
syncTaskColumnForStatus(board, task, previousColumnId);
|
|
6882
6904
|
task.updatedAt = nowIso();
|
|
6883
6905
|
board.updatedAt = task.updatedAt;
|
|
6906
|
+
event = createKanbanEvent(board.id, task, assignmentEventType(task.assignment.status), {
|
|
6907
|
+
before: beforeAssignment,
|
|
6908
|
+
after: { ...task.assignment },
|
|
6909
|
+
note: patch.error ?? patch.lastResult
|
|
6910
|
+
});
|
|
6884
6911
|
return task;
|
|
6885
6912
|
});
|
|
6913
|
+
if (updated && event) await emitKanbanEvent(projectRoot, event);
|
|
6886
6914
|
return updated?.result ? updated.board : null;
|
|
6887
6915
|
}
|
|
6888
6916
|
async function claimReadyTask(projectRoot, input = {}) {
|
|
@@ -6929,14 +6957,25 @@ function buildAssignment(input) {
|
|
|
6929
6957
|
...input.fallbackProfile !== void 0 ? { fallbackProfile: input.fallbackProfile } : {},
|
|
6930
6958
|
...input.fallbackModels !== void 0 ? { fallbackModels: input.fallbackModels } : {},
|
|
6931
6959
|
...input.tools !== void 0 ? { tools: input.tools } : {},
|
|
6932
|
-
...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {}
|
|
6960
|
+
...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {},
|
|
6961
|
+
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
6962
|
+
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
6963
|
+
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
6964
|
+
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
6965
|
+
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
6966
|
+
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {},
|
|
6967
|
+
...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
|
|
6968
|
+
...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
|
|
6969
|
+
...input.lastFailureKind !== void 0 ? { lastFailureKind: input.lastFailureKind } : {}
|
|
6933
6970
|
};
|
|
6934
6971
|
}
|
|
6935
6972
|
async function claimReadyTaskOnBoard(projectRoot, boardId, input) {
|
|
6973
|
+
let event;
|
|
6936
6974
|
const updated = await mutateBoard(projectRoot, boardId, (board) => {
|
|
6937
6975
|
const candidates = input.taskId ? [findTask(board, input.taskId)].filter((task2) => Boolean(task2)) : board.tasks.filter((task2) => isTaskReadyForWork(board, task2)).sort(compareTasksForWork);
|
|
6938
6976
|
const task = candidates.find((candidate) => isTaskReadyForWork(board, candidate));
|
|
6939
6977
|
if (!task) return null;
|
|
6978
|
+
const previousColumnId = task.columnId;
|
|
6940
6979
|
const current = task.assignment;
|
|
6941
6980
|
const assignment = buildAssignment({
|
|
6942
6981
|
...current?.agentId !== void 0 ? { agentId: current.agentId } : {},
|
|
@@ -6948,6 +6987,15 @@ async function claimReadyTaskOnBoard(projectRoot, boardId, input) {
|
|
|
6948
6987
|
...current?.fallbackModels !== void 0 ? { fallbackModels: current.fallbackModels } : {},
|
|
6949
6988
|
...current?.tools !== void 0 ? { tools: current.tools } : {},
|
|
6950
6989
|
...current?.allowedCapabilities !== void 0 ? { allowedCapabilities: current.allowedCapabilities } : {},
|
|
6990
|
+
...current?.leaseId !== void 0 ? { leaseId: current.leaseId } : {},
|
|
6991
|
+
...current?.claimedAt !== void 0 ? { claimedAt: current.claimedAt } : {},
|
|
6992
|
+
...current?.heartbeatAt !== void 0 ? { heartbeatAt: current.heartbeatAt } : {},
|
|
6993
|
+
...current?.leaseExpiresAt !== void 0 ? { leaseExpiresAt: current.leaseExpiresAt } : {},
|
|
6994
|
+
...current?.attempt !== void 0 ? { attempt: current.attempt } : {},
|
|
6995
|
+
...current?.maxAttempts !== void 0 ? { maxAttempts: current.maxAttempts } : {},
|
|
6996
|
+
...current?.costCeilingUsd !== void 0 ? { costCeilingUsd: current.costCeilingUsd } : {},
|
|
6997
|
+
...current?.retryPolicy !== void 0 ? { retryPolicy: current.retryPolicy } : {},
|
|
6998
|
+
...current?.lastFailureKind !== void 0 ? { lastFailureKind: current.lastFailureKind } : {},
|
|
6951
6999
|
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
6952
7000
|
...input.name !== void 0 ? { name: input.name } : {},
|
|
6953
7001
|
...input.role !== void 0 ? { role: input.role } : {},
|
|
@@ -6957,10 +7005,20 @@ async function claimReadyTaskOnBoard(projectRoot, boardId, input) {
|
|
|
6957
7005
|
...input.fallbackModels !== void 0 ? { fallbackModels: input.fallbackModels } : {},
|
|
6958
7006
|
...input.tools !== void 0 ? { tools: input.tools } : {},
|
|
6959
7007
|
...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {},
|
|
7008
|
+
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
7009
|
+
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
7010
|
+
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
7011
|
+
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
7012
|
+
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
7013
|
+
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {},
|
|
7014
|
+
...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
|
|
7015
|
+
...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
|
|
7016
|
+
...input.lastFailureKind !== void 0 ? { lastFailureKind: input.lastFailureKind } : {},
|
|
6960
7017
|
...input.assignee !== void 0 ? { assignee: input.assignee } : {},
|
|
6961
7018
|
status: input.status ?? "queued"
|
|
6962
7019
|
});
|
|
6963
|
-
assignment.
|
|
7020
|
+
assignment.claimedAt = assignment.claimedAt ?? nowIso();
|
|
7021
|
+
assignment.dispatchedAt = assignment.dispatchedAt ?? assignment.claimedAt;
|
|
6964
7022
|
task.assignment = assignment;
|
|
6965
7023
|
if (assignment.agentId ?? assignment.role ?? assignment.name) {
|
|
6966
7024
|
task.assignedAgent = assignment.agentId ?? assignment.role ?? assignment.name;
|
|
@@ -6970,10 +7028,16 @@ async function claimReadyTaskOnBoard(projectRoot, boardId, input) {
|
|
|
6970
7028
|
}
|
|
6971
7029
|
task.status = assignment.status === "running" ? "in_progress" : "ready";
|
|
6972
7030
|
delete task.completedAt;
|
|
7031
|
+
syncTaskColumnForStatus(board, task, previousColumnId);
|
|
6973
7032
|
task.updatedAt = nowIso();
|
|
6974
7033
|
board.updatedAt = task.updatedAt;
|
|
7034
|
+
event = createKanbanEvent(board.id, task, "task.claimed", {
|
|
7035
|
+
before: current ? { ...current } : void 0,
|
|
7036
|
+
after: { ...assignment }
|
|
7037
|
+
});
|
|
6975
7038
|
return task;
|
|
6976
7039
|
});
|
|
7040
|
+
if (updated && event) await emitKanbanEvent(projectRoot, event);
|
|
6977
7041
|
return updated?.result ? { board: updated.board, task: updated.result } : null;
|
|
6978
7042
|
}
|
|
6979
7043
|
function compareTasksForWork(a, b) {
|
|
@@ -7007,6 +7071,18 @@ function findTask(board, taskId) {
|
|
|
7007
7071
|
}
|
|
7008
7072
|
return matches[0];
|
|
7009
7073
|
}
|
|
7074
|
+
function existingColumnId(board, columnId) {
|
|
7075
|
+
if (!columnId) return void 0;
|
|
7076
|
+
const exact = board.columns.find((column) => column.id === columnId);
|
|
7077
|
+
if (exact) return exact.id;
|
|
7078
|
+
const matches = board.columns.filter((column) => column.id.startsWith(columnId));
|
|
7079
|
+
if (matches.length > 1) {
|
|
7080
|
+
throw new Error(
|
|
7081
|
+
`Ambiguous kanban column id "${columnId}": ${matches.slice(0, 5).map((column) => column.id).join(", ")}`
|
|
7082
|
+
);
|
|
7083
|
+
}
|
|
7084
|
+
return matches[0]?.id;
|
|
7085
|
+
}
|
|
7010
7086
|
function isTaskReadyForWork(board, task) {
|
|
7011
7087
|
if (!["pending", "ready"].includes(task.status)) return false;
|
|
7012
7088
|
if (task.assignment && ["queued", "running"].includes(task.assignment.status)) return false;
|
|
@@ -7014,6 +7090,60 @@ function isTaskReadyForWork(board, task) {
|
|
|
7014
7090
|
if (!areDependenciesMet(board, task.id)) return false;
|
|
7015
7091
|
return true;
|
|
7016
7092
|
}
|
|
7093
|
+
function createKanbanEvent(boardId, task, type, details = {}) {
|
|
7094
|
+
return {
|
|
7095
|
+
id: randomUUID(),
|
|
7096
|
+
boardId,
|
|
7097
|
+
taskId: task.id,
|
|
7098
|
+
type,
|
|
7099
|
+
ts: nowIso(),
|
|
7100
|
+
...task.assignment?.agentId !== void 0 ? { actor: task.assignment.agentId } : {},
|
|
7101
|
+
...task.assignment?.subagentId !== void 0 ? { subagentId: task.assignment.subagentId } : {},
|
|
7102
|
+
...task.assignment?.runTaskId !== void 0 ? { runTaskId: task.assignment.runTaskId } : {},
|
|
7103
|
+
...details
|
|
7104
|
+
};
|
|
7105
|
+
}
|
|
7106
|
+
async function emitKanbanEvent(projectRoot, event) {
|
|
7107
|
+
try {
|
|
7108
|
+
await appendKanbanEvent(projectRoot, event.boardId, event);
|
|
7109
|
+
} catch {
|
|
7110
|
+
}
|
|
7111
|
+
}
|
|
7112
|
+
function assignmentEventType(status) {
|
|
7113
|
+
return status === "completed" ? "task.assignment.completed" : status === "failed" ? "task.assignment.failed" : status === "running" ? "task.assignment.running" : status === "cancelled" ? "task.assignment.cancelled" : "task.assignment.updated";
|
|
7114
|
+
}
|
|
7115
|
+
function columnIdForKanbanStatus(board, status) {
|
|
7116
|
+
const preferred = status === "completed" ? ["done", "completed"] : status === "in_progress" ? ["in-progress", "progress", "doing"] : status === "review" || status === "failed" ? ["review"] : status === "blocked" ? ["blocked", "backlog"] : status === "ready" ? ["todo", "ready", "backlog"] : status === "archived" ? ["done", "archive", "backlog"] : ["todo", "backlog"];
|
|
7117
|
+
for (const columnRef of preferred) {
|
|
7118
|
+
const columnId = existingColumnId(board, columnRef);
|
|
7119
|
+
if (columnId) return columnId;
|
|
7120
|
+
}
|
|
7121
|
+
return board.columns[0]?.id;
|
|
7122
|
+
}
|
|
7123
|
+
function normalizeColumnTaskOrders(board, columnId) {
|
|
7124
|
+
board.tasks.filter((task) => task.columnId === columnId).sort((a, b) => a.order - b.order || a.createdAt.localeCompare(b.createdAt)).forEach((task, index) => {
|
|
7125
|
+
task.order = index;
|
|
7126
|
+
});
|
|
7127
|
+
}
|
|
7128
|
+
function syncTaskColumnForStatus(board, task, previousColumnId) {
|
|
7129
|
+
const nextColumnId = columnIdForKanbanStatus(board, task.status);
|
|
7130
|
+
if (!nextColumnId || nextColumnId === task.columnId) return;
|
|
7131
|
+
task.columnId = nextColumnId;
|
|
7132
|
+
normalizeColumnTaskOrders(board, previousColumnId);
|
|
7133
|
+
placeTaskInColumn(board, task, nextColumnId, void 0);
|
|
7134
|
+
}
|
|
7135
|
+
function placeTaskInColumn(board, task, columnId, targetOrder) {
|
|
7136
|
+
const tasks = board.tasks.filter((candidate) => candidate.columnId === columnId && candidate.id !== task.id).sort((a, b) => a.order - b.order || a.createdAt.localeCompare(b.createdAt));
|
|
7137
|
+
const index = clampOrder(targetOrder, tasks.length);
|
|
7138
|
+
tasks.splice(index, 0, task);
|
|
7139
|
+
tasks.forEach((candidate, order) => {
|
|
7140
|
+
candidate.order = order;
|
|
7141
|
+
});
|
|
7142
|
+
}
|
|
7143
|
+
function clampOrder(order, max) {
|
|
7144
|
+
if (!Number.isFinite(order)) return max;
|
|
7145
|
+
return Math.max(0, Math.min(Math.trunc(order), max));
|
|
7146
|
+
}
|
|
7017
7147
|
function nowIso() {
|
|
7018
7148
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
7019
7149
|
}
|
|
@@ -7145,6 +7275,9 @@ function makeLLMClassifier(complete) {
|
|
|
7145
7275
|
}
|
|
7146
7276
|
|
|
7147
7277
|
// src/coordination/director-tools.ts
|
|
7278
|
+
function nowIso2() {
|
|
7279
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
7280
|
+
}
|
|
7148
7281
|
function makeSpawnTool(director, roster) {
|
|
7149
7282
|
const inputSchema = {
|
|
7150
7283
|
type: "object",
|
|
@@ -7702,8 +7835,8 @@ function makeAssignTool(director) {
|
|
|
7702
7835
|
function makeKanbanQueueTool(director, roster) {
|
|
7703
7836
|
return {
|
|
7704
7837
|
name: "kanban_queue",
|
|
7705
|
-
description: "Claim dependency-ready Kanban tasks and dispatch them into the Director fleet. Preserves per-task provider/model/role/tool routing metadata, assigns each claimed task to a spawned subagent, and can await results while writing completion back to Kanban.",
|
|
7706
|
-
usageHint: 'Use action:"dispatch_ready" with optional boardId/taskId/maxTasks. Set awaitCompletion:true when you want this call to update Kanban to completed/failed before returning; otherwise use await_tasks and kanban mark_assignment later.',
|
|
7838
|
+
description: "Claim dependency-ready Kanban tasks and dispatch them into the Director fleet. Preserves per-task provider/model/role/tool routing metadata, seeds lease metadata (claimedAt, leaseExpiresAt, heartbeatAt), assigns each claimed task to a spawned subagent, instructs workers to call kanban heartbeat_assignment periodically, and can await results while writing completion back to Kanban.",
|
|
7839
|
+
usageHint: 'Use action:"dispatch_ready" with optional boardId/taskId/maxTasks. Set heartbeatIntervalMs (default 60s) and leaseTtlMs (default 5m) to size stale-recovery windows. Set awaitCompletion:true when you want this call to update Kanban to completed/failed before returning; otherwise use await_tasks and kanban mark_assignment later.',
|
|
7707
7840
|
permission: "auto",
|
|
7708
7841
|
mutating: true,
|
|
7709
7842
|
capabilities: [ToolCapabilities.SUBAGENT_SPAWN, ToolCapabilities.FS_WRITE],
|
|
@@ -7734,6 +7867,16 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7734
7867
|
minimum: 1,
|
|
7735
7868
|
description: "Optional per-assigned-task tool-call cap."
|
|
7736
7869
|
},
|
|
7870
|
+
heartbeatIntervalMs: {
|
|
7871
|
+
type: "number",
|
|
7872
|
+
minimum: 1e3,
|
|
7873
|
+
description: "Suggested heartbeat interval for the worker. Default 60000 (60s). Workers are instructed to call kanban heartbeat_assignment at this cadence."
|
|
7874
|
+
},
|
|
7875
|
+
leaseTtlMs: {
|
|
7876
|
+
type: "number",
|
|
7877
|
+
minimum: 1e3,
|
|
7878
|
+
description: "Lease time-to-live seeded on dispatch. Default 300000 (5m). Workers should refresh via heartbeat_assignment before expiry."
|
|
7879
|
+
},
|
|
7737
7880
|
agentId: { type: "string" },
|
|
7738
7881
|
name: { type: "string" },
|
|
7739
7882
|
role: { type: "string" },
|
|
@@ -7756,6 +7899,14 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7756
7899
|
const projectRoot = ctx.projectRoot;
|
|
7757
7900
|
if (!projectRoot) return { error: "kanban_queue requires ctx.projectRoot." };
|
|
7758
7901
|
const maxTasks = Math.max(1, Math.min(20, Math.floor(i.maxTasks ?? 1)));
|
|
7902
|
+
const leaseTtlMs = Math.max(1e3, Math.floor(i.leaseTtlMs ?? 5 * 60 * 1e3));
|
|
7903
|
+
const claimedAt = nowIso2();
|
|
7904
|
+
const leaseSeeding = {
|
|
7905
|
+
leaseId: randomUUID(),
|
|
7906
|
+
claimedAt,
|
|
7907
|
+
heartbeatAt: claimedAt,
|
|
7908
|
+
leaseExpiresAt: new Date(Date.now() + leaseTtlMs).toISOString()
|
|
7909
|
+
};
|
|
7759
7910
|
const candidateTaskIds = i.taskId !== void 0 ? [i.taskId] : i.query ? (await listReadyTasks(projectRoot, {
|
|
7760
7911
|
...i.boardId !== void 0 ? { boardId: i.boardId } : {}
|
|
7761
7912
|
})).filter((candidate) => matchesKanbanQueueQuery(candidate.task, i.query ?? "")).slice(0, maxTasks).map((candidate) => candidate.task.id) : void 0;
|
|
@@ -7776,6 +7927,7 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7776
7927
|
...i.fallbackModels !== void 0 ? { fallbackModels: i.fallbackModels } : {},
|
|
7777
7928
|
...i.tools !== void 0 ? { tools: i.tools } : {},
|
|
7778
7929
|
...i.allowedCapabilities !== void 0 ? { allowedCapabilities: i.allowedCapabilities } : {},
|
|
7930
|
+
...leaseSeeding !== void 0 ? leaseSeeding : {},
|
|
7779
7931
|
status: "queued"
|
|
7780
7932
|
});
|
|
7781
7933
|
if (!claim) {
|
|
@@ -7784,13 +7936,34 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7784
7936
|
}
|
|
7785
7937
|
let subagentId;
|
|
7786
7938
|
let runTaskId;
|
|
7939
|
+
const costCeiling = claim.task.assignment?.costCeilingUsd;
|
|
7940
|
+
if (costCeiling !== void 0) {
|
|
7941
|
+
const remaining = director.getRemainingBudgetUsd();
|
|
7942
|
+
if (remaining !== void 0 && remaining < costCeiling) {
|
|
7943
|
+
await updateTaskAssignment(projectRoot, claim.board.id, claim.task.id, {
|
|
7944
|
+
status: "failed",
|
|
7945
|
+
error: `Cost ceiling ${costCeiling} exceeds remaining budget ${remaining.toFixed(4)}`,
|
|
7946
|
+
lastResult: "Skipped by kanban_queue cost gate (Sprint 3)"
|
|
7947
|
+
});
|
|
7948
|
+
errors.push({
|
|
7949
|
+
taskId: claim.task.id,
|
|
7950
|
+
error: `Cost ceiling ${costCeiling} exceeds remaining budget ${remaining.toFixed(4)}`
|
|
7951
|
+
});
|
|
7952
|
+
continue;
|
|
7953
|
+
}
|
|
7954
|
+
}
|
|
7787
7955
|
try {
|
|
7788
7956
|
const config = buildKanbanSubagentConfig(claim.task, i, roster);
|
|
7789
7957
|
subagentId = await director.spawn(config);
|
|
7790
7958
|
runTaskId = await director.assign({
|
|
7791
7959
|
id: randomUUID(),
|
|
7792
7960
|
subagentId,
|
|
7793
|
-
description: buildKanbanFleetTaskPrompt(claim.board, claim.task
|
|
7961
|
+
description: buildKanbanFleetTaskPrompt(claim.board, claim.task, {
|
|
7962
|
+
heartbeatIntervalMs: i.heartbeatIntervalMs ?? 6e4,
|
|
7963
|
+
leaseTtlMs,
|
|
7964
|
+
leaseId: leaseSeeding.leaseId,
|
|
7965
|
+
leaseExpiresAt: leaseSeeding.leaseExpiresAt
|
|
7966
|
+
}),
|
|
7794
7967
|
...i.maxToolCalls !== void 0 ? { maxToolCalls: i.maxToolCalls } : {},
|
|
7795
7968
|
...i.timeoutMs !== void 0 ? { timeoutMs: i.timeoutMs } : {},
|
|
7796
7969
|
context: {
|
|
@@ -7888,7 +8061,9 @@ function normalizeKanbanQueueInput(input) {
|
|
|
7888
8061
|
fallbackModels: stringArray(raw.fallbackModels),
|
|
7889
8062
|
tools: stringArray(raw.tools),
|
|
7890
8063
|
allowedCapabilities: stringArray(raw.allowedCapabilities),
|
|
7891
|
-
worktree: normalizeWorktreeOverride(raw.worktree)
|
|
8064
|
+
worktree: normalizeWorktreeOverride(raw.worktree),
|
|
8065
|
+
heartbeatIntervalMs: typeof raw.heartbeatIntervalMs === "number" ? raw.heartbeatIntervalMs : void 0,
|
|
8066
|
+
leaseTtlMs: typeof raw.leaseTtlMs === "number" ? raw.leaseTtlMs : void 0
|
|
7892
8067
|
};
|
|
7893
8068
|
}
|
|
7894
8069
|
function buildKanbanSubagentConfig(task, input, roster) {
|
|
@@ -7906,7 +8081,8 @@ function buildKanbanSubagentConfig(task, input, roster) {
|
|
|
7906
8081
|
...input.fallbackModels ?? assignment?.fallbackModels ? { fallbackModels: input.fallbackModels ?? assignment?.fallbackModels } : {},
|
|
7907
8082
|
...tools ? { tools: ensureKanbanTool(tools) } : {},
|
|
7908
8083
|
...input.allowedCapabilities ?? assignment?.allowedCapabilities ? { allowedCapabilities: input.allowedCapabilities ?? assignment?.allowedCapabilities } : {},
|
|
7909
|
-
...input.worktree !== void 0 ? { worktree: input.worktree } : {}
|
|
8084
|
+
...input.worktree !== void 0 ? { worktree: input.worktree } : {},
|
|
8085
|
+
...assignment?.costCeilingUsd !== void 0 ? { maxCostUsd: assignment.costCeilingUsd } : {}
|
|
7910
8086
|
};
|
|
7911
8087
|
}
|
|
7912
8088
|
function ensureKanbanTool(tools) {
|
|
@@ -7926,7 +8102,7 @@ function matchesKanbanQueueQuery(task, query) {
|
|
|
7926
8102
|
...task.labels ?? []
|
|
7927
8103
|
].filter(Boolean).some((value) => String(value).toLowerCase().includes(normalized));
|
|
7928
8104
|
}
|
|
7929
|
-
function buildKanbanFleetTaskPrompt(board, task) {
|
|
8105
|
+
function buildKanbanFleetTaskPrompt(board, task, lease) {
|
|
7930
8106
|
const dependencyLines = (task.dependsOn ?? []).map((depId) => board.tasks.find((candidate) => candidate.id === depId)).filter((dep) => Boolean(dep)).map((dep) => `- ${dep.title} [${dep.status}] (${dep.id})`);
|
|
7931
8107
|
const checks = task.successCriteria?.map((check) => `- ${check.description}`).join("\n");
|
|
7932
8108
|
const metrics = task.goalMetrics?.map(
|
|
@@ -7973,9 +8149,24 @@ ${checks}` : "",
|
|
|
7973
8149
|
metrics ? `Goal metrics:
|
|
7974
8150
|
${metrics}` : "",
|
|
7975
8151
|
task.labels?.length ? `Labels: ${task.labels.join(", ")}` : "",
|
|
8152
|
+
"Lease contract (Sprint 1):",
|
|
8153
|
+
`- leaseId: ${lease.leaseId}`,
|
|
8154
|
+
`- claimedAt: ${task.assignment?.claimedAt ?? "<unknown>"}`,
|
|
8155
|
+
`- leaseExpiresAt: ${lease.leaseExpiresAt}`,
|
|
8156
|
+
`- expected heartbeatIntervalMs: ${lease.heartbeatIntervalMs}`,
|
|
8157
|
+
`- expected leaseTtlMs: ${lease.leaseTtlMs}`,
|
|
8158
|
+
"",
|
|
8159
|
+
"Retry policy:",
|
|
8160
|
+
`- retryPolicy: ${task.assignment?.retryPolicy ?? task.retryPolicy ?? "<unset>"}`,
|
|
8161
|
+
`- maxAttempts: ${task.assignment?.maxAttempts ?? "<unset>"}`,
|
|
8162
|
+
`- costCeilingUsd: ${task.assignment?.costCeilingUsd ?? task.costCeilingUsd ?? "<unset>"}`,
|
|
7976
8163
|
"",
|
|
7977
8164
|
"Work this task end-to-end. If scope is too broad or too small, use the kanban tool to split_task or merge_tasks instead of losing traceability.",
|
|
7978
|
-
`When you start
|
|
8165
|
+
`When you start, call kanban with action "mark_assignment", boardId "${board.id}", taskId "${task.id}", and assignmentStatus "running". Include subagentId and runTaskId when you have them.`,
|
|
8166
|
+
`To stay alive in the queue, call kanban with action "heartbeat_assignment", boardId "${board.id}", taskId "${task.id}", and heartbeatAt set to the current time. Cadence must be <= heartbeatIntervalMs and well before leaseExpiresAt.`,
|
|
8167
|
+
`When you finish, call kanban with action "mark_assignment", boardId "${board.id}", taskId "${task.id}", and assignmentStatus "completed" or "failed". Include lastResult or error.`,
|
|
8168
|
+
`If you cannot finish in time, call kanban with action "heartbeat_assignment" to extend the lease, or with action "release_task" to release so another worker can claim. Do NOT silently abandon the assignment.`,
|
|
8169
|
+
'On failure the host may call kanban with action "recover_stale" (mode: retry/release/fail); respect its decisions and do not duplicate work in parallel.',
|
|
7979
8170
|
"When finished, report what changed, what you verified, and any remaining blockers."
|
|
7980
8171
|
].filter(Boolean).join("\n");
|
|
7981
8172
|
}
|
|
@@ -10018,6 +10209,15 @@ var Director = class _Director {
|
|
|
10018
10209
|
getLeaderContextPressure() {
|
|
10019
10210
|
return this.leaderContextPressure;
|
|
10020
10211
|
}
|
|
10212
|
+
/**
|
|
10213
|
+
* Remaining USD budget for the entire fleet (when a cap is configured).
|
|
10214
|
+
* Returns `undefined` when no cap was set (Infinity).
|
|
10215
|
+
*/
|
|
10216
|
+
getRemainingBudgetUsd() {
|
|
10217
|
+
if (this.maxFleetCostUsd === Number.POSITIVE_INFINITY) return void 0;
|
|
10218
|
+
const totalCost = this.usage.snapshot().total?.cost ?? 0;
|
|
10219
|
+
return Math.max(0, this.maxFleetCostUsd - totalCost);
|
|
10220
|
+
}
|
|
10021
10221
|
resolveMaxContext() {
|
|
10022
10222
|
const resolved = typeof this.maxContext === "function" ? this.maxContext() : this.maxContext;
|
|
10023
10223
|
return resolved && resolved > 0 ? resolved : 128e3;
|
|
@@ -19889,107 +20089,170 @@ var DEFAULT_MODES = [
|
|
|
19889
20089
|
{
|
|
19890
20090
|
id: "default",
|
|
19891
20091
|
name: "Default",
|
|
19892
|
-
description: "
|
|
20092
|
+
description: "Balanced general-purpose mode; use when no special token/coverage trade-off is needed",
|
|
19893
20093
|
prompt: "",
|
|
19894
|
-
tags: ["general"]
|
|
20094
|
+
tags: ["general", "balanced"]
|
|
20095
|
+
},
|
|
20096
|
+
{
|
|
20097
|
+
id: "brief",
|
|
20098
|
+
name: "Brief",
|
|
20099
|
+
description: "Ultra-compact responses for low-context, high-speed work",
|
|
20100
|
+
prompt: modePrompt("brief"),
|
|
20101
|
+
tags: ["lite", "fast", "concise", "token-saving"],
|
|
20102
|
+
toolPreferences: ["read", "edit", "bash"],
|
|
20103
|
+
suggestedSkills: []
|
|
20104
|
+
},
|
|
20105
|
+
{
|
|
20106
|
+
id: "review-lite",
|
|
20107
|
+
name: "Review Lite",
|
|
20108
|
+
description: "Token-saving code review: changed files only, top correctness/security risks",
|
|
20109
|
+
prompt: modePrompt("review-lite"),
|
|
20110
|
+
tags: ["lite", "review", "quality", "token-saving"],
|
|
20111
|
+
toolPreferences: ["git", "diff", "read", "grep"],
|
|
20112
|
+
suggestedSkills: ["bug-hunter", "typescript-strict"]
|
|
20113
|
+
},
|
|
20114
|
+
{
|
|
20115
|
+
id: "audit-lite",
|
|
20116
|
+
name: "Audit Lite",
|
|
20117
|
+
description: "Token-saving security triage for a small diff or named file",
|
|
20118
|
+
prompt: modePrompt("audit-lite"),
|
|
20119
|
+
tags: ["lite", "security", "audit", "token-saving"],
|
|
20120
|
+
toolPreferences: ["grep", "read", "git"],
|
|
20121
|
+
suggestedSkills: ["security-scanner"]
|
|
20122
|
+
},
|
|
20123
|
+
{
|
|
20124
|
+
id: "plan-lite",
|
|
20125
|
+
name: "Plan Lite",
|
|
20126
|
+
description: "Token-saving planning: 3-6 actionable steps, minimal design debate",
|
|
20127
|
+
prompt: modePrompt("plan-lite"),
|
|
20128
|
+
tags: ["lite", "planning", "architecture", "token-saving"],
|
|
20129
|
+
toolPreferences: ["tree", "glob", "read", "grep"],
|
|
20130
|
+
suggestedSkills: ["refactor-planner"]
|
|
20131
|
+
},
|
|
20132
|
+
{
|
|
20133
|
+
id: "debug-lite",
|
|
20134
|
+
name: "Debug Lite",
|
|
20135
|
+
description: "Token-saving bug triage: one hypothesis, nearest evidence, narrow check",
|
|
20136
|
+
prompt: modePrompt("debug-lite"),
|
|
20137
|
+
tags: ["lite", "debug", "triage", "token-saving"],
|
|
20138
|
+
toolPreferences: ["read", "grep", "test", "logs"],
|
|
20139
|
+
suggestedSkills: ["bug-hunter"]
|
|
20140
|
+
},
|
|
20141
|
+
{
|
|
20142
|
+
id: "test-lite",
|
|
20143
|
+
name: "Test Lite",
|
|
20144
|
+
description: "Token-saving tests: one focused regression or narrow verification target",
|
|
20145
|
+
prompt: modePrompt("test-lite"),
|
|
20146
|
+
tags: ["lite", "testing", "qa", "token-saving"],
|
|
20147
|
+
toolPreferences: ["test", "read", "grep"],
|
|
20148
|
+
suggestedSkills: ["testing"]
|
|
20149
|
+
},
|
|
20150
|
+
{
|
|
20151
|
+
id: "refactor-lite",
|
|
20152
|
+
name: "Refactor Lite",
|
|
20153
|
+
description: "Token-saving cleanup: small scoped behavior-preserving changes",
|
|
20154
|
+
prompt: modePrompt("refactor-lite"),
|
|
20155
|
+
tags: ["lite", "refactor", "token-saving"],
|
|
20156
|
+
toolPreferences: ["read", "edit", "test"],
|
|
20157
|
+
suggestedSkills: ["typescript-strict"]
|
|
20158
|
+
},
|
|
20159
|
+
{
|
|
20160
|
+
id: "research-lite",
|
|
20161
|
+
name: "Research Lite",
|
|
20162
|
+
description: "Token-saving web research: one search, one authoritative fetch, short answer",
|
|
20163
|
+
prompt: modePrompt("research-lite"),
|
|
20164
|
+
tags: ["lite", "research", "web", "token-saving"],
|
|
20165
|
+
toolPreferences: ["search", "fetch"],
|
|
20166
|
+
suggestedSkills: ["research-web"]
|
|
19895
20167
|
},
|
|
19896
20168
|
{
|
|
19897
20169
|
id: "code-reviewer",
|
|
19898
|
-
name: "
|
|
19899
|
-
description: "
|
|
20170
|
+
name: "Review Deep",
|
|
20171
|
+
description: "Comprehensive code review across contracts, edge cases, lifecycle, errors, concurrency",
|
|
19900
20172
|
prompt: modePrompt("code-reviewer"),
|
|
19901
|
-
tags: ["review", "quality", "security"],
|
|
20173
|
+
tags: ["deep", "review", "quality", "security"],
|
|
19902
20174
|
toolPreferences: ["read", "grep", "git", "diff", "test"],
|
|
19903
20175
|
suggestedSkills: ["bug-hunter", "security-scanner", "typescript-strict", "testing"]
|
|
19904
20176
|
},
|
|
19905
20177
|
{
|
|
19906
20178
|
id: "code-auditor",
|
|
19907
|
-
name: "
|
|
19908
|
-
description: "
|
|
20179
|
+
name: "Audit Deep",
|
|
20180
|
+
description: "Comprehensive security audit with category coverage and exploitability notes",
|
|
19909
20181
|
prompt: modePrompt("code-auditor"),
|
|
19910
|
-
tags: ["security", "audit", "compliance"],
|
|
20182
|
+
tags: ["deep", "security", "audit", "compliance"],
|
|
19911
20183
|
toolPreferences: ["grep", "read", "audit", "bash"],
|
|
19912
20184
|
suggestedSkills: ["security-scanner", "bug-hunter", "audit-log"]
|
|
19913
20185
|
},
|
|
19914
20186
|
{
|
|
19915
20187
|
id: "architect",
|
|
19916
|
-
name: "
|
|
19917
|
-
description: "
|
|
20188
|
+
name: "Architecture Deep",
|
|
20189
|
+
description: "Comprehensive architecture and cross-module contract analysis",
|
|
19918
20190
|
prompt: modePrompt("architect"),
|
|
19919
|
-
tags: ["architecture", "design", "scalability"],
|
|
20191
|
+
tags: ["deep", "architecture", "design", "scalability"],
|
|
19920
20192
|
toolPreferences: ["read", "glob", "tree", "diff"],
|
|
19921
20193
|
suggestedSkills: ["api-design", "refactor-planner", "node-modern", "docker-deploy"]
|
|
19922
20194
|
},
|
|
19923
20195
|
{
|
|
19924
20196
|
id: "debugger",
|
|
19925
|
-
name: "
|
|
19926
|
-
description: "
|
|
20197
|
+
name: "Debug Deep",
|
|
20198
|
+
description: "Comprehensive root-cause analysis with traces, logs, assumptions, and verification",
|
|
19927
20199
|
prompt: modePrompt("debugger"),
|
|
19928
|
-
tags: ["debug", "investigation", "error-resolution"],
|
|
20200
|
+
tags: ["deep", "debug", "investigation", "error-resolution"],
|
|
19929
20201
|
toolPreferences: ["read", "grep", "bash", "logs", "test"],
|
|
19930
20202
|
suggestedSkills: ["bug-hunter", "audit-log", "observability"]
|
|
19931
20203
|
},
|
|
19932
20204
|
{
|
|
19933
20205
|
id: "tester",
|
|
19934
|
-
name: "
|
|
19935
|
-
description: "
|
|
20206
|
+
name: "Test Deep",
|
|
20207
|
+
description: "Comprehensive QA mode for coverage, boundaries, isolation, and integration gaps",
|
|
19936
20208
|
prompt: modePrompt("tester"),
|
|
19937
|
-
tags: ["testing", "qa", "quality"],
|
|
20209
|
+
tags: ["deep", "testing", "qa", "quality"],
|
|
19938
20210
|
toolPreferences: ["read", "grep", "test", "bash"],
|
|
19939
20211
|
suggestedSkills: ["testing", "bug-hunter", "typescript-strict"]
|
|
19940
20212
|
},
|
|
19941
20213
|
{
|
|
19942
20214
|
id: "devops",
|
|
19943
|
-
name: "DevOps
|
|
19944
|
-
description: "
|
|
20215
|
+
name: "DevOps Deep",
|
|
20216
|
+
description: "Comprehensive infrastructure, deployment, observability, and operations review",
|
|
19945
20217
|
prompt: modePrompt("devops"),
|
|
19946
|
-
tags: ["devops", "infrastructure", "operations"],
|
|
20218
|
+
tags: ["deep", "devops", "infrastructure", "operations"],
|
|
19947
20219
|
toolPreferences: ["read", "bash", "grep", "logs", "git"],
|
|
19948
20220
|
suggestedSkills: ["docker-deploy", "observability", "security-scanner"]
|
|
19949
20221
|
},
|
|
19950
20222
|
{
|
|
19951
20223
|
id: "refactorer",
|
|
19952
|
-
name: "
|
|
19953
|
-
description: "
|
|
20224
|
+
name: "Refactor Deep",
|
|
20225
|
+
description: "Comprehensive modernization/refactor mode with contracts and verification discipline",
|
|
19954
20226
|
prompt: modePrompt("refactorer"),
|
|
19955
|
-
tags: ["refactor", "modernization", "improvement"],
|
|
20227
|
+
tags: ["deep", "refactor", "modernization", "improvement"],
|
|
19956
20228
|
toolPreferences: ["read", "edit", "test", "git", "grep"],
|
|
19957
20229
|
suggestedSkills: ["refactor-planner", "typescript-strict", "node-modern", "testing"]
|
|
19958
20230
|
},
|
|
19959
20231
|
{
|
|
19960
20232
|
id: "ui-design",
|
|
19961
|
-
name: "UI Design",
|
|
19962
|
-
description: "
|
|
20233
|
+
name: "UI Design Deep",
|
|
20234
|
+
description: "Comprehensive design-first frontend/mobile UI work with kit, tokens, and accessibility",
|
|
19963
20235
|
prompt: modePrompt("ui-design"),
|
|
19964
|
-
tags: ["ui", "frontend", "mobile", "design"],
|
|
20236
|
+
tags: ["deep", "ui", "frontend", "mobile", "design"],
|
|
19965
20237
|
toolPreferences: ["design", "write", "edit", "read", "scaffold"],
|
|
19966
20238
|
suggestedSkills: ["react-modern"]
|
|
19967
20239
|
},
|
|
19968
|
-
{
|
|
19969
|
-
id: "brief",
|
|
19970
|
-
name: "Brief",
|
|
19971
|
-
description: "Fast, no-nonsense \u2014 get to the point",
|
|
19972
|
-
prompt: modePrompt("brief"),
|
|
19973
|
-
tags: ["fast", "concise", "direct"],
|
|
19974
|
-
toolPreferences: ["read", "edit", "bash"],
|
|
19975
|
-
suggestedSkills: []
|
|
19976
|
-
},
|
|
19977
20240
|
{
|
|
19978
20241
|
id: "teach",
|
|
19979
|
-
name: "Teach",
|
|
19980
|
-
description: "Mentor mode
|
|
20242
|
+
name: "Teach Deep",
|
|
20243
|
+
description: "Mentor mode with explanations, mental models, trade-offs, and takeaways",
|
|
19981
20244
|
prompt: modePrompt("teach"),
|
|
19982
|
-
tags: ["teaching", "mentor", "learning"],
|
|
20245
|
+
tags: ["deep", "teaching", "mentor", "learning"],
|
|
19983
20246
|
toolPreferences: ["read", "edit", "explain"],
|
|
19984
20247
|
suggestedSkills: ["prompt-engineering", "skill-creator", "node-modern", "typescript-strict"]
|
|
19985
20248
|
},
|
|
19986
20249
|
{
|
|
19987
20250
|
id: "research-web",
|
|
19988
|
-
name: "Research
|
|
19989
|
-
description: "
|
|
20251
|
+
name: "Research Deep",
|
|
20252
|
+
description: "Comprehensive current-data research with cross-checking and reusable findings",
|
|
19990
20253
|
prompt: modePrompt("research-web"),
|
|
19991
|
-
tags: ["research", "web", "current-data", "up-to-date"],
|
|
19992
|
-
toolPreferences: ["search
|
|
20254
|
+
tags: ["deep", "research", "web", "current-data", "up-to-date"],
|
|
20255
|
+
toolPreferences: ["search", "fetch", "context_manager"],
|
|
19993
20256
|
suggestedSkills: ["research-web", "tech-stack", "node-modern", "security-scanner", "react-modern"]
|
|
19994
20257
|
}
|
|
19995
20258
|
];
|
|
@@ -29930,8 +30193,8 @@ ${body.trim()}`);
|
|
|
29930
30193
|
if (!this.persistBackup || scope === "project-agents") return;
|
|
29931
30194
|
try {
|
|
29932
30195
|
const content = await this.backend.readAll(scope, this.files[scope]);
|
|
29933
|
-
const { writeFile: writeFile9, mkdir:
|
|
29934
|
-
await
|
|
30196
|
+
const { writeFile: writeFile9, mkdir: mkdir11 } = await import('fs/promises');
|
|
30197
|
+
await mkdir11(this.backupDir, { recursive: true });
|
|
29935
30198
|
await writeFile9(`${this.backupDir}/${scope}.md`, content, "utf8");
|
|
29936
30199
|
} catch {
|
|
29937
30200
|
}
|