beacon-framework 1.2.0__tar.gz → 1.2.2__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.0 → beacon_framework-1.2.2}/PKG-INFO +1 -1
  2. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/pyproject.toml +5 -1
  3. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/__init__.py +1 -1
  4. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/cli.py +19 -12
  5. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/epic.py +2 -2
  6. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/roadmap.py +74 -43
  7. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/.gitignore +0 -0
  8. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/BEACON.md +0 -0
  9. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/LICENSE +0 -0
  10. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/README.md +0 -0
  11. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/pragmatic-principles.md +0 -0
  12. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/__main__.py +0 -0
  13. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/bullet.py +0 -0
  14. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/docs.py +0 -0
  15. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/doctor.py +0 -0
  16. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/installer.py +0 -0
  17. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/integrations/__init__.py +0 -0
  18. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/integrations/claude_code.py +0 -0
  19. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/integrations/release.py +0 -0
  20. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/language.py +0 -0
  21. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/manifest.py +0 -0
  22. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/beacon.md +0 -0
  23. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/help/commands.md +0 -0
  24. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/help/doctor.md +0 -0
  25. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/help/git-flow.md +0 -0
  26. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/help/phases.md +0 -0
  27. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/help/quickstart.md +0 -0
  28. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/CLAUDE.md.fragment +0 -0
  29. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/agents/beacon-reviewer.md +0 -0
  30. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/beacon/constitution.md +0 -0
  31. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/beacon/continue.md +0 -0
  32. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/beacon/implement.md +0 -0
  33. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/beacon/plan.md +0 -0
  34. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/beacon/prd.md +0 -0
  35. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/beacon/review.md +0 -0
  36. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/beacon/roadmap.md +0 -0
  37. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/beacon/seed.md +0 -0
  38. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/beacon/specify.md +0 -0
  39. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/beacon/status.md +0 -0
  40. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/beacon/tasks.md +0 -0
  41. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/design/diagram.md +0 -0
  42. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/design/evaluate.md +0 -0
  43. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/design/wardley.md +0 -0
  44. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/git/feature.md +0 -0
  45. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/git/pr.md +0 -0
  46. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/git/release.gitflow.md +0 -0
  47. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/git/release.trunk.md +0 -0
  48. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/commands/init.md +0 -0
  49. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/settings.json +0 -0
  50. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/skills/beacon-build/SKILL.md +0 -0
  51. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/skills/beacon-design/SKILL.md +0 -0
  52. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/skills/beacon-seed/SKILL.md +0 -0
  53. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/claude_code/skills/beacon-ship/SKILL.md +0 -0
  54. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/release/PUBLISHING.gitflow.md +0 -0
  55. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/release/PUBLISHING.trunk.md +0 -0
  56. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/release/psr_config.gitflow.toml +0 -0
  57. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/release/psr_config.trunk.toml +0 -0
  58. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/release/workflows/release.gitflow.yml +0 -0
  59. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/integrations/release/workflows/release.trunk.yml +0 -0
  60. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/languages/node.toml +0 -0
  61. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/languages/python.toml +0 -0
  62. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/.beacon/doctor.toml.example +0 -0
  63. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/ADRs/ADR-000-template.md +0 -0
  64. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/ADRs/README.md +0 -0
  65. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/Background/00-problem-statement.md +0 -0
  66. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/Background/01-final-architecture-document.md +0 -0
  67. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/Roadmap/README.md +0 -0
  68. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/Roadmap/archive/.gitkeep +0 -0
  69. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/Roadmap/epics/.gitkeep +0 -0
  70. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/Roadmap/epics/EPIC-TEMPLATE.md +0 -0
  71. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/Roadmap/epics/archive/.gitkeep +0 -0
  72. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/Work/README.md +0 -0
  73. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/Work/analysis/.gitkeep +0 -0
  74. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/Work/branches/.gitkeep +0 -0
  75. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/Work/planning/.gitkeep +0 -0
  76. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/project-management/Work/sessions/.gitkeep +0 -0
  77. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/roadmap_template.html +0 -0
  78. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/user-facing-docs/README.md +0 -0
  79. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/user-facing-docs/explanation/index.md +0 -0
  80. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/user-facing-docs/how-to/index.md +0 -0
  81. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/user-facing-docs/reference/index.md +0 -0
  82. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/resources/user-facing-docs/tutorials/getting-started.md +0 -0
  83. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/seed.py +0 -0
  84. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/src/beacon/upgrader.py +0 -0
  85. {beacon_framework-1.2.0 → beacon_framework-1.2.2}/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.0
3
+ Version: 1.2.2
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.0"
7
+ version = "1.2.2"
8
8
  description = "Light-touch, pragmatic, artifact-driven framework for AI-assisted software delivery"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -74,6 +74,8 @@ Repository = "https://github.com/darth-veitcher/beacon"
74
74
 
75
75
 
76
76
 
77
+
78
+
77
79
 
78
80
 
79
81
 
@@ -181,6 +183,8 @@ exclude_commit_patterns = ["^chore\\(release\\):"]
181
183
 
182
184
 
183
185
 
186
+
187
+
184
188
 
185
189
 
186
190
 
@@ -1,3 +1,3 @@
1
1
  """BEACON Framework: lifecycle and discipline for artifact-driven AI-assisted delivery."""
2
2
 
3
- __version__ = "1.2.0"
3
+ __version__ = "1.2.2"
@@ -1281,9 +1281,10 @@ def roadmap_export(
1281
1281
  Markdown document. Safe to commit alongside beacon.md; regenerate on demand or
1282
1282
  wire into CI with ``beacon roadmap export``.
1283
1283
  """
1284
- from beacon import roadmap as _roadmap
1285
1284
  from datetime import date
1286
1285
 
1286
+ from beacon import roadmap as _roadmap
1287
+
1287
1288
  root = _resolve_root(here, path)
1288
1289
  if not (root / "project-management").is_dir():
1289
1290
  console.print(f"[red]✗[/red] Not a BEACON project at [cyan]{root}[/cyan].")
@@ -1300,14 +1301,21 @@ def roadmap_export(
1300
1301
  return
1301
1302
  dest = output or (root / "ROADMAP.md")
1302
1303
  dest.write_text(content, encoding="utf-8")
1303
- console.print(
1304
- f"[green]✓[/green] Roadmap exported → [cyan]{dest.relative_to(root)}[/cyan]"
1305
- )
1304
+ console.print(f"[green]✓[/green] Roadmap exported → [cyan]{dest.relative_to(root)}[/cyan]")
1306
1305
  if not data.epics:
1307
- console.print(
1308
- f"[dim] (project-management/Roadmap/epics/ has no epics yet"
1309
- f" — run [/dim][dim cyan]beacon epic new <slug>[/dim cyan][dim])[/dim]"
1310
- )
1306
+ if data.archived_epics and not include_done:
1307
+ n = len(data.archived_epics)
1308
+ plural = "s" if n > 1 else ""
1309
+ console.print(
1310
+ 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]"
1313
+ )
1314
+ elif not data.archived_epics:
1315
+ console.print(
1316
+ "[dim] (project-management/Roadmap/epics/ has no epics yet"
1317
+ " — run [/dim][dim cyan]beacon epic new <slug>[/dim cyan][dim])[/dim]"
1318
+ )
1311
1319
 
1312
1320
 
1313
1321
  @roadmap_app.command("render")
@@ -1325,9 +1333,10 @@ def roadmap_render(
1325
1333
  design system (dark, blueprint-grid, engineering-whitepaper aesthetic). Open
1326
1334
  directly in a browser, screenshot into a slide deck, or host as a static page.
1327
1335
  """
1328
- from beacon import roadmap as _roadmap
1329
1336
  from datetime import date
1330
1337
 
1338
+ from beacon import roadmap as _roadmap
1339
+
1331
1340
  root = _resolve_root(here, path)
1332
1341
  if not (root / "project-management").is_dir():
1333
1342
  console.print(f"[red]✗[/red] Not a BEACON project at [cyan]{root}[/cyan].")
@@ -1341,9 +1350,7 @@ def roadmap_render(
1341
1350
  content = _roadmap.render_html(data, today)
1342
1351
  dest = output or (root / "ROADMAP.html")
1343
1352
  dest.write_text(content, encoding="utf-8")
1344
- console.print(
1345
- f"[green]✓[/green] Roadmap rendered → [cyan]{dest.relative_to(root)}[/cyan]"
1346
- )
1353
+ console.print(f"[green]✓[/green] Roadmap rendered → [cyan]{dest.relative_to(root)}[/cyan]")
1347
1354
  console.print(
1348
1355
  "[dim]i[/dim] Add [dim]ROADMAP.html[/dim] to [dim].gitignore[/dim] "
1349
1356
  "to keep it out of version control."
@@ -162,8 +162,8 @@ def _extract_list_under_header(content: str, header: str) -> list[str]:
162
162
  if in_section:
163
163
  m = _LIST_ITEM_RE.match(line)
164
164
  if m and not m.group("body").startswith("<!--"):
165
- # Strip trailing " — description" so we get just the path / file ref
166
- body = m.group("body").split(" —", 1)[0].split(" --", 1)[0].strip()
165
+ # Strip trailing " — description" (em dash with any surrounding spaces)
166
+ body = m.group("body").split("—", 1)[0].split(" --", 1)[0].strip()
167
167
  items.append(body.rstrip("/"))
168
168
  return items
169
169
 
@@ -72,6 +72,7 @@ class RoadmapData:
72
72
  fidelity: dict[str, EpicFidelity]
73
73
  adrs: list[AdrSummary]
74
74
  in_flight_bullets: list # list[Bullet] — avoid circular import
75
+ archived_epics: list[Epic] = field(default_factory=list)
75
76
 
76
77
 
77
78
  # ---------------------------------------------------------------------------
@@ -168,10 +169,10 @@ def _task_pct(root: Path, epic: Epic) -> int | None:
168
169
 
169
170
 
170
171
  def _fidelity_badge(f: EpicFidelity) -> str:
171
- """Return ASCII fidelity string like 'S+ A+ T:67%' or 'S? A? T:–'."""
172
+ """Return ASCII fidelity string like 'S+ A+ T:67%' or 'S? A? T:-'."""
172
173
  spec = "S+" if f.has_specs else "S?"
173
174
  adr = "A+" if f.has_adrs else "A?"
174
- task = f"T:{f.task_pct}%" if f.task_pct is not None else "T:–"
175
+ task = f"T:{f.task_pct}%" if f.task_pct is not None else "T:-"
175
176
  return f"{spec} {adr} {task}"
176
177
 
177
178
 
@@ -257,9 +258,7 @@ def _mermaid_gantt_src(
257
258
  else:
258
259
  tag = ""
259
260
  safe_title = e.title.replace(":", " -").replace(",", " ")
260
- lines.append(
261
- f" {safe_title} :{tag}{e.slug}, {start}, {dur}d"
262
- )
261
+ lines.append(f" {safe_title} :{tag}{e.slug}, {start}, {dur}d")
263
262
  lines.append("")
264
263
 
265
264
  return "\n".join(lines)
@@ -273,7 +272,11 @@ def _markdown_status_table(
273
272
  """Render the epic status Markdown table."""
274
273
  rows = ["| Epic | Title | Status | Specs | Fidelity |", "|---|---|---|---|---|"]
275
274
  for e in epics:
276
- link = f"[{e.slug}](project-management/Roadmap/epics/{e.slug}.md)"
275
+ if e.path.parent.name == "archive":
276
+ path_rel = f"project-management/Roadmap/epics/archive/{e.slug}.md"
277
+ else:
278
+ path_rel = f"project-management/Roadmap/epics/{e.slug}.md"
279
+ link = f"[{e.slug}]({path_rel})"
277
280
  roll = rollups.get(e.slug)
278
281
  if roll and roll.total > 0:
279
282
  specs_col = f"{len(roll.shipped)}/{roll.total} shipped"
@@ -289,33 +292,36 @@ def _markdown_status_table(
289
292
 
290
293
  def _to_json_data(data: RoadmapData, generated_at: str, *, include_done: bool = False) -> dict:
291
294
  """Serialise RoadmapData to the shape embedded in ROADMAP.html."""
295
+ all_epics = data.epics + (data.archived_epics if include_done else [])
292
296
  epics_list = []
293
- for e in data.epics:
297
+ for e in all_epics:
294
298
  if not include_done and e.status == "Done":
295
299
  continue
296
300
  roll = data.rollups.get(e.slug)
297
301
  f = data.fidelity.get(e.slug)
298
302
  dur = max(7, (len(roll.in_flight) + len(roll.missing)) * 7) if roll else 7
299
- epics_list.append({
300
- "slug": e.slug,
301
- "title": e.title,
302
- "status": e.status,
303
- "started": e.started or "",
304
- "specs_total": roll.total if roll else 0,
305
- "specs_shipped": len(roll.shipped) if roll else 0,
306
- "specs_in_flight": len(roll.in_flight) if roll else 0,
307
- "has_adrs": f.has_adrs if f else False,
308
- "adr_count": f.adr_count if f else 0,
309
- "task_pct": f.task_pct if f else None,
310
- "fidelity_badge": _fidelity_badge(f) if f else "—",
311
- "duration_days": dur,
312
- })
303
+ epics_list.append(
304
+ {
305
+ "slug": e.slug,
306
+ "title": e.title,
307
+ "status": e.status,
308
+ "started": e.started or "",
309
+ "specs_total": roll.total if roll else 0,
310
+ "specs_shipped": len(roll.shipped) if roll else 0,
311
+ "specs_in_flight": len(roll.in_flight) if roll else 0,
312
+ "has_adrs": f.has_adrs if f else False,
313
+ "adr_count": f.adr_count if f else 0,
314
+ "task_pct": f.task_pct if f else None,
315
+ "fidelity_badge": _fidelity_badge(f) if f else "—",
316
+ "duration_days": dur,
317
+ }
318
+ )
313
319
 
314
320
  # Deduplicate ADRs and attach which epics reference each
315
321
  adr_epics: dict[str, list[str]] = {}
316
322
  for adr in data.adrs:
317
323
  adr_epics.setdefault(adr.token, [])
318
- for e in data.epics:
324
+ for e in all_epics:
319
325
  for adr_path in e.adrs:
320
326
  m = _ADR_TOKEN_RE.search(adr_path)
321
327
  if m:
@@ -347,20 +353,22 @@ def _to_json_data(data: RoadmapData, generated_at: str, *, include_done: bool =
347
353
 
348
354
  # Build gantt_bars for D3
349
355
  gantt_bars = []
350
- for e in data.epics:
356
+ for e in all_epics:
351
357
  if not include_done and e.status == "Done":
352
358
  continue
353
359
  if not e.started:
354
360
  continue
355
361
  roll = data.rollups.get(e.slug)
356
362
  dur = max(7, (len(roll.in_flight) + len(roll.missing)) * 7) if roll else 7
357
- gantt_bars.append({
358
- "slug": e.slug,
359
- "title": e.title,
360
- "status": e.status,
361
- "started": e.started,
362
- "duration_days": dur,
363
- })
363
+ gantt_bars.append(
364
+ {
365
+ "slug": e.slug,
366
+ "title": e.title,
367
+ "status": e.status,
368
+ "started": e.started,
369
+ "duration_days": dur,
370
+ }
371
+ )
364
372
 
365
373
  return {
366
374
  "project_name": data.project_name,
@@ -384,13 +392,14 @@ def collect(root: Path) -> RoadmapData:
384
392
  from beacon import epic as _epic
385
393
 
386
394
  epics = _epic.list_active(root)
395
+ archived = _epic.list_archived(root)
387
396
  rollups: dict[str, Rollup] = {}
388
397
  fidelity_map: dict[str, EpicFidelity] = {}
389
398
 
390
- # Collect ADRs deduplicating across all epics
399
+ # Collect ADRs deduplicating across all epics (active + archived)
391
400
  seen_adrs: dict[str, AdrSummary] = {} # token → summary
392
401
 
393
- for e in epics:
402
+ for e in epics + archived:
394
403
  try:
395
404
  roll = _epic.rollup(root, e)
396
405
  except Exception:
@@ -435,6 +444,7 @@ def collect(root: Path) -> RoadmapData:
435
444
  fidelity=fidelity_map,
436
445
  adrs=adrs,
437
446
  in_flight_bullets=bullets,
447
+ archived_epics=archived,
438
448
  )
439
449
 
440
450
 
@@ -455,27 +465,51 @@ def render_markdown(
455
465
  lines.append(f"> {data.vision_excerpt}")
456
466
  lines.append("")
457
467
 
458
- if not data.epics:
459
- lines.append(
460
- "_No epics declared yet. Run `beacon epic new <slug> --title \"<title>\"` to start._"
461
- )
468
+ visible_epics = data.epics + (data.archived_epics if include_done else [])
469
+
470
+ if not visible_epics:
471
+ if data.archived_epics:
472
+ n = len(data.archived_epics)
473
+ plural = "s" if n > 1 else ""
474
+ lines.append(
475
+ f"_No active epics — {n} epic{plural} archived. "
476
+ f"Run `beacon roadmap export --include-done` to include completed work._"
477
+ )
478
+ else:
479
+ lines.append(
480
+ '_No epics declared yet. Run `beacon epic new <slug> --title "<title>"` to start._'
481
+ )
482
+ # Still render ADR section if there are any (e.g. from archived epics)
483
+ if data.adrs:
484
+ lines.append("")
485
+ lines.append("## Architectural Decisions")
486
+ lines.append("")
487
+ lines.append("| ADR | Title | Status |")
488
+ lines.append("|---|---|---|")
489
+ for a in data.adrs:
490
+ link = f"[{a.token}]({a.path})"
491
+ lines.append(f"| {link} | {a.title} | {a.status} |")
492
+ lines.append("")
462
493
  return "\n".join(lines)
463
494
 
464
495
  # Mermaid Gantt
465
496
  lines.append("## Timeline")
466
497
  lines.append("")
467
498
  lines.append("```mermaid")
468
- lines.append(_mermaid_gantt_src(data.epics, data.rollups, generated_at, include_done=include_done))
499
+ lines.append(
500
+ _mermaid_gantt_src(visible_epics, data.rollups, generated_at, include_done=include_done)
501
+ )
469
502
  lines.append("```")
470
503
  lines.append("")
471
504
 
472
505
  # Epic status table
473
506
  lines.append("## Epics")
474
507
  lines.append("")
475
- lines.append(_markdown_status_table(data.epics, data.rollups, data.fidelity))
508
+ lines.append(_markdown_status_table(visible_epics, data.rollups, data.fidelity))
476
509
  lines.append("")
477
510
  lines.append(
478
- "_Fidelity: `S+/S?` = has specs / none · `A+/A?` = has ADRs / none · `T:N%` = task completion_"
511
+ "_Fidelity: `S+/S?` = has specs / none"
512
+ " · `A+/A?` = has ADRs / none · `T:N%` = task completion_"
479
513
  )
480
514
  lines.append("")
481
515
 
@@ -516,10 +550,7 @@ def render_html(data: RoadmapData, generated_at: str) -> str:
516
550
  from importlib import resources as _res
517
551
 
518
552
  template_bytes = (
519
- _res.files("beacon")
520
- .joinpath("resources")
521
- .joinpath("roadmap_template.html")
522
- .read_bytes()
553
+ _res.files("beacon").joinpath("resources").joinpath("roadmap_template.html").read_bytes()
523
554
  )
524
555
  template = template_bytes.decode("utf-8")
525
556