✅ fresh
last synced 2026-06-17T22:39:35.514075+00:00 · coverage 100% (
active-table)Validation by Beadloom
doc_sync— same source assync-check.
Active Table (component)
Internal building block of the application layer.
Source: src/beadloom/application/active_table.py
Overview
Single source of truth for the ACTIVE.md bead-status table format used by the packaged agentic flow. Extracted from the MCP S4 helpers (BDL-051) so the MCP process-tools (checkpoint / complete_bead, in services/mcp_server.py) and the active-sync command (BDL-053, in services/commands/docsync.py) share one tolerant, fail-safe parser/updater rather than each carrying its own copy.
The module is deliberately pure with respect to bd: it never shells out to the beads CLI. Callers query bd and inject the resulting status map; this layer only parses and rewrites the markdown. That keeps it trivially testable and makes the no-op contract (no bd, no ACTIVE table → nothing happens) the caller's responsibility, not a hidden side effect here.
The bead-status table format
The table is a markdown table whose header's first cell is Bead, directly followed by a separator row (| --- | --- | ... |). Each data row carries a bead-id in its first cell and a state token in its Status column. Both the 3-column | Bead | Role | Status | and 4-column | Bead | Role | Status | Depends | shapes are supported — the Status column is located by its header cell index (case-insensitive), not by position.
Public surface
split_table_row(line)/is_separator_cells(cells)— markdown table row primitives.split_table_rowreturns the stripped inner cells of a| … |line (orNoneif the line is not a table row);is_separator_cellsisTruefor a header-separator row (cells are only-/:).set_active_table_status(active_path, bead_id, status)— flips one bead's Status cell (the row's last cell) by whole-token bead-id match in the first cell (so…mukc.1never collaterally matches…mukc.10). This is the extracted MCP S4 updater, byte-identical to the pre-extraction behaviour;services/mcp_server.pyre-exports it for back-compat. ReturnsTrueon a write,False(file untouched) on a missing file / no table / no matching row.bd_status_to_cell(bd_status)— the documentedbd-status → Status-cell map:closed → "✓ done",in_progress → "in progress",blocked → "blocked",open/ready → "ready". An unrecognised status returnsNoneso the caller leaves the row untouched (never corrupt). The"blocked"token is injected by the caller for anopenbead that has an open blocker.reconcile_active_tables(project_root, bd_statuses, *, epic=None)— the pure reconcile-from-bdcore. Discovers the target ACTIVE.md files (just epic's.claude/development/docs/features/<epic>/ACTIVE.mdwhen given, else everyfeatures/*/ACTIVE.md), finds each file's bead-status table, and for every data row whose bead-id is present inbd_statusesrewrites the Status cell to the mapped state — unless the existing cell already STARTS WITH that state token, so a coordinator's richer note (✓ done (PASS-WITH-FIXES)) is preserved when the state agrees. Rows whose bead-id is absent, or whosebdstatus is unrecognised, are left untouched. Only files with a changed cell are rewritten; every other file is byte-preserved.ReconcileResult(dataclass) — the outcome:changed_files(paths rewritten) anddrifted_rows((path, bead_id, old_cell, new_cell)per corrected cell).active-syncusesdrifted_rowsto drive--check(nonzero exit when non-empty) vs the default fix mode.
Best-effort throughout: never raises, never corrupts the file. Prose, headings, the Progress Log, and non-Status columns are always left untouched.
Collaborators
services/commands/docsync.py—active-synccommand (and helpers). Queriesbd list --json(via the mockableservices/bd_seam.run_bdseam), maps it to a{bead_id → status}dict (_bd_statuses_from_list, which injects"blocked"for anopenbead with an openblocksdependency on a non-closed target), then callsreconcile_active_tables. After a fix it best-effort runsbd export -o .beads/issues.jsonl(only when that file is git-tracked) so the tracker artifact stays honest across branch/squash-merge. The--stageflag runsgit addon EXACTLY the reconciled ACTIVE.md paths plus the exported jsonl (via_stage_reconciled), never staging unrelated files. The--checkmode runs reconcile on a throwaway sandbox copy (_active_sync_check) so it never writes to the real tree; it exits 1 on drift, 0 when clean.services/mcp_server.py— S4 process-tools.checkpointandcomplete_beadflip a single bead's row via the re-exportedset_active_table_status.
See the active-sync CLI reference and the Agentic Dev Flow guide for the user-facing command, the pre-commit "ACTIVE / tracker coherence" step, and the no-op contract.
Testing
The pure core is covered by tests/test_active_table.py and tests/test_active_table_hardening.py (table primitives, the bd-status map, and the reconcile core's drift / no-op / byte-preservation cases). The active-sync command's check / fix / no-op paths are covered by tests/test_cli_active_sync.py and tests/test_cli_active_sync_hardening.py, the pre-commit hook wiring by tests/test_cli_hooks.py, and the re-exported S4 updater by tests/test_mcp_process_tools.py.