@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.
Files changed (53) hide show
  1. package/README.md +495 -0
  2. package/docs/DEPLOYMENT.md +195 -0
  3. package/package.json +46 -0
  4. package/schema/backlog.schema.json +65 -0
  5. package/schema/roadmap.schema.json +273 -0
  6. package/scripts/assistant.mjs +30 -0
  7. package/scripts/backlog.mjs +81 -0
  8. package/scripts/cleanup.mjs +69 -0
  9. package/scripts/cli.mjs +119 -0
  10. package/scripts/cycle.mjs +89 -0
  11. package/scripts/dispatch.mjs +302 -0
  12. package/scripts/estimate.mjs +201 -0
  13. package/scripts/fanout.mjs +355 -0
  14. package/scripts/grab.mjs +119 -0
  15. package/scripts/init.mjs +60 -0
  16. package/scripts/lib/assistant-core.mjs +64 -0
  17. package/scripts/lib/backlog-core.mjs +318 -0
  18. package/scripts/lib/brief.mjs +123 -0
  19. package/scripts/lib/cli-core.mjs +97 -0
  20. package/scripts/lib/cycle-core.mjs +58 -0
  21. package/scripts/lib/estimate-core.mjs +204 -0
  22. package/scripts/lib/execution.mjs +186 -0
  23. package/scripts/lib/fanout-core.mjs +39 -0
  24. package/scripts/lib/graph.mjs +346 -0
  25. package/scripts/lib/journal-core.mjs +48 -0
  26. package/scripts/lib/linear-core.mjs +812 -0
  27. package/scripts/lib/mcp-core.mjs +313 -0
  28. package/scripts/lib/plan.mjs +68 -0
  29. package/scripts/lib/plate-core.mjs +53 -0
  30. package/scripts/lib/pr-watch-core.mjs +72 -0
  31. package/scripts/lib/priority.mjs +43 -0
  32. package/scripts/lib/recommend.mjs +178 -0
  33. package/scripts/lib/render-core.mjs +215 -0
  34. package/scripts/lib/review-core.mjs +104 -0
  35. package/scripts/lib/store.mjs +113 -0
  36. package/scripts/lib/sync-core.mjs +89 -0
  37. package/scripts/lib/validate-core.mjs +130 -0
  38. package/scripts/lib/wizard-core.mjs +50 -0
  39. package/scripts/linear.mjs +780 -0
  40. package/scripts/mcp.mjs +193 -0
  41. package/scripts/next.mjs +43 -0
  42. package/scripts/plate.mjs +77 -0
  43. package/scripts/promote.mjs +27 -0
  44. package/scripts/prompt.mjs +124 -0
  45. package/scripts/render.mjs +39 -0
  46. package/scripts/review.mjs +79 -0
  47. package/scripts/scheduler.mjs +78 -0
  48. package/scripts/set.mjs +31 -0
  49. package/scripts/show.mjs +53 -0
  50. package/scripts/test/run.mjs +3943 -0
  51. package/scripts/validate.mjs +28 -0
  52. package/scripts/watch-prs.mjs +72 -0
  53. package/scripts/wizard.mjs +97 -0
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ // roadmap — wave scheduler CLI (print-only; spawns nothing).
3
+ // Builds the execution plan via lib/plan.mjs (recommended cap + waves) and prints it,
4
+ // or emits it as JSON (consumed by fanout.mjs / adapters / the MCP read tools).
5
+ //
6
+ // Usage:
7
+ // node scheduler.mjs [--in docs/roadmap/roadmap.yaml] [--cap N] [--json]
8
+ // [--use-free-ram] [--review-ceiling N] [--wave N]
9
+ // --cap N override the recommended cap
10
+ // --json emit the plan as JSON
11
+ // --use-free-ram size RAM off currently-free memory instead of 75% of total
12
+ // --wave N when printing, mark the launch detail for wave N (default 1)
13
+
14
+ import { loadGraph } from "./lib/graph.mjs";
15
+ import { buildPlan } from "./lib/plan.mjs";
16
+ import { baseRefOf, remoteOf } from "./lib/brief.mjs";
17
+ import { tierBadge } from "./lib/priority.mjs";
18
+
19
+ const args = process.argv.slice(2);
20
+ const val = (name, def) => {
21
+ const i = args.indexOf(name);
22
+ return i >= 0 && args[i + 1] && !args[i + 1].startsWith("--") ? args[i + 1] : def;
23
+ };
24
+ const has = (name) => args.includes(name);
25
+
26
+ const inPath = val("--in", "docs/roadmap/roadmap.yaml");
27
+ const asJson = has("--json");
28
+ const useFree = has("--use-free-ram");
29
+ const reviewCeiling = Number(val("--review-ceiling", 5));
30
+ const detailWave = Number(val("--wave", 1));
31
+
32
+ const hasCap = has("--cap");
33
+ const capVal = hasCap ? Number(val("--cap", "")) : null;
34
+
35
+ const graph = loadGraph(inPath);
36
+
37
+ let plan;
38
+ try {
39
+ plan = buildPlan(graph, { cap: hasCap && Number.isFinite(capVal) ? capVal : undefined, useFree, reviewCeiling });
40
+ } catch (e) {
41
+ console.error(`✗ ${e.message}`);
42
+ process.exit(1);
43
+ }
44
+
45
+ if (asJson) {
46
+ process.stdout.write(JSON.stringify(plan, null, 2) + "\n");
47
+ process.exit(0);
48
+ }
49
+
50
+ // ── human plan ─────────────────────────────────────────────────────────────
51
+ const capNote = hasCap ? `(you set --cap ${plan.cap}; recommended ${plan.recommended})` : `(recommended)`;
52
+ console.log(`Concurrency cap: ${plan.cap} ${capNote}`);
53
+ console.log(` bound by: ${plan.binding.why}`);
54
+ console.log(` machine: ${plan.sys.cores} cores, ${plan.sys.totalGb}GB total / ${plan.sys.freeGb}GB free (${plan.sys.platform})`);
55
+ console.log(` ceilings: ${plan.candidates.map((c) => `${c.n} [${c.why.split(" — ")[0]}]`).join(" · ")}`);
56
+ console.log("");
57
+ if (!plan.waves.length) console.log("No agent-runnable slices right now.");
58
+ plan.waves.forEach((w, i) => {
59
+ const marker = i + 1 === detailWave ? " ◀ detail" : "";
60
+ const closes = (plan.waveCloses && plan.waveCloses[i]) || [];
61
+ console.log(`Wave ${i + 1}${marker} — ${w.length} concurrent${closes.length ? ` (closes ${closes.join(", ")})` : ""}:`);
62
+ for (const n of w) console.log(` • ${tierBadge(n.priority) ? `[${tierBadge(n.priority)}] ` : ""}${n.invoke} (${n.weight}, ~${n.est_sessions ?? "?"} sess) — ${n.what}`);
63
+ });
64
+ if (plan.held.onHuman.length) {
65
+ console.log(`\nHeld on a human:`);
66
+ for (const n of plan.held.onHuman) console.log(` • ${n.invoke} — gated on ${n.gatedOn}`);
67
+ }
68
+
69
+ // Expand launch commands for the detail wave (print-only — copy/paste or feed to fanout).
70
+ const dw = plan.waves[detailWave - 1];
71
+ if (dw && dw.length) {
72
+ console.log(`\n--- Wave ${detailWave} launch (illustrative) — 'roadmap fan --wave ${detailWave}' does this AND writes each worktree's .kickoff.md (which the session reads) ---`);
73
+ console.log(`git fetch ${remoteOf(graph)} --quiet`);
74
+ for (const n of dw) {
75
+ console.log(`git worktree add "${n.worktree}" -b "${n.branch}" ${baseRefOf(graph)}`);
76
+ console.log(`(cd "${n.worktree}" && claude "${n.prompt}") # ${n.invoke}`);
77
+ }
78
+ }
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ // roadmap set <invoke> field=value [...] — edit one slice's fields from the shell.
3
+ // Values are YAML scalars/flow (deps=[s1,s2], priority='{tier: P1, weight: 60}'),
4
+ // field=@path reads the value from a file (multiline prompts/briefs), field=null deletes.
5
+ // Same allow-list + pre-write gate as the MCP set_fields tool.
6
+
7
+ import { readFileSync } from "node:fs";
8
+ import YAML from "yaml";
9
+ import { parseAssignments } from "./lib/cli-core.mjs";
10
+ import { mutateRoadmap } from "./lib/store.mjs";
11
+ import { setFields } from "./lib/mcp-core.mjs";
12
+
13
+ const args = process.argv.slice(2);
14
+ const invoke = args.find((a) => !a.includes("=") && !a.startsWith("-"));
15
+ const assigns = args.filter((a) => a.includes("="));
16
+ if (!invoke || !assigns.length) {
17
+ console.error("usage: roadmap set <invoke> field=value [field2=value2 ...] (field=@file reads a file; field=null deletes)");
18
+ process.exit(2);
19
+ }
20
+
21
+ try {
22
+ const fields = {};
23
+ for (const a of parseAssignments(assigns)) {
24
+ fields[a.field] = a.fromFile !== undefined ? readFileSync(a.fromFile, "utf8") : YAML.parse(a.raw);
25
+ }
26
+ const r = mutateRoadmap(process.cwd(), (doc) => setFields(doc, { invoke, fields }));
27
+ console.log(`✓ ${invoke}: set ${r.fields.join(", ")} (re-rendered ${r.rerendered})`);
28
+ } catch (e) {
29
+ console.error(`✗ ${e.message}`);
30
+ process.exit(1);
31
+ }
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ // roadmap show <invoke> — print one slice's detail (what / deps / read-order / next / gate
3
+ // + branch/worktree), for /slice orientation. Read-only.
4
+
5
+ import { loadGraph, flatten, statusDisplay, resolveGate } from "./lib/graph.mjs";
6
+ import { branchFor, worktreeFor } from "./lib/brief.mjs";
7
+ import { executionDirectiveLines } from "./lib/execution.mjs";
8
+
9
+ const args = process.argv.slice(2);
10
+ const val = (n, d) => { const i = args.indexOf(n); return i >= 0 && args[i + 1] && !args[i + 1].startsWith("--") ? args[i + 1] : d; };
11
+ const inPath = val("--in", "docs/roadmap/roadmap.yaml");
12
+ const invoke = args.find((a) => !a.startsWith("--"));
13
+
14
+ if (!invoke) { console.error("usage: roadmap show <slice-invoke>"); process.exit(2); }
15
+
16
+ const graph = loadGraph(inPath);
17
+ const model = flatten(graph);
18
+ const node = model.nodes.find((n) => n.invoke === invoke);
19
+ if (!node) {
20
+ const names = model.nodes.map((n) => n.invoke).sort();
21
+ console.error(`roadmap: no slice "${invoke}". Available:\n ${names.join("\n ")}`);
22
+ process.exit(2);
23
+ }
24
+
25
+ const gate = resolveGate(node, graph).replace(/\{\{\s*default\s*\}\}/gi, "default gate").trim();
26
+ const deps = [...node.deps.map((d) => d.split("/")[1].toUpperCase()), ...node.piDeps.map((p) => p.toUpperCase())];
27
+ const out = [];
28
+ out.push(`Slice: ${node.invoke} [${statusDisplay(node.status, node.statusLabel)}]`);
29
+ out.push(`PI: ${node.programLabel} · ${node.id.toUpperCase()}${node.estSessions != null ? ` (~${node.estSessions} sessions)` : ""}`);
30
+ out.push(`What: ${node.what}`);
31
+ if (node.priority) {
32
+ const parts = [node.priority.tier, node.priority.weight != null ? `weight ${node.priority.weight}` : null].filter(Boolean).join(" · ");
33
+ out.push(`Priority: ${parts || "(set)"}${node.priority.reason ? ` — ${node.priority.reason}` : ""}`);
34
+ }
35
+ // Execution directive at the top of the read-out (only when the slice declares one).
36
+ const execLines = executionDirectiveLines(node);
37
+ if (execLines) { out.push(""); execLines.forEach((l) => out.push(l)); }
38
+ if (deps.length) out.push(`Deps: ${deps.join(", ")}`);
39
+ out.push(`Branch: ${branchFor(node, graph)}`);
40
+ out.push(`Worktree: ${worktreeFor(node, graph)}`);
41
+ if (node.gatedOn) out.push(`Gated on: ${node.gatedOn} — an agent prepares; it does NOT perform the gate.`);
42
+ out.push("");
43
+ out.push("Read-order:");
44
+ (node.readOrder.length ? node.readOrder : ["(none listed — see the PI's detail dir)"]).forEach((r, i) => out.push(` ${i + 1}. ${r}`));
45
+ out.push("");
46
+ out.push(`Next: ${node.resumeAction ? node.resumeAction.trim() : "(see read-order)"}`);
47
+ if (node.prompt) {
48
+ out.push("");
49
+ out.push("Prompt (author instructions, verbatim):");
50
+ node.prompt.trimEnd().split("\n").forEach((l) => out.push(` ${l}`));
51
+ }
52
+ out.push(`Gate: ${gate}`);
53
+ console.log(out.join("\n"));