beacon-framework 1.2.2__tar.gz → 1.3.0__tar.gz

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 (85) hide show
  1. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/PKG-INFO +1 -1
  2. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/pyproject.toml +3 -1
  3. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/__init__.py +1 -1
  4. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/cli.py +22 -7
  5. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/manifest.py +6 -0
  6. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/beacon/roadmap.md +3 -1
  7. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/roadmap.py +36 -14
  8. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/.gitignore +0 -0
  9. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/BEACON.md +0 -0
  10. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/LICENSE +0 -0
  11. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/README.md +0 -0
  12. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/pragmatic-principles.md +0 -0
  13. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/__main__.py +0 -0
  14. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/bullet.py +0 -0
  15. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/docs.py +0 -0
  16. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/doctor.py +0 -0
  17. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/epic.py +0 -0
  18. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/installer.py +0 -0
  19. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/integrations/__init__.py +0 -0
  20. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/integrations/claude_code.py +0 -0
  21. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/integrations/release.py +0 -0
  22. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/language.py +0 -0
  23. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/beacon.md +0 -0
  24. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/help/commands.md +0 -0
  25. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/help/doctor.md +0 -0
  26. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/help/git-flow.md +0 -0
  27. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/help/phases.md +0 -0
  28. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/help/quickstart.md +0 -0
  29. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/CLAUDE.md.fragment +0 -0
  30. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/agents/beacon-reviewer.md +0 -0
  31. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/beacon/constitution.md +0 -0
  32. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/beacon/continue.md +0 -0
  33. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/beacon/implement.md +0 -0
  34. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/beacon/plan.md +0 -0
  35. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/beacon/prd.md +0 -0
  36. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/beacon/review.md +0 -0
  37. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/beacon/seed.md +0 -0
  38. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/beacon/specify.md +0 -0
  39. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/beacon/status.md +0 -0
  40. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/beacon/tasks.md +0 -0
  41. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/design/diagram.md +0 -0
  42. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/design/evaluate.md +0 -0
  43. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/design/wardley.md +0 -0
  44. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/git/feature.md +0 -0
  45. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/git/pr.md +0 -0
  46. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/git/release.gitflow.md +0 -0
  47. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/git/release.trunk.md +0 -0
  48. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/commands/init.md +0 -0
  49. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/settings.json +0 -0
  50. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/skills/beacon-build/SKILL.md +0 -0
  51. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/skills/beacon-design/SKILL.md +0 -0
  52. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/skills/beacon-seed/SKILL.md +0 -0
  53. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/claude_code/skills/beacon-ship/SKILL.md +0 -0
  54. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/release/PUBLISHING.gitflow.md +0 -0
  55. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/release/PUBLISHING.trunk.md +0 -0
  56. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/release/psr_config.gitflow.toml +0 -0
  57. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/release/psr_config.trunk.toml +0 -0
  58. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/release/workflows/release.gitflow.yml +0 -0
  59. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/integrations/release/workflows/release.trunk.yml +0 -0
  60. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/languages/node.toml +0 -0
  61. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/languages/python.toml +0 -0
  62. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/.beacon/doctor.toml.example +0 -0
  63. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/ADRs/ADR-000-template.md +0 -0
  64. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/ADRs/README.md +0 -0
  65. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/Background/00-problem-statement.md +0 -0
  66. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/Background/01-final-architecture-document.md +0 -0
  67. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/Roadmap/README.md +0 -0
  68. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/Roadmap/archive/.gitkeep +0 -0
  69. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/Roadmap/epics/.gitkeep +0 -0
  70. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/Roadmap/epics/EPIC-TEMPLATE.md +0 -0
  71. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/Roadmap/epics/archive/.gitkeep +0 -0
  72. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/Work/README.md +0 -0
  73. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/Work/analysis/.gitkeep +0 -0
  74. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/Work/branches/.gitkeep +0 -0
  75. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/Work/planning/.gitkeep +0 -0
  76. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/project-management/Work/sessions/.gitkeep +0 -0
  77. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/roadmap_template.html +0 -0
  78. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/user-facing-docs/README.md +0 -0
  79. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/user-facing-docs/explanation/index.md +0 -0
  80. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/user-facing-docs/how-to/index.md +0 -0
  81. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/user-facing-docs/reference/index.md +0 -0
  82. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/resources/user-facing-docs/tutorials/getting-started.md +0 -0
  83. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/seed.py +0 -0
  84. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/upgrader.py +0 -0
  85. {beacon_framework-1.2.2 → beacon_framework-1.3.0}/src/beacon/validate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: beacon-framework
3
- Version: 1.2.2
3
+ Version: 1.3.0
4
4
  Summary: Light-touch, pragmatic, artifact-driven framework for AI-assisted software delivery
5
5
  Project-URL: Repository, https://github.com/darth-veitcher/beacon
6
6
  Author: darth-veitcher
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "beacon-framework"
7
- version = "1.2.2"
7
+ version = "1.3.0"
8
8
  description = "Light-touch, pragmatic, artifact-driven framework for AI-assisted software delivery"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -77,6 +77,7 @@ Repository = "https://github.com/darth-veitcher/beacon"
77
77
 
78
78
 
79
79
 
80
+
80
81
 
81
82
 
82
83
  [tool.hatch.build.targets.wheel]
@@ -186,6 +187,7 @@ exclude_commit_patterns = ["^chore\\(release\\):"]
186
187
 
187
188
 
188
189
 
190
+
189
191
 
190
192
 
191
193
  [dependency-groups]
@@ -1,3 +1,3 @@
1
1
  """BEACON Framework: lifecycle and discipline for artifact-driven AI-assisted delivery."""
2
2
 
3
- __version__ = "1.2.2"
3
+ __version__ = "1.3.0"
@@ -1271,8 +1271,8 @@ def roadmap_export(
1271
1271
  stdout: bool = typer.Option(
1272
1272
  False, "--stdout", help="Print to stdout instead of writing a file."
1273
1273
  ),
1274
- include_done: bool = typer.Option(
1275
- False, "--include-done", help="Include Done epics in the Gantt chart."
1274
+ exclude_done: bool = typer.Option(
1275
+ False, "--exclude-done", help="Exclude Done/archived epics from the roadmap."
1276
1276
  ),
1277
1277
  ) -> None:
1278
1278
  """Generate ROADMAP.md — version-controlled Markdown + Mermaid source of truth.
@@ -1280,10 +1280,14 @@ def roadmap_export(
1280
1280
  Renders epics, spec progress, ADRs, and active bullets into a Mermaid-enhanced
1281
1281
  Markdown document. Safe to commit alongside beacon.md; regenerate on demand or
1282
1282
  wire into CI with ``beacon roadmap export``.
1283
+
1284
+ Done/archived epics are included by default (up to ``roadmap_done_limit`` in
1285
+ your manifest, defaulting to 3). Pass ``--exclude-done`` to suppress them.
1283
1286
  """
1284
1287
  from datetime import date
1285
1288
 
1286
1289
  from beacon import roadmap as _roadmap
1290
+ from beacon.manifest import Manifest
1287
1291
 
1288
1292
  root = _resolve_root(here, path)
1289
1293
  if not (root / "project-management").is_dir():
@@ -1293,9 +1297,11 @@ def roadmap_export(
1293
1297
  "[dim]beacon roadmap export /path/to/project[/dim]"
1294
1298
  )
1295
1299
  raise typer.Exit(1)
1300
+ _m = Manifest.load(root)
1301
+ done_limit = 0 if exclude_done else (_m.roadmap_done_limit if _m else 3)
1296
1302
  today = date.today().isoformat()
1297
1303
  data = _roadmap.collect(root)
1298
- content = _roadmap.render_markdown(data, today, include_done=include_done)
1304
+ content = _roadmap.render_markdown(data, today, done_limit=done_limit)
1299
1305
  if stdout:
1300
1306
  console.print(content)
1301
1307
  return
@@ -1303,13 +1309,13 @@ def roadmap_export(
1303
1309
  dest.write_text(content, encoding="utf-8")
1304
1310
  console.print(f"[green]✓[/green] Roadmap exported → [cyan]{dest.relative_to(root)}[/cyan]")
1305
1311
  if not data.epics:
1306
- if data.archived_epics and not include_done:
1312
+ if data.archived_epics and exclude_done:
1307
1313
  n = len(data.archived_epics)
1308
1314
  plural = "s" if n > 1 else ""
1309
1315
  console.print(
1310
1316
  f"[dim] ({n} archived epic{plural}"
1311
- " — run [/dim][dim cyan]beacon roadmap export --include-done"
1312
- "[/dim cyan][dim] to include completed work)[/dim]"
1317
+ " — remove [/dim][dim cyan]--exclude-done"
1318
+ "[/dim cyan][dim] to include recent completed work)[/dim]"
1313
1319
  )
1314
1320
  elif not data.archived_epics:
1315
1321
  console.print(
@@ -1325,6 +1331,9 @@ def roadmap_render(
1325
1331
  output: Path | None = typer.Option(
1326
1332
  None, "--output", "-o", help="Output path (default: ROADMAP.html in project root)."
1327
1333
  ),
1334
+ exclude_done: bool = typer.Option(
1335
+ False, "--exclude-done", help="Exclude Done/archived epics from the visualization."
1336
+ ),
1328
1337
  ) -> None:
1329
1338
  """Generate ROADMAP.html — Structural Breath interactive visualization (gitignored).
1330
1339
 
@@ -1332,10 +1341,14 @@ def roadmap_render(
1332
1341
  cards, active-bullet feed, and ADR index — all styled with the Structural Breath
1333
1342
  design system (dark, blueprint-grid, engineering-whitepaper aesthetic). Open
1334
1343
  directly in a browser, screenshot into a slide deck, or host as a static page.
1344
+
1345
+ Done/archived epics are included by default (up to ``roadmap_done_limit`` in
1346
+ your manifest, defaulting to 3). Pass ``--exclude-done`` to suppress them.
1335
1347
  """
1336
1348
  from datetime import date
1337
1349
 
1338
1350
  from beacon import roadmap as _roadmap
1351
+ from beacon.manifest import Manifest
1339
1352
 
1340
1353
  root = _resolve_root(here, path)
1341
1354
  if not (root / "project-management").is_dir():
@@ -1345,9 +1358,11 @@ def roadmap_render(
1345
1358
  "[dim]beacon roadmap render /path/to/project[/dim]"
1346
1359
  )
1347
1360
  raise typer.Exit(1)
1361
+ _m = Manifest.load(root)
1362
+ done_limit = 0 if exclude_done else (_m.roadmap_done_limit if _m else 3)
1348
1363
  today = date.today().isoformat()
1349
1364
  data = _roadmap.collect(root)
1350
- content = _roadmap.render_html(data, today)
1365
+ content = _roadmap.render_html(data, today, done_limit=done_limit)
1351
1366
  dest = output or (root / "ROADMAP.html")
1352
1367
  dest.write_text(content, encoding="utf-8")
1353
1368
  console.print(f"[green]✓[/green] Roadmap rendered → [cyan]{dest.relative_to(root)}[/cyan]")
@@ -50,6 +50,11 @@ class Manifest:
50
50
  # `--no-speckit` / declining the offer sets this False; pre-PR installs
51
51
  # without the field default False (no nag for users who never opted in).
52
52
  speckit_requested: bool = False
53
+ # How many of the most-recently-completed (archived) epics to include in
54
+ # roadmap output by default. 0 = exclude Done epics entirely; any positive
55
+ # integer caps the count. Use ``--exclude-done`` on the CLI to override to
56
+ # 0 for a single run without touching this value.
57
+ roadmap_done_limit: int = 3
53
58
 
54
59
  @classmethod
55
60
  def fresh(cls) -> Manifest:
@@ -71,6 +76,7 @@ class Manifest:
71
76
  hooks_enabled=bool(data.get("hooks_enabled", True)),
72
77
  git_flow=str(data.get("git_flow", "trunk")),
73
78
  speckit_requested=bool(data.get("speckit_requested", False)),
79
+ roadmap_done_limit=int(data.get("roadmap_done_limit", 3)),
74
80
  )
75
81
 
76
82
  def save(self, project_root: Path) -> Path:
@@ -17,6 +17,8 @@ Run:
17
17
  beacon roadmap export
18
18
  ```
19
19
 
20
+ This includes the most-recently-completed epics by default (controlled by `roadmap_done_limit` in the manifest, defaulting to 3). Pass `--exclude-done` to suppress Done/archived epics.
21
+
20
22
  If the user passed `render` as an argument (i.e. `$ARGUMENTS` contains the word `render`), also run:
21
23
  ```bash
22
24
  beacon roadmap render
@@ -26,7 +28,7 @@ beacon roadmap render
26
28
 
27
29
  Read the just-written `ROADMAP.md` and report concisely:
28
30
 
29
- - **Epic counts** by status: Active / Planning / Paused / Done
31
+ - **Epic counts** by status: Active / Planning / Paused / Done (including any archived epics shown)
30
32
  - **Fidelity** for each epic — show the badge (`S+/S? A+/A? T:N%`) and flag any that are `S?` (no specs) or `A?` (no ADRs)
31
33
  - **In-flight bullets** — how many worktrees have active bullets and which epics they're attached to
32
34
  - **Roadmap coverage** — fraction of Active epics that have both specs and ADRs filled in
@@ -208,6 +208,15 @@ def _parse_adr(root: Path, adr_path: str) -> AdrSummary:
208
208
  return AdrSummary(path=adr_path, token=token, title=title, status=status)
209
209
 
210
210
 
211
+ def _select_done_epics(archived: list[Epic], done_limit: int) -> list[Epic]:
212
+ """Return the N most-recently-started archived epics, sorted chronologically."""
213
+ if done_limit <= 0:
214
+ return []
215
+ by_recency = sorted(archived, key=lambda e: e.started or "0000-00-00", reverse=True)
216
+ selected = by_recency[:done_limit]
217
+ return sorted(selected, key=lambda e: e.started or "9999-99-99")
218
+
219
+
211
220
  def _mermaid_gantt_src(
212
221
  epics: list[Epic],
213
222
  rollups: dict[str, Rollup],
@@ -290,12 +299,13 @@ def _markdown_status_table(
290
299
  return "\n".join(rows)
291
300
 
292
301
 
293
- def _to_json_data(data: RoadmapData, generated_at: str, *, include_done: bool = False) -> dict:
302
+ def _to_json_data(data: RoadmapData, generated_at: str, *, done_limit: int = 3) -> dict:
294
303
  """Serialise RoadmapData to the shape embedded in ROADMAP.html."""
295
- all_epics = data.epics + (data.archived_epics if include_done else [])
304
+ done_epics = _select_done_epics(data.archived_epics, done_limit)
305
+ all_epics = data.epics + done_epics
296
306
  epics_list = []
297
307
  for e in all_epics:
298
- if not include_done and e.status == "Done":
308
+ if done_limit == 0 and e.status == "Done":
299
309
  continue
300
310
  roll = data.rollups.get(e.slug)
301
311
  f = data.fidelity.get(e.slug)
@@ -354,7 +364,7 @@ def _to_json_data(data: RoadmapData, generated_at: str, *, include_done: bool =
354
364
  # Build gantt_bars for D3
355
365
  gantt_bars = []
356
366
  for e in all_epics:
357
- if not include_done and e.status == "Done":
367
+ if done_limit == 0 and e.status == "Done":
358
368
  continue
359
369
  if not e.started:
360
370
  continue
@@ -452,7 +462,7 @@ def render_markdown(
452
462
  data: RoadmapData,
453
463
  generated_at: str,
454
464
  *,
455
- include_done: bool = False,
465
+ done_limit: int = 3,
456
466
  ) -> str:
457
467
  """Render ROADMAP.md content from collected data."""
458
468
  lines: list[str] = []
@@ -465,7 +475,8 @@ def render_markdown(
465
475
  lines.append(f"> {data.vision_excerpt}")
466
476
  lines.append("")
467
477
 
468
- visible_epics = data.epics + (data.archived_epics if include_done else [])
478
+ done_epics = _select_done_epics(data.archived_epics, done_limit)
479
+ visible_epics = data.epics + done_epics
469
480
 
470
481
  if not visible_epics:
471
482
  if data.archived_epics:
@@ -473,7 +484,8 @@ def render_markdown(
473
484
  plural = "s" if n > 1 else ""
474
485
  lines.append(
475
486
  f"_No active epics — {n} epic{plural} archived. "
476
- f"Run `beacon roadmap export --include-done` to include completed work._"
487
+ "Remove `--exclude-done` or raise `roadmap_done_limit` in your manifest"
488
+ " to include completed work._"
477
489
  )
478
490
  else:
479
491
  lines.append(
@@ -497,7 +509,7 @@ def render_markdown(
497
509
  lines.append("")
498
510
  lines.append("```mermaid")
499
511
  lines.append(
500
- _mermaid_gantt_src(visible_epics, data.rollups, generated_at, include_done=include_done)
512
+ _mermaid_gantt_src(visible_epics, data.rollups, generated_at, include_done=(done_limit > 0))
501
513
  )
502
514
  lines.append("```")
503
515
  lines.append("")
@@ -545,7 +557,7 @@ def render_markdown(
545
557
  return "\n".join(lines)
546
558
 
547
559
 
548
- def render_html(data: RoadmapData, generated_at: str) -> str:
560
+ def render_html(data: RoadmapData, generated_at: str, *, done_limit: int = 3) -> str:
549
561
  """Render ROADMAP.html by injecting JSON into the Structural Breath template."""
550
562
  from importlib import resources as _res
551
563
 
@@ -554,7 +566,7 @@ def render_html(data: RoadmapData, generated_at: str) -> str:
554
566
  )
555
567
  template = template_bytes.decode("utf-8")
556
568
 
557
- json_data = _to_json_data(data, generated_at)
569
+ json_data = _to_json_data(data, generated_at, done_limit=done_limit)
558
570
  # Use json.dumps with separators for compact embedding; escape </script> sequences
559
571
  json_str = json.dumps(json_data, ensure_ascii=False).replace("</script>", "<\\/script>")
560
572
 
@@ -568,26 +580,36 @@ def generate_markdown(
568
580
  root: Path,
569
581
  output: Path | None = None,
570
582
  *,
571
- include_done: bool = False,
583
+ done_limit: int | None = None,
572
584
  ) -> Path:
573
585
  """Collect data, render, and write ROADMAP.md. Returns the written path."""
574
586
  from datetime import date
575
587
 
588
+ from beacon.manifest import Manifest
589
+
590
+ if done_limit is None:
591
+ m = Manifest.load(root)
592
+ done_limit = m.roadmap_done_limit if m else 3
576
593
  today = date.today().isoformat()
577
594
  data = collect(root)
578
- content = render_markdown(data, today, include_done=include_done)
595
+ content = render_markdown(data, today, done_limit=done_limit)
579
596
  dest = output or (root / "ROADMAP.md")
580
597
  dest.write_text(content, encoding="utf-8")
581
598
  return dest
582
599
 
583
600
 
584
- def generate_html(root: Path, output: Path | None = None) -> Path:
601
+ def generate_html(root: Path, output: Path | None = None, *, done_limit: int | None = None) -> Path:
585
602
  """Collect data, render, and write ROADMAP.html. Returns the written path."""
586
603
  from datetime import date
587
604
 
605
+ from beacon.manifest import Manifest
606
+
607
+ if done_limit is None:
608
+ m = Manifest.load(root)
609
+ done_limit = m.roadmap_done_limit if m else 3
588
610
  today = date.today().isoformat()
589
611
  data = collect(root)
590
- content = render_html(data, today)
612
+ content = render_html(data, today, done_limit=done_limit)
591
613
  dest = output or (root / "ROADMAP.html")
592
614
  dest.write_text(content, encoding="utf-8")
593
615
  return dest