blue/.blue/docs/rfcs/0017-plan-file-authority.md
Eric Garcia 16d45d9a11 feat: alignment dialogue subagents, MCP instructions, and document batch
Alignment dialogues now use custom `alignment-expert` subagents with
max_turns: 10, tool restrictions (Read/Grep/Glob), and hard 400-word
output limits. Judge protocol injects as prose via RFC 0023. Moved
Blue voice patterns from CLAUDE.md to MCP server instructions field
for cross-repo portability.

Includes RFCs 0017-0026, spikes, and alignment dialogues from
2026-01-25/26 sessions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 07:09:39 -05:00

5.8 KiB

RFC 0017: Plan File Authority

Status Superseded
Superseded By RFC 0022: Filesystem Authority
Date 2026-01-26
Dialogue 0001: Plan Files And Dialogue Separation

Summary

RFC task tracking currently requires loading entire RFC documents to check status. This creates token inefficiency and couples operational state with design documentation. By introducing .plan.md companion files as the authoritative source for task state (with SQLite as a derived index rebuilt on read), we achieve: 60-80% token reduction for status operations, cleaner git diffs, surgical commits, and separation of ephemeral operational state from permanent documentation.

Problem

  1. Token Waste: Loading a 2000-line RFC to check task status wastes tokens
  2. Coupled Concerns: Operational state (tasks) mixed with design documentation (RFC content)
  3. Noisy Diffs: Task status changes pollute RFC git history
  4. Cross-Repo Limitation: When using Blue from another repo, no plan file support exists
  5. Header Format Drift: RFC headers have drifted between table and inline formats

Header Format Drift

The canonical RFC header uses a metadata table:

# RFC 0058: Remove Training Infrastructure

| | |
|---|---|
| **Status** | Accepted |
| **Created** | 2026-01-24 |
| **Dialogue** | [0058-remove-training...](./0058-remove-training...) |

But some RFCs use inline format:

# RFC 0075: Spot Capacity Providers

**Status**: Draft
**Created**: 2026-01-25
**Author**: Claude

The table format is canonical. Blue's to_markdown() generates it correctly, but manually-created or external RFCs may drift.

Architecture

Authority Inversion

The key insight: make .plan.md authoritative, not derived.

CURRENT (problematic):
  RFC.md (source) → SQLite (derived) → .plan.md (derived)
  Drift accumulates at each derivation step.

PROPOSED:
  .plan.md (authoritative) → SQLite (derived index, rebuild-on-read)
  Single source of truth. SQLite is ephemeral cache.

File Structure

.blue/docs/rfcs/
├── 0017-plan-file-authority.md       # Design documentation (permanent)
├── 0017-plan-file-authority.plan.md  # Task state (ephemeral scaffolding)
└── ...

Plan File Format

# Plan: 0017-plan-file-authority

| | |
|---|---|
| **RFC** | 0017-plan-file-authority |
| **Status** | in-progress |
| **Updated** | 2026-01-26T10:30:00Z |

## Tasks

- [x] Define architecture
- [x] Run alignment dialogue
- [ ] Implement plan file parsing
- [ ] Add rebuild-on-read to SQLite
- [ ] Update blue_rfc_plan tool
- [ ] Update blue_rfc_task_complete tool
- [ ] Write tests

Atomic Update Sequence

To prevent drift during writes:

1. Set RFC status: "updating-plan"
2. Write .plan.md file
3. Clear RFC status flag
4. SQLite rebuilds on next read

If interrupted between steps 1-3, the status flag indicates recovery needed.

SQLite as Derived Index

// On any plan query:
fn get_plan_tasks(rfc_id: &str) -> Vec<Task> {
    let plan_file = format!("{}.plan.md", rfc_id);

    if plan_file.exists() && plan_file.mtime() > cache.mtime() {
        // Rebuild cache from authoritative source
        let tasks = parse_plan_markdown(&plan_file);
        cache.rebuild(rfc_id, &tasks);
    }

    cache.get_tasks(rfc_id)
}

API Unchanged

Existing tools work identically:

  • blue_rfc_plan - Creates/updates .plan.md (now authoritative)
  • blue_rfc_task_complete - Marks task done in .plan.md

Callers don't know or care about the authority change.

Guardrails

From Git Workflow Expert (converged dialogue):

  1. Status Gating: Plans only for accepted or in-progress RFCs
  2. File Limit: Maximum 3 companion files per RFC (plan, test-plan, architecture notes)
  3. Single Responsibility: Each companion file serves exactly one purpose

Header Format Validation

Add blue_lint validation for RFC header format:

fn validate_rfc_header(content: &str) -> Result<(), LintError> {
    // Must have table format, not inline
    if content.contains("**Status**:") {
        return Err(LintError::HeaderFormat {
            expected: "| **Status** | value |",
            found: "**Status**: value",
        });
    }

    // Must have table structure
    if !content.contains("| | |\n|---|---|") {
        return Err(LintError::MissingMetadataTable);
    }

    Ok(())
}

The blue_lint tool should:

  • Detect inline format (**Status**: value)
  • Offer auto-fix to convert to table format
  • Run on blue_rfc_create to catch manual edits

Key Insight

"Plans are like scaffolding - essential during construction, removed after completion."

The .plan.md file is operational state, not documentation. It exists to track work, then can be deleted or archived when the RFC completes. This "ephemeral framing" resolved documentation cohesion objections in the alignment dialogue.

References

  • ADR 0005: Single Source of Truth
  • ADR 0006: Relationships (file co-location)
  • Dialogue 0001: 12-expert alignment achieved 100% convergence on this architecture

Test Plan

Plan Files

  • Create plan file for existing RFC
  • Verify SQLite rebuilds from plan on read
  • Verify atomic update sequence handles interruption
  • Verify blue_rfc_plan creates companion file
  • Verify blue_rfc_task_complete updates companion file
  • Test cross-repo usage (Blue in external project)
  • Verify status gating (no plans for draft RFCs)

Header Format Validation

  • Detect inline format (**Status**: Draft)
  • Auto-fix inline to table format
  • Validate table structure exists
  • blue_lint reports header format errors
  • blue_lint --fix converts inline to table

"Right then. Let's get to it."

— Blue