@inetafrica/open-claudia 3.5.67 → 3.5.69

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.5.69
4
+
5
+ 3.5.68 plus the card fixes from Sumeet's 17 Oct review of the test cards (Sumeet, 20 Oct 07:43 EAT: "Why don't you just push them out now?"): a bare date did not say what it was, and the board's side-by-side columns are cramped on a phone.
6
+
7
+ - **Due dates say "Due"** (`bin/cards.js`): `card example due-dates` sets dateLabel "Due" on its rows question, so Kazee Chat draws "Due Tue 20 Oct" (chat-central f20b993, phone 4.6.3, web e25a670).
8
+ - **Status list instead of the board**: new `card example status`, a rows question with To do / Doing / Done chips, each row starting where it is. `card pieces` marks board "old cards only" and points to it; the card coach no longer suggests a board. Old board cards still draw and wake as before.
9
+
10
+ Tests: `test-card-blocks.js` (the due-dates label, the status example, board marked old cards only).
11
+
12
+ ## 3.5.68
13
+
14
+ 3.5.67 plus a guard on test runs (Sumeet, 19 Oct 11:47 EAT, card "Mac: stop parallel threads from running it out of memory": "Both: checker rule and safety net"). The Mac froze twice that morning (power button held at 10:51 and 11:09 EAT): parallel conversations' test runs wanted about 26 GB on the 16 GB Mac, 12 of those GB from one helper's `npx jest` with no worker cap. The same morning the Chat Spaces bot pod was OOM-killed twice by `jest --maxWorkers=4` under its 3 Gi limit. The rule in lessons.md never reached helpers; the tool hook does.
15
+
16
+ - **Test-run guard** (`core/tool-guard.js`, every Bash call of every conversation and helper). A jest or vitest run is refused, with the exact fix, when it would start more than 2 workers (`--maxWorkers=2`, `--runInBand`, or a project config that sets `maxWorkers` all pass); when another test run is already going on the machine (only one at a time, plus a 30 s claim so two cannot start in the same second); or when free memory is under 40% (macOS `memory_pressure`; on pods the cgroup limit, else MemAvailable). It follows `cd`, package scripts (`npm test` -> `jest --ci`, `npm test -- --maxWorkers=2`), `npx`/`npm exec`/`yarn`/`pnpm exec`, `node .../jest.js` and `bash -c`; greps, reads and quoted text that mention jest stay free. Runs ahead of the relaxed tooling mode (it protects the machine). Refusals log as `kind: "test-run"`, outside the tool-first KPI. `OC_TEST_RUN_MAX_WORKERS`, `OC_TEST_RUN_MIN_FREE_PCT` change the limits; `OC_TEST_RUN_GUARD=off` turns it off. Any error in the guard allows.
17
+ - Proven with a real `claude -p` run: an Agent-tool helper's `npx jest` was refused by the hook before jest started. Not covered: a raw `claude -p` started by hand without the bot's hook settings, and test runners inside script files; on the Mac the memory guard (mac-health `memory-guard`, a launchd agent that stops the newest big test/build run below 20% free) is the net under this.
18
+
19
+ Tests: `test-test-run-guard.js` (worker cap by flag, script and config; one at a time; claim; memory floor; cgroup and meminfo; env limits; fail-open; through the hook).
20
+
3
21
  ## 3.5.67
4
22
 
5
23
  3.5.66 plus card building blocks (APP-31; Sumeet, 16 Oct 08:28 EAT: "ok build and push, ensure you also update open claudia so it knows how to use these, we also need to nudge it to try to discover them as it works"). Pairs with chat-central 5d28c9c (v2 cards: table block; tick, date, slots, rows, board questions), Kazee Chat web and phone 4.6.0.
package/bin/cards.js CHANGED
@@ -67,7 +67,7 @@ const EXAMPLES = {
67
67
  body: "Tick the ones that are fine. Tap a date to change it.",
68
68
  input: {
69
69
  fields: [
70
- { id: "tasks", type: "rows", label: "Tasks", tick: true, checked: true,
70
+ { id: "tasks", type: "rows", label: "Tasks", tick: true, checked: true, dateLabel: "Due",
71
71
  options: [{ id: "close", label: "Close it" }],
72
72
  rows: [
73
73
  { id: "clickhouse", label: "ClickHouse public access", detail: "Remove it, or office/VPN only", date: "2026-10-20" },
@@ -83,6 +83,29 @@ const EXAMPLES = {
83
83
  },
84
84
  policy: { responders: ["<kazeeUserId>"], mode: "first", results: "hidden" },
85
85
  },
86
+ // Where things stand: a list with a status chip per row (Sumeet, 17 Oct:
87
+ // the board's side-by-side columns are cramped on a phone). Use this, not
88
+ // a board, to ask where items are.
89
+ status: {
90
+ v: 1, kind: "form", kicker: "Spaces · this week", tag: "3 tasks",
91
+ title: "Where do these stand?",
92
+ body: "Tap where each one really is, then press Send once.",
93
+ input: {
94
+ fields: [
95
+ { id: "week", type: "rows", label: "This week", tick: false, dates: false,
96
+ options: [{ id: "todo", label: "To do" }, { id: "doing", label: "Doing" }, { id: "done", label: "Done" }],
97
+ rows: [
98
+ { id: "dog-06", label: "Clean iNet's CRM data", detail: "DOG-06 · due 23 Oct", option: "todo" },
99
+ { id: "dog-14", label: "Stop old Zoho history going out as new", detail: "DOG-14 · due 20 Oct", option: "todo" },
100
+ { id: "dog-04", label: "Fix the nightly Zoho import", detail: "DOG-04", option: "done" },
101
+ ] },
102
+ ],
103
+ submitLabel: "Send",
104
+ },
105
+ policy: { responders: ["<kazeeUserId>"], mode: "first", results: "hidden" },
106
+ },
107
+ // Old cards only: phones and the web still draw a board, but a new card
108
+ // asks where things stand with `status` above.
86
109
  board: {
87
110
  v: 1, kind: "form", kicker: "Spaces · this week", tag: "3 tasks",
88
111
  title: "Where do these stand?",
@@ -161,8 +184,8 @@ const PIECES = [
161
184
  { name: "tick", where: "question", use: "one yes/no tick box, a confirmation", example: "checks" },
162
185
  { name: "date", where: "question", use: "one date picker, optional min and max", example: "checks" },
163
186
  { name: "slots", where: "question", use: "pick meeting times from a day x time grid; busy slots can't be picked", example: "meeting" },
164
- { name: "rows", where: "question", use: "a list where each row gets a tick, a date and/or option chips (approve, re-date, close items)", example: "due-dates" },
165
- { name: "board", where: "question", use: "move items between columns (to do, doing, done)", example: "board" },
187
+ { name: "rows", where: "question", use: "a list where each row gets a tick, a date (dateLabel names it: \"Due\") and/or option chips (approve, re-date, close items; To do / Doing / Done)", example: "due-dates, status" },
188
+ { name: "board", where: "question", use: "old cards only: to ask where things stand use rows with To do / Doing / Done options", example: "status" },
166
189
  ];
167
190
 
168
191
  function piecesText() {
@@ -204,13 +227,13 @@ const CARD_USAGE = `Usage:
204
227
  open-claudia card send <spec.json | '{json}' | -> [--wake each|first|none] [--content "plain text"]
205
228
  open-claudia card update <messageId> [--close | --open] [--note "text"] [--spec <file|json>]
206
229
  open-claudia card show <messageId>
207
- open-claudia card example <decision|choice|rating|slider|form|report|due-dates|board|meeting|table|checks>
230
+ open-claudia card example <decision|choice|rating|slider|form|report|due-dates|status|meeting|table|checks>
208
231
  open-claudia card pieces
209
232
  Cards are Kazee Chat only. Answers wake this conversation when --wake is each
210
233
  (default for a card a few named people answer) or first; polls anyone answers
211
234
  default to none. Spec: chat-central CARDS-V1 (v:1, kind, title, input, policy...).
212
235
  Pieces (card pieces lists them): blocks text, stats, bars, facts, table; form
213
- questions choice, rating, text, tick, date, slots, rows, board.`;
236
+ questions choice, rating, text, tick, date, slots, rows (board: old cards only).`;
214
237
 
215
238
  async function runCard(args) {
216
239
  const sub = args.shift();
@@ -47,7 +47,7 @@ function detect(text) {
47
47
  }
48
48
 
49
49
  function hintLine(items) {
50
- return `Card coach: your last message listed ${items} items for the reader to decide in plain text. One card built from pieces (rows with ticks, dates or options; board; time slots; table) lets them answer with one Send. See open-claudia card pieces.`;
50
+ return `Card coach: your last message listed ${items} items for the reader to decide in plain text. One card built from pieces (rows with ticks, dates or options such as To do / Doing / Done; time slots; table) lets them answer with one Send. See open-claudia card pieces.`;
51
51
  }
52
52
 
53
53
  function conversationKey({ adapterId, channelId, threadId }) {
@@ -994,6 +994,375 @@ function browserDriverDenial(command, now = Date.now()) {
994
994
  }
995
995
  }
996
996
 
997
+ // ── test runs: worker cap, one at a time, enough free memory ──────────────
998
+ // 19 Oct 2026: Sumeet's 16 GB Mac froze twice (power button held at 10:51 and
999
+ // 11:09 EAT) when parallel conversations' test runs wanted ~26 GB: a helper's
1000
+ // `npx jest` with no worker cap started 12 workers of ~1 GB each next to
1001
+ // another thread's jest. The same morning the Chat Spaces bot pod was
1002
+ // OOM-killed twice by `jest --maxWorkers=4` under its 3 Gi limit. A rule in
1003
+ // lessons.md did not reach helpers; this hook does, because every Bash call
1004
+ // of every conversation and helper passes here. So a jest/vitest run must cap
1005
+ // its workers (2 by default), only one runs at a time on the machine, and
1006
+ // none starts while free memory is already low. On the Mac, mac-health
1007
+ // memory-guard is the net under this for anything that slips past (a script
1008
+ // file, a runner this does not know). Deliberately ahead of the relaxed
1009
+ // tooling-mode switch: it protects the machine, it is not a workflow
1010
+ // preference. Any error here allows: the net still holds.
1011
+ const TEST_RUNNERS = new Set(["jest", "vitest"]);
1012
+ const PACKAGE_MANAGERS = new Set(["npm", "yarn", "pnpm", "bun"]);
1013
+ const TEST_RUN_CLAIM_MS = 30000;
1014
+ const NODE_VALUE_FLAGS = new Set(["-r", "--require", "--import", "--loader", "--experimental-loader", "--env-file", "--conditions", "-C"]);
1015
+ const NOT_A_RUN_FLAGS = new Set(["--listTests", "--showConfig", "--version", "-v", "--help", "-h", "--clearCache"]);
1016
+ // A process that IS a test run (not a shell or grep that mentions one).
1017
+ const TEST_PROCESS_RE = /(?:^|[\s/])(?:jest|jest-cli|vitest)(?:\.c?m?js)?(?=\s|$)|jest-worker\/|vitest\/dist\/|tinypool\//;
1018
+ const TEST_PROCESS_BINS = new Set(["node", "nodejs", "jest", "vitest", "npm", "npx", "yarn", "pnpm", "pnpx", "bun", "bunx"]);
1019
+
1020
+ function positiveInt(value, fallback) {
1021
+ const n = Number(value);
1022
+ return Number.isInteger(n) && n > 0 ? n : fallback;
1023
+ }
1024
+
1025
+ function testRunLimits() {
1026
+ return {
1027
+ maxWorkers: positiveInt(process.env.OC_TEST_RUN_MAX_WORKERS, 2),
1028
+ minFreePct: positiveInt(process.env.OC_TEST_RUN_MIN_FREE_PCT, 40),
1029
+ claimFile: process.env.OC_TEST_RUN_CLAIM || path.join(CONFIG_DIR, "test-run.claim.json"),
1030
+ };
1031
+ }
1032
+
1033
+ function readText(file, limit = 256 * 1024) {
1034
+ try {
1035
+ const st = fs.statSync(file);
1036
+ if (!st.isFile() || st.size > limit) return null;
1037
+ return fs.readFileSync(file, "utf8");
1038
+ } catch (e) { return null; }
1039
+ }
1040
+
1041
+ function readPackageJson(dir) {
1042
+ const text = readText(path.join(dir, "package.json"));
1043
+ if (text === null) return null;
1044
+ try { return JSON.parse(text); } catch (e) { return null; }
1045
+ }
1046
+
1047
+ // jest/vitest by name or path: jest, npx jest, node_modules/.bin/jest,
1048
+ // node [flags] node_modules/jest/bin/jest.js, and the package managers' own
1049
+ // ways of running a binary (npm exec, yarn, pnpm exec/dlx, bun x).
1050
+ function directTestRunner(words, at, pkg) {
1051
+ const name = baseName(words[at]).replace(/\.c?m?js$/, "");
1052
+ if (TEST_RUNNERS.has(name)) return { runner: name, args: words.slice(at + 1) };
1053
+ if (name === "node" || name === "nodejs") {
1054
+ let j = at + 1;
1055
+ while (j < words.length && words[j].startsWith("-")) j += NODE_VALUE_FLAGS.has(words[j]) ? 2 : 1;
1056
+ const script = words[j] ? baseName(words[j]).replace(/\.c?m?js$/, "") : "";
1057
+ if (TEST_RUNNERS.has(script)) return { runner: script, args: words.slice(j + 1) };
1058
+ return null;
1059
+ }
1060
+ const sub = words[at + 1];
1061
+ const bin = (k) => {
1062
+ let j = k;
1063
+ if (words[j] === "--") j += 1;
1064
+ const runner = words[j] ? baseName(words[j]) : "";
1065
+ return TEST_RUNNERS.has(runner) ? { runner, args: words.slice(j + 1) } : null;
1066
+ };
1067
+ if (name === "npm" && (sub === "exec" || sub === "x")) return bin(at + 2);
1068
+ if (name === "pnpm" && (sub === "exec" || sub === "dlx")) return bin(at + 2);
1069
+ if (name === "bun" && sub === "x") return bin(at + 2);
1070
+ if (name === "yarn" && sub === "exec") return bin(at + 2);
1071
+ const scripts = (pkg && pkg.scripts) || {};
1072
+ if ((name === "yarn" || name === "pnpm") && TEST_RUNNERS.has(sub) && !scripts[sub]) return bin(at + 1);
1073
+ return null;
1074
+ }
1075
+
1076
+ // A package script that runs tests: npm test / npm run <s> -- args,
1077
+ // yarn [run] <s> args, pnpm [run] <s> args, bun run <s> args. Returns the
1078
+ // script's text with the extra args appended, or null.
1079
+ function packageScriptRun(words, at, pkg) {
1080
+ const name = baseName(words[at]);
1081
+ if (!PACKAGE_MANAGERS.has(name) || !pkg || !pkg.scripts) return null;
1082
+ let j = at + 1;
1083
+ while (j < words.length && words[j].startsWith("-")) j += 1; // --silent, -s ...
1084
+ const sub = words[j];
1085
+ if (!sub) return null;
1086
+ let script = null;
1087
+ let extra = [];
1088
+ const after = (k) => {
1089
+ const rest = words.slice(k);
1090
+ if (name === "npm") {
1091
+ const dash = rest.indexOf("--");
1092
+ return dash < 0 ? [] : rest.slice(dash + 1);
1093
+ }
1094
+ return rest[0] === "--" ? rest.slice(1) : rest;
1095
+ };
1096
+ if (["test", "t", "tst"].includes(sub) && name !== "bun") { script = "test"; extra = after(j + 1); }
1097
+ else if (["run", "run-script", "rum", "urn"].includes(sub)) { script = words[j + 1]; extra = after(j + 2); }
1098
+ else if ((name === "yarn" || name === "pnpm") && pkg.scripts[sub]) { script = sub; extra = after(j + 1); }
1099
+ if (!script || typeof pkg.scripts[script] !== "string") return null;
1100
+ const quoted = extra.map((arg) => require("./providers/hook-command").shellQuote(arg));
1101
+ // How to pass a flag through to the script, for the deny message.
1102
+ const example = name === "npm"
1103
+ ? (script === "test" ? "npm test --" : `npm run ${script} --`)
1104
+ : name === "bun" ? `bun run ${script}` : `${name} ${script}`;
1105
+ return { script, example, text: [pkg.scripts[script], ...quoted].join(" ") };
1106
+ }
1107
+
1108
+ // Every jest/vitest run a command line would start: [{ runner, args, cwd }].
1109
+ // Follows `cd` within the line, package scripts (npm test → "jest --ci"),
1110
+ // and what a shell runs from a string (bash -c, eval, $(...)).
1111
+ function testRunInvocations(command, cwd, depth = 0) {
1112
+ const runs = [];
1113
+ if (depth > 4 || !/jest|vitest|test|run|\bt\b|exec|yarn|pnpm/.test(String(command || ""))) return runs;
1114
+ let dir = path.resolve(String(cwd || process.cwd()));
1115
+ const { commands, nested } = shellCommands(command);
1116
+ const scripts = [...nested];
1117
+ for (const { words, input } of commands) {
1118
+ const at = commandWordIndex(words, scripts);
1119
+ if (at < 0) continue;
1120
+ const name = baseName(words[at]);
1121
+ if (name === "cd" || name === "pushd") {
1122
+ const target = words.slice(at + 1).find((w) => !w.startsWith("-"));
1123
+ dir = target ? path.resolve(dir, expandHome(target)) : os.homedir();
1124
+ continue;
1125
+ }
1126
+ if (name === "eval") { scripts.push(words.slice(at + 1).join(" ")); continue; }
1127
+ if (SHELLS.has(name)) {
1128
+ const flag = words.findIndex((w, k) => k > at && /^-[A-Za-z]*c[A-Za-z]*$/.test(w));
1129
+ if (flag > 0 && words[flag + 1] !== undefined) runs.push(...testRunInvocations(words[flag + 1], dir, depth + 1));
1130
+ else for (const text of input) runs.push(...testRunInvocations(text, dir, depth + 1));
1131
+ continue;
1132
+ }
1133
+ const pkg = PACKAGE_MANAGERS.has(name) ? readPackageJson(dir) : null;
1134
+ const direct = directTestRunner(words, at, pkg);
1135
+ if (direct) { runs.push({ ...direct, cwd: dir }); continue; }
1136
+ const scriptRun = packageScriptRun(words, at, pkg);
1137
+ if (scriptRun) {
1138
+ for (const run of testRunInvocations(scriptRun.text, dir, depth + 1)) runs.push({ ...run, via: scriptRun.example });
1139
+ }
1140
+ }
1141
+ for (const script of scripts) runs.push(...testRunInvocations(script, dir, depth + 1));
1142
+ return runs;
1143
+ }
1144
+
1145
+ function flagValue(args, names) {
1146
+ for (let k = 0; k < args.length; k += 1) {
1147
+ for (const name of names) {
1148
+ if (args[k] === name && args[k + 1] !== undefined) return args[k + 1];
1149
+ if (args[k].startsWith(`${name}=`)) return args[k].slice(name.length + 1);
1150
+ }
1151
+ }
1152
+ return null;
1153
+ }
1154
+
1155
+ function workersFrom(value, cpus) {
1156
+ const text = String(value).trim().replace(/^['"]|['"]$/g, "");
1157
+ const pct = text.match(/^(\d+(?:\.\d+)?)%$/);
1158
+ if (pct) return Math.max(1, Math.floor((cpus * Number(pct[1])) / 100));
1159
+ const n = Number(text);
1160
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
1161
+ }
1162
+
1163
+ const CONFIG_CAP_RE = {
1164
+ jest: /\bmaxWorkers\b\s*[:=]?\s*(['"]?\d+(?:\.\d+)?%?['"]?)?/,
1165
+ vitest: /\b(maxWorkers|maxThreads|maxForks|singleThread|singleFork|fileParallelism)\b\s*[:=]?\s*(['"]?[\w.%]+['"]?)?/,
1166
+ };
1167
+ const DEFAULT_CONFIGS = {
1168
+ jest: ["jest.config.js", "jest.config.ts", "jest.config.mjs", "jest.config.cjs", "jest.config.json"],
1169
+ vitest: ["vitest.config.ts", "vitest.config.mts", "vitest.config.js", "vitest.config.mjs", "vitest.config.cjs", "vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"],
1170
+ };
1171
+
1172
+ // The project's own config caps the workers: { workers } (null = set by code
1173
+ // we cannot read, trusted) or null when the config sets no cap.
1174
+ function configCap(runner, args, cwd, cpus) {
1175
+ const files = [];
1176
+ const explicit = flagValue(args, ["--config", "-c"]);
1177
+ if (explicit) files.push(path.resolve(cwd, expandHome(explicit)));
1178
+ else files.push(...DEFAULT_CONFIGS[runner].map((f) => path.join(cwd, f)));
1179
+ for (const file of files) {
1180
+ const text = readText(file);
1181
+ if (text === null) continue;
1182
+ const m = text.match(CONFIG_CAP_RE[runner]);
1183
+ if (!m) continue;
1184
+ if (runner === "vitest") {
1185
+ if (/^single/.test(m[1])) return { workers: /false/.test(m[2] || "") ? null : 1 };
1186
+ if (m[1] === "fileParallelism") return /false/.test(m[2] || "") ? { workers: 1 } : null;
1187
+ return { workers: m[2] ? workersFrom(m[2], cpus) : null };
1188
+ }
1189
+ return { workers: m[1] ? workersFrom(m[1], cpus) : null };
1190
+ }
1191
+ if (runner === "jest" && !explicit) {
1192
+ const pkg = readPackageJson(cwd);
1193
+ if (pkg && pkg.jest && typeof pkg.jest === "object" && pkg.jest.maxWorkers !== undefined) {
1194
+ return { workers: workersFrom(pkg.jest.maxWorkers, cpus) };
1195
+ }
1196
+ }
1197
+ return null;
1198
+ }
1199
+
1200
+ // How many workers a run will start: { notARun } | { workers, source }.
1201
+ // workers null = capped by project code we cannot evaluate (trusted).
1202
+ function testRunWorkers(run, cpus) {
1203
+ const { runner, args } = run;
1204
+ if (args.some((a) => NOT_A_RUN_FLAGS.has(a)) || (runner === "vitest" && ["list", "init"].includes(args[0]))) {
1205
+ return { notARun: true };
1206
+ }
1207
+ if (runner === "jest") {
1208
+ if (args.includes("--runInBand") || args.includes("-i") || args.includes("--run-in-band")) return { workers: 1, source: "--runInBand" };
1209
+ const value = flagValue(args, ["--maxWorkers", "--max-workers", "-w"]);
1210
+ if (value !== null) return { workers: workersFrom(value, cpus) ?? cpus, source: "--maxWorkers" };
1211
+ } else {
1212
+ if (args.includes("--no-file-parallelism") || args.includes("--fileParallelism=false")
1213
+ || args.includes("--poolOptions.threads.singleThread") || args.includes("--poolOptions.forks.singleFork")) {
1214
+ return { workers: 1, source: "single worker" };
1215
+ }
1216
+ const value = flagValue(args, ["--maxWorkers", "--poolOptions.threads.maxThreads", "--poolOptions.forks.maxForks", "--poolOptions.vmThreads.maxThreads"]);
1217
+ if (value !== null) return { workers: workersFrom(value, cpus) ?? cpus, source: "--maxWorkers" };
1218
+ }
1219
+ const cap = configCap(runner, args, run.cwd, cpus);
1220
+ if (cap) return { workers: cap.workers, source: "config" };
1221
+ return { workers: Math.max(1, cpus - 1), source: "default" };
1222
+ }
1223
+
1224
+ // Free memory as a percentage: macOS memory_pressure; Linux the cgroup's
1225
+ // limit (a pod) or the host's MemAvailable. null when unknown (no check).
1226
+ function linuxFreeMemoryPct({ root = "/" } = {}) {
1227
+ const read = (rel) => readText(path.join(root, rel));
1228
+ const max2 = read("sys/fs/cgroup/memory.max");
1229
+ if (max2 !== null && /^\d+\s*$/.test(max2)) {
1230
+ const max = Number(max2);
1231
+ const current = Number(read("sys/fs/cgroup/memory.current"));
1232
+ const inactive = Number(((read("sys/fs/cgroup/memory.stat") || "").match(/^inactive_file (\d+)$/m) || [])[1] || 0);
1233
+ if (max > 0 && Number.isFinite(current)) return Math.round(((max - Math.max(0, current - inactive)) / max) * 100);
1234
+ }
1235
+ const max1 = Number(read("sys/fs/cgroup/memory/memory.limit_in_bytes"));
1236
+ if (max1 > 0 && max1 < 2 ** 60) {
1237
+ const usage = Number(read("sys/fs/cgroup/memory/memory.usage_in_bytes"));
1238
+ const inactive = Number(((read("sys/fs/cgroup/memory/memory.stat") || "").match(/^total_inactive_file (\d+)$/m) || [])[1] || 0);
1239
+ if (Number.isFinite(usage)) return Math.round(((max1 - Math.max(0, usage - inactive)) / max1) * 100);
1240
+ }
1241
+ const info = read("proc/meminfo") || "";
1242
+ const total = Number((info.match(/^MemTotal:\s+(\d+)/m) || [])[1]);
1243
+ const avail = Number((info.match(/^MemAvailable:\s+(\d+)/m) || [])[1]);
1244
+ return total > 0 && Number.isFinite(avail) ? Math.round((avail / total) * 100) : null;
1245
+ }
1246
+
1247
+ const systemProbe = {
1248
+ cpus: () => (os.availableParallelism ? os.availableParallelism() : os.cpus().length) || 1,
1249
+ freePct() {
1250
+ if (process.platform === "darwin") {
1251
+ const out = require("child_process").spawnSync("memory_pressure", ["-Q"], { encoding: "utf8", timeout: 3000 });
1252
+ const m = String(out.stdout || "").match(/free percentage:\s*(\d+)%/);
1253
+ return m ? Number(m[1]) : null;
1254
+ }
1255
+ return process.platform === "linux" ? linuxFreeMemoryPct() : null;
1256
+ },
1257
+ processes() {
1258
+ const out = require("child_process").spawnSync("ps", ["-axo", "pid=,etime=,command="], { encoding: "utf8", timeout: 3000 });
1259
+ return String(out.stdout || "").split("\n").map((line) => {
1260
+ const m = line.match(/^\s*(\d+)\s+(\S+)\s+(.*)$/);
1261
+ return m && { pid: Number(m[1]), etime: m[2], command: m[3] };
1262
+ }).filter(Boolean);
1263
+ },
1264
+ cwdOf(pid) {
1265
+ if (process.platform === "linux") { try { return fs.readlinkSync(`/proc/${pid}/cwd`); } catch (e) { return ""; } }
1266
+ const out = require("child_process").spawnSync("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], { encoding: "utf8", timeout: 3000 });
1267
+ const line = String(out.stdout || "").split("\n").find((l) => l.startsWith("n"));
1268
+ return line ? line.slice(1) : "";
1269
+ },
1270
+ now: () => Date.now(),
1271
+ };
1272
+
1273
+ // A process that is a test run: the runner or one of its workers, not a shell,
1274
+ // grep or `node -e` script whose text mentions jest (and never claude, whose
1275
+ // command line carries the whole system prompt).
1276
+ function isTestProcess(command) {
1277
+ const words = String(command).split(/\s+/);
1278
+ const bin = baseName(words[0] || "");
1279
+ if (!TEST_PROCESS_BINS.has(bin)) return false;
1280
+ if (bin === "node" || bin === "nodejs" || bin === "bun") {
1281
+ let j = 1;
1282
+ while (j < words.length && words[j].startsWith("-")) {
1283
+ if (["-e", "--eval", "-p", "--print"].includes(words[j])) return false;
1284
+ j += NODE_VALUE_FLAGS.has(words[j]) ? 2 : 1;
1285
+ }
1286
+ return TEST_PROCESS_RE.test(words[j] || "");
1287
+ }
1288
+ return TEST_PROCESS_RE.test(command);
1289
+ }
1290
+
1291
+ function runningTestProcess(probe) {
1292
+ return probe.processes().find((p) => p.pid !== process.pid && isTestProcess(p.command)) || null;
1293
+ }
1294
+
1295
+ function readClaim(file) {
1296
+ const text = readText(file, 64 * 1024);
1297
+ if (text === null) return null;
1298
+ try { return JSON.parse(text); } catch (e) { return null; }
1299
+ }
1300
+
1301
+ const home = (p) => String(p || "").replace(os.homedir(), "~");
1302
+
1303
+ // null (allow) or { reason, message } for a Bash command. `session` is the
1304
+ // hook payload's session_id: a conversation retrying its own just-started
1305
+ // run is not blocked by its own claim.
1306
+ function testRunDenial(command, { cwd, session = "", probe = systemProbe, limits = testRunLimits() } = {}) {
1307
+ try {
1308
+ if (process.env.OC_TEST_RUN_GUARD === "off") return null;
1309
+ const runs = testRunInvocations(command, cwd);
1310
+ if (!runs.length) return null;
1311
+ const cpus = probe.cpus();
1312
+ let heavy = null;
1313
+ for (const run of runs) {
1314
+ const verdict = testRunWorkers(run, cpus);
1315
+ if (verdict.notARun) continue;
1316
+ heavy = heavy || run;
1317
+ if (verdict.workers !== null && verdict.workers > limits.maxWorkers) {
1318
+ const capped = `--maxWorkers=${limits.maxWorkers}`;
1319
+ const how = run.via ? `${run.via} ${capped}` : `npx ${run.runner} ${capped} <files>`;
1320
+ return {
1321
+ reason: "workers",
1322
+ message:
1323
+ `⛔ Test-run guard: this ${run.runner} run would start ${verdict.workers} workers (${verdict.source === "default" ? "no worker cap: one per CPU core" : `${verdict.source} allows ${verdict.workers}`}), about 1 GB each. ` +
1324
+ `On 19 Oct uncapped test runs from parallel conversations froze the Mac twice.\n` +
1325
+ `Run it with at most ${limits.maxWorkers} workers: add ${capped} (or --runInBand for a few files), e.g. \`${how}\`.`,
1326
+ };
1327
+ }
1328
+ }
1329
+ if (!heavy) return null;
1330
+ const busy = runningTestProcess(probe);
1331
+ if (busy) {
1332
+ const where = home(probe.cwdOf(busy.pid));
1333
+ return {
1334
+ reason: "busy",
1335
+ message:
1336
+ `⛔ Test-run guard: another test run is already going on this machine (pid ${busy.pid}, running ${busy.etime}${where ? `, in ${where}` : ""}): ${String(busy.command).slice(0, 140)}\n` +
1337
+ `Only one test run at a time, so parallel conversations cannot run the machine out of memory. Wait for it to finish, then run yours. If it is your own leftover run, stop it first.`,
1338
+ };
1339
+ }
1340
+ const claim = readClaim(limits.claimFile);
1341
+ if (claim && claim.session !== session && probe.now() - Number(claim.at || 0) < TEST_RUN_CLAIM_MS) {
1342
+ return {
1343
+ reason: "busy",
1344
+ message:
1345
+ `⛔ Test-run guard: another conversation started a test run ${Math.round((probe.now() - claim.at) / 1000)} s ago${claim.cwd ? ` in ${home(claim.cwd)}` : ""}. Only one runs at a time; wait for it to finish, then run yours.`,
1346
+ };
1347
+ }
1348
+ const free = probe.freePct();
1349
+ if (free !== null && free < limits.minFreePct) {
1350
+ return {
1351
+ reason: "memory",
1352
+ message:
1353
+ `⛔ Test-run guard: free memory is ${free}%, under the ${limits.minFreePct}% a test run needs. Wait a few minutes and try again; do not raise the worker count.` +
1354
+ (process.platform === "darwin" ? " See what is using memory: open-claudia tool run mac-health memory-guard-status" : ""),
1355
+ };
1356
+ }
1357
+ try {
1358
+ fs.writeFileSync(limits.claimFile, JSON.stringify({ at: probe.now(), session, cwd: heavy.cwd, runner: heavy.runner }));
1359
+ } catch (e) { /* best effort */ }
1360
+ return null;
1361
+ } catch (e) {
1362
+ return null;
1363
+ }
1364
+ }
1365
+
997
1366
  // Claude sends the shell command as a string; accept an argv array too.
998
1367
  function shellCommandOf(input) {
999
1368
  if (!input || typeof input !== "object") return null;
@@ -1072,6 +1441,17 @@ function runHook(payload) {
1072
1441
  logGuardEvent({ kind: "deny", rule: "browser-driver", subcommand: driving.subcommand });
1073
1442
  return { exit: 2, stderr: driving.message };
1074
1443
  }
1444
+ // Test runs (worker cap, one at a time, free memory): ahead of the
1445
+ // tooling-mode switch too. Logged as its own kind so the tool-first KPI
1446
+ // (denies/bypasses) does not count it.
1447
+ const testRun = testRunDenial(command, {
1448
+ cwd: typeof data.cwd === "string" ? data.cwd : undefined,
1449
+ session: typeof data.session_id === "string" ? data.session_id : "",
1450
+ });
1451
+ if (testRun) {
1452
+ logGuardEvent({ kind: "test-run", decision: "deny", reason: testRun.reason, command: String(command).slice(0, 300) });
1453
+ return { exit: 2, stderr: testRun.message };
1454
+ }
1075
1455
  // Relaxed tooling mode: the gate observes but never blocks. The event is
1076
1456
  // still logged (as relaxed-pass, not deny) so the dream KPI can report
1077
1457
  // what WOULD have been denied — visibility survives the mode switch.
@@ -1107,4 +1487,5 @@ module.exports = {
1107
1487
  protectedReadRoots, isProtectedReadPath, isProtectedSearchRoot, secretReadRule,
1108
1488
  matchRawOp, noteRawOp, readGuardEvents,
1109
1489
  agentBrowserInvocations, browserDriverDenial,
1490
+ testRunInvocations, testRunWorkers, testRunDenial, linuxFreeMemoryPct,
1110
1491
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.5.67",
3
+ "version": "3.5.69",
4
4
  "description": "An always-on, provider-agnostic coding-agent harness for Claude Code and OpenAI Codex via chat",
5
5
  "main": "bot.js",
6
6
  "bin": {
@@ -11,7 +11,7 @@
11
11
  "setup": "node setup.js",
12
12
  "start": "node bot.js",
13
13
  "pretest": "node test-provider-fixture.js && node test-provider-core-prompt.js && node test-turn-observer.js && node test-provider-prompt-parity.js && node test-provider-env-isolation.js && node test-provider-resume-parity.js && node test-provider-bootstrap.js && node test-provider-registry.js && node test-provider-stream-decoder.js && node test-provider-events.js && node test-provider-claude.js && node test-provider-codex.js && node test-codex-output-schema.js && node test-codex-device-login.js && node test-provider-runner.js && node test-provider-refusal-recovery.js && node test-provider-run-lifecycle.js && node test-provider-gateway.js && node test-provider-side-chat.js && node test-single-runtime.js && node test-provider-migration-backup.js && node test-provider-state-migration.js && node test-provider-session-history.js && node test-provider-session-commands.js && node test-provider-compaction.js && node test-provider-scheduler.js && node test-credential-capture.js && node test-cursor-removal.js && node test-utility-provider-policy.js && node test-provider-subagent.js && node test-provider-recall.js && node test-provider-pack-review.js && node test-memory-mutation-queue.js && node test-provider-dream.js && node test-provider-enforcer.js && node test-provider-tool-hooks.js && node test-provider-boot-matrix.js && node test-provider-capabilities.js && node test-provider-language.js && node test-upgrade-path.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-kazee-media-caption.js && node test-kazee-context-media.js && node test-kazee-typing-join.js && node test-kazee-inbound-dedup.js && node test-typing-phase-push.js && node test-socket-reconnect.js && node test-speech-loopback.js && node test-prompt-budget.js && node test-agency-ledger.js && node test-agency-deliberate.js && node test-guardrail.js && node test-group-status-notes.js && node test-spaces-comment-content.js && node test-boot-health.js && node test-lexical-floor.js && node test-safe-mode.js && node test-logout.js && node test-kazee-call-audio.js && node test-kazee-call-janus.js && node test-kazee-call-webrtc.js && node test-kazee-call-speech.js && node test-kazee-call-manager.js && node test-call-speech.js && node test-kazee-call-session.js",
14
- "test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && node test-codex-rollout.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-room-scope.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-mandate-hygiene.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-external-rule-scopes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-owner-cards-dm.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-cards.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-people-owner-seed.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-lanes.js && node test-thread-titles.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-stop.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-stop-queue.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-typing-heartbeat.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-stream-preview-purpose.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-quiet-defaults.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-say-loopback.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-union.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-subst-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-section-budgets.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-dedup-adjudicator.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-prospective.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-regressions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cost-report.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cost-by-part.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-mining.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-certification.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-apprenticeship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-attest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-attest.js && node test-sandbox-probe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-bot-endpoint.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cli-owner-gates.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-verify-contact.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-owner-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-watch-check-env.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-intro-adoption.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-grants.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-room-provider-settings.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-store-durability.js && node test-kazee-cards.js && node test-card-blocks.js && node test-decision-upkeep.js && node test-passive-replies.js && node test-kazee-bot-loops.js && node test-kazee-bot-handoffs.js && node test-placeholder-reply.js && node test-media-speech.js && node test-speech-voices.js && node test-tts-voice.js && node test-passive-replies-router.js && node test-loopback-cards.js && node test-task-injection.js && node test-speaker-extras.js && node test-token-diet.js && node test-dream-tasks.js && node test-task-archive.js && node test-cron-tolerance.js && node test-dream-schedule.js && node test-job-failures.js && node test-scheduled-placeholder.js && node test-lessons-since.js && node test-pack-reinject.js && node test-turn-journal.js && node test-kazee-catchup.js && node test-usage-limits.js && node test-limit-waits.js && node test-signin-race-retry.js && node test-limit-pickup.js && node test-browser-view.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-browser-view.js && node test-browser-address.js && node test-browser-control.js && node test-browser-drive.js && node test-browser-teach.js && node test-kazee-bot-apps.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-browser-app.js && node test-safe-filename.js && node test-media-filename-paths.js && node test-router-document-guard.js && OPEN_CLAUDIA_TEST=1 node test-web-dashboard-escape.js && OPEN_CLAUDIA_TEST=1 node test-web-bad-requests.js && OPEN_CLAUDIA_TEST=1 node test-kazee-download-limit.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-auth-routes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-otp-link-preview.js && node test-call-quick-voice.js && node test-call-run-stop.js && node test-call-stopped-transcript.js && node test-call-greeting-context.js && node test-call-camera.js && node test-switchboard-calls.js && node test-switchboard-supervisor.js"
14
+ "test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && node test-codex-rollout.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-room-scope.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-test-run-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-mandate-hygiene.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-external-rule-scopes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-owner-cards-dm.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-cards.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-people-owner-seed.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-lanes.js && node test-thread-titles.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-stop.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-stop-queue.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-typing-heartbeat.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-stream-preview-purpose.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-quiet-defaults.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-say-loopback.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-union.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-subst-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-section-budgets.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-dedup-adjudicator.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-prospective.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-regressions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cost-report.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cost-by-part.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-mining.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-certification.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-apprenticeship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-attest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-attest.js && node test-sandbox-probe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-bot-endpoint.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cli-owner-gates.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-verify-contact.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-owner-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-watch-check-env.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-intro-adoption.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-grants.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-room-provider-settings.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-store-durability.js && node test-kazee-cards.js && node test-card-blocks.js && node test-decision-upkeep.js && node test-passive-replies.js && node test-kazee-bot-loops.js && node test-kazee-bot-handoffs.js && node test-placeholder-reply.js && node test-media-speech.js && node test-speech-voices.js && node test-tts-voice.js && node test-passive-replies-router.js && node test-loopback-cards.js && node test-task-injection.js && node test-speaker-extras.js && node test-token-diet.js && node test-dream-tasks.js && node test-task-archive.js && node test-cron-tolerance.js && node test-dream-schedule.js && node test-job-failures.js && node test-scheduled-placeholder.js && node test-lessons-since.js && node test-pack-reinject.js && node test-turn-journal.js && node test-kazee-catchup.js && node test-usage-limits.js && node test-limit-waits.js && node test-signin-race-retry.js && node test-limit-pickup.js && node test-browser-view.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-browser-view.js && node test-browser-address.js && node test-browser-control.js && node test-browser-drive.js && node test-browser-teach.js && node test-kazee-bot-apps.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-browser-app.js && node test-safe-filename.js && node test-media-filename-paths.js && node test-router-document-guard.js && OPEN_CLAUDIA_TEST=1 node test-web-dashboard-escape.js && OPEN_CLAUDIA_TEST=1 node test-web-bad-requests.js && OPEN_CLAUDIA_TEST=1 node test-kazee-download-limit.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-auth-routes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-otp-link-preview.js && node test-call-quick-voice.js && node test-call-run-stop.js && node test-call-stopped-transcript.js && node test-call-greeting-context.js && node test-call-camera.js && node test-switchboard-calls.js && node test-switchboard-supervisor.js"
15
15
  },
16
16
  "files": [
17
17
  "bot.js",
@@ -650,7 +650,13 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
650
650
  // 3.5.67 = 3.5.66 + card building blocks (APP-31): examples, wake wording for
651
651
  // tick/date/slots/rows/board, `card pieces`, the card coach nudge.
652
652
  // Sumeet, 16 Oct 08:28 EAT: "ok build and push, ensure you also update open claudia".
653
- assert.strictEqual(pkg.version, "3.5.67", "release version must remain unchanged without explicit approval");
653
+ // 3.5.68 = 3.5.67 + test-run guard (jest/vitest: 2 workers, one run at a time,
654
+ // 40% free memory) in the tool hook, after the Mac froze twice on 19 Oct.
655
+ // Sumeet, 19 Oct 11:47 EAT card: "Both: checker rule and safety net".
656
+ // 3.5.69 = 3.5.68 + card fixes from the 17 Oct review: due dates say "Due",
657
+ // a status list (rows with To do / Doing / Done) instead of the board.
658
+ // Sumeet, 20 Oct 07:43 EAT: "Why don't you just push them out now?"
659
+ assert.strictEqual(pkg.version, "3.5.69", "release version must remain unchanged without explicit approval");
654
660
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
655
661
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
656
662
  }