Files

1046 lines
62 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:** 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:** 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 + 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` (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 -- <file>`, `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: Archive v1 + verify state of carried-forward work
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: Verify `tracks.md` pruning is intact**
- WHERE: `conductor/tracks.md`
- 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).
- [ ] **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 = "<today's date YYYY-MM-DD>"`. Reset all `[phases].phase_N.status` to `"pending"`, `checkpointsha` to `""`. Reset all `[tasks].t<N>_<M>.status` to `"pending"`, `commit_sha` to `""`. Reset all `[verification].<flag>` 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 2: Rewrite the helper script — git-history classifier (FR5, TDD red+green)
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 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
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")
```
- 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 3: Update `extract_summary` + add per-row evidence line to `format_markdown` (FR1, FR5)
Focus: The summary priority chain needs to reject metadata-field text (the v1 bug). The markdown output needs a per-row evidence line.
- [ ] **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 4: Add the quality gate script (FR7, TDD)
Focus: A new wrapper script that runs after the classifier's output and gates the cross-check on the confidence distribution.
- [ ] **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 5: Run the new classifier, generate v2 staging (FR6 Stage 1)
Focus: Run the script; verify the staging file has the per-row evidence + confidence + "Needs Review" section.
- [ ] **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).
- [ ] **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/<tracks|archive>/<id>/spec.md" | Select-Object -First 1` (verify summary source; should NOT start with `**`)
- `git log --oneline --reverse -- "conductor/<tracks|archive>/<id>/"` (verify init SHA)
- `git log -1 --format='%h' -- "conductor/<tracks|archive>/<id>/"` (verify end SHA)
- 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 6: Quality gate (FR7) — HARD GATE
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 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 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 7: Tier 1 reviews "Needs Review" queue (FR6 Stage 2) — PAUSE FOR TIER 1
Focus: Tier 1 (not the Tier 2 worker) reviews the `low`-confidence rows. **Tier 2 PAUSES here and reports to Tier 1.**
- [ ] **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 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 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 11.1. Track marked complete in tracks.md."
- [ ] **Task 11.2: Update `state.toml` to completed**
- WHERE: `conductor/tracks/chronology_20260619/state.toml`
- WHAT: Set `[meta].status = "completed"`, `[meta].current_phase = "complete"`, `[meta].last_updated = "<today's date>"`. 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; 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
| Plan Phase | Scope | Pause point? | Spec Phase |
|---|---|---|---|
| 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: 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 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** (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 -- <file>` / `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).