trantor 0.18.27 → 0.18.29

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.18.27",
3
+ "version": "0.18.28",
4
4
  "description": "Trantor — the hub-world for AI agent crews: live message bus, presence, project Kanban/flow board + crew orchestration for independent AI coding agents (Claude, Codex, Gemini, Kimi, DeepSeek)",
5
5
  "mcpServers": {
6
6
  "relay": {
package/README.md CHANGED
@@ -393,10 +393,16 @@ rate, not work rate.
393
393
 
394
394
  ```
395
395
  trantor setup | doctor | connect | profile | provider | models
396
- | up <agents…> | swap <old> <new> | down | seat-why <agent> | ui | advise | hub | watch
396
+ | new <name> | up <agents…> | swap <old> <new> | down | seat-why <agent> | ui | advise | hub | watch
397
397
  | adopt <project> | reconcile | duty | orchestrate | patrol | app | backfill | init-hooks
398
398
  ```
399
399
 
400
+ - **`trantor new <name>`** — project genesis in one command: makes the dir under your dev root
401
+ (`TRANTOR_DEV_ROOT` or `~/development`), git on main (or `--from <git-url>`, or `--adopt` an
402
+ existing folder), seeds CLAUDE.md from `--brief <file>`, installs the auto-card hook, posts
403
+ the brief to the hub, and opens the first card "genesis: <name>". `--json` for machines.
404
+ It never spawns a session — firing the crew stays your call.
405
+
400
406
  - **`trantor provider`** — `list` every crew seat (built-in + brought) with availability + tier ·
401
407
  `add <name> --key … [--plan api] [--base-url <url> --models a,b]` to bring any provider (custom
402
408
  endpoints are wired into OpenCode for you) · `remove <name>`.
package/bin/cli.mjs CHANGED
@@ -69,6 +69,7 @@ switch (cmd) {
69
69
  case "sweep": run("bin/sweep.mjs"); break;
70
70
  case "reconcile": run("bin/reconcile.mjs"); break;
71
71
  case "init-hooks": run("bin/init-hooks.mjs"); break;
72
+ case "new": run("bin/new.mjs"); break;
72
73
  case "balances": case "balance": case "credits": run("bin/balances.mjs"); break;
73
74
  case "recost": run("bin/recost.mjs"); break;
74
75
  case "handoff": run("bin/baton.mjs"); break;
@@ -208,6 +209,7 @@ switch (cmd) {
208
209
  trantor gates verification gates: "must verify before shipping" claims that survive handoffs — [--all] [--json]
209
210
  trantor backfill card past GIT work onto the board (solo commits that were never carded) — [--since "14 days ago"] [--dry-run]
210
211
  trantor init-hooks install a git post-commit hook so EVERY commit auto-cards on the board (reliable solo-work backstop) — [--uninstall]
212
+ trantor new project genesis: new <name> [--from <git-url>] [--brief <file>] [--dir <path>] [--adopt] [--json] — makes the dir, git main, CLAUDE.md from the brief, hooks, hub brief + first card (never spawns a session)
211
213
  trantor balances how much credit is left on each CONFIGURED provider (from your profile) — refill before you stall — [--json]
212
214
  trantor recost recompute sub-agent notional cost from on-disk transcripts + reseed the board (repair after upgrade) — [--dry-run]
213
215
  trantor handoff finish this session NOW: write a handoff, open a fresh session that takes over, and close this one (manual baton)
@@ -66,10 +66,35 @@ function ensureSeatWorktree(sourceDir) {
66
66
  }
67
67
 
68
68
  try { mkdirSync(join(homedir(), ".agent-bus", "worktrees", safePathSegment(PROJ)), { recursive: true }); } catch {}
69
- const r = spawnSync("git", ["-C", root, "worktree", "add", "-B", branch, seatDir, "HEAD"], {
69
+
70
+ // Fast-forward the base branch before branching: the worktree should build
71
+ // against the latest main, not a stale checkout. (#5403)
72
+ const base = gitOut(["-C", root, "rev-parse", "--abbrev-ref", "HEAD"], root);
73
+ if (base) {
74
+ const remote = gitOut(["-C", root, "rev-parse", "--abbrev-ref", `${base}@{upstream}`], root);
75
+ if (remote) {
76
+ const ff = spawnSync("git", ["-C", root, "merge", "--ff-only", remote], { stdio: "ignore", timeout: 15000 });
77
+ if (ff && ff.status === 0) console.log(`\x1b[2m[runner]\x1b[0m ${base} fast-forwarded to ${remote}`);
78
+ }
79
+ }
80
+
81
+ const r = spawnSync("git", ["-C", root, "worktree", "add", "--no-track", "-B", branch, seatDir, "HEAD"], {
70
82
  encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 30000,
71
83
  });
72
- if (r.status === 0) return seatDir;
84
+ if (r.status === 0) {
85
+ // Persist the base branch so the Review lens can name the real base instead
86
+ // of guessing via merge-base. Stale metadata is unset, not trusted. (#5403)
87
+ if (base) {
88
+ spawnSync("git", ["-C", seatDir, "config", `branch.${branch}.base`, base], { stdio: "ignore", timeout: 5000 });
89
+ }
90
+ // Set push.autoSetupRemote so a plain git push creates and sets upstream on
91
+ // first push (git >= 2.37, older clients ignore it). (#5403)
92
+ const pushAuto = gitOut(["-C", seatDir, "config", "--get", "push.autoSetupRemote"], seatDir);
93
+ if (!pushAuto) {
94
+ spawnSync("git", ["-C", seatDir, "config", "push.autoSetupRemote", "true"], { stdio: "ignore", timeout: 5000 });
95
+ }
96
+ return seatDir;
97
+ }
73
98
  console.log(`\x1b[33m[runner]\x1b[0m could not create ${branch} worktree — using ${sourceDir}`);
74
99
  return sourceDir;
75
100
  }
package/bin/crew.sh CHANGED
@@ -613,13 +613,16 @@ usage: trantor open [<project>]
613
613
  EOF
614
614
  }
615
615
  open_orchestrator() {
616
- local a
616
+ local a PROJ_ARG=""
617
617
  for a in "$@"; do case "$a" in
618
618
  --help|-h) usage_open; return 0 ;;
619
619
  --*) echo "trantor open: unknown flag '$a'"; usage_open; return 1 ;;
620
- *) PROJ="$a" ;;
620
+ *) PROJ="$a"; PROJ_ARG="$a" ;;
621
621
  esac; done
622
- DIR="$(_orch_resolve_dir "$DIR" "$PROJ")" || exit 1
622
+ # only an EXPLICIT name is resolved to its checkout; a project declared by RELAY_PROJECT from
623
+ # inside a differently named dir (the test harness, RELAY_PROJECT=<name> sessions) means "this
624
+ # cwd IS the project" — refusing it made `trantor open` impossible for exactly those sessions
625
+ DIR="$(_orch_resolve_dir "$DIR" "${PROJ_ARG:-}")" || exit 1
623
626
  command -v herdr >/dev/null 2>&1 || { echo "trantor open needs herdr (the pane host) — install: curl -fsSL https://herdr.dev/install.sh | sh"; exit 1; }
624
627
  local wsid="" orch="" live_ids="" live_names="" pair="" line fresh=0
625
628
  local sid; sid="$(_orch_sid "$PROJ")" || { echo "trantor open: could not mint a session id (uuidgen missing?)" >&2; exit 1; }
package/bin/new.mjs ADDED
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env node
2
+ // trantor new — project genesis, the CLI half (#5862). One command stands a project up:
3
+ //
4
+ // trantor new <name> [--from <git-url>] [--brief <file>] [--dir <path>] [--adopt] [--json]
5
+ //
6
+ // It makes the directory under the dev root (TRANTOR_DEV_ROOT or ~/development), starts git on
7
+ // main (or clones --from, or adopts an existing folder with --adopt), seeds CLAUDE.md from the
8
+ // brief (verbatim brief + the trantor conventions block), installs the same auto-card hook as
9
+ // `trantor init-hooks`, posts the brief as the hub project brief (POST /project — the same call
10
+ // relay_project_brief makes), and opens the first card "genesis: <name>" on the new board.
11
+ //
12
+ // It NEVER spawns a session: the wake is genesis-2, the app half. A hub that is down downgrades
13
+ // the genesis to a warning (card: null) — the directory is real either way, and a dead hub must
14
+ // not make a made project look unmade.
15
+ import { spawnSync } from "node:child_process";
16
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, appendFileSync } from "node:fs";
17
+ import { homedir } from "node:os";
18
+ import { dirname, isAbsolute, join, resolve } from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+ import { ensureEnrolled, loadIdentity, signedPost } from "../hooks/lib/api.mjs";
21
+ import { resolveHub } from "../lib/project.mjs";
22
+
23
+ const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
24
+
25
+ // The trantor conventions block — what every trantor-wired project's CLAUDE.md carries so the
26
+ // first session knows the board, the crew, and the gates exist. Kept SHORT and factual; the
27
+ // brief above it is the project's own voice.
28
+ const CONVENTIONS = [
29
+ "",
30
+ "## Trantor conventions",
31
+ "",
32
+ "This project is wired into Trantor (the board, the bus, the crew):",
33
+ "",
34
+ "- **Board** — work lives on the board, not in anyone's head. `trantor catchup` answers",
35
+ " \"where are we?\"; the desktop app (`trantor app install`) shows the live board.",
36
+ "- **Crew** — `trantor up codex kimi glm` fires seats into their own worktrees; contracts go",
37
+ " over the bus (`relay_send`), and every seat reports what became of its card. See the",
38
+ " `/trantor:crew` skill for the full doctrine before firing anything.",
39
+ "- **Gates** — a card reaches done only with real evidence: the test command, the counts,",
40
+ " and the observed behavior. \"It should work\" is not a state a card can be in.",
41
+ "- **Commits card themselves** — the post-commit hook (trantor auto-card) backfills the board",
42
+ " from git; keep commit messages in the imperative and reference card ids when one exists.",
43
+ "",
44
+ ].join("\n");
45
+
46
+ const die = (msg) => { console.error(`trantor new: ${msg}`); process.exit(1); };
47
+
48
+ // ── args ────────────────────────────────────────────────────────────────────────────────────────
49
+ const args = process.argv.slice(2);
50
+ const flag = (n) => { const i = args.indexOf("--" + n); return i >= 0 ? args[i + 1] : null; };
51
+ const has = (n) => args.includes("--" + n);
52
+ const json = has("json");
53
+ const name = args.find(a => !a.startsWith("--"));
54
+ if (!name) die("usage: trantor new <name> [--from <git-url>] [--brief <file>] [--dir <path>] [--adopt] [--json]");
55
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) die(`invalid project name "${name}" — letters, digits, dot, dash, underscore`);
56
+ const from = flag("from");
57
+ const briefFile = flag("brief");
58
+ const adopt = has("adopt");
59
+ // The brief is read BEFORE anything is created: a refused genesis must leave no directory behind.
60
+ let brief = "";
61
+ if (briefFile) {
62
+ if (!existsSync(briefFile)) die(`brief file not found: ${briefFile}`);
63
+ brief = readFileSync(briefFile, "utf8").trimEnd();
64
+ }
65
+
66
+ // ── the directory ───────────────────────────────────────────────────────────────────────────────
67
+ const dirArg = flag("dir");
68
+ const devRoot = dirArg
69
+ ? resolve(dirArg)
70
+ : (process.env.TRANTOR_DEV_ROOT ? resolve(process.env.TRANTOR_DEV_ROOT) : join(homedir(), "development"));
71
+ if (dirArg && !isAbsolute(dirArg)) { /* relative --dir is allowed; resolved above */ }
72
+ const dir = isAbsolute(name) ? name : join(devRoot, name);
73
+
74
+ const existed = existsSync(dir);
75
+ const occupied = existed && readdirSync(dir).length > 0;
76
+ if (occupied && !adopt) die(`"${dir}" already exists and is not empty — pass --adopt to adopt it`);
77
+ if (adopt && !existed) die(`--adopt: "${dir}" does not exist — nothing to adopt`);
78
+ if (!existed) mkdirSync(dir, { recursive: true });
79
+
80
+ // ── git ─────────────────────────────────────────────────────────────────────────────────────────
81
+ const git = (gitArgs) => spawnSync("git", gitArgs, { cwd: dir, encoding: "utf8" });
82
+ const gitOk = (r, what) => { if (r.status !== 0) die(`${what} failed: ${(r.stderr || r.stdout || "").trim()}`); };
83
+
84
+ let branch;
85
+ if (from) {
86
+ gitOk(git(["clone", from, "."]), "git clone");
87
+ branch = git(["branch", "--show-current"]).stdout.trim() || "main";
88
+ } else {
89
+ if (!existsSync(join(dir, ".git"))) gitOk(git(["init", "-b", "main"]), "git init");
90
+ branch = git(["branch", "--show-current"]).stdout.trim() || "main";
91
+ }
92
+
93
+ // ── CLAUDE.md — verbatim brief + the conventions block ──────────────────────────────────────────
94
+ const claude = join(dir, "CLAUDE.md");
95
+ if (existsSync(claude)) {
96
+ const current = readFileSync(claude, "utf8");
97
+ if (!current.includes("## Trantor conventions")) appendFileSync(claude, `\n${CONVENTIONS}\n`);
98
+ } else {
99
+ const head = brief ? `${brief}\n` : `# ${name}\n\n(Genesis — no brief was given. Add this project's what/why/goal here.)\n`;
100
+ writeFileSync(claude, `${head}${CONVENTIONS}`);
101
+ }
102
+
103
+ // ── hooks — the SAME install trantor init-hooks performs, in the new repo ───────────────────────
104
+ const hook = spawnSync(process.execPath, [join(ROOT, "bin", "init-hooks.mjs")], { cwd: dir, encoding: "utf8" });
105
+ if (hook.status !== 0) die(`hook install failed: ${(hook.stderr || "").trim()}`);
106
+
107
+ // ── the hub: brief + first card, signed like every other client ─────────────────────────────────
108
+ const hub = resolveHub(name);
109
+ const session = process.env.RELAY_SESSION || `genesis:${name}`;
110
+ const identity = loadIdentity(session);
111
+ let card = null;
112
+ let hubError = null;
113
+ try {
114
+ await ensureEnrolled(session, identity, name);
115
+ const briefForHub = (brief || `Genesis of ${name} — created by trantor new.`).slice(0, 600);
116
+ const r1 = await signedPost("/project", { project: name, brief: briefForHub, by: session }, { session, project: name, timeoutMs: 8000 });
117
+ if (!r1.ok) throw new Error(`hub ${r1.status} on /project`);
118
+ const r2 = await signedPost("/task", {
119
+ project: name,
120
+ title: `genesis: ${name}`,
121
+ status: "todo",
122
+ by: session,
123
+ note: "project genesis — created by trantor new",
124
+ }, { session, project: name, timeoutMs: 8000 });
125
+ if (!r2.ok) throw new Error(`hub ${r2.status} on /task`);
126
+ card = r2.json?.task?.id ?? null;
127
+ } catch (e) {
128
+ hubError = e instanceof Error ? e.message : String(e);
129
+ console.error(`trantor new: hub unreachable or refusing (${hubError}) — the project exists locally; the brief and first card were NOT posted.`);
130
+ }
131
+
132
+ // ── report ──────────────────────────────────────────────────────────────────────────────────────
133
+ if (json) {
134
+ console.log(JSON.stringify({ name, dir, branch, hub, card }));
135
+ } else {
136
+ console.log(`✓ ${dir} (${branch}${from ? ", cloned" : adopt ? ", adopted" : ""})`);
137
+ console.log(`✓ CLAUDE.md seeded${brief ? " from the brief" : " (no brief — add the project's what/why/goal)"}`);
138
+ console.log(`✓ auto-card hook installed (trantor init-hooks)`);
139
+ if (card !== null) console.log(`✓ hub ${hub}: brief posted, card #${card} ("genesis: ${name}")`);
140
+ else if (hubError) console.log(`! hub ${hub}: brief/card not posted (${hubError})`);
141
+ console.log(`\nNext: cd ${dir} && claude — or fire the crew with \`trantor up\`. No session was spawned.`);
142
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.18.27",
3
+ "version": "0.18.29",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "trantor": "bin/cli.mjs"
@@ -11,7 +11,7 @@
11
11
  "zod": "^4.4.3"
12
12
  },
13
13
  "scripts": {
14
- "test": "node bin/slop-gate.mjs --surface desktop/src && node test.mjs && node test-scenarios.mjs && node test-failure.mjs && node test-redelivery.mjs && node test-crew-completion.mjs && node test-contracts.mjs && node test-autonomy.mjs && node test-integrate.mjs && node test-handoff.mjs && node test-handoff-summarizer.mjs && node test-baton-turn-boundary.mjs && node test-agents.mjs && node test-update.mjs && node test-handoff-guard.mjs && node test-baton-latest.mjs && node test-balances.mjs && node test-usage-live.mjs && node test-subagent-cost.mjs && node test-inbox.mjs && node test-inflight.mjs && node test-notify.mjs && node test-focus.mjs && node test-cardlog.mjs && node test-checklist.mjs && node test-relay-note.mjs && node test-reaper.mjs && node test-events.mjs && node test-proposals.mjs && node test-scrub.mjs && node test-store-delta.mjs && node test-message-re.mjs && node test-claims.mjs && node test-adopt.mjs && node test-summarize.mjs && node test-identity-core.mjs && node test-identity.mjs && node test-identity-instances.mjs && node test-duty.mjs && node test-duty-dark.mjs && node test-duty-seat.mjs && node test-discovery.mjs && node test-doctor.mjs && node test-crew-env.mjs && node test-desktop-transport.mjs && node test-crew-worktree.mjs && bash test-crew-herdr.sh && npm --prefix desktop run test --silent && node test-splitbrain.mjs && node test-overseer.mjs && node test-overseer-lib.mjs && node test-overseer-warn.mjs && node test-provider-keys.mjs && node test-inbox-delivery.mjs && node test-identity-drift.mjs && node test-inbox-staleness.mjs && node test-seats.mjs && node test-seat-identity.mjs && node test-hub-routing.mjs && node test-hook-routing.mjs && node test-relay-wait.mjs && node test-dsh-seat.mjs && node test-kimi-bridge.mjs && node test-kimi-events.mjs && python3 engine/test-routing.py && node test-reconcile.mjs && node test-resources.mjs && node test-patrol.mjs && node test-bridge.mjs && bash test-crew.sh"
14
+ "test": "node bin/slop-gate.mjs --surface desktop/src && node test.mjs && node test-scenarios.mjs && node test-failure.mjs && node test-redelivery.mjs && node test-crew-completion.mjs && node test-contracts.mjs && node test-autonomy.mjs && node test-integrate.mjs && node test-handoff.mjs && node test-handoff-summarizer.mjs && node test-baton-turn-boundary.mjs && node test-agents.mjs && node test-update.mjs && node test-handoff-guard.mjs && node test-baton-latest.mjs && node test-balances.mjs && node test-usage-live.mjs && node test-subagent-cost.mjs && node test-inbox.mjs && node test-inflight.mjs && node test-notify.mjs && node test-focus.mjs && node test-cardlog.mjs && node test-checklist.mjs && node test-relay-note.mjs && node test-reaper.mjs && node test-events.mjs && node test-proposals.mjs && node test-scrub.mjs && node test-store-delta.mjs && node test-message-re.mjs && node test-claims.mjs && node test-adopt.mjs && node test-summarize.mjs && node test-identity-core.mjs && node test-identity.mjs && node test-identity-instances.mjs && node test-duty.mjs && node test-duty-dark.mjs && node test-duty-seat.mjs && node test-discovery.mjs && node test-doctor.mjs && node test-crew-env.mjs && node test-desktop-transport.mjs && node test-crew-worktree.mjs && bash test-crew-herdr.sh && npm --prefix desktop run test --silent && node test-splitbrain.mjs && node test-overseer.mjs && node test-overseer-lib.mjs && node test-overseer-warn.mjs && node test-provider-keys.mjs && node test-inbox-delivery.mjs && node test-identity-drift.mjs && node test-inbox-staleness.mjs && node test-seats.mjs && node test-seat-identity.mjs && node test-hub-routing.mjs && node test-hook-routing.mjs && node test-relay-wait.mjs && node test-dsh-seat.mjs && node test-kimi-bridge.mjs && node test-kimi-events.mjs && python3 engine/test-routing.py && node test-new.mjs && node test-reconcile.mjs && node test-resources.mjs && node test-patrol.mjs && node test-bridge.mjs && bash test-crew.sh"
15
15
  },
16
16
  "description": "The hub-world for AI agent crews — orchestrate Claude Code, Codex, GLM, Kimi, DeepSeek & any OpenRouter model as live crews with a plan-aware Advisor, a Kanban/flow command center, a testing gate, and an economics brain (Scrooge).",
17
17
  "files": [
@@ -32,6 +32,9 @@ PRD.md + TDD.md. The TDD MUST define one file-set per agent (no merge conflicts)
32
32
  explicit EVENT/INTERFACE CONTRACT — cross-agent bugs come from contract drift.
33
33
 
34
34
  ## Phase 1 — board setup
35
+ 0. No project yet? `trantor new <name> --brief <file>` stands one up (dir, git main, CLAUDE.md
36
+ from the brief, hooks, hub brief + first card) — it never spawns a session; firing the crew
37
+ is this phase's job.
35
38
  1. `relay_project_brief("<what + why + goal>")`
36
39
  2. One card per package: `relay_task_add(title, assignee, difficulty, model)` — set `model`
37
40
  to the advisor-routed model (or the CLI's default name); difficulty + model show as badges