@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 +6 -5
- package/dist/full/version.json +2 -2
- package/docs/MCP_SETUP.md +1 -1
- package/package.json +1 -1
- package/src/cli/orbit.js +17 -8
- package/src/core/boards.js +59 -14
- package/src/core/provision-repo-board.js +8 -2
- package/src/core/seed.js +12 -3
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
|
|
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. **
|
|
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`;
|
|
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 (
|
|
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.
|
package/dist/full/version.json
CHANGED
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.
|
|
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
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
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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) {
|
package/src/core/boards.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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,
|
|
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, {
|
|
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, {
|
|
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) => {
|