@wrongstack/tools 0.309.1 → 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 (57) hide show
  1. package/dist/_regex.d.ts +6 -34
  2. package/dist/bash.js +3 -3
  3. package/dist/builtin.js +3011 -1677
  4. package/dist/codebase-index/binary-frame.d.ts +57 -8
  5. package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +6 -0
  6. package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +6 -0
  7. package/dist/codebase-index/codebase-search-tool.d.ts +15 -5
  8. package/dist/codebase-index/index-service.d.ts +3 -19
  9. package/dist/codebase-index/index.js +2735 -1415
  10. package/dist/codebase-index/indexer.d.ts +3 -0
  11. package/dist/codebase-index/parser-batch.d.ts +53 -0
  12. package/dist/codebase-index/parser-dispatch.d.ts +32 -0
  13. package/dist/codebase-index/parser-output.d.ts +14 -0
  14. package/dist/codebase-index/parser-worker-pool.d.ts +57 -4
  15. package/dist/codebase-index/parser-worker-script.d.ts +5 -2
  16. package/dist/codebase-index/parser-worker-script.js +4042 -0
  17. package/dist/codebase-index/project-server-cache.d.ts +16 -0
  18. package/dist/codebase-index/project-server-client.d.ts +2 -2
  19. package/dist/codebase-index/project-server-query-cache.d.ts +88 -0
  20. package/dist/codebase-index/project-server.js +2846 -1308
  21. package/dist/codebase-index/py-parser.d.ts +5 -0
  22. package/dist/codebase-index/schema.d.ts +14 -1
  23. package/dist/codebase-index/sqlite-runtime.d.ts +2 -2
  24. package/dist/codebase-index/tree-sitter/queries.d.ts +30 -3
  25. package/dist/codebase-index/tree-sitter/visitor.d.ts +2 -1
  26. package/dist/codebase-index/vector-search.d.ts +12 -0
  27. package/dist/codebase-index/wal-maintenance.d.ts +58 -0
  28. package/dist/codebase-index/worker-protocol/contracts.d.ts +44 -0
  29. package/dist/codebase-index/worker-protocol.d.ts +17 -1
  30. package/dist/codebase-index/worker.js +2300 -1020
  31. package/dist/codebase-index/writer-admin.d.ts +11 -0
  32. package/dist/codebase-index/writer-helpers.d.ts +31 -1
  33. package/dist/codebase-index/writer-mutations.d.ts +0 -6
  34. package/dist/codebase-index/writer-schema.d.ts +2 -2
  35. package/dist/codebase-index/writer.d.ts +15 -0
  36. package/dist/edit.js +2511 -1203
  37. package/dist/exec.js +5 -3
  38. package/dist/grep.js +5 -124
  39. package/dist/index.js +3007 -1736
  40. package/dist/json.js +5 -124
  41. package/dist/kanban.js +130 -0
  42. package/dist/logs.js +5 -121
  43. package/dist/pack.js +3011 -1677
  44. package/dist/patch.js +2524 -1216
  45. package/dist/plan.js +106 -0
  46. package/dist/read.js +2506 -1198
  47. package/dist/replace.js +2487 -1295
  48. package/dist/search.js +6 -2
  49. package/dist/session-kanban.js +24 -16
  50. package/dist/task.js +106 -0
  51. package/dist/todo.js +106 -0
  52. package/dist/tool-tier.d.ts +11 -0
  53. package/dist/tool-tier.js +3019 -1677
  54. package/dist/tree.js +14 -3
  55. package/dist/win32.js +3 -3
  56. package/dist/write.js +2513 -1205
  57. package/package.json +5 -4
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.");
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";