@starlove/orbit 1.0.2 → 1.0.3

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/README.md CHANGED
@@ -37,6 +37,7 @@ From the repo you want a board for:
37
37
  cd path/to/your-app
38
38
  orbit init # creates .orbit/board.db + SKILL-ORBIT.md + AGENTS.md
39
39
  orbit init --example # optional: add onboarding example tickets
40
+ orbit init --no-ai # optional: create or update the board with AI disabled
40
41
  ```
41
42
 
42
43
  Start one runtime and keep using it:
@@ -67,9 +68,9 @@ Wipe a single board and start over with `orbit reset` (deletes `.orbit/`, `SKILL
67
68
 
68
69
  ## Use with AI
69
70
 
70
- Orbit ships with everything an agent needs to claim work, read tickets, and write back implementation notes — but it stays out of your way until you turn it on.
71
+ Orbit ships with everything an agent needs to claim work, read tickets, and write back implementation notes. New boards have AI collaboration enabled by default; pass `orbit init --no-ai` or turn off **Enable AI** in Settings → AI if you want a human-only board.
71
72
 
72
- 1. **Enable AI on the board.** Open Settings (gear icon) → **AI** tab → toggle **Enable AI**. Orbit provisions the `AI Ready`, `In Progress`, and `Review` lanes if they're missing, surfaces the agent-context fields, and reveals the MCP setup snippet.
73
+ 1. **Confirm AI is enabled.** Open Settings (gear icon) → **AI** tab → toggle **Enable AI** if needed. Orbit provisions the `AI Ready`, `In Progress`, and `Review` lanes if they're missing, surfaces the agent-context fields, and reveals the MCP setup snippet.
73
74
  2. **Fill in agent context.** Settings → AI → *Agent Instructions* is the project-level briefing every agent reads before touching a ticket. Describe what the project is, who it serves, the stack, and any rules of the road.
74
75
  3. **Register the MCP server with your agent.** The AI tab generates copy-pasteable snippets for Claude Code, Cursor, Codex, OpenCode, OpenClaw, and other MCP-capable clients — pick your OS + client and paste the command into your agent's MCP config.
75
76
  4. **Restart the agent.** On its next boot it discovers the Orbit MCP tools (`board_claim_next`, `board_get_ticket_context`, `board_update_ticket`, etc.).
@@ -80,7 +81,7 @@ If you initialized with `--example`, ticket #12 (`Try Orbit MCP on this ticket`)
80
81
  ## Vocabulary
81
82
 
82
83
  - **Board** — one repo's planning surface. Each board carries its own `agent_instructions`, lanes, tickets, and memory entries.
83
- - **Lane** — a column. Lanes are user-defined and freely named/reordered. The seed gives you `Backlog`, `Todo`, `In Progress`, `Review`, `Done`, `Cancelled`; turning on AI also pins `AI Ready`, `In Progress`, and `Review` as anchor lanes the agent flow keys off.
84
+ - **Lane** — a column. Lanes are user-defined and freely named/reordered. The default seed gives you `Backlog`, `Todo`, `AI Ready`, `In Progress`, `Review`, `Done`, `Cancelled`; `AI Ready`, `In Progress`, and `Review` are anchor lanes the agent flow keys off. `AI Ready` is inserted between `Todo` and `In Progress` when AI is enabled.
84
85
  - **Ticket types**:
85
86
  - `epic` — index card for a multi-feature initiative. Owns child tickets via `parent_ticket_id`.
86
87
  - `feature` — a standalone capability or new behavior.
@@ -101,7 +102,7 @@ Bun is not used as the Orbit runtime because Orbit depends on Node's built-in `n
101
102
 
102
103
  Each repo gets its own board database. Orbit keeps a small central registry that maps repo paths to their board files; the CLI, web app, and MCP server use explicit project roots when provided, then fall back to walking upward to find `.orbit/board.db`.
103
104
 
104
- - **Per-repo board** — `.orbit/board.db` lives in the repo it tracks. `orbit init` creates it, drops `SKILL-ORBIT.md` at the repo root, and creates/updates `AGENTS.md` with a terse pointer so agents load the full Orbit protocol only when relevant. Add `--example` to create onboarding cards.
105
+ - **Per-repo board** — `.orbit/board.db` lives in the repo it tracks. `orbit init` creates it, drops `SKILL-ORBIT.md` at the repo root, and creates/updates `AGENTS.md` with a terse pointer so agents load the full Orbit protocol only when relevant. Add `--example` to create onboarding cards, or `--no-ai` to disable AI collaboration for the board.
105
106
  - **Central registry** — `registry.db` records each board's path, slug, and last-active timestamp. Host `orbit serve` uses `~/.orbit`; `orbit docker` uses `<repo>/.orbit/docker-data`. Keep one mode unless you intentionally share `DATA_DIR`, re-register boards, or import snapshots.
106
107
  - **Project-root discovery** — `orbit serve`, `orbit mcp`, and MCP-attached agents resolve a board from an explicit `--cwd` / `PROJECT_ROOT` first, then fall back to walking up from process cwd to find `.orbit/board.db`. Persistent MCP configs should use an explicit root so the right board attaches even when the agent launches elsewhere.
107
108
  - **Two channels for AI** — `AGENTS.md` is the auto-loaded briefing, with an Orbit-managed pointer to `SKILL-ORBIT.md`; the *MCP config* tells the agent runtime how to launch Orbit's MCP server for the right project. The briefing travels with the repo; the MCP registration is per-agent-install.
@@ -167,7 +168,7 @@ A copy-editable client config lives at [docs/mcp-client.example.json](docs/mcp-c
167
168
  - **Board**: name from `BOARD_NAME` env var, otherwise inferred from `package.json` `name` or the repo folder name. Slug from `BOARD_SLUG` or derived from the name.
168
169
  - **Repo metadata**: `REPO_URL` env var or `git config --get remote.origin.url`; `SYSTEM_PATH` env var or the repo path passed to `orbit init`.
169
170
  - **Default branch**: `DEFAULT_BRANCH` env var, otherwise the current git branch.
170
- - **Lanes (6)**: `Backlog`, `Todo` (default), `In Progress`, `Review`, `Done`, `Cancelled`.
171
+ - **Lanes (7)**: `Backlog`, `Todo` (default), `AI Ready`, `In Progress`, `Review`, `Done`, `Cancelled`. Pass `--no-ai` to seed without `AI Ready` and leave AI collaboration disabled.
171
172
  - **Labels (7)**: `human-only`, `needs-human-input`, `needs-decomposition`, `needs-followup`, `tech-debt`, `security`, `onboarding`.
172
173
  - **Onboarding cards (optional)**: pass `--example` to create an epic `#1` plus children `#2`, `#3`, and `#12 — Try Orbit MCP on this ticket`.
173
174
  - **Free-form fields**: `project_notes` (Notes tab) and `agent_instructions` (AI tab) get placeholder text you should replace.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starlove/orbit",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "edition": "full",
5
- "builtAt": "2026-05-19T05:18:31.299Z"
5
+ "builtAt": "2026-05-19T05:38:00.174Z"
6
6
  }
package/docs/MCP_SETUP.md CHANGED
@@ -13,7 +13,7 @@ Orbit generates neutral template paths for Windows, macOS, and Linux. Replace th
13
13
 
14
14
  Board data lives under each tracked repo at `.orbit/board.db` (flat file or `.orbit/boards/<slug>/` for extra boards)—not beside the MCP script.
15
15
 
16
- After installing Orbit as a package, run **`orbit init`** in (or with `--cwd` pointing at) the repo where you want `.orbit/`, **`AGENTS.md`**, and a copy of **`SKILL-ORBIT.md`** so agents and MCP line up with that tree. Add `--ai` to enable the AI Ready / In Progress / Review agent lanes immediately, and add `--example` if you want the onboarding example tickets.
16
+ After installing Orbit as a package, run **`orbit init`** in (or with `--cwd` pointing at) the repo where you want `.orbit/`, **`AGENTS.md`**, and a copy of **`SKILL-ORBIT.md`** so agents and MCP line up with that tree. Orbit enables the AI Ready / In Progress / Review agent lanes by default. Add `--no-ai` if you want AI collaboration disabled, and add `--example` if you want the onboarding example tickets.
17
17
 
18
18
  The generated format changes by app:
19
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starlove/orbit",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Starscape Orbit — SQLite-backed, agent-first task board (Starscape suite).",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/cli/orbit.js CHANGED
@@ -23,7 +23,7 @@ Options (init / reset / serve / docker / mcp):
23
23
  --cwd <dir> Project root (default: process.cwd())
24
24
 
25
25
  Options (init only):
26
- --ai Enable AI collaboration lanes
26
+ --no-ai Create the board with AI collaboration disabled
27
27
  --example Create example onboarding tickets
28
28
  --refresh-agents-md Overwrite SKILL-ORBIT.md and refresh Orbit section in AGENTS.md
29
29
 
@@ -135,22 +135,30 @@ function syncAgentsMd(projectRoot, options) {
135
135
  console.log(`Appended Orbit instructions to ${agentsPath}`);
136
136
  }
137
137
 
138
- function enableAiIfRequested(options, registryRow, core) {
138
+ function enableAiIfEnabled(options, registryRow, core) {
139
139
  if (!options.ai || !registryRow) return;
140
140
  const { enableAiCollaboration, openConnection } = core;
141
141
  const db = openConnection(registryRow.db_path);
142
- enableAiCollaboration(db, registryRow.id, { stageOnboardingTicket: options.example, actor: "orbit init --ai" });
142
+ enableAiCollaboration(db, registryRow.id, { stageOnboardingTicket: options.example, actor: "orbit init" });
143
143
  const suffix = options.example ? "; example ticket #12 is staged in AI Ready." : ".";
144
144
  console.log(`AI collaboration enabled — AI Ready, In Progress, and Review lanes are present${suffix}`);
145
145
  }
146
146
 
147
+ function disableAiIfRequested(options, registryRow, core) {
148
+ if (options.ai || !registryRow) return;
149
+ const { openConnection } = core;
150
+ const db = openConnection(registryRow.db_path);
151
+ db.prepare("UPDATE boards SET ai_enabled = 0, updated_at = ? WHERE id = ?").run(new Date().toISOString(), registryRow.id);
152
+ console.log("AI collaboration disabled for this board.");
153
+ }
154
+
147
155
  function parseArgs(argv) {
148
156
  const args = {
149
157
  command: null,
150
158
  cwd: process.env.PROJECT_ROOT ? resolve(process.env.PROJECT_ROOT) : process.cwd(),
151
159
  cwdProvided: false,
152
160
  refreshAgentsMd: false,
153
- ai: false,
161
+ ai: true,
154
162
  example: false,
155
163
  port: null,
156
164
  image: "starscape-orbit:local",
@@ -169,7 +177,7 @@ function parseArgs(argv) {
169
177
  for (let i = 1; i < rest.length; i++) {
170
178
  const a = rest[i];
171
179
  if (a === "--refresh-agents-md") args.refreshAgentsMd = true;
172
- else if (a === "--ai") args.ai = true;
180
+ else if (a === "--no-ai") args.ai = false;
173
181
  else if (a === "--example") args.example = true;
174
182
  else if (a === "--cwd" && rest[i + 1]) {
175
183
  args.cwdProvided = true;
@@ -217,7 +225,7 @@ async function runInit(options) {
217
225
  mkdirSync(DATA_DIR, { recursive: true });
218
226
  createRegistrySchema();
219
227
 
220
- const result = provisionRepoBoard(projectRoot, { includeExamples: options.example });
228
+ const result = provisionRepoBoard(projectRoot, { enableAi: options.ai, includeExamples: options.example });
221
229
  const skillSrc = resolve(ROOT_DIR, "SKILL-ORBIT.md");
222
230
  const skillDest = resolve(projectRoot, "SKILL-ORBIT.md");
223
231
 
@@ -235,7 +243,8 @@ async function runInit(options) {
235
243
  syncAgentsMd(projectRoot, options);
236
244
  }
237
245
 
238
- enableAiIfRequested(options, result.registryRow, core);
246
+ enableAiIfEnabled(options, result.registryRow, core);
247
+ disableAiIfRequested(options, result.registryRow, core);
239
248
 
240
249
  if (result.message && result.status === "skipped") {
241
250
  console.log(result.message);
@@ -254,7 +263,7 @@ ${mcpLine}
254
263
  --- Next: agent prompt (from this repo) ---
255
264
  Read AGENTS.md in this repository, then use the Orbit MCP tools. Use board_list, board_set_active, and ticket context tools per docs/AGENT_PROTOCOL.md before claiming or updating work.
256
265
 
257
- ${options.example ? (options.ai ? "Example ticket #12 is \"Try Orbit MCP on this ticket\" in AI Ready — good first exercise after MCP is connected." : "Example tickets were created. Add `--ai` to also create AI collaboration lanes and stage ticket #12 in AI Ready.") : "No example tickets were created. Rerun on a fresh board with `orbit init --example` if you want the onboarding examples."}
266
+ ${options.example ? (options.ai ? "Example ticket #12 is \"Try Orbit MCP on this ticket\" in AI Ready — good first exercise after MCP is connected." : "Example tickets were created. AI collaboration was disabled with `--no-ai`, so ticket #12 remains in Backlog.") : "No example tickets were created. Rerun on a fresh board with `orbit init --example` if you want the onboarding examples."}
258
267
  `);
259
268
 
260
269
  if (result.registryRow) {
@@ -1,5 +1,5 @@
1
1
  import { getRegistry, tx } from "./db.js";
2
- import { boardById } from "./queries.js";
2
+ import { boardById, compactStatePositions } from "./queries.js";
3
3
  import {
4
4
  computeNewBoardDbPath,
5
5
  ensureBoardDbFileAndSchema,
@@ -33,14 +33,23 @@ export function createBoard(body, ctx) {
33
33
  const time = now();
34
34
  const sniff = buildBoardFromRepo(repoPath);
35
35
 
36
- const defaultStates = body.states || [
36
+ const aiEnabled = body.ai_enabled === undefined ? true : Boolean(body.ai_enabled);
37
+ const defaultStates = body.states || (aiEnabled ? [
37
38
  ["Backlog", null],
38
39
  ["Todo", null],
40
+ ["AI Ready", "ai_ready"],
39
41
  ["In Progress", "in_progress"],
40
42
  ["Review", "review"],
41
43
  ["Done", "done"],
42
44
  ["Cancelled", null]
43
- ];
45
+ ] : [
46
+ ["Backlog", null],
47
+ ["Todo", null],
48
+ ["In Progress", "in_progress"],
49
+ ["Review", "review"],
50
+ ["Done", "done"],
51
+ ["Cancelled", null]
52
+ ]);
44
53
 
45
54
  // Board row + lanes + creation event land atomically in the board DB. The
46
55
  // registry insert below is a different database (can't share this
@@ -49,8 +58,8 @@ export function createBoard(body, ctx) {
49
58
  tx(db, () => {
50
59
  db.prepare(
51
60
  `INSERT INTO boards
52
- (id, slug, name, repo_url, system_path, default_branch, created_at, updated_at)
53
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
61
+ (id, slug, name, repo_url, system_path, default_branch, ai_enabled, created_at, updated_at)
62
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
54
63
  ).run(
55
64
  boardId,
56
65
  slug,
@@ -58,6 +67,7 @@ export function createBoard(body, ctx) {
58
67
  body.repo_url || sniff.repoUrl || "",
59
68
  repoPath,
60
69
  body.default_branch || sniff.defaultBranch || "main",
70
+ aiEnabled ? 1 : 0,
61
71
  time,
62
72
  time
63
73
  );
@@ -70,6 +80,7 @@ export function createBoard(body, ctx) {
70
80
  ).run(id(), boardId, stateName, position, stateName === "Todo" ? 1 : 0, role || null, time);
71
81
  });
72
82
 
83
+ if (aiEnabled) ensureAgentLanes(db, boardId);
73
84
  recordEvent(db, boardId, "board_created", null, actor.name, { board_id: boardId, slug });
74
85
  });
75
86
 
@@ -95,24 +106,58 @@ const AGENT_LANES = [
95
106
  { name: "Review", role: "review" }
96
107
  ];
97
108
 
98
- /** Ensure the three role-bearing lanes that agent flow depends on
99
- * (ai_ready / in_progress / review) exist on the board. Missing lanes are
100
- * appended at the end of the lane order. Idempotent. */
109
+ function desiredAgentLanePosition(db, boardId, role) {
110
+ if (role === "ai_ready") {
111
+ const todo = db.prepare("SELECT position FROM states WHERE board_id = ? AND name = 'Todo'").get(boardId);
112
+ if (todo) return Number(todo.position) + 1;
113
+ const inProgress = db.prepare("SELECT position FROM states WHERE board_id = ? AND role = 'in_progress'").get(boardId);
114
+ if (inProgress) return Number(inProgress.position);
115
+ }
116
+ const maxPosition = db
117
+ .prepare("SELECT MAX(position) AS value FROM states WHERE board_id = ?")
118
+ .get(boardId).value;
119
+ return Number(maxPosition ?? -1) + 1;
120
+ }
121
+
122
+ function openStateSlot(db, boardId, position, exceptStateId = null) {
123
+ db.prepare(
124
+ `UPDATE states
125
+ SET position = position + 1
126
+ WHERE board_id = ?
127
+ AND position >= ?
128
+ AND (? IS NULL OR id != ?)`
129
+ ).run(boardId, position, exceptStateId, exceptStateId);
130
+ }
131
+
132
+ /** Ensure the role-bearing lanes that agent flow depends on exist on the
133
+ * board. AI Ready is kept directly after Todo so claim-next has a predictable
134
+ * handoff column. Idempotent. */
101
135
  export function ensureAgentLanes(db, boardId) {
102
136
  const time = now();
103
137
  for (const lane of AGENT_LANES) {
104
138
  const existing = db
105
- .prepare("SELECT id FROM states WHERE board_id = ? AND role = ?")
139
+ .prepare("SELECT id, position FROM states WHERE board_id = ? AND role = ?")
106
140
  .get(boardId, lane.role);
107
- if (existing) continue;
108
- const maxPosition = db
109
- .prepare("SELECT MAX(position) AS value FROM states WHERE board_id = ?")
110
- .get(boardId).value;
141
+ if (existing) {
142
+ if (lane.role === "ai_ready") {
143
+ let desiredPosition = desiredAgentLanePosition(db, boardId, lane.role);
144
+ if (Number(existing.position) !== desiredPosition) {
145
+ db.prepare("UPDATE states SET position = -1 WHERE id = ?").run(existing.id);
146
+ compactStatePositions(db, boardId);
147
+ desiredPosition = desiredAgentLanePosition(db, boardId, lane.role);
148
+ openStateSlot(db, boardId, desiredPosition, existing.id);
149
+ db.prepare("UPDATE states SET position = ? WHERE id = ?").run(desiredPosition, existing.id);
150
+ }
151
+ }
152
+ continue;
153
+ }
154
+ const position = desiredAgentLanePosition(db, boardId, lane.role);
155
+ openStateSlot(db, boardId, position);
111
156
  db.prepare(
112
157
  `INSERT INTO states
113
158
  (id, board_id, name, position, is_default, role, created_at)
114
159
  VALUES (?, ?, ?, ?, 0, ?, ?)`
115
- ).run(id(), boardId, lane.name, Number(maxPosition || 0) + 1, lane.role, time);
160
+ ).run(id(), boardId, lane.name, position, lane.role, time);
116
161
  }
117
162
  }
118
163
 
@@ -45,7 +45,10 @@ export function provisionRepoBoard(repoPathRaw, options = {}) {
45
45
 
46
46
  const db = openConnection(dbPath);
47
47
  createBoardSchema(db);
48
- const seeded = seedIfEmpty(db, repoRoot, { includeExamples: options.includeExamples });
48
+ const seeded = seedIfEmpty(db, repoRoot, {
49
+ enableAi: options.enableAi,
50
+ includeExamples: options.includeExamples
51
+ });
49
52
  if (!seeded) {
50
53
  const boardMeta = readBoardMetaFromSqlite(db);
51
54
  if (!boardMeta) {
@@ -60,7 +63,10 @@ export function provisionRepoBoard(repoPathRaw, options = {}) {
60
63
  function provisionExistingDbFile(repoRoot, dbPath, options = {}) {
61
64
  const db = openConnection(dbPath);
62
65
  createBoardSchema(db);
63
- const seeded = seedIfEmpty(db, repoRoot, { includeExamples: options.includeExamples });
66
+ const seeded = seedIfEmpty(db, repoRoot, {
67
+ enableAi: options.enableAi,
68
+ includeExamples: options.includeExamples
69
+ });
64
70
  if (seeded) {
65
71
  return insertAfterSeed(repoRoot, dbPath, db, seeded, "repaired");
66
72
  }
package/src/core/seed.js CHANGED
@@ -23,8 +23,8 @@ export function seedIfEmpty(db, repoPath, options = {}) {
23
23
  db.prepare(
24
24
  `INSERT INTO boards
25
25
  (id, slug, name, repo_url, system_path, default_branch,
26
- project_notes, agent_instructions, created_at, updated_at)
27
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
26
+ project_notes, agent_instructions, ai_enabled, created_at, updated_at)
27
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
28
28
  ).run(
29
29
  boardId,
30
30
  seed.boardSlug,
@@ -34,17 +34,26 @@ export function seedIfEmpty(db, repoPath, options = {}) {
34
34
  seed.defaultBranch,
35
35
  seed.projectNotes,
36
36
  seed.agentInstructions,
37
+ options.enableAi === false ? 0 : 1,
37
38
  time,
38
39
  time
39
40
  );
40
41
 
41
- const states = [
42
+ const states = options.enableAi === false ? [
42
43
  ["Backlog", null],
43
44
  ["Todo", null],
44
45
  ["In Progress", "in_progress"],
45
46
  ["Review", "review"],
46
47
  ["Done", "done"],
47
48
  ["Cancelled", null]
49
+ ] : [
50
+ ["Backlog", null],
51
+ ["Todo", null],
52
+ ["AI Ready", "ai_ready"],
53
+ ["In Progress", "in_progress"],
54
+ ["Review", "review"],
55
+ ["Done", "done"],
56
+ ["Cancelled", null]
48
57
  ];
49
58
  const stateIds = new Map();
50
59
  states.forEach(([name, role], position) => {