@oriro/orirocli 0.3.5 → 0.3.6

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 (2) hide show
  1. package/dist/cli.js +181 -29
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -4570,8 +4570,13 @@ var armed = false;
4570
4570
  function armPostureGate() {
4571
4571
  armed = true;
4572
4572
  }
4573
+ function bypassPosture(depthEnv) {
4574
+ const d = Number(depthEnv);
4575
+ return Number.isFinite(d) && d > 0;
4576
+ }
4573
4577
  function registerPostureGate(pi) {
4574
4578
  pi.on("tool_call", async (event, ctx) => {
4579
+ if (bypassPosture(process.env.ORIRO_AGENT_DEPTH)) return void 0;
4575
4580
  const d = decideTool({ toolName: event.toolName, guardianBlocked: false });
4576
4581
  if (d.decision === "block") {
4577
4582
  return {
@@ -6724,6 +6729,136 @@ async function handleUndo(session) {
6724
6729
  }
6725
6730
  }
6726
6731
 
6732
+ // src/agents/worktree.ts
6733
+ import { execFile } from "child_process";
6734
+ import { promisify } from "util";
6735
+ import { join as join27, basename as basename2 } from "path";
6736
+ var run = promisify(execFile);
6737
+ var MAX_FAN = 4;
6738
+ function parseAgentsSlash(line) {
6739
+ const m = /^\/agents(?:\s+(\S[\s\S]*))?$/i.exec(line.trim());
6740
+ if (!m) return void 0;
6741
+ const rest = m[1]?.trim();
6742
+ if (!rest) return { cmd: "help" };
6743
+ const nx = /^(\d+)x\s+(\S[\s\S]*)$/i.exec(rest);
6744
+ if (nx) {
6745
+ const n = Math.min(Math.max(Number(nx[1]), 1), MAX_FAN);
6746
+ return { cmd: "fan", tasks: Array.from({ length: n }, () => nx[2].trim()) };
6747
+ }
6748
+ const tasks = rest.split("|").map((s) => s.trim()).filter(Boolean).slice(0, MAX_FAN);
6749
+ return tasks.length ? { cmd: "fan", tasks } : { cmd: "help" };
6750
+ }
6751
+ function fanStamp(now) {
6752
+ const p = (n, w = 2) => String(n).padStart(w, "0");
6753
+ return `${p(now.getMonth() + 1)}${p(now.getDate())}-${p(now.getHours())}${p(now.getMinutes())}${p(now.getSeconds())}`;
6754
+ }
6755
+ function fanBranch(stamp, i) {
6756
+ return `oriro/agents/${stamp}-a${i + 1}`;
6757
+ }
6758
+ function fanDir(repoRoot, stamp, i) {
6759
+ return join27(oriroDir(), "worktrees", `${basename2(repoRoot)}-${stamp}-a${i + 1}`);
6760
+ }
6761
+ async function git(cwd, ...args) {
6762
+ try {
6763
+ const { stdout: stdout12 } = await run("git", ["-C", cwd, ...args], { windowsHide: true });
6764
+ return { ok: true, out: stdout12.trim() };
6765
+ } catch (e) {
6766
+ return { ok: false, out: e instanceof Error ? e.message : String(e) };
6767
+ }
6768
+ }
6769
+ async function gitRoot(cwd) {
6770
+ const r = await git(cwd, "rev-parse", "--show-toplevel");
6771
+ return r.ok && r.out ? r.out : void 0;
6772
+ }
6773
+ async function addWorktree(root, dir, branch) {
6774
+ const r = await git(root, "worktree", "add", "-b", branch, dir);
6775
+ return r.ok ? void 0 : r.out;
6776
+ }
6777
+ async function changedFiles(dir) {
6778
+ const r = await git(dir, "status", "--short");
6779
+ return r.ok && r.out ? r.out.split("\n").map((s) => s.trim()).filter(Boolean) : [];
6780
+ }
6781
+ async function removeWorktree(root, dir, branch, force = false) {
6782
+ await git(root, "worktree", "remove", ...force ? ["--force"] : [], dir);
6783
+ if (branch) await git(root, "branch", "-D", branch);
6784
+ }
6785
+ var SNIPPET = 400;
6786
+ function formatFanReport(reports) {
6787
+ const lines = [];
6788
+ for (const r of reports) {
6789
+ lines.push(` \u2692 ${r.role} ${r.ok ? "\u2713" : "\u2717"} \u2014 ${r.task.length > 70 ? `${r.task.slice(0, 70)}\u2026` : r.task}`);
6790
+ const snip = r.output.length > SNIPPET ? `${r.output.slice(0, SNIPPET)}\u2026` : r.output;
6791
+ if (snip) lines.push(...snip.split("\n").map((l) => ` ${l}`));
6792
+ if (r.dir && r.branch && r.changes?.length) {
6793
+ lines.push(` \u270E ${r.changes.length} file${r.changes.length === 1 ? "" : "s"} changed on ${r.branch}`);
6794
+ lines.push(` review: cd "${r.dir}" \xB7 keep: commit there, then \`git merge ${r.branch}\` here`);
6795
+ } else if (r.changes && r.changes.length === 0) {
6796
+ lines.push(" (no file changes \u2014 worktree cleaned up)");
6797
+ }
6798
+ }
6799
+ const kept = reports.filter((r) => r.dir).length;
6800
+ lines.push(` \u2692 fan-out done: ${reports.filter((r) => r.ok).length}/${reports.length} ok${kept ? ` \xB7 ${kept} worktree${kept === 1 ? "" : "s"} kept for review` : ""}`);
6801
+ return lines;
6802
+ }
6803
+
6804
+ // src/agents/fanout.ts
6805
+ var CONCURRENCY = 2;
6806
+ function defFor(role, task) {
6807
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6808
+ return { name: `fan-${role}`, task, createdAt: now, updatedAt: now };
6809
+ }
6810
+ async function runFanout(tasks, cwd) {
6811
+ const capped = tasks.slice(0, MAX_FAN);
6812
+ const root = await gitRoot(cwd);
6813
+ const stamp = fanStamp(/* @__PURE__ */ new Date());
6814
+ const prevDepth = process.env.ORIRO_AGENT_DEPTH;
6815
+ process.env.ORIRO_AGENT_DEPTH = String((Number(prevDepth) || 0) + 1);
6816
+ let reports;
6817
+ try {
6818
+ reports = await runPool(capped.map((task, i) => ({ task, i })), CONCURRENCY, async ({ task, i }) => {
6819
+ const role = `a${i + 1}`;
6820
+ if (!root) {
6821
+ const r2 = await runAgent2(defFor(role, task), { cwd });
6822
+ return { role, task, ok: r2.ok, output: r2.output };
6823
+ }
6824
+ const dir = fanDir(root, stamp, i);
6825
+ const branch = fanBranch(stamp, i);
6826
+ const err = await addWorktree(root, dir, branch);
6827
+ if (err) return { role, task, ok: false, output: `could not create worktree: ${err}` };
6828
+ const r = await runAgent2(defFor(role, task), { cwd: dir });
6829
+ const changes = await changedFiles(dir);
6830
+ if (changes.length === 0) {
6831
+ await removeWorktree(root, dir, branch);
6832
+ return { role, task, ok: r.ok, output: r.output, changes: [] };
6833
+ }
6834
+ return { role, task, ok: r.ok, output: r.output, dir, branch, changes };
6835
+ });
6836
+ } finally {
6837
+ if (prevDepth === void 0) delete process.env.ORIRO_AGENT_DEPTH;
6838
+ else process.env.ORIRO_AGENT_DEPTH = prevDepth;
6839
+ }
6840
+ const lines = formatFanReport(reports);
6841
+ if (!root) lines.unshift(" \u2692 not a git repo \u2014 agents ran in the SAME directory (no worktree isolation)");
6842
+ return lines;
6843
+ }
6844
+
6845
+ // src/repl-ui/slash-agents.ts
6846
+ function isAgentsSlash(slash) {
6847
+ return parseAgentsSlash(slash) !== void 0;
6848
+ }
6849
+ async function handleAgents(line) {
6850
+ const p = parseAgentsSlash(line);
6851
+ if (!p || p.cmd === "help") {
6852
+ return [
6853
+ ` ${accent("/agents")} ${dim("\u2014 parallel sub-agents in isolated git worktrees (results merged here)")}`,
6854
+ ` ${accent("/agents 3x <task>")} ${dim("three agents race the same task")}`,
6855
+ ` ${accent("/agents <task A> | <task B>")} ${dim(`different tasks in parallel (max ${MAX_FAN}; '|' separates tasks)`)}`,
6856
+ ` ${dim("each agent gets its own worktree + branch; clean ones are removed, changed ones kept for review")}`
6857
+ ];
6858
+ }
6859
+ return runFanout(p.tasks, process.cwd());
6860
+ }
6861
+
6727
6862
  // src/repl-ui/tui-repl.ts
6728
6863
  var editorTheme = {
6729
6864
  borderColor: (s) => dim(s),
@@ -6810,7 +6945,7 @@ async function runTuiRepl(session) {
6810
6945
  ` ${accent("/routers")} pool add\xB7rotate ${accent("/model")} <id\u2026> switch ${accent("/usage")} health ${accent("/trace")} tool+router activity ${accent("/compact")} free context`,
6811
6946
  ` ${accent("/review")} artifacts ${accent("/save")} <n> [path] ${accent("/init")} AGENTS.md ${accent("/skills")} ${accent("/connectors")} ${accent("/voice")}`,
6812
6947
  ` ${accent("/sessions")} list saved ${accent("/undo")} rewind a turn ${dim("resume:")} ${accent("oriro -c")} / ${accent("oriro --resume <id>")}`,
6813
- ` ${accent("/plan")} <task> plan read-only ${accent("/approve")} execute it ${accent("/reject")} discard`,
6948
+ ` ${accent("/plan")} <task> plan read-only ${accent("/approve")} execute it ${accent("/reject")} discard ${accent("/agents")} parallel worktree fan-out`,
6814
6949
  ` ${dim("Shift+Tab")} posture ${dim("Alt+Shift+T")} thinking ${accent("/help")} ${accent("/exit")}`
6815
6950
  ].join("\n");
6816
6951
  chat.addChild(new Text(help, 0, 0));
@@ -6891,6 +7026,18 @@ async function runTuiRepl(session) {
6891
7026
  })();
6892
7027
  return;
6893
7028
  }
7029
+ if (isAgentsSlash(slash)) {
7030
+ editor.setText("");
7031
+ const pending = new Text(dim(" \u2692 deploying agents\u2026"), 0, 0);
7032
+ chat.addChild(pending);
7033
+ tui.requestRender();
7034
+ void (async () => {
7035
+ const lines = await handleAgents(text);
7036
+ pending.setText(lines.join("\n"));
7037
+ tui.requestRender();
7038
+ })();
7039
+ return;
7040
+ }
6894
7041
  const plan = parsePlanSlash(text);
6895
7042
  let internalPrompt;
6896
7043
  let turnText = text;
@@ -7023,7 +7170,7 @@ ${english}`;
7023
7170
  // src/voice/mic.ts
7024
7171
  import { spawn as spawn3 } from "child_process";
7025
7172
  import { tmpdir as tmpdir3 } from "os";
7026
- import { join as join27 } from "path";
7173
+ import { join as join28 } from "path";
7027
7174
  import { existsSync as existsSync18, statSync as statSync4 } from "fs";
7028
7175
  function recorders(outFile, seconds) {
7029
7176
  const dur = String(seconds);
@@ -7045,7 +7192,7 @@ function recorders(outFile, seconds) {
7045
7192
  ];
7046
7193
  }
7047
7194
  async function recordMic(seconds = 6) {
7048
- const outFile = join27(tmpdir3(), `oriro-voice-${process.pid}-${seconds}.wav`);
7195
+ const outFile = join28(tmpdir3(), `oriro-voice-${process.pid}-${seconds}.wav`);
7049
7196
  for (const r of recorders(outFile, seconds)) {
7050
7197
  const okFile = await new Promise((resolve3) => {
7051
7198
  const child = spawn3(r.cmd, r.args, { stdio: "ignore" });
@@ -7118,6 +7265,7 @@ function replHelp() {
7118
7265
  ${dim("This session")} ${accent("/usage")} pool health & turns ${accent("/trace")} activity ${accent("/compact")} free context ${accent("/undo")} rewind a turn
7119
7266
  ${dim("Continuity")} ${accent("/sessions")} list saved sessions ${dim("resume:")} ${accent("oriro -c")} ${dim("or")} ${accent("oriro --resume <id>")}
7120
7267
  ${dim("Plan loop")} ${accent("/plan")} <task> read-only plan ${accent("/approve")} execute it ${accent("/reject")} discard
7268
+ ${dim("Fan-out")} ${accent("/agents")} <A> | <B> parallel sub-agents in isolated git worktrees
7121
7269
  ${dim("Artifacts")} ${accent("/review")} code/SVG from the last reply ${accent("/save")} <n> [path] write one
7122
7270
  ${dim("Project")} ${accent("/init")} write a starter AGENTS.md ORIRO reads each session
7123
7271
  ${dim("Capabilities")} ${accent("/skills")} ${accent("/connectors")} ${accent("/voice")} speak a turn
@@ -7217,6 +7365,10 @@ async function runReadlineRepl(session) {
7217
7365
  stdout7.write(handleArtifactSlash(line).join("\n") + "\n");
7218
7366
  continue;
7219
7367
  }
7368
+ if (isAgentsSlash(slash)) {
7369
+ stdout7.write((await handleAgents(line)).join("\n") + "\n");
7370
+ continue;
7371
+ }
7220
7372
  const plan = parsePlanSlash(line);
7221
7373
  let internalPrompt;
7222
7374
  let turnText = line;
@@ -7377,7 +7529,7 @@ async function confirmDestructive(what, opts = {}) {
7377
7529
 
7378
7530
  // src/config/store.ts
7379
7531
  import { readFileSync as readFileSync22, writeFileSync as writeFileSync20, mkdirSync as mkdirSync16 } from "fs";
7380
- import { join as join28 } from "path";
7532
+ import { join as join29 } from "path";
7381
7533
  var KEYS = {
7382
7534
  output: {
7383
7535
  desc: "default output format for list commands: text | json | csv",
@@ -7399,7 +7551,7 @@ function validateConfig(key, value) {
7399
7551
  return KEYS[key].validate?.(value) ?? null;
7400
7552
  }
7401
7553
  function file4() {
7402
- return join28(oriroDir(), "config.json");
7554
+ return join29(oriroDir(), "config.json");
7403
7555
  }
7404
7556
  var cache = null;
7405
7557
  function readAll() {
@@ -8002,9 +8154,9 @@ function registerConnectorsCommand(program2) {
8002
8154
 
8003
8155
  // src/channels/config.ts
8004
8156
  import { readFileSync as readFileSync25, writeFileSync as writeFileSync21 } from "fs";
8005
- import { join as join29 } from "path";
8157
+ import { join as join30 } from "path";
8006
8158
  function file5() {
8007
- return join29(oriroDir(), "channels.json");
8159
+ return join30(oriroDir(), "channels.json");
8008
8160
  }
8009
8161
  function readChannels() {
8010
8162
  try {
@@ -8017,10 +8169,10 @@ function readChannels() {
8017
8169
  function saveChannel(cfg) {
8018
8170
  const all = readChannels().filter((c) => c.kind !== cfg.kind);
8019
8171
  all.push(cfg);
8020
- writeFileSync21(join29(ensureOriroDir(), "channels.json"), JSON.stringify(all, null, 2), "utf8");
8172
+ writeFileSync21(join30(ensureOriroDir(), "channels.json"), JSON.stringify(all, null, 2), "utf8");
8021
8173
  }
8022
8174
  function removeChannel(kind) {
8023
- writeFileSync21(join29(ensureOriroDir(), "channels.json"), JSON.stringify(readChannels().filter((c) => c.kind !== kind), null, 2), "utf8");
8175
+ writeFileSync21(join30(ensureOriroDir(), "channels.json"), JSON.stringify(readChannels().filter((c) => c.kind !== kind), null, 2), "utf8");
8024
8176
  }
8025
8177
 
8026
8178
  // src/channels/telegram.ts
@@ -8137,9 +8289,9 @@ async function startDiscord(token) {
8137
8289
  }
8138
8290
 
8139
8291
  // src/channels/whatsapp.ts
8140
- import { join as join30 } from "path";
8292
+ import { join as join31 } from "path";
8141
8293
  function whatsappAuthDir() {
8142
- return join30(oriroDir(), "whatsapp-auth");
8294
+ return join31(oriroDir(), "whatsapp-auth");
8143
8295
  }
8144
8296
  async function startWhatsApp() {
8145
8297
  let baileys;
@@ -8258,7 +8410,7 @@ function registerChannelsCommand(program2) {
8258
8410
 
8259
8411
  // src/commands/skills.ts
8260
8412
  import { existsSync as existsSync21, statSync as statSync5, mkdirSync as mkdirSync17, cpSync, rmSync as rmSync4 } from "fs";
8261
- import { resolve as resolve2, join as join31, basename as basename2, dirname as dirname4 } from "path";
8413
+ import { resolve as resolve2, join as join32, basename as basename3, dirname as dirname4 } from "path";
8262
8414
  function registerSkillsCommand(program2) {
8263
8415
  const skills = program2.command("skills").description("the ORIRO skill library \u2014 bundled + your own");
8264
8416
  skills.command("list").description("show CORE / TAIL skill counts (use --all to list names)").option("-a, --all", "list every skill name").option("-o, --output <fmt>", "output format: text (default) | json | csv").option("-q, --query <expr>", "filter/select: 'field', 'field=value', or 'field=value:selectField'").action(async (opts) => {
@@ -8295,22 +8447,22 @@ function registerSkillsCommand(program2) {
8295
8447
  mkdirSync17(dest, { recursive: true });
8296
8448
  const st = statSync5(src);
8297
8449
  if (st.isDirectory()) {
8298
- if (!existsSync21(join31(src, "SKILL.md"))) die(`no SKILL.md in ${src} \u2014 a skill folder must contain SKILL.md`);
8299
- const name = basename2(src);
8300
- cpSync(src, join31(dest, name), { recursive: true });
8301
- ok(`added skill ${accent(name)} \u2192 ${join31(dest, name)}`);
8302
- } else if (basename2(src).toLowerCase() === "skill.md") {
8303
- const name = basename2(dirname4(src)) || "custom-skill";
8304
- mkdirSync17(join31(dest, name), { recursive: true });
8305
- cpSync(src, join31(dest, name, "SKILL.md"));
8306
- ok(`added skill ${accent(name)} \u2192 ${join31(dest, name)}`);
8450
+ if (!existsSync21(join32(src, "SKILL.md"))) die(`no SKILL.md in ${src} \u2014 a skill folder must contain SKILL.md`);
8451
+ const name = basename3(src);
8452
+ cpSync(src, join32(dest, name), { recursive: true });
8453
+ ok(`added skill ${accent(name)} \u2192 ${join32(dest, name)}`);
8454
+ } else if (basename3(src).toLowerCase() === "skill.md") {
8455
+ const name = basename3(dirname4(src)) || "custom-skill";
8456
+ mkdirSync17(join32(dest, name), { recursive: true });
8457
+ cpSync(src, join32(dest, name, "SKILL.md"));
8458
+ ok(`added skill ${accent(name)} \u2192 ${join32(dest, name)}`);
8307
8459
  } else {
8308
8460
  die("expected a folder containing SKILL.md, or a SKILL.md file");
8309
8461
  }
8310
8462
  info("It loads on next launch \u2014 and is available in chat via /skill.");
8311
8463
  });
8312
8464
  skills.command("remove <name>").description("remove a skill you added").option("-f, --force", "skip the confirmation prompt").action(async (name, opts) => {
8313
- const target = join31(userSkillsDir(), name);
8465
+ const target = join32(userSkillsDir(), name);
8314
8466
  if (!existsSync21(target)) {
8315
8467
  info(`'${name}' is not a user-added skill \u2014 nothing to remove`);
8316
8468
  return;
@@ -8899,7 +9051,7 @@ function registerConfigCommand(program2) {
8899
9051
 
8900
9052
  // src/commands/setup.ts
8901
9053
  import { rmSync as rmSync5 } from "fs";
8902
- import { join as join32 } from "path";
9054
+ import { join as join33 } from "path";
8903
9055
  import { stdin as stdin12, stdout as stdout11 } from "process";
8904
9056
  var MARKERS = [
8905
9057
  "language.json",
@@ -8907,14 +9059,14 @@ var MARKERS = [
8907
9059
  "skills-onboarded.json",
8908
9060
  "connectors-onboarded.json",
8909
9061
  "models-onboarded.json",
8910
- join32("routers", "onboarded.json")
9062
+ join33("routers", "onboarded.json")
8911
9063
  ];
8912
9064
  function registerSetupCommand(program2) {
8913
9065
  program2.command("setup").description("run the guided setup wizard (language \xB7 routers \xB7 connectors \xB7 skills \xB7 avatar)").option("--reset", "clear your settled choices and re-ask every step").action(async (opts) => {
8914
9066
  if (opts.reset) {
8915
9067
  for (const m of MARKERS) {
8916
9068
  try {
8917
- rmSync5(join32(oriroDir(), m), { force: true });
9069
+ rmSync5(join33(oriroDir(), m), { force: true });
8918
9070
  } catch {
8919
9071
  }
8920
9072
  }
@@ -8932,7 +9084,7 @@ function registerSetupCommand(program2) {
8932
9084
 
8933
9085
  // src/commands/import.ts
8934
9086
  import { existsSync as existsSync22, readFileSync as readFileSync27, readdirSync as readdirSync4, statSync as statSync6, cpSync as cpSync2, mkdirSync as mkdirSync18 } from "fs";
8935
- import { join as join33, basename as basename3 } from "path";
9087
+ import { join as join34, basename as basename4 } from "path";
8936
9088
  function registerImportCommand(program2) {
8937
9089
  const imp = program2.command("import").description("migrate from another CLI (MCP servers, skills)");
8938
9090
  imp.command("mcp <file>").description("import MCP servers from a Claude-compatible mcp.json (Guardian-vetted)").action((file6) => {
@@ -8989,11 +9141,11 @@ function registerImportCommand(program2) {
8989
9141
  const dest = userSkillsDir();
8990
9142
  mkdirSync18(dest, { recursive: true });
8991
9143
  heading("Import skills");
8992
- const sources = existsSync22(join33(dir, "SKILL.md")) ? [dir] : readdirSync4(dir).map((e) => join33(dir, e)).filter((p) => statSync6(p).isDirectory() && existsSync22(join33(p, "SKILL.md")));
9144
+ const sources = existsSync22(join34(dir, "SKILL.md")) ? [dir] : readdirSync4(dir).map((e) => join34(dir, e)).filter((p) => statSync6(p).isDirectory() && existsSync22(join34(p, "SKILL.md")));
8993
9145
  let n = 0;
8994
9146
  for (const src of sources) {
8995
- cpSync2(src, join33(dest, basename3(src)), { recursive: true });
8996
- process.stdout.write(` ${fgHex(PALETTE.success, "\u2713")} ${accent(basename3(src))}
9147
+ cpSync2(src, join34(dest, basename4(src)), { recursive: true });
9148
+ process.stdout.write(` ${fgHex(PALETTE.success, "\u2713")} ${accent(basename4(src))}
8997
9149
  `);
8998
9150
  n++;
8999
9151
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oriro/orirocli",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "ORIRO — a free, on-device-friendly terminal AI agent. Built on the Pi agent harness (used as a library).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,7 +23,7 @@
23
23
  "dev": "tsx src/cli.ts",
24
24
  "build": "tsup",
25
25
  "typecheck": "tsc --noEmit",
26
- "test:unit": "tsx scripts/test-tool-sanitize.ts && tsx scripts/test-guardian.ts && tsx scripts/test-scribe.ts && tsx scripts/test-race.ts && tsx scripts/test-weights.ts && tsx scripts/test-output.ts && tsx scripts/test-connectors.ts && tsx scripts/test-artifacts.ts && tsx scripts/test-project-md.ts && tsx scripts/test-compact.ts && tsx scripts/test-init.ts && tsx scripts/test-sessions.ts && tsx scripts/test-permission.ts && tsx scripts/test-plan-mode.ts",
26
+ "test:unit": "tsx scripts/test-tool-sanitize.ts && tsx scripts/test-guardian.ts && tsx scripts/test-scribe.ts && tsx scripts/test-race.ts && tsx scripts/test-weights.ts && tsx scripts/test-output.ts && tsx scripts/test-connectors.ts && tsx scripts/test-artifacts.ts && tsx scripts/test-project-md.ts && tsx scripts/test-compact.ts && tsx scripts/test-init.ts && tsx scripts/test-sessions.ts && tsx scripts/test-permission.ts && tsx scripts/test-plan-mode.ts && tsx scripts/test-agents-fanout.ts",
27
27
  "smoke": "npm run build && node scripts/smoke.mjs",
28
28
  "prepublishOnly": "npm run build && npm run test:unit && node scripts/smoke.mjs && node scripts/prepublish-check.mjs",
29
29
  "start": "node dist/cli.js"