@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/install.js
CHANGED
|
@@ -145,12 +145,12 @@ function resolveWin32Command(cmd) {
|
|
|
145
145
|
}
|
|
146
146
|
return cmd;
|
|
147
147
|
}
|
|
148
|
-
var WIN32_SHELL_META = /[&|<>"
|
|
148
|
+
var WIN32_SHELL_META = /[&|<>"%\r\n\0]/;
|
|
149
149
|
function assertSafeWin32ShellArgs(args) {
|
|
150
150
|
for (const arg of args) {
|
|
151
151
|
if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
|
|
152
152
|
throw new Error(
|
|
153
|
-
'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > "
|
|
153
|
+
'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)
|
|
154
154
|
);
|
|
155
155
|
}
|
|
156
156
|
}
|
|
@@ -381,7 +381,7 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
381
381
|
// redaction function. Synced with core copy.
|
|
382
382
|
/(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
|
|
383
383
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
384
|
-
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
384
|
+
/(?: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,
|
|
385
385
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
386
386
|
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
387
387
|
// every such flag in the command line is redacted, not just the first.
|
package/dist/json.js
CHANGED
|
@@ -3,130 +3,11 @@ import * as fs from "node:fs/promises";
|
|
|
3
3
|
import { deepMerge as deepMergeCore } from "@wrongstack/core/utils";
|
|
4
4
|
|
|
5
5
|
// src/_regex.ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// Adjacent quantifiers: a++ a*+
|
|
12
|
-
/[+*]{2,}/,
|
|
13
|
-
// Quantifier on alternation with length 2+
|
|
14
|
-
/\([^|)]+\|[^)]+\)[+*][+*]/,
|
|
15
|
-
// Greedy quantifier inside lookahead/lookbehind — (?!.*a+)
|
|
16
|
-
/[([][^)\]]*[+*][^)\]]*[)\]][^)]*\?\??/
|
|
17
|
-
];
|
|
18
|
-
function hasAmbiguousQuantifiedAlternation(pattern) {
|
|
19
|
-
for (let i = 0; i < pattern.length; i++) {
|
|
20
|
-
if (pattern[i] !== "(") continue;
|
|
21
|
-
if (i > 0 && pattern[i - 1] === "\\") continue;
|
|
22
|
-
let depth = 0;
|
|
23
|
-
let inClass = false;
|
|
24
|
-
let j = i;
|
|
25
|
-
for (; j < pattern.length; j++) {
|
|
26
|
-
const ch = pattern[j];
|
|
27
|
-
if (ch === "\\") {
|
|
28
|
-
j++;
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
if (inClass) {
|
|
32
|
-
if (ch === "]") inClass = false;
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
if (ch === "[") {
|
|
36
|
-
inClass = true;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (ch === "(") depth++;
|
|
40
|
-
else if (ch === ")") {
|
|
41
|
-
depth--;
|
|
42
|
-
if (depth === 0) break;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (j >= pattern.length) return false;
|
|
46
|
-
const next = pattern[j + 1];
|
|
47
|
-
if (next !== "+" && next !== "*" && next !== "{") continue;
|
|
48
|
-
let inner = pattern.slice(i + 1, j);
|
|
49
|
-
inner = inner.replace(/^\?(?::|<?[=!])/u, "");
|
|
50
|
-
const branches = [];
|
|
51
|
-
let current = "";
|
|
52
|
-
let d = 0;
|
|
53
|
-
let cls = false;
|
|
54
|
-
for (let k = 0; k < inner.length; k++) {
|
|
55
|
-
const ch = inner[k];
|
|
56
|
-
if (ch === "\\") {
|
|
57
|
-
current += ch + (inner[k + 1] ?? "");
|
|
58
|
-
k++;
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
if (cls) {
|
|
62
|
-
if (ch === "]") cls = false;
|
|
63
|
-
current += ch;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (ch === "[") {
|
|
67
|
-
cls = true;
|
|
68
|
-
current += ch;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (ch === "(") d++;
|
|
72
|
-
if (ch === ")") d--;
|
|
73
|
-
if (ch === "|" && d === 0) {
|
|
74
|
-
branches.push(current);
|
|
75
|
-
current = "";
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
current += ch;
|
|
79
|
-
}
|
|
80
|
-
branches.push(current);
|
|
81
|
-
if (branches.length < 2) continue;
|
|
82
|
-
for (let a = 0; a < branches.length; a++) {
|
|
83
|
-
for (let b = a + 1; b < branches.length; b++) {
|
|
84
|
-
const x = branches[a];
|
|
85
|
-
const y = branches[b];
|
|
86
|
-
if (x === "" || y === "") return true;
|
|
87
|
-
if (x === y || x.startsWith(y) || y.startsWith(x)) return true;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
function compileUserRegex(pattern, flags) {
|
|
94
|
-
if (typeof pattern !== "string") {
|
|
95
|
-
return { ok: false, reason: "pattern must be a string" };
|
|
96
|
-
}
|
|
97
|
-
if (pattern.length === 0) {
|
|
98
|
-
return { ok: false, reason: "pattern is empty" };
|
|
99
|
-
}
|
|
100
|
-
if (pattern.length > MAX_PATTERN_LEN) {
|
|
101
|
-
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
102
|
-
}
|
|
103
|
-
for (const rx of DANGEROUS_PATTERNS) {
|
|
104
|
-
if (rx.test(pattern)) {
|
|
105
|
-
return {
|
|
106
|
-
ok: false,
|
|
107
|
-
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
if (hasAmbiguousQuantifiedAlternation(pattern)) {
|
|
112
|
-
return {
|
|
113
|
-
ok: false,
|
|
114
|
-
reason: "pattern quantifies an alternation with overlapping branches \u2014 rewrite so no two branches can match the same text"
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
try {
|
|
118
|
-
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
119
|
-
} catch (err) {
|
|
120
|
-
return {
|
|
121
|
-
ok: false,
|
|
122
|
-
reason: err instanceof Error ? err.message : "invalid regex"
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
var MAX_SUBJECT_LEN = 64 * 1024;
|
|
127
|
-
function capSubject(line) {
|
|
128
|
-
return line.length > MAX_SUBJECT_LEN ? line.slice(0, MAX_SUBJECT_LEN) : line;
|
|
129
|
-
}
|
|
6
|
+
import {
|
|
7
|
+
capSubject,
|
|
8
|
+
compileUserRegex,
|
|
9
|
+
MAX_SUBJECT_LEN
|
|
10
|
+
} from "@wrongstack/primitives";
|
|
130
11
|
|
|
131
12
|
// src/_util.ts
|
|
132
13
|
import * as fsp from "node:fs/promises";
|
package/dist/kanban.js
CHANGED
|
@@ -201,10 +201,112 @@ function taskFileToSerializedGraph(tasks, sessionId) {
|
|
|
201
201
|
};
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
// src/session-kanban-sync.ts
|
|
205
|
+
import { getSharedProjectMailbox } from "@wrongstack/core/coordination";
|
|
206
|
+
import { mutatePlan, mutateTasks } from "@wrongstack/core/storage";
|
|
207
|
+
import { formatTodosForModel, resolveWstackPaths } from "@wrongstack/core/utils";
|
|
208
|
+
import {
|
|
209
|
+
getDependencyReadinessIssues
|
|
210
|
+
} from "@wrongstack/kanban";
|
|
211
|
+
function sourceStatus(task) {
|
|
212
|
+
if (task.status === "completed") return "completed";
|
|
213
|
+
if (task.status === "in_progress") return "in_progress";
|
|
214
|
+
if (task.status === "review") return "review";
|
|
215
|
+
if (task.status === "blocked") return "blocked";
|
|
216
|
+
if (task.status === "failed") return "failed";
|
|
217
|
+
return "pending";
|
|
218
|
+
}
|
|
219
|
+
function todoStatus(task) {
|
|
220
|
+
const status = sourceStatus(task);
|
|
221
|
+
if (status === "completed") return "completed";
|
|
222
|
+
if (status === "review" && task.assignment?.status === "completed") return "completed";
|
|
223
|
+
if (status === "in_progress" || status === "review") return "in_progress";
|
|
224
|
+
return "pending";
|
|
225
|
+
}
|
|
226
|
+
async function applySessionKanbanTaskToSource(context, task, suppressedTodoMirrors2, options = {}) {
|
|
227
|
+
const originId = task.origin?.taskId;
|
|
228
|
+
const graphId = task.origin?.graphId ?? "";
|
|
229
|
+
const isTodoOrigin = task.origin?.system === "session-todo" || graphId.startsWith("todo:");
|
|
230
|
+
const targetTodo = context.todos?.find(
|
|
231
|
+
(todo) => originId !== void 0 && todo.id === originId || todo.kanbanTaskId === task.id || todo.id === task.id && !todo.promotedFromPlan && !todo.promotedFromTask
|
|
232
|
+
);
|
|
233
|
+
if (isTodoOrigin || targetTodo) {
|
|
234
|
+
const matchedId = targetTodo?.id ?? originId;
|
|
235
|
+
if (matchedId) {
|
|
236
|
+
const mappedStatus = todoStatus(task);
|
|
237
|
+
const next = options.remove ? context.todos.filter((todo) => todo.id !== matchedId) : context.todos.map(
|
|
238
|
+
(todo) => todo.id === matchedId ? {
|
|
239
|
+
...todo,
|
|
240
|
+
content: task.title,
|
|
241
|
+
status: mappedStatus,
|
|
242
|
+
kanbanTaskId: task.id
|
|
243
|
+
} : todo
|
|
244
|
+
);
|
|
245
|
+
suppressedTodoMirrors2.add(context);
|
|
246
|
+
try {
|
|
247
|
+
context.state.replaceTodos(next);
|
|
248
|
+
} finally {
|
|
249
|
+
suppressedTodoMirrors2.delete(context);
|
|
250
|
+
}
|
|
251
|
+
return { source: "todo", todos: [...context.todos] };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (!originId) return { source: null };
|
|
255
|
+
const id = context.session?.id ?? "";
|
|
256
|
+
if (task.origin?.system === "session-plan" || graphId.startsWith("plan:")) {
|
|
257
|
+
const planPath = context.meta["plan.path"];
|
|
258
|
+
if (typeof planPath !== "string" || !planPath) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
"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."
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
const plan = await mutatePlan(planPath, id, (file) => ({
|
|
264
|
+
...file,
|
|
265
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
266
|
+
items: options.remove ? file.items.filter((item) => item.id !== originId) : file.items.map(
|
|
267
|
+
(item) => item.id === originId ? {
|
|
268
|
+
...item,
|
|
269
|
+
title: task.title,
|
|
270
|
+
details: task.description,
|
|
271
|
+
status: task.status === "completed" ? "done" : task.status === "in_progress" || task.status === "review" ? "in_progress" : "open",
|
|
272
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
273
|
+
} : item
|
|
274
|
+
)
|
|
275
|
+
}));
|
|
276
|
+
return { source: "plan", plan };
|
|
277
|
+
}
|
|
278
|
+
if (task.origin?.system === "session-task" || task.origin?.system === "session" || graphId.startsWith("session:")) {
|
|
279
|
+
const taskPath = context.meta["task.path"];
|
|
280
|
+
if (typeof taskPath !== "string" || !taskPath) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
"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."
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
const tasks = await mutateTasks(taskPath, id, (file) => ({
|
|
286
|
+
...file,
|
|
287
|
+
tasks: options.remove ? file.tasks.filter((item) => item.id !== originId) : file.tasks.map(
|
|
288
|
+
(item) => item.id === originId ? {
|
|
289
|
+
...item,
|
|
290
|
+
title: task.title,
|
|
291
|
+
description: task.description,
|
|
292
|
+
status: sourceStatus(task),
|
|
293
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
294
|
+
} : item
|
|
295
|
+
)
|
|
296
|
+
}));
|
|
297
|
+
return { source: "task", tasks };
|
|
298
|
+
}
|
|
299
|
+
return { source: null };
|
|
300
|
+
}
|
|
301
|
+
|
|
204
302
|
// src/session-kanban.ts
|
|
205
303
|
var SESSION_KANBAN_COLUMNS = DEFAULT_COLUMNS.map((column) => ({
|
|
206
304
|
...column
|
|
207
305
|
}));
|
|
306
|
+
var suppressedTodoMirrors = /* @__PURE__ */ new WeakSet();
|
|
307
|
+
function applySessionKanbanTaskToSource2(context, task, options = {}) {
|
|
308
|
+
return applySessionKanbanTaskToSource(context, task, suppressedTodoMirrors, options);
|
|
309
|
+
}
|
|
208
310
|
|
|
209
311
|
// src/kanban-board-actions.ts
|
|
210
312
|
async function handleKanbanBoardAction(projectRoot, input, ctx) {
|
|
@@ -1027,12 +1129,20 @@ function assignmentForTaskCreate(input) {
|
|
|
1027
1129
|
}
|
|
1028
1130
|
|
|
1029
1131
|
// src/kanban-lifecycle-actions.ts
|
|
1132
|
+
async function syncContextTask(ctx, task, options = {}) {
|
|
1133
|
+
if (!ctx?.state || !task) return;
|
|
1134
|
+
try {
|
|
1135
|
+
await applySessionKanbanTaskToSource2(ctx, task, options);
|
|
1136
|
+
} catch {
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1030
1139
|
async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
1031
1140
|
switch (input.action) {
|
|
1032
1141
|
case "add_task": {
|
|
1033
1142
|
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
1034
1143
|
const result = await addTask2(projectRoot, input.boardId, taskInput(input));
|
|
1035
1144
|
if (!result) return fail("Board not found.");
|
|
1145
|
+
await syncContextTask(ctx, result.task);
|
|
1036
1146
|
return okTask(result.board, result.task, `Task added.${atomicityNudge(result.task)}`);
|
|
1037
1147
|
}
|
|
1038
1148
|
case "split_task": {
|
|
@@ -1177,6 +1287,7 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1177
1287
|
);
|
|
1178
1288
|
}
|
|
1179
1289
|
ctx.setCurrentKanbanTask(task.id, board.id);
|
|
1290
|
+
await syncContextTask(ctx, task);
|
|
1180
1291
|
return okTask(
|
|
1181
1292
|
board,
|
|
1182
1293
|
task,
|
|
@@ -1186,6 +1297,9 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1186
1297
|
case "update_task": {
|
|
1187
1298
|
if (!input.boardId || !input.taskId) return fail("update_task requires boardId and taskId.");
|
|
1188
1299
|
const board = await updateTask2(projectRoot, input.boardId, input.taskId, taskPatch(input));
|
|
1300
|
+
if (board) {
|
|
1301
|
+
await syncContextTask(ctx, board.tasks.find((t) => t.id === input.taskId));
|
|
1302
|
+
}
|
|
1189
1303
|
return board ? okBoard(board, "Task updated.") : fail("Task not found.");
|
|
1190
1304
|
}
|
|
1191
1305
|
case "transition_task": {
|
|
@@ -1225,6 +1339,9 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1225
1339
|
if (result && input.lifecycleStage === "done" && result.task.verificationReport) {
|
|
1226
1340
|
recordKanbanVerificationEvidence(ctx, result.task.verificationReport);
|
|
1227
1341
|
}
|
|
1342
|
+
if (result?.task) {
|
|
1343
|
+
await syncContextTask(ctx, result.task);
|
|
1344
|
+
}
|
|
1228
1345
|
return result ? okTask(result.board, result.task, `Task advanced to ${result.transition.to}.`) : fail("Board or task not found.");
|
|
1229
1346
|
}
|
|
1230
1347
|
case "repair_managed_projection": {
|
|
@@ -1237,6 +1354,9 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1237
1354
|
actor: input.author,
|
|
1238
1355
|
comment: input.transitionComment
|
|
1239
1356
|
});
|
|
1357
|
+
if (result?.task) {
|
|
1358
|
+
await syncContextTask(ctx, result.task);
|
|
1359
|
+
}
|
|
1240
1360
|
return result ? okTask(
|
|
1241
1361
|
result.board,
|
|
1242
1362
|
result.task,
|
|
@@ -1254,14 +1374,22 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1254
1374
|
input.targetColumnId,
|
|
1255
1375
|
input.order
|
|
1256
1376
|
);
|
|
1377
|
+
if (board) {
|
|
1378
|
+
await syncContextTask(ctx, board.tasks.find((t) => t.id === input.taskId));
|
|
1379
|
+
}
|
|
1257
1380
|
return board ? okBoard(board, "Task moved.") : fail("Move failed.");
|
|
1258
1381
|
}
|
|
1259
1382
|
case "delete_task": {
|
|
1260
1383
|
if (!input.boardId || !input.taskId) return fail("delete_task requires boardId and taskId.");
|
|
1384
|
+
const boardBefore = await getBoard4(projectRoot, input.boardId);
|
|
1385
|
+
const taskToDelete = boardBefore?.tasks.find((t) => t.id === input.taskId);
|
|
1261
1386
|
const board = await removeTask(projectRoot, input.boardId, input.taskId);
|
|
1262
1387
|
if (board && ctx.currentKanbanTaskId === input.taskId) {
|
|
1263
1388
|
ctx.setCurrentKanbanTask?.(void 0, ctx.currentKanbanBoardId);
|
|
1264
1389
|
}
|
|
1390
|
+
if (board && taskToDelete) {
|
|
1391
|
+
await syncContextTask(ctx, taskToDelete, { remove: true });
|
|
1392
|
+
}
|
|
1265
1393
|
return board ? okBoard(board, "Task deleted.") : fail("Task not found.");
|
|
1266
1394
|
}
|
|
1267
1395
|
case "set_chain": {
|
|
@@ -1368,6 +1496,7 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1368
1496
|
issues: finalized.gate.issues.map((issue) => issue.message)
|
|
1369
1497
|
};
|
|
1370
1498
|
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(" | ")}`;
|
|
1499
|
+
await syncContextTask(ctx, finalized.task);
|
|
1371
1500
|
return {
|
|
1372
1501
|
...okTask(finalized.board, finalized.task, `Assignment updated. ${gateMessage}`),
|
|
1373
1502
|
gate: gateSummary
|
|
@@ -1473,6 +1602,7 @@ async function handleKanbanLifecycleAction(projectRoot, input, ctx) {
|
|
|
1473
1602
|
msgParts.push(`Card advanced to ${transitionResult.transition.to}.`);
|
|
1474
1603
|
}
|
|
1475
1604
|
for (const w of lifecycleWarnings) msgParts.push(`Warning: ${w}`);
|
|
1605
|
+
await syncContextTask(ctx, responseTask);
|
|
1476
1606
|
return okTask(responseBoard, responseTask, msgParts.join(" "));
|
|
1477
1607
|
}
|
|
1478
1608
|
return okBoard(board, "Assignment updated.");
|
package/dist/languages/index.js
CHANGED
|
@@ -2266,12 +2266,12 @@ function resolveWin32Command(cmd) {
|
|
|
2266
2266
|
}
|
|
2267
2267
|
return cmd;
|
|
2268
2268
|
}
|
|
2269
|
-
var WIN32_SHELL_META = /[&|<>"
|
|
2269
|
+
var WIN32_SHELL_META = /[&|<>"%\r\n\0]/;
|
|
2270
2270
|
function assertSafeWin32ShellArgs(args) {
|
|
2271
2271
|
for (const arg of args) {
|
|
2272
2272
|
if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
|
|
2273
2273
|
throw new Error(
|
|
2274
|
-
'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > "
|
|
2274
|
+
'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)
|
|
2275
2275
|
);
|
|
2276
2276
|
}
|
|
2277
2277
|
}
|
|
@@ -2502,7 +2502,7 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
2502
2502
|
// redaction function. Synced with core copy.
|
|
2503
2503
|
/(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
|
|
2504
2504
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
2505
|
-
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
2505
|
+
/(?: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,
|
|
2506
2506
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
2507
2507
|
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
2508
2508
|
// every such flag in the command line is redacted, not just the first.
|
package/dist/lint.js
CHANGED
|
@@ -138,12 +138,12 @@ function resolveWin32Command(cmd) {
|
|
|
138
138
|
}
|
|
139
139
|
return cmd;
|
|
140
140
|
}
|
|
141
|
-
var WIN32_SHELL_META = /[&|<>"
|
|
141
|
+
var WIN32_SHELL_META = /[&|<>"%\r\n\0]/;
|
|
142
142
|
function assertSafeWin32ShellArgs(args) {
|
|
143
143
|
for (const arg of args) {
|
|
144
144
|
if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
|
|
145
145
|
throw new Error(
|
|
146
|
-
'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > "
|
|
146
|
+
'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)
|
|
147
147
|
);
|
|
148
148
|
}
|
|
149
149
|
}
|
|
@@ -374,7 +374,7 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
374
374
|
// redaction function. Synced with core copy.
|
|
375
375
|
/(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
|
|
376
376
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
377
|
-
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
377
|
+
/(?: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,
|
|
378
378
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
379
379
|
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
380
380
|
// every such flag in the command line is redacted, not just the first.
|
package/dist/logs.js
CHANGED
|
@@ -3,127 +3,11 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import { buildChildEnv } from "@wrongstack/core/utils";
|
|
4
4
|
|
|
5
5
|
// src/_regex.ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// Adjacent quantifiers: a++ a*+
|
|
12
|
-
/[+*]{2,}/,
|
|
13
|
-
// Quantifier on alternation with length 2+
|
|
14
|
-
/\([^|)]+\|[^)]+\)[+*][+*]/,
|
|
15
|
-
// Greedy quantifier inside lookahead/lookbehind — (?!.*a+)
|
|
16
|
-
/[([][^)\]]*[+*][^)\]]*[)\]][^)]*\?\??/
|
|
17
|
-
];
|
|
18
|
-
function hasAmbiguousQuantifiedAlternation(pattern) {
|
|
19
|
-
for (let i = 0; i < pattern.length; i++) {
|
|
20
|
-
if (pattern[i] !== "(") continue;
|
|
21
|
-
if (i > 0 && pattern[i - 1] === "\\") continue;
|
|
22
|
-
let depth = 0;
|
|
23
|
-
let inClass = false;
|
|
24
|
-
let j = i;
|
|
25
|
-
for (; j < pattern.length; j++) {
|
|
26
|
-
const ch = pattern[j];
|
|
27
|
-
if (ch === "\\") {
|
|
28
|
-
j++;
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
if (inClass) {
|
|
32
|
-
if (ch === "]") inClass = false;
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
if (ch === "[") {
|
|
36
|
-
inClass = true;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (ch === "(") depth++;
|
|
40
|
-
else if (ch === ")") {
|
|
41
|
-
depth--;
|
|
42
|
-
if (depth === 0) break;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (j >= pattern.length) return false;
|
|
46
|
-
const next = pattern[j + 1];
|
|
47
|
-
if (next !== "+" && next !== "*" && next !== "{") continue;
|
|
48
|
-
let inner = pattern.slice(i + 1, j);
|
|
49
|
-
inner = inner.replace(/^\?(?::|<?[=!])/u, "");
|
|
50
|
-
const branches = [];
|
|
51
|
-
let current = "";
|
|
52
|
-
let d = 0;
|
|
53
|
-
let cls = false;
|
|
54
|
-
for (let k = 0; k < inner.length; k++) {
|
|
55
|
-
const ch = inner[k];
|
|
56
|
-
if (ch === "\\") {
|
|
57
|
-
current += ch + (inner[k + 1] ?? "");
|
|
58
|
-
k++;
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
if (cls) {
|
|
62
|
-
if (ch === "]") cls = false;
|
|
63
|
-
current += ch;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (ch === "[") {
|
|
67
|
-
cls = true;
|
|
68
|
-
current += ch;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (ch === "(") d++;
|
|
72
|
-
if (ch === ")") d--;
|
|
73
|
-
if (ch === "|" && d === 0) {
|
|
74
|
-
branches.push(current);
|
|
75
|
-
current = "";
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
current += ch;
|
|
79
|
-
}
|
|
80
|
-
branches.push(current);
|
|
81
|
-
if (branches.length < 2) continue;
|
|
82
|
-
for (let a = 0; a < branches.length; a++) {
|
|
83
|
-
for (let b = a + 1; b < branches.length; b++) {
|
|
84
|
-
const x = branches[a];
|
|
85
|
-
const y = branches[b];
|
|
86
|
-
if (x === "" || y === "") return true;
|
|
87
|
-
if (x === y || x.startsWith(y) || y.startsWith(x)) return true;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
function compileUserRegex(pattern, flags) {
|
|
94
|
-
if (typeof pattern !== "string") {
|
|
95
|
-
return { ok: false, reason: "pattern must be a string" };
|
|
96
|
-
}
|
|
97
|
-
if (pattern.length === 0) {
|
|
98
|
-
return { ok: false, reason: "pattern is empty" };
|
|
99
|
-
}
|
|
100
|
-
if (pattern.length > MAX_PATTERN_LEN) {
|
|
101
|
-
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
102
|
-
}
|
|
103
|
-
for (const rx of DANGEROUS_PATTERNS) {
|
|
104
|
-
if (rx.test(pattern)) {
|
|
105
|
-
return {
|
|
106
|
-
ok: false,
|
|
107
|
-
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
if (hasAmbiguousQuantifiedAlternation(pattern)) {
|
|
112
|
-
return {
|
|
113
|
-
ok: false,
|
|
114
|
-
reason: "pattern quantifies an alternation with overlapping branches \u2014 rewrite so no two branches can match the same text"
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
try {
|
|
118
|
-
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
119
|
-
} catch (err) {
|
|
120
|
-
return {
|
|
121
|
-
ok: false,
|
|
122
|
-
reason: err instanceof Error ? err.message : "invalid regex"
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
var MAX_SUBJECT_LEN = 64 * 1024;
|
|
6
|
+
import {
|
|
7
|
+
capSubject,
|
|
8
|
+
compileUserRegex,
|
|
9
|
+
MAX_SUBJECT_LEN
|
|
10
|
+
} from "@wrongstack/primitives";
|
|
127
11
|
|
|
128
12
|
// src/_util.ts
|
|
129
13
|
import * as fsp from "node:fs/promises";
|
package/dist/outdated.js
CHANGED
|
@@ -219,7 +219,7 @@ function assertSafeWin32ShellArgs(args) {
|
|
|
219
219
|
for (const arg of args) {
|
|
220
220
|
if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
|
|
221
221
|
throw new Error(
|
|
222
|
-
'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > "
|
|
222
|
+
'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)
|
|
223
223
|
);
|
|
224
224
|
}
|
|
225
225
|
}
|
|
@@ -240,7 +240,7 @@ var WIN32_SHELL_META;
|
|
|
240
240
|
var init_win32_resolve = __esm({
|
|
241
241
|
"src/_win32-resolve.ts"() {
|
|
242
242
|
"use strict";
|
|
243
|
-
WIN32_SHELL_META = /[&|<>"
|
|
243
|
+
WIN32_SHELL_META = /[&|<>"%\r\n\0]/;
|
|
244
244
|
}
|
|
245
245
|
});
|
|
246
246
|
|
|
@@ -2759,7 +2759,7 @@ var init_redact_command = __esm({
|
|
|
2759
2759
|
// redaction function. Synced with core copy.
|
|
2760
2760
|
/(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
|
|
2761
2761
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
2762
|
-
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
2762
|
+
/(?: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,
|
|
2763
2763
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
2764
2764
|
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
2765
2765
|
// every such flag in the command line is redacted, not just the first.
|