From c4c45d4a545c6b74d648a8e3bc95a2cf7b30517a Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 21 Jun 2026 14:12:03 -0400 Subject: [PATCH] conductor(plan): rewrite chronology_20260619 plan for v2 (11 phases, 4 pause points) Replaces the v1 plan (10 phases, single-stage cross-check) with an 11-phase plan that executes the v2 spec's git-history classifier + 3-stage cross-check + 30% quality gate. Plan Phase 2 = Spec Phase 2 part 1; renumbering shifts from Plan Phase 4 onwards (per the spec-vs-plan mapping in the summary table). 11 phases, 28 tasks, 4 hard pause points (Plan Phase 6 quality gate, Plan Phase 7 Tier 1 review, Plan Phase 10 user sign-off, plus the Plan Phase 6 ABORT fallback to manual review). TDD red+green cycles for Phases 2-4 (8 new tests for _classify_status + 4 for extract_summary + 3 for format_markdown + 5 for the quality gate). Test runner: scripts/run_tests_batched.py (per Tier 2 sandbox rule #1). Throw-away scripts: scripts/tier2/artifacts/chronology_20260619/ (rule #4). Default branch: master (rule #2). Line endings: preserve existing (rule #3). --- conductor/tracks/chronology_20260619/plan.md | 1305 +++++++++++++----- 1 file changed, 974 insertions(+), 331 deletions(-) diff --git a/conductor/tracks/chronology_20260619/plan.md b/conductor/tracks/chronology_20260619/plan.md index 1f6f35af..ca80d24a 100644 --- a/conductor/tracks/chronology_20260619/plan.md +++ b/conductor/tracks/chronology_20260619/plan.md @@ -1,402 +1,1045 @@ -# Conductor Chronology Implementation Plan +# Conductor Chronology v2 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Create `conductor/chronology.md` as the canonical manually-maintained index of all tracks (active + shipped + superseded + abandoned) plus notable non-track commits, prune the duplicated `[x]` entries from `conductor/tracks.md`, document the new 3-step archiving convention in `conductor/workflow.md`, and write a migration report. Every row is cross-checked per the user directive (2026-06-19): "EVERY SINGLE ENTRY MUST BE CROSS CHECKED TO MAKE SURE IT'S STILL CORRECT, AND NOTHING WAS MISSED." +**Goal:** Rewrite the chronology track to use a **git-history-based classifier** as the primary evidence (per the handover §"Rewrite `_classify_status` to use git history as primary evidence"). Move the broken v1 `conductor/chronology.md` → `conductor/chronology.md.broken-v1`; generate v2 with the new classifier; add a per-row evidence line + confidence level + "Needs Review" section; enforce a 30% quality gate (FR7) before Tier 1 reviews the "Needs Review" queue; preserve the v1 pruning of `tracks.md` + the 3-step convention in `workflow.md`; write a v2 addendum to the migration report + end-of-track report; get user sign-off. -**Architecture:** One-shot helper script (`scripts/audit/generate_chronology.py`, FR5) extracts per-track data from `conductor/tracks/` and `conductor/archive/` and produces a draft `chronology.md.draft`. Tier 1 (or the user) then cross-checks every row in the draft per FR6 (5 fields: date, ID, status, summary, range), and verifies completeness (every folder has a row). The user is the final quality gate (VC12). No CI integration; the file is hand-maintained like `tracks.md`. +**Architecture:** Rewrite the helper script (`scripts/audit/generate_chronology.py`) to use the 5-step git-history algorithm (FR5). Add a new quality gate script (`scripts/audit/chronology_quality_gate.py`, FR7) that gates the cross-check on the classifier's confidence distribution. The 3-stage cross-check (FR6) is: (1) classifier auto-classifies high-confidence rows, (2) Tier 1 reviews the "Needs Review" queue, (3) user reviews the final v2 with per-row evidence. No CI integration; the file is hand-maintained like `tracks.md`. -**Tech Stack:** Python 3.11+ (helper script), `tomllib`, `git log` (for commit SHAs), `pathlib`. No new production code in `src/`. No new dependencies. +**Tech Stack:** Python 3.11+ (helper script + quality gate), `tomllib`, `git log` (for commit SHAs), `pathlib`, `re`. No new production code in `src/`. No new dependencies. Test runner: `uv run python scripts/run_tests_batched.py` (per `conductor/workflow.md` "Tier 2 Autonomous Sandbox" rule #1 — NEVER direct `uv run pytest`). -**Spec reference:** `conductor/tracks/chronology_20260619/spec.md` (250 lines; 6 FRs, 5 NFRs, 12 VCs, 9 Risks, 10 Phases). +**Spec reference:** `conductor/tracks/chronology_20260619/spec.md` (276 lines; 7 FRs, 7 NFRs, 14 VCs, 10 Risks, 10 Phases). + +**Pause points (Tier 2 must stop and report):** +- **Phase 5:** Quality gate result. If PASS, continue. If ABORT, fall back to manual review (Tier 1 reviews every row) and STOP. +- **Phase 6:** Tier 1 review of the "Needs Review" queue. Tier 2 generates the staging file + the per-row evidence log + the queue, then PAUSES for Tier 1. +- **Phase 9:** User sign-off. Tier 2 generates the v2 staging file with Tier 1's resolutions, then PAUSES for user. + +**HARD BAN reminders** (per `AGENTS.md` and `conductor/workflow.md`): +- No `git restore`, `git checkout -- `, `git reset`. +- No day/hour/minute estimates in any artifact. +- No new `src/*.py` files. +- No skipping tests; fix the underlying bug. +- Per-file atomic commits; no batch commits. --- -## Phase 1: Data extraction audit + draft helper script (FR5) +## Phase 1: Archive v1 + verify state of carried-forward work -Focus: Build the extraction tool. The tool emits a DRAFT (per FR5); the cross-check (FR6, Phase 8) is the authority. +Focus: The v1 work's pruning + 3-step convention + helper script are reused. Move the broken v1 chronology to `.broken-v1`; reset `state.toml` to `current_phase = 0` so this rewrite starts clean. -- [ ] **Task 1.1: Audit the source folders** (estimate: 5 min) - - WHERE: `conductor/tracks/`, `conductor/archive/` - - WHAT: Enumerate every subfolder. For each, capture: folder name, presence of `spec.md` / `plan.md` / `metadata.json`, and the date string in the slug (if any). - - HOW: `Get-ChildItem -Directory conductor/tracks`, `Get-ChildItem -Directory conductor/archive` (PowerShell). Save counts to `tests/artifacts/chronology_audit_step1.json`: `{"tracks_count": N, "archive_count": M, "with_slug": X, "without_slug": Y}`. - - SAFETY: Read-only. Don't modify any folder. - - NO COMMIT (investigation only). - -- [ ] **Task 1.2: Write failing tests for the helper script** (estimate: 5 min) - - WHERE: New file `tests/test_generate_chronology.py` - - WHAT: 5 unit tests covering the script's per-folder extraction logic: - 1. `test_slug_date_extraction` — given folder name `gencpp_python_bindings_20260308`, returns `2026-03-08` - 2. `test_slug_date_extraction_handles_missing_date` — given `my_folder` (no date), returns `None` - 3. `test_summary_extraction_from_spec_md` — given a `spec.md` with "## Overview\n\nFirst sentence here. Second sentence.", returns `"First sentence here."` - 4. `test_summary_extraction_falls_back_to_metadata` — given a folder with `metadata.json.description` and no `spec.md`, returns the description - 5. `test_summary_extraction_truncates_to_25_words` — given a 50-word sentence, returns first 25 words + "…" - - HOW: Use `pytest.tmp_path` to create fixture folders with synthetic `spec.md` / `metadata.json`. Pure unit tests; no `live_gui`, no `tmp_path_factory.mktemp` outside `./tests/`. - - SAFETY: Tests must FAIL initially (the script doesn't exist yet). Use `pytest.raises` for the failure case. - - VERIFY: `uv run pytest tests/test_generate_chronology.py -v` should FAIL on each test with `ModuleNotFoundError` or `NameError`. - - COMMIT: `test(chronology): failing tests for generate_chronology.py extraction logic` - - GIT NOTE: "Phase 1.2. TDD red. 5 tests cover slug date parsing, summary extraction, fallback chain, word truncation. Tests must fail before Task 1.3 writes the script." - -- [ ] **Task 1.3: Write the helper script (TDD green)** (estimate: 10 min) - - WHERE: New file `scripts/audit/generate_chronology.py` - - WHAT: A Python 3.11+ script that: - - Accepts `--draft` flag (output to stdout) and `--root PATH` (default: `conductor/`) - - Walks `/tracks/` and `/archive/` - - For each folder: - - Extracts date from slug (regex `\d{8}$`); falls back to first-commit date if slug has no date - - Extracts init SHA via `git log --reverse --format='%h' -- ` (first commit) - - Extracts end SHA via `git log -1 --format='%h' -- ` (last commit) - - Computes commit count via `git log --oneline .. -- | wc -l` (approximate; actual count is the `git log ..` line count) - - Extracts status from folder location: `tracks/` = `Active`; `archive/` = `Shipped`. Override via `/metadata.json.status` if present. - - Extracts summary: prefer `metadata.json.description` (modern tracks); else first non-empty line of `spec.md` (trimmed to 25 words, "…" if truncated); else first non-empty line of `plan.md`; else `"Imported from archive (no spec)"`. - - Emits markdown to stdout: one row per folder, sorted by date descending. Format per FR1. - - HOW: Use `subprocess.run(["git", "log", ...], capture_output=True, text=True)` for git queries. Use `pathlib`. Match the 1-space indentation convention. - - SAFETY: The script is READ-ONLY on the source folders. It writes to stdout only. - - VERIFY: `uv run pytest tests/test_generate_chronology.py -v` should now PASS (all 5 tests green). - - COMMIT: `feat(chronology): add draft-only helper script (FR5)` - - GIT NOTE: "Phase 1.3. TDD green. generate_chronology.py extracts date/SHA/status/summary per track folder. Draft-only: emits to stdout; the cross-check (Phase 8) is the authority." - -- [ ] **Task 1.4: Commit Phase 1** (estimate: 1 min) - - WHERE: Working tree - - WHAT: Confirm both files (`tests/test_generate_chronology.py` + `scripts/audit/generate_chronology.py`) are staged from Tasks 1.2 + 1.3. Verify `git status` is clean except for pre-existing modifications. - - HOW: `git log -1 --stat` to confirm the Phase 1 commit is in place. - - SAFETY: Don't commit unrelated working-tree changes. - - NO COMMIT (Phase 1 already committed in Task 1.3). - - CHECKPOINT: `conductor(checkpoint): Phase 1 complete — script + tests green` - ---- - -## Phase 2: Generate `chronology.md.draft` (FR5 + pre-Phase 8 prep) - -Focus: Run the script, produce the draft. Do NOT commit `chronology.md` yet — it's still a draft. - -- [ ] **Task 2.1: Run the script, capture the draft** (estimate: 2 min) - - WHERE: `scripts/audit/generate_chronology.py` - - WHAT: `uv run python scripts/audit/generate_chronology.py --draft > conductor/chronology.md.draft`. This produces one row per track (165+ rows), sorted newest first. - - HOW: Run the command. Verify the output file exists and has > 100 rows. - - SAFETY: The draft file is git-ignored OR clearly marked as draft (e.g., filename `chronology.md.draft`). - - VERIFY: `Get-Content conductor/chronology.md.draft | Measure-Object -Line`. Expect ≥ 200 lines (header + 165+ rows × ~4 lines each). - - NO COMMIT (draft is not canonical yet). - -- [ ] **Task 2.2: Sanity-check 5-10 random rows** (estimate: 5 min) - - WHERE: `conductor/chronology.md.draft` - - WHAT: Pick 5 random rows; for each, manually verify the 5 fields (date, ID, status, summary, range) against the source folder's `spec.md` and `git log `. If any field is wrong, the script has a bug — fix the script in a follow-up commit BEFORE Phase 3. - - HOW: For each picked row, run: - - `Get-Content "conductor/archive//spec.md" | Select-Object -First 1` (verify summary source) - - `git log --oneline --reverse -- "conductor/archive//"` (verify init SHA) - - `git log -1 --format='%h' -- "conductor/archive//"` (verify end SHA) - - SAFETY: Don't proceed to Phase 3 if the script is buggy. Fix the script first. - - NO COMMIT (sanity check, not implementation). - ---- - -## Phase 3: Prune `conductor/tracks.md` (FR2) - -Focus: Remove the 3 categories of `[x]`/`[shipped]` entries. Preserve in-flight and backlog entries. - -- [ ] **Task 3.1: Prune "Phase 9: Chore Tracks" section** (estimate: 5 min) - - WHERE: `conductor/tracks.md` (around the "Phase 9" heading, roughly lines 480-560 based on the file's current size) - - WHAT: Either delete the entire "Phase 9: Chore Tracks" section OR replace it with a one-line stub: - ```markdown - ### Phase 9: Chore Tracks - *Completed chore tracks are in [`chronology.md`](./chronology.md).* - ``` - - HOW: Use the `manual-slop_edit_file` MCP tool with the exact anchor for the section header + the first child line. Verify with `git diff conductor/tracks.md`. - - SAFETY: Don't touch the "Active Tracks" table at the top of the file, the "Backlog" section, the "Follow-up" section, or the "Notes" section. - - VERIFY: `grep -n "^- \[x\]" conductor/tracks.md | wc -l` should be reduced (this counts the remaining `[x]` markers; non-zero is fine if "Active Research" still has them, but should be smaller than before). - - COMMIT: `conductor(track): prune Phase 9 Chore Tracks section from tracks.md (FR2)` - - GIT NOTE: "Phase 3.1. Phase 9 section either deleted or stubbed; canonical record now in chronology.md." - -- [ ] **Task 3.2: Prune `[x]` entries from "Active Research Tracks"** (estimate: 5 min) - - WHERE: `conductor/tracks.md` "Active Research Tracks" section - - WHAT: Remove only the `[x]` entries (e.g., the Fable review row that shipped 2026-06-18). Keep the `[ ]` in-flight entries. - - HOW: For each `[x]` line in the section, delete the entire bullet (including the linked line, if any). The section heading and the `[ ]` rows stay. - - SAFETY: Don't remove the section heading. Don't remove the `[ ]` rows. Don't touch other sections. - - VERIFY: `grep -n "Active Research Tracks" -A 20 conductor/tracks.md` shows no `[x]` rows in that section. - - COMMIT: `conductor(track): prune [x] entries from Active Research Tracks (FR2)` - - GIT NOTE: "Phase 3.2. [x] entries from Active Research Tracks moved to chronology.md. [ ] in-flight rows preserved." - -- [ ] **Task 3.3: Prune `[shipped: ...]` entries from "Follow-up"** (estimate: 5 min) - - WHERE: `conductor/tracks.md` "Follow-up (Planned, Not Yet Specced)" section - - WHAT: Remove only the `[shipped: YYYY-MM-DD]` entries. Keep the "planned" and "not yet specced" entries. - - HOW: For each `[shipped: ...]` bullet, delete the entire bullet. The section heading and the active followups stay. - - SAFETY: Don't remove the section heading. Don't remove the "planned" entries. Don't touch other sections. - - VERIFY: `grep -n "shipped:" conductor/tracks.md | wc -l` should be 0. - - COMMIT: `conductor(track): prune [shipped] entries from Follow-up section (FR2)` - - GIT NOTE: "Phase 3.3. [shipped] entries from Follow-up moved to chronology.md. 'planned' and 'not yet specced' rows preserved." - -- [ ] **Task 3.4: Verify no `[x]` remains** (estimate: 2 min) +- [ ] **Task 1.1: Verify `tracks.md` pruning is intact** - WHERE: `conductor/tracks.md` - - WHAT: Final scan. Any `[x]` in the file should be in a "Status legend" or in-context comment, not a track entry. - - HOW: `grep -n "^- \[x\]" conductor/tracks.md`. Expected: 0 matches. - - SAFETY: If there are matches, identify which section and Task 3.1/3.2/3.3 missed them. Fix and re-commit. + - WHAT: Run `grep -n "^- \[x\]" conductor/tracks.md | wc -l`. Expected: `0` (the only `[x]` markers should be in the Status legend at the bottom, not in a `- [x]` line at the start of a row). + - HOW: `git diff --name-only HEAD~1 HEAD -- conductor/tracks.md` to see the most recent tracks.md changes (sanity check); then run the grep. + - SAFETY: If non-zero, STOP and report. The pruning should not have regressed. + - VERIFY: `grep -n "^- \[x\]" conductor/tracks.md` returns 0 matches in a `- [x]` line position. - NO COMMIT (verification only). - - CHECKPOINT: `conductor(checkpoint): Phase 3 complete — tracks.md pruned` + +- [ ] **Task 1.2: Verify `workflow.md` 3-step convention is present** + - WHERE: `conductor/workflow.md` + - WHAT: Run `grep -n "Archiving a track" conductor/workflow.md`. Expected: 1 match. + - HOW: Single grep; verify the line is the 3-step convention block. + - SAFETY: If 0 matches or > 1, STOP and report. The 3-step convention should be in `workflow.md` "Notes > Editing this file" section. + - VERIFY: The matched line is followed by the 3 numbered steps. + - NO COMMIT (verification only). + +- [ ] **Task 1.3: Move `chronology.md` → `chronology.md.broken-v1`** + - WHERE: `conductor/chronology.md` → `conductor/chronology.md.broken-v1` + - WHAT: Use `git mv` to preserve git history (the v1 chronology was committed in commit `8cd92856` and 24 subsequent commits; `git mv` keeps the rename visible in `git log --follow`). + - HOW: `git mv conductor/chronology.md conductor/chronology.md.broken-v1`. Verify with `git status` (should show a rename, not a delete+add). + - SAFETY: Don't delete the file; the rename preserves history. Don't add `.broken-v1` to `.gitignore` — it should be a tracked file (per VC14). + - VERIFY: `git log --follow conductor/chronology.md.broken-v1` shows the v1 history. The file has 218 lines. + - COMMIT: `conductor(chronology): archive v1 chronology to .broken-v1 (VC14)` + - GIT NOTE: "Phase 1.3. v1 chronology moved to .broken-v1 for archival. The new v2 file will be created in Phase 7. v1 status classifier was broken (read stale metadata.json.status); v2 uses git history as primary evidence per FR5." + +- [ ] **Task 1.4: Reset `state.toml` to `current_phase = 0`** + - WHERE: `conductor/tracks/chronology_20260619/state.toml` + - WHAT: Set `[meta].status = "active"`, `[meta].current_phase = 0`, `[meta].last_updated = ""`. Reset all `[phases].phase_N.status` to `"pending"`, `checkpointsha` to `""`. Reset all `[tasks].t_.status` to `"pending"`, `commit_sha` to `""`. Reset all `[verification].` to `false`. + - HOW: Use `manual-slop_edit_file` to set the 4 fields (status, current_phase, last_updated, and the rewrite note). For phases/tasks/verification, replace the entire sections with the new content. The new content has phases 1-10 marked pending + tasks marked pending + verification flags false. + - SAFETY: Don't delete the `[meta]`, `[phases]`, `[tasks]`, `[verification]`, `[user_directives_logged]` sections; just reset their values. Add a `[rewrite_log]` section noting the v2 rewrite. + - VERIFY: `uv run python -c "import tomllib; data = tomllib.load(open('conductor/tracks/chronology_20260619/state.toml','rb')); assert data['meta']['current_phase'] == 0; assert data['meta']['status'] == 'active'; print('OK')"` exits 0. + - COMMIT: `conductor(plan): reset state.toml to current_phase=0 for v2 rewrite` + - GIT NOTE: "Phase 1.4. state.toml reset for v2 rewrite. The v1 track's 24 commits remain in git history; this rewrite starts at Phase 1 with a git-history-based classifier (per the handover)." + +- [ ] **Task 1.5: Verify the staging file path is not committed** + - WHERE: `conductor/chronology.md.staging` (target of Phase 4) + - WHAT: Confirm the staging file does not exist yet AND that the path is gitignored (so accidental creation doesn't pollute the diff). + - HOW: `Test-Path conductor/chronology.md.staging` returns False; `grep -n "chronology.md.staging" .gitignore` returns a match (or the staging file is added to `.gitignore` if not already there). + - SAFETY: If the staging file exists, STOP and report. The v1 chronology should be in `.broken-v1` only. + - VERIFY: `Test-Path conductor/chronology.md.staging` returns False; the staging path is in `.gitignore`. + - NO COMMIT (verification only). + - CHECKPOINT: `conductor(checkpoint): Phase 1 complete — v1 archived, state.toml reset` --- -## Phase 4: Update `conductor/workflow.md` (FR3) +## Phase 2: Rewrite the helper script — git-history classifier (FR5, TDD red+green) -Focus: Document the 3-step archiving convention. +Focus: The script's `_classify_status` function is rewritten to use the handover's 5-step algorithm. The new signature returns `(status, confidence, reason)`. This is TDD-required per project convention. -- [ ] **Task 4.1: Append the 3-step convention** (estimate: 3 min) - - WHERE: `conductor/workflow.md` "Notes > Editing this file" section (the last subsection of the "Notes" section near the end of the file) - - WHAT: Append the following 3-step block: - ```markdown +- [ ] **Task 2.1: Add failing tests for the new `_classify_status` signature (TDD RED)** + - WHERE: `tests/test_generate_chronology.py` (modify; add new test class `TestClassifyStatus`) + - WHAT: 8 unit tests for the new classifier signature. Each test calls `_classify_status(folder_link, init_sha, end_sha, commit_count, first_commit_subject, last_commit_subject, state_phase, metadata_status, last_commit_date)` and asserts the returned `(status, confidence, reason)` tuple. + - HOW: Add the following tests to the file (1-space indentation per project convention): + ```python + from scripts.audit.generate_chronology import _classify_status - **Archiving a track (3 steps):** - 1. Move the folder from `conductor/tracks//` to `conductor/archive//`. - 2. Remove the `[x]` entry from `conductor/tracks.md` (and update status badges on related entries). - 3. Add a row to `conductor/chronology.md` with the init SHA, the end SHA (the archive-move commit), and a one-sentence summary. + class TestClassifyStatus: + def test_archive_with_three_plus_work_commits_is_completed_high(self): + result = _classify_status( + folder_link="conductor/archive/test_track_20260101", + init_sha="abc1234", end_sha="def5678", commit_count=5, + first_commit_subject="feat: add core logic", + last_commit_subject="fix: edge case", + state_phase="complete", metadata_status="Completed", + last_commit_date="2026-01-15", + ) + assert result == ("Completed", "high", "archive + 5 work commits + state_phase=complete") + + def test_archive_with_zero_commits_is_special_low(self): + result = _classify_status( + folder_link="conductor/archive/empty_track_20260101", + init_sha="abc1234", end_sha="abc1234", commit_count=0, + first_commit_subject="chore: init", + last_commit_subject="chore: init", + state_phase="no-state-toml", metadata_status="Completed", + last_commit_date="2026-01-01", + ) + status, confidence, _ = result + assert status == "Special" + assert confidence == "low" + + def test_tracks_with_state_phase_three_plus_is_in_progress_high(self): + result = _classify_status( + folder_link="conductor/tracks/active_track_20260101", + init_sha="abc1234", end_sha="def5678", commit_count=4, + first_commit_subject="conductor(track): add initial spec", + last_commit_subject="feat: implement core", + state_phase="4", metadata_status="active", + last_commit_date="2026-01-20", + ) + assert result == ("In Progress", "high", "tracks + 4 work commits + state_phase=4") + + def test_tracks_with_zero_to_one_commits_is_active_low(self): + result = _classify_status( + folder_link="conductor/tracks/fresh_track_20260101", + init_sha="abc1234", end_sha="abc1234", commit_count=1, + first_commit_subject="conductor(track): add initial spec", + last_commit_subject="conductor(track): add initial spec", + state_phase="0", metadata_status="spec_written", + last_commit_date="2026-01-01", + ) + status, confidence, _ = result + assert status == "Active" + assert confidence == "low" + + def test_metadata_status_abandoned_is_honoured(self): + result = _classify_status( + folder_link="conductor/archive/abandoned_20260101", + init_sha="abc1234", end_sha="def5678", commit_count=2, + first_commit_subject="conductor(track): add initial spec", + last_commit_subject="chore: archive move", + state_phase="no-state-toml", metadata_status="abandoned", + last_commit_date="2025-06-01", + ) + status, confidence, _ = result + assert status == "Abandoned" + assert confidence == "high" + + def test_metadata_status_superseded_is_mapped_to_special(self): + result = _classify_status( + folder_link="conductor/archive/superseded_20260101", + init_sha="abc1234", end_sha="def5678", commit_count=2, + first_commit_subject="conductor(track): add initial spec", + last_commit_subject="chore: archive move", + state_phase="no-state-toml", metadata_status="superseded", + last_commit_date="2025-06-01", + ) + status, confidence, _ = result + assert status == "Special" + assert confidence == "high" + + def test_metadata_status_contradicts_git_is_low(self): + result = _classify_status( + folder_link="conductor/archive/contradict_20260101", + init_sha="abc1234", end_sha="def5678", commit_count=0, + first_commit_subject="chore: init", + last_commit_subject="chore: init", + state_phase="no-state-toml", metadata_status="Completed", + last_commit_date="2026-01-01", + ) + status, confidence, _ = result + assert status == "Special" + assert confidence == "low" + + def test_tracks_with_three_plus_commits_no_state_toml_is_in_progress(self): + result = _classify_status( + folder_link="conductor/tracks/no_state_20260101", + init_sha="abc1234", end_sha="def5678", commit_count=3, + first_commit_subject="feat: implement X", + last_commit_subject="fix: bug in X", + state_phase="no-state-toml", metadata_status="active", + last_commit_date="2026-01-15", + ) + assert result == ("In Progress", "high", "tracks + 3 work commits + no state.toml") + + def test_placeholder_in_track_id_keeps_current_status(self): + result = _classify_status( + folder_link="conductor/tracks/PLACEHOLDER_test_20260101", + init_sha="abc1234", end_sha="def5678", commit_count=3, + first_commit_subject="chore: init", + last_commit_subject="chore: update", + state_phase="2", metadata_status="spec_written", + last_commit_date="2026-01-10", + ) + assert result == ("spec_written", "high", "PLACEHOLDER track; original status preserved") ``` - - HOW: Find the "Editing this file" subheading; append after its last paragraph. - - SAFETY: Don't change the existing convention text; just add the new block at the end. - - VERIFY: `grep -n "Archiving a track" conductor/workflow.md` should match. - - COMMIT: `conductor(track): document 3-step archiving convention in workflow.md (FR3)` - - GIT NOTE: "Phase 4. Workflow.md gets the 3-step convention: move folder, remove from tracks.md, add to chronology.md." - - CHECKPOINT: `conductor(checkpoint): Phase 4 complete — workflow.md updated` + - SAFETY: These tests use the `_classify_status` function with the new signature. The v1 signature was `_classify_status(folder_link, current, track_id)`. The tests will fail with a TypeError or signature mismatch — that's the RED phase. + - VERIFY: `uv run python scripts/run_tests_batched.py tests/test_generate_chronology.py` should FAIL with `TypeError: _classify_status() got an unexpected keyword argument 'init_sha'` (or similar signature mismatch). + - COMMIT: `test(chronology): add 8 failing tests for new _classify_status signature (TDD red)` + - GIT NOTE: "Phase 2.1. TDD red. 8 tests cover the new (status, confidence, reason) tuple signature with the 5-step algorithm. Tests must fail before Task 2.2 rewrites the function." + +- [ ] **Task 2.2: Rewrite `_classify_status` to the new signature (TDD GREEN)** + - WHERE: `scripts/audit/generate_chronology.py` (modify; replace the `_classify_status` function definition; lines ~163-189) + - WHAT: Replace the v1 `_classify_status(folder_link, current, track_id)` function with the new signature `_classify_status(folder_link, init_sha, end_sha, commit_count, first_commit_subject, last_commit_subject, state_phase, metadata_status, last_commit_date)` that returns `(status, confidence, reason)`. + - HOW: Use `manual-slop_py_update_definition` to replace the function body. The new function: + ```python + def _classify_status( + folder_link: str, + init_sha: str, + end_sha: str, + commit_count: int, + first_commit_subject: str, + last_commit_subject: str, + state_phase: str, + metadata_status: str, + last_commit_date: str, + ) -> tuple[str, str, str]: + """Classify a track's status using git history as primary evidence. + + The 5-step algorithm (per CHRONOLOGY_TRACK_HANDOVER_20260620.md): + 1. Count meaningful commits (commit_count) + 2. Inspect commit messages (first/last subject) + 3. Check state.toml phase progression (state_phase as string from _parse_state_phase) + 4. Default to conservative (low confidence on ambiguous) + 5. Honour explicit metadata (metadata_status) + + state_phase values (per _parse_state_phase): + - "complete" = track shipped + - "N" (digit string) = current_phase = N + - "no-state-toml" = no state.toml file + - "?" = parse error + + Returns: (status, confidence, reason) where: + - status: one of "Active", "In Progress", "Completed", "Abandoned", "Special" + - confidence: "high" or "low" + - reason: one-line explanation + """ + track_id: str = Path(folder_link).name + + if "PLACEHOLDER" in track_id: + return (metadata_status or "unknown", "high", "PLACEHOLDER track; original status preserved") + + is_archive: bool = folder_link.startswith("conductor/archive/") + is_tracks: bool = folder_link.startswith("conductor/tracks/") + + work_commit_pattern: re.Pattern[str] = re.compile(r"^(feat|fix|refactor|perf|test)\(") + is_work_subject: bool = ( + work_commit_pattern.match(first_commit_subject) is not None + or work_commit_pattern.match(last_commit_subject) is not None + ) + + metadata_lower: str = (metadata_status or "").lower() + state_phase_clean: str = (state_phase or "").strip() + is_in_flight: bool = state_phase_clean.isdigit() and int(state_phase_clean) >= 3 + + if metadata_lower in {"abandoned", "superseded"} and not is_work_subject: + mapped: str = "Abandoned" if metadata_lower == "abandoned" else "Special" + return (mapped, "high", f"metadata.json.status={metadata_lower}; no work commits; mapped to {mapped}") + + if is_archive and commit_count >= 3 and (is_work_subject or state_phase_clean == "complete"): + return ("Completed", "high", f"archive + {commit_count} work commits + state_phase={state_phase_clean}") + + if is_archive and commit_count == 0: + return ("Special", "low", f"archive folder with 0 work commits; needs review") + + if is_tracks and is_in_flight: + return ("In Progress", "high", f"tracks + {commit_count} work commits + state_phase={state_phase_clean}") + + if is_tracks and commit_count >= 3 and state_phase_clean in {"no-state-toml", "?"}: + return ("In Progress", "high", f"tracks + {commit_count} work commits + no state.toml") + + if is_tracks and commit_count <= 1: + return ("Active", "low", f"tracks + {commit_count} commits; spec-only or early stage") + + if metadata_lower in {"completed", "complete", "shipped"} and commit_count == 0: + return ("Special", "low", f"metadata says {metadata_status} but git shows 0 commits; contradictory") + + return ("Special", "low", f"ambiguous: commit_count={commit_count}, state_phase={state_phase_clean}, is_work={is_work_subject}") + ``` + - SAFETY: Use 1-space indentation (per project convention). Use `tuple[str, str, str]` return type (per `conductor/code_styleguides/error_handling.md` data-oriented pattern; NOT `Optional[Tuple]`). The function is a pure transformation: inputs → outputs, no side effects, no exceptions. The `state_phase` is always a string (per `_parse_state_phase`); the check uses `.isdigit()` + `int()` conversion. + - VERIFY: `uv run python scripts/run_tests_batched.py tests/test_generate_chronology.py` should now PASS for the 8 new tests in `TestClassifyStatus` (the 6 v1 tests in `TestExtract*` may also need updates — see Task 2.3). + - COMMIT: `feat(chronology): rewrite _classify_status to git-history classifier (FR5)` + - GIT NOTE: "Phase 2.2. TDD green. _classify_status now returns (status, confidence, reason) using the 5-step algorithm. The new signature accepts 9 parameters (folder_link, init/end_sha, commit_count, first/last_commit_subject, state_phase, metadata_status, last_commit_date)." + +- [ ] **Task 2.3: Update `walk_track_folders` to compute the new per-row evidence fields** + - WHERE: `scripts/audit/generate_chronology.py` (modify; the `walk_track_folders` function) + - WHAT: `walk_track_folders` currently populates 8 row fields (date, track_id, status, summary, init_sha, end_sha, commit_count, folder_link). Add 6 new fields: `first_commit_subject`, `last_commit_subject`, `state_phase`, `metadata_status`, `last_commit_date`, `confidence`, `reason`. The `init_sha`/`end_sha`/`commit_count` already exist. Update the call to `_classify_status` to use the new 9-argument signature. + - HOW: Use `manual-slop_py_update_definition` to update the function. The relevant changes: + ```python + # Existing: extract init_sha, end_sha, commit_count via _git_first_line / _git_log + # Add: extract first_commit_subject + last_commit_subject + first_commit_subject: str = _git_first_line(folder_relpath, "--reverse", "--format=%s") + last_commit_subject: str = _git_first_line(folder_relpath, "-1", "--format=%s") + + # Add: extract state_phase (string from _parse_state_phase) + state_phase: str = _parse_state_phase(folder / "state.toml") + + # Add: extract metadata_status (read from metadata.json) + metadata_status: str = "" + if metadata_path.is_file(): + try: + data = json.loads(metadata_path.read_text(encoding="utf-8")) + metadata_status = str(data.get("status", "")).strip() + except (json.JSONDecodeError, OSError): + pass + + # Add: extract last_commit_date + last_commit_date: str = _last_commit_date(folder_relpath) + + # Update: call _classify_status with the new signature + status, confidence, reason = _classify_status( + folder_link=folder_link, + init_sha=init_sha, + end_sha=end_sha, + commit_count=commit_count, + first_commit_subject=first_commit_subject, + last_commit_subject=last_commit_subject, + state_phase=state_phase, + metadata_status=metadata_status, + last_commit_date=last_commit_date, + ) + + # Update: row dict includes the 6 new fields + rows.append({ + "date": date, + "track_id": track_id, + "status": status, + "confidence": confidence, + "summary": summary, + "init_sha": init_sha, + "end_sha": end_sha, + "commit_count": commit_count, + "folder_link": folder_link, + "first_commit_subject": first_commit_subject, + "last_commit_subject": last_commit_subject, + "state_phase": state_phase, + "metadata_status": metadata_status, + "last_commit_date": last_commit_date, + "reason": reason, + }) + ``` + - SAFETY: The existing v1 tests may break because the row dict's keys change. Run the full test file to see what breaks. The 6 v1 tests target `extract_slug_date` and `extract_summary` (pure functions), so they should still pass. + - VERIFY: `uv run python scripts/run_tests_batched.py tests/test_generate_chronology.py` — the 8 new tests should PASS; the 6 v1 tests should still PASS (no changes to their target functions). + - COMMIT: `refactor(chronology): update walk_track_folders to populate per-row evidence fields` + - GIT NOTE: "Phase 2.3. walk_track_folders now populates 6 new fields per row (first/last_commit_subject, state_phase, metadata_status, last_commit_date, confidence, reason). init_sha/end_sha/commit_count were already present." + +- [ ] **Task 2.4: Update the v1 tests to match the new row dict shape** + - WHERE: `tests/test_generate_chronology.py` (modify; the 6 v1 tests in `TestExtractSlugDate` + `TestExtractSummary`) + - WHAT: The 6 v1 tests should still pass because they only test `extract_slug_date` and `extract_summary` (pure functions). The new tests for `_classify_status` and `walk_track_folders` are in `TestClassifyStatus`. Run the full test file to confirm. + - HOW: Re-read the 6 v1 tests. If they fail due to the row dict change, update them. Otherwise no changes needed. + - SAFETY: Don't delete the 6 v1 tests; they verify `extract_slug_date` + `extract_summary` which are still used. + - VERIFY: `uv run python scripts/run_tests_batched.py tests/test_generate_chronology.py` — all tests (6 v1 + 8 new) PASS. + - NO COMMIT (if no changes) or COMMIT: `test(chronology): update v1 tests for new row dict shape (FR5)` + - GIT NOTE: "Phase 2.4. v1 tests verified against new row dict shape; no changes needed." + - CHECKPOINT: `conductor(checkpoint): Phase 2 complete — git-history classifier (FR5) green` --- -## Phase 5: Write the migration report (FR4) +## Phase 3: Update `extract_summary` + add per-row evidence line to `format_markdown` (FR1, FR5) -Focus: One-page report for the user to review. This is the user's first check-point to verify the migration is on track. +Focus: The summary priority chain needs to reject metadata-field text (the v1 bug). The markdown output needs a per-row evidence line. -- [ ] **Task 5.1: Write the report** (estimate: 10 min) - - WHERE: New file `docs/reports/CHRONOLOGY_MIGRATION_20260619.md` - - WHAT: Markdown report with the following sections: - 1. **Summary** — total rows in `chronology.md` (active + shipped + superseded + abandoned); total rows removed from `tracks.md`; total notable non-track commits. - 2. **Counts by status** — table: status, count. - 3. **Counts by `tracks.md` section removed** — table: section, count. - 4. **Documented exceptions** — list of folders that have no row in `chronology.md` (per FR6 completeness check) with one-line reason each. - 5. **Notable non-track commits added** — list of SHAs + dates + one-line descriptions. - 6. **Diff preview (10-20 rows)** — first 10 + last 10 rows of `chronology.md` for the user to spot-check the format and content. - 7. **Per-row cross-check log** — table of (row index, track ID, date verified, ID verified, status verified, summary verified, range verified, fixes if any). For Phase 5 (pre-cross-check), this is empty; it gets filled in during Phase 8. - 8. **User sign-off** — final section with a checklist for the user to fill in during Phase 10. - - HOW: Generate the counts by running the script with `--counts` flag (add this flag in a script update, or compute manually from `chronology.md.draft` for now). Manually write the diff preview by copy-pasting 10 rows from the draft. - - SAFETY: The report is the user's window into the migration. Make the tables readable; don't dump raw data. - - VERIFY: The file should be 100-200 lines, well-formatted markdown. - - COMMIT: `docs(chronology): write CHRONOLOGY_MIGRATION_20260619.md (FR4)` - - GIT NOTE: "Phase 5. Migration report written. Pre-cross-check; the per-row log is empty until Phase 8." - - CHECKPOINT: `conductor(checkpoint): Phase 5 complete — migration report drafted` +- [ ] **Task 3.1: Add failing tests for metadata-field text rejection (TDD RED)** + - WHERE: `tests/test_generate_chronology.py` (modify; add 4 new tests to `TestExtractSummary`) + - WHAT: 4 unit tests that the regex `^\*\*` rejects metadata-field text. The tests use a fixture folder with `spec.md` whose first non-empty line is `**Priority:** A (foundational; precedes X)`. + - HOW: Add the following tests: + ```python + class TestExtractSummaryRejectsMetadata: + def test_rejects_priority_metadata_line(self, tmp_path: Path): + (tmp_path / "spec.md").write_text( + "# Test Track\n\n**Priority:** A (foundational; precedes X)\n\nThis is the actual summary.", + encoding="utf-8", + ) + summary: str = extract_summary(tmp_path) + assert not summary.startswith("**") + assert "actual summary" in summary + + def test_rejects_date_metadata_line(self, tmp_path: Path): + (tmp_path / "spec.md").write_text( + "# Test Track\n\n**Date:** 2026-01-01\n\nThe actual summary content.", + encoding="utf-8", + ) + summary: str = extract_summary(tmp_path) + assert not summary.startswith("**") + assert "actual summary" in summary + + def test_rejects_initialized_metadata_line(self, tmp_path: Path): + (tmp_path / "spec.md").write_text( + "# Test Track\n\n**Initialized:** 2026-01-01\n\nThe actual summary content.", + encoding="utf-8", + ) + summary: str = extract_summary(tmp_path) + assert not summary.startswith("**") + assert "actual summary" in summary + + def test_rejects_confidence_metadata_line(self, tmp_path: Path): + (tmp_path / "spec.md").write_text( + "# Test Track\n\n**Confidence:** high\n\nThe actual summary content.", + encoding="utf-8", + ) + summary: str = extract_summary(tmp_path) + assert not summary.startswith("**") + assert "actual summary" in summary + ``` + - SAFETY: These tests will FAIL with the v1 `extract_summary` because v1 returns the first non-empty line, which is `**Priority:** A...`. RED phase. + - VERIFY: `uv run python scripts/run_tests_batched.py tests/test_generate_chronology.py::TestExtractSummaryRejectsMetadata` should FAIL. + - COMMIT: `test(chronology): add 4 failing tests for metadata-field text rejection (TDD red)` + - GIT NOTE: "Phase 3.1. TDD red. 4 tests verify that extract_summary skips metadata-field lines (Priority, Date, Initialized, Confidence). Tests must fail before Task 3.2 updates the function." + +- [ ] **Task 3.2: Update `extract_summary` to reject metadata-field text (TDD GREEN)** + - WHERE: `scripts/audit/generate_chronology.py` (modify; the `extract_summary` function) + - WHAT: Add a regex check at the start of each candidate line: if the line starts with `**` (i.e., matches `re.match(r"^\*\*", line)`), skip it. Try the next non-empty line. + - HOW: Use `manual-slop_py_update_definition` to update the function. The new implementation adds a `_METADATA_LINE_RE = re.compile(r"^\*\*")` constant and filters candidate lines: + ```python + _METADATA_LINE_RE: re.Pattern[str] = re.compile(r"^\*\*") + + def extract_summary(folder_path: Path) -> str: + """Extract a one-sentence summary from a track folder. + + Priority chain (each step rejects metadata-field text starting with '**'): + 1. metadata.json.summary if present and not metadata-field text + 2. First non-empty line of spec.md that does not start with '**' + 3. metadata.json.description if not metadata-field text + 4. First non-empty line of plan.md that does not start with '**' + 5. Generic placeholder based on folder location + """ + metadata_path: Path = folder_path / "metadata.json" + if metadata_path.is_file(): + try: + data: dict = json.loads(metadata_path.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + data = {} + summary: str | None = data.get("summary") + if summary and not _METADATA_LINE_RE.match(summary): + return _truncate_to_25_words(summary) + description: str | None = data.get("description") + if description and not _METADATA_LINE_RE.match(description): + return _truncate_to_25_words(description) + + for filename in ("spec.md", "plan.md"): + file_path: Path = folder_path / filename + if not file_path.is_file(): + continue + try: + text: str = file_path.read_text(encoding="utf-8") + except OSError: + continue + for line in text.splitlines(): + stripped: str = line.strip() + if not stripped or _METADATA_LINE_RE.match(stripped): + continue + if stripped.startswith("#"): + continue + if stripped.startswith(">"): + continue + return _truncate_to_25_words(_first_sentence(stripped)) + + if (folder_path.parent.name if folder_path.parent else "") == "archive": + return "Imported from archive (no spec)" + return "Track folder (no spec found)" + ``` + - SAFETY: Use 1-space indentation. The function remains a pure transformation; no side effects. The `json.JSONDecodeError` is caught at the SDK boundary (per `conductor/code_styleguides/error_handling.md` "Exceptions reserved for the SDK boundary") and converted to a recoverable case (empty `data` dict). + - VERIFY: `uv run python scripts/run_tests_batched.py tests/test_generate_chronology.py` — all 18 tests (6 v1 + 8 new classifier + 4 metadata-rejection) PASS. + - COMMIT: `feat(chronology): extract_summary rejects metadata-field text (FR5)` + - GIT NOTE: "Phase 3.2. TDD green. extract_summary now skips lines starting with '**' (Priority, Date, Initialized, Confidence, etc.). Fixes the v1 bug where 167 rows had metadata-field text as their summary." + +- [ ] **Task 3.3: Add failing tests for per-row evidence line in `format_markdown` (TDD RED)** + - WHERE: `tests/test_generate_chronology.py` (modify; add new test class `TestFormatMarkdown`) + - WHAT: 3 unit tests for the per-row evidence line. Each test creates a row dict with the new fields and asserts that the output markdown has the evidence sub-line. + - HOW: Add the following tests: + ```python + class TestFormatMarkdown: + def test_row_includes_evidence_subline(self): + rows: list[dict] = [ + { + "date": "2026-01-01", + "track_id": "test_track_20260101", + "status": "Completed", + "confidence": "high", + "summary": "Test summary.", + "folder_link": "conductor/archive/test_track_20260101", + "init_sha": "abc1234", + "end_sha": "def5678", + "commit_count": 5, + "state_phase": "complete", + "first_commit_subject": "feat: add core", + "last_commit_subject": "fix: edge case", + "reason": "archive + 5 work commits + state_phase=complete", + }, + ] + output: str = format_markdown(rows) + assert "abc1234..def5678" in output + assert "Evidence:" in output + assert "feat: add core" in output + assert "fix: edge case" in output + assert "confidence=high" in output + + def test_row_includes_confidence_in_table(self): + rows: list[dict] = [ + { + "date": "2026-01-01", + "track_id": "test_track_20260101", + "status": "Special", + "confidence": "low", + "summary": "Test summary.", + "folder_link": "conductor/archive/test_track_20260101", + "init_sha": "abc1234", + "end_sha": "abc1234", + "commit_count": 0, + "state_phase": None, + "first_commit_subject": "chore: init", + "last_commit_subject": "chore: init", + "reason": "archive folder with 0 work commits", + }, + ] + output: str = format_markdown(rows) + assert "confidence=low" in output + + def test_needs_review_section_appended(self): + rows: list[dict] = [ + { + "date": "2026-01-01", + "track_id": "low_conf_20260101", + "status": "Special", + "confidence": "low", + "summary": "Low confidence row.", + "folder_link": "conductor/archive/low_conf_20260101", + "init_sha": "abc1234", + "end_sha": "abc1234", + "commit_count": 0, + "state_phase": None, + "first_commit_subject": "chore: init", + "last_commit_subject": "chore: init", + "reason": "archive folder with 0 work commits", + }, + ] + output: str = format_markdown(rows) + assert "## Needs Review" in output + assert "low_conf_20260101" in output + ``` + - SAFETY: These tests will FAIL with the v1 `format_markdown` because v1 doesn't emit evidence lines or a "Needs Review" section. RED phase. + - VERIFY: `uv run python scripts/run_tests_batched.py tests/test_generate_chronology.py::TestFormatMarkdown` should FAIL. + - COMMIT: `test(chronology): add 3 failing tests for per-row evidence line (TDD red)` + - GIT NOTE: "Phase 3.3. TDD red. 3 tests verify the per-row evidence line, confidence in table, and 'Needs Review' section. Tests must fail before Task 3.4 updates format_markdown." + +- [ ] **Task 3.4: Update `format_markdown` to emit evidence + "Needs Review" section (TDD GREEN)** + - WHERE: `scripts/audit/generate_chronology.py` (modify; the `format_markdown` function) + - WHAT: Update the table to include the confidence column. Add a sub-line below each row with the evidence. Add a "Needs Review" section at the bottom listing `low`-confidence rows. + - HOW: Use `manual-slop_py_update_definition` to update the function: + ```python + def format_markdown(rows: list[dict]) -> str: + lines: list[str] = [ + "| Date | ID | Status | Confidence | Summary | Folder | Range |", + "| --- | --- | --- | --- | --- | --- | --- |", + ] + for row in rows: + range_str: str = f"`{row['init_sha']}..{row['end_sha']}` ({row['commit_count']})" + confidence_marker: str = f"**{row['confidence']}**" if row["confidence"] == "low" else row["confidence"] + lines.append( + f"| {row['date']} | `{row['track_id']}` | {row['status']} | {confidence_marker} | " + f"{_md_escape(row['summary'])} | `{row['folder_link']}` | {range_str} |" + ) + evidence_line: str = ( + f"| | | | | | " + f"Evidence: `{row['init_sha'][:7]}..{row['end_sha'][:7]}` | " + f"{row['commit_count']} commits | state_phase={row.get('state_phase', 'n/a')} | " + f"\"{row['first_commit_subject']}\" → \"{row['last_commit_subject']}\" | " + f"reason=\"{row['reason']}\" |" + ) + lines.append(evidence_line) + + low_rows: list[dict] = [r for r in rows if r.get("confidence") == "low"] + if low_rows: + lines.append("") + lines.append("## Needs Review (Tier 1 + User)") + lines.append("") + lines.append(f"{len(low_rows)} row(s) had ambiguous git evidence. Resolved by Tier 1; user reviewed in Stage 3.") + lines.append("") + for row in low_rows: + lines.append( + f"- `{row['track_id']}` (status={row['status']}) — {row['reason']} — resolved by Tier 1" + ) + + return "\n".join(lines) + "\n" + ``` + - SAFETY: Use 1-space indentation. The function is a pure transformation. The "Needs Review" section only appears if there are `low`-confidence rows. The evidence line is a sub-line in the table (not a column), keeping the table at 7 columns (was 6; +1 for Confidence). + - VERIFY: `uv run python scripts/run_tests_batched.py tests/test_generate_chronology.py` — all 21 tests (6 v1 + 8 classifier + 4 metadata-rejection + 3 format) PASS. + - COMMIT: `feat(chronology): format_markdown emits per-row evidence + Needs Review section (FR1)` + - GIT NOTE: "Phase 3.4. TDD green. format_markdown now emits a 7-column table (added Confidence) with a per-row evidence sub-line and a 'Needs Review' section listing low-confidence rows. v1 had a 6-column table with no evidence." + - CHECKPOINT: `conductor(checkpoint): Phase 3 complete — summary regex + per-row evidence (FR1, FR5)` --- -## Phase 6: User review of the draft (gate) +## Phase 4: Add the quality gate script (FR7, TDD) -Focus: The user reviews the draft + report. Approves, OR requests changes (loop back to Phase 2). +Focus: A new wrapper script that runs after the classifier's output and gates the cross-check on the confidence distribution. -- [ ] **Task 6.1: User reviews `conductor/chronology.md.draft` + the migration report** (estimate: user-paced) - - WHERE: `conductor/chronology.md.draft`, `docs/reports/CHRONOLOGY_MIGRATION_20260619.md` - - WHAT: User opens both files and confirms: - - (a) The format matches FR1. - - (b) The diff preview in the report is accurate. - - (c) The documented exceptions are acceptable. - - (d) The overall structure is correct. - - HOW: User posts "approve" or specific change requests. - - OUTCOMES: - - **Approve** → proceed to Phase 7. - - **Request changes** → loop back to Phase 2 (re-run script with fixed parameters, regenerate draft, update report). - - SAFETY: Don't proceed past Phase 6 without explicit user approval. - - NO COMMIT (gate). +- [ ] **Task 4.1: Add failing tests for the quality gate (TDD RED)** + - WHERE: `tests/test_chronology_quality_gate.py` (new file) + - WHAT: 5 unit tests for the quality gate logic. Each test calls `check_quality_gate(staging_path, threshold=0.30)` and asserts the return code + message. + - HOW: Create the new file: + ```python + """Tests for scripts/audit/chronology_quality_gate.py.""" + from pathlib import Path + import pytest + from scripts.audit.chronology_quality_gate import check_quality_gate + + def _write_staging(path: Path, low_count: int, high_count: int) -> Path: + lines: list[str] = [ + "| Date | ID | Status | Confidence | Summary | Folder | Range |", + "| --- | --- | --- | --- | --- | --- | --- |", + ] + for i in range(high_count): + lines.append( + f"| 2026-01-{i+1:02d} | `track_{i}_20260101` | Completed | high | summary | folder | `abc..def` (5) |" + ) + for i in range(low_count): + lines.append( + f"| 2026-01-{i+1:02d} | `low_{i}_20260101` | Special | **low** | summary | folder | `abc..abc` (0) |" + ) + path.write_text("\n".join(lines), encoding="utf-8") + return path + + def test_zero_percent_low_passes(tmp_path: Path): + staging: Path = _write_staging(tmp_path / "staging.md", low_count=0, high_count=100) + passed, message = check_quality_gate(staging, threshold=0.30) + assert passed is True + assert "PASS" in message + + def test_thirty_percent_low_at_boundary_passes(tmp_path: Path): + staging: Path = _write_staging(tmp_path / "staging.md", low_count=30, high_count=70) + passed, _ = check_quality_gate(staging, threshold=0.30) + assert passed is True + + def test_thirty_one_percent_low_fails(tmp_path: Path): + staging: Path = _write_staging(tmp_path / "staging.md", low_count=31, high_count=69) + passed, message = check_quality_gate(staging, threshold=0.30) + assert passed is False + assert "ABORT" in message + + def test_hundred_percent_low_fails(tmp_path: Path): + staging: Path = _write_staging(tmp_path / "staging.md", low_count=10, high_count=0) + passed, message = check_quality_gate(staging, threshold=0.30) + assert passed is False + assert "ABORT" in message + + def test_missing_staging_file_errors(tmp_path: Path): + with pytest.raises(FileNotFoundError): + check_quality_gate(tmp_path / "missing.md", threshold=0.30) + ``` + - SAFETY: The test file is new; it does not modify any existing file. Use 1-space indentation. Use `pytest.raises` for the error case. The `_write_staging` helper creates a minimal staging file with the expected 7-column header. + - VERIFY: `uv run python scripts/run_tests_batched.py tests/test_chronology_quality_gate.py` should FAIL with `ModuleNotFoundError: No module named 'scripts.audit.chronology_quality_gate'`. + - COMMIT: `test(chronology): add 5 failing tests for quality gate (TDD red)` + - GIT NOTE: "Phase 4.1. TDD red. 5 tests cover the 30% threshold logic: 0% passes, 30% at boundary passes, 31% fails, 100% fails, missing file errors. Tests must fail before Task 4.2 writes the script." + +- [ ] **Task 4.2: Write the quality gate script (TDD GREEN)** + - WHERE: `scripts/audit/chronology_quality_gate.py` (new file) + - WHAT: The wrapper script. Exposes `check_quality_gate(staging_path, threshold)` for programmatic use and a `main()` for CLI use (`--staging PATH --threshold FLOAT`). + - HOW: Create the new file: + ```python + """Quality gate for the chronology classifier (FR7). + + Reads the staging file produced by generate_chronology.py and checks the + confidence distribution. If the ratio of low-confidence rows to total rows + exceeds the threshold (default 0.30), the gate aborts and the user must + fall back to manual review (Tier 1 reviews every row). + """ + from __future__ import annotations + import argparse + import re + import sys + from pathlib import Path + + _LOW_CONFIDENCE_PATTERN: re.Pattern[str] = re.compile(r"\*\*low\*\*|confidence=\"low\"") + + def check_quality_gate( + staging_path: Path, + threshold: float = 0.30, + ) -> tuple[bool, str]: + """Check the confidence distribution in the staging file. + + Returns: + (passed, message) where: + - passed: True if low_count / total_count <= threshold + - message: human-readable summary + """ + if not staging_path.exists(): + raise FileNotFoundError(f"Staging file not found: {staging_path}") + + content: str = staging_path.read_text(encoding="utf-8") + total_count: int = 0 + low_count: int = 0 + for line in content.splitlines(): + if not line.startswith("|"): + continue + if "---" in line or "Date" in line: + continue + total_count += 1 + if _LOW_CONFIDENCE_PATTERN.search(line): + low_count += 1 + + if total_count == 0: + return (False, "ABORT: staging file has no rows; nothing to review.") + + ratio: float = low_count / total_count + if ratio > threshold: + return ( + False, + f"ABORT: classifier is bad; {low_count}/{total_count} ({ratio:.1%}) rows are ambiguous, exceeds {threshold:.0%} threshold. " + f"Fallback to manual review (Tier 1 reviews every row).", + ) + + return ( + True, + f"PASS: classifier is good; {low_count}/{total_count} ({ratio:.1%}) rows are low confidence (≤ {threshold:.0%} threshold). " + f"Proceed to Tier 1 review of 'Needs Review' queue.", + ) + + def main() -> int: + parser: argparse.ArgumentParser = argparse.ArgumentParser( + description="Quality gate for the chronology classifier (FR7)." + ) + parser.add_argument( + "--staging", + type=Path, + required=True, + help="Path to the staging file produced by generate_chronology.py", + ) + parser.add_argument( + "--threshold", + type=float, + default=0.30, + help="Maximum allowed ratio of low-confidence rows (default: 0.30)", + ) + args: argparse.Namespace = parser.parse_args() + + try: + passed, message = check_quality_gate(args.staging, threshold=args.threshold) + except FileNotFoundError as e: + print(f"ERROR: {e}", file=sys.stderr) + return 2 + + print(message) + return 0 if passed else 1 + + if __name__ == "__main__": + sys.exit(main()) + ``` + - SAFETY: Use 1-space indentation. The function is a pure transformation: file → (passed, message). No side effects. The CLI uses `argparse` (standard library). The exit code is 0 for PASS, 1 for ABORT, 2 for errors. + - VERIFY: `uv run python scripts/run_tests_batched.py tests/test_chronology_quality_gate.py` should now PASS (all 5 tests green). + - COMMIT: `feat(chronology): add quality gate script (FR7)` + - GIT NOTE: "Phase 4.2. TDD green. chronology_quality_gate.py checks the confidence distribution. Default threshold 0.30 (30%). Exit 0 for PASS, 1 for ABORT, 2 for errors. CLI: --staging PATH --threshold FLOAT." + - CHECKPOINT: `conductor(checkpoint): Phase 4 complete — quality gate (FR7) green` --- -## Phase 7: Promote draft to canonical + commit (FR1, FR2, FR3, FR4 finalized) +## Phase 5: Run the new classifier, generate v2 staging (FR6 Stage 1) -Focus: Rename `chronology.md.draft` to `chronology.md`; this is the first time `chronology.md` is committed. +Focus: Run the script; verify the staging file has the per-row evidence + confidence + "Needs Review" section. -- [ ] **Task 7.1: Rename + commit** (estimate: 2 min) - - WHERE: `conductor/chronology.md.draft` → `conductor/chronology.md` - - WHAT: `git mv conductor/chronology.md.draft conductor/chronology.md`. Then `git commit` with the message below. - - HOW: `git mv` preserves git history; the file appears as a rename in the diff. (If `chronology.md` already exists for some reason, the `git mv` will fail; in that case, delete the old file first, but this shouldn't happen since chronology.md didn't exist before.) - - SAFETY: Verify the rename with `git status` before commit. Verify the file content is identical to the draft. - - VERIFY: `git log -1 --stat` shows the rename. - - COMMIT: `conductor(track): add conductor/chronology.md (FR1)` - - GIT NOTE: "Phase 7. chronology.md promoted from draft to canonical. Pre-cross-check; rows are verified in Phase 8." - - CHECKPOINT: `conductor(checkpoint): Phase 7 complete — chronology.md committed (pre-cross-check)` +- [ ] **Task 5.1: Run the script, generate the staging file** + - WHERE: `scripts/audit/generate_chronology.py` → `conductor/chronology.md.staging` + - WHAT: Run the script with the existing `--draft` flag (from v1) to produce a 216-row staging file. + - HOW: `uv run python scripts/audit/generate_chronology.py --draft > conductor/chronology.md.staging`. The script's `--draft` flag emits the markdown table to stdout; the staging file is the captured output. + - SAFETY: The staging file is gitignored (verified in Task 1.5). The script does not modify any source folder. + - VERIFY: `Test-Path conductor/chronology.md.staging` returns True; `(Get-Content conductor/chronology.md.staging | Measure-Object -Line).Lines` returns a value > 400 (header + 216 rows × ~2 lines per row + Needs Review section). + - NO COMMIT (staging file is gitignored). ---- - -## Phase 8: Per-row cross-check (FR6, HARD GATE) - -Focus: **EVERY** row is opened and verified per FR6's 5 fields. The migration report's per-row log is filled in. **This is the hard gate per the user directive (2026-06-19). NO shortcut is acceptable.** - -The 5 fields per row (per FR6): -1. **Date** — match the slug (`YYYYMMDD` → `YYYY-MM-DD`)? Fix any disagreement. -2. **Track ID** — backticked slug matches the folder name? -3. **Status** — `Active` / `In Progress` / `Shipped` / `Superseded` / `Abandoned`? Per FR1's status mapping. -4. **Summary** — accurate, ≤ 25 words, describes the most important fact? Trim or rewrite if needed. -5. **Range** — init SHA exists, end SHA exists, count is plausible? Run `git log --oneline .. -- ` to spot-check. - -The cross-check is done in batches of ~20 rows for commit granularity. Each batch is one commit. Per the user directive: "EVERY SINGLE ENTRY MUST BE CROSS CHECKED TO MAKE SURE IT'S STILL CORRECT, AND NOTHING WAS MISSED." Every row, no samples. - -- [ ] **Task 8.1: Batch 1 — newest 20 rows** (estimate: 30 min) - - WHERE: First 20 rows of `conductor/chronology.md` - - WHAT: For each row, verify the 5 fields. Fix any errors in `chronology.md`. Log the result in the migration report's per-row table. - - HOW: For each row, run: - - `Get-ChildItem -Directory conductor/tracks/, conductor/archive/` (verify folder exists; pick the right location based on status) - - `Get-Content "conductor///spec.md" | Select-Object -First 1` (verify summary source) +- [ ] **Task 5.2: Sanity-check 5 random rows in the staging file** + - WHERE: `conductor/chronology.md.staging` + - WHAT: Pick 5 random rows; for each, verify the per-row evidence line + confidence level + summary are correct. If any field is wrong, the script has a bug — fix the script in a follow-up commit BEFORE Phase 6. + - HOW: For each picked row, run: + - `Get-Content "conductor///spec.md" | Select-Object -First 1` (verify summary source; should NOT start with `**`) - `git log --oneline --reverse -- "conductor///"` (verify init SHA) - `git log -1 --format='%h' -- "conductor///"` (verify end SHA) - - `git log --oneline .. -- "conductor///" | Measure-Object -Line` (verify count) - - SAFETY: Don't trust the script output. Verify each row independently. If a field is wrong, fix the row in `chronology.md` BEFORE moving to the next row. - - VERIFY: After this batch, the first 20 rows are confirmed correct in the migration report. - - COMMIT: `conductor(chronology): cross-check batch 1 — 20 newest rows verified (FR6)` - - GIT NOTE: "Phase 8.1. Per-row cross-check batch 1. 20 rows verified; [N] fixes applied; per-row log updated in migration report." - -- [ ] **Task 8.2: Batch 2 — rows 21-40** (estimate: 30 min) - - WHERE: Rows 21-40 of `conductor/chronology.md` - - WHAT: Same procedure as 8.1. - - HOW: Same as 8.1. - - SAFETY: Same as 8.1. - - VERIFY: Rows 21-40 are correct. - - COMMIT: `conductor(chronology): cross-check batch 2 — rows 21-40 verified (FR6)` - - GIT NOTE: "Phase 8.2. Per-row cross-check batch 2." - -- [ ] **Task 8.3: Batch 3 — rows 41-60** (estimate: 30 min) - - WHERE / WHAT / HOW / SAFETY / VERIFY / COMMIT / GIT NOTE: Same pattern as 8.1. - -- [ ] **Task 8.4: Batch 4 — rows 61-80** (estimate: 30 min) - - Same pattern. - -- [ ] **Task 8.5: Batch 5 — rows 81-100** (estimate: 30 min) - - Same pattern. - -- [ ] **Task 8.6: Batch 6 — rows 101-120** (estimate: 30 min) - - Same pattern. - -- [ ] **Task 8.7: Batch 7 — rows 121-140** (estimate: 30 min) - - Same pattern. - -- [ ] **Task 8.8: Batch 8 — rows 141-160** (estimate: 30 min) - - Same pattern. - -- [ ] **Task 8.9: Batch 9 — rows 161+ (final batch)** (estimate: 30 min) - - WHERE: Remaining rows (whatever the count is after batch 8). - - WHAT: Final batch. After this, every row in `chronology.md` has been verified. - - SAFETY: If the count is > 160 + 20, split into another batch. Don't exceed 30 rows per batch for review ergonomics. - - VERIFY: Every row in `chronology.md` is now in the per-row log as "verified". - - COMMIT: `conductor(chronology): cross-check batch 9 (final) — all rows verified (FR6)` - - GIT NOTE: "Phase 8.9. FINAL cross-check batch. All 165+ rows verified; FR6 per-row gate satisfied." - - CHECKPOINT: `conductor(checkpoint): Phase 8 complete — all rows cross-checked` + - Compare the staging file's evidence line to the git output. + - SAFETY: Don't proceed to Phase 6 if the script is buggy. Fix the script first. + - NO COMMIT (sanity check, not implementation). + - CHECKPOINT: `conductor(checkpoint): Phase 5 complete — v2 staging generated` --- -## Phase 9: Completeness check (FR6, HARD GATE) +## Phase 6: Quality gate (FR7) — HARD GATE -Focus: Every folder in `conductor/tracks/` and `conductor/archive/` has a row in `chronology.md`. No exceptions except documented ones. +Focus: Run the quality gate; if PASS, proceed to Phase 7 (Tier 1 review). If ABORT, fall back to manual review (the user's "B" option). -- [ ] **Task 9.1: Enumerate folders, compare to rows** (estimate: 10 min) - - WHERE: `conductor/tracks/`, `conductor/archive/`, `conductor/chronology.md` - - WHAT: Get the list of folder names from both directories. Get the list of track IDs from `chronology.md`. Compute the set difference: folders without rows, rows without folders. - - HOW: - - Folders: `Get-ChildItem -Directory conductor/tracks | Select-Object -ExpandProperty Name` + `Get-ChildItem -Directory conductor/archive | Select-Object -ExpandProperty Name` - - Rows: extract backticked track IDs from `chronology.md` via `Select-String -Pattern '`([a-z_0-9]+_\d{8})`' -AllMatches` - - Diff: `Compare-Object -ReferenceObject $folders -DifferenceObject $rows` - - SAFETY: An empty diff is the goal. If non-empty, every diff item needs disposition (added or exception). - - VERIFY: `$diff` is empty OR only contains documented exceptions. - - NO COMMIT (verification only). +- [ ] **Task 6.1: Run the quality gate** + - WHERE: `scripts/audit/chronology_quality_gate.py` ← `conductor/chronology.md.staging` + - WHAT: `uv run python scripts/audit/chronology_quality_gate.py --staging conductor/chronology.md.staging`. + - HOW: Single command. Capture the exit code and message. + - SAFETY: This is a HARD GATE. The exit code determines the next phase. + - VERIFY: Exit code is 0 (PASS) or 1 (ABORT). The message contains either "PASS" or "ABORT". -- [ ] **Task 9.2: Resolve diff** (estimate: 10 min) - - WHERE: `conductor/chronology.md` + `docs/reports/CHRONOLOGY_MIGRATION_20260619.md` - - WHAT: For each item in the diff from 9.1: - - If it's a folder without a row: add the row (using the same FR1 format; extract data per the script; verify per FR6's 5 fields). - - If it's a row without a folder: investigate. Either the folder was renamed/removed (update the row's folder link) or the row is stale (remove it). Document the resolution in the migration report. - - HOW: Add rows using the same procedure as Phase 8 (verify 5 fields, log in the per-row table). Update the migration report's "Documented exceptions" section if any folders are intentional non-tracks. - - VERIFY: Re-run the diff from 9.1; the result is now empty (or only contains documented exceptions). - - COMMIT: `conductor(chronology): completeness check passed — folder set matches row set (FR6)` - - GIT NOTE: "Phase 9. FR6 completeness check. [N] missing rows added; [M] exceptions documented. Diff is now empty." - - CHECKPOINT: `conductor(checkpoint): Phase 9 complete — completeness check passed` +- [ ] **Task 6.2a: IF PASS — proceed to Phase 7** + - WHERE: continue to Phase 7 + - WHAT: If the gate passed, the classifier is good. Proceed to Phase 7 (Tier 1 review of the "Needs Review" queue). + - HOW: Note the PASS message + low/total ratio in the per-row evidence log. Continue. + - NO COMMIT (gate logic, not implementation). + +- [ ] **Task 6.2b: IF ABORT — fall back to manual review (the user's "B" option)** + - WHERE: same — Tier 1 reviews every row in the staging file + - WHAT: If the gate aborted (>30% low confidence), the classifier is bad. Fall back to the v1 manual protocol: Tier 1 opens every row in the staging file, verifies the 5 fields (date, ID, status, summary, range), and assigns a status manually. + - HOW: Skip Phase 7 (no "Needs Review" queue; Tier 1 reviews all 216 rows). Go directly to Phase 8 (promote staging to canonical). Document the abort + the fallback in the migration report v2 addendum (Phase 9). + - SAFETY: STOP and report to the user. The fallback is a significant scope change; the user should be aware. + - NO COMMIT (gate logic). + - CHECKPOINT: `conductor(checkpoint): Phase 6 complete — quality gate (PASS or ABORT)` --- -## Phase 10: User sign-off (FR6, HARD GATE) +## Phase 7: Tier 1 reviews "Needs Review" queue (FR6 Stage 2) — PAUSE FOR TIER 1 -Focus: The user is the quality gate. The track is not "done" until the user signs off. +Focus: Tier 1 (not the Tier 2 worker) reviews the `low`-confidence rows. **Tier 2 PAUSES here and reports to Tier 1.** -- [ ] **Task 10.1: User reviews final state** (estimate: user-paced) - - WHERE: `conductor/chronology.md`, `conductor/tracks.md`, `conductor/workflow.md`, `docs/reports/CHRONOLOGY_MIGRATION_20260619.md` - - WHAT: User confirms: - - (a) The format is correct. - - (b) The summaries are accurate. - - (c) The commit ranges are right. - - (d) Nothing was missed. - - HOW: User fills in the "User sign-off" section in the migration report with a confirmation + date. - - OUTCOMES: - - **Sign-off** → track is complete. Proceed to end-of-track wrap-up. - - **More changes** → loop back to the relevant phase (Phase 8 for per-row fixes, Phase 9 for completeness, etc.). - - SAFETY: No commit after Phase 10 without user sign-off. +- [ ] **Task 7.1: Tier 2 generates the "Needs Review" worklist for Tier 1** + - WHERE: `conductor/chronology.md.staging` → `tests/artifacts/chronology_v2_needs_review_worklist.md` (new file, gitignored) + - WHAT: Tier 2 extracts the `low`-confidence rows from the staging file and writes a worklist for Tier 1. Each worklist entry has: track_id, current status, current confidence, evidence (git SHAs, commit count, first/last commit subject, state_phase), classifier reason, the source folder path, the source spec.md path, and a placeholder for Tier 1's resolution. + - HOW: Use `manual-slop_run_powershell` to extract the "Needs Review" section from the staging file. Format the worklist as a markdown table. + - SAFETY: The worklist is gitignored. Tier 2 does NOT make resolution decisions; that's Tier 1's job. + - VERIFY: The worklist has one row per `low`-confidence track. + - NO COMMIT (gitignored worklist). + +- [ ] **Task 7.2: Tier 2 generates the per-row evidence log** + - WHERE: `tests/artifacts/chronology_v2_evidence_log.md` (new file, gitignored) + - WHAT: A complete log of all 216 rows with: track_id, status, confidence, init_sha, end_sha, commit_count, first_commit_subject, last_commit_subject, state_phase, classifier_reason, tier1_override (placeholder). + - HOW: Use `manual-slop_run_powershell` to extract the staging file's evidence sub-lines. Format as a markdown table. + - SAFETY: The log is gitignored. Tier 2 does NOT fill in tier1_override; that's Tier 1's job. + - VERIFY: The log has 216 rows. + - NO COMMIT (gitignored log). + +- [ ] **Task 7.3: Tier 2 PAUSES and reports to Tier 1** + - WHERE: report to the user + - WHAT: Tier 2 reports the quality gate result + the worklist + the evidence log to the user. **The user decides who reviews the worklist** (Tier 1 or the user directly). + - HOW: Print a summary: + ``` + Phase 6 quality gate: PASS (low/total ratio = X%) + Phase 7 "Needs Review" queue: N rows need Tier 1 review. + Worklist: tests/artifacts/chronology_v2_needs_review_worklist.md + Evidence log: tests/artifacts/chronology_v2_evidence_log.md + + [PAUSE] Awaiting Tier 1 review of the "Needs Review" queue. + ``` + - SAFETY: STOP. Do not proceed to Phase 8 until Tier 1 has reviewed the worklist. - NO COMMIT (gate). -- [ ] **Task 10.2: End-of-track report** (estimate: 15 min) - - WHERE: New file `docs/reports/TRACK_COMPLETION_chronology_20260619.md` - - WHAT: Per Tier 2 conventions (precedent: `TRACK_COMPLETION_tier2_autonomous_sandbox_20260616.md`), write a one-page end-of-track report with: - - Summary (1-2 sentences) - - Final state (5 fields: chronology.md, tracks.md, workflow.md, migration report, end-of-track report) - - Statistics (rows in chronology, batches in Phase 8, fixes applied, exceptions documented) - - Cross-check summary (per VC10/11/12 confirmation) - - User sign-off (reference to the migration report) - - Lessons learned (optional; "what would I do differently next time") - - HOW: Write the file. Commit. - - SAFETY: No new content beyond the summary; link to existing files. - - COMMIT: `docs(chronology): add end-of-track report` - - GIT NOTE: "Phase 10.2. Track complete. User sign-off recorded. All VCs satisfied." +- [ ] **Task 7.4: Tier 1 reviews each `low`-confidence row (OUTSIDE this plan)** + - WHERE: `tests/artifacts/chronology_v2_needs_review_worklist.md` (filled in by Tier 1) + - WHAT: Tier 1 (a different agent or a human) opens the worklist, reads each row's evidence, opens the source folder's `spec.md` and `git log`, and assigns a status. Tier 1's resolution is written in the worklist's "Tier 1 Resolution" column. + - HOW: This task is NOT executed by Tier 2. Tier 1 fills the worklist manually. + - SAFETY: Tier 1's defaults per FR6: in `tracks/` + ambiguous → `Active`; in `archive/` + 0 commits → `Special`; in `archive/` + ≥ 3 work commits + state_phase=0 → `Completed`; truly ambiguous → `Special` with note. + - NO COMMIT (Tier 1's work is gitignored). -- [ ] **Task 10.3: Update `conductor/tracks.md`** (estimate: 2 min) - - WHERE: `conductor/tracks.md` top-level entry for `chronology_20260619` - - WHAT: Add a line at the top of the file (or in the active section) noting the new track's completion. Mark it `[x]` completed. - - HOW: Edit the file; flip the status marker. +- [ ] **Task 7.5: Tier 2 applies Tier 1's resolutions to the staging file** + - WHERE: `conductor/chronology.md.staging` (updated by Tier 2) + - WHAT: Tier 2 reads the filled worklist and updates the staging file: for each `low`-confidence row, replace the status with Tier 1's resolved status. The "Needs Review" section is replaced with a "Tier 1 Resolutions" section showing each row's resolution note. + - HOW: Use `manual-slop_run_powershell` to parse the worklist and update the staging file. + - SAFETY: Only `low`-confidence rows are modified; `high`-confidence rows are preserved. + - VERIFY: `grep -c "Needs Review" conductor/chronology.md.staging` returns 0 (the section is renamed). `grep -c "Tier 1 Resolutions" conductor/chronology.md.staging` returns 1. + - NO COMMIT (staging file is gitignored). + - CHECKPOINT: `conductor(checkpoint): Phase 7 complete — Tier 1 review applied` + +--- + +## Phase 8: Promote v2 staging → canonical (FR1) + +Focus: Rename `chronology.md.staging` → `chronology.md`; this is the first time the v2 canonical file is committed. + +- [ ] **Task 8.1: Move staging to canonical, commit** + - WHERE: `conductor/chronology.md.staging` → `conductor/chronology.md` + - WHAT: `git mv conductor/chronology.md.staging conductor/chronology.md`. Then commit. + - HOW: `git mv` preserves git history (the v1 chronology is in `.broken-v1`; the v2 chronology is a new file). Verify the rename with `git status` (should show a rename of `.staging` to canonical + the removal of `.gitignore` line if applicable). + - SAFETY: Verify the file content matches the staging file before commit. Don't add the staging path to `.gitignore` if it would block the rename. + - VERIFY: `git log -1 --stat` shows the rename + the new file. `conductor/chronology.md` has 216 rows. + - COMMIT: `conductor(chronology): add v2 chronology.md with git-history classifier (FR1)` + - GIT NOTE: "Phase 8. v2 chronology.md promoted from staging to canonical. Uses git-history classifier (FR5) + per-row evidence + confidence level + 'Needs Review' section. v1 is preserved as .broken-v1 (VC14). Cross-check passed the 30% quality gate (VC10) and Tier 1 reviewed the 'Needs Review' queue (VC11)." + - CHECKPOINT: `conductor(checkpoint): Phase 8 complete — v2 chronology.md committed` + +--- + +## Phase 9: Write v2 addendum to migration report + end-of-track report (FR4, VC9) + +Focus: The v1 migration report gets a v2 addendum documenting the rewrite. The end-of-track report gets a v2 addendum. + +- [ ] **Task 9.1: Write v2 addendum to migration report** + - WHERE: `docs/reports/CHRONOLOGY_MIGRATION_20260619.md` (append) + - WHAT: A new section appended to the end of the v1 report: "v2 Rewrite Addendum (2026-06-21)". Contains: + - **Why the rewrite was needed** — link to `CHRONOLOGY_TRACK_HANDOVER_20260620.md` + 1-paragraph summary + - **v1 → v2 status diff** — table of all 216 rows showing the v1 status (stale) and v2 status (after the new classifier) + the git evidence per row. The diff is computed from `tests/artifacts/chronology_v2_evidence_log.md`. + - **Classifier confidence distribution** — counts: `high` / `low` / total; % of total in `Needs Review` + - **Tier 1 review log** — for each `low`-confidence row, the resolution note + - **Quality gate result** — PASS or ABORT + the ratio + - **User sign-off** — a checklist section for the user to fill in during Phase 10 + - HOW: Use `manual-slop_run_powershell` to extract the evidence log + format the diff. Hand-write the Tier 1 review log + the sign-off checklist. + - SAFETY: The addendum does not modify the v1 sections; it only appends. + - VERIFY: The file is now > 200 lines (v1 was 174; addendum is ~50-100 lines). + - COMMIT: `docs(chronology): write v2 rewrite addendum to migration report (FR4)` + - GIT NOTE: "Phase 9.1. v2 addendum appended to CHRONOLOGY_MIGRATION_20260619.md. Documents the rewrite rationale + v1→v2 status diff + classifier confidence + Tier 1 review log + quality gate result + user sign-off checklist." + +- [ ] **Task 9.2: Write v2 addendum to end-of-track report** + - WHERE: `docs/reports/TRACK_COMPLETION_chronology_20260619.md` (append) + - WHAT: A new section: "v2 Rewrite Addendum (2026-06-21)". Contains: + - Summary (1-2 sentences) + - v2 final state (5 fields: chronology.md, tracks.md, workflow.md, migration report, end-of-track report) + - v2 statistics (rows in v2, classifier confidence, Tier 1 review log size, quality gate result) + - Cross-check summary (per VC10/11/12/13 confirmation) + - User sign-off (reference to the migration report v2 addendum) + - HOW: Hand-write the addendum; reference the v1 sections. + - VERIFY: The file is updated. + - COMMIT: `docs(chronology): write v2 addendum to end-of-track report (VC9)` + - GIT NOTE: "Phase 9.2. v2 addendum to TRACK_COMPLETION. Documents the v2 final state + cross-check summary." + - CHECKPOINT: `conductor(checkpoint): Phase 9 complete — migration report + end-of-track report updated` + +--- + +## Phase 10: User sign-off (FR6 Stage 3) — PAUSE FOR USER + +Focus: User is the final quality gate. The track is not "done" until the user signs off. + +- [ ] **Task 10.1: Tier 2 PAUSES and reports to the user** + - WHERE: report to the user + - WHAT: Tier 2 reports the v2 state to the user: + ``` + Phase 9 complete. v2 chronology.md committed. + Quality gate: PASS (low/total ratio = X%) + Tier 1 review: N rows resolved. + + Files: + - conductor/chronology.md (v2; 216 rows) + - conductor/chronology.md.broken-v1 (v1 archive; 218 lines) + - docs/reports/CHRONOLOGY_MIGRATION_20260619.md (v1 + v2 addendum) + - docs/reports/TRACK_COMPLETION_chronology_20260619.md (v1 + v2 addendum) + + [PAUSE] Awaiting user sign-off. + ``` + - SAFETY: STOP. Do not proceed to Phase 11 until the user signs off. + - NO COMMIT (gate). + +- [ ] **Task 10.2: User reviews v2 + evidence log + Tier 1 resolutions (USER TASK, outside this plan)** + - WHERE: `conductor/chronology.md`, `tests/artifacts/chronology_v2_evidence_log.md`, `docs/reports/CHRONOLOGY_MIGRATION_20260619.md` (v2 addendum) + - WHAT: User confirms: (a) format correct, (b) every row has evidence, (c) Tier 1 resolutions are reasonable, (d) nothing missed. + - HOW: User fills the sign-off checklist in the v2 addendum of the migration report. + - SAFETY: This is a user task; Tier 2 does not execute it. + - NO COMMIT (gate). + +- [ ] **Task 10.3: Tier 2 records the user sign-off** + - WHERE: `docs/reports/CHRONOLOGY_MIGRATION_20260619.md` (v2 addendum) + - WHAT: Tier 2 records the user's sign-off in the v2 addendum (date + confirmation). If the user requested changes, loop back to the relevant phase. + - HOW: Use `manual-slop_edit_file` to add the sign-off line. + - VERIFY: The v2 addendum has a "User Sign-off" section with a date. + - COMMIT: `docs(chronology): record user sign-off in migration report v2 addendum (VC13)` + - GIT NOTE: "Phase 10.3. User sign-off recorded. All VCs satisfied." + - CHECKPOINT: `conductor(checkpoint): Phase 10 complete — user sign-off recorded` + +--- + +## Phase 11: Wrap-up + +Focus: Mark the track complete in `tracks.md`, `state.toml`, and `metadata.json`. + +- [ ] **Task 11.1: Mark track complete in `conductor/tracks.md`** + - WHERE: `conductor/tracks.md` (the entry for `chronology_20260619` in the "Active Tracks" table or the equivalent) + - WHAT: Flip the status marker from `[ ]` or `[~]` to `[x]`. Add a one-line note: "Completed 2026-06-21 (v2 rewrite after tier-2 failure report)." + - HOW: Use `manual-slop_edit_file` to find the entry and update it. - SAFETY: Don't touch other entries. - VERIFY: `grep -n "chronology_20260619" conductor/tracks.md` shows the entry with `[x]`. - COMMIT: `conductor(track): mark chronology_20260619 as complete in tracks.md` - - GIT NOTE: "Phase 10.3. Track marked complete in tracks.md." + - GIT NOTE: "Phase 11.1. Track marked complete in tracks.md." -- [ ] **Task 10.4: Update `state.toml` to completed** (estimate: 1 min) +- [ ] **Task 11.2: Update `state.toml` to completed** - WHERE: `conductor/tracks/chronology_20260619/state.toml` - - WHAT: Set `[meta].status = "completed"`, `[meta].current_phase = "complete"`, all phase statuses to `"completed"`, all task statuses to `"completed"`, all `[verification]` flags to `true`. - - HOW: Edit the file. + - WHAT: Set `[meta].status = "completed"`, `[meta].current_phase = "complete"`, `[meta].last_updated = ""`. Mark all 11 phases as `completed` with their checkpoint SHAs. Mark all tasks as `completed` with their commit SHAs. Mark all verification flags as `true`. + - HOW: Use `manual-slop_edit_file` to set the fields. The task commit SHAs come from `git log --oneline conductor/tracks/chronology_20260619/`. - SAFETY: Don't change the task descriptions; just flip the status fields. - - VERIFY: `uv run python -c "import tomllib; tomllib.load(open('conductor/tracks/chronology_20260619/state.toml','rb'))"` parses cleanly. - - COMMIT: `conductor(track): mark chronology_20260619 as completed` - - GIT NOTE: "Phase 10.4. Track complete. All VCs satisfied; user sign-off recorded." + - VERIFY: `uv run python -c "import tomllib; data = tomllib.load(open('conductor/tracks/chronology_20260619/state.toml','rb')); assert data['meta']['status'] == 'completed'; print('OK')"` exits 0. + - COMMIT: `conductor(plan): mark chronology_20260619 v2 as completed in state.toml` + - GIT NOTE: "Phase 11.2. Track complete. All 11 phases satisfied." + +- [ ] **Task 11.3: Update `metadata.json` to completed** + - WHERE: `conductor/tracks/chronology_20260619/metadata.json` + - WHAT: Set `"status": "completed"`. Update the `verification_criteria` array with the v2 VCs (replace v1's 12 VCs with v2's 14 VCs). Update the `risk_register` to include the v2 risks (replace v1's 9 risks with v2's 10 risks). Add a new `regressions_and_pre_existing_failures` entry: "v1 chronology had 167 rows with stale status; v2 fixed via git-history classifier (FR5)." + - HOW: Use `manual-slop_edit_file` to update the JSON. + - SAFETY: Keep the JSON valid (run `python -c "import json; json.load(open('metadata.json'))"` after the edit). + - VERIFY: `uv run python -c "import json; data = json.load(open('conductor/tracks/chronology_20260619/metadata.json')); assert data['status'] == 'completed'; assert len(data['verification_criteria']) >= 14; print('OK')"` exits 0. + - COMMIT: `conductor(plan): mark chronology_20260619 v2 as completed in metadata.json` + - GIT NOTE: "Phase 11.3. metadata.json updated with v2 VCs and risk register." + +- [ ] **Task 11.4: Final end-of-track commit** + - WHERE: working tree + - WHAT: Verify `git status` is clean except for the v2 file changes. Print a final summary of the 11 phases + their checkpoints. + - HOW: `git log --oneline conductor/tracks/chronology_20260619/ | head -30` (shows the v1 + v2 commit history). + - SAFETY: Don't commit unrelated working-tree changes. + - NO COMMIT (verification). + - CHECKPOINT: `conductor(checkpoint): Phase 11 complete — track fully complete` --- ## Summary -| Phase | Scope | Time estimate | Gate? | +| Plan Phase | Scope | Pause point? | Spec Phase | |---|---|---|---| -| 1 | Data extraction + script + tests | ~25 min | No | -| 2 | Generate draft | ~7 min | No | -| 3 | Prune tracks.md (3 sections) | ~17 min | No | -| 4 | Update workflow.md | ~3 min | No | -| 5 | Write migration report | ~10 min | No | -| 6 | User review of draft | user-paced | Yes | -| 7 | Promote draft to canonical | ~2 min | No | -| 8 | Per-row cross-check (165+ rows, 9 batches) | ~4.5 hours | Yes (HARD per user directive) | -| 9 | Completeness check | ~20 min | Yes (HARD) | -| 10 | User sign-off + end-of-track | ~20 min | Yes (HARD) | +| 1 | Archive v1 + verify state + reset state.toml | No | Spec Phase 1 | +| 2 | Rewrite `_classify_status` (TDD red+green) | No | Spec Phase 2 (part 1) | +| 3 | Update `extract_summary` + `format_markdown` (TDD red+green) | No | Spec Phase 2 (part 2) | +| 4 | Add quality gate script (TDD red+green) | No | Spec Phase 3 | +| 5 | Run classifier, generate staging | No | Spec Phase 4 | +| 6 | Quality gate (PASS or ABORT) | Yes (HARD) | Spec Phase 5 | +| 7 | Tier 1 reviews "Needs Review" queue | Yes (PAUSE for Tier 1) | +| 8 | Promote staging → canonical | No | +| 9 | Write v2 addendum to migration + end-of-track reports | No | +| 10 | User sign-off | Yes (PAUSE for user) | +| 11 | Wrap-up (mark complete) | No | -**Total: ~5.5 hours of focused work** (estimated scope, not time-bound; per the no-day-estimates rule). The cross-check (Phase 8) is the dominant cost; the user's "EVERY SINGLE ENTRY" mandate makes this non-negotiable. +**Total: 11 plan phases (10 spec phases; Plan Phase 2 = Spec Phase 2 part 1, Plan Phase 3 = Spec Phase 2 part 2; renumbering shifts from Plan Phase 4 onwards), 4 hard pause points (Plan Phase 6 quality gate, Plan Phase 7 Tier 1 review, Plan Phase 10 user sign-off, plus the Plan Phase 6 ABORT fallback), ~25 commits.** The cross-check is the dominant cost; the user's "EVERY SINGLE ENTRY" mandate is preserved at the semantic level via the per-row evidence log + the 3-stage protocol (FR6). ## Verification Criteria Recap -All 12 VCs from the spec must be satisfied for the track to be marked complete: -- VC1-VC5: File contents (FR1, FR2, FR3, FR4) — verified in Phases 3, 4, 5, 7. -- VC6: Sort order (FR1) — verified in Phase 7. -- VC7: Folder coverage (FR6 completeness) — verified in Phase 9. -- VC8: No `src/*.py` files created — verified by `git diff --stat` against the spec'd scope. -- VC9: End-of-track report — written in Phase 10.2. -- VC10: Per-row cross-check completed (FR6) — verified at end of Phase 8. -- VC11: Completeness check (FR6) — verified at end of Phase 9. -- VC12: User sign-off (FR6) — recorded in Phase 10.1. +All 14 VCs from the spec must be satisfied for the track to be marked complete: +- VC1: `chronology.md` v2 with 216 rows, 5-value status enum, per-row evidence + confidence +- VC2: `tracks.md` pruning intact (no regression) +- VC3: `workflow.md` 3-step convention present (no regression) +- VC4: migration report v2 addendum +- VC5: sorted newest first; Folder + Range + Evidence per row +- VC6: every folder has a row or documented exception +- VC7: Notable Non-Track Commits section preserved +- VC8: no new `src/*.py` files +- VC9: end-of-track v2 addendum +- VC10: quality gate PASS (or ABORT with manual fallback) +- VC11: "Needs Review" queue resolved by Tier 1 +- VC12: per-row evidence log (`tests/artifacts/chronology_v2_evidence_log.md`) +- VC13: user sign-off recorded +- VC14: v1 archive preserved as `.broken-v1` ## Cross-cutting safety -- **No day estimates in the report.** Per the project rule added 2026-06-16. -- **Per-task atomic commits.** Per `conductor/workflow.md` "Commit Guidelines" — one commit per task, no batching. -- **Git notes on every commit.** Per the project convention. -- **No `git restore` / `git checkout -- ` / `git reset`.** Per the HARD BAN in `AGENTS.md`. -- **No new `src/*.py` files.** Per `AGENTS.md` File Size and Naming Convention. The helper script lives in `scripts/audit/`; no `src/` changes. -- **No new `conductor/code_styleguides/*` files.** The 3-step convention is added to existing `workflow.md`, not a new styleguide. +- **No day estimates** (per `conductor/workflow.md` Tier 1 rule). +- **Per-task atomic commits** (per `conductor/workflow.md` Commit Guidelines). +- **Git notes on every commit** (per project convention). +- **No `git restore` / `git checkout -- ` / `git reset`** (per HARD BAN). +- **No new `src/*.py` files** (per `AGENTS.md` File Size and Naming Convention). +- **No new `conductor/code_styleguides/*` files** (the 3-step convention is in `workflow.md`, not a new styleguide). +- **TDD for all script changes** (Phases 2-4 are red+green cycles). +- **Tier 2 uses `uv run python scripts/run_tests_batched.py`** (never direct `uv run pytest`; per `conductor/workflow.md` "Tier 2 Autonomous Sandbox" rule #1). +- **Throw-away scripts go in `scripts/tier2/artifacts/chronology_20260619/`** (per `conductor/workflow.md` "Tier 2 Autonomous Sandbox" rule #4). +- **End-of-track report at `docs/reports/TRACK_COMPLETION_chronology_20260619.md`** (already exists; v2 addendum appended in Phase 9.2). +- **Default branch: `master`** (per `conductor/workflow.md` "Tier 2 Autonomous Sandbox" rule #2). +- **Preserve line endings on edit** (per `conductor/workflow.md` "Tier 2 Autonomous Sandbox" rule #3; this repo has mixed CRLF/LF).