@connorbritain/roadmap 0.3.0
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 +495 -0
- package/docs/DEPLOYMENT.md +195 -0
- package/package.json +46 -0
- package/schema/backlog.schema.json +65 -0
- package/schema/roadmap.schema.json +273 -0
- package/scripts/assistant.mjs +30 -0
- package/scripts/backlog.mjs +81 -0
- package/scripts/cleanup.mjs +69 -0
- package/scripts/cli.mjs +119 -0
- package/scripts/cycle.mjs +89 -0
- package/scripts/dispatch.mjs +302 -0
- package/scripts/estimate.mjs +201 -0
- package/scripts/fanout.mjs +355 -0
- package/scripts/grab.mjs +119 -0
- package/scripts/init.mjs +60 -0
- package/scripts/lib/assistant-core.mjs +64 -0
- package/scripts/lib/backlog-core.mjs +318 -0
- package/scripts/lib/brief.mjs +123 -0
- package/scripts/lib/cli-core.mjs +97 -0
- package/scripts/lib/cycle-core.mjs +58 -0
- package/scripts/lib/estimate-core.mjs +204 -0
- package/scripts/lib/execution.mjs +186 -0
- package/scripts/lib/fanout-core.mjs +39 -0
- package/scripts/lib/graph.mjs +346 -0
- package/scripts/lib/journal-core.mjs +48 -0
- package/scripts/lib/linear-core.mjs +812 -0
- package/scripts/lib/mcp-core.mjs +313 -0
- package/scripts/lib/plan.mjs +68 -0
- package/scripts/lib/plate-core.mjs +53 -0
- package/scripts/lib/pr-watch-core.mjs +72 -0
- package/scripts/lib/priority.mjs +43 -0
- package/scripts/lib/recommend.mjs +178 -0
- package/scripts/lib/render-core.mjs +215 -0
- package/scripts/lib/review-core.mjs +104 -0
- package/scripts/lib/store.mjs +113 -0
- package/scripts/lib/sync-core.mjs +89 -0
- package/scripts/lib/validate-core.mjs +130 -0
- package/scripts/lib/wizard-core.mjs +50 -0
- package/scripts/linear.mjs +780 -0
- package/scripts/mcp.mjs +193 -0
- package/scripts/next.mjs +43 -0
- package/scripts/plate.mjs +77 -0
- package/scripts/promote.mjs +27 -0
- package/scripts/prompt.mjs +124 -0
- package/scripts/render.mjs +39 -0
- package/scripts/review.mjs +79 -0
- package/scripts/scheduler.mjs +78 -0
- package/scripts/set.mjs +31 -0
- package/scripts/show.mjs +53 -0
- package/scripts/test/run.mjs +3943 -0
- package/scripts/validate.mjs +28 -0
- package/scripts/watch-prs.mjs +72 -0
- package/scripts/wizard.mjs +97 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Deploying roadmap — every surface, every config, where secrets live
|
|
2
|
+
|
|
3
|
+
One rule governs everything on this page:
|
|
4
|
+
|
|
5
|
+
> **Committed YAML carries configuration. The environment carries secrets. Nothing else exists.**
|
|
6
|
+
> `docs/roadmap/roadmap.yaml` (including `meta.linear`) is committed and shareable — it never contains a credential. API keys live in environment variables only — never in the YAML, never in `.mcp.json`, never in plugin files. There is no hidden per-plugin config store to manage.
|
|
7
|
+
|
|
8
|
+
## The surfaces at a glance
|
|
9
|
+
|
|
10
|
+
| Surface | What you get | Install | Credentials come from |
|
|
11
|
+
|---|---|---|---|
|
|
12
|
+
| **CLI** (`roadmap ...`) | Everything: plan/fan/backlog/next/set/linear | `npm install -D @connorbritain/roadmap` | Your shell environment |
|
|
13
|
+
| **Claude Code plugin** | Skills (`/slice /backlog /imagine /prioritize /sync /init /fanout`), 4 agents, SessionStart hook, PR-watch monitor, **and** the MCP server `graph` (16 tools) — one install | `claude plugin install roadmap@roadmap` | Inherited from your shell environment |
|
|
14
|
+
| **Standalone MCP** (no plugin) | Just the 16 `graph` tools in any MCP client | register `scripts/mcp.mjs` (below) | Inherited env, or an `env` block in the client's MCP config |
|
|
15
|
+
| **Codex / other agents** | CLI + MCP + optional local assistant profile | `npm install -D @connorbritain/roadmap` + `roadmap init` | Shell environment |
|
|
16
|
+
| **CI / headless** | CLI (`validate`, `render`, `linear sync`) | `npm ci` in the tool checkout | CI secret store → env var |
|
|
17
|
+
|
|
18
|
+
A **consuming repo** commits only its own `docs/roadmap/roadmap.yaml` + `backlog.yaml` (and the generated `SLICES.md`/`BACKLOG.md`). The tool itself is installed once per machine. The Linear sync cursor (`.roadmap-linear-state.json`) is per-machine local state — git-ignore it.
|
|
19
|
+
|
|
20
|
+
## 1 · CLI
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install --save-dev @connorbritain/roadmap
|
|
24
|
+
npx roadmap init
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`roadmap` now works from anywhere inside any repo that has `docs/roadmap/roadmap.yaml`. No configuration files beyond the repo's own YAML.
|
|
28
|
+
|
|
29
|
+
## 2 · Claude Code plugin (the full experience)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
claude plugin marketplace add ConnorBritain/roadmap # or a local path
|
|
33
|
+
claude plugin install roadmap@roadmap # --scope project to pin per-repo
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
That single install wires the skills, agents, the SessionStart hook, the PR-watch monitor, **and** the bundled MCP server (`.mcp.json` → server name `graph`, tools `mcp__plugin_roadmap_graph__*`). There is nothing to configure inside the plugin itself.
|
|
37
|
+
|
|
38
|
+
**How the plugin's MCP server finds your repo:** it walks up from the session's project directory (`CLAUDE_PROJECT_DIR`) to the nearest `docs/roadmap/roadmap.yaml`.
|
|
39
|
+
|
|
40
|
+
**How it gets credentials:** the spawned server inherits your environment. If `LINEAR_API_KEY` is set at the user level (below), the plugin's Linear tools are authed with zero extra steps.
|
|
41
|
+
|
|
42
|
+
**Permissions:** in a consuming repo's `.claude/settings.json`, reads are safe to allow; mutators belong on the ask list:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"permissions": {
|
|
47
|
+
"allow": [
|
|
48
|
+
"mcp__plugin_roadmap_graph__plan", "mcp__plugin_roadmap_graph__ready_wave",
|
|
49
|
+
"mcp__plugin_roadmap_graph__show", "mcp__plugin_roadmap_graph__validate",
|
|
50
|
+
"mcp__plugin_roadmap_graph__backlog_list", "mcp__plugin_roadmap_graph__linear_status"
|
|
51
|
+
],
|
|
52
|
+
"ask": [
|
|
53
|
+
"mcp__plugin_roadmap_graph__set_fields", "mcp__plugin_roadmap_graph__bulk_set",
|
|
54
|
+
"mcp__plugin_roadmap_graph__backlog_add", "mcp__plugin_roadmap_graph__linear_sync"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 3 · Standalone MCP (Claude Desktop, other MCP clients, plugin-less Claude Code)
|
|
61
|
+
|
|
62
|
+
The server is one command: `node <tool-checkout>/scripts/mcp.mjs` (stdio JSON-RPC). Register it wherever your client takes MCP servers.
|
|
63
|
+
|
|
64
|
+
**Claude Code without the plugin** — in the consuming repo:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
claude mcp add graph -- node "C:/Users/you/Code/roadmap/scripts/mcp.mjs"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
(Repo discovery works because Claude Code runs servers with the project dir available. Don't do this *and* install the plugin — you'd get two servers.)
|
|
71
|
+
|
|
72
|
+
**Claude Desktop** (`claude_desktop_config.json`) — Desktop has no "current repo", so point the server at one explicitly via the env block. This is also where Desktop users put the Linear key, because GUI apps don't always inherit your shell profile:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"mcpServers": {
|
|
77
|
+
"roadmap-graph": {
|
|
78
|
+
"command": "node",
|
|
79
|
+
"args": ["C:/Users/you/Code/roadmap/scripts/mcp.mjs"],
|
|
80
|
+
"env": {
|
|
81
|
+
"CLAUDE_PROJECT_DIR": "C:/Users/you/Code/my-app",
|
|
82
|
+
"LINEAR_API_KEY": "lin_api_..."
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
> `claude_desktop_config.json` lives in your OS user profile and is never committed — a key here is machine-local, same trust level as an env var. **Never** put a key in a repo's `.mcp.json` or any committed file.
|
|
90
|
+
|
|
91
|
+
**Codex / anything else:** `npm run mcp` from the tool checkout (set `CODEX_PROJECT_DIR` or run with cwd inside the consuming repo).
|
|
92
|
+
|
|
93
|
+
## 4 · Linear
|
|
94
|
+
|
|
95
|
+
**Config** (committed, secret-free) — in the consuming repo's `roadmap.yaml`:
|
|
96
|
+
|
|
97
|
+
```yaml
|
|
98
|
+
meta:
|
|
99
|
+
linear:
|
|
100
|
+
team: ENG # push target
|
|
101
|
+
granularity: slices # pis | slices | slices+backlog
|
|
102
|
+
pull: propose # off | propose | auto
|
|
103
|
+
watch: # optional inbound sources
|
|
104
|
+
- { team: PUB, project: "Submit an issue", kind: bug, priority: { tier: P3 } }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Secret** (environment only) — `LINEAR_API_KEY`, a Linear personal API key (Linear → Settings → Security & access → Personal API keys):
|
|
108
|
+
|
|
109
|
+
| Where | How |
|
|
110
|
+
|---|---|
|
|
111
|
+
| Windows (persistent) | `[Environment]::SetEnvironmentVariable('LINEAR_API_KEY','<key>','User')` then a new shell |
|
|
112
|
+
| macOS / Linux | `echo 'export LINEAR_API_KEY=<key>' >> ~/.zshrc` (or `.bashrc`) |
|
|
113
|
+
| Claude Code plugin / CLI | nothing extra — both inherit the above |
|
|
114
|
+
| Claude Desktop | the `env` block shown in §3 |
|
|
115
|
+
| CI | repo/organization secret → exposed as `LINEAR_API_KEY` on the job |
|
|
116
|
+
|
|
117
|
+
**Bootstrap sequence** in a consuming repo:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
roadmap linear auth # prints the key instructions (never stores anything)
|
|
121
|
+
roadmap linear status --probe # confirms auth with one viewer query
|
|
122
|
+
roadmap linear setup --team ENG # queries your teams, writes meta.linear via the validated store
|
|
123
|
+
roadmap linear provision # labels + standard views + the two guidance texts to paste
|
|
124
|
+
roadmap linear sync --dry # shows the push plan + pull inbox, writes nothing
|
|
125
|
+
roadmap linear sync # projects the roadmap; /sync now includes the Linear phase
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`provision` also prints the **repo dispatch contract** — paste it into `CLAUDE.md`/`AGENTS.md` so cloud agents delegated a Linear issue (Claude Code coding sessions, Codex, Warp Oz) self-orient from the issue footer.
|
|
129
|
+
|
|
130
|
+
**Detection is graceful at every state** — the same sentence everywhere (hook, CLI, MCP):
|
|
131
|
+
|
|
132
|
+
| State | Behavior |
|
|
133
|
+
|---|---|
|
|
134
|
+
| No `meta.linear` | All Linear behavior off; tool is byte-identical to an unwired install |
|
|
135
|
+
| Configured, no key | One advisory line; everything else works; sync errors with the fix |
|
|
136
|
+
| Wired | `/sync` runs the Linear phase; hook reports team/pull/last-sync |
|
|
137
|
+
|
|
138
|
+
## 5 · Cloud dispatch (Claude Code Routines)
|
|
139
|
+
|
|
140
|
+
`roadmap dispatch <key>` / `roadmap fan --cloud` fire **Claude Code cloud sessions** directly via the Routines API — no Linear plan required, no local worktrees, bounded only by the firing account's Claude plan (Pro/Max/Team). ⚠ The fire endpoint is **beta** (`experimental-cc-routine-2026-04-01`); shapes may change.
|
|
141
|
+
|
|
142
|
+
**One-time routine setup (per claude.ai account, per repo):**
|
|
143
|
+
|
|
144
|
+
1. On claude.ai → **Code → Routines** (claude.ai/code/routines) → New routine.
|
|
145
|
+
2. Point it at the target **GitHub repo** (must be pushed/connected). Saved prompt — keep it generic; the dispatch capsule arrives as the fired text:
|
|
146
|
+
> You are a roadmap dispatch worker. The trigger message contains a machine capsule naming a slice — follow it exactly: read docs/SLICES.md and docs/roadmap/roadmap.yaml for the named slice, honor its gate, open a PR, never merge, leftovers to the backlog only.
|
|
147
|
+
3. Add an **API trigger** (save the routine first — the endpoint is generated after saving). The modal shows a **URL** (the `trig_…` id is embedded in it, never labeled separately) and a **Generate token** button — the token (`sk-ant-oat01-…`) is shown ONCE; copy it immediately. Use the whole URL as the `trigger` value — the tool accepts either the full URL or the bare `trig_…` id.
|
|
148
|
+
|
|
149
|
+
**Single-account:** put them in env — `CLAUDE_ROUTINE_TRIGGER` + `CLAUDE_ROUTINE_TOKEN`. Done.
|
|
150
|
+
|
|
151
|
+
**Multi-account on one workstation** (people swapping `claude /login` on the same OS user): each person creates the same routine under *their own* claude.ai account, and the pairs live in a machine-local **`~/.claude-routines.json`** (never committed; same trust level as env — override the path with `CLAUDE_ROUTINES_FILE`):
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"connor": {
|
|
156
|
+
"account": "connor@example.com",
|
|
157
|
+
"routines": {
|
|
158
|
+
"default": { "trigger": "trig_aaa", "token": "sk-ant-oat01-..." },
|
|
159
|
+
"acme/webapp": { "trigger": "trig_bbb", "token": "sk-ant-oat01-..." }
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"sam": {
|
|
163
|
+
"account": "sam@example.com",
|
|
164
|
+
"routines": { "default": { "trigger": "trig_ccc", "token": "sk-ant-oat01-..." } }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Resolution order (the hot-swap):** the env pair wins outright (CI/override) → `CLAUDE_ROUTINE_PROFILE=<name>` pins a profile explicitly → otherwise dispatch reads the **currently-authed claude.ai account** from the CLI's own config (`~/.claude.json → oauthAccount.emailAddress`) and matches it to a profile's `account`. Swap people with `claude /login`; the next dispatch fires on the new person's limits with zero config changes. Within a profile, the repo-specific routine (keyed `owner/repo` from the git remote) wins over `default`. Every miss is an actionable error naming the fix.
|
|
170
|
+
|
|
171
|
+
When the dispatched slice is also Linear-mapped and `LINEAR_API_KEY` is set, dispatch comments the session URL onto the issue — the board links to the live session. Best-effort: a comment failure never fails the dispatch.
|
|
172
|
+
|
|
173
|
+
## 6 · Jira (planned — not yet implemented)
|
|
174
|
+
|
|
175
|
+
Jira support is the designed follow-up and will mirror this layout exactly, so nothing about your deployment changes shape:
|
|
176
|
+
|
|
177
|
+
```yaml
|
|
178
|
+
meta:
|
|
179
|
+
jira: # PLANNED — does not work yet
|
|
180
|
+
project: ENG # push target project key
|
|
181
|
+
granularity: slices
|
|
182
|
+
pull: propose
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
with secrets in `JIRA_BASE_URL`, `JIRA_EMAIL`, `JIRA_API_TOKEN` (Atlassian API token), and a `roadmap jira status|setup|sync` family. The sync brain is already tracker-neutral; only the field maps and REST transport are Jira-specific. Until it ships, a `meta.jira` block is ignored and `roadmap validate` warns about it — don't add it yet.
|
|
186
|
+
|
|
187
|
+
**Why direct APIs instead of the Linear/Atlassian MCP servers?** The sync is a deterministic batch program (diffing, batching, cursors, idempotent re-runs) that must run headless from CLI/CI — MCP tools are built for a model in the loop, and the hosted servers authenticate with the *same* credential anyway, so routing through them adds a protocol layer without removing the key. Interactive agent work (chatting about issues, agent delegation from Linear/Jira) is exactly what those hosted MCP servers are for — pushed issues carry a machine footer so agents dispatched from them self-orient with one command.
|
|
188
|
+
|
|
189
|
+
## 7 · Troubleshooting
|
|
190
|
+
|
|
191
|
+
- `roadmap linear status` tells you which of the three states you're in and the exact next command.
|
|
192
|
+
- Plugin tools missing in a session → `/mcp` to reconnect, or restart the session after install.
|
|
193
|
+
- Two `graph` servers listed → you both installed the plugin and `claude mcp add`ed it; remove one.
|
|
194
|
+
- `Linear API HTTP 401` → key invalid/expired; re-issue and reset the env var.
|
|
195
|
+
- Upgrading from `slice-roadmap` ≤0.1.x → see README → *Upgrading* (permission allow-lists need rewriting to `mcp__plugin_roadmap_graph__*`).
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@connorbritain/roadmap",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Roadmap + backlog graph with prioritized, deterministic multi-session fanout. YAML canonical, SLICES.md/BACKLOG.md generated.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"author": "Connor England",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/ConnorBritain/roadmap.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/ConnorBritain/roadmap#readme",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18.15"
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"roadmap": "scripts/cli.mjs"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"scripts/",
|
|
22
|
+
"schema/",
|
|
23
|
+
"README.md",
|
|
24
|
+
"docs/DEPLOYMENT.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"cli": "node scripts/cli.mjs",
|
|
32
|
+
"plan": "node scripts/cli.mjs plan",
|
|
33
|
+
"show": "node scripts/cli.mjs show",
|
|
34
|
+
"fan": "node scripts/cli.mjs fan",
|
|
35
|
+
"cleanup": "node scripts/cli.mjs cleanup",
|
|
36
|
+
"validate": "node scripts/validate.mjs",
|
|
37
|
+
"render": "node scripts/render.mjs",
|
|
38
|
+
"mcp": "node scripts/mcp.mjs",
|
|
39
|
+
"prepack": "npm test",
|
|
40
|
+
"watch": "node scripts/watch-prs.mjs",
|
|
41
|
+
"test": "node scripts/test/run.mjs"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"yaml": "^2.5.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/ConnorBritain/roadmap/schema/backlog.schema.json",
|
|
4
|
+
"title": "roadmap backlog.yaml",
|
|
5
|
+
"description": "The erratic-work tracker beside the roadmap: follow-ups, bugs, chores, urgent items that surface outside the planned feature/value work. Items are directly launchable (roadmap grab <id>) or promotable into a roadmap sprint (roadmap promote <id> --pi <pi>). BACKLOG.md is generated from this file.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["meta", "items"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"meta": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"required": ["schema_version"],
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"properties": {
|
|
15
|
+
"schema_version": { "const": 1 }
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"items": {
|
|
19
|
+
"type": "array",
|
|
20
|
+
"items": { "$ref": "#/$defs/item" }
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"$defs": {
|
|
24
|
+
"item": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"required": ["id", "title", "kind", "status"],
|
|
27
|
+
"additionalProperties": false,
|
|
28
|
+
"properties": {
|
|
29
|
+
"id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$", "description": "Stable item slug (auto b1..bN when omitted at add). Becomes the sprint invoke key on promote and the grab branch backlog/<id>." },
|
|
30
|
+
"title": { "type": "string" },
|
|
31
|
+
"kind": { "enum": ["bug", "chore", "followup", "urgent", "idea"] },
|
|
32
|
+
"status": { "enum": ["open", "in_progress", "promoted", "done", "dropped"] },
|
|
33
|
+
"priority": { "$ref": "roadmap.schema.json#/$defs/priority" },
|
|
34
|
+
"source": {
|
|
35
|
+
"type": "object", "additionalProperties": false,
|
|
36
|
+
"description": "Where this surfaced: the slice that spawned it, an inbound Linear issue, when, and a note",
|
|
37
|
+
"properties": {
|
|
38
|
+
"slice": { "type": "string", "description": "Invoke key of the slice this fell out of" },
|
|
39
|
+
"date": { "type": "string", "description": "ISO date captured" },
|
|
40
|
+
"note": { "type": "string" },
|
|
41
|
+
"linear": {
|
|
42
|
+
"type": "object", "additionalProperties": false,
|
|
43
|
+
"description": "Origin demarcation for items captured from a watched Linear source — lets /prioritize weight by where work comes from",
|
|
44
|
+
"properties": {
|
|
45
|
+
"team": { "type": "string" },
|
|
46
|
+
"project": { "type": "string" },
|
|
47
|
+
"issue": { "type": "string" }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"linear": { "type": "string", "description": "Linear issue identifier this item maps to (set on push or on pull-capture)" },
|
|
53
|
+
"refs": { "type": "array", "items": { "type": "string" }, "description": "Related roadmap slice invoke keys" },
|
|
54
|
+
"touches": { "type": "array", "items": { "type": "string" }, "description": "Files this item writes (grab uses it for the brief scope)" },
|
|
55
|
+
"est_sessions": { "type": "number", "minimum": 0 },
|
|
56
|
+
"gate": { "type": "string", "description": "'default' inherits the roadmap's meta.default_gate" },
|
|
57
|
+
"prompt": { "type": "string", "description": "Author-stashed pickup instructions, embedded verbatim in the grab kickoff brief" },
|
|
58
|
+
"dispatch_tier": { "type": "string", "description": "Cloud-dispatch routine tier for this item (e.g. 'fable'). Dispatch resolves routines[\"<owner/repo>#<tier>\"] (or \"default#<tier>\") from ~/.claude-routines.json and THROWS if the tier isn't configured — never a silent fallback to the standard routine. Absent: the standard repo routine. CLI --tier overrides per launch." },
|
|
59
|
+
"prs": { "type": "array", "items": { "type": "string" } },
|
|
60
|
+
"completed_on": { "type": "string", "description": "ISO date the item closed (done/dropped)" },
|
|
61
|
+
"promoted_to": { "type": "string", "description": "pi-id/sprint-id nodeKey written by promote; the back-link to the roadmap" }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/ConnorBritain/roadmap/schema/roadmap.schema.json",
|
|
4
|
+
"title": "roadmap roadmap.yaml",
|
|
5
|
+
"description": "Canonical roadmap graph: PIs containing sprints, with dependency edges, file-ownership for two-wave detection, session estimates, gates, and per-sprint kickoff briefs. SLICES.md is generated from this file; do not edit the generated markdown.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["meta", "pis"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"meta": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"required": ["schema_version", "program"],
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"properties": {
|
|
15
|
+
"schema_version": { "const": 1 },
|
|
16
|
+
"program": { "type": "string", "description": "Active program label, e.g. Q3-PLATFORM" },
|
|
17
|
+
"north_star": { "type": "string", "description": "Path to the strategy / vision source of truth" },
|
|
18
|
+
"updated": { "type": "string", "description": "ISO date of last hand-edit" },
|
|
19
|
+
"default_gate": { "type": "string", "description": "Verification gate inherited by sprints whose gate is 'default' or interpolates {{default}}" },
|
|
20
|
+
"branch_convention": { "type": "string", "default": "{pi}/{sprint}" },
|
|
21
|
+
"worktree_root": { "type": "string", "description": "Parent dir for per-sprint worktrees; use a WSL/Linux-fs path, NOT a OneDrive-synced checkout" },
|
|
22
|
+
"worktree_gb": { "type": "number", "minimum": 0, "description": "Calibration knob for the disk ceiling: GB one worktree really costs (checkout + installs + build litter). Omitted → estimated from the tracked tree's size × 1.3" },
|
|
23
|
+
"terminal": { "enum": ["warp", "wt", "tmux", "iterm", "background", "print"], "default": "print", "description": "Default terminal adapter for /fanout launches" },
|
|
24
|
+
"default_concurrency": { "type": "integer", "minimum": 1, "default": 3 },
|
|
25
|
+
"worker_mode": { "enum": ["plan", "auto", "acceptEdits", "bypassPermissions"], "default": "plan", "description": "Default --permission-mode for launched worker + lead sessions; --worker-mode overrides. 'auto' = auto-approve tool/bash/MCP with safety checks (NOT a bypass); 'plan' = read-only research that gates edits behind plan approval." },
|
|
26
|
+
"agent_cmd": { "type": "string", "description": "Launch-command template for INTERACTIVE worker/lead sessions — swap the agent (e.g. a codex invocation). Tokens: {mode} = permission mode, {prompt} = the quoted kickoff prompt. Default: 'claude --permission-mode {mode} {prompt}'. Autonomous headless launches stay claude." },
|
|
27
|
+
"completed_window_days": { "type": "integer", "minimum": 1, "default": 14, "description": "How recently a sprint must have completed to appear in the Recently-completed table" },
|
|
28
|
+
"base_branch": { "type": "string", "default": "main", "description": "Branch worktrees are cut from + PR base" },
|
|
29
|
+
"remote": { "type": "string", "default": "origin", "description": "Git remote name for fetch + worktree base ref" },
|
|
30
|
+
"default_weight": { "enum": ["heavy", "medium", "light"], "description": "Weight assumed for a slice whose gate runner isn't recognized and that touches no code (default light)" },
|
|
31
|
+
"weight_patterns": {
|
|
32
|
+
"type": "object", "additionalProperties": false,
|
|
33
|
+
"description": "Extend the built-in multi-ecosystem runner classifier with repo-specific regexes (case-insensitive strings)",
|
|
34
|
+
"properties": {
|
|
35
|
+
"heavy": { "type": "array", "items": { "type": "string" } },
|
|
36
|
+
"medium": { "type": "array", "items": { "type": "string" } }
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"weight_cost": {
|
|
40
|
+
"type": "object", "additionalProperties": false,
|
|
41
|
+
"description": "Override per-class per-session resource cost used by the concurrency recommender",
|
|
42
|
+
"properties": {
|
|
43
|
+
"heavy": { "$ref": "#/$defs/cost" },
|
|
44
|
+
"medium": { "$ref": "#/$defs/cost" },
|
|
45
|
+
"light": { "$ref": "#/$defs/cost" }
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"initiatives": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"description": "Optional per-initiative Linear styling: maps an initiative NAME (matching a pi.initiative) to its board icon/color, so grouping reads as signal (every Lumen project a WritingAI icon, every Trust project a Shield) instead of the deterministic-but-arbitrary fallback palette. Applied to a declared initiative's projects AND its header; undeclared initiatives keep Linear's default header and use the fallback palette on their projects only.",
|
|
51
|
+
"additionalProperties": {
|
|
52
|
+
"type": "object", "additionalProperties": false,
|
|
53
|
+
"properties": {
|
|
54
|
+
"icon": { "type": "string", "description": "Linear icon name (e.g. Robot, Shield, Database, WritingAI). Names are a fixed Linear set; an unrecognized one degrades to no icon (color still groups)." },
|
|
55
|
+
"color": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$", "description": "Hex color like #5e6ad2." }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"plate": {
|
|
60
|
+
"type": "array",
|
|
61
|
+
"items": { "type": "string" },
|
|
62
|
+
"description": "The 'My Issues' hopper: an explicit list of slice invoke keys / backlog ids the Linear sync assigns to YOU (assignee), so Linear's My Issues tab = your curated current batch. Presence enables the feature (absent → no assignee projection at all). Active slices / in_progress items are ALWAYS included on top; completed slices auto-drain on sync. Capped for signal by meta.linear.plate_max. Issues we assign carry a 'plate' label so a hand-assignment in Linear is never disturbed."
|
|
63
|
+
},
|
|
64
|
+
"linear": {
|
|
65
|
+
"type": "object", "additionalProperties": false,
|
|
66
|
+
"required": ["team"],
|
|
67
|
+
"description": "Optional Linear integration. Absent → all Linear behavior off (byte-identical). Linear is a PROJECTION of this file plus a proposal inbox — the YAML stays canonical. Auth is the LINEAR_API_KEY env var, never stored here.",
|
|
68
|
+
"properties": {
|
|
69
|
+
"team": { "type": "string", "description": "Push-target team key (e.g. ENG)" },
|
|
70
|
+
"granularity": { "enum": ["pis", "slices", "slices+backlog"], "default": "slices", "description": "What projects to Linear: PIs as Projects only, +slices as Issues, +backlog items too. Per-PI override via pi.linear.granularity (ack-gated on conflict)." },
|
|
71
|
+
"horizon": { "enum": ["all", "near"], "default": "all", "description": "Issue-creation horizon: all = every not-done slice becomes an issue; near = scheduled/optionality slices stay YAML-only (no NEW issues) until promoted, so the board carries only actionable work. Already-mapped issues keep updating either way — never orphaned." },
|
|
72
|
+
"verbosity": { "enum": ["title", "brief", "full"], "default": "brief", "description": "Issue-description detail. Never copies read-order/prompt; always ends with the machine footer + docs link." },
|
|
73
|
+
"cycles": { "enum": ["off", "on"], "default": "off", "description": "Mirror the team's ACTIVE Linear cycle from slice status: active+next issues join the current cycle, demoted ones leave it. The cycle IS the elected weekly batch — promotion (scheduled→next) is the planning ritual's job. Requires cycles enabled on the Linear team; converges with Linear's native rollover/auto-add. With cycles on, dispatch/fan refuse out-of-cycle slices without --force." },
|
|
74
|
+
"cycle_capacity": { "type": "integer", "minimum": 1, "default": 10, "description": "est_sessions the cycle election packs per cycle ('roadmap cycle plan'). Unestimated slices are never silently packed; committed-but-unpriced work is flagged rather than counted as zero." },
|
|
75
|
+
"history": { "enum": ["off", "window", "full"], "default": "off", "description": "Whether DONE slices project as completed issues (with completedAt = completed_on). off = never (pre-history behavior); window = completed within meta.completed_window_days (default 14); full = all shipped work — what makes a project's progress % real and keeps the record queryable in Linear (auto-archive handles aging). Flipping to full on a large roadmap is a one-time backfill; an interrupted push resumes safely (ids write back incrementally)." },
|
|
76
|
+
"pull": { "enum": ["off", "propose", "auto"], "default": "off", "description": "Inbound handling: propose = /sync walks the inbox with you; auto = applied directly." },
|
|
77
|
+
"push_on": { "enum": ["sync", "manual"], "default": "sync", "description": "sync = /sync runs the Linear phase when authed; manual = only 'roadmap linear sync'." },
|
|
78
|
+
"estimate_max": { "type": "integer", "minimum": 1, "default": 5, "description": "Max value pushed to a slice's native Linear estimate (est_sessions is rounded, then clamped here). Set to your Linear team's estimate scale max — use the 'Linear' scale (1..5; extended adds 6,7) for a direct 1-point=1-session read. validate warns when a slice exceeds it: the signal to split it, not extend." },
|
|
79
|
+
"plate_max": { "type": "integer", "minimum": 1, "default": 7, "description": "Cap on the EXPLICIT meta.plate list — validate warns above it so Linear's My Issues stays signal (active work adds on top, uncapped)." },
|
|
80
|
+
"stale_days": { "type": "integer", "minimum": 1, "description": "Staleness threshold (days). Presence enables: committed work (active/next) whose latest journal comment (fallback: issue creation) is older than this gets a 'stale' label on sync, surfaces in the Stale view, and is reviewed FIRST by the cycle election. Basis is journal activity, never issue updatedAt (our own pushes would reset that clock)." },
|
|
81
|
+
"status_map": { "type": "object", "additionalProperties": { "type": "string" }, "description": "roadmap status → exact Linear workflow-state NAME overrides; unmapped statuses use the state-TYPE defaults (scheduled→backlog, next/blocked/paused/gated→unstarted, active→started, complete→completed). Only active means In Progress; held statuses read Todo + a status:<held> label." },
|
|
82
|
+
"watch": {
|
|
83
|
+
"type": "array",
|
|
84
|
+
"description": "Inbound sources (multiple teams/projects, e.g. a public 'Submit an issue' team). New issues become proposed backlog items carrying source.linear demarcation.",
|
|
85
|
+
"items": {
|
|
86
|
+
"type": "object", "additionalProperties": false, "required": ["team"],
|
|
87
|
+
"properties": {
|
|
88
|
+
"team": { "type": "string" },
|
|
89
|
+
"project": { "type": "string", "description": "Omit to watch the whole team" },
|
|
90
|
+
"capture": { "enum": ["backlog"], "default": "backlog" },
|
|
91
|
+
"kind": { "enum": ["bug", "chore", "followup", "urgent", "idea"], "default": "idea" },
|
|
92
|
+
"priority": { "$ref": "#/$defs/priority", "description": "Default priority for inbound items = implicit source weighting (the issue's own Linear priority wins when set)" }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"estimation": {
|
|
99
|
+
"type": "object", "additionalProperties": false,
|
|
100
|
+
"description": "Optional agent-time integration: turns calibrated per-slice duration estimates (from the agent-time estimator skill) into projected PI target dates. Absent → estimation off; slices with no `estimate` block simply don't feed the timeline. The shape/risk vocabulary is owned by agent-time (its SHAPES/RISKS tables validate them), not duplicated here.",
|
|
101
|
+
"properties": {
|
|
102
|
+
"engine": { "type": "string", "description": "Path to agent-time's estimator.py. Omit to resolve ~/.claude/skills/agent-time-estimator/estimator.py (or $AGENT_TIME_ENGINE)." },
|
|
103
|
+
"python": { "type": "string", "default": "python3", "description": "Python launcher for the estimator (the IO layer falls back to 'python' when 'python3' is absent)." },
|
|
104
|
+
"hours_per_day": { "type": "number", "exclusiveMinimum": 0, "default": 6, "description": "Productive agent wall-clock hours per calendar day — the rate that converts rolled-up slice minutes into a calendar span for projected_target_date." },
|
|
105
|
+
"point": { "enum": ["expected", "high"], "default": "expected", "description": "Which point of the low/expected/high estimate drives the projected date: 'expected' (realistic) or 'high' (under-promise)." },
|
|
106
|
+
"model": { "type": "string", "default": "opus-4.8", "description": "Model name passed to the estimator (drives its per-model pace + checkpoint horizon)." }
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"discipline": {
|
|
110
|
+
"type": "object", "additionalProperties": false,
|
|
111
|
+
"description": "Scope-discipline knobs read by the /sync and /debrief guardrails and the wave scheduler",
|
|
112
|
+
"properties": {
|
|
113
|
+
"capture_ratio": { "type": "number", "exclusiveMinimum": 0, "default": 2, "description": "Max (captured backlog items + added sprints) per completed slice per review window before the sprawl warning fires" },
|
|
114
|
+
"pi_min_slices": { "type": "integer", "minimum": 1, "description": "Composition floor: validate warns (one aggregated line) when a non-complete PI holds fewer slices than this. A PI under ~3 slices is usually a slice wearing a PI's coat — fold it into a sibling or grow it. Absent → off." },
|
|
115
|
+
"coherence": { "type": "boolean", "default": true, "description": "Wave-packing coherence: prefer finishing started PIs over opening fresh ones (strictly below declared priority). false restores pure priority/status/est ordering." }
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"last_review": {
|
|
119
|
+
"type": "object", "additionalProperties": false,
|
|
120
|
+
"required": ["date", "commit"],
|
|
121
|
+
"description": "The /debrief / /retro anchor: when the human last reviewed, and the roadmap.yaml git rev AT that review (captured before the review's own edits). Written by the skills via Edit+validate.",
|
|
122
|
+
"properties": {
|
|
123
|
+
"date": { "type": "string", "description": "ISO date the review closed" },
|
|
124
|
+
"commit": { "type": "string", "description": "git rev of the previous review's roadmap.yaml state" }
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"links": {
|
|
128
|
+
"type": "object", "additionalProperties": true,
|
|
129
|
+
"description": "Optional repo cross-references the renderer links from (all optional; omitted ones are skipped)",
|
|
130
|
+
"properties": {
|
|
131
|
+
"narrative": { "type": "string" },
|
|
132
|
+
"status": { "type": "string" },
|
|
133
|
+
"tracker": { "type": "string" },
|
|
134
|
+
"bootstrap": { "type": "string" },
|
|
135
|
+
"strategy": { "type": "string" }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"pis": {
|
|
141
|
+
"type": "array",
|
|
142
|
+
"items": { "$ref": "#/$defs/pi" }
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"$defs": {
|
|
146
|
+
"cost": {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"additionalProperties": false,
|
|
149
|
+
"description": "Per-session resource cost for a weight class",
|
|
150
|
+
"properties": {
|
|
151
|
+
"ram": { "type": "number", "description": "GB RAM per session" },
|
|
152
|
+
"cores": { "type": "number", "description": "CPU cores per session" }
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"status": {
|
|
156
|
+
"enum": ["active", "next", "scheduled", "complete", "blocked", "paused", "gated", "optionality"],
|
|
157
|
+
"description": "Canonical status. Emoji derived: active 🟢 · next 🟡 · scheduled ⚪ · complete ✅ · blocked 🔴 · paused ⏸️ · gated 🔒 · optionality 🟣"
|
|
158
|
+
},
|
|
159
|
+
"pi": {
|
|
160
|
+
"type": "object",
|
|
161
|
+
"required": ["id", "title", "status", "sprints"],
|
|
162
|
+
"additionalProperties": false,
|
|
163
|
+
"properties": {
|
|
164
|
+
"id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$", "description": "Stable PI slug; matches the branch-slug and the tracker row" },
|
|
165
|
+
"title": { "type": "string" },
|
|
166
|
+
"theme": { "type": "string" },
|
|
167
|
+
"program_label": { "type": "string", "description": "Display string for the 'PI / program' column when it differs from id (e.g. 'Billing Rewrite P2'). Optional; defaults to the uppercased id." },
|
|
168
|
+
"status": { "$ref": "#/$defs/status" },
|
|
169
|
+
"deps": { "type": "array", "items": { "type": "string" }, "description": "PI-level dependencies (PI ids)" },
|
|
170
|
+
"estimate_weeks": { "type": "string" },
|
|
171
|
+
"exit_criteria": { "type": "string" },
|
|
172
|
+
"detail": { "type": "string", "description": "Pointer to the sprint dir / detail doc" },
|
|
173
|
+
"exec_hint": { "type": ["string", "null"], "description": "Optional human override of the derived exec-plan line; rendered alongside the derived line" },
|
|
174
|
+
"initiative": { "type": "string", "description": "Groups this PI under a Linear Initiative of this name (the tier above projects). Sync creates the initiative and attaches this PI's project. Several PIs sharing a name group together." },
|
|
175
|
+
"priority": { "$ref": "#/$defs/priority", "description": "STRATEGIC priority of this bet (not derived from slice heat) → Linear project priority. tier P0=Urgent … P3=Low." },
|
|
176
|
+
"summary": { "type": "string", "maxLength": 255, "description": "Short one-line subtitle → Linear project description (the ≤255 field the board shows under the title). Authored so it never truncates; when omitted the subtitle is derived from the exit's first sentence and validate warns if that would truncate." },
|
|
177
|
+
"start_date": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$", "description": "Start date (YYYY-MM-DD) → Linear project startDate (drives the roadmap timeline). Set it explicitly, or leave blank and the sync auto-stamps it the first time the PI is active (≈ when it was picked up). Explicit always wins." },
|
|
178
|
+
"target_date": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$", "description": "Target date (YYYY-MM-DD) → Linear project targetDate. A deliberate commitment date; omit when there's no dated commitment." },
|
|
179
|
+
"projected_target_date": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$", "description": "DERIVED target date (YYYY-MM-DD) computed by 'roadmap estimate timeline' from rolled-up slice estimates — the estimate-driven timeline. Never overwrites an explicit target_date (which always wins); the Linear projection uses target_date || projected_target_date. Recomputed each run; don't hand-author." },
|
|
180
|
+
"linear": {
|
|
181
|
+
"type": "object", "additionalProperties": false,
|
|
182
|
+
"description": "Per-PI Linear override + the pushed-back project id. A granularity or verbosity conflicting with the global requires the yes_linear_override ack at creation; validate warns on stored mismatches.",
|
|
183
|
+
"properties": {
|
|
184
|
+
"granularity": { "enum": ["pis", "slices", "slices+backlog"] },
|
|
185
|
+
"verbosity": { "enum": ["title", "brief", "full"], "description": "Per-PI issue-description detail override — quiet a noisy PI to title, or richen an active one to full, without moving the global." },
|
|
186
|
+
"project": { "type": "string", "description": "Linear project id — written back by the first push; don't hand-author" }
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
"sprints": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/sprint" } }
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
"sprint": {
|
|
193
|
+
"type": "object",
|
|
194
|
+
"required": ["id", "title", "status", "invoke"],
|
|
195
|
+
"additionalProperties": false,
|
|
196
|
+
"properties": {
|
|
197
|
+
"id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$", "description": "Sprint slug local to its PI, e.g. s1, s3, m1-s2" },
|
|
198
|
+
"title": { "type": "string" },
|
|
199
|
+
"status": { "$ref": "#/$defs/status" },
|
|
200
|
+
"status_label": { "type": "string", "description": "Display override for the status cell (e.g. 'Planned', 'Queued', 'Gated (Connor)', 'Next'); defaults to the capitalized enum" },
|
|
201
|
+
"invoke": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$", "description": "The /slice invocation key. Stable — renames break references. Unique across the whole file." },
|
|
202
|
+
"what": { "type": "string", "description": "One-line summary for the At-a-glance table" },
|
|
203
|
+
"est_sessions": { "type": "number", "minimum": 0, "description": "Focused Claude sessions remaining to clear this sprint (0 when done)" },
|
|
204
|
+
"shape": { "type": "string", "description": "agent-time task shape (e.g. localized-feature, refactor, migration-schema) — the primary classifier the estimator prices from. One of agent-time's SHAPES; validated there, not enum-checked here (avoids cross-repo vocab drift). Set it (+ optional risks) to make a slice estimable via 'roadmap estimate'." },
|
|
205
|
+
"risks": { "type": "array", "items": { "type": "string" }, "description": "agent-time risk factors (e.g. external-api, slow-flaky-tests) that skew the estimate right. From agent-time's RISKS vocabulary; validated there." },
|
|
206
|
+
"estimate": {
|
|
207
|
+
"type": "object", "additionalProperties": false,
|
|
208
|
+
"description": "Cached agent-time estimate — written by 'roadmap estimate', read by the timeline rollup. Not hand-authored; refresh with 'roadmap estimate <slice> --force'.",
|
|
209
|
+
"properties": {
|
|
210
|
+
"minutes": { "type": "object", "additionalProperties": false, "properties": { "low": { "type": "number" }, "expected": { "type": "number" }, "high": { "type": "number" } }, "description": "Wall-clock estimate (low/expected/high minutes)." },
|
|
211
|
+
"confidence": { "type": "string", "description": "Estimator confidence label (never 'high' by design)." },
|
|
212
|
+
"task_id": { "type": "string", "description": "agent-time task id — links this slice to its history record for the calibration log." },
|
|
213
|
+
"at": { "type": "string", "description": "ISO timestamp the estimate was produced." },
|
|
214
|
+
"basis": { "type": "string", "description": "Calibration basis (which history bucket + spread the estimate drew on)." }
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
"weight": { "enum": ["heavy", "medium", "light"], "description": "Optional override of the inferred resource weight (heavy=dotnet test, medium=build/vitest, light=docs); drives the concurrency recommender" },
|
|
218
|
+
"deps": { "type": "array", "items": { "type": "string" }, "description": "Dependency edges: sibling sprint id (s2), fully-qualified pi-id/sprint-id, or a PI id (complete-when-all-sprints-complete)" },
|
|
219
|
+
"touches": { "type": "array", "items": { "type": "string" }, "description": "Files this sprint writes; used to detect shared-file contention (two-wave) in the scheduler" },
|
|
220
|
+
"owns": { "type": "array", "items": { "type": "string" }, "description": "Dirs/files this sprint exclusively owns" },
|
|
221
|
+
"gate": { "type": "string", "description": "'default' (inherit meta.default_gate) or a string; {{default}} interpolates meta.default_gate" },
|
|
222
|
+
"gated_on": { "type": "string", "description": "Name of the human this is gated on; such a node never enters the auto-ready set" },
|
|
223
|
+
"optional": { "type": "boolean", "default": false },
|
|
224
|
+
"read_order": { "type": "array", "items": { "type": "string" }, "description": "Ordered docs/sections a session reads to self-orient (markdown link or 'path — note')" },
|
|
225
|
+
"resume_action": { "type": "string", "description": "The next action; first sentence is the immediate step" },
|
|
226
|
+
"kickoff_brief": { "type": "string", "description": "'brief' = synthesize from fields; or an inline brief string; or a path to a brief file" },
|
|
227
|
+
"prs": { "type": "array", "items": { "type": "string" }, "description": "Merged PR refs, e.g. ['#359']" },
|
|
228
|
+
"completed_on": { "type": "string", "description": "ISO date a sprint flipped to complete (drives the Recently-completed window)" },
|
|
229
|
+
"track": { "type": "string", "description": "Optional lane label for the three-track partition (e.g. A | B | C). --track filters a fanout to one lane. Omit for an untracked slice." },
|
|
230
|
+
"execution": { "$ref": "#/$defs/execution" },
|
|
231
|
+
"priority": { "$ref": "#/$defs/priority" },
|
|
232
|
+
"prompt": { "type": "string", "description": "Author-stashed pickup instructions, embedded VERBATIM in the synthesized kickoff brief and shown by /slice and roadmap show. Update as new info comes in (set_fields / roadmap set)." },
|
|
233
|
+
"milestone": { "type": "string", "description": "Optional stage name within the PI's Linear project (e.g. 'harness', 'hardening', 'ship'). Sync creates the distinct milestones per project and attaches this slice's issue — turning a project's flat issue list into legible stages. Project-scoped: the same name under two PIs is two milestones. Opt-in; absent everywhere → no milestone sync." },
|
|
234
|
+
"dispatch_tier": { "type": "string", "description": "Cloud-dispatch routine tier for this slice (e.g. 'fable' = a routine whose claude.ai model selector is set to the frontier tier). Dispatch resolves routines[\"<owner/repo>#<tier>\"] (or \"default#<tier>\") from ~/.claude-routines.json and THROWS if the tier isn't configured — a frontier slice never silently falls back to the standard routine. Absent: the standard repo routine. CLI --tier overrides per launch." },
|
|
235
|
+
"linear": { "type": "string", "description": "Linear issue identifier (e.g. ABC-123) this slice maps to — written back by the first push. The {linear} branch_convention token uses it so Linear auto-links the PR." }
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
"priority": {
|
|
239
|
+
"type": "object",
|
|
240
|
+
"additionalProperties": false,
|
|
241
|
+
"description": "Optional prioritization: tier for coarse triage buckets, weight for fine ordering within a tier, reason so agents and humans know why. Sort order is derived (tier asc, weight desc), never stored. Shared by roadmap sprints and backlog items.",
|
|
242
|
+
"properties": {
|
|
243
|
+
"tier": { "enum": ["P0", "P1", "P2", "P3"] },
|
|
244
|
+
"weight": { "type": "number", "minimum": 0, "maximum": 100 },
|
|
245
|
+
"reason": { "type": "string" }
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
"execution": {
|
|
249
|
+
"type": "object",
|
|
250
|
+
"additionalProperties": false,
|
|
251
|
+
"description": "Per-slice staffing-strategy HINT: how to fan this slice out (topology + suggested LIVE worker count + team composition). All fields optional and backward-compatible — a slice that omits the block behaves exactly as before; the lead chooses by gut. Rendered as an imperative directive so a launched session staffs at the declared topology instead of chronically under-parallelizing.",
|
|
252
|
+
"properties": {
|
|
253
|
+
"mode": {
|
|
254
|
+
"enum": ["solo", "subagents", "dynamic-workflow", "agent-team"],
|
|
255
|
+
"description": "Fan-out topology. solo = atomic/exploratory/branching-sequential (no fan-out). subagents = scoped sprint, lead-merges + disjoint-file workers (the current default). dynamic-workflow = an in-slice pipeline whose steps depend on each other. agent-team = many genuinely-independent file-clusters + peer coordination (native Agent Teams)."
|
|
256
|
+
},
|
|
257
|
+
"concurrency": { "type": "integer", "minimum": 1, "description": "Suggested LIVE worker count" },
|
|
258
|
+
"min_concurrency": { "type": "integer", "minimum": 1, "description": "Floor — warn/refuse if a run uses fewer on disjoint files. Must be ≤ concurrency when both are set." },
|
|
259
|
+
"team": { "type": "array", "description": "Team composition; omit for solo. When concurrency is also set, the head-count must agree.", "items": { "$ref": "#/$defs/execRole" } },
|
|
260
|
+
"rationale": { "type": "string", "description": "Why this topology + count (surfaced verbatim in the directive)" }
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
"execRole": {
|
|
264
|
+
"type": "object",
|
|
265
|
+
"additionalProperties": false,
|
|
266
|
+
"required": ["role"],
|
|
267
|
+
"properties": {
|
|
268
|
+
"role": { "enum": ["verifier", "implementer", "reviewer", "researcher", "integrator"] },
|
|
269
|
+
"count": { "type": "integer", "minimum": 1, "default": 1, "description": "How many of this role (default 1)" }
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|