100%

Section 03: scripts/plan_routing.py + /create-plan Step 0.6

Status: Complete Goal: Extract the mechanical routing logic into a tested, reusable Python module, and wire both consumers (/add-bug and /create-plan) to the same SSOT. This eliminates the current algorithmic duplication (/add-bug has the one-liner inline; /create-plan would have to duplicate it if not factored first) and creates the verification surface §04’s reviewer wiring relies on.

Success Criteria:

  • scripts/plan_routing.py exists, executable, with --help output documenting all CLI modes (connects to mission criterion: “scripts/plan_routing.py exists and factors owns_crates inversion”)
  • Primary-by-count algorithm is the core decision function; single-crate case is a degenerate instance (same code path)
  • Unit tests cover: single-crate hit, dominant multi-crate, tie → Fallback-R, no owner → Fallback-R, stale crate input → error raised
  • /add-bug/workflow.md Step 0 consumes plan_routing.py (no inline Python one-liner duplicating the inversion)
  • /create-plan/SKILL.md Phase 0 / Phase 0.5 / Step 0.6 section structure is clean: fork decision → routing triage → classification
  • Smoke test: running plan_routing.py on repr-opt’s scope returns route=subsection target=§21A-llvm
  • ./test-all.sh green; plan_routing tests green

Context: /add-bug/workflow.md:21–70 contains an inline Python one-liner that reads every plans/roadmap/section-*.md frontmatter, inverts owns_crates: into a crate → section-path map, and emits the target section path. The one-liner is correct but unshared; adding /create-plan Step 0.6 as a second consumer would be an impl-hygiene.md LEAK:algorithmic-duplication violation. Extracting to a helper restores SSOT.

Reference implementations:

  • scripts/plan_corpus/bug_markers.py — canonical single-file SSOT: roadmap_scan.py and bug_queue_scan.py both import parse_bug_entries() + classify_bug_exclusion() rather than duplicate regex. plan_routing.py follows the same pattern.
  • /add-bug/workflow.md:43–60 — existing routing decision logic to preserve behavior parity (single-crate case).

Depends on: Section 01 — plan_routing.py operates on the normalized owns_crates map; empty/stale entries would produce incorrect routing.


Intelligence Reconnaissance

Queries run 2026-04-24:

  • scripts/intel-query.sh --human symbols "plan_routing" --repo ori --limit 10 — no existing symbol; module is new.
  • scripts/intel-query.sh --human file-symbols "scripts/plan_corpus" --repo ori — enumerates existing plan_corpus modules as pattern precedent: schemas.py, bug_markers.py, bug_validators.py, dag.py, parser.py.
  • scripts/intel-query.sh --human similar "classify_bug_exclusion" --repo rust,swift,go --limit 5 — cross-repo routing/classification patterns (deprioritized; plan_routing is simpler than bug triage).
  • grep -n "owns_crates" .claude/skills/add-bug/workflow.md — confirms routing logic at lines 21–70.

Results summary (≤500 chars) [ori]: scripts/plan_corpus/bug_markers.py is the canonical single-file SSOT precedent for helper factoring. /add-bug/workflow.md:21-70 contains the inline routing one-liner to extract. scripts/plan_corpus/schemas.py provides RoadmapSectionSchema with owns_crates: list[str] (required per line 89) — the helper parses from this. .claude/skills/create-plan/SKILL.md Phase 0 + Phase 0.5 are the insertion points for Step 0.6.

See .claude/skills/query-intel/compose-intel-summary.md for the full query protocol.


03.1 Author scripts/plan_routing.py with primary-by-count algorithm

File(s): scripts/plan_routing.py (new file)

Target CLI surface:

python3 scripts/plan_routing.py --scope <crate1>[,<crate2>,...] [--plans-dir plans/roadmap]
  → emits JSON: {"route": "subsection"|"fallback-r"|"error", "target_section": "...", "section_hits": {...}, "reason": "..."}
python3 scripts/plan_routing.py --validate-map
  → validates every live crate is claimed by exactly one section; exits 0 clean or 1 with diagnostics
python3 scripts/plan_routing.py --help
  → usage
  • Create scripts/plan_routing.py with top-level #!/usr/bin/env python3 shebang

  • Define @dataclass(frozen=True) class RoutingDecision with fields: route: str, target_section: str | None, section_hits: dict[str, int], routing_justification: str | None, reason: str

  • Implement load_owns_crates_map(plans_dir: Path) -> dict[str, str] — iterates plans_dir/section-*.md, parses frontmatter via yaml.safe_load, builds crate → section_path dict; raises RoutingError on duplicate (crate in two sections’ lists) or on missing owns_crates: field per RoadmapSectionSchema requirement

  • Implement primary_by_count(scope: set[str], owns_map: dict[str, str]) -> RoutingDecision:

    section_hits: Counter[str] = Counter()
    unowned: set[str] = set()
    for crate in scope:
        if crate in owns_map:
            section_hits[owns_map[crate]] += 1
        else:
            unowned.add(crate)
    
    if not section_hits:
        return RoutingDecision(
            route="fallback-r",
            target_section=None,
            section_hits={},
            routing_justification=f"no scoped crate has an owning roadmap section (unowned: {sorted(unowned)})",
            reason="zero-owner",
        )
    
    most_common = section_hits.most_common()
    top_count = most_common[0][1]
    winners = [s for s, n in most_common if n == top_count]
    
    if len(winners) == 1 and not unowned:
        return RoutingDecision(
            route="subsection",
            target_section=winners[0],
            section_hits=dict(section_hits),
            routing_justification=None,
            reason=f"primary-by-count: {winners[0]} owns {top_count} of {len(scope)} crates",
        )
    
    if len(winners) > 1:
        return RoutingDecision(
            route="fallback-r",
            target_section=None,
            section_hits=dict(section_hits),
            routing_justification=f"tie across sections {sorted(winners)} with {top_count} hits each",
            reason="tie",
        )
    
    # Single winner but some unowned crates
    return RoutingDecision(
        route="fallback-r",
        target_section=None,
        section_hits=dict(section_hits),
        routing_justification=f"scope includes unowned crates {sorted(unowned)} alongside owned crates in {winners[0]}",
        reason="partial-ownership",
    )
  • Implement validate_map(plans_dir: Path, live_crates_dir: Path) -> list[str] returning list of diagnostic strings (empty means clean); detects: stale entries (in owns_crates but not in live_crates_dir), uncovered crates (in live_crates_dir but in no section’s list), duplicates

  • Implement CLI entry point in main() with argparse; subcommands/flags: --scope, --validate-map, --plans-dir, --live-crates-dir, --help

  • Emit JSON output for machine consumers (default) and --human flag for pretty-print

  • Subsection close-out (03.1) — MANDATORY before starting 03.2:

    • python3 scripts/plan_routing.py --help exits 0 with usage text
    • python3 scripts/plan_routing.py --scope ori_canon,ori_arc,ori_repr,ori_llvm returns route=subsection, target_section=plans/roadmap/section-21A-llvm.md
    • Update this subsection’s status to complete
    • Repo hygiene checkcompiler_repo/diagnostics/repo-hygiene.sh --check

03.2 Unit tests at compiler_repo/tests/plan-routing/

File(s): compiler_repo/tests/plan-routing/test_plan_routing.py (new), compiler_repo/tests/plan-routing/fixtures/ (new synthetic roadmap dirs)

Coverage matrix for the algorithm:

CaseScopeExpected routeExpected target
Single-crate hit{ori_parse}subsectionsection-00-parser
Multi-crate all-hit-one-section{ori_canon, ori_arc, ori_repr, ori_llvm}subsectionsection-21A-llvm
Multi-crate dominant with unowned{ori_parse, ori_parse, missing_crate}fallback-rNone (partial-ownership)
Tie two sections{ori_parse, ori_llvm} (1 each)fallback-rNone (tie)
Zero owners{nonexistent_crate}fallback-rNone (zero-owner)
Stale crate in maplive_crates lacks ori_lspvalidate_map returns “STALE: ori_lsp”
Uncovered cratelive_crates has new_crate, no section owns itvalidate_map returns “UNCOVERED: new_crate”
Duplicate claimtwo fixture sections both list ori_parseload raises RoutingError
  • Create fixture dir compiler_repo/tests/plan-routing/fixtures/roadmap-basic/ with 3–4 synthetic section-NN-*.md files covering the cases

  • Create fixture dir compiler_repo/tests/plan-routing/fixtures/roadmap-stale/ for stale-entry test

  • Create fixture dir compiler_repo/tests/plan-routing/fixtures/roadmap-duplicate/ for duplicate-claim test

  • Write test_plan_routing.py with one test function per row of the matrix; use pytest

  • Run tests: pytest compiler_repo/tests/plan-routing/ → all green

  • Add a smoke test that runs the algorithm against the REAL plans/roadmap/ tree and verifies repr-opt’s scope routes to §21A (end-to-end verification against production data)

  • Subsection close-out (03.2) — MANDATORY before starting 03.3:

    • pytest compiler_repo/tests/plan-routing/ exits 0 with all tests passing
    • Update this subsection’s status to complete
    • Repo hygiene checkcompiler_repo/diagnostics/repo-hygiene.sh --check

03.3 Migrate /add-bug Step 0 to consume plan_routing.py

File(s): .claude/skills/add-bug/workflow.md (edit Step 0)

The inline one-liner at lines 21–70 produces the same inversion plan_routing.py now owns. Replace it with a plan_routing.py invocation to restore algorithmic SSOT.

  • Read current .claude/skills/add-bug/workflow.md Step 0 to capture the single-crate routing logic (for behavior parity)

  • Replace the inline Python one-liner with:

    python3 scripts/plan_routing.py --scope <owning-crate> --human
    # Parses JSON output; routes bug to target_section for subsection OR to bug-tracker/plans/BUG-XX-NNN/ for fallback-r
  • Preserve the existing tracker-index carve-out (bugs orthogonal to compiler code) — the helper emits route=fallback-r, reason=zero-owner; the workflow then decides subsection under bug-tracker/ vs tracker-index based on subsystem

  • Verify /add-bug behavior hasn’t regressed: run a synthetic bug filing (dry-run if available) for a known-single-crate case; confirm output matches pre-migration routing

  • grep -c "owns_crates" .claude/skills/add-bug/workflow.md drops significantly (the one-liner was the main consumer)

  • Subsection close-out (03.3) — MANDATORY before starting 03.4:

    • /add-bug now consumes plan_routing.py; inline one-liner removed
    • Behavior parity verified (single-crate routing matches pre-migration)
    • Update this subsection’s status to complete
    • Repo hygiene checkcompiler_repo/diagnostics/repo-hygiene.sh --check

03.4 Wire /create-plan Step 0.6

File(s): .claude/skills/create-plan/SKILL.md

Insert a new Step 0.6 block between Phase 0 (fork decision) and Phase 0.5 (classification).

Design decision — Step 0.6 vs Phase 0.6 naming: the skill uses “Phase N” for major workflow stages (Phase 0–5) and “Step N” for minor sub-steps inside phases. “Step 0.6” fits the existing Step nomenclature in Phase 0; “Phase 0.6” would imply a new major stage. Adopt Step 0.6.

  • Locate the insertion point: between the existing Phase 0 “Fork Decision” close (after Phase 0.5 classification per plan-schema drives template selection) and actual Phase 1 prerequisites

  • Draft Step 0.6 body:

    ### Step 0.6: Mechanical routing triage (MANDATORY after fork decision, before Phase 0.5 classification)
    
    Before classifying plan type, determine the plan's target artifact kind via mechanical `owns_crates:` inversion. Plan author declares scope (crates touched); helper decides where the plan lands.
    
    1. Gather scope: list of compiler crates the plan touches (can be inferred from user's expanded mission + Step 1B scope declaration)
    2. If scope is empty (pure skill-infra-docs) → route = tracker-index; continue to Phase 0.5 classification
    3. Otherwise invoke: `python3 scripts/plan_routing.py --scope <crate1>,<crate2>,... --human`
    4. Parse the JSON output and record in the plan's future `00-overview.md` frontmatter:
       - `route=subsection, target_section=...` → plan will be created inside `plans/roadmap/section-NN-*.md` as a new subsection
       - `route=fallback-r, routing_justification=...` → plan will be created as `plans/<name>/` peer directory with `reroute: true` + `routing_justification: "<text>"` in `index.md`
    5. If `--validate-map` detects stale/uncovered/duplicate crates, STOP and direct the user to fix the roadmap `owns_crates` frontmatter first — do NOT proceed with plan creation on a broken map
    
    The decision becomes authoritative for Phase 4 section-writing (SKILL.md §Step 11c template branches on `plan_type` AND on `route`):
    - `route=subsection` → sections are written inside the target roadmap section file as `## NN.X` subsections (no new directory)
    - `route=fallback-r` → standard `plans/<name>/` directory creation + full section-file authoring per plan-schema.md
    - `route=tracker-index` → no plan directory; entry is added to the relevant `bug-tracker/section-NN-*.md`
  • Edit SKILL.md to insert Step 0.6 at the correct position

  • Verify: grep -n "Step 0.6" .claude/skills/create-plan/SKILL.md returns at least one match

  • Run prose-lint: python3 scripts/prose-lint.py .claude/skills/create-plan/SKILL.md exits 0

  • Subsection close-out (03.4) — MANDATORY before starting 03.N:

    • Step 0.6 published in SKILL.md
    • prose-lint + grep verification green
    • Update this subsection’s status to complete
    • Repo hygiene checkcompiler_repo/diagnostics/repo-hygiene.sh --check

03.N Completion Checklist

  • All implementation subsections (03.1–03.4) are [x] and status complete
  • All section success criteria have corresponding [x] checkboxes
  • python3 scripts/plan_routing.py --help exits 0
  • pytest compiler_repo/tests/plan-routing/ exits 0
  • python3 scripts/plan_routing.py --scope ori_canon,ori_arc,ori_repr,ori_llvm returns route=subsection target_section=plans/roadmap/section-21A-llvm.md
  • /add-bug behavior parity verified; inline one-liner removed
  • /create-plan/SKILL.md has Step 0.6 grep-findable
  • ./test-all.sh green — regression canary
  • python -m scripts.plan_corpus check exits 0
  • Plan sync:
    • This section’s frontmatter statuscomplete, subsection statuses → complete
    • 00-overview.md Quick Reference: Section 03 status → Complete
    • 00-overview.md mission success criteria: check off “scripts/plan_routing.py exists” + “/create-plan SKILL.md has Step 0.6”
    • index.md Section 03 status → Complete
  • Repo hygiene checkcompiler_repo/diagnostics/repo-hygiene.sh --check

Exit Criteria: plan_routing.py runnable + tested; /add-bug consumes it (no duplication); /create-plan Step 0.6 invokes it; repr-opt smoke test returns §21A-llvm; all tests green.