@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.
Files changed (67) hide show
  1. package/dist/_regex.d.ts +6 -34
  2. package/dist/audit.js +3 -3
  3. package/dist/bash.js +4 -4
  4. package/dist/builtin.js +3020 -1680
  5. package/dist/codebase-index/binary-frame.d.ts +57 -8
  6. package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +6 -0
  7. package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +6 -0
  8. package/dist/codebase-index/codebase-search-tool.d.ts +15 -5
  9. package/dist/codebase-index/index-service.d.ts +3 -19
  10. package/dist/codebase-index/index.js +2784 -1464
  11. package/dist/codebase-index/indexer.d.ts +3 -0
  12. package/dist/codebase-index/parser-batch.d.ts +53 -0
  13. package/dist/codebase-index/parser-dispatch.d.ts +32 -0
  14. package/dist/codebase-index/parser-output.d.ts +14 -0
  15. package/dist/codebase-index/parser-worker-pool.d.ts +57 -4
  16. package/dist/codebase-index/parser-worker-script.d.ts +5 -2
  17. package/dist/codebase-index/parser-worker-script.js +4042 -0
  18. package/dist/codebase-index/project-server-cache.d.ts +16 -0
  19. package/dist/codebase-index/project-server-client.d.ts +2 -2
  20. package/dist/codebase-index/project-server-query-cache.d.ts +88 -0
  21. package/dist/codebase-index/project-server.js +2846 -1308
  22. package/dist/codebase-index/py-parser.d.ts +5 -0
  23. package/dist/codebase-index/schema.d.ts +14 -1
  24. package/dist/codebase-index/sqlite-runtime.d.ts +2 -2
  25. package/dist/codebase-index/tree-sitter/queries.d.ts +30 -3
  26. package/dist/codebase-index/tree-sitter/visitor.d.ts +2 -1
  27. package/dist/codebase-index/vector-search.d.ts +12 -0
  28. package/dist/codebase-index/wal-maintenance.d.ts +58 -0
  29. package/dist/codebase-index/worker-protocol/contracts.d.ts +44 -0
  30. package/dist/codebase-index/worker-protocol.d.ts +17 -1
  31. package/dist/codebase-index/worker.js +2300 -1020
  32. package/dist/codebase-index/writer-admin.d.ts +11 -0
  33. package/dist/codebase-index/writer-helpers.d.ts +31 -1
  34. package/dist/codebase-index/writer-mutations.d.ts +0 -6
  35. package/dist/codebase-index/writer-schema.d.ts +2 -2
  36. package/dist/codebase-index/writer.d.ts +15 -0
  37. package/dist/edit.js +2511 -1203
  38. package/dist/exec.js +8 -6
  39. package/dist/format.js +3 -3
  40. package/dist/git.js +6 -0
  41. package/dist/grep.js +5 -124
  42. package/dist/index.js +3016 -1739
  43. package/dist/install.js +3 -3
  44. package/dist/json.js +5 -124
  45. package/dist/kanban.js +130 -0
  46. package/dist/languages/index.js +3 -3
  47. package/dist/lint.js +3 -3
  48. package/dist/logs.js +5 -121
  49. package/dist/outdated.js +3 -3
  50. package/dist/pack.js +3020 -1680
  51. package/dist/patch.js +2524 -1216
  52. package/dist/plan.js +106 -0
  53. package/dist/process-registry.js +1 -1
  54. package/dist/read.js +2506 -1198
  55. package/dist/replace.js +2487 -1295
  56. package/dist/search.js +6 -2
  57. package/dist/session-kanban.js +24 -16
  58. package/dist/task.js +106 -0
  59. package/dist/test.js +3 -3
  60. package/dist/todo.js +106 -0
  61. package/dist/tool-tier.d.ts +11 -0
  62. package/dist/tool-tier.js +3028 -1680
  63. package/dist/tree.js +14 -3
  64. package/dist/typecheck.js +3 -3
  65. package/dist/win32.js +5 -5
  66. package/dist/write.js +2513 -1205
  67. 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 = /[&|<>"\r\n\0]/;
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 & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
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
- var MAX_PATTERN_LEN = 256;
7
- var DANGEROUS_PATTERNS = [
8
- // (a+)+, (.*)+, etc — nested quantifier on a group with internal quantifier
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.");
@@ -2266,12 +2266,12 @@ function resolveWin32Command(cmd) {
2266
2266
  }
2267
2267
  return cmd;
2268
2268
  }
2269
- var WIN32_SHELL_META = /[&|<>"\r\n\0]/;
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 & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
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 = /[&|<>"\r\n\0]/;
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 & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
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
- var MAX_PATTERN_LEN = 256;
7
- var DANGEROUS_PATTERNS = [
8
- // (a+)+, (.*)+, etc — nested quantifier on a group with internal quantifier
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 & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
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 = /[&|<>"\r\n\0]/;
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.