Manage branching timeline for BoardClaude audits. Handle forks, merges, comparisons, and timeline visualization data. Triggers on "fork", "branch", "compare", "merge", "timeline", "strategy", "worktree".
Manage the branching decision timeline that tracks every audit, fork, merge, and rollback in a BoardClaude project. The timeline makes strategic decisions visible: which approaches were tested in parallel, which won, and why. Every meaningful choice becomes a node in a horizontal tree, backed by git worktrees for isolated experimentation.
The timeline is the visual heart of BoardClaude. It tells the complete story of how a project evolved through structured experimentation rather than linear guessing.
git rev-parse --is-inside-work-tree 2>/dev/null || echo "NOT A GIT REPO"ls .boardclaude/state.json 2>/dev/null || echo "NOT FOUND"git branch --show-current 2>/dev/nullgit worktree list 2>/dev/nullCreate an isolated worktree to test a hypothesis without affecting the main branch.
Steps:
BRANCH_NAME="bc/$1"
WORKTREE_PATH=".boardclaude/worktrees/$1"
git worktree add "$WORKTREE_PATH" -b "$BRANCH_NAME"
.boardclaude/ config to the new worktree (panels, agent definitions — NOT audit history):
cp -r panels/ "$WORKTREE_PATH/panels/"
cp -r agents/ "$WORKTREE_PATH/agents/"
mkdir -p "$WORKTREE_PATH/.boardclaude/audits"
state.json in the worktree with the fork metadata:
{
"project": "<project name>",
"panel": "<active panel>",
"branch": "bc/<branch-name>",
"audit_count": 0,
"latest_audit": null,
"latest_score": null,
"score_history": [],
"forked_from": {
"branch": "<source branch>",
"audit_id": "<latest audit id at fork time>",
"commit": "<git commit hash>",
"score": <score at fork time>
},
"worktrees": [],
"status": "active"
}
timeline.json:
{
"id": "fork-<timestamp>",
"type": "fork",
"branch": "<source branch>",
"timestamp": "<ISO 8601>",
"hypothesis": "<what is being tested>",
"children": [],
"parent": "<latest audit id>",
"to_branch": "bc/<branch-name>",
"worktree_path": ".boardclaude/worktrees/<name>"
}
state.json: Add the new worktree to the worktrees array and record it in branches:
{
"branches": {
"bc/<branch-name>": {
"forked_from": "<source branch>",
"forked_at": "<audit id>",
"status": "active",
"hypothesis": "<what is being tested>",
"latest_audit": null,
"worktree_path": ".boardclaude/worktrees/<name>"
}
}
}
Load audit results from two or more branches and produce a detailed comparison.
Steps:
Load audit results from each branch:
.boardclaude/state.json (or the worktree's state).boardclaude/audits/Produce side-by-side score comparison:
┌──────────────────────────────────────────────────────────────┐
│ Branch Comparison: <comparison id> │
├─────────────┬──────────────────┬────────────────────────────┤
│ Agent │ <branch A> │ <branch B> │
├─────────────┼──────────────────┼────────────────────────────┤
│ Boris │ XX (+/-delta) │ XX (+/-delta) │
│ Cat │ XX (+/-delta) │ XX (+/-delta) │
│ ... │ ... │ ... │
├─────────────┼──────────────────┼────────────────────────────┤
│ COMPOSITE │ XX.X │ XX.X │
└─────────────┴──────────────────┴────────────────────────────┘
Calculate per-agent deltas: For each agent, compute the score difference between branches and relative to the fork point (the last audit on the source branch before forking)
Identify which branch wins on each dimension:
Produce recommendation:
Save comparison to .boardclaude/audits/compare-{timestamp}.json:
{
"comparison_id": "compare-<timestamp>",
"timestamp": "<ISO 8601>",
"branches": [
{
"name": "<branch A>",
"audit_id": "<audit id>",
"composite": <score>,
"agent_scores": { "<agent>": <score>, "..." : "..." }
},
{
"name": "<branch B>",
"audit_id": "<audit id>",
"composite": <score>,
"agent_scores": { "<agent>": <score>, "..." : "..." }
}
],
"deltas": { "<agent>": <branch_a - branch_b>, "..." : "..." },
"winner": "<branch name>",
"key_differentiator": "<explanation>",
"recommendation": "<merge | continue | hybrid>"
}
Merge the winning worktree branch into the target branch (usually main).
Steps:
git checkout <target-branch>
git merge "bc/<branch-name>" -m "merge(bc/<branch-name>): score XX.X, <key differentiator>"
"abandoned" in state.jsongit worktree remove ".boardclaude/worktrees/<losing-branch>"
timeline.json:
{
"id": "merge-<timestamp>",
"type": "merge",
"branch": "<target branch>",
"timestamp": "<ISO 8601>",
"merged_branch": "bc/<branch-name>",
"score_delta": "+/-XX.X composite",
"parent": "<winning branch's latest audit id>"
}
timeline.json — set their status:
{
"id": "<losing branch audit id>",
"status": "abandoned",
"abandon_reason": "<from comparison recommendation>"
}
git worktree prune
Roll back to a known good state when an experiment goes wrong.
Steps:
git reset --hard):
AUDIT_COMMIT=$(jq -r '.nodes[] | select(.id == "<audit-id>") | .commit' .boardclaude/timeline.json)
git worktree add ".boardclaude/worktrees/rollback-<audit-id>" -b "bc/rollback-<audit-id>" "$AUDIT_COMMIT"
.boardclaude/state.json from the target audit pointtimeline.json:
{
"id": "rollback-<timestamp>",
"type": "rollback",
"branch": "<current branch>",
"timestamp": "<ISO 8601>",
"target_audit": "<audit-id>",
"reason": "<user-provided reason>",
"parent": "<current latest audit>"
}
.boardclaude/state.jsonTracks the current project state, audit history, and active branches.
{
"project": "<project name>",
"panel": "<active panel name>",
"branch": "<current git branch>",
"audit_count": 5,
"latest_audit": "audit-20260212-143022",
"latest_score": 87,
"score_history": [44, 58, 72, 81, 87],
"current_audit": {
"id": "<audit id or null>",
"state": "<IDLE | INGEST | PANEL_AUDIT | SYNTHESIZE | REPORT | COMPLETE>",
"started_at": "<ISO 8601 or null>",
"panel": "<panel name>",
"branch": "<branch>",
"commit": "<git commit hash>"
},
"branches": {
"bc/<branch-name>": {
"forked_from": "<source branch>",
"forked_at": "<audit id>",
"status": "<active | merged | abandoned>",
"hypothesis": "<what was being tested>",
"latest_audit": "<audit id or null>",
"worktree_path": "<path or null if removed>"
}
},
"worktrees": ["<list of active worktree paths>"],
"status": "<active | idle>"
}
.boardclaude/timeline.jsonAppend-only event log that forms the branching tree visualization.
{
"nodes": [
{
"id": "<unique id>",
"type": "<audit | fork | merge | rollback>",
"branch": "<git branch>",
"timestamp": "<ISO 8601>",
"parent": "<parent node id or null>",
"commit": "<git hash, for audit nodes>",
"scores": { "<agent>": "<score>" },
"composite": "<number, for audit nodes>",
"label": "<human-readable description>",
"status": "<active | merged | abandoned, for audit nodes>",
"abandon_reason": "<string, for abandoned nodes>",
"hypothesis": "<string, for fork nodes>",
"children": ["<child node ids, for fork nodes>"],
"to_branch": "<target branch, for fork nodes>",
"merged_branch": "<branch that was merged, for merge nodes>",
"score_delta": "<string description, for merge nodes>",
"target_audit": "<audit id, for rollback nodes>",
"reason": "<string, for rollback nodes>"
}
]
}
Node types and their required fields:
| Field | audit | fork | merge | rollback |
|---|---|---|---|---|
| id | required | required | required | required |
| type | "audit" | "fork" | "merge" | "rollback" |
| branch | required | required | required | required |
| timestamp | required | required | required | required |
| parent | required | required | required | required |
| commit | required | - | - | - |
| scores | required | - | - | - |
| composite | required | - | - | - |
| label | required | - | - | - |
| status | optional | - | - | - |
| hypothesis | - | required | - | - |
| children | - | required | - | - |
| merged_branch | - | - | required | - |
| score_delta | - | - | required | - |
| target_audit | - | - | - | required |
| reason | - | - | - | required |
The timeline JSON is designed to be consumed by the dashboard's TimelineTree component:
Periodically clean up stale worktrees to prevent disk bloat:
# List all worktrees
git worktree list
# Remove a specific worktree
git worktree remove ".boardclaude/worktrees/<name>"
# Prune stale references
git worktree prune
# Delete the git branch after worktree removal (optional)
git branch -d "bc/<name>"
Always archive before removing — the audit data from abandoned branches is valuable for the timeline visualization and for understanding what approaches were tried.