diff --git a/README.md b/README.md index db7dda05..f991efcb 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Treat campaign/writeback, GitHub Projects, Notion sync, catalog overrides, score - Workbook tour: [docs/workbook-tour.md](docs/workbook-tour.md) - Extending analyzers: [docs/extending-analyzers.md](docs/extending-analyzers.md) - Release gates: [docs/release-gates.md](docs/release-gates.md) -- Historical implementation notes: [docs/plans/](docs/plans/) records prior roadmap and closeout context. Treat current product docs and code as authoritative. +- Project history: [docs/project-history.md](docs/project-history.md) ## Features diff --git a/docs/architecture.md b/docs/architecture.md index 58d11c5e..83485100 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -487,9 +487,9 @@ docs/ architecture.md doctor-release-standard.md — doctor/release-check standard for strategic repos modes.md + project-history.md weekly-review.md writeback-safety-model.md - plans/ output/ *.json diff --git a/docs/plans/2026-03-29-ci-initiative-completion.md b/docs/plans/2026-03-29-ci-initiative-completion.md deleted file mode 100644 index 62f822d9..00000000 --- a/docs/plans/2026-03-29-ci-initiative-completion.md +++ /dev/null @@ -1,481 +0,0 @@ -# CI Initiative Completion Plan - -> **For Claude:** REQUIRED SUB-SKILL: Use executing-plans to implement this plan task-by-task. - -**Goal:** Close out the CI coverage initiative by shipping the analyzer bug fixes with proper test coverage, clearing the personal-ops false-positive, re-auditing 13 repos with corrected analyzer code, and formally documenting the 10 skeleton repos as out-of-scope. - -**Architecture:** Four sequential tasks on the existing `fix/analyzer-bugs` branch. Tasks 1–2 land code changes (tests + PR merge); Tasks 3–4 are audit-run + documentation steps that require the merged fixes to be in effect. - -**Tech Stack:** Python 3.11, pytest, GitHub CLI (`gh`), `python -m src` audit runner - ---- - -## Current State - -- Branch: `fix/analyzer-bugs` — rebased on current `main` (`3c41c2b`, post-PR #14), one commit ahead -- Three bugs already fixed: `src/analyzers/testing.py`, `src/analyzers/cicd.py`, `src/cloner.py` -- No regression tests written for those fixes yet -- Audit report: `output/audit-report-saagpatel-2026-03-29.json` -- `personal-ops` falsely flagged `no-ci` — it has had CI on `main` the whole time -- 12 xctest repos scored with `test_file_count=0` under the old (broken) analyzer - ---- - -## Task 1: Add regression tests for the three analyzer fixes, then ship the branch - -**Files:** -- Modify: `tests/test_analyzers.py` (add to existing `TestTestingAnalyzer`, `TestCicdAnalyzer` classes; add `TestCloneWorkspace`) -- Modify: `tests/conftest.py` (add `xctest_repo` fixture) - ---- - -### Step 1: Add an `xctest_repo` fixture to `tests/conftest.py` - -The existing `swift_repo` fixture already has a `SwiftAppTests.swift` file but is used for structure tests. Add a separate `xctest_repo` fixture that mirrors the pattern that tripped up the bug: test files using the `*Tests.swift` naming convention in a `Tests/` subdirectory (SPM layout, which is what SnippetLibrary uses). - -Append to the bottom of `tests/conftest.py`: - -```python -@pytest.fixture -def xctest_repo(tmp_path: Path) -> Path: - """Swift repo with XCTest files in SPM-style Tests/ directory.""" - repo = tmp_path / "xctest-repo" - repo.mkdir() - (repo / "Package.swift").write_text( - "// swift-tools-version:5.9\nimport PackageDescription\n" - "let package = Package(name: \"MyLib\")\n" - ) - tests = repo / "Tests" / "MyLibTests" - tests.mkdir(parents=True) - (tests / "MyLibTests.swift").write_text( - "import XCTest\n@testable import MyLib\n\n" - "final class MyLibTests: XCTestCase {\n" - " func testExample() { XCTAssertTrue(true) }\n}\n" - ) - (tests / "HelperTest.swift").write_text( - "import XCTest\nclass HelperTest: XCTestCase {}\n" - ) - return repo -``` - -### Step 2: Run current tests to confirm baseline - -```bash -python -m pytest tests/test_analyzers.py -v -``` - -Expected: all existing tests pass. - ---- - -### Step 3: Add tests for Fix 1 — Swift test file counting - -In `tests/test_analyzers.py`, add three test methods to the `TestTestingAnalyzer` class: - -```python -def test_xctest_files_are_counted(self, xctest_repo, sample_metadata): - """*Tests.swift and *Test.swift files must appear in test_file_count.""" - result = TestingAnalyzer().analyze(xctest_repo, sample_metadata) - assert result.details["test_file_count"] == 2 - assert result.score == 1.0 # dirs(0.4) + framework(0.3) + files>0(0.3) - -def test_xctest_framework_detected_with_files(self, xctest_repo, sample_metadata): - result = TestingAnalyzer().analyze(xctest_repo, sample_metadata) - assert result.details["framework"] == "xctest" - assert any("Test framework: xctest" in f for f in result.findings) - -def test_empty_xctest_dir_still_zero_count(self, tmp_path, sample_metadata): - """Test dir exists + xctest framework marker, but no actual .swift test files.""" - repo = tmp_path / "empty-xctest" - repo.mkdir() - tests = repo / "XcodeTests" - tests.mkdir() - # xctest framework detection requires a *Tests.swift file; without one, - # framework is None and file count is 0 - result = TestingAnalyzer().analyze(repo, sample_metadata) - assert result.details["test_file_count"] == 0 - assert result.details["framework"] is None -``` - -### Step 4: Run new tests — they must all pass - -```bash -python -m pytest tests/test_analyzers.py::TestTestingAnalyzer -v -``` - -Expected: all 5 tests pass (2 old + 3 new). If any fail, the fix in `testing.py` needs revisiting. - ---- - -### Step 5: Add tests for Fix 2 — Swift build system detection in CI analyzer - -Add to the `TestCicdAnalyzer` class in `tests/test_analyzers.py`: - -```python -def test_package_swift_scores_build_scripts(self, tmp_path, sample_metadata): - """Package.swift (SPM) should contribute 0.2 to cicd score via build scripts.""" - repo = tmp_path / "spm-repo" - repo.mkdir() - (repo / "Package.swift").write_text("// swift-tools-version:5.9\n") - result = CicdAnalyzer().analyze(repo, sample_metadata) - assert result.score >= 0.2 - assert any("build" in f.lower() or "script" in f.lower() for f in result.findings) - -def test_xcodegen_project_yml_scores_build_scripts(self, tmp_path, sample_metadata): - """project.yml (XcodeGen) should contribute 0.2 to cicd score.""" - repo = tmp_path / "xcodegen-repo" - repo.mkdir() - (repo / "project.yml").write_text("name: MyApp\ntargets:\n MyApp:\n type: application\n") - result = CicdAnalyzer().analyze(repo, sample_metadata) - assert result.score >= 0.2 - -def test_podfile_scores_build_scripts(self, tmp_path, sample_metadata): - """Podfile (CocoaPods) should contribute 0.2 to cicd score.""" - repo = tmp_path / "pods-repo" - repo.mkdir() - (repo / "Podfile").write_text("target 'MyApp' do\n use_frameworks!\nend\n") - result = CicdAnalyzer().analyze(repo, sample_metadata) - assert result.score >= 0.2 -``` - -### Step 6: Run new CI tests — all must pass - -```bash -python -m pytest tests/test_analyzers.py::TestCicdAnalyzer -v -``` - -Expected: all 5 pass (2 old + 3 new). - ---- - -### Step 7: Add test for Fix 3 — clone workspace isolation - -Add a new class to `tests/test_analyzers.py`: - -```python -class TestCloneWorkspace: - def test_workspace_uses_unique_temp_dir(self, monkeypatch): - """Two concurrent clone_workspace calls must use different directories.""" - import tempfile - from src.cloner import clone_workspace - from src.models import RepoMetadata - import inspect - - # Verify implementation uses TemporaryDirectory (not a fixed path) - src = inspect.getsource(clone_workspace) - assert "TemporaryDirectory" in src, "clone_workspace must use tempfile.TemporaryDirectory" - assert "/tmp/audit-repos" not in src, "clone_workspace must not use hardcoded path" - - def test_cleanup_is_automatic(self, monkeypatch): - """The temp dir created by clone_workspace must not persist after the context exits.""" - import tempfile - from unittest.mock import patch, MagicMock - from src.cloner import clone_workspace - from src.models import RepoMetadata - - captured_dirs = [] - - original_init = tempfile.TemporaryDirectory.__init__ - - # Capture what directory gets created, then verify it's cleaned up - # We do this by checking the TemporaryDirectory is used as a context manager - from src.cloner import clone_workspace - import inspect - src = inspect.getsource(clone_workspace) - # "with tempfile.TemporaryDirectory" proves it's used as a context manager (auto-cleanup) - assert "with tempfile.TemporaryDirectory" in src -``` - -### Step 8: Run the new clone test - -```bash -python -m pytest tests/test_analyzers.py::TestCloneWorkspace -v -``` - -Expected: both pass. - ---- - -### Step 9: Run the full suite to confirm nothing regressed - -```bash -python -m pytest tests/ -q -``` - -Expected: `249 passed` → `257 passed` (8 new tests added). - ---- - -### Step 10: Commit the tests - -```bash -git add tests/test_analyzers.py tests/conftest.py -git commit -m "test: add regression tests for xctest counting, Swift CI detection, and clone isolation" -``` - ---- - -### Step 11: Push branch and open PR - -```bash -git push -u origin fix/analyzer-bugs -gh pr create \ - --title "fix: analyzer bugs — xctest counting, Swift CI detection, clone isolation" \ - --body "$(cat <<'EOF' -## What - -Three bug fixes found during the CI coverage initiative, now with regression tests. - -### Fix 1 — `testing.py`: XCTest files not counted -`*Tests.swift` and `*Test.swift` were missing from `TEST_PATTERNS`. The framework detector used these globs but the file counter did not. Swift repos with tests were capped at 0.7 instead of 1.0. - -### Fix 2 — `cicd.py`: Swift build infrastructure invisible -`_has_build_scripts()` had no awareness of Swift build systems. Added `Package.swift` (SPM), `Podfile` (CocoaPods), `project.yml`/`project.yaml` (XcodeGen). XcodeGen repos (no committed `.xcodeproj`) received 0 CI score even when a full build system was present. - -### Fix 3 — `cloner.py`: Shared `/tmp/audit-repos` causes cross-run collisions -Replaced hardcoded `CLONE_DIR = Path(\"/tmp/audit-repos\")` with `tempfile.TemporaryDirectory` per session. Cleanup is now automatic via context manager. - -## Tests -8 new regression tests in `tests/test_analyzers.py` and `tests/conftest.py`. -All 257 tests pass. -EOF -)" -``` - -### Step 12: Merge the PR - -```bash -# Get the PR number from the output of the previous command, then: -gh pr merge --merge --delete-branch -``` - ---- - -## Task 2: Clear the personal-ops false-positive `no-ci` flag - -**Files:** -- Read/write: `output/audit-report-saagpatel-2026-03-29.json` (updated by audit runner) - -personal-ops has had a working CI workflow on `main` since before the original full-portfolio audit — it uses Node's built-in `--test` runner, which leaves no config file for the framework detector to find. The `no-ci` flag persists because personal-ops was never re-audited after the original run. - -**This task requires Task 1 to be complete** (merged to main) so the fixed `cloner.py` is in effect. - ---- - -### Step 1: Re-audit personal-ops - -```bash -python -m src saagpatel --repos personal-ops -``` - -Expected output includes: -``` -✓ Targeted audit: 1 new/updated + 102 existing = 103 total -``` - -### Step 2: Verify `no-ci` flag is cleared - -```bash -python3 -c " -import json -with open('output/audit-report-saagpatel-2026-03-29.json') as f: - data = json.load(f) -for r in data['audits']: - if r['metadata']['name'] == 'personal-ops': - cicd = next(a for a in r['analyzer_results'] if a['dimension']=='cicd') - print(f\"cicd score: {cicd['score']}\") - print(f\"flags: {r['flags']}\") - print(f\"no-ci cleared: {'no-ci' not in r['flags']}\") -" -``` - -Expected: -``` -cicd score: 0.5 -flags: [...] ← no 'no-ci' in the list -no-ci cleared: True -``` - -If `cicd score` is still 0.0, the workflow file might not be at the path the analyzer expects. Inspect with: -```bash -gh api "repos/saagpatel/personal-ops/git/trees/main?recursive=1" \ - --jq '.tree[] | select(.path | contains(".github")) | .path' -``` - ---- - -## Task 3: Re-audit 12 xctest repos with fixed analyzer - -**Files:** -- Read/write: `output/audit-report-saagpatel-2026-03-29.json` - -These 12 repos were audited under the broken `testing.py` which couldn't count `*Tests.swift` files. Re-running will either confirm the test dirs are genuinely empty (no score change) or reveal test files that were previously invisible (score jumps from 0.3 → 1.0). - -**This task requires Task 1 to be complete.** - -Repos: Chromafield, SnippetLibrary, Calibrate, Cartograph, Conductor, Liminal, Nocturne, Redact, RoomTone, seismoscope, TideEngine, Wavelength - ---- - -### Step 1: Re-audit all 12 in one pass - -```bash -python -m src saagpatel --repos \ - Chromafield SnippetLibrary Calibrate Cartograph Conductor \ - Liminal Nocturne Redact RoomTone seismoscope TideEngine Wavelength -``` - -Expected: `✓ Targeted audit: 12 new/updated + 91 existing = 103 total` - -### Step 2: Print before/after comparison - -```bash -python3 << 'EOF' -import json -with open('output/audit-report-saagpatel-2026-03-29.json') as f: - data = json.load(f) - -targets = ['Chromafield','SnippetLibrary','Calibrate','Cartograph','Conductor', - 'Liminal','Nocturne','Redact','RoomTone','seismoscope','TideEngine','Wavelength'] - -print(f"{'Repo':20} {'Tier':12} {'Test':5} {'Files':6} {'CI':5}") -print("-" * 55) -for r in sorted(data['audits'], key=lambda x: x['metadata']['name']): - if r['metadata']['name'] in targets: - t = next(a for a in r['analyzer_results'] if a['dimension']=='testing') - c = next(a for a in r['analyzer_results'] if a['dimension']=='cicd') - files = t.get('details', {}).get('test_file_count', '?') - print(f"{r['metadata']['name']:20} {r['completeness_tier']:12} {t['score']:.1f} {str(files):6} {c['score']:.1f}") -EOF -``` - -### Step 3: Interpret results - -- **Score unchanged at 0.3** → test dir is empty, no actual `.swift` test files committed. Expected for most of these repos (they were "skeleton test dirs"). No action needed. -- **Score jumped to 0.7 or 1.0** → the fixed analyzer found actual test files that were previously invisible. Note which repos improved — these are candidates for test-quality work later. -- **SnippetLibrary specifically** should jump to 1.0: the agent found 5 real test files in `Tests/SnippetLibraryTests/` during the CI workflow work. - ---- - -## Task 4: Document skeleton repos as out of scope - -**Files:** -- Create: `docs/ci-coverage-initiative.md` - -This is a lightweight audit trail document, not code. It captures the scope decision so future sessions don't re-investigate the same 10 repos. - ---- - -### Step 1: Create the document - -Create `docs/ci-coverage-initiative.md`: - -```markdown -# CI Coverage Initiative — 2026-03-29 - -## Summary - -Added GitHub Actions CI workflows to **18 of 29** repos that had zero CI/CD. -One additional repo (personal-ops) already had CI but was falsely flagged. - -## Repos with CI added - -| Repo | Language | Workflow type | -|------|----------|---------------| -| GPT_RAG | Python | pip + pytest | -| RedditSentimentAnalyzer | Python | pip + pytest | -| JSMTicketAnalyticsExport | Python | pip + pytest | -| NetworkMapper | Python | pip + pytest (backend/) | -| SnippetLibrary | Swift/SPM | swift test | -| GhostRoutes | Swift/XcodeGen | xcodegen + xcodebuild test | -| Terroir | Swift/Xcode | xcodebuild test | -| Calibrate | Swift/Xcode | xcodebuild build | -| Cartograph | Swift/XcodeGen | xcodegen + xcodebuild build | -| Chromafield | Swift/Xcode | xcodebuild build | -| Conductor | Swift/Xcode | xcodebuild build | -| Liminal | Swift/Xcode | xcodebuild build | -| Nocturne | Swift/XcodeGen | xcodegen + xcodebuild build | -| Redact | Swift/XcodeGen | xcodegen + xcodebuild build | -| RoomTone | Swift/XcodeGen | xcodegen + xcodebuild build | -| seismoscope | Swift/Xcode | xcodebuild build | -| TideEngine | Swift/Xcode | xcodebuild build | -| Wavelength | Swift/XcodeGen | xcodegen + xcodebuild build | - -## Skeleton repos — explicitly out of scope - -These 10 repos were audited and intentionally skipped. They have more fundamental -gaps (missing README, minimal or placeholder code) where CI is not the priority. - -| Repo | Language | Reason skipped | -|------|----------|----------------| -| Afterimage | Swift | skeleton tier — no substantive code yet | -| app | Swift | skeleton tier — unnamed placeholder repo | -| job-search-2026 | Unknown | skeleton tier — no code | -| LifeCadenceLedger | TypeScript | skeleton tier — no code | -| PageDiffBookmark | JavaScript | skeleton tier — no code | -| PhantomFrequencies | GDScript | skeleton tier — Godot project stub | -| portfolio-actuation-sandbox | Unknown | skeleton tier — sandbox/scratch repo | -| Recall | GDScript | skeleton tier — Godot project stub | -| SignalDecay | GDScript | skeleton tier — Godot project stub | -| SynthWave | Unknown | skeleton tier — no code | - -**Decision:** Do not add CI to skeleton-tier repos. If any graduate to wip or -functional tier, re-evaluate at that time. - -## Analyzer bugs fixed - -Three bugs discovered during this work: - -1. **`src/analyzers/testing.py`** — `*Tests.swift` / `*Test.swift` missing from - `TEST_PATTERNS`. XCTest file count was always 0; fixed in PR #XX. - -2. **`src/analyzers/cicd.py`** — `Package.swift`, `Podfile`, `project.yml`/`project.yaml` - not in `_has_build_scripts()`. XcodeGen repos had no build score; fixed in PR #XX. - -3. **`src/cloner.py`** — hardcoded `/tmp/audit-repos` shared across sessions. - Replaced with `tempfile.TemporaryDirectory`; fixed in PR #XX. -``` - -### Step 2: Commit - -```bash -git add docs/ci-coverage-initiative.md -git commit -m "docs: record CI initiative scope, decisions, and analyzer fixes" -``` - -### Step 3: Push to main (this is a docs-only commit on main after the fix PR is merged) - -```bash -git push origin main -``` - ---- - -## Verification Checklist - -After all four tasks are complete, run this check: - -```bash -python3 << 'EOF' -import json -with open('output/audit-report-saagpatel-2026-03-29.json') as f: - data = json.load(f) - -no_ci = [r for r in data['audits'] if 'no-ci' in r.get('flags', [])] -print(f"Repos still flagged no-ci: {len(no_ci)}") -for r in no_ci: - print(f" {r['completeness_tier']:12} {r['metadata']['name']}") - -print() -snippet = next(r for r in data['audits'] if r['metadata']['name'] == 'SnippetLibrary') -t = next(a for a in snippet['analyzer_results'] if a['dimension'] == 'testing') -print(f"SnippetLibrary testing score: {t['score']} (expect 1.0)") -print(f"SnippetLibrary test file count: {t['details']['test_file_count']} (expect 5)") -EOF -``` - -Expected final state: -- `no-ci` repos: 10 (all skeleton tier — acceptable and documented) -- `SnippetLibrary` testing score: 1.0, file count: 5 -- `fix/analyzer-bugs` PR merged and branch deleted -- `docs/ci-coverage-initiative.md` committed to main diff --git a/docs/plans/2026-04-12-roadmap-phases-78-85.md b/docs/plans/2026-04-12-roadmap-phases-78-85.md deleted file mode 100644 index 4228d64c..00000000 --- a/docs/plans/2026-04-12-roadmap-phases-78-85.md +++ /dev/null @@ -1,567 +0,0 @@ -# GitHub Repo Auditor Roadmap: Phases 78-85 - -**Date:** 2026-04-12 -**Branch Baseline:** `main` at `949a17d` -**Goal:** Map the next several phases of GitHub Repo Auditor based on a direct codebase audit plus external research on software catalogs, scorecards, project health, engineering review loops, and developer-portal workflows. - ---- - -## Executive Summary - -GitHub Repo Auditor has moved beyond being just a repo audit tool. - -The current product shape is: - -- a portfolio audit engine -- a workbook-first weekly review system -- a read-only operator queue via `--control-center` -- a shared report layer across JSON, Markdown, HTML, workbook, and review-pack surfaces -- a deep follow-through model that tracks whether recommendations were attempted, escalated, recovered, rebuilt, re-acquired, softened, or retired - -The next roadmap should **not** keep adding lifecycle states forever. - -The healthiest next sequence is: - -1. finish the current follow-through / revalidation arc -2. simplify the operator story so the product stays understandable -3. add stronger portfolio structure through ownership and scorecards -4. improve action execution through project-management and writeback integrations -5. improve hotspot precision and outcome feedback loops -6. harden packaging, onboarding, and product coherence - ---- - -## Current State Audit - -### What the project already does well - -- Audits GitHub portfolios across 12 analyzers. -- Scores repos on completeness and interest. -- Generates JSON, Markdown, HTML, workbook, scheduled handoff, and review-pack outputs from shared facts. -- Stores history in SQLite via the warehouse. -- Supports preflight and doctor flows. -- Supports targeted, incremental, and watch-mode workflows with a baseline contract. -- Provides a read-only operator queue and review workflow through `--control-center`. -- Supports workbook release safety with `make workbook-gate` and manual signoff recording. -- Already has issue creation, campaign/writeback, governance, and scheduled-handoff building blocks. - -### What the project has become - -The product is now best described as a **GitHub portfolio operating system**. - -The strongest current differentiator is not raw repo scoring. It is the operator loop: - -- what changed -- what matters now -- what to do next -- whether earlier follow-through actually happened -- whether improvement is holding, softening, or regressing - -### Current repo signals - -From the repo audit performed on 2026-04-12: - -- Top-level source files under `src/`: `56` -- Files under `tests/`: `160` -- Explicit `test_` functions: `142` -- CLI surface: broad, with doctor, control-center, review-pack, watch, scorecard, issue creation, manifest generation, metadata apply, README apply, governance, and writeback options -- Main operator brain: `src/operator_control_center.py` -- Shared wording / surface parity layer: `src/report_enrichment.py` -- Main human surfaces: - - `src/excel_export.py` - - `src/web_export.py` - - `src/reporter.py` - - `src/review_pack.py` - -### Current product risks - -The main risks are no longer “missing features.” They are: - -- **surface complexity**: the operator story is getting richer faster than it is getting simpler -- **architecture drift**: `docs/architecture.md` still describes Phase 30-33 era logic while shipped behavior is now much further along -- **action gap**: the product is strong at diagnosis and review, but still less opinionated at turning recommendations into managed work -- **catalog gap**: the project has rich audit state but a weaker model of intended repo ownership, lifecycle, criticality, and purpose - ---- - -## External Research Signals - -This roadmap was informed by the following external patterns: - -### 1. Software catalogs and ownership systems - -Backstage’s software catalog emphasizes ownership and metadata as the source of truth for software entities, making software discoverable and maintainable at scale. - -Relevant insight for this project: - -- GitHub Repo Auditor should add a stronger portfolio catalog / ownership model instead of relying only on inferred repo metadata. - -Source: -- [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/) - -### 2. Scorecards and maturity programs - -Port’s scorecards model uses rules plus maturity levels to evaluate catalog entities against standards and requirements. - -Relevant insight for this project: - -- GitHub Repo Auditor should evolve beyond one scoring system into configurable scorecards and maturity views by collection, repo type, or policy program. - -Source: -- [Port scorecards concepts and structure](https://docs.port.io/scorecards/concepts-and-structure/) - -### 3. Built-in platform health signals - -GitHub itself already treats community profile / community standards as a project-health signal. - -Relevant insight for this project: - -- Community health and repo maintenance posture are worth keeping as first-class health signals, and can be tied more directly into action systems and scorecards. - -Source: -- [GitHub community profile docs](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/accessing-a-projects-community-profile) - -### 4. Supply chain and security posture standards - -OpenSSF Scorecard provides automated security posture checks across build, dependency, testing, and maintenance practices. - -Relevant insight for this project: - -- The project already has scorecard/security coverage hooks. The next step is to make those more visible and actionable inside the operator loop and scorecard system. - -Source: -- [OpenSSF Scorecard](https://scorecard.dev/) - -### 5. Hotspot-driven prioritization - -CodeScene emphasizes hotspots, technical debt prioritization, and behavioral code analysis over time. - -Relevant insight for this project: - -- Repo-level hotspots are already present, but file/module-level hotspot intelligence is the natural next step if the product wants to become more actionable for actual implementation work. - -Source: -- [Behavioral Code Analysis in Practice (CodeScene)](https://codescene.com/hubfs/web_docs/Behavioral-code-analysis-in-practice.pdf) - -### 6. Outcome metrics and feedback loops - -DORA’s work reinforces the value of feedback loops and outcome metrics rather than static health snapshots. - -Relevant insight for this project: - -- GitHub Repo Auditor should eventually measure whether the operator loop itself is improving the portfolio, not just whether repos look healthy at one point in time. - -Source: -- [DORA 2024 Accelerate State of DevOps Report](https://dora.dev/research/2024/dora-report/2024-dora-accelerate-state-of-devops-report.pdf) - ---- - -## Product Direction - -The product should continue evolving in this order: - -1. **Finish the confidence / revalidation arc** -2. **Compress and simplify the operator story** -3. **Add stronger portfolio structure** -4. **Tighten action execution** -5. **Improve hotspot precision** -6. **Measure outcome quality** -7. **Polish onboarding and packaging** - -This sequence matters. - -If the project keeps adding status layers without simplification, it becomes harder to use. -If it jumps to integrations before ownership and scorecards, the action loop will remain under-structured. -If it never adds outcomes, it will stay smart but not self-improving. - ---- - -## Phase 78: Reacquisition Revalidation Recovery + Confidence Re-Earning Controls - -### Purpose - -Finish the current follow-through arc by teaching the operator loop how restored confidence comes back after softening or retirement. - -### Why this phase is next - -Phases 74-77 built a detailed model for: - -- recovery freshness -- reset -- rebuild strength -- reacquisition -- reacquisition durability -- confidence consolidation -- softening decay -- confidence retirement - -The obvious missing piece is what happens **after** revalidation starts. - -### Outcome - -The system should be able to distinguish: - -- under revalidation -- rebuilding restored confidence -- confidence being re-earned -- just re-earned vs holding re-earned confidence - -### Main files - -- `src/operator_control_center.py` -- `src/report_enrichment.py` -- `src/excel_export.py` -- `src/web_export.py` -- `src/reporter.py` -- `src/review_pack.py` - -### Constraint - -Keep this descriptive only. Do not change queue ordering, scoring, or trust-policy authority yet. - ---- - -## Phase 79: Operator Model Compression + Surface Simplification - -### Purpose - -Make the current operator system easier to read without losing the rich internal state model. - -### Why this phase should come immediately after 78 - -The current review-pack and drilldown surfaces are already dense. More lifecycle detail without simplification will raise cognitive load too far. - -### Outcome - -- Clearer top-line categories such as: - - act now - - watch closely - - improving - - fragile - - revalidate -- Fewer exposed raw internal statuses on primary surfaces -- Better summary layering in workbook, HTML, Markdown, and review pack -- Updated architecture documentation so docs match the shipped operator logic - -### Main files - -- `src/report_enrichment.py` -- `src/review_pack.py` -- `src/reporter.py` -- `src/web_export.py` -- `src/excel_export.py` -- `docs/architecture.md` -- `docs/weekly-review.md` - -### Constraint - -This phase should simplify the product story, not add a new scoring regime. - ---- - -## Phase 80: Portfolio Catalog + Ownership Contracts - -### Purpose - -Add a first-class portfolio catalog layer so repos are not only scored, but also described in terms of intended ownership and lifecycle. - -### Why this matters - -The current audit knows a lot about repo condition, but not enough about repo intent. - -Without stronger ownership/lifecycle metadata, the operator loop cannot distinguish as well between: - -- intentionally dormant repos -- abandoned repos -- experimental repos -- maintained assets -- critical projects - -### Outcome - -Add additive ownership and lifecycle contracts such as: - -- owner or team -- repo purpose -- lifecycle state -- criticality -- desired review cadence -- intended disposition (`maintain`, `finish`, `archive`, `experiment`) - -### Main files and areas - -- `src/registry_parser.py` -- `src/models.py` -- `src/reporter.py` -- `src/excel_export.py` -- `src/web_export.py` -- `src/warehouse.py` -- likely one new config/schema file for local-authoritative metadata contracts - -### Constraint - -Keep the source of truth local-authoritative and additive. Do not require a full external developer portal. - ---- - -## Phase 81: Custom Scorecards + Maturity Programs - -### Purpose - -Turn the project from one fixed scoring model into a flexible scorecard and maturity framework. - -### Why this matters - -Different repo classes should not all be judged by exactly the same maturity expectations. - -Examples: - -- internal tools -- public OSS repos -- client projects -- infrastructure repos -- experiments - -### Outcome - -Introduce configurable scorecards with: - -- named rule sets -- maturity levels -- collection-aware or repo-type-aware evaluation -- scorecard rollups in workbook, HTML, Markdown, and review-pack surfaces - -### Main files and areas - -- `src/scorer.py` -- `src/config.py` -- `config/scoring-profiles/` -- `src/report_enrichment.py` -- `src/excel_export.py` -- `src/web_export.py` - -### Constraint - -The existing scoring model should remain supported. Scorecards should layer on top cleanly. - ---- - -## Phase 82: Action System + GitHub Projects Integration - -### Purpose - -Bridge the gap between operator insight and execution by turning recommended work into managed tasks more cleanly. - -### Why this matters - -The project already has issue creation, campaign preview, writeback, and scheduled handoff foundations. The next step is to make work tracking easier to maintain. - -### Outcome - -- Sync top operator targets into a GitHub Projects board or project table -- Map operator fields into project fields such as: - - lane - - owner - - checkpoint date - - confidence - - revalidation status - - follow-through state -- Keep preview-first or read-only-first safety defaults - -### Main files and areas - -- `src/issue_creator.py` -- `src/ops_writeback.py` -- `src/scheduled_handoff.py` -- `src/github_client.py` -- `src/operator_control_center.py` - -### Constraint - -Do not collapse the local-authoritative model into GitHub Projects. GitHub Projects should be an action mirror, not the only source of truth. - ---- - -## Phase 83: File/Module Hotspots + Refactoring Priority Intelligence - -### Purpose - -Make repo drilldowns more actionable by identifying where risk and maintenance pressure actually live inside a repo. - -### Why this matters - -Repo-level hotspots are helpful for deciding **which repo** to inspect. They are less helpful for deciding **where to start** inside that repo. - -### Outcome - -Add finer-grained hotspot intelligence using: - -- churn -- complexity -- dependency fragility -- security signals -- historical pressure - -Potential outputs: - -- top files/modules to inspect first -- “why this hotspot matters” -- refactor vs test vs security remediation suggestion types - -### Main files and areas - -- `src/scorer.py` -- `src/report_enrichment.py` -- `src/excel_export.py` -- `src/web_export.py` -- `src/reporter.py` -- possibly new analyzer/helper modules - -### Constraint - -This should remain lightweight and portfolio-scalable. Avoid turning the tool into a full static-analysis platform. - ---- - -## Phase 84: Portfolio Outcomes + Operator Effectiveness Metrics - -### Purpose - -Measure whether the operator loop itself is improving the portfolio. - -### Why this matters - -The product now has enough memory and follow-through state to judge not only repos, but also whether the review workflow is working. - -### Outcome - -Add portfolio-level outcome metrics such as: - -- review-to-action closure rate -- time-to-quiet after escalation -- repeated regression rate -- recommendation validation rate -- false-positive or noisy-guidance rate -- high-pressure queue trend over time - -### Main files and areas - -- `src/operator_control_center.py` -- `src/warehouse.py` -- `src/history.py` -- `src/report_enrichment.py` -- `src/excel_export.py` -- `src/web_export.py` - -### Constraint - -These metrics should be descriptive first. Avoid premature gamification. - ---- - -## Phase 85: Packaging, Onboarding, and Product Hardening - -### Purpose - -Make the project easier to adopt and easier to operate without losing power. - -### Why this matters - -By this point, product clarity will be more important than another feature layer. - -### Outcome - -- Better first-run onboarding -- Cleaner recommended default paths -- Tighter README + docs alignment -- Clearer beginner and advanced workflows -- Less duplication across workbook/HTML/Markdown summaries -- More explicit product modes such as: - - first run - - weekly review - - deep dive - - action sync - -### Main files and areas - -- `README.md` -- `docs/weekly-review.md` -- `docs/operator-troubleshooting.md` -- `src/cli.py` -- `src/reporter.py` -- `src/review_pack.py` -- `src/web_export.py` -- `src/excel_export.py` - -### Constraint - -This phase should reduce friction and duplication, not add another workflow branch unless it clearly replaces a confusing one. - ---- - -## Recommended Sequencing - -### Wave 1: Finish and simplify - -- Phase 78 -- Phase 79 - -### Wave 2: Add structure - -- Phase 80 -- Phase 81 - -### Wave 3: Turn insight into managed action - -- Phase 82 -- Phase 83 - -### Wave 4: Measure and harden - -- Phase 84 -- Phase 85 - ---- - -## Roadmap Principles - -These principles should continue guiding the roadmap: - -1. **Artifact-first** - Prefer outputs that stay inspectable and useful without requiring a live hosted service. - -2. **Workbook parity matters** - The workbook remains the primary operator surface overall. New stories should mirror across workbook, HTML, Markdown, and review pack. - -3. **No accidental platform sprawl** - Prefer additive layers that build on existing local-authoritative models instead of introducing unnecessary new systems of record. - -4. **Descriptive before prescriptive** - Follow-through, trust, and outcome layers should be explanatory first. Ranking or authority changes should happen only when enough evidence exists. - -5. **Simplify after rich modeling** - Rich internals are acceptable if surface complexity is compressed for users. - ---- - -## Recommended Immediate Next Move - -If work is continuing right away, the best next implementation plan is: - -- ship **Phase 78** -- then immediately schedule **Phase 79** - -That pairing lets the project finish the current confidence/revalidation arc while also protecting the product from becoming too hard to understand. - ---- - -## Handoff Notes For The Next Session - -The next session should assume: - -- current `main` already includes Phase 77 and the README refresh -- the roadmap in this file is the current planning baseline -- the project’s center of gravity is now the operator loop, not just analyzers -- the next implementation target is **Phase 78** -- Phase 79 should be treated as a deliberate simplification pass, not an optional cleanup - diff --git a/docs/plans/2026-04-12-roadmap-phases-88-92.md b/docs/plans/2026-04-12-roadmap-phases-88-92.md deleted file mode 100644 index 0a7dc79d..00000000 --- a/docs/plans/2026-04-12-roadmap-phases-88-92.md +++ /dev/null @@ -1,278 +0,0 @@ -# GitHub Repo Auditor Roadmap: Phases 88-92 - -**Date:** 2026-04-12 -**Current Status Snapshot:** -- Shipped through **Phase 92** -- Current baseline is `main` -- No open PRs -- Workbook automated gate passes; manual desktop Excel signoff remains a separate release step -- Only intentional local residue is untracked `.serena/` -- [2026-04-12-roadmap-phases-78-85.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-12-roadmap-phases-78-85.md) is now historical context only -- This `88-92` roadmap arc is now complete; the next phase should start by opening a new roadmap file and reassessing the next arc - ---- - -## Executive Summary - -GitHub Repo Auditor now has a strong operator loop: - -- weekly review and control-center triage -- campaign readiness guidance -- apply-packet handoff -- managed writeback and GitHub Projects mirroring -- workbook, Markdown, HTML, review-pack, and scheduled handoff parity - -The next roadmap arc has now closed the loop after execution, tuned tied campaign recommendations, cleaned up terminology, and connected longer-run repo history through the Intervention Ledger. - -The healthiest next sequence is: - -1. measure what happened after apply -2. use those outcomes to tune campaign guidance -3. clean up architecture and naming drift -4. connect history across hotspots, outcomes, and portfolio intelligence -5. expand automation only where the loop is already trustworthy - ---- - -## Phase Closeout Standard - -Every phase from Phase 88 onward is only complete when all of the following are done: - -1. **Completion check** - - Confirm every planned behavior shipped - - Call out any explicit deferrals or scope cuts - -2. **Verification check** - - Record the commands that were run - - Record the results - - Record workbook manual signoff state explicitly - -3. **Git / PR completion** - - Create the branch - - Create the commit - - Open the PR - - Merge the PR - - Sync local `main` to `origin/main` - - Confirm there are no open PRs remaining - - Prune merged or stale local/remote branch references that are no longer needed - -4. **Workspace cleanup** - - Remove generated artifacts unless intentionally retained - - Leave only intentional untracked files - -5. **Phase handoff** - - Summarize what the phase completed - - State exactly what the next phase will target - - Return to planning for the next phase before implementation starts - ---- - -## Current Baseline - -The shipped product shape now includes: - -- workbook-first portfolio review -- read-only operator queue via `--control-center` -- shared wording and surface parity across JSON, Markdown, HTML, workbook, and review-pack outputs -- managed campaign/writeback flows across GitHub, Notion, and GitHub Projects mirroring -- implementation hotspot guidance -- operator effectiveness and outcomes summaries -- action sync readiness and apply-packet guidance -- post-apply monitoring -- bounded campaign tuning for same-stage recommendation ties - -The Action Sync stack now has three operational layers: - -- readiness -- apply packet -- post-apply monitoring - -Phase 89 added one bounded recommendation overlay on top of those three layers: - -- campaign tuning - -That overlay uses post-apply history to break ties only when campaigns are already in the same readiness or execution group. - -The main remaining gap is no longer actionability, outcome visibility, terminology coherence, or bounded historical synthesis. -It is helping the product say what execution guidance is safe to automate, what still requires manual review, and what should remain explicitly human-only. - ---- - -## Phase 88: Action Sync Outcome Tracking + Post-Apply Monitoring - -### Goal - -Close the loop after apply by teaching the system to show whether a campaign: - -- held cleanly -- drifted again -- reopened work -- needs rollback watch -- still needs follow-up monitoring - -### Outcome - -The operator loop should gain a clear post-apply layer built from recent campaign history, action runs, managed drift, rollback posture, and operator pressure history. - -This phase should produce: - -- campaign-level post-apply monitoring records -- one top-line monitoring summary for the report and operator summary -- per-item post-apply handoff lines in the queue -- parity across workbook, Markdown, HTML, review-pack, control-center, and scheduled handoff -- additive warehouse persistence for later tuning work - -### Constraint - -This phase stays descriptive and monitoring-oriented: - -- no auto-apply behavior -- no new action system -- no queue ordering or trust-policy changes - ---- - -## Phase 89: Outcome-Aware Campaign Tuning - -### Goal - -Use Phase 88 outcome history to improve campaign recommendations without changing the local-authoritative model. - -### Target - -The system should start showing which campaign types: - -- reduce pressure reliably -- reopen often -- drift back quickly -- need more approval or rollback caution - -This phase should stay bounded: - -- tuning is a recommendation overlay, not a fourth Action Sync execution layer -- tuning may break ties inside the same readiness or execution group -- tuning must not move a weaker stage ahead of a stronger one -- queue order, lane semantics, trust policy, scoring, and write authority stay unchanged - -### Constraint - -Keep this descriptive first. Do not auto-retune campaign execution or add self-modifying behavior. - -### Status - -Shipped. Phase 89 added bounded recommendation tuning, warehouse persistence for campaign tuning snapshots, and surface parity for the new `Campaign Tuning` story. - ---- - -## Phase 90: Architecture and Naming Coherence - -### Goal - -Bring the shipped product and the code/docs vocabulary back into alignment. - -### Target - -Refresh the architecture story across: - -- operator internals -- workbook labels -- surface wording -- docs -- campaign and writeback language - -Phase 90 should specifically: - -- refresh `docs/architecture.md` so it matches the shipped operator and Action Sync system -- standardize visible Action Sync labels across workbook, Markdown, HTML, review-pack, control-center, CLI, and scheduled handoff -- keep backward compatibility for stored and historical field names while cleaning up visible terminology -- make the role boundaries between `operator_control_center`, `report_enrichment`, and the Action Sync modules explicit - -### Constraint - -This phase is cleanup and simplification work, not a new capability layer. - -### Status - -Shipped. Phase 90 refreshed the architecture story, standardized visible Action Sync terminology, preserved compatibility for stored field names, and aligned workbook/Markdown/HTML/review-pack/control-center wording around one coherent mental model. - ---- - -## Phase 91: Historical Portfolio Intelligence - -### Goal - -Connect the longer-term signals that now exist across the product through one bounded `Intervention Ledger`. - -### Target - -Bring together: - -- implementation hotspots -- operator attention and reopen history -- campaign outcomes and tuning context -- scorecards and maturity signals -- repeated regressions and recurring pressure - -The product should begin answering: - -- which repos improved after intervention -- which repos are relapsing after intervention -- which repos keep consuming attention without durable progress -- which repos are now holding steady - -This phase should specifically: - -- add a cross-run historical intelligence builder through `src/intervention_ledger.py` -- persist implementation hotspot history additively so recurrence can be assessed credibly -- surface a `Historical Portfolio Intelligence` block across workbook, Markdown, HTML, review-pack, control-center, and scheduled handoff -- keep the synthesis path inside `AuditReport` -> `operator_control_center` -> `report_enrichment` - -### Constraint - -Prefer additive historical insight over new automation, new queues, or new scoring. - ---- - -## Phase 92: Cautious Automation Expansion - -### Goal - -Add safer execution helpers only after the outcome loop is trustworthy. - -### Target - -Phase 92 should stay bounded. The product should tell the operator: - -- what is safe to automate as a preview-only step -- what still requires approval or human review first -- what is safe to treat as non-mutating follow-up -- what must stay explicitly manual - -This phase should specifically: - -- add one `Automation Guidance` layer on top of Action Sync readiness, apply packets, post-apply monitoring, campaign tuning, and historical portfolio intelligence -- keep scheduled handoff and issue automation artifact-first -- surface safe command hints only when the posture is clearly bounded -- keep `--writeback-apply` human-only even when a campaign is otherwise ready - -### Constraint - -No new command tree, no background mutation runner, and no widening of write authority. -`apply-manual` remains an explicit human action, not an automatic one. - -### Status - -Shipped. Phase 92 added bounded `Automation Guidance`, automation-safe command/posture packaging across the Action Sync stack, scheduled-handoff parity for safe execution hints, workbook/Markdown/HTML/review-pack/control-center parity, and additive warehouse persistence for automation guidance snapshots. - -### Target - -Possible candidates: - -- stronger next-step execution guidance -- optional approval workflows -- better recurring review handoff and follow-up -- limited automation around already-proven safe paths - -### Constraint - -No unsafe default automation. The operator and local report remain authoritative. diff --git a/docs/plans/2026-04-13-phase-95-weekly-scheduling-overlay.md b/docs/plans/2026-04-13-phase-95-weekly-scheduling-overlay.md deleted file mode 100644 index 5bbeaec4..00000000 --- a/docs/plans/2026-04-13-phase-95-weekly-scheduling-overlay.md +++ /dev/null @@ -1,44 +0,0 @@ -# Phase 95 Design Note: Weekly Scheduling Overlay - -Date: 2026-04-13 -Status: Deferred design note (non-shipping) - -This note is intentionally retained as design context only. Phase 97 explicitly quarantines the scheduling overlay from the tracked release boundary because the proposal is not wired into tracked weekly surfaces and depends on approval follow-up facts that the tracked approval model does not yet produce. - -## Decision - -Phase 95 is an additive weekly scheduling overlay, not a rewrite of raw operator targeting. - -## Why - -The operator queue, lane semantics, `primary_target`, and `what_to_do_next` already carry the raw triage contract. Reusing those fields for approval-aware planning would blur two different jobs: - -- raw operational priority -- weekly planning guidance - -That would create a shadow queue and make future regressions harder to detect. - -## What This Phase Would Add - -- one bounded scheduling computation in `src/weekly_scheduling.py` -- shared `Weekly Scheduling` and `Next Weekly Focus` lines across weekly-facing artifacts -- additive candidate lists for approval backlog, approval follow-up timing, and pressure conflicts - -## What This Phase Explicitly Defers - -- any rewrite of `operator_queue` -- any rewrite of `primary_target` -- any rewrite of `what_to_do_next` -- any new command tree -- any widening of write authority -- any approval-aware auto-apply behavior - -## Current Repo Status - -- `src/weekly_scheduling.py` is not part of the tracked release boundary. -- No tracked weekly surface currently consumes the scheduling overlay directly. -- If this idea is revisited later, it must be ported into the tracked weekly packaging seams after approval follow-up state exists in the tracked approval architecture. - -## Safety Rule - -Approval-aware weekly scheduling may recommend review, re-check, refresh, or approval-center paths. It must never treat `--writeback-apply` as the weekly scheduling outcome unless another existing Action Sync layer had already independently earned that posture. diff --git a/docs/plans/2026-04-13-phase-98-closeout.md b/docs/plans/2026-04-13-phase-98-closeout.md deleted file mode 100644 index 274abf32..00000000 --- a/docs/plans/2026-04-13-phase-98-closeout.md +++ /dev/null @@ -1,53 +0,0 @@ -# Phase 98 Closeout: Truth Reset + Delivery Governance Baseline - -## Review Of What Was Built - -- corrected roadmap, architecture, product-mode, weekly-review, and repo-orientation docs so they match the shipped workbook-first operator system -- added a lightweight decision-log path for deferred and superseded roadmap work -- upgraded the existing PR template and published a reusable phase closeout template for future phases - -## Cleanup Review - -- moved the reusable closeout workflow out of the historical `93-97` roadmap and into active forward-planning surfaces -- kept Phase 98 feature-free and refactor-free; no runtime behavior, workbook logic, or queue semantics changed -- left deferred approval follow-up and approval-aware scheduling explicitly parked instead of smuggling them back into shipped behavior - -## Verification Summary - -- doc-to-code coherence review for weekly authority, deferred approval state, scheduled-handoff residuals, and active-vs-historical roadmap roles -- PR-template contract review for the required closeout sections -- decision-log integration review for presence, status clarity, and roadmap linkage -- changed-doc reference review for the edited roadmap and decision paths -- `python3 -m ruff check src tests` -- `pytest -q` - -## Shipped Summary - -Phase 98 leaves the repo with one active roadmap, one historical roadmap, one lightweight deferred-decision log, and one reusable closeout contract. Future phases now have to end with a review, cleanup summary, verification summary, shipped summary, next-phase writeup, and one-line summaries for the remaining roadmap phases. - -## Next Phase - -### Phase 99: Weekly Packaging Extraction - -Objective: -- Extract `weekly_story_v1` assembly into a dedicated packaging seam so shared weekly behavior is easier to test, evolve, and reuse without further inflating `src/report_enrichment.py`. - -Why it is next: -- The shared weekly contract is correct, but it is still assembled inside a broad enrichment module. -- Later approval work should not land on top of that seam until it becomes thinner and more testable. - -Main work: -- create a dedicated weekly packaging module for `weekly_story_v1` -- move weekly section and evidence-pack builders out of `src/report_enrichment.py` -- preserve current cross-surface behavior exactly -- make `scheduled_handoff` a thinner consumer of the shared weekly contract - -Main risks: -- behavior drift during extraction -- parity regressions across workbook, Markdown, HTML, review-pack, and scheduled handoff - -## Remaining Roadmap - -- `Phase 100`: Decompose the operator core into bounded modules without changing queue behavior. -- `Phase 101`: Add tracked approval follow-up facts and recurring review support without widening write authority. -- `Phase 102`: Reopen approval-aware weekly scheduling inside the shared weekly contract after the prerequisites exist. diff --git a/docs/plans/2026-04-13-roadmap-phases-93-97.md b/docs/plans/2026-04-13-roadmap-phases-93-97.md deleted file mode 100644 index 3dc98f8f..00000000 --- a/docs/plans/2026-04-13-roadmap-phases-93-97.md +++ /dev/null @@ -1,50 +0,0 @@ -# Roadmap: Phases 93-97 - -This roadmap is retained as historical context for the `93-97` arc. The active forward roadmap now lives in [2026-04-14-roadmap-phases-98-102.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-14-roadmap-phases-98-102.md), and the reusable phase closeout workflow now lives in [phase-closeout-template.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/phase-closeout-template.md). - -## Current Status Snapshot -- Phases 96 and 97 are complete and merged on `main` -- Phase 95 scheduling remains explicitly deferred and quarantined from the tracked release boundary -- The workbook automated gate remains part of the release standard for workbook-facing changes -- The `88-92` roadmap arc is complete and now historical context only -- The next planning arc begins in [2026-04-14-roadmap-phases-98-102.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-14-roadmap-phases-98-102.md) - -## Historical Closeout Note -The `93-97` arc used a stricter release-closeout checklist because those phases included workbook-facing changes, release-boundary reconciliation, and manual workbook signoff. Keep that history for context, but use the active closeout workflow in [phase-closeout-template.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/phase-closeout-template.md) for future phases. - -## Phase 93: Unified Approval Workflow + Approval Ledger -Status: Complete - -Goal: -- Create one local, artifact-first approval workflow that unifies governance approvals and approval-eligible campaign packets without widening write authority. - -Key targets: -- add `src/approval_ledger.py` -- add an approval ledger bundle to `AuditReport`, `operator_summary`, queue items, and weekly review pack surfaces -- add `--approval-center`, `--approve-governance`, and `--approve-packet` -- persist approval ledger snapshots and approval records in the warehouse -- add workbook, Markdown, HTML, review-pack, scheduled-handoff, and approval-center parity - -## Phase 94: Recurring Review + Follow-Up Handoff -Status: Deferred on tracked baseline - -Goal: -- Use the approval ledger plus the existing review and monitoring layers to manage approved-but-not-applied work, stale approvals, and recurring follow-up reminders without adding auto-apply behavior. - -## Phase 95: Approval-Aware Portfolio Scheduling -Status: Deferred and quarantined - -Goal: -- Blend approval backlog, follow-up timing, and portfolio pressure into clearer weekly scheduling guidance while keeping execution human-led. - -## Phase 96: Weekly Story Consolidation + Explainability Baseline -Status: Complete on `main` - -Goal: -- Consolidate the weekly story into one shared `weekly_story_v1` contract, route the weekly-facing artifacts through that contract, and then compress repeated wording into compact evidence packs without rewriting raw operator targeting. - -## Phase 97: Stability, Docs, and Release Hardening -Status: Complete - -Goal: -- Reconcile the deferred Phase 94/95 residue, close the current local work end to end, and harden the post-Phase-96 system with stronger regression coverage, docs truthfulness, and release-readiness cleanup. diff --git a/docs/plans/2026-04-14-arc-109-112-handoff.md b/docs/plans/2026-04-14-arc-109-112-handoff.md deleted file mode 100644 index 1420c8f9..00000000 --- a/docs/plans/2026-04-14-arc-109-112-handoff.md +++ /dev/null @@ -1,169 +0,0 @@ -# Handoff: Arc C (Phase 109) + Arc B (Phases 110-112) - -## Status As Of 2026-04-14 - -**Branch:** `main` — clean, all work merged. -**Tests:** 799 passing, ruff clean. -**Schema:** 0.4.0 (`src/portfolio_truth_types.py` line 8). -**Last PR merged:** saagpatel/GithubRepoAuditor#109 (phases 103-108 arc). - ---- - -## What Was Just Built (103-108 Arc) - -The portfolio truth arc is complete. Key artifacts: - -- `src/portfolio_truth_types.py` — schema 0.4.0, `RiskFields`, `DeclaredFields.doctor_standard` -- `src/portfolio_risk.py` — `build_risk_entry()`, 6 factors, 4 tiers (elevated/moderate/baseline/deferred) -- `src/portfolio_truth_reconcile.py` — reconcile pipeline, risk wired in -- `src/portfolio_truth_render.py` — truth table with Risk column, Coverage Summary with risk posture line -- `src/portfolio_truth_validate.py` — risk_tier + doctor_standard validation -- `src/weekly_command_center.py` — risk_posture in digest, `## Risk Posture` in markdown -- `config/portfolio-catalog.yaml` — 5 strategic repos have `doctor_standard` + `criticality: high` -- `docs/doctor-release-standard.md` — full/basic tiers with stack patterns - -Current portfolio risk state: 54 elevated, 4 moderate, 40 baseline, 17 deferred across 115 projects. -Context quality: 88 boilerplate, 13 minimum-viable, 8 standard, 4 full, 2 none. -54 active/recent repos have weak context (the actionable recovery cohort). - ---- - -## Immediate Next Work: Phase 109 (Arc C) + Phases 110-112 (Arc B) - -### Phase 109: Dead Code Cleanup (Arc C) - -**Scope:** Small. ~30 min. Single commit. - -6 orphaned public functions identified by the post-merge audit. They are defined in their files but never imported or called externally: - -| File | Function | Disposition | -|------|----------|-------------| -| `src/portfolio_pathing.py:25` | `resolve_declared_operating_path` | Keep — utility called internally by pathing logic chain; add inline comment | -| `src/portfolio_context_contract.py:121` | `choose_primary_context_file` | Keep — utility for context file selection; add inline comment | -| `src/portfolio_context_recovery.py:183` | `render_context_recovery_plan_markdown` | **Wire to CLI** — should be emitted when `--portfolio-context-recovery --dry-run` runs | -| `src/weekly_command_center.py:29` | `latest_portfolio_truth_path` | Keep — already used transitively via `load_latest_portfolio_truth`; not truly dead | -| `src/portfolio_truth_sources.py:267` | `read_context_text` | Keep — utility used indirectly; add inline comment | -| `src/portfolio_truth_sources.py:276` | `detect_boilerplate_context` | Keep — utility used indirectly; add inline comment | - -**The only real action item**: Wire `render_context_recovery_plan_markdown` into the `--portfolio-context-recovery` CLI path so it emits a human-readable `.md` plan file alongside the JSON. Currently `_run_portfolio_context_recovery_mode()` in `src/cli.py` (around line 2219) writes the truth output but doesn't write a markdown recovery plan. - -**Exit criteria:** -- `render_context_recovery_plan_markdown` is called and its output written to `output/portfolio-context-recovery-plan-.md` -- Inline comments on the other 5 functions clarifying their role -- `python3 -m pytest -q` passes, ruff clean - ---- - -### Phases 110-112: Risk Integration into Render Surfaces (Arc B) - -**Scope:** Medium. 3 phases, 3-4 files modified. - -Risk data currently lives in `portfolio-truth-latest.json` and the weekly command center digest. It does NOT flow into Excel, HTML dashboard, or review-pack. - -**Key architectural constraint:** The audit pipeline (`--html`, `--excel`, `--control-center`) works from `report_data` (audit JSON from `src/reporter.py`), NOT from `portfolio-truth-latest.json` directly. The bridge is `src/report_enrichment.py` which assembles the weekly pack. The safest integration pattern is: -- Load `portfolio-truth-latest.json` alongside audit data in `report_enrichment.py` or in each render function -- Extract risk summary totals (elevated/moderate/baseline/deferred counts) + top elevated items -- Pass through as a new optional `risk_posture` key in the enriched data - -Do NOT modify the audit pipeline data model. Keep risk as an additive optional layer. - -#### Phase 110: Report Enrichment Bridge - -**File:** `src/report_enrichment.py` (2044 lines) - -Goal: Load `portfolio-truth-latest.json` when available and extract a `risk_posture` summary that can be consumed by downstream renderers. - -- Find `build_weekly_review_pack()` (the main enrichment entry point) -- Add optional `output_dir: Path | None = None` parameter -- When `output_dir` is provided, call `load_latest_portfolio_truth(output_dir)` from `weekly_command_center.py` and extract risk posture: - ```python - risk_posture = { - "elevated_count": , - "moderate_count": , - "baseline_count": , - "deferred_count": , - "top_elevated": [{"repo": ..., "risk_summary": ...}, ...], # top 5 - } - ``` -- Return this in the weekly pack dict as `"risk_posture": risk_posture` (empty dict if truth not available) -- Add `tests/test_report_enrichment_risk.py` with 2-3 tests: risk_posture present when truth available, graceful empty dict when not available - -**Exit criteria:** `python3 -m pytest -q tests/test_report_enrichment_risk.py` passes. - -#### Phase 111: Excel Risk Sheet - -**File:** `src/excel_export.py` (7832 lines) - -Goal: Add risk tier column to the All Repos sheet + a new Risk Summary sheet. - -**All Repos sheet:** Find the header row construction (grep for `"Risk"` or the last column header in the All Repos sheet). Add `"Risk Tier"` column. Populate from `audit.get("portfolio_risk", {}).get("risk_tier", "")` — this requires the reconcile pipeline to write a `portfolio_risk` key into each audit dict (done in Phase 110 or here). - -Actually simpler approach: The truth JSON is the source. At Excel export time, load `portfolio-truth-latest.json`, build a `{display_name: risk_tier}` lookup dict, and use it to fill the Risk Tier column. - -**Risk Summary sheet:** New sheet with a table showing elevated/moderate/baseline/deferred counts and top elevated repos with their risk summaries. Follow the pattern of existing summary sheets (e.g., the Maturity sheet or the Campaign Summary sheet). - -**Exit criteria:** `python3 -m pytest -q tests/test_excel_enhanced.py` passes (no regressions). Spot-check: generate Excel and confirm Risk Tier column and Risk Summary sheet are present. - -#### Phase 112: HTML + Review Pack Risk - -**Files:** `src/web_export.py` (1611 lines), `src/review_pack.py` (377 lines) - -**HTML (`web_export.py`):** -- Find the summary stats section (grep for `"elevated"` or the coverage summary rendering) -- Add a Risk Posture card/panel showing tier counts -- Add risk_tier badge to each repo row in the project table (colored: elevated=red, moderate=yellow, baseline=green, deferred=gray) -- Source data: accept `risk_posture` dict from enrichment or load truth JSON directly - -**Review Pack (`review_pack.py`):** -- Find the weekly/operator summary section -- Add a `## Risk Posture` block: 2-3 lines showing tier counts + top 3 elevated repos -- This is a small additive change, ~15 lines - -**Exit criteria:** `python3 -m pytest -q tests/test_web_export.py tests/test_review_pack.py` passes. HTML spot-check: open generated HTML and confirm risk section is visible. - ---- - -## Files To Touch (Phases 109-112) - -| Phase | File | Change | -|-------|------|--------| -| 109 | `src/cli.py` | Wire `render_context_recovery_plan_markdown` into recovery mode output | -| 109 | `src/portfolio_context_recovery.py` | Add `# used by CLI --portfolio-context-recovery --dry-run` comment | -| 109 | `src/portfolio_pathing.py` | Add `# utility — resolve catalog entry to stable path string` comment | -| 109 | `src/portfolio_context_contract.py` | Add `# utility — prefer CLAUDE.md over AGENTS.md` comment | -| 109 | `src/portfolio_truth_sources.py` | Comments on `read_context_text`, `detect_boilerplate_context` | -| 110 | `src/report_enrichment.py` | Add `output_dir` param, load truth JSON, extract risk_posture | -| 110 | `tests/test_report_enrichment_risk.py` | New test file — risk_posture present/absent | -| 111 | `src/excel_export.py` | Risk Tier column in All Repos + new Risk Summary sheet | -| 111 | `tests/test_excel_enhanced.py` | Assert Risk Tier column present, Risk Summary sheet exists | -| 112 | `src/web_export.py` | Risk Posture card + risk_tier badges per repo row | -| 112 | `src/review_pack.py` | `## Risk Posture` section in review pack markdown | -| 112 | `tests/test_web_export.py` | Assert risk section present in HTML | -| 112 | `tests/test_review_pack.py` | Assert risk posture in review pack output | - ---- - -## After Arc B: What Comes Next - -**Arc A (Phase 113+): Context Recovery Operation** -This is operational, not code. The infrastructure (`src/portfolio_context_recovery.py`) is fully built. The session would: -1. Run `python3 -m src.cli --portfolio-context-recovery --dry-run --output-dir output` -2. Review `output/portfolio-context-recovery-plan-.md` together -3. Approve targets, run apply -4. Re-run `--portfolio-truth` to see improvement in context quality and risk tiers - -**Arc D (Phase 114+): Safe Automation Expansion** -Requires Arc A to reduce elevated count from 54 → ~20 before automation is non-noisy. - -**Arc E: Desktop Portfolio Shell** -Independent — can start any time. `JobCommandCenter` Tauri 2 repo consuming `portfolio-truth-latest.json`. - ---- - -## How To Resume After Compaction - -1. Read this file: `docs/plans/2026-04-14-arc-109-112-handoff.md` -2. Read memory: `/Users/d/.claude/projects/-Users-d/memory/project_github_repo_auditor.md` -3. Confirm state: `git status` (should be clean on main), `python3 -m pytest -q` (799 passing) -4. Go into plan mode for Phase 109 first, then phases 110-112 -5. Branch naming: `feat/phase-109-dead-code` and `feat/phase-110-112-risk-integration` diff --git a/docs/plans/2026-04-14-claude-code-handoff.md b/docs/plans/2026-04-14-claude-code-handoff.md deleted file mode 100644 index 33c8dcf8..00000000 --- a/docs/plans/2026-04-14-claude-code-handoff.md +++ /dev/null @@ -1,77 +0,0 @@ -# Continuation Prompt For New Claude Code Thread - -Continue this work in the same workspace unless I say otherwise. - -## Mission -You are resuming work in `/Users/d/Projects/GithubRepoAuditor`, which is now a workbook-first portfolio operating system for the broader `/Users/d/Projects` workspace. Start with discovery, not implementation: rebuild the real current state from the repo and recent closeout docs, then produce a serious Phase 108 implementation plan for the next arc. - -Phase 108 is expected to be a bounded risk-and-readiness phase: add a structured portfolio risk overlay and define a minimal cross-repo doctor/release-check standard for the most important repos, without turning this into a security platform, auto-remediation system, or second weekly authority. - -## Workspace -- Same folder as the previous thread: `/Users/d/Projects/GithubRepoAuditor` -- Treat the workspace as source of truth -- The working tree is dirty and should be treated as the active baseline, not casually cleaned up or normalized -- Do not revert unrelated changes you did not make - -## Read These First -- `/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-14-roadmap-phases-103-108.md` -- `/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-14-phase-107-closeout.md` -- `/Users/d/Projects/GithubRepoAuditor/docs/architecture.md` -- `/Users/d/Projects/GithubRepoAuditor/src/portfolio_pathing.py` -- `/Users/d/Projects/GithubRepoAuditor/src/weekly_command_center.py` -- `/Users/d/Projects/DecisionStressTest/docs/local-operator-checklist.md` -- `/Users/d/Projects/DecisionStressTest/docs/release-readiness-checklist.md` - -## Latest Checkpoint -- Phases 103-107 are effectively shipped in the current working tree: - - portfolio truth layer - - minimum-viable context recovery - - `decision_quality_v1` - - operating-path normalization - - bounded weekly command-center digest -- Focused verification most recently passed: - - `python3 -m pytest -q tests/test_portfolio_pathing.py tests/test_weekly_command_center.py tests/test_operator_decision_quality.py` -- Current truth snapshot is `/Users/d/Projects/GithubRepoAuditor/output/portfolio-truth-latest.json` - - `schema_version: 0.3.0` - - `project_count: 114` - - `context_quality_counts: {'boilerplate': 87, 'minimum-viable': 13, 'full': 4, 'none': 2, 'standard': 8}` - - `declared_operating_path_counts: {'maintain': 51, 'experiment': 5, 'archive': 15, '': 43}` - - `path_override_counts: {'investigate': 93, '': 21}` - - `path_confidence_counts: {'low': 93, 'medium': 9, 'high': 12}` - -## Decisions Already Made -- `weekly_story_v1` remains the only weekly authority. -- `weekly_command_center_digest_v1` is report-only and derived from `weekly_story_v1` + operator summary + portfolio truth. -- `investigate` is override-only, never a stable declared operating path. -- Tactical collections like `finish-next` are useful, but not canonical path labels. -- Path/trust/weekly improvements are advisory-only and must not widen automation, approval, or execution authority. -- Phase 108 should be a risk overlay + doctor/release standard phase, not a full security platform or auto-remediation system. - -## Rejected Paths -- Do not restart discovery from old bootstrap assumptions; this repo is no longer “just a repo auditor.” -- Do not invent a new weekly authority, new queue, or new command authority. -- Do not turn Phase 108 into portfolio-wide mutation, auto-fixing, or a giant scoring rewrite. -- Do not treat tactical collections or temporary overrides as stable path semantics. - -## Current State That Matters -- The system is stronger than the workspace metadata around it. -- Many repos still have weak context and low path confidence, which is why `investigate` is still common. -- The best current reference shape for a minimal doctor/release standard lives in `DecisionStressTest`, not in this repo yet. -- The roadmap and closeout docs are current enough to anchor planning, but if they disagree with the code or generated artifacts, inspect the code/artifacts and explain the mismatch before proposing changes. - -## Open Loops -- Produce a serious, execution-grade Phase 108 plan. -- Define what the portfolio risk overlay should mean in machine-checkable terms. -- Decide how to pilot a minimal doctor/release-check contract across the most important repos. -- Keep any Phase 108 proposal bounded, advisory-only, and compatible with the shipped weekly/path/truth contracts. - -## Next Best Step -1. Re-read the roadmap and Phase 107 closeout, then inspect the current truth/path/weekly seams in code. -2. Audit what already exists for risk signals and doctor/release checks across this repo and the key sibling repos. -3. Produce a contract-first Phase 108 implementation plan before touching code. - -## Guardrails -- Reuse established decisions unless I explicitly reopen them. -- Keep the first Claude Code response discovery-oriented. -- Treat the workspace as the main source of truth, not this prompt. -- If the prompt and the files disagree, inspect the files and explain the mismatch before proceeding. diff --git a/docs/plans/2026-04-14-phase-100-closeout.md b/docs/plans/2026-04-14-phase-100-closeout.md deleted file mode 100644 index e12b7933..00000000 --- a/docs/plans/2026-04-14-phase-100-closeout.md +++ /dev/null @@ -1,82 +0,0 @@ -# Phase 100 Closeout: Operator Core Boundary Decomposition - -## Review Of What Was Built - -- kept [`src/operator_control_center.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_control_center.py) as the public compatibility façade while extracting the highest-risk internal seams into: - - [`src/operator_snapshot_packaging.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_snapshot_packaging.py) - - [`src/operator_follow_through.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_follow_through.py) - - [`src/operator_resolution_trend.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_resolution_trend.py) - - [`src/operator_control_center_rendering.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_control_center_rendering.py) -- rewired `build_operator_snapshot(...)`, `render_control_center_markdown(...)`, and `control_center_artifact_payload(...)` so downstream callers still use the same public entrypoints while the extracted modules now own the moved logic -- added an explicit operator snapshot contract suite in [`tests/test_operator_snapshot_contract.py`](/Users/d/Projects/GithubRepoAuditor/tests/test_operator_snapshot_contract.py) to lock the top-level snapshot shape, required `operator_summary` fields, required queue-item fields, and basic queue invariants -- migrated the direct private-helper tests in [`tests/test_operator_control_center.py`](/Users/d/Projects/GithubRepoAuditor/tests/test_operator_control_center.py) so they now target the extracted follow-through and resolution-trend modules instead of the old private locations - -## Cleanup Review - -- removed the broken partial extraction state and regenerated the subsystem modules from the last good source instead of leaving hand-patched helper drift in place -- kept queue bootstrap, warehouse/history loading, and external Action Sync / approval bundle orchestration in [`src/operator_control_center.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_control_center.py) for this phase; this was a boundary-decomposition phase, not a full operator rewrite -- kept the public façade functions stable and did not leave long-lived private compatibility aliases behind for the moved helper families - -## Verification Summary - -- focused boundary checks: - - `python3 -m ruff check src/operator_control_center.py src/operator_control_center_rendering.py src/operator_snapshot_packaging.py src/operator_follow_through.py src/operator_resolution_trend.py` - - `pytest -q tests/test_operator_control_center.py` - - `pytest -q tests/test_operator_control_center.py tests/test_operator_snapshot_contract.py tests/test_review_pack.py tests/test_scheduled_handoff.py tests/test_weekly_story.py tests/test_excel_enhanced.py` -- full repo gates: - - `python3 -m ruff check src tests` - - `pytest -q` - - `make workbook-gate` -- results: - - lint passed - - full repo tests passed: `749 passed` - - workbook gate automated checks passed - - manual desktop Excel signoff was not run because this phase stayed behavior-preserving and workbook gate did not show workbook-visible drift that required escalation - -## Shipped Summary - -Phase 100 leaves the repo with a real operator-core façade plus extracted subsystem boundaries for operator packaging, follow-through, resolution-trend reasoning, and control-center rendering. The public control-center API and snapshot schema remain stable, but the largest internal concentration risk is no longer forced to live in one file. - -## Next Phase - -### Phase 101: Approval Follow-Up Foundation - -Objective: -- Reopen the deferred approval follow-up work by adding tracked follow-up facts and recurring review state to the approval architecture without introducing any automatic mutation or a second weekly authority. - -Why it is next: -- Phase 99 stabilized the weekly seam. -- Phase 100 reduced the operator-core concentration risk. -- The missing foundation for later scheduling work is now the approval follow-up data model, not another extraction seam. - -Main work: -- extend [`src/approval_ledger.py`](/Users/d/Projects/GithubRepoAuditor/src/approval_ledger.py) and the persisted artifact shape with approval follow-up facts such as: - - follow-up due state - - stale approval state - - recurring review posture - - compatibility fallbacks for older snapshots -- package those facts consistently across: - - workbook - - Markdown - - HTML - - review-pack - - scheduled handoff - - approval-facing surfaces -- keep all approval capture local-only and read-only in posture -- prove compatibility for older payloads that do not yet have the new fields - -Main risks: -- widening the phase into approval-aware scheduling before the tracked follow-up model exists -- leaking new approval fields into only one surface and creating parity drift -- introducing write authority or background mutation behavior while trying to add recurring follow-up facts - -Verification expectations: -- add compatibility tests for older snapshots without approval follow-up fields -- add cross-surface parity checks for the new approval follow-up facts -- run `python3 -m ruff check src tests` -- run `pytest -q` -- run `make workbook-gate` - -## Remaining Roadmap - -- `Phase 102`: Reopen approval-aware weekly scheduling only after the tracked approval follow-up foundation is fully shipped across all weekly-facing surfaces. diff --git a/docs/plans/2026-04-14-phase-101-closeout.md b/docs/plans/2026-04-14-phase-101-closeout.md deleted file mode 100644 index 655b0372..00000000 --- a/docs/plans/2026-04-14-phase-101-closeout.md +++ /dev/null @@ -1,103 +0,0 @@ -# Phase 101 Closeout: Approval Follow-Up Foundation - -## Review Of What Was Built - -- extended [`src/approval_ledger.py`](/Users/d/Projects/GithubRepoAuditor/src/approval_ledger.py) so approval workflow rows now carry additive approval freshness facts without changing the existing `approval_state` contract: - - `last_reviewed_at` - - `last_reviewed_by` - - `follow_up_cadence_days` - - `next_follow_up_due_at` - - `follow_up_state` - - `follow_up_summary` - - `stale_approval` - - `follow_up_command` -- added append-only recurring follow-up persistence in [`src/warehouse.py`](/Users/d/Projects/GithubRepoAuditor/src/warehouse.py) through the new `approval_followup_events` table instead of overwriting the original approval record for the same unchanged fingerprint -- added distinct local-only recurring review commands in [`src/cli.py`](/Users/d/Projects/GithubRepoAuditor/src/cli.py): - - `--review-governance --governance-scope ` - - `--review-packet --campaign ` -- kept the top-level approval bundle stable while extending it with additive packaging buckets: - - `top_overdue_approval_followups` - - `top_due_soon_approval_followups` -- pushed the new approval freshness story through the shared shipped surfaces: - - [`src/weekly_packaging.py`](/Users/d/Projects/GithubRepoAuditor/src/weekly_packaging.py) - - [`src/report_enrichment.py`](/Users/d/Projects/GithubRepoAuditor/src/report_enrichment.py) - - [`src/operator_control_center.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_control_center.py) - - [`src/operator_snapshot_packaging.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_snapshot_packaging.py) - - [`src/excel_export.py`](/Users/d/Projects/GithubRepoAuditor/src/excel_export.py) -- widened workbook-visible and hidden approval ledger output so workbook users can see follow-up freshness directly on the `Approval Ledger` sheet and in `Data_ApprovalLedger` -- strengthened regression coverage across approval persistence, CLI hardening, weekly packaging, docs, and workbook-facing approval surfaces in: - - [`tests/test_approval_ledger.py`](/Users/d/Projects/GithubRepoAuditor/tests/test_approval_ledger.py) - - [`tests/test_warehouse.py`](/Users/d/Projects/GithubRepoAuditor/tests/test_warehouse.py) - - [`tests/test_cli_hardening.py`](/Users/d/Projects/GithubRepoAuditor/tests/test_cli_hardening.py) - - [`tests/test_weekly_packaging.py`](/Users/d/Projects/GithubRepoAuditor/tests/test_weekly_packaging.py) - - [`tests/test_phase93_approval_surfaces.py`](/Users/d/Projects/GithubRepoAuditor/tests/test_phase93_approval_surfaces.py) - - [`tests/test_phase93_approval_docs.py`](/Users/d/Projects/GithubRepoAuditor/tests/test_phase93_approval_docs.py) - -## Cleanup Review - -- kept the original `approval_records` primary key and approval capture semantics intact; the phase avoided a broad approval storage rewrite -- kept approval validity and approval freshness separate: - - `approval_state` still describes approval validity and apply posture - - `follow_up_state` now carries recurring local review freshness -- kept `weekly_story_v1` as the only weekly authority and did not introduce a second weekly approval section -- kept `operator_queue`, `primary_target`, and `what_to_do_this_week` unchanged; approval-aware scheduling remains deferred to Phase 102 -- did not add new write authority, auto-apply behavior, pytest configuration churn, or CI churn - -## Verification Summary - -- targeted approval and parity checks: - - `pytest -q tests/test_approval_ledger.py tests/test_warehouse.py tests/test_weekly_packaging.py tests/test_weekly_story.py tests/test_phase93_approval_surfaces.py tests/test_cli_hardening.py` - - `pytest -q tests/test_phase93_approval_docs.py tests/test_phase93_approval_surfaces.py tests/test_approval_ledger.py tests/test_weekly_packaging.py tests/test_cli_hardening.py tests/test_warehouse.py` -- full repo gates: - - `python3 -m ruff check src tests` - - `pytest -q` - - `make workbook-gate` -- results: - - lint passed - - full repo tests passed: `755 passed` - - workbook gate automated checks passed - - manual desktop Excel signoff passed and is recorded in `output/workbook-gate/workbook-gate-result.json` - -## Shipped Summary - -Phase 101 leaves the repo with a durable approval follow-up foundation instead of a one-shot approval memory model. Initial approval capture remains local-only and unchanged, recurring follow-up review is now tracked append-only, and shipped approval surfaces can distinguish “still approved but due for local review” from “needs reapproval” without widening command authority. - -## Next Phase - -### Phase 102: Approval-Aware Weekly Scheduling - -Objective: -- add one bounded weekly scheduling overlay that uses the new tracked approval freshness facts inside the existing `weekly_story_v1` contract without creating a second recommendation engine - -Why it is next: -- approval follow-up timing is now real tracked data instead of inferred prose -- the weekly packaging seam already exists -- the operator core and approval surfaces are now safer places to consume one shared scheduling overlay - -Main work: -- extend [`src/weekly_packaging.py`](/Users/d/Projects/GithubRepoAuditor/src/weekly_packaging.py) so weekly approval evidence can influence section emphasis and next-step wording through one explicit overlay -- keep the overlay bounded to weekly packaging and approval packaging rather than rewriting: - - `operator_queue` - - `primary_target` - - `what_to_do_next` -- make approval timing compete only where it should: - - overdue follow-up should matter more than due-soon follow-up - - approval timing should not outrank stronger blocked or urgent portfolio pressure -- preserve read-only posture by surfacing guidance and command hints only; no new authority should be added - -Main risks: -- accidentally creating a second weekly priority engine outside `weekly_story_v1` -- letting approval timing outrank more important blocked or urgent pressure -- widening the phase into queue-model or operator-core rewrites - -Verification expectations: -- add precedence tests proving approval-aware scheduling never outranks stronger blocked or urgent pressure -- add cross-surface parity tests across workbook, Markdown, HTML, review-pack, and scheduled handoff -- run `python3 -m ruff check src tests` -- run `pytest -q` -- run `make workbook-gate` -- complete the manual desktop Excel signoff because workbook-visible weekly wording is likely to change again - -## Remaining Roadmap - -- No later phases remain in the active 98-102 roadmap after Phase 102. diff --git a/docs/plans/2026-04-14-phase-102-closeout.md b/docs/plans/2026-04-14-phase-102-closeout.md deleted file mode 100644 index 2583033b..00000000 --- a/docs/plans/2026-04-14-phase-102-closeout.md +++ /dev/null @@ -1,64 +0,0 @@ -# Phase 102 Closeout: Approval-Aware Weekly Scheduling - -## Review Of What Was Built - -- added the bounded weekly overlay seam in [`src/weekly_scheduling_overlay.py`](/Users/d/Projects/GithubRepoAuditor/src/weekly_scheduling_overlay.py) - - approval-aware weekly overrides now happen in one pure helper instead of being scattered across renderers - - the overlay reuses the shipped approval buckets from Phase 101 in this order: - - `needs-reapproval` - - `overdue-follow-up` - - `ready-for-review` - - `due-soon-follow-up` - - blocked or urgent operator pressure suppresses the overlay entirely -- wired the overlay into [`src/report_enrichment.py`](/Users/d/Projects/GithubRepoAuditor/src/report_enrichment.py) before `weekly_story_v1` finalization, so the weekly decision changes inside the shared weekly contract rather than in operator-core logic -- extended [`src/weekly_packaging.py`](/Users/d/Projects/GithubRepoAuditor/src/weekly_packaging.py) so the `weekly-priority` section can explain approval-aware wins with explicit reason codes and evidence items instead of only queue-pressure evidence -- rerouted weekly-facing summary slots that were still bypassing the shared weekly story: - - [`src/excel_export.py`](/Users/d/Projects/GithubRepoAuditor/src/excel_export.py) now prefers shared weekly decision and why-this-week values in workbook `Dashboard` and `Executive Summary` - - [`src/web_export.py`](/Users/d/Projects/GithubRepoAuditor/src/web_export.py) now prefers shared weekly decision and why-this-week values in the `Run Changes` weekly summary block - - [`src/reporter.py`](/Users/d/Projects/GithubRepoAuditor/src/reporter.py) now prefers shared weekly decision and why-this-week values in the Markdown `Run Changes` summary block -- added and extended regression coverage for: - - the pure overlay decision table - - weekly-story override behavior - - workbook / Markdown / HTML / handoff parity for the approval-aware weekly decision path - -## Cleanup Review - -- kept `operator_queue`, `primary_target`, and `operator_summary.what_to_do_next` unchanged; the phase did not widen into operator-core or queue-model work -- kept approval validity and follow-up freshness semantics unchanged; the phase consumed Phase 101 approval facts instead of recalculating approval state from raw records -- kept persistence, warehouse schema, CLI flags, and command authority unchanged -- left raw operator-control-center views intentionally raw; only weekly-facing summary slots were rerouted to the shared weekly story -- did not add a second weekly authority or a separate scheduling engine - -## Verification Summary - -- targeted weekly overlay and parity checks: - - `python3 -m pytest -q tests/test_weekly_scheduling_overlay.py tests/test_weekly_packaging.py tests/test_weekly_story.py tests/test_review_pack.py tests/test_scheduled_handoff.py tests/test_phase93_approval_surfaces.py tests/test_web_export.py tests/test_excel_enhanced.py tests/test_reporter.py` -- full repo gates: - - `python3 -m ruff check src tests` - - `pytest -q` - - `make workbook-gate` -- results: - - lint passed - - targeted weekly and parity suites passed - - full repo tests passed - - workbook gate automated checks passed - - manual desktop Excel signoff is still required because workbook-visible weekly wording changed in the flagship surface - -## Shipped Summary - -Phase 102 closes the active roadmap arc with one real shared weekly scheduling story. Approval review and follow-up work can now become the weekly winner when that is the highest-value bounded step, but only inside `weekly_story_v1` and only when stronger blocked or urgent portfolio pressure is not active. The visible weekly surfaces now have a better chance of staying aligned because the overlay is centralized and the most important workbook / HTML / Markdown summary slots now read from the same shared weekly story. - -## Next Phase - -There is no active next phase left in the current 98-102 roadmap. The roadmap arc is complete and any later work should start from a new roadmap document instead of extending this one implicitly. - -If a new roadmap arc starts, the next planning pass should answer these questions explicitly before implementation begins: - -- should the weekly overlay stay bounded to shared weekly packaging, or is there now evidence that operator-core recommendation rules also need revision -- which remaining weekly-facing summary slots still deserve semantic rerouting, and which should stay intentionally raw -- does the repo now need a dedicated shared weekly-story utility module for fallback resolution instead of the current lightweight helper in [`src/weekly_scheduling_overlay.py`](/Users/d/Projects/GithubRepoAuditor/src/weekly_scheduling_overlay.py) -- is there a new post-roadmap cleanup phase needed for renderer simplification, or is the current architecture stable enough to pivot back to product work - -## Remaining Roadmap - -- none diff --git a/docs/plans/2026-04-14-phase-103-closeout.md b/docs/plans/2026-04-14-phase-103-closeout.md deleted file mode 100644 index bad1353b..00000000 --- a/docs/plans/2026-04-14-phase-103-closeout.md +++ /dev/null @@ -1,144 +0,0 @@ -# Phase 103 Closeout - -## Review Of What Was Built - -Phase 103 shipped a dedicated portfolio truth subsystem instead of extending the weekly/report pipeline. - -Core delivered behavior: -- added a versioned canonical truth contract in [`src/portfolio_truth_types.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_truth_types.py) -- added safe workspace, legacy-registry, and optional Notion source adapters in [`src/portfolio_truth_sources.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_truth_sources.py) -- added field-by-field reconciliation and provenance in [`src/portfolio_truth_reconcile.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_truth_reconcile.py) -- added truth/output validation and publish safety checks in [`src/portfolio_truth_validate.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_truth_validate.py) and [`src/portfolio_truth_publish.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_truth_publish.py) -- added compatibility renderers for the shared workspace artifacts in [`src/portfolio_truth_render.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_truth_render.py) -- added the CLI entrypoint `audit --portfolio-truth` in [`src/cli.py`](/Users/d/Projects/GithubRepoAuditor/src/cli.py) -- extended [`config/portfolio-catalog.yaml`](/Users/d/Projects/GithubRepoAuditor/config/portfolio-catalog.yaml) and [`src/portfolio_catalog.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_catalog.py) so grouped-folder defaults can be declared instead of inferred - -Compatibility and publish behavior that now exists: -- the canonical machine-readable artifact is now [`output/portfolio-truth-latest.json`](/Users/d/Projects/GithubRepoAuditor/output/portfolio-truth-latest.json) -- dated historical truth snapshots are written alongside it in `output/` -- `/Users/d/Projects/project-registry.md` is now generated from the truth snapshot -- `/Users/d/Projects/PORTFOLIO-AUDIT-REPORT.md` is now generated from the same truth snapshot and explicitly framed as derived, not canonical -- publish is staged through temp files and replace-on-success behavior -- unchanged compatibility outputs are not rewritten -- `--sync-registry` now fails closed instead of silently mutating the shared registry - -The live publish pass succeeded against the real workspace after the validation pass was hardened for two real portfolio conditions: -- a duplicate display name (`OrbitForge`) still exists across compatibility sections -- one project path still carries leading whitespace in the underlying folder name (`Fun:GamePrjs/ CryptForge`) - -The generated live snapshot currently reports: -- `113` discovered projects -- `59` active -- `18` recent -- `21` parked -- `15` archived -- duplicate display name warning for `OrbitForge` - -## Cleanup Review - -Removed or shut down: -- the old `--sync-registry` mutation path is no longer allowed -- the brittle live-home-path registry parser assertion in [`tests/test_registry_parser.py`](/Users/d/Projects/GithubRepoAuditor/tests/test_registry_parser.py) was replaced with a hermetic compatibility fixture - -Simplified or contained: -- portfolio truth generation no longer rides inside `AuditReport` -- workspace compatibility publishing is isolated behind one publish module instead of scattered `write_text()` calls -- grouped-folder policy now has a declared config seam instead of living only in code assumptions - -Temporary compatibility shims that remain: -- `project-registry.md` is still a lossy compatibility surface keyed by display name, so duplicate names still collapse in parser-style consumers -- the legacy registry still feeds migration evidence for category/tool/notes where explicit catalog data is missing -- root-level Swift projects still depend on a bounded compatibility inference to stay visible under the iOS section - -Open cleanup findings that should not be forgotten: -- `OrbitForge` still exists as a duplicate display name across two compatibility sections -- `Fun:GamePrjs/ CryptForge` still exposes a leading-space path in the live workspace -- many projects still fall back to `unknown` category or tool because explicit catalog contracts are missing -- Notion context remains optional and currently contributed `0` rows on the live publish - -Automation-noise and secrets-exposure posture: -- compatibility outputs now skip rewrites when content is unchanged, which reduces false automation churn for the active weekly portfolio review automation -- the truth layer only reads small allowlisted text/manifests, refuses symlinks, and does not persist raw Notion `next_move` text -- the durable truth artifact still includes the absolute workspace root at the snapshot top level; that is acceptable for this local-first system today but should remain intentional - -## Verification Summary - -Focused local verification run: -- `python3 -m ruff check src tests` -- `pytest -q tests/test_portfolio_truth.py tests/test_portfolio_catalog.py tests/test_registry_parser.py tests/test_notion_registry.py` - -What those checks covered: -- truth contract and precedence behavior -- grouped-folder catalog rules -- registry parser compatibility -- no-op publish behavior -- publish-failure safety -- CLI override path behavior -- fail-closed `--sync-registry` - -Live workspace verification: -- ran `audit d --portfolio-truth` through the CLI entrypoint -- regenerated `/Users/d/Projects/project-registry.md` -- regenerated `/Users/d/Projects/PORTFOLIO-AUDIT-REPORT.md` -- wrote [`output/portfolio-truth-latest.json`](/Users/d/Projects/GithubRepoAuditor/output/portfolio-truth-latest.json) plus a dated snapshot - -Not run in this phase: -- workbook-facing gates, because the workbook/report pipeline was intentionally not changed -- broad `pytest -q`, because the truth-layer cut was isolated and the targeted suite already covered the touched seams directly - -## Shipped Summary - -`GithubRepoAuditor` now owns a real portfolio truth layer for `/Users/d/Projects`. - -After Phase 103: -- one canonical truth snapshot exists -- the shared registry and portfolio audit report are generated from that truth snapshot -- the old direct registry mutation seam is closed -- grouped-folder defaults have a declared config home -- the system can publish compatibility outputs safely without forcing unnecessary file churn - -This phase did **not** solve context quality yet. It made the truth and compatibility layer real enough that Phase 104 can improve context on top of stable portfolio facts instead of stale markdown. - -## Next Phase - -### Phase 104: Minimum Viable Context Recovery - -The next phase should improve context quality for active and recent projects first, using the new truth layer as the authoritative project inventory. - -Immediate starting point: -1. read [`output/portfolio-truth-latest.json`](/Users/d/Projects/GithubRepoAuditor/output/portfolio-truth-latest.json) as the canonical project inventory -2. target projects whose `registry_status` is `active` or `recent` and whose `context_quality` is `none` or `boilerplate` -3. define the new context-quality ladder: - - `none` - - `boilerplate` - - `minimum-viable` - - `standard` - - `full` -4. decide which minimum files and fields make a project “minimum-viable”, at minimum: - - what the project is - - current state - - stack - - how to run it - - known risks - - next recommended move -5. make the truth layer understand the new `minimum-viable` band without weakening the stricter `standard` and `full` bands -6. build a repeatable context-recovery workflow that can update one project at a time without inventing a second source of truth - -Execution guidance for Phase 104: -- start with the live truth snapshot counts and sort candidates by `active/recent` plus weak context -- treat grouped boilerplate-heavy sections as batch candidates only after the highest-signal standalone projects are addressed -- keep context recovery local and report-first; do not mix it with automation restart -- preserve the strict scan contract from Phase 103 so context detection stays safe and predictable -- add tests that prove the truth layer can distinguish `minimum-viable` from `boilerplate` - -Known Phase 104 risks already exposed by Phase 103: -- duplicate display names will make context-recovery reporting noisier if they are not handled explicitly -- some current “standard” classifications are still optimistic because they rely on shallow AGENTS/CLAUDE presence rather than a richer semantic contract -- catalog coverage is still weak, so context recovery and declared portfolio intent will continue to drift unless repo/group contracts are filled in alongside context work - -## Remaining Roadmap - -- `Phase 105` — Turn trust/effectiveness signals into a measurable decision-quality layer for weekly recommendations and future automation gates. -- `Phase 106` — Introduce explicit portfolio golden paths like maintain, finish, archive, and experiment so weekly guidance becomes intent-aware instead of generic. -- `Phase 107` — Reboot a bounded weekly command-center automation loop only after truth, context, and decision-quality inputs are strong enough. -- `Phase 108` — Add a structured risk overlay and a reusable doctor/release standard across the key repos in the workspace. diff --git a/docs/plans/2026-04-14-phase-104-closeout.md b/docs/plans/2026-04-14-phase-104-closeout.md deleted file mode 100644 index 9a72aacb..00000000 --- a/docs/plans/2026-04-14-phase-104-closeout.md +++ /dev/null @@ -1,140 +0,0 @@ -# Phase 104 Closeout - -## Review Of What Was Built - -Phase 104 turned context recovery into a real portfolio workflow instead of a vague docs goal. - -Core delivered behavior: -- bumped the portfolio truth contract to schema `0.2.0` -- added the new `context_quality` ladder: - - `none` - - `boilerplate` - - `minimum-viable` - - `standard` - - `full` -- added explicit minimum-context booleans and `primary_context_file` to [`src/portfolio_truth_types.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_truth_types.py) -- added the semantic contract, heading aliases, and managed context block rules in [`src/portfolio_context_contract.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_context_contract.py) -- added frozen-cohort planning, dirty/temp skip rules, managed context writes, and bounded catalog seeding in [`src/portfolio_context_recovery.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_context_recovery.py) -- taught the truth source/reconcile/render/validate stack about the new band and completeness signals in: - - [`src/portfolio_truth_sources.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_truth_sources.py) - - [`src/portfolio_truth_reconcile.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_truth_reconcile.py) - - [`src/portfolio_truth_render.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_truth_render.py) - - [`src/portfolio_truth_validate.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_truth_validate.py) -- added the standalone CLI mode `audit --portfolio-context-recovery` in [`src/cli.py`](/Users/d/Projects/GithubRepoAuditor/src/cli.py) - -Live workspace impact: -- wrote managed context blocks into `25` repo-local `CLAUDE.md` or `AGENTS.md` files under `/Users/d/Projects` -- seeded `25` repo-level catalog contracts in [`config/portfolio-catalog.yaml`](/Users/d/Projects/GithubRepoAuditor/config/portfolio-catalog.yaml) -- generated dry-run recovery plan artifacts in `output/context-recovery-plan-*.json` and `output/context-recovery-plan-*.md` -- regenerated: - - [`output/portfolio-truth-latest.json`](/Users/d/Projects/GithubRepoAuditor/output/portfolio-truth-latest.json) - - [/Users/d/Projects/project-registry.md](/Users/d/Projects/project-registry.md) - - [/Users/d/Projects/PORTFOLIO-AUDIT-REPORT.md](/Users/d/Projects/PORTFOLIO-AUDIT-REPORT.md) - -The shipped live snapshot now reports: -- `114` total projects -- `4` with `full` context -- `8` with `standard` context -- `13` with `minimum-viable` context -- `87` with `boilerplate` context -- `2` with `none` context - -The important Phase 104 result is not the portfolio-wide count alone. It is that the live recovery planner now shows the remaining active/recent weak-context cohort as a safety problem, not a discovery problem: -- `53` active/recent weak-context projects remain -- `0` are currently eligible for clean automated recovery -- `51` are skipped by the planner because of local safety rules like dirty worktrees -- `2` are excluded as temporary/generated repos - -## Cleanup Review - -Removed or retired assumptions: -- the old four-band context model is gone -- the roadmap’s stale “74 none / 18 boilerplate” framing is no longer the working baseline -- context recovery is no longer an undefined future manual exercise; it now has a real planner and write path - -Contained rather than expanded: -- context recovery stays repo-local; the truth snapshot remains derived -- Notion stayed read-only and out of the write path -- paused weekly automations were not restarted - -What was intentionally left in place: -- remaining weak-context repos that are dirty were not force-written -- temporary/generated repos such as scaffold and `*-tmp-*` repos were excluded from automation -- the shared registry and report remain compatibility outputs rather than becoming new writable surfaces - -Temporary or compatibility seams that still remain: -- `project-registry.md` is still a compatibility view and not a rich operator surface -- many active repos still rely on older `CLAUDE.md` conventions and therefore need a later deeper handoff pass, not just a minimum-context block -- the current repo itself was intentionally skipped by the live recovery planner because the Phase 104 implementation kept its worktree dirty during execution - -## Verification Summary - -Focused repo verification: -- `python3 -m ruff check src tests` -- `pytest -q tests/test_portfolio_truth.py tests/test_portfolio_catalog.py tests/test_registry_parser.py tests/test_notion_registry.py` - -Live workflow verification: -- ran the recovery planner in dry-run mode against `/Users/d/Projects` -- ran the recovery workflow in bounded apply mode until the clean eligible cohort was exhausted -- re-ran `--portfolio-truth` after the live recovery sweep -- confirmed the latest recovery plan artifacts now show `0` eligible repos in the remaining active/recent weak-context cohort - -What those checks proved: -- the new five-band contract is enforced -- the planner freezes the live target cohort and honors skip/exclusion rules -- dry-run mode does not mutate repos -- live recovery writes stay bounded to the primary context file -- the truth snapshot and compatibility outputs can regenerate safely after recovery work - -Not run in this phase: -- workbook-specific gates, because workbook-facing code was not intentionally changed -- full-repo `pytest -q`, because the Phase 104 cut stayed inside truth, recovery, and compatibility seams - -## Shipped Summary - -`GithubRepoAuditor` now has a real minimum-context recovery system on top of the Phase 103 truth layer. - -After Phase 104: -- the truth contract can express minimum-viable context explicitly -- the workspace has a repeatable planner for active/recent weak-context recovery -- clean eligible repos were upgraded and catalog-seeded without touching dirty or temporary repos -- the remaining weak-context problem is now mostly a local repo hygiene problem rather than a missing workflow problem - -This phase did **not** solve decision quality yet. It made context quality measurable and recoverable enough that Phase 105 can evaluate weekly trust and recommendation quality on stronger footing. - -## Next Phase - -### Phase 105: Decision Quality And Trust Calibration - -Phase 105 should treat the new truth + context layers as stable inputs and formalize how much the system should trust its own guidance. - -Immediate starting point: -1. use [`output/portfolio-truth-latest.json`](/Users/d/Projects/GithubRepoAuditor/output/portfolio-truth-latest.json) as the canonical portfolio fact set -2. use the live remaining weak-context cohort as a weighting signal rather than pretending all repos have equal decision quality -3. inventory the current trust/effectiveness/calibration outputs already present in operator and weekly modules -4. define one decision-quality contract for: - - weekly recommendations - - approval follow-up guidance - - Action Sync readiness posture - - future automation go/no-go gates -5. separate “descriptive evidence” from “decision-confidence” so the next phase does not create a second recommendation engine - -Implementation guidance for Phase 105: -- start by mapping every current trust/effectiveness signal back to the source module that emits it -- define explicit evidence windows, downgrade triggers, and “needs human skepticism” cases before changing any wording -- treat the remaining dirty-repo weak-context set as a live confidence penalty instead of a hidden caveat -- verify workbook, Markdown, HTML, review-pack, and scheduled handoff together if weekly-facing confidence language changes -- end the phase with the same closeout contract: - - review of what was built - - cleanup review - - verification summary - - shipped summary - - detailed next phase - - one-line remaining roadmap summaries - -## Remaining Roadmap - -- `Phase 105` — Turn trust/effectiveness facts into one explicit decision-quality contract for weekly guidance and future automation gates. -- `Phase 106` — Introduce supported portfolio golden paths like maintain, finish, archive, and experiment so guidance becomes intent-aware. -- `Phase 107` — Reboot only the bounded weekly automation loop that can now consume stronger truth, context, and decision-quality inputs. -- `Phase 108` — Add a structured portfolio risk overlay and doctor/release standards that can scale across the key repos in the workspace. diff --git a/docs/plans/2026-04-14-phase-105-closeout.md b/docs/plans/2026-04-14-phase-105-closeout.md deleted file mode 100644 index 07735312..00000000 --- a/docs/plans/2026-04-14-phase-105-closeout.md +++ /dev/null @@ -1,116 +0,0 @@ -# Phase 105 Closeout - -## Review Of What Was Built - -Phase 105 extracted the repo's existing trust and recommendation-quality signals into one bounded decision-quality contract instead of letting that logic continue to sprawl across operator surfaces. - -Core delivered behavior: -- added [`src/operator_decision_quality.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_decision_quality.py) as the single owner of the new `decision_quality_v1` contract -- defined a compact structured contract that now includes: - - `contract_version` - - `authority_cap` - - evidence and validation windows - - judged, validated, partial, reopened, and unresolved recommendation counts - - confidence hit rates and caution rate - - `confidence_validation_status` - - `decision_quality_status` - - `human_skepticism_required` - - `downgrade_reasons` -- wired [`src/operator_control_center.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_control_center.py) to build decision quality through the shared contract seam instead of owning a duplicate calibration path -- updated [`src/operator_snapshot_packaging.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_snapshot_packaging.py) so the packaged `operator_summary` now carries `decision_quality_v1` and mirrors legacy top-level trust fields from that shared source -- updated [`src/warehouse.py`](/Users/d/Projects/GithubRepoAuditor/src/warehouse.py) to persist compact decision-quality summaries in warehouse-backed run history - -What changed in runtime behavior: -- decision quality now has one bounded owner -- the current operator summary exposes one structured trust contract instead of only prose and scattered top-level fields -- older warehouse runs that predate the contract now load as `insufficient-data` rather than being over-read as fully comparable trust history -- the contract carries a fixed `authority_cap` of `advisory-only`, so this phase did not widen execution, approval, or automation posture - -## Cleanup Review - -Removed or reduced: -- duplicate confidence-calibration ownership inside [`src/operator_control_center.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_control_center.py) is now reduced to compatibility shims that delegate to the shared decision-quality module -- warehouse history no longer needs to infer future decision quality from prose-only summaries when explicit contract data is available - -Intentionally preserved: -- top-level trust fields on `operator_summary` still exist for compatibility across workbook, Markdown, HTML, review-pack, and scheduled handoff consumers -- existing evidence windows and scoring semantics were preserved instead of retuned -- weekly authority remains `weekly_story_v1` -- Action Sync, approval, and automation surfaces did not gain stronger authority - -Temporary or compatibility seams that remain: -- several weekly and export surfaces still read mirrored top-level trust fields rather than the nested `decision_quality_v1` object directly -- [`src/operator_resolution_trend.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_resolution_trend.py) still owns the raw calibration primitives and remains a large concentration-risk module -- the repo working tree is still the active implementation baseline, so closeout reflects shipped behavior without pretending the broader tree is pristine - -## Verification Summary - -Focused verification run: -- `git diff --check` -- `python3 -m ruff check src tests` -- `pytest -q tests/test_operator_decision_quality.py tests/test_operator_control_center.py tests/test_operator_effectiveness.py tests/test_weekly_packaging.py tests/test_action_sync_automation.py tests/test_warehouse.py tests/test_reporter.py tests/test_scheduled_handoff.py tests/test_web_export.py tests/test_excel_enhanced.py` - -What those checks proved: -- the new contract is assembled deterministically -- legacy top-level trust fields stay aligned with the shared contract -- warehouse persistence and mixed-history fallback behave as expected -- operator, weekly, workbook/export, Markdown, HTML, and scheduled-handoff surfaces continue to render trust language without breaking compatibility -- the new contract does not widen automation or approval posture through command or authority changes - -Not run in this phase: -- full `pytest -q`, because the phase stayed inside operator trust, packaging, persistence, and surface-render compatibility seams -- workbook gate, because the workbook-facing behavior was covered through the focused export and weekly tests without reopening workbook-specific generation logic - -## Shipped Summary - -`GithubRepoAuditor` now has a real decision-quality contract. - -After Phase 105: -- trust and recommendation-quality reasoning has one bounded owner -- current operator state and warehouse history can carry the same structured trust contract -- old historical runs are handled honestly as `insufficient-data` when they predate the contract -- current surfaces can explain trust and skepticism more consistently without inventing a second recommendation engine -- automation, approval, and execution posture remain bounded and advisory-only - -This phase did **not** widen authority. It made trust measurable and portable enough that the next phase can safely tie portfolio operating paths to explicit decision-quality signals. - -## Next Phase - -### Phase 106: Operating Path Normalization - -Phase 106 should stop treating every repo as the same kind of weekly-review object and make guidance explicitly intent-aware. - -Immediate starting point: -1. use [`output/portfolio-truth-latest.json`](/Users/d/Projects/GithubRepoAuditor/output/portfolio-truth-latest.json) as the canonical portfolio fact set -2. use the newly structured decision-quality signals from current operator history as the trust layer for path-sensitive guidance -3. define supported paths such as: - - maintain - - finish - - archive - - experiment -4. keep `investigate` as a temporary derived override instead of a stable path -5. tie each path to: - - context expectations - - review cadence - - acceptable automation posture - - expected closeout behavior -5. keep `weekly_story_v1` as the only weekly authority while making its guidance path-aware rather than generic - -Implementation guidance for Phase 106: -- treat portfolio intent as a supported operating model, not just a label -- use decision quality to downgrade or gate path confidence, not to invent a new queue -- keep workbook, Markdown, HTML, review-pack, and scheduled handoff aligned if path language changes -- preserve the Phase 105 authority boundary so path-aware guidance still does not auto-upgrade automation or execution posture -- end the phase with the same closeout contract: - - review of what was built - - cleanup review - - verification summary - - shipped summary - - detailed next phase - - one-line remaining roadmap summaries - -## Remaining Roadmap - -- `Phase 106` — Normalize explicit operating paths so weekly guidance becomes intent-aware instead of generic. -- `Phase 107` — Reboot only the bounded weekly command-center automation loop that can now consume stronger truth, context, and decision-quality inputs. -- `Phase 108` — Add a structured portfolio risk overlay and doctor/release standards that can scale across the key repos in the workspace. diff --git a/docs/plans/2026-04-14-phase-106-closeout.md b/docs/plans/2026-04-14-phase-106-closeout.md deleted file mode 100644 index 51767360..00000000 --- a/docs/plans/2026-04-14-phase-106-closeout.md +++ /dev/null @@ -1,99 +0,0 @@ -# Phase 106 Closeout - -## Review Of What Was Built - -Phase 106 normalized operating-path semantics into one truth-layer contract instead of leaving path-like meaning split across catalog fields, scorecard programs, tactical collections, and renderer-local wording. - -Core delivered behavior: -- added [`src/portfolio_pathing.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_pathing.py) as the bounded owner for stable `operating_path`, temporary `path_override`, `path_confidence`, and `path_rationale` -- extended the portfolio truth contract in [`src/portfolio_truth_types.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_truth_types.py) and [`src/portfolio_truth_reconcile.py`](/Users/d/Projects/GithubRepoAuditor/src/portfolio_truth_reconcile.py) so normalized path semantics now live in the machine-facing truth layer -- preserved the stable v1 path vocabulary as: - - `maintain` - - `finish` - - `archive` - - `experiment` -- kept `investigate` override-only instead of allowing it to persist as a stable declared path -- updated workbook, Markdown, HTML, review-pack, operator queue context, and warehouse-backed summaries so they all render the same normalized path story - -What changed in runtime behavior: -- stable path semantics are now derived once and reused -- current surfaces can explain path confidence and temporary caution explicitly -- warehouse-backed summaries now preserve operating-path distribution instead of forcing later readers to infer it from prose -- catalog and scorecard metadata still matter, but they no longer compete as separate path owners - -## Cleanup Review - -Removed or reduced: -- renderer-local path interpretation is reduced because shared path lines and summaries now come from the same normalization seam -- tactical collections such as `finish-next` no longer need to masquerade as canonical path labels - -Intentionally preserved: -- `lifecycle_state`, `intended_disposition`, `maturity_program`, and `target_maturity` still exist as distinct inputs -- tactical collections remain available as derived prioritization overlays -- approval, automation, execution, and command authority remain unchanged - -Compatibility seams that remain: -- some broad surfaces still consume formatted path lines rather than the raw normalized fields directly -- historical artifacts that predate the new path fields still need to be treated as legacy/incomplete instead of first-class path history -- the working tree remains the active implementation baseline, so this closeout describes shipped behavior without pretending the wider tree is pristine - -## Verification Summary - -Focused verification run: -- `git diff --check` -- `python3 -m ruff check src tests` -- `pytest -q tests/test_portfolio_pathing.py tests/test_portfolio_truth.py tests/test_reporter.py tests/test_review_pack.py tests/test_web_export.py tests/test_excel_enhanced.py tests/test_warehouse.py` - -What those checks proved: -- normalized path derivation is deterministic -- `investigate` stays override-only -- truth rendering, workbook/export surfaces, and warehouse-backed summaries all consume the same path contract -- compatibility artifacts and rendered outputs continue to work with the richer path model -- path normalization did not widen approval, execution, or automation posture - -## Shipped Summary - -`GithubRepoAuditor` now has one explicit operating-path model instead of several overlapping ones. - -After Phase 106: -- path-aware portfolio guidance is grounded in one truth-layer contract -- stable path, temporary override, confidence, and rationale are all portable across surfaces -- workbook, Markdown, HTML, review-pack, and operator queue context now speak the same path language -- tactical collections still help with prioritization, but they no longer compete with stable operating-path semantics - -This phase did **not** create a new recommendation engine or change authority. It made existing portfolio intent and maturity signals explicit enough that the next phase can safely restart a bounded weekly command-center loop on top of stronger portfolio truth. - -## Next Phase - -### Phase 107: Weekly Command Center Reboot - -Phase 107 should restart only the bounded weekly automation loop that can now consume: -- portfolio truth -- minimum-context recovery state -- `decision_quality_v1` -- normalized operating-path semantics - -Immediate starting point: -1. treat the paused weekly command-center automation as a candidate, not an automatic default -2. define one canonical weekly digest contract for `/Users/d/Projects` -3. keep the loop report-only and workbook-first -4. make digest prioritization path-aware and trust-aware without widening authority -5. verify the automation reads structured truth instead of stale manual artifacts - -Implementation guidance for Phase 107: -- use normalized operating path and decision quality as bounded advisory inputs -- do not let automation invent a second weekly authority -- preserve manual approval and execution boundaries -- keep the automation non-mutating unless a later policy phase explicitly reopens that contract -- end the phase with the same closeout contract: - - review of what was built - - cleanup review - - verification summary - - shipped summary - - detailed next phase - - one-line remaining roadmap summaries - -## Remaining Roadmap - -- `Phase 107` — Reboot only the bounded weekly command-center automation loop that can now consume stronger truth, context, trust, and path signals. -- `Phase 108` — Add a structured portfolio risk overlay and doctor/release standards that can scale across the key repos in the workspace. diff --git a/docs/plans/2026-04-14-phase-107-closeout.md b/docs/plans/2026-04-14-phase-107-closeout.md deleted file mode 100644 index 0b6f6d91..00000000 --- a/docs/plans/2026-04-14-phase-107-closeout.md +++ /dev/null @@ -1,91 +0,0 @@ -# Phase 107 Closeout - -## Review Of What Was Built - -Phase 107 restarted the weekly command-center loop in the narrowest durable way: by shipping one report-only digest contract instead of trying to revive mutation or automation authority. - -Core delivered behavior: -- fixed operating-path precedence so explicit intended disposition no longer loses to a defaulted maturity program -- seeded explicit catalog contracts for the small set of strategic repos the weekly loop depends on most: - - `GithubRepoAuditor` - - `JobCommandCenter` - - `MCPAudit` - - `ApplyKit` - - `LifeCadenceLedger` -- added [`src/weekly_command_center.py`](/Users/d/Projects/GithubRepoAuditor/src/weekly_command_center.py) as the bounded owner of `weekly_command_center_digest_v1` -- wired `--control-center` and shared artifact refresh to emit: - - `weekly-command-center--.json` - - `weekly-command-center--.md` -- kept the digest derived from the shipped system rather than inventing a new authority: - - `weekly_story_v1` - - operator summary / decision quality - - current portfolio-truth snapshot - -What changed in runtime behavior: -- the weekly loop now has one canonical digest artifact it can consume later -- that digest is path-aware and trust-aware -- the digest stays report-only and workbook-first -- root-level strategic repos now have clearer stable path intent in the catalog instead of falling back to vague defaults - -## Cleanup Review - -Removed or reduced: -- a path-normalization bug where `maturity_program` could silently outrank explicit `intended_disposition` -- one source of weekly-loop ambiguity by giving the paused loop a real structured digest instead of relying on stale hand-maintained notes - -Intentionally preserved: -- `weekly_story_v1` remains the only weekly authority -- the digest does not create commands, execution posture, approval power, or automation widening -- the weekly loop remains bounded and non-mutating - -Compatibility seams that remain: -- many lower-value repos still need better declared path metadata -- the digest is now ready for paused automation to consume, but automation schedules themselves remain a separate explicit decision -- the working tree is still the active implementation baseline, so this closeout describes shipped behavior without pretending the wider tree is pristine - -## Verification Summary - -Focused verification run: -- `python3 -m pytest -q tests/test_portfolio_pathing.py tests/test_weekly_packaging.py tests/test_weekly_command_center.py` -- `python3 -m pytest -q tests/test_cli_hardening.py tests/test_reporter.py tests/test_review_pack.py tests/test_web_export.py tests/test_excel_enhanced.py tests/test_scheduled_handoff.py` -- `python3 -m ruff check src/portfolio_pathing.py src/weekly_command_center.py src/cli.py tests/test_portfolio_pathing.py tests/test_weekly_command_center.py` - -What those checks proved: -- operating-path precedence now matches declared intent better -- the new digest contract is report-only and structurally stable -- shared weekly/report/export surfaces still pass after the digest and path changes -- the weekly reboot did not create a second weekly authority or widen automation posture - -## Shipped Summary - -`GithubRepoAuditor` now has a real weekly command-center digest loop instead of a paused concept. - -After Phase 107: -- the weekly reboot is based on stronger truth, context, trust, and path inputs -- the loop has one bounded digest contract that future automation can read -- strategic repos now carry better declared path intent -- workbook-first review is still the center of gravity - -This phase did **not** unpause external automation schedules, auto-apply anything, or create a new weekly decision engine. It made the weekly loop operationally real without widening authority. - -## Next Phase - -### Phase 108: Risk Overlay + Cross-Repo Doctor Standard - -Phase 108 should add the next missing layer: a reusable, explainable portfolio risk overlay plus a minimal doctor/release-check standard for the key repos that matter most. - -Immediate starting point: -1. keep weekly authority and digest authority bounded exactly as they are now -2. add structured risk posture that can flow into portfolio truth and the weekly digest without becoming noise -3. standardize a minimal doctor/release-check contract for the strategic repos first -4. keep the overlay descriptive and explainable before any thought of auto-remediation - -Implementation guidance for Phase 108: -- use machine-checkable risk posture rather than prose-only warnings -- prefer reusable repo standards over one-off repo-specific fixes -- start with the strategic repos already named in the roadmap -- keep all new risk signals advisory unless a later policy phase explicitly reopens stronger authority - -## Remaining Roadmap - -- `Phase 108` — Add a structured portfolio risk overlay and doctor/release standards that can scale across the key repos in the workspace. diff --git a/docs/plans/2026-04-14-phase-108-closeout.md b/docs/plans/2026-04-14-phase-108-closeout.md deleted file mode 100644 index 4d601fdf..00000000 --- a/docs/plans/2026-04-14-phase-108-closeout.md +++ /dev/null @@ -1,66 +0,0 @@ -# Phase 108 Closeout: Risk Overlay + Cross-Repo Doctor Standard - -## Review Of What Was Built - -Phase 108 adds a structured portfolio risk overlay and a minimal doctor/release-check standard for strategic repos. The overlay is advisory-only — it derives from already-present truth fields and does not widen any automation or approval authority. - -**Core modules:** -- `src/portfolio_risk.py` (new) — owns risk tier derivation. `build_risk_entry()` accumulates up to six risk factors (`weak-context-active`, `investigate-override`, `missing-operating-path`, `missing-doctor-standard`, `no-run-instructions`, `undocumented-risks`), derives tiers (`elevated`, `moderate`, `baseline`, `deferred`), and returns a flat dict matching `RiskFields`. `build_portfolio_risk_summary()` aggregates tier counts. - -**Truth schema (0.3.0 → 0.4.0):** -- `RiskFields` dataclass added to `PortfolioTruthProject` (parallel to `advisory`). -- `declared.doctor_standard` added to `DeclaredFields` (catalog intent, like `operating_path`). -- `VALID_RISK_TIERS` and `VALID_DOCTOR_STANDARDS` constant sets added to `portfolio_truth_types.py`. - -**Catalog enrichment:** -- 5 strategic repos in `portfolio-catalog.yaml` now carry `doctor_standard` (`full` or `basic`). -- GithubRepoAuditor, MCPAudit, and JobCommandCenter also carry explicit `criticality: high`. - -**Pipeline wiring:** -- `portfolio_truth_reconcile.py` computes `risk_entry` after path derivation and wires `RiskFields` into `PortfolioTruthProject`. `doctor_standard` flows through `declared_values` via `_select_declared()`. -- `portfolio_truth_validate.py` validates `risk_tier` against `VALID_RISK_TIERS` and `doctor_standard` against `VALID_DOCTOR_STANDARDS`. -- `portfolio_truth_render.py` adds a `| Risk |` column to the portfolio truth table and a risk posture line to the Coverage Summary. - -**Weekly integration:** -- `weekly_command_center.py` counts risk tiers in `_build_truth_summary()`, surfaces `_build_risk_attention_items()` for elevated repos, and adds `risk_posture` to the digest and `## Risk Posture` to the markdown. - -**Documentation:** -- `docs/doctor-release-standard.md` documents the full and basic standard with stack-specific patterns. -- `docs/architecture.md` updated with Portfolio Risk Overlay and Cross-Repo Doctor Standard sections, and full directory map with 13 new module entries. - -## Cleanup Review - -- No debug code added. No stale imports. -- `ruff check src/ tests/` passes clean. -- No backward-compat shims needed — schema bump is purely additive (new fields, no renames/removals). -- `portfolio-catalog.yaml` changes are additive only — no existing field removals. - -## Verification Summary - -- `python3 -m pytest -q tests/test_portfolio_risk.py` — 11/11 pass -- `python3 -m pytest -q tests/test_portfolio_truth.py` — 13/13 pass (schema 0.4.0, risk field present) -- `python3 -m pytest -q tests/test_portfolio_catalog.py` — 6/6 pass (doctor_standard normalization) -- `python3 -m pytest -q tests/test_weekly_command_center.py` — 1/1 pass (risk_posture in digest and markdown) -- `python3 -m pytest -q` — full suite passing, ruff clean - -## Shipped Summary - -The portfolio truth snapshot now carries a structured risk overlay on every project. Strategic repos have a declared doctor standard. The weekly command center digest surfaces elevated risk items and a risk posture summary. Schema version is 0.4.0. The 103-108 arc is complete. - -## Next Phase - -The 103-108 arc is complete. The next arc candidates are: - -**Arc A: Context Quality Recovery** — 53 active/recent repos still have weak context. `portfolio_context_recovery.py` was built in Phase 104. A focused arc would systematically run recovery against the worst cohort, targeting real repos and improving the 53 weak-context cases. - -**Arc B: Enrichment Layer Risk Integration** — Risk data currently lives in the truth JSON and weekly digest. Wire `risk` through `report_enrichment.py` into workbook Excel, HTML dashboard, and review-pack views so risk posture is visible across all five surfaces, not just two. - -**Arc C: Desktop Portfolio Shell** — JobCommandCenter has the Tauri 2 framing. A richer command-center UI that consumes portfolio truth JSON and surfaces risk, path attention, and weekly digest natively. - -**Arc D: Safe Automation Expansion** — `decision_quality_v1` provides trust gates and the weekly digest is report-only. A future arc could enable bounded automation (e.g., auto-PR context improvements) for repos with high path confidence and high decision quality, using `doctor_standard` conformance as a prerequisite. - -**Arc E: Renderer Simplification** — Five parallel render surfaces (workbook Excel, markdown, HTML dashboard, review-pack, handoff) create a parity tax. Simplifying could reduce maintenance burden. - -## Remaining Roadmap - -The 103-108 arc is now complete. Future arc candidates are documented above and in the roadmap file. diff --git a/docs/plans/2026-04-14-phase-99-closeout.md b/docs/plans/2026-04-14-phase-99-closeout.md deleted file mode 100644 index 6cf06b09..00000000 --- a/docs/plans/2026-04-14-phase-99-closeout.md +++ /dev/null @@ -1,61 +0,0 @@ -# Phase 99 Closeout: Weekly Packaging Extraction - -## Review Of What Was Built - -- extracted the shared weekly contract finalization layer into [`src/weekly_packaging.py`](/Users/d/Projects/GithubRepoAuditor/src/weekly_packaging.py) while keeping `build_weekly_review_pack(...)` in [`src/report_enrichment.py`](/Users/d/Projects/GithubRepoAuditor/src/report_enrichment.py) as the public compatibility façade -- moved `weekly_story_v1` assembly, evidence-item building, and compact explainability enrichment for `top_attention` and `repo_briefings` behind the new `finalize_weekly_pack(...)` seam -- thinned [`src/scheduled_handoff.py`](/Users/d/Projects/GithubRepoAuditor/src/scheduled_handoff.py) so it reads shared weekly-story fields and section values more directly before falling back to legacy `operator_summary` fields -- added focused extraction coverage in [`tests/test_weekly_packaging.py`](/Users/d/Projects/GithubRepoAuditor/tests/test_weekly_packaging.py) and kept the existing weekly-story parity suite in place - -## Cleanup Review - -- removed the extracted private weekly packaging helpers from [`src/report_enrichment.py`](/Users/d/Projects/GithubRepoAuditor/src/report_enrichment.py) so the new seam has one internal home instead of split ownership -- kept the wider `weekly_pack` assembly logic in place; this phase did not widen into a full enrichment rewrite -- kept scheduled-handoff legacy fallback behavior for older payload compatibility instead of over-cleaning that seam prematurely - -## Verification Summary - -- focused parity checks: - - `python3 -m pytest -q tests/test_weekly_packaging.py tests/test_weekly_story.py tests/test_excel_enhanced.py` -- full repo gates: - - `python3 -m ruff check src tests` - - `pytest -q` - - `make workbook-gate` -- workbook gate result: - - automated checks passed - - cross-mode parity checks passed - - manual desktop Excel signoff was not run because this phase stayed behavior-preserving and the automated workbook gate did not show workbook-visible drift requiring escalation - -## Shipped Summary - -Phase 99 leaves the repo with a dedicated weekly packaging seam in `src/weekly_packaging.py`, a thinner scheduled-handoff consumer of shared weekly fields, and the same `weekly_story_v1` contract flowing through workbook, Markdown, HTML, review-pack, and scheduled handoff without changing the public `build_weekly_review_pack(...)` entrypoint. - -## Next Phase - -### Phase 100: Operator Core Decomposition - -Objective: -- Reduce the maintenance and change-risk concentration inside [`src/operator_control_center.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_control_center.py) by extracting bounded submodules without changing queue behavior or operator semantics. - -Why it is next: -- The weekly seam is now safer, but the operator core is still the largest architectural risk in the repo. -- Approval follow-up and later scheduling work should not land on top of a `37k+` line operator module if we can avoid it. - -Main work: -- identify behavior-preserving extraction seams for: - - queue shaping - - follow-through state - - intervention-history synthesis - - trust/actionability packaging -- keep one compatibility façade while splitting the internals into smaller modules -- strengthen regression coverage around the extracted seams before broadening later approval work - -Main risks: -- semantic drift during extraction from a highly concentrated file -- moving code faster than tests can prove parity -- accidentally widening the phase into queue-model redesign - -## Remaining Roadmap - -- `Phase 101`: Add tracked approval follow-up facts and recurring review support without widening write authority. -- `Phase 102`: Reopen approval-aware weekly scheduling inside the shared weekly contract after the tracked approval foundation exists. diff --git a/docs/plans/2026-04-14-roadmap-phases-103-108.md b/docs/plans/2026-04-14-roadmap-phases-103-108.md deleted file mode 100644 index ed312fa5..00000000 --- a/docs/plans/2026-04-14-roadmap-phases-103-108.md +++ /dev/null @@ -1,607 +0,0 @@ -# Roadmap: Phases 103-108 - -## Current Status Snapshot -- the `98-102` roadmap arc is complete, but the current working tree is still the active operating baseline for the `103-108` follow-on work -- the workbook manual signoff is recorded and the workbook gate is back to `ready` -- `weekly_story_v1` remains the only weekly authority -- approval follow-up history and bounded approval-aware weekly scheduling are both shipped -- the repo-side health baseline is strong: - - `python3 -m ruff check src tests` passes - - `pytest -q` passes - - `make workbook-gate` passes -- the codebase still carries concentration risk in a few large modules: - - `src/operator_resolution_trend.py` (`32839` lines) - - `src/operator_follow_through.py` (`3704` lines) - - `src/operator_snapshot_packaging.py` (`3136` lines) - - `src/scheduled_handoff.py` (`2256` lines) - - `src/report_enrichment.py` (`2024` lines) -- the test suite is materially stronger than the old bootstrap framing suggests: - - `765` tests collected -- the product is no longer best described as a repo scoring CLI; it is now a workbook-first portfolio operating system with shared weekly packaging, operator triage, bounded execution guidance, and warehouse-backed history -- normalized operating-path semantics are now part of the truth-layer contract rather than spread across catalog, scorecard, and renderer wording -- the bounded weekly command-center digest loop is now real: - - `--control-center` writes `weekly-command-center--.json` and `.md` - - the digest is report-only and derived from `weekly_story_v1` + operator summary + portfolio truth -- the broader `/Users/d/Projects` workspace is the real leverage bottleneck: - - `114` total projects - - `4` projects with full context - - `8` with standard context - - `13` with minimum-viable context - - `87` with boilerplate context - - `2` with no context -- important portfolio automations already exist but the relevant weekly ones are paused: - - `weekly-command-center` - - `weekly-governance-health` - - `weekly-docs-knowledge-sync` - -## Successor Relationship - -This roadmap is the successor to [2026-04-14-roadmap-phases-98-102.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-14-roadmap-phases-98-102.md). - -The `98-102` arc repaired architecture and weekly authority. The `103-108` arc should not simply add more recommendation logic. It should turn the shipped system into a durable portfolio operating layer that can survive a large local workspace, recurring automation, and future thread handoffs. - -## Why This Roadmap Exists - -The repo is no longer in immediate feature debt or delivery chaos. The next problem is larger and more structural: - -1. the product is stronger than the portfolio metadata around it -2. the workspace has many projects but weak context coverage -3. recurring operating loops exist, but they are not yet fed by a reliable enough portfolio truth layer -4. future work risks fragmenting across sister repos unless responsibilities are made explicit -5. the next thread should not need to rediscover the current strategy from chat history - -This roadmap exists to solve those problems directly. - -## Reality Check From The Fresh Audit - -### What the repo actually is now - -`GithubRepoAuditor` is now the most mature candidate for the central portfolio command surface in `/Users/d/Projects`. - -It already owns: -- multi-surface weekly review -- operator triage -- action readiness and approval freshness -- workbook-first review -- longitudinal warehouse-backed history - -It does **not** yet fully own: -- the broader project registry as a canonical fact system -- cross-project minimum context enforcement -- portfolio-level decision quality measurement across sister repos -- the weekly automation loop that should sit on top of those facts - -### What the surrounding workspace is telling us - -The workspace already contains adjacent systems that point toward a larger operating model: - -- [JobCommandCenter](/Users/d/Projects/JobCommandCenter/README.md): command-center framing and desktop operating shell direction -- [DecisionStressTest](/Users/d/Projects/DecisionStressTest/README.md): staged analysis, doctor flows, immutable snapshots, release discipline -- [MCPAudit](/Users/d/Projects/MCPAudit/README.md): local risk/audit posture for agent and MCP systems -- [ResumeEvolver](/Users/d/Projects/ResumeEvolver/README.md): evidence-led private-first workflow and strong phase discipline -- [LifeCadenceLedger](/Users/d/Projects/LifeCadenceLedger/README.md): cadence and recurring obligation concepts -- [ApplyKit](/Users/d/Projects/ApplyKit/README.md): deterministic local-first package generation with strong trust boundaries - -The pattern is clear: this is no longer one repo among many. It is becoming the planning and review spine for a portfolio of local-first tools. - -### What is weak right now - -- the portfolio truth layer is stale relative to the repo -- most projects still lack usable context -- important weekly operating automations are paused -- the repo has no post-102 roadmap arc yet -- several historical planning artifacts still reflect an older, smaller product identity - -### What would be a mistake next - -- building another recommendation engine -- expanding write automation before measuring recommendation quality -- moving straight into a richer app shell without first fixing portfolio truth -- pretending the repo is still just an analyzer when the workspace is already using portfolio-style workflows - -## External Research That Changed This Roadmap - -The strongest outside patterns were not generic “dashboard” ideas. They were: - -### 1. Shared truth with multiple views beats one giant surface - -GitHub Projects is explicitly built as an adaptable collection of items that can be viewed as a table, board, or roadmap while staying up to date with GitHub data. - -Sources: -- [Planning and tracking with Projects](https://docs.github.com/en/issues/planning-and-tracking-with-projects) -- [Quickstart for Projects](https://docs.github.com/en/issues/planning-and-tracking-with-projects/learning-about-projects/quickstart-for-projects) -- [Best practices for Projects](https://docs.github.com/en/enterprise-cloud@latest/issues/planning-and-tracking-with-projects/learning-about-projects/best-practices-for-projects) - -Implication for this repo: -- keep one canonical portfolio truth layer -- allow many views and artifacts -- do not let each surface invent its own state - -### 2. Good workflow systems reduce manual upkeep by standardizing fields, views, and updates - -GitHub’s own Projects guidance emphasizes descriptions, READMEs, status updates, customized views, fields, automation, charts, and templates, with a specific warning to maintain a single source of truth. - -Sources: -- [Best practices for Projects](https://docs.github.com/en/enterprise-cloud@latest/issues/planning-and-tracking-with-projects/learning-about-projects/best-practices-for-projects) -- [Sharing project updates](https://docs.github.com/en/issues/planning-and-tracking-with-projects/sharing-project-updates) - -Implication for this repo: -- the next arc should formalize portfolio truth and update cadence before adding more behavior - -### 3. Approval and automation systems should keep human freshness gates explicit - -GitHub environments and protected branches emphasize manual approval, required reviewers, self-review prevention, and stale approval invalidation after changes. - -Sources: -- [Deployments and environments](https://docs.github.com/en/actions/reference/workflows-and-actions/deployments-and-environments) -- [About protected branches](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches) - -Implication for this repo: -- safe automation should only expand after the repo can measure recommendation quality and freshness well - -### 4. Strong local-first and docs-like-code systems win through ownership, not just presentation - -Backstage’s adoption guidance and Spotify’s TechDocs writeup emphasize catalog ownership, golden paths, docs-like-code, and metrics instead of treating the portal UI as the main product. - -Sources: -- [Getting started with Backstage adoption](https://backstage.io/docs/next/golden-path/adoption/getting-started) -- [Announcing TechDocs](https://backstage.io/blog/2020/09/08/announcing-tech-docs/) - -Implication for this repo: -- the next arc should focus on supported portfolio paths and ownership-quality signals before heavier surface expansion - -### 5. Security and repo health layers are most useful when they are machine-checkable and recurring - -OpenSSF Scorecard is useful because it turns repo health and security posture into recurring, explainable checks with clear remediation direction. - -Source: -- [OpenSSF Scorecard](https://scorecard.dev/) - -Implication for this repo: -- future security/risk work should be a structured overlay, not scattered one-off warnings - -### 6. Community examples reinforce the local-first, report-first pattern - -Useful analogs: -- [Repo Dashboard - A Local GitHub Visibility Tool](https://albertoroura.com/repo-dashboard-local-github-visibility-tool/) -- [github-repo-stats](https://github.com/jgehrcke/github-repo-stats) -- [RepoSense user guide](https://reposense.org/ug/index.html) - -Implication for this repo: -- keep workbook and report artifacts serious -- avoid turning the next arc into “replace everything with a bigger app” - -## Strategic Point Of View For 103-108 - -The next arc should optimize for: - -1. truthful portfolio metadata -2. reliable project context -3. measurable decision quality -4. supported operating paths for different project intents -5. safe weekly automation built on those facts -6. risk overlays and doctor-style standards that can scale across the workspace - -The next arc should **not** optimize for: - -- a second weekly authority -- more implicit automation -- another queue or scoring rewrite -- portalization for its own sake -- broad cross-repo mutation before truth and trust are stronger - -## Phase Workflow Standard - -Use [phase-closeout-template.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/phase-closeout-template.md) as the closeout contract for every phase in this arc. - -Every phase in this roadmap should end with: - -1. review of what was built -2. cleanup of what is no longer needed -3. verification summary -4. shipped summary -5. detailed writeup for the next phase -6. one-line summaries for the remaining roadmap phases - -## Cross-Arc Guardrails - -- Keep `weekly_story_v1` as the only weekly authority unless a later roadmap explicitly reopens that contract. -- Do not change `operator_queue`, `primary_target`, or `what_to_do_next` unless a dedicated future phase explicitly reopens operator-core decision semantics. -- Do not widen command authority before decision-quality evidence exists. -- Prefer structured facts and supported paths over clever heuristics. -- Preserve workbook-first operation while the workbook remains the highest-signal operating surface. -- When a change affects weekly-facing summaries, verify workbook, Markdown, HTML, review-pack, and scheduled handoff together. -- When a change touches portfolio truth, verify compatibility against the broader `/Users/d/Projects` workspace, not just this repo. - -## Canonical Inputs For This Arc - -These artifacts should be treated as the main source-of-truth set for planning and implementation: - -- [README.md](/Users/d/Projects/GithubRepoAuditor/README.md) -- [docs/architecture.md](/Users/d/Projects/GithubRepoAuditor/docs/architecture.md) -- [docs/weekly-review.md](/Users/d/Projects/GithubRepoAuditor/docs/weekly-review.md) -- [docs/plans/2026-04-14-phase-102-closeout.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-14-phase-102-closeout.md) -- [project-registry.md](/Users/d/Projects/project-registry.md) -- [PORTFOLIO-AUDIT-REPORT.md](/Users/d/Projects/PORTFOLIO-AUDIT-REPORT.md) - -The workspace-level registry and audit artifacts are not yet fully trustworthy enough to be final authority, but they are important current-state evidence and should be actively reconciled during this arc. - -## Phase 103: Portfolio Truth Layer -Status: Shipped - -Goal: -- Make `GithubRepoAuditor` the explicit source of truth for the broader `/Users/d/Projects` portfolio layer, not just for its own repo outputs. - -Why now: -- the current portfolio registry and portfolio audit artifacts are stale -- the workspace has many projects with weak or missing context -- weekly command-center style automation is not worth reactivating until portfolio truth is stronger - -Key targets: -- define a canonical portfolio truth schema for projects under `/Users/d/Projects` -- ingest and reconcile: - - project identity - - status - - category - - tool provenance - - context quality - - last meaningful activity - - ownership or operator intent if available -- regenerate [project-registry.md](/Users/d/Projects/project-registry.md) from structured facts instead of treating it as a hand-maintained artifact -- regenerate [PORTFOLIO-AUDIT-REPORT.md](/Users/d/Projects/PORTFOLIO-AUDIT-REPORT.md) from the same truth layer or explicitly downgrade it from canonical status -- add explicit self-representation for `GithubRepoAuditor` in the portfolio -- create a compatibility path for existing registry consumers and Cowork tasks - -Non-goals: -- no weekly automation restart yet -- no cross-repo mutation -- no deeper portal UI work yet - -Exit criteria: -- one clear canonical portfolio truth path exists -- registry and portfolio audit can be regenerated from current facts -- stale registry drift is materially reduced -- downstream consumers know which artifact is authoritative - -Shipped outcomes: -- added the versioned portfolio truth subsystem and safe publish path behind `--portfolio-truth` -- made `output/portfolio-truth-latest.json` the canonical workspace portfolio artifact -- regenerated `/Users/d/Projects/project-registry.md` and `/Users/d/Projects/PORTFOLIO-AUDIT-REPORT.md` as derived compatibility outputs -- fail-closed the legacy `--sync-registry` mutation path -- preserved parser compatibility while moving the source of truth into the structured snapshot - -## Phase 104: Minimum Viable Context Recovery -Status: Shipped - -Goal: -- reduce the portfolio’s context deficit by creating a minimum viable context contract and systematically improving low-context repos. - -Why now: -- Phase 103 made the truth layer real, but the stronger contract still exposed too many active/recent repos as weak-context execution risks -- weak context remains the biggest blocker to trustworthy weekly and portfolio-level operating decisions - -Key targets: -- define a minimum viable context contract for local projects: - - what the project is - - current state - - stack - - how to run it - - known risks - - next recommended move -- add context-quality scoring rules that distinguish: - - none - - boilerplate - - minimal viable - - standard - - full -- build or document a repeatable context-recovery workflow for active and recent projects first -- prioritize the first recovery sweep across: - - active projects - - recent projects - - strategic adjacent projects tied to this repo’s future arc - -Non-goals: -- no attempt to fully rehabilitate every archived repo -- no major repo implementation inside sibling projects unless separately scoped - -Exit criteria: -- minimum viable context is defined and enforced in the portfolio truth layer -- the number of no-context projects drops meaningfully for active and recent repos -- future weekly planning can reason over better context instead of folder names and timestamps alone - -Shipped outcomes: -- schema `0.2.0` added the `minimum-viable` band plus explicit completeness booleans and `primary_context_file` -- `--portfolio-context-recovery` now freezes the active/recent weak-context cohort, writes plan artifacts, and applies bounded repo-local recovery updates -- the phase seeded managed context blocks across `25` workspace repos and added bounded repo-level catalog seeds for those recovered priorities -- the current live snapshot now reports `13` minimum-viable repos and `53` active/recent weak-context repos still remaining -- the remaining weak-context cohort is no longer a clean automation candidate: - - `51` are currently skipped by the live planner because of local safety rules such as dirty worktrees - - `2` are excluded as temporary/generated repos - -## Phase 105: Decision Quality And Trust Calibration -Status: Shipped - -Goal: -- turn existing trust/effectiveness/calibration signals into a first-class decision-quality layer for weekly recommendations, approval guidance, and bounded execution posture. - -Why now: -- the repo already produces descriptive trust and effectiveness facts -- future automation should be gated by measured recommendation quality, not intuition - -Key targets: -- define a decision-quality contract for: - - weekly decisions - - approval follow-up recommendations - - Action Sync readiness suggestions - - bounded automation posture -- formalize evidence windows, hit-rate logic, downgrade triggers, and “needs human skepticism” cases -- make operator-visible confidence explanations clearer without inventing a second recommendation engine -- expose enough historical measurement to support later automation go/no-go choices - -Non-goals: -- no scoring-system rewrite -- no change to weekly authority -- no automation widening yet - -Exit criteria: -- recommendation quality is measurable and explainable -- later phases can use explicit trust gates instead of judgment calls alone - -Shipped outcomes: -- added `src/operator_decision_quality.py` as the bounded owner for `decision_quality_v1` -- attached the versioned contract to `operator_summary` and mirrored legacy top-level trust fields from that shared contract -- removed duplicate confidence-calibration ownership from `src/operator_control_center.py` by delegating through the shared decision-quality seam -- persisted compact `decision_quality_v1` summaries in warehouse-backed run history -- downgraded warehouse reads for legacy runs without the contract to `insufficient-data` instead of pretending they are directly comparable -- kept weekly, approval, automation, and execution posture advisory-only by preserving a fixed `authority_cap` of `advisory-only` - -## Phase 106: Operating Path Normalization -Status: Shipped - -Goal: -- stop treating all repos as the same kind of weekly-review object and normalize supported operating paths inside the truth layer instead of leaving them split across catalog, scorecards, tactical collections, and renderer-local wording. - -Why now: -- once portfolio truth and minimum viable context are real, the system can guide by repo intent instead of generic summaries - -Key targets: -- define stable operating paths: - - maintain - - finish - - archive - - experiment -- make `investigate` a derived temporary override instead of a stable path -- extend the truth contract so it carries: - - stable `operating_path` - - `maturity_program` - - `target_maturity` - - derived `path_override` - - derived `path_confidence` - - derived `path_rationale` -- enrich workbook, Markdown, HTML, review-pack, warehouse history, and compatibility outputs so they all read the same normalized path semantics -- keep tactical collections like `finish-next` and `archive-soon` useful without letting them become canonical path labels - -Non-goals: -- no new UI shell -- no broad automation rollout - -Exit criteria: -- supported paths are explicit and usable -- truth and rendered surfaces agree on path meaning -- temporary overrides remain clearly distinct from stable declared path - -Shipped outcomes: -- added `src/portfolio_pathing.py` as the bounded owner of normalized operating-path derivation -- extended the portfolio truth contract to carry stable path, override, confidence, and rationale fields -- normalized the relationship between `intended_disposition`, `maturity_program`, `target_maturity`, and stable `operating_path` -- kept `investigate` override-only so temporary caution does not become a durable path label -- rewired workbook, Markdown, HTML, review-pack, operator queue context, and warehouse-backed summaries to consume the same path contract -- preserved approval, automation, execution, and command authority boundaries while making path guidance more explicit - -## Phase 107: Weekly Command Center Reboot -Status: Shipped - -Goal: -- reactivate a bounded weekly command-center automation loop built on stronger truth, context, and decision-quality foundations. - -Why now: -- the local automation layer already exists but is paused -- it should only restart after the truth and context layers are more reliable - -Key targets: -- define the canonical weekly command-center digest contract for `/Users/d/Projects` -- unpause or replace the existing weekly automation only after its inputs are trustworthy -- ensure the digest is report-only and aligned with workbook-first review rather than competing with it -- connect the digest to: - - portfolio changes - - current risk - - decisions needed - - top next actions -- verify that the automation does not mutate repos or external systems unless a later explicit policy phase authorizes it - -Non-goals: -- no auto-apply behavior -- no full portfolio orchestration engine - -Exit criteria: -- one reliable weekly portfolio digest loop is operational -- the weekly automation consumes structured portfolio truth rather than stale hand-maintained artifacts - -Delivered: -- added `weekly_command_center_digest_v1` as a canonical report-only digest contract -- wired `--control-center` to emit weekly command-center JSON and Markdown artifacts beside the normal control-center output -- made the digest consume the shared weekly story, current decision-quality posture, and live portfolio-truth/path-attention facts -- kept the reboot bounded: - - no auto-apply behavior - - no authority widening - - no second weekly authority - -## Phase 108: Risk Overlay + Cross-Repo Doctor Standard -Status: Shipped - -Goal: -- add a structured portfolio risk overlay and standardize doctor/release-check patterns across the most important repos in the workspace. - -Why now: -- by this point the system should know what exists, what matters, and how much to trust its own recommendations -- the next leverage step is consistent risk and readiness posture across the portfolio - -Key targets: -- add a portfolio risk overlay informed by repo-health and security patterns -- evaluate whether selected OpenSSF-style checks should be surfaced in a portfolio-friendly way -- standardize a minimal doctor/release-check contract for strategic repos -- capture which neighboring repos should conform first, such as: - - `GithubRepoAuditor` - - `JobCommandCenter` - - `DecisionStressTest` - - `MCPAudit` - - `ResumeEvolver` -- make risk posture visible in portfolio truth and weekly command-center outputs - -Non-goals: -- no full security platform -- no portfolio-wide mutation or auto-remediation - -Exit criteria: -- portfolio-level risk overlays exist and are explainable -- doctor/release standards are documented and reusable across key repos - -Shipped outcomes: -- `src/portfolio_risk.py` — new risk module with `build_risk_entry()` and `build_portfolio_risk_summary()` -- `RiskFields` dataclass added to `PortfolioTruthProject`; schema bumped to `0.4.0` -- `declared.doctor_standard` added to `DeclaredFields` and catalog normalization -- 5 strategic repos in `portfolio-catalog.yaml` now carry `doctor_standard` (full/basic) and criticality -- risk tier derivation wired into the reconcile pipeline -- risk tier validation in `portfolio_truth_validate.py` -- `Risk` column added to the portfolio truth table in `portfolio_truth_render.py` -- risk posture line and `## Risk Posture` section added to the weekly command center digest and markdown -- `docs/doctor-release-standard.md` documents the standard for strategic repos -- 11 new tests in `tests/test_portfolio_risk.py`; existing tests updated for schema 0.4.0 and doctor_standard - -## Longer-Horizon Candidate Arcs After 108 - -These are real candidates, but they should not displace `103-108` without a fresh audit: - -- desktop portfolio shell / richer command-center UI -- knowledge and evidence fabric across repos -- broader safe automation expansion -- renderer simplification if parity tax becomes the dominant cost -- deeper integration with sister repos where responsibilities should be split rather than duplicated - -## Roadmap Summary - -The recommended default sequence is: - -1. `Phase 103` — Portfolio Truth Layer -2. `Phase 104` — Minimum Viable Context Recovery -3. `Phase 105` — Decision Quality And Trust Calibration -4. `Phase 106` — Operating Path Normalization -5. `Phase 107` — Weekly Command Center Reboot -6. `Phase 108` — Risk Overlay + Cross-Repo Doctor Standard - -This order is deliberate: - -- truth before automation -- context before policy -- trust measurement before leverage expansion -- supported paths before richer orchestration -- risk overlays after the portfolio layer is stable enough to use them - -## Rejected Or Deferred Directions - -The following directions are intentionally not the default next arc: - -- **operator-core rewrite** - - rejected because the current leverage bottleneck is outside the repo core, not inside the weekly contract -- **new weekly authority** - - rejected because `weekly_story_v1` is now a hard-won stable contract -- **automation-first arc** - - rejected because the workspace truth layer and decision-quality evidence are not strong enough yet -- **portalization-first arc** - - rejected because a larger shell would sit on top of weak portfolio metadata -- **full portfolio mutation** - - rejected because the current operating stance is still bounded and human-led - -## Phase Readiness Gates - -Before opening each phase, verify: - -### Before Phase 103 -- current portfolio artifacts are re-audited -- expected consumers of registry/audit artifacts are identified -- no hidden dependency will break if the registry becomes generated - -### Before Phase 104 -- Phase 103 truth schema is stable enough to score context quality consistently - -### Before Phase 105 -- Phase 104 has materially improved context quality for active and recent projects - -### Before Phase 106 -- decision-quality evidence exists and supported paths can be tied to trustworthy metadata - -### Before Phase 107 -- weekly command-center inputs are trustworthy enough to avoid garbage-in/garbage-out automation - -### Before Phase 108 -- strategic repo set is stable enough to standardize doctor/release expectations without thrash - -## Documentation Requirements For This Arc - -At minimum, keep these docs current as work progresses: - -- the active roadmap for the arc -- each phase closeout note -- `docs/architecture.md` when architecture meaning changes materially -- `README.md` when the repo’s product framing changes -- any decision records for work that is deferred, superseded, or materially re-sequenced - -Historical design notes should remain historical. If they conflict with shipped reality, they should be marked as superseded or clearly labeled as non-current context. - -## Thread Continuity Anchor - -When it is time to start a new thread, the next thread should anchor on this roadmap first. - -Recommended read order for the next thread: - -1. [2026-04-14-roadmap-phases-103-108.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-14-roadmap-phases-103-108.md) -2. [2026-04-14-phase-102-closeout.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-14-phase-102-closeout.md) -3. [docs/architecture.md](/Users/d/Projects/GithubRepoAuditor/docs/architecture.md) -4. [project-registry.md](/Users/d/Projects/project-registry.md) -5. [PORTFOLIO-AUDIT-REPORT.md](/Users/d/Projects/PORTFOLIO-AUDIT-REPORT.md) - -The next thread should assume: - -- `98-102` is complete -- this roadmap is the active roadmap -- the default next phase is `Phase 103` -- the main current risk is portfolio truth and context quality, not weekly packaging or approval behavior - -## Verification Baseline For This Roadmap Refresh - -This roadmap was written after: - -- local repo audit of current docs and architecture -- workspace audit of `/Users/d/Projects` -- targeted review of adjacent strategic repos -- current automation inventory review under `/Users/d/.codex/automations` -- focused web research across GitHub Docs, GitHub workflow guidance, Backstage, OpenSSF Scorecard, local-first references, and high-signal community examples - -Repo-side checks at roadmap creation time: - -- `python3 -m ruff check src tests` -- targeted pytest weekly/approval/surface suites -- current `main` clean and up to date - ---- - -## Post-Arc A Status (2026-04-15) - -Arc A (Context Quality Recovery, Phases 113-118) is complete. See [`docs/plans/2026-04-15-arc-a-closeout.md`](2026-04-15-arc-a-closeout.md) for full details. - -**Key outcomes**: -- Elevated repos: 54 → 16 (−38) -- Baseline repos: 40 → 78 (+38) -- 72 repos had managed context blocks written (0 failures) -- Remaining 16 elevated repos all have `investigate-override` — requires catalog review, not context recovery -- Arc D prerequisite met (elevated ≤ 20) diff --git a/docs/plans/2026-04-14-roadmap-phases-98-102.md b/docs/plans/2026-04-14-roadmap-phases-98-102.md deleted file mode 100644 index bb57f61c..00000000 --- a/docs/plans/2026-04-14-roadmap-phases-98-102.md +++ /dev/null @@ -1,233 +0,0 @@ -# Roadmap: Phases 98-102 - -## Current Status Snapshot -- `main` is clean and all open PRs are closed -- Successor roadmap for the next arc: [2026-04-14-roadmap-phases-103-108.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-14-roadmap-phases-103-108.md) -- Phase 98 is complete and establishes the current docs/governance baseline -- Phase 99 is complete and extracts the weekly packaging seam into `src/weekly_packaging.py` -- Phase 100 is complete and decomposes the operator core into façade + subsystem modules -- Phase 101 is complete and adds tracked approval follow-up history plus shared approval freshness packaging -- Phase 102 is complete and adds a bounded approval-aware weekly scheduling overlay inside the tracked weekly authority -- The tracked weekly authority is still `weekly_story_v1` -- Deferred phase decisions now live under [`docs/plans/decisions/`](/Users/d/Projects/GithubRepoAuditor/docs/plans/decisions/) -- The phase closeout contract now lives in [phase-closeout-template.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/phase-closeout-template.md) -- The product is operationally healthy: lint and test baselines are passing -- The 98-102 roadmap arc is complete; any later work needs a new roadmap arc instead of extending this one implicitly - -## Why This Roadmap Exists - -The repo is no longer in a firefighting state. The next several phases should not -optimize for more visible features first. They should optimize for: - -1. truthful planning surfaces -2. safer module boundaries -3. smaller blast radius for future changes -4. reopening deferred approval work only after the tracked architecture can absorb it cleanly - -Phase 98 already handled the truth-reset and workflow-governance cleanup for this arc. The remaining work is implementation-facing again, but it should still respect the same sequencing discipline. - -This roadmap intentionally avoids reviving deferred approval work too early. -Phase 94 and Phase 95 were deferred for good reasons: - -- the tracked approval model does not yet carry the recurring follow-up state that those phases need -- the current core seams are already large enough that dropping new approval logic into them would create avoidable rework - -## Phase Workflow Standard - -Use [phase-closeout-template.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/phase-closeout-template.md) as the closeout contract for every phase in this arc. - -Every phase should end with: - -1. review of what was built -2. cleanup of what is no longer needed -3. verification summary -4. shipped summary -5. detailed writeup for the next phase -6. one-line summaries for the remaining roadmap phases - -## Deferred Decision Records - -Deferred work from this roadmap arc should be recorded and maintained in: - -- [2026-04-13-deferred-approval-follow-up-foundation.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/decisions/2026-04-13-deferred-approval-follow-up-foundation.md) -- [2026-04-13-deferred-approval-aware-weekly-scheduling.md](/Users/d/Projects/GithubRepoAuditor/docs/plans/decisions/2026-04-13-deferred-approval-aware-weekly-scheduling.md) - -## Guardrails For This Arc - -- Keep `weekly_story_v1` as the only weekly authority until a later phase explicitly changes that contract. -- Do not reintroduce a second weekly recommendation engine. -- Do not reopen approval-aware scheduling before tracked approval follow-up facts exist. -- Prefer extraction and boundary cleanup over layering new behavior into already-large modules. -- Keep workbook, Markdown, HTML, review-pack, and scheduled handoff aligned through shared packaging seams. - -## Phase 98: Truth Reset + Delivery Governance Baseline -Status: Complete - -Goal: -- Refresh the planning and architecture docs so they describe the actual shipped state, the real deferred work, and the next executable sequence without relying on thread history. - -Why now: -- The old roadmap and orientation docs carried archive-era wording, old product framing, and no durable closeout contract for future phases. -- Starting future implementation from stale roadmap text is an avoidable planning failure. - -Key targets: -- update roadmap status from historical closeout language to post-ship truth -- classify deferred approval work explicitly: - - `Phase 94`: follow-up foundation, still not shipped - - `Phase 95`: scheduling overlay, still not shipped -- define the active architecture pressure points: - - `src/operator_control_center.py` - - `src/report_enrichment.py` - - `src/excel_export.py` - - `src/scheduled_handoff.py` -- write the next-phase sequencing rule directly into docs: extraction before deferred approval features -- add a lightweight deferred-decision log -- upgrade the existing PR template and publish a reusable closeout template - -Exit criteria: -- roadmap and architecture docs match current `main` -- deferred approval work is described as design backlog, not half-shipped behavior -- the next phase after 98 is named and scoped in writing -- the phase closeout workflow is documented in one active place instead of hidden in historical roadmap prose - -Closeout: -- [Phase 98 closeout](/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-13-phase-98-closeout.md) - -## Phase 99: Weekly Packaging Extraction -Status: Complete - -Goal: -- Extract `weekly_story_v1` assembly into a dedicated packaging seam so shared weekly behavior is easier to test, evolve, and reuse without further inflating `src/report_enrichment.py`. - -Why now: -- `weekly_story_v1` is the correct release contract, but its assembly still lives inside a broad enrichment module. -- Future weekly additions will be safer if the contract builder is isolated before more features land on it. - -Key targets: -- create a dedicated weekly packaging module for `weekly_story_v1` -- move weekly section-building helpers out of `src/report_enrichment.py` -- preserve the current visible weekly behavior exactly -- keep renderer behavior stable while making scheduled handoff a thinner consumer - -Non-goals: -- no new weekly recommendation layer -- no approval-aware scheduling -- no queue precedence changes - -Exit criteria: -- `weekly_story_v1` is built outside the current god-module path -- cross-surface parity tests still pass -- scheduled handoff relies more directly on shared packaging and less on local fallback meaning - -Closeout: -- [Phase 99 closeout](/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-14-phase-99-closeout.md) - -## Phase 100: Operator Core Decomposition -Status: Complete - -Goal: -- Reduce the maintenance risk inside `src/operator_control_center.py` by splitting raw operator logic into bounded submodules without changing behavior. - -Why now: -- The operator core is the largest and highest-risk file in the repo. -- Approval follow-up and later scheduling work will be unsafe if this remains the only place to land new operator logic. - -Key targets: -- keep [`src/operator_control_center.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_control_center.py) as the public façade while extracting: - - [`src/operator_snapshot_packaging.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_snapshot_packaging.py) - - [`src/operator_follow_through.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_follow_through.py) - - [`src/operator_resolution_trend.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_resolution_trend.py) - - [`src/operator_control_center_rendering.py`](/Users/d/Projects/GithubRepoAuditor/src/operator_control_center_rendering.py) -- move code without changing queue order, snapshot schema, or operator semantics -- strengthen regression coverage around the extracted seams and add an explicit snapshot contract suite - -Non-goals: -- no feature expansion -- no new queue model -- no new writeback authority - -Exit criteria: -- `src/operator_control_center.py` is materially smaller -- extracted modules have focused tests -- queue behavior and surface outputs remain stable - -Closeout: -- [Phase 100 closeout](/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-14-phase-100-closeout.md) - -## Phase 101: Approval Follow-Up Foundation -Status: Complete - -Goal: -- Reopen the deferred Phase 94 work properly by adding tracked approval follow-up facts and recurring review support to the approval architecture without introducing any automatic mutation. - -Why now: -- This is the first phase in the sequence where the tracked codebase should be ready to absorb approval follow-up cleanly. -- Scheduling cannot be done correctly until approval follow-up facts are real tracked data. - -Key targets: -- extend `src/approval_ledger.py` and the persisted report shape with tracked approval follow-up facts -- add append-only approval follow-up history in `src/warehouse.py` without changing initial approval capture semantics -- add recurring review, stale-approval packaging, and shared follow-up freshness state across workbook, Markdown, HTML, review-pack, scheduled handoff, and approval-center surfaces -- keep all approval capture and follow-up review local-only and non-mutating - -Non-goals: -- no approval-aware weekly scheduling yet -- no auto-apply behavior -- no second weekly authority - -Exit criteria: -- approval follow-up facts exist in tracked architecture -- recurring review is visible across shipped approval surfaces -- compatibility coverage exists for older snapshots without the new fields - -Closeout: -- [Phase 101 closeout](/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-14-phase-101-closeout.md) - -## Phase 102: Approval-Aware Weekly Scheduling -Status: Complete - -Goal: -- Reopen the deferred Phase 95 work as a bounded weekly scheduling overlay that uses tracked approval follow-up facts inside the existing weekly-story contract. - -Why now: -- This phase should only happen after the approval follow-up foundation exists and the shared weekly packaging seam is already extracted. -- That keeps scheduling inside the shipped weekly authority instead of creating a second engine. - -Key targets: -- add one bounded weekly scheduling overlay that considers: - - approval backlog - - approval follow-up timing - - existing portfolio pressure -- render that overlay through the shared weekly-story contract -- keep `operator_queue`, `primary_target`, and `what_to_do_next` unchanged -- keep all scheduling guidance read-only in posture - -Non-goals: -- no queue rewrite -- no target-selection rewrite -- no widened command authority - -Exit criteria: -- one shared weekly overlay exists inside the tracked weekly contract -- all weekly-facing surfaces render the same scheduling story -- precedence tests prove approval-aware scheduling never outranks stronger blocked or urgent pressure - -Closeout: -- [Phase 102 closeout](/Users/d/Projects/GithubRepoAuditor/docs/plans/2026-04-14-phase-102-closeout.md) - -## Sequence Summary - -The recommended order is deliberate: - -1. `98` fixes planning truth. -2. `99` stabilizes the weekly packaging seam. -3. `100` reduces operator-core implementation risk. -4. `101` adds the missing tracked approval-follow-up facts. -5. `102` reopens approval-aware scheduling on top of the safer foundation. - -If this order is violated, the most likely failure mode is predictable: -- new approval logic gets layered into oversized modules -- deferred design ideas leak into tracked behavior without the right data model -- weekly surfaces drift again because the shared seam is not stable enough yet - -This roadmap arc is now complete. Start a new roadmap document before opening any later architecture or feature phase. diff --git a/docs/plans/2026-04-15-arc-a-closeout.md b/docs/plans/2026-04-15-arc-a-closeout.md deleted file mode 100644 index b26abcfc..00000000 --- a/docs/plans/2026-04-15-arc-a-closeout.md +++ /dev/null @@ -1,116 +0,0 @@ -# Arc A Closeout: Context Quality Recovery - -**Date**: 2026-04-15 -**Phases**: 113-118 -**Branch**: main (Phases 113-117 operational; Phase 114 via PR #113) - ---- - -## What Arc A Did - -Deployed the `--allow-dirty-worktree` flag (Phase 114) to unblock the context recovery pipeline, then ran batch apply across all 50 eligible repos in 4 cohorts (Phases 115-116). The managed context block (`` markers) was written into AGENTS.md or CLAUDE.md for each target, scraping README/package.json/pyproject signals to populate 6 structured sections. - ---- - -## Pre-Recovery Baseline (Phase 113) - -| Metric | Count | -|---|---| -| Total projects | 115 | -| context: boilerplate | 88 | -| context: minimum-viable | 13 | -| context: standard | 8 | -| context: full | 4 | -| context: none | 2 | -| **risk: elevated** | **54** | -| risk: moderate | 4 | -| risk: baseline | 40 | -| risk: deferred | 17 | - -Recovery plan cohort: 54 targets — 0 eligible (100% skipped as `dirty-worktree`), 2 excluded (temporary names). - ---- - -## Post-Recovery Results (Phase 117) - -| Metric | Before | After | Delta | -|---|---|---|---| -| context: boilerplate | 88 | 51 | -37 | -| context: minimum-viable | 13 | 34 | +21 | -| context: standard | 8 | 18 | +10 | -| context: full | 4 | 11 | +7 | -| context: none | 2 | 1 | -1 | -| **risk: elevated** | **54** | **16** | **-38** | -| risk: moderate | 4 | 4 | 0 | -| risk: baseline | 40 | 78 | +38 | -| risk: deferred | 17 | 17 | 0 | - -**Total repos updated**: 72 (across 4 cohort runs: 20 + 20 + 20 + 12) -**Failed**: 0 -**Skipped (ambiguous-primary-context)**: 4 -**Excluded (temporary/generated)**: 2 - ---- - -## Repos Still Elevated (16) — Manual Resolution Queue - -All 16 share `investigate-override` (catalog `intended_disposition: investigate`), which is an independent elevated factor. Most also have `no-run-instructions` from context scraping. - -| Repo | Factors | -|---|---| -| APIReverse | weak-context-active, investigate-override, no-run-instructions | -| ApplyKit | weak-context-active, investigate-override, no-run-instructions | -| AuraForge | weak-context-active, investigate-override, missing-operating-path, no-run-instructions | -| bridge-db | weak-context-active, investigate-override | -| da-scaffold | weak-context-active, investigate-override, missing-operating-path, no-run-instructions | -| DevToolsTranslator | weak-context-active, investigate-override, no-run-instructions | -| DNSWatcher | weak-context-active, investigate-override | -| GithubRepoAuditor | weak-context-active, investigate-override, no-run-instructions, undocumented-risks | -| IncidentMgmt | weak-context-active, investigate-override, missing-operating-path, no-run-instructions | -| IncidentReview | weak-context-active, investigate-override, no-run-instructions | -| JobCommandCenter | weak-context-active, investigate-override, no-run-instructions, undocumented-risks (ambiguous-context: has both CLAUDE.md and AGENTS.md) | -| notification-hub | weak-context-active, investigate-override, no-run-instructions | -| resume-evolver-tmp-1776063720 | weak-context-active, investigate-override, missing-operating-path, no-run-instructions (temporary name) | -| SpecCompanion | weak-context-active, investigate-override, no-run-instructions | -| thought-trails | weak-context-active, investigate-override, no-run-instructions (ambiguous-context: has both CLAUDE.md and AGENTS.md) | -| visual-album-studio | weak-context-active, investigate-override, no-run-instructions | - -**Pattern**: `investigate-override` won't be resolved by context recovery — it requires either updating the catalog disposition to `maintain`/`grow` or manually downgrading activity status. - ---- - -## Repos That Moved elevated → baseline (~38) - -The full set is visible in `output/portfolio-truth-latest.json`. The `weak-context-active` factor was removed for all repos whose context quality upgraded from `boilerplate`/`none` to `minimum-viable` or above, and who did not have other elevated factors. - ---- - -## Lessons Learned - -1. **Dirty worktree was the entire blocker.** Adding `--allow-dirty-worktree` unlocked 48 repos instantly. The flag is safe: `upsert_managed_context_block` only replaces the fenced managed block, doesn't touch other file content. - -2. **`investigate-override` is the next elevation floor.** 16 repos remain elevated solely because their catalog disposition is `investigate`. Context recovery cannot fix this — it requires a catalog review pass or an Arc D automated catalog update. - -3. **Ambiguous-context repos (4) need manual resolution.** JobCommandCenter, thought-trails, and 2 others have both CLAUDE.md and AGENTS.md with non-trivial content. Pick one file, consolidate, then re-run recovery. - -4. **Context scraping produces `minimum-viable` skeleton, not `standard`.** The `no-run-instructions` factor persists on many recovered repos because README-scraping didn't find a "How To Run" section. Operator can manually add run instructions to the managed block to resolve this. - -5. **Batch size of 20 was appropriate.** 4 runs to clear 72 repos. No failures across any cohort. - ---- - -## What Comes Next - -### Arc D (Phases 119+): Safe Automation Expansion -- **Prerequisite met**: elevated count dropped from 54 → 16 (below the ~20 threshold) -- Add bounded `--auto-apply` automation for repos with `path_confidence=high` and `decision_quality=trusted` -- Key files: `src/operator_decision_quality.py`, approval center in `src/cli.py`, `config/portfolio-catalog.yaml` - -### Arc E: Desktop Portfolio Shell -- Independent of Arc A — can start anytime -- Tauri 2 + React app consuming `portfolio-truth-latest.json` and `weekly-command-center-*.json` -- Repo: `JobCommandCenter` - -### Arc F: Renderer Simplification -- After Arcs A+D prove parity model stable across 2+ weekly review cycles -- Shared render contract for 5 parallel surfaces (Excel, Markdown, HTML, review-pack, handoff) diff --git a/docs/plans/2026-04-15-arc-d-closeout.md b/docs/plans/2026-04-15-arc-d-closeout.md deleted file mode 100644 index e9aedf09..00000000 --- a/docs/plans/2026-04-15-arc-d-closeout.md +++ /dev/null @@ -1,118 +0,0 @@ -# Arc D Closeout: Safe Automation Expansion - -**Date**: 2026-04-15 -**Phases**: 119-122 -**Branch**: main (Phases 119 operational; Phases 120-122 via PRs) - ---- - -## What Arc D Did - -Built the bounded automation infrastructure — transitioning the system from purely advisory to bounded-automation with an explicit trust bar. Operator remains in the loop via catalog opt-in and approval workflow; the system now has the plumbing to execute approved packets automatically when all three trust gates pass. - ---- - -## Phase Summary - -| Phase | Type | Deliverable | -|---|---|---| -| 119 | Operational | Manual context enrichment for 16 remaining elevated repos | -| 120 | Code | `automation_eligible` field in catalog + truth model | -| 121 | Code | `--auto-apply-approved` CLI flag + `src/auto_apply.py` trust bar module | -| 122 | Code | `AUTHORITY_CAP` → `bounded-automation`; `auto-apply-safe` posture added | - ---- - -## Trust Bar Definition - -For a repo to receive automated writes via `--auto-apply-approved`: - -1. **`automation_eligible: true`** — explicit catalog opt-in per repo (default `false`) -2. **`risk_tier: "baseline"`** — from the latest truth snapshot (per repo) -3. **`decision_quality_status: "trusted"`** — from operator summary (portfolio-level gate) - -All three must pass. Any failure excludes the repo from auto-apply. - -Safe mutation targets (allowlist): `github-topics`, `github-custom-properties`, `github-issue`, `notion-action`. -Excluded: `github-project-item`, `github-project-fields` (modify shared project boards). - ---- - -## Code Changes - -### New: `src/auto_apply.py` -- `build_trust_bar_index(truth_snapshot, decision_quality_status)` — builds `{repo_name: bool}` index -- `get_approved_manual_campaigns(ledger_bundle)` — finds `approved-manual` campaign records -- `filter_safe_actions(actions)` — allowlist filter on mutation_target -- `filter_trusted_repo_actions(actions, trust_bar_index)` — per-repo trust bar filter - -### Modified: `src/portfolio_catalog.py` -- Added `automation_eligible: bool` field (default `false`, parsed from YAML) - -### Modified: `src/portfolio_truth_types.py` -- Added `automation_eligible: bool = False` to `DeclaredFields` - -### Modified: `src/portfolio_truth_reconcile.py` -- Threads `automation_eligible` from catalog entry into `DeclaredFields` - -### Modified: `src/cli.py` -- Added `--auto-apply-approved` flag with mutual exclusion against `--writeback-apply`, `--approve-packet`, `--campaign` -- Added `_run_auto_apply_approved_mode()` handler - -### Modified: `src/action_sync_automation.py` -- Added `"auto-apply-safe": 4` to `AUTOMATION_PRIORITY` (sits between `apply-manual` and `follow-up-safe`) -- Shifted `follow-up-safe` → 5, `quiet-safe` → 6 - -### Modified: 3 `AUTHORITY_CAP` constants -- `src/operator_decision_quality.py`: `"advisory-only"` → `"bounded-automation"` -- `src/portfolio_risk.py`: `"advisory-only"` → `"bounded-automation"` -- `src/weekly_command_center.py`: `"report-only"` → `"bounded-automation"` - -Note: `AUTHORITY_CAP` is decorative metadata for operator review surfaces — not a runtime execution gate. The actual write gate remains `args.writeback_apply` in cli.py. - ---- - -## Test Coverage - -- `tests/test_auto_apply.py` — 17 tests covering trust bar, action filtering, campaign selection -- `tests/test_portfolio_catalog.py` — 2 new tests for `automation_eligible` parsing -- `tests/test_portfolio_truth.py` — 1 new assertion that `automation_eligible` appears in truth snapshot - -At the original Arc D closeout, the suite had grown from 809 tests to the low 800s. Treat that as historical context only; rerun `python3 -m pytest -q -p no:cacheprovider` for the current test count before reporting release status. - ---- - -## What Phase 123 Needs - -Phase 123 (first automated run) requires: -1. Phase 119 complete — elevated count ≤4 -2. Phases 120-122 on main -3. At least 2-3 repos with `automation_eligible: true` in `config/portfolio-catalog.yaml` -4. At least 1 approved-manual campaign packet targeting those repos - -Runbook: -1. Run `audit --portfolio-truth` and confirm the opted-in repos have `risk_tier: "baseline"`. -2. Run `audit --approval-center` and confirm the target campaign packet is `approved-manual`. -3. Dry-run the bounded apply path with `audit --auto-apply-approved --dry-run`. -4. Review the receipt and excluded-action list; only continue if all writes target the safe mutation allowlist. -5. Run the non-dry-run `--auto-apply-approved` path for the same approved packet. -6. Rerun `audit --control-center` and confirm the weekly command-center digest shows the action holding cleanly. - ---- - -## Forward Arcs - -### Arc E: Desktop Portfolio Shell -- Tauri 2 + React desktop app consuming `portfolio-truth-latest.json` and `weekly-command-center-*.json` -- Repo: `JobCommandCenter` -- Key surfaces: risk tier dashboard, approval queue, weekly command center, context quality heatmap - -### Arc F: Renderer Simplification -- Shared render contract for 5 parallel surfaces (Excel, Markdown, HTML, review-pack, handoff) -- Prerequisite: stable schema (no new fields for 2+ weekly review cycles) -- Highest regression risk of any arc - -### Arc G: Catalog Auto-Maintenance -- Expand automation to catalog-level mutations (auto-updating `intended_disposition`, `lifecycle`, `doctor_standard`) -- Gate: operator approval per batch, not per repo -- Prerequisite: Arc D proven safe at small scale across ≥3 automated runs diff --git a/docs/plans/2026-04-24-phase-123-readiness-prep.md b/docs/plans/2026-04-24-phase-123-readiness-prep.md deleted file mode 100644 index d31e1703..00000000 --- a/docs/plans/2026-04-24-phase-123-readiness-prep.md +++ /dev/null @@ -1,219 +0,0 @@ -# Phase 123 Readiness Prep - 2026-04-24 - -## Status - -Phase 123 is still not ready for live automated apply. - -Fresh checks from `main` at `e5fc2d7`: - -- `python3 -m src saagpatel --portfolio-truth --registry-output output/project-registry.md --portfolio-report-output output/PORTFOLIO-AUDIT-REPORT.md` - - Generated `output/portfolio-truth-latest.json` for 115 projects. - - Current automation opt-ins: `0`. -- `python3 -m src saagpatel --approval-center` - - No current approval needs review. - - Wrote `output/approval-center-saagpatel-2026-04-24.json` and `.md`. -- `python3 -m src saagpatel --auto-apply-approved --dry-run` - - No `approved-manual` campaign packets found. - -## Current Gates - -Live apply remains blocked until all three trust-bar inputs exist: - -1. At least 2-3 intentionally selected repos have `automation_eligible: true` in `config/portfolio-catalog.yaml`. -2. A bounded campaign packet exists and is approved through the local approval workflow. -3. `--auto-apply-approved --dry-run` shows eligible actions and expected receipts before any live apply. - -## 2026-04-24 Opt-In Pass - -The first manual opt-in pass selected the primary shortlist: - -- `mcpforge` -- `TradeOffAtlas` -- `TideEngine` - -`config/portfolio-catalog.yaml` now marks those three repos with `automation_eligible: true`. A regenerated portfolio-truth snapshot confirmed exactly 3 automation-eligible projects, all with baseline risk, high path confidence, full context, and no warnings. - -Follow-up gate results: - -- `python3 -m src saagpatel --campaign security-review --writeback-target github` was stopped after it stayed silent for several minutes during full-portfolio analysis while holding GitHub HTTPS connections. -- `python3 -m src saagpatel --repos mcpforge TradeOffAtlas TideEngine --campaign security-review --writeback-target github --max-actions 10` completed successfully as a targeted audit. -- The completed `security-review` preview still produced a portfolio-level packet for 10 repos: `AuraForge`, `SlackIncidentBot`, `prompt-englab`, `RedditSentimentAnalyzer`, `IncidentWorkbench`, `OPscinema`, `PersonalKBDrafter`, `StatusPage`, `WorkdayDebrief`, and `visual-album-studio`. -- Approval center still reported no current approval needs review. -- `--auto-apply-approved --dry-run` still reported no approved-manual campaign packets. - -Do not live-apply from this state. The campaign packet generation and approval flow now carry an `automation_subset` field, and `--auto-apply-approved --dry-run` prints the trust-bar counts before looking for approved packets. The next safe step is to review the visible `promotion-push` packet and decide whether the single eligible `TideEngine` action should be approved manually. - -Follow-up implementation check: - -- `python3 -m src saagpatel --repos mcpforge TradeOffAtlas TideEngine --campaign security-review --writeback-target github --max-actions 10` - - `security-review` packet now shows 3 automation-eligible repos and 0 eligible actions. - - `promotion-push` packet now shows 3 automation-eligible repos and 1 eligible action on `TideEngine`. -- `python3 -m src saagpatel --auto-apply-approved --dry-run` - - Trust-bar summary reports 3 opted-in repos, 3 baseline opted-in repos, and 0 full trust-bar repos because decision quality is still `insufficient-data`. - - No approved-manual campaign packets exist yet. - -## Decision-Quality Evidence Audit - -The decision-quality gate is no longer the active blocker. - -Fresh read-only probes from `main` at `3dcfeb1`: - -- `python3 -m src saagpatel --portfolio-truth --registry-output output/project-registry.md --portfolio-report-output output/PORTFOLIO-AUDIT-REPORT.md` - - Generated `output/portfolio-truth-latest.json` for 115 projects. - - Current automation opt-ins remain `mcpforge`, `TradeOffAtlas`, and `TideEngine`. -- `python3 -m src saagpatel --control-center` - - Refreshed the control-center and weekly-command-center artifacts from the latest report. - - `decision_quality_v1.decision_quality_status` stayed `insufficient-data`. - - `confidence_validation_status` stayed `insufficient-data`. - - Current judged confidence outcomes: 2 total, with 1 validated and 1 partially validated. - - Downgrade reasons: `insufficient-calibration-history`, `primary-target-monitor-only`, and `next-action-needs-verification`. -- `python3 -m src saagpatel --approval-center` - - No current approval needs review. -- `python3 -m src saagpatel --auto-apply-approved --dry-run` - - Trust-bar summary still reports 3 opted-in repos, 3 baseline opted-in repos, and 0 full trust-bar repos. - - No approved-manual campaign packets exist. - -Conclusion: this does not look like an automation-subset wiring bug. The system has too little judged recommendation history to honestly promote decision quality to `trusted`, and the current trust policies are still `monitor` / `verify-first`. Do not capture a campaign approval yet. - -Follow-up calibration pass: - -- Two non-live targeted audit/control-center cycles added enough judged outcomes to reach the calibration floor. -- A small action-selection fix now lets ready and chronic targets use concrete closure guidance before quiet-streak monitor guidance. -- `python3 -m src saagpatel --control-center` - - `decision_quality_v1.decision_quality_status` is now `trusted`. - - `confidence_validation_status` is `healthy`. - - Judged confidence outcomes: 4 total, with 1 validated and 3 partially validated. - - `primary_target_trust_policy` and `next_action_trust_policy` are both `act-with-review`. - - Downgrade reasons are empty. -- `python3 -m src saagpatel --auto-apply-approved --dry-run` - - Trust-bar summary reports 3 opted-in repos, 3 baseline opted-in repos, and 3 full trust-bar repos. - - No approved-manual campaign packets exist. - -Conclusion: Phase 123 is now blocked only on the local approval step. Do not live-apply yet. Review the `promotion-push` packet first because it is the only current packet with an automation-eligible action (`TideEngine`). - -Post-preview refinement: - -- `python3 -m src saagpatel --repos mcpforge TradeOffAtlas TideEngine --campaign promotion-push --writeback-target github --max-actions 10` - - Completed as a bounded non-live targeted preview. - - Refreshed the portfolio packet set; `promotion-push` remains apply-ready with 20 actions across 17 repos and 1 automation-eligible action on `TideEngine`. - - The full unbounded `promotion-push` preview was stopped after it stayed silent during full-portfolio analysis; use the bounded `--repos mcpforge TradeOffAtlas TideEngine` preview while Phase 123 is still in approval prep. -- `python3 -m src saagpatel --control-center` - - `decision_quality_v1.decision_quality_status` is `trusted`. - - `confidence_validation_status` is `healthy`. - - `primary_target_trust_policy` and `next_action_trust_policy` are both `act-with-review`. - - The next action now names the concrete manual review: review the reconcile queue before any manual writeback. -- `python3 -m src saagpatel --approval-center` - - No current approval needs review. -- `python3 -m src saagpatel --auto-apply-approved --dry-run` - - Trust-bar summary reports 3 opted-in repos, 3 baseline opted-in repos, and 3 full trust-bar repos. - - No approved-manual campaign packets exist. - -Current conclusion: the data sufficiency gate is clear, but there is still no local approval record. The next human action is to review the `promotion-push` reconcile packet and decide whether the single automation-eligible `TideEngine` action should receive manual approval. - -Approval routing and local approval pass: - -- Apply-ready packets with an `automation_subset.automation_eligible_action_count` greater than zero now route through `approval-first` instead of generic `apply-manual`, so the approval center can surface the local approval subject before any auto-apply dry run. -- `python3 -m src saagpatel --approval-center --approval-view ready` - - Surfaced `Promotion Push` as the strongest approval review candidate. -- `python3 -m src saagpatel --campaign promotion-push --approve-packet --approval-reviewer local-operator --approval-note "Phase 123 dry-run approval for the single automation-eligible TideEngine promotion-push action after bounded packet review; no live apply authorized here."` - - Captured local approval only. - - Wrote `output/approval-receipt-saagpatel-2026-04-25.json` and `.md`. - - Preserved the `automation_subset`: 3 opted-in repos, 1 automation-eligible action repo (`TideEngine`), and 19 non-eligible actions. -- `python3 -m src saagpatel --approval-center` - - Shows `campaign:promotion-push` as `approved-manual`. - - Manual apply remains explicit and separate. -- `python3 -m src saagpatel --auto-apply-approved --dry-run` - - Found the approved packet, but applied nothing. - - Trust-bar summary is now 3 opted-in repos, 3 baseline opted-in repos, and 0 full trust-bar repos because the latest repeated preview lowered decision quality to `use-with-review`. - - Skipped all 20 `promotion-push` actions, including `TideEngine`. - -Current conclusion: the approval gate is now satisfied locally, but live apply is still blocked by the decision-quality trust bar. Do not run live auto-apply. The next safe step is a non-mutating control-center/approval-center cycle after the current approved packet has had a chance to stabilize; only revisit auto-apply when `decision_quality_v1.decision_quality_status` returns to `trusted` and dry-run shows exactly the expected eligible `TideEngine` action. - -Trust-recovery progress visibility: - -- `python3 -m src saagpatel --control-center` - - Still reports `decision_quality_v1.decision_quality_status=use-with-review`. - - Still reports `primary_target_trust_policy=verify-first` and `next_action_trust_policy=verify-first`. - - Now makes the recovery gate explicit: trust recovery is blocked by recent trust-policy flip churn, with stable progress at 2/3 run(s) and 1 more stable confirming run needed. -- `python3 -m src saagpatel --approval-center` - - Confirms `Promotion Push` remains `approved-manual`. - -Current conclusion: do not rerun live apply. The next useful signal is one more non-mutating confirming cycle that keeps `Promotion Push` stable enough for `decision_quality_v1` to return to `trusted`; until then, the dry-run/live-apply gate should remain closed. - -Trust gate repair and bounded dry-run pass: - -- Approved manual campaign packets now suppress the obsolete `campaign-ready:` review queue item while their follow-up state is still fresh, so local approval no longer leaves stale review debt in the operator queue. -- The control center now recalculates its primary target after approval-ledger queue updates, so decision quality uses the final approval-adjusted queue rather than a pre-approval target. -- Healthy quiet runs with no active primary target can now be `trusted`; monitor-only guidance remains conservative when there is an active target or noisy calibration. -- Auto-apply safety filtering now recognizes the repo auditor's real GitHub writeback target shape (`writeback_targets.github.managed_topics` and `issue_title`) instead of requiring a synthetic `mutation_target` field. -- `python3 -m src saagpatel --control-center` - - `decision_quality_v1.decision_quality_status` is `trusted`. - - `human_skepticism_required` is `False`. - - Downgrade reasons are empty. - - `Promotion Push is ready for review` is no longer the primary target after the approved packet is accounted for. -- `python3 -m src saagpatel --auto-apply-approved --dry-run` - - Trust-bar summary reports 3 opted-in repos, 3 baseline opted-in repos, and 3 full trust-bar repos. - - Finds exactly 1 eligible `promotion-push` action after trust filtering. - - Applies 0 changes in dry-run mode. - - Still emits expected GitHub custom-property read warnings for this account/repo shape: `404` on repo custom property values and org custom property schema. - -Current conclusion: the trusted dry-run gate is now satisfied for the single expected `TideEngine` action. No live apply has been run. The next decision is whether to run the explicit live apply command for the approved packet, accepting the known custom-property read warnings as non-blocking for this dry-run path. - -## Candidate Shortlist - -These are candidates for manual opt-in review, not automatic opt-ins. They currently have baseline risk, high path confidence, active or recent activity, full context, and no portfolio-truth warnings: - -| Project | Stack | Activity | Why candidate-worthy | -| --- | --- | --- | --- | -| `mcpforge` | Python | active | Full context, active registry status, baseline risk, high path confidence. | -| `TradeOffAtlas` | React, TypeScript, Tauri 2 | active | Full context, active registry status, baseline risk, high path confidence. | -| `TideEngine` | Swift | active | Full context, active registry status, baseline risk, high path confidence. | - -Secondary candidates if one of the first three is rejected: - -- `RoomTone` -- `Recall` -- `SignalDecay` - -## Safe Prep Sequence - -1. Manually choose the first 2-3 repos to opt into automation. -2. Add `automation_eligible: true` only to those repo entries in `config/portfolio-catalog.yaml`. -3. Regenerate portfolio truth: - - ```bash - python3 -m src saagpatel --portfolio-truth --registry-output output/project-registry.md --portfolio-report-output output/PORTFOLIO-AUDIT-REPORT.md - ``` - -4. Confirm `decision_quality_v1.decision_quality_status` is `trusted` in the latest control-center output. If it is `use-with-review`, stop before live apply and let the trust posture recover through a confirming non-mutating cycle. - -5. Preview a bounded campaign packet, starting with the lowest-risk campaign that has useful eligible actions: - - ```bash - python3 -m src saagpatel --repos mcpforge TradeOffAtlas TideEngine --campaign promotion-push --writeback-target github --max-actions 10 - ``` - - Confirm the packet's `automation_subset` lists only the intentionally opted-in repos and separates eligible from non-eligible actions. - -6. Review the approval center: - - ```bash - python3 -m src saagpatel --approval-center - ``` - - Confirm the approval record preserves the same `automation_subset` before capturing approval. As of the latest pass, `promotion-push` is already `approved-manual` locally for the `TideEngine` subset. - -7. Only after the packet is intentionally approved, run: - - ```bash - python3 -m src saagpatel --auto-apply-approved --dry-run - ``` - -8. Live apply remains blocked until the dry run's trust-bar summary and eligible action output show exactly the expected repo/action set. The latest dry run now satisfies that gate for exactly 1 `promotion-push` action on `TideEngine`; no live apply has been run. - -## Not Done Here - -- A local `promotion-push` campaign approval was captured for the single automation-eligible `TideEngine` action. -- The trusted dry-run gate now finds exactly that 1 eligible action and applies 0 changes in dry-run mode. -- No writeback apply or auto-apply live command was run. -- Manual desktop Excel signoff for the 2026-04-24 workbook remains outside this prep note. diff --git a/docs/plans/2026-04-24-post-merge-current-state.md b/docs/plans/2026-04-24-post-merge-current-state.md deleted file mode 100644 index ad099c33..00000000 --- a/docs/plans/2026-04-24-post-merge-current-state.md +++ /dev/null @@ -1,353 +0,0 @@ -# Post-Merge Current State - 2026-04-24 - -## Status - -PR #120, PR #121, and PR #122 are merged into `main`. - -- PR #120 closed the workbook/export and operator-trend refactor batch, stabilized time-sensitive tests, and refreshed stale operator docs. -- PR #121 updated GitHub Actions to Node 24-compatible major versions. -- PR #122 recorded the post-merge rehearsal state and restored `python3 -m src.cli --help` behavior. -- Latest verified local branch before this note update: `main` aligned with `origin/main` at merge commit `743d833`. -- Latest verified GitHub main CI after PR #122: passed. - -No P1/P2/P3 review findings from the April repair list remain open. - -## 2026-05-16 Arc H Post-Merge Refresh - -PR #176 is merged into `main`, and local `main` is aligned with `origin/main`. - -Arc H added context-quality tooling: - -- description confidence analyzer -- README age-based staleness signal -- catalog completeness validator -- tier recalibration report -- portfolio context triage output -- composite `context_quality_score` - -Post-merge verification and refresh commands run: - -```bash -python3 -m src report saagpatel --portfolio-truth --registry-output output/project-registry.md --portfolio-report-output output/PORTFOLIO-AUDIT-REPORT.md -python3 -m src report saagpatel --context-triage -python3 -m src report saagpatel --tier-recalibration-report -python3 -m src report saagpatel --portfolio-context-recovery --context-recovery-limit 5 -python3 -m pytest tests/test_cli_subcommands.py tests/test_context_quality.py tests/test_portfolio_context_triage.py tests/test_catalog_validator.py -q -p no:cacheprovider -ruff check src/cli.py tests/test_cli_subcommands.py -``` - -Observed results: - -- Portfolio truth regenerated for 131 projects. -- New truth warning remains display-name ambiguity: `IncidentWorkbench`, `OrbitForge`, and `StatusPage` require path-qualified registry labels. -- Context quality distribution is still weak: 79 `boilerplate`, 20 `minimum-viable`, 18 `none`, 11 `full`, and 3 `standard`. -- Path confidence is still the dominant portfolio risk: 108 projects are under an `investigate` override. -- Context triage flagged 107 repos: 42 moderate and 65 low. No critical rows were produced by the current scoring rules. -- Triage failure modes were concentrated in weak context quality (97 rows) and catalog completeness gaps (52 rows). -- Tier recalibration report found bunching: 51 Bronze, 79 Silver, 0 Gold, and 0 Platinum; Silver holds 60.3% of repos. -- Context recovery planning froze a 78-project target cohort: 50 eligible, 28 skipped by safety rules, 0 excluded. -- No context recovery writes were applied. - -Incidental follow-up fixed during this refresh: - -- The Arc H report flags were present in subcommand help but missing from the legacy parser used for execution. `--context-triage` and `--tier-recalibration-report` are now registered in both paths, with regression coverage in `tests/test_cli_subcommands.py`. - -Context recovery batch 1 follow-up: - -- Applied the first bounded recovery batch to 5 eligible projects: `AIFortuneTeller`, `AIWorkFlow`, `APIReverse`, `ApplyKit`, and `ArguMap`. -- Added parser hardening so fenced command blocks with shell comments are preserved and counted correctly, `Development Conventions` is not treated as a runnable command section, and one-line pointer preambles such as `@AGENTS.md` are not copied as project summaries when README product context is available. -- Removed the managed block from the two originally dirty skipped repos touched during correction (`AssistSupport` and `AssistSupport-security-alerts`); their pre-existing unrelated dirty files remain untouched. -- Refreshed portfolio truth after the fix: context distribution is now 76 `boilerplate`, 20 `minimum-viable`, 18 `none`, 13 `full`, and 4 `standard`. -- Context triage now flags 105 repos, down from 107 before the batch. -- The refreshed recovery plan is `output/context-recovery-plan-2026-05-16T093430Z.md`: 74 targets remain, with 45 eligible, 29 skipped, and 0 excluded. - -Context recovery batch 2 follow-up: - -- Applied the next bounded recovery batch to 5 eligible projects: `BrowserHistoryVisualizer`, `ConvictionMapper`, `DecisionStressTest`, `Devil's Advocate`, and `DevToolsTranslator`. -- Re-applied the batch after the pointer-preamble hardening so `DecisionStressTest` uses README product context instead of a bare `@AGENTS.md` pointer as its recovered project summary. -- Refreshed portfolio truth after batch 2: context distribution is now 72 `boilerplate`, 24 `minimum-viable`, 17 `none`, 13 `full`, and 5 `standard`. -- Context triage now flags 100 repos. -- The refreshed recovery plan is `output/context-recovery-plan-2026-05-16T095726Z.md`: 69 targets remain, with 40 eligible, 29 skipped, and 0 excluded. - -Context recovery batch 3 follow-up: - -- Applied the next bounded recovery batch to 5 eligible projects: `DNSWatcher`, `EvolutionSandbox`, `GlassLayer`, `hermes-harness-foundation`, and `HowMoneyMoves`. -- Added recovery hardening so placeholder stack values such as `Unknown` are not copied as meaningful stack context. -- Refreshed portfolio truth after batch 3: context distribution is now 69 `boilerplate`, 27 `minimum-viable`, 15 `none`, 13 `full`, and 7 `standard`. -- Context triage now flags 96 repos. -- The refreshed recovery plan is `output/context-recovery-plan-2026-05-16T101550Z.md`: 64 targets remain, with 35 eligible, 29 skipped, and 0 excluded. - -Context recovery batch 4 follow-up: - -- Opened recovery-only follow-up PRs for batch 3 side branches: `DNSWatcher` PR #2 and `HowMoneyMoves` PR #13. -- Applied the next bounded recovery batch to 5 eligible projects: `IncidentReview`, `ink`, `Interruption Resume Studio`, `ITServiceHealth`, and `JobMarketHeatmap`. -- Refreshed portfolio truth after batch 4: context distribution is now 64 `boilerplate`, 29 `minimum-viable`, 15 `none`, 13 `full`, and 10 `standard`. -- Context triage now flags 92 repos. -- The refreshed recovery plan is `output/context-recovery-plan-2026-05-16T102351Z.md`: 59 targets remain, with 30 eligible, 29 skipped, and 0 excluded. - -Context recovery batch 5 follow-up: - -- Applied the next bounded recovery batch to 5 eligible projects: `LifeCadenceLedger`, `NetworkDecoder`, `NetworkMapper`, `PageDiffBookmark`, and `Phantom Frequencies`. -- Tightened fallback-generated summaries for `NetworkDecoder`, `PageDiffBookmark`, and `Phantom Frequencies` so the recovered context names the actual product purpose instead of only saying the project is active locally. -- Refreshed portfolio truth after batch 5: context distribution is now 59 `boilerplate`, 30 `minimum-viable`, 15 `none`, 14 `full`, and 13 `standard`. -- Context triage now flags 87 repos. -- The refreshed recovery plan is `output/context-recovery-plan-2026-05-16T111023Z.md`: 54 targets remain, with 25 eligible, 29 skipped, and 0 excluded. - -Context recovery batch 6 follow-up: - -- Opened and merged recovery-only follow-up PRs for batch 5 side branches: `LifeCadenceLedger` PR #5, `NetworkDecoder` PR #14, `NetworkMapper` PR #4, `PageDiffBookmark` PR #3, and `PhantomFrequencies` PR #4. -- Applied the next bounded recovery batch to 5 eligible projects: `Pulse Orbit`, `RedditSentimentAnalyzer`, `ResumeEvolver`, `ReturnRadar`, and `ScreenshottoDataSelect`. -- Tightened fallback-generated summaries for `RedditSentimentAnalyzer`, `ResumeEvolver`, and `ScreenshottoDataSelect` so the recovered context names the actual product purpose instead of only saying the project is active locally. -- Opened and merged recovery-only follow-up PRs for batch 6 side branches: `Pulse-Orbit` PR #11, `RedditSentimentAnalyzer` PR #13, `ResumeEvolver` PR #4, `ReturnRadar` PR #2, and `ScreenshottoDataSelect` PR #14. -- Refreshed portfolio truth after batch 6: context distribution is now 54 `boilerplate`, 31 `minimum-viable`, 15 `none`, 14 `full`, and 17 `standard`. -- Context triage now flags 82 repos. -- The refreshed recovery plan is `output/context-recovery-plan-2026-05-16T111848Z.md`: 49 targets remain, with 20 eligible, 29 skipped, and 0 excluded. -- `ResumeEvolver` still has a pre-existing local-only `main` commit (`fix: stabilize local verification tooling`); the batch 6 recovery PR was based on `origin/main` and only included `AGENTS.md`. - -Context recovery batch 7 follow-up: - -- Applied the next bounded recovery batch to 5 eligible projects: `SignalDecay`, `stockpulse`, `Terroir`, `thought-trails`, and `TradeOffAtlas`. -- Tightened fallback-generated summaries for `SignalDecay`, `stockpulse`, and `thought-trails` so the recovered context names the actual product purpose instead of only saying the project is active locally or a create-next-app scaffold. -- Opened and merged recovery-only follow-up PRs for remote-backed batch 7 side branches: `SignalDecay` PR #4, `Terroir` PR #11, `thought-trails` PR #3, and `TradeOffAtlas` PR #4. -- `stockpulse` has no configured GitHub remote, so its recovery block is committed locally on `codex/docs/context-recovery-batch-7` only. -- `Terroir` has local App Store prep history diverged from `origin/main`; the remote context PR is merged, and its docs-only recovery commit was cherry-picked onto local `main` to keep workspace scans aligned without rewriting local history. -- Refreshed portfolio truth after batch 7: context distribution is now 50 `boilerplate`, 32 `minimum-viable`, 14 `none`, 15 `full`, and 20 `standard`. -- Context triage now flags 78 repos. -- The refreshed recovery plan is `output/context-recovery-plan-2026-05-16T112551Z.md`: 44 targets remain, with 15 eligible, 29 skipped, and 0 excluded. - -Context recovery batch 8 follow-up: - -- Applied the next bounded recovery batch to 5 eligible projects: `app`, `Calibrate`, `Chromafield`, `Conductor`, and `DeepTank`. -- Corrected nested-path handling for `app`, `Conductor`, and `DeepTank`; their display names map to `Misc:NoGoPRJs/app`, `VanityPRJs/Conductor`, and `Fun:GamePrjs/DeepTank`. -- Tightened generated context for `app`, `Conductor`, and `DeepTank`; `app` now reflects the scaffold-stop status from `STATUS.md` instead of describing itself as active implementation work. -- Opened and merged recovery-only follow-up PRs for batch 8 side branches: `app` PR #4, `Calibrate` PR #4, `Chromafield` PR #5, `Conductor` PR #4, and `DeepTank` PR #13. -- Refreshed portfolio truth after batch 8: context distribution is now 48 `boilerplate`, 36 `minimum-viable`, 11 `none`, 15 `full`, and 21 `standard`. -- Context triage now flags 76 repos. -- The refreshed recovery plan is `output/context-recovery-plan-2026-05-16T193257Z.md`: 39 targets remain, with 10 eligible, 29 skipped, and 0 excluded. - -Context recovery batch 9 follow-up: - -- Applied the next bounded recovery batch to 5 eligible projects: `GhostRoutes`, `Liminal`, `PomGambler`, `Redact`, and `RoomTone`. -- Tightened generated context for `PomGambler` so the recovered summary uses the README's AuraFlow/Pomodoro prediction-market product framing instead of a generic local-project sentence. -- Opened and merged recovery-only follow-up PRs for batch 9 side branches: `GhostRoutes` PR #5, `Liminal` PR #5, `PomGambler-prod` PR #8, `Redact` PR #4, and `RoomTone` PR #4. -- `Liminal` has a pre-existing local `chore/add-system-card` branch; the remote context PR was based on `origin/main`, and its docs-only recovery commit was cherry-picked onto the local branch so workspace scans include both the system card and recovered context without dragging unrelated work into the PR. -- Refreshed portfolio truth after batch 9: context distribution is now 43 `boilerplate`, 37 `minimum-viable`, 11 `none`, 16 `full`, and 24 `standard`. -- Context triage now flags 71 repos. -- The refreshed recovery plan is `output/context-recovery-plan-2026-05-16T193933Z.md`: 34 targets remain, with 5 eligible, 29 skipped, and 0 excluded. - -Context recovery batch 10 follow-up: - -- Applied the final eligible bounded recovery batch to 5 projects: `Seismoscope`, `SnippetLibrary`, `TerraSynth`, `Wavelength`, and `knowledgecore`. -- Tightened generated context for `SnippetLibrary`, `TerraSynth`, and `knowledgecore` so the recovered summaries and risks reflect the README product/architecture instead of generic local-project language. -- Opened and merged recovery-only follow-up PRs for batch 10 side branches: `seismoscope` PR #5, `SnippetLibrary` PR #6, `TerraSynth` PR #3, `Wavelength` PR #5, and `knowledgecore` PR #105. -- `TerraSynth` uses `master` as its default branch; the recovery PR targeted `master`. -- Refreshed portfolio truth after batch 10: context distribution is now 40 `boilerplate`, 42 `minimum-viable`, 9 `none`, 16 `full`, and 24 `standard`. -- Context triage now flags 68 repos. -- The refreshed recovery plan is `output/context-recovery-plan-2026-05-16T194600Z.md`: 29 targets remain, with 0 eligible, 29 skipped, and 0 excluded. - -Manual ambiguous-context follow-up: - -- Repaired the 3 clean ambiguous-primary-context repos skipped after batch 10: `cross-system-smoke`, `MCPAudit`, and `Notion`. -- First added repo-specific portfolio context to `AGENTS.md` through merged follow-up PRs: `cross-system-smoke` PR #2, `MCPAudit` PR #91, and `notion-operating-system` PR #75. -- The refreshed recovery planner then correctly resolved ambiguity but selected `CLAUDE.md` as the primary context file for those same repos, so a second primary-context pass added matching repo-specific context through merged PRs: `cross-system-smoke` PR #3, `MCPAudit` PR #92, and `notion-operating-system` PR #76. -- Refreshed portfolio truth after the manual follow-up: context distribution is now 36 `boilerplate`, 44 `minimum-viable`, 26 `standard`, 16 `full`, and 9 `none`. -- Context triage now flags 66 repos. -- The refreshed recovery plan is `output/context-recovery-plan-2026-05-17T034323Z.md`: 26 targets remain, with 0 eligible, 26 skipped, and 0 excluded. All 26 remaining skipped targets are dirty worktrees. - -Dirty-worktree context follow-up: - -- Repaired all 26 dirty-worktree recovery targets while preserving their existing unrelated local changes. -- Published documentation-only recovery PRs for the remote-backed repos across the dirty-worktree batches. `AssistSupport-security-alerts` was kept as local-only context because it is a second checkout of the shared `AssistSupport` remote and its branch-specific note should not be merged into the default branch. -- `JobCommandCenter` required a final manual primary-context note because its rich existing `CLAUDE.md` content did not use the exact headings the auditor counts. -- Refreshed portfolio truth after the dirty-worktree follow-up: context distribution is now 16 `boilerplate`, 66 `minimum-viable`, 27 `standard`, 19 `full`, and 3 `none`. -- Context triage now flags 53 repos. -- The refreshed recovery plan is `output/context-recovery-plan-2026-05-17T043623Z.md`: 0 targets remain, with 0 eligible, 0 skipped, and 0 excluded. - -Current gate: - -- Arc H tooling is merged and locally usable. -- Automated, clean manual, and dirty-worktree context recovery have exhausted the live target cohort. `output/context-recovery-plan-2026-05-17T043623Z.md` reports no remaining recovery targets. -- Tier recalibration should stay report-only until the operator reviews whether the Bronze/Silver bunching reflects real maturity or threshold drift. - -Tier recalibration follow-up: - -- The report-only review found a strict-signal drift: portfolio truth was checking root tests, CI, README length, and release counts, but it was not carrying a root `LICENSE`/`COPYING` signal into maturity tiers. -- The truth layer now records `derived.has_license`, and maturity tiers use it for the Gold license requirement while preserving the legacy `derived.context_files` fallback. -- With release-count overlay and the license signal, the refreshed tier report no longer bunches: 51 Bronze, 76 Silver, 1 Gold, 2 Platinum, and 1 untracked/no-git project outside the named tier counts. -- The path-qualified catalog follow-up added explicit contracts for each duplicate-name path: `IncidentWorkbench`, `ITPRJsViaClaude/IncidentWorkbench`, `StatusPage`, `MoneyPRJsViaGPT/StatusPage`, `Fun:GamePrjs/OrbitForge`, and `FunGamePrjs/OrbitForge`. -- Portfolio truth still records the duplicate display names for visibility, but `unresolved_duplicate_display_names` is now empty and the top-level duplicate-name warning is clear. -- Context triage now flags 52 repos. `OrbitForge` and `StatusPage` are still visible only for weak context on the archived/dormant duplicate paths, not for missing catalog contracts. - -Phase 123 preview-only readiness refresh: - -- Ran the preview-only `security-review` campaign against 117 GitHub repos with `--writeback-target all`; no live writeback or apply flag was used. -- The generated preview reported 20 actions across 19 repos, led by `GithubRepoAuditor`, `LifeCadenceLedger`, and `EvolutionSandbox`. -- The preview guidance says to keep Security Review manual-only for now because human review is stronger than automation convenience. -- Approval center now surfaces one `ready-for-review` packet for `Security Review` with rollback coverage as the blocker to review before any approval. -- The automation subset remains empty for this packet: `TideEngine` and `TradeOffAtlas` are automation-eligible overall, but neither has actions in the current Security Review packet. -- Auto-apply dry run still blocks live automation: 2 opted-in repos, 0 repos pass the full trust bar, and no `approved-manual` campaign packets exist. -- Current gate: review the Security Review packet manually if desired, but do not capture approval or live apply until rollback coverage and the expected manual scope are reviewed. -- Follow-up fix: approval-center records now keep rollback-blocked or zero-automation-action packets reviewable without marking them approval-ready or apply-ready-after-approval, and the approval command is withheld for those packets. -- Follow-up fix: Action Sync readiness now treats active packets with missing or partial rollback coverage as blocked instead of `apply-ready`, keeping readiness, packet, and approval-center surfaces aligned. -- Security review follow-up: the `GithubRepoAuditor` exposed-secrets flag was validated as scanner noise from runtime shell variable references and ignored/generated paths. The scanner now skips generated output/agent/cache directories and ignores shell variable secret references, tracked-file gitleaks is clean, and a targeted security-review preview reports `GithubRepoAuditor` with `secrets_found=0` and no security recommendations. -- Dependabot security batch 1: opened and merged config-only Dependabot PRs for the first five queue repos: `DNSWatcher` PR #3, `DecisionStressTest` PR #3, `EvolutionSandbox` PR #3, `ITServiceHealth` PR #30, and `LifeCadenceLedger` PR #6. `ITServiceHealth` CI did not start because GitHub reported an account billing/spending-limit issue, not a repo test failure; keep that billing caveat visible for future CI checks. -- Dependabot security batch 2: opened and merged config-only Dependabot PRs for the next actionable dependency surfaces: `ResumeEvolver` PR #5, `TabTriage` PR #1, `bridge-db` PR #24, `notification-hub` PR #42, and `renovate-config` PR #1. All PRs passed GitHub's Dependabot config validation before merge; `notification-hub` also passed its repo check. `renovate-config` uses default branch `feat/init`, so its config landed there rather than `main`. -- Post-batch evidence refresh: reran a targeted preview-only `security-review` campaign for the ten repos touched across Dependabot batches 1 and 2; no live writeback or apply flag was used. The refreshed preview no longer reports missing Dependabot config for those ten repos. Remaining Security Review items are now led by `notification-hub` code security controls, `SECURITY.md` gaps, and unsupported/no-manifest Dependabot recommendations for Godot or otherwise dependency-surface-empty repos (`PhantomFrequencies`, `Recall`, `SignalDecay`, and `SynthWave`) that should be handled by auditor refinement or manual review rather than empty config PRs. -- Dependabot recommendation refinement: the security analyzer now records whether a repo has a supported Dependabot ecosystem before applying a missing-config penalty or emitting an `Add Dependabot config` recommendation. A targeted preview-only refresh across the previously noisy no-manifest repos reduced the Security Review preview to 12 repos and 14 actions, with the unsupported/no-manifest Dependabot recommendations cleared. -- `notification-hub` security controls: opened and merged PR #50 to add CodeQL and `SECURITY.md`, enabled repository secret scanning through GitHub's repository security setting, then opened and merged PR #51 to clear the high CodeQL path-handling alerts. Main-branch CodeQL and CI passed after both PRs. A targeted preview-only refresh now reports 12 repos and 13 Security Review actions; `notification-hub` still has medium exception-exposure CodeQL alerts and a low Scorecard workflow suggestion, so the next code-security follow-up should address sanitized error responses rather than setup. -- `notification-hub` CodeQL closeout: opened and merged PR #52 to sanitize `/review` endpoint error responses, then opened and merged PR #53 to move exception-derived report fields to generic operator-facing messages where they are created. PR and main-branch CI/CodeQL passed after both PRs, and GitHub code scanning now reports 0 open alerts for `notification-hub`. A targeted audit refresh with GHAS alerts merged the updated `notification-hub` evidence back into `output/audit-report-saagpatel-2026-05-17.json`; its security posture is now `healthy` with Code scanning enabled (0 alerts), Secret scanning enabled (0 alerts), SECURITY.md present, and Dependabot present. The remaining `notification-hub` Security Review item is now only the low-priority OpenSSF Scorecard workflow suggestion. -- Current Security Review queue after the `notification-hub` refresh: medium `SECURITY.md` gaps remain for `DNSWatcher`, `DecisionStressTest`, `EvolutionSandbox`, `ITServiceHealth`, `Recall`, `ResumeEvolver`, `TabTriage`, `bridge-db`, `cross-system-smoke`, `hermes-harness-foundation`, and `renovate-config`; `notification-hub` has only the low Scorecard action. A full campaign preview rerun was intentionally stopped after it expanded into a long 117-repo audit path; use the targeted audit evidence plus the live GitHub code-scanning API result as the current closeout evidence for this code-security slice. -- SECURITY.md policy batch 3: opened and merged documentation-only security policy PRs for `DNSWatcher` PR #4, `DecisionStressTest` PR #9, and `EvolutionSandbox` PR #9 after verifying their default branches lacked `SECURITY.md` or `.github/SECURITY.md`. No repo CI checks were configured on those PRs; all three PRs were clean, merged, and verified by reading `SECURITY.md` from the default branch. -- Post-policy-batch refresh: reran a targeted audit with GHAS alerts for `DNSWatcher`, `DecisionStressTest`, and `EvolutionSandbox`; `DNSWatcher` and `EvolutionSandbox` no longer appear in the Security Review queue. `DecisionStressTest` no longer has the `SECURITY.md` gap, but the fresh evidence now surfaces `Enable CodeQL default setup` as a high-priority item plus a low Scorecard suggestion. Remaining medium `SECURITY.md` gaps are now `ITServiceHealth`, `Recall`, `ResumeEvolver`, `TabTriage`, `bridge-db`, `cross-system-smoke`, `hermes-harness-foundation`, and `renovate-config`. -- `DecisionStressTest` CodeQL closeout: opened and merged PR #10 to add CodeQL for JavaScript/TypeScript, then opened and merged PR #11 to clear seven CodeQL `js/trivial-conditional` quality alerts by removing redundant snapshot truthiness checks after the null guard. Local `DecisionStressTest` checks passed (`npm run typecheck`, `npm run lint`, and `npm test`), PR and main-branch CodeQL passed, and GitHub code scanning now reports 0 open alerts. A targeted audit refresh with GHAS alerts now leaves `DecisionStressTest` with only the low-priority OpenSSF Scorecard suggestion; remaining medium `SECURITY.md` gaps are `ITServiceHealth`, `Recall`, `ResumeEvolver`, `TabTriage`, `bridge-db`, `cross-system-smoke`, `hermes-harness-foundation`, and `renovate-config`. -- SECURITY.md policy batch 4: opened and merged documentation-only security policy PRs for `ITServiceHealth` PR #32, `Recall` PR #4, and `ResumeEvolver` PR #6 after verifying their default branches lacked `SECURITY.md` or `.github/SECURITY.md`. `Recall` and `ResumeEvolver` had no PR checks. `ITServiceHealth` CI ran but failed on existing main-branch backend Ruff SIM117 issues and frontend npm peer dependency resolution drift; the PR diff was only `SECURITY.md`, so it was merged with that baseline-drift caveat. GitHub API verification confirms `SECURITY.md` now exists on all three default branches. -- Parallel refresh follow-up: fixed the parallel analysis path to disable the SQLite-backed analyzer result cache whenever more than one analysis worker is requested. This prevents cross-thread SQLite connection warnings during broad refreshes while preserving analyzer-cache behavior for the default single-worker path. -- Full Security Review evidence refresh: reran the 118-repo read-only audit with GHAS alerts and 8 analysis workers after the cache fix. The generated report now has `portfolio_baseline_size=118`, `total_repos=118`, and 20 Security Review preview actions: 18 high-priority CodeQL setup items plus 2 high-priority open code-scanning alert review items (`AIGCCore` and `AssistSupport`). No `add-security-md` actions remain in the generated queue. -- `AssistSupport` security alert closeout: opened and merged `AssistSupport` PR #116 to clear the concrete high-risk CodeQL/OSV slice. The PR tightened YouTube URL host validation, removed hard-coded crypto test values and fixed seed buffers, and updated the OpenSSL lockfile entries. Local focused frontend, Rust, audit, and security-regression checks passed; GitHub PR checks also passed, including CodeQL, OSV, dependency audit, quality gates, Rust backend, UI/search lanes, and the macOS build. A full read-only audit refresh was required because the portfolio baseline expanded to 119 repos after `ApplyKit-private-archive-20260517` appeared. The refreshed report now has `portfolio_baseline_size=119`, `total_repos=119`, and 20 Security Review preview actions: 12 open code-scanning review items plus 8 CodeQL setup items. `AssistSupport` now reports 0 critical/high code-scanning alerts and remains in the queue only for warning-level CodeQL cleanup; the strongest next Security Review move is `AIGCCore`, which still has 41 high code-scanning alerts. No `add-security-md` actions remain. -- `AIGCCore` workflow-permissions hardening: opened and merged `AIGCCore` PR #26 to replace `permissions: read-all` with `contents: read` defaults across six workflows while preserving explicit job permissions for SARIF upload and PR-only checks. PR checks passed after rewriting the commit message to satisfy commitlint, and main-branch CodeQL, `codex-quality-security`, and `quality-gates` passed on merge commit `19767ab`. A targeted read-only audit refresh with GHAS alerts merged the updated repo evidence back into `output/audit-report-saagpatel-2026-05-17.json`; it reports AIGCCore as improving after intervention, but GitHub code scanning still shows the six high `TokenPermissionsID` Scorecard alerts until the scheduled Scorecard job re-runs because the Scorecard upload job is schedule-only. Remaining AIGCCore high alerts are policy-level Scorecard findings (`BranchProtectionID`, `CodeReviewID`, and `MaintainedID`) plus the pending Scorecard re-baseline for token permissions. -- `AIGCCore` Scorecard refresh closeout: opened and merged PR #27 to add manual dispatch for the existing Scorecard SARIF job while keeping the job limited to scheduled or manually dispatched runs. PR checks passed, including CodeQL, SAST, secrets, verify, quality gates, perf-build, UI gates, and Lighthouse. After merge, manually dispatched `codex-quality-security` on `main`; the run passed and uploaded Scorecard SARIF. GitHub code scanning now shows AIGCCore down from 41 high alerts to 3 high policy-level Scorecard findings (`BranchProtectionID`, `CodeReviewID`, and `MaintainedID`). GithubRepoAuditor's GHAS alert fetcher now prefers GitHub's `security_severity_level` over the generic code-scanning rule severity so Scorecard medium/low findings are not inflated to high. A targeted read-only audit refresh with GHAS alerts now reports portfolio Code Scanning pressure at 0 critical and 3 high across 10 repos, and Dependabot pressure at 0 critical and 397 high across 68 repos. -- `AIGCCore` governance/policy closeout: opened and merged PR #29 to correct `SECURITY.md` for the real `main` branch, private vulnerability reporting, and `@saagpatel` ownership, then enabled conservative protection on `main` with pull-request review required and force-push/delete disabled. Opened and merged PR #30 to add the direct private advisory report URL after Scorecard still flagged missing linked reporting content. PR checks passed for both policy PRs, including CodeQL, SAST, secrets, verify, quality gates, UI gates, and performance gates. Manual `codex-quality-security` Scorecard refreshes passed; GitHub code scanning now has `BranchProtectionID` and `SecurityPolicyID` cleared for `AIGCCore`, leaving only `CodeReviewID` and `MaintainedID` as high contextual history/age signals. GithubRepoAuditor now preserves raw GHAS high counts while exposing actionable versus contextual high code-scanning counts, so `AIGCCore` drops out of the high-priority Security Review top actions when fresh GitHub evidence is fetched with `--no-cache`. -- `BrowserHistoryVisualizer` CodeQL closeout: opened and merged PR #17 to replace a dictionary-membership test assertion that CodeQL flagged as high `py/incomplete-url-substring-sanitization` with an exact cache lookup. Local backend verification passed (`test_categorizer.py`: 9 passed; full `backend/tests`: 52 passed, with existing pandas warnings), PR CodeQL passed, default-branch CodeQL passed on merge commit `03fe5b4`, and GitHub code scanning now reports 0 open alerts for `BrowserHistoryVisualizer`. A targeted read-only audit refresh with GHAS alerts merged the updated evidence back into `output/audit-report-saagpatel-2026-05-17.json`; portfolio code-scanning pressure is now 41 high alerts across 11 repos. -- `AssistSupport` stale OSV closeout and queue-priority refinement: live GitHub code scanning briefly still showed a high `openssl@0.10.78` OSV alert, but `master` already had `openssl 0.10.80` in `src-tauri/Cargo.lock`. Manually dispatched the repo's `OSV Scanner` workflow on `master`; run `26011109157` passed and marked alert #94 fixed. A targeted read-only audit refresh with GHAS alerts merged updated AssistSupport evidence into `output/audit-report-saagpatel-2026-05-18.json`; AssistSupport now has 0 critical/high code-scanning alerts and drops out of the Security Review preview. GithubRepoAuditor now records code-scanning severity buckets from the GitHub API and downgrades warning-only code-scanning cleanup below high priority, keeping the queue focused on critical/high findings. -- `SpecCompanion` critical Dependabot closeout: opened and merged PR #30 to skip lockfile-rationale enforcement for Dependabot-authored lockfile PRs while preserving the gate for human PRs. After PR #30 passed and merged, refreshed the blocked npm security PRs, merged PR #3 for the critical `basic-ftp` group, merged the refreshed PR #31 for the follow-up npm security group, and closed conflicted PR #12 as superseded. A targeted read-only audit refresh with GHAS alerts merged updated SpecCompanion evidence into `output/audit-report-saagpatel-2026-05-18.json`; portfolio Dependabot pressure is now 0 critical and 408 high alerts, and SpecCompanion no longer appears in the Security Review preview. Remaining SpecCompanion alerts are high/medium/low dependency debt led by Rust transitives and `lodash` families, not a critical item. -- `SpecCompanion` Rust security follow-up: opened by Dependabot after the critical batch, PR #32 was a lockfile-only Cargo group update for `tauri`, `openssl`, `quinn-proto`, and `rustls-webpki`. PR #32 was mergeable with passing checks and was squash-merged into `main`; a targeted read-only audit refresh with GHAS alerts now reports portfolio Dependabot pressure at 0 critical and 401 high alerts, while SpecCompanion is down to 1 high, 4 medium, and 3 low Dependabot alerts. The remaining SpecCompanion high alert is no longer the strongest queue driver compared with AIGCCore's high code-scanning backlog. -- `ContentEngine` workflow-permissions closeout: opened and merged `ContentEngine` PR #24 to add explicit read-only workflow token permissions to `desktop-ci` and `quality-gates`, clearing the two medium CodeQL `actions/missing-workflow-permissions` alerts. PR checks passed before merge, and main-branch `Push on main`, `quality-gates`, and `desktop-ci` checks passed after merge. GitHub code scanning now reports 0 open alerts for `ContentEngine`; a targeted read-only audit refresh with GHAS alerts shows `ContentEngine` as healthy with Code scanning, Secret scanning, `SECURITY.md`, and Dependabot present, leaving only the low-priority OpenSSF Scorecard suggestion. -- Workflow-permissions batch follow-up: opened workflow-token hardening PRs for `Cartograph` PR #8, `Chromafield` PR #6, `Calibrate` PR #5, and `Conductor` PR #6 after live code-scanning showed medium `actions/missing-workflow-permissions` alerts in each repo's `ci.yml`. `Cartograph` PR #8 passed PR checks, merged, passed main-branch CI, and live code scanning now reports 0 open alerts; a targeted read-only audit refresh with GHAS alerts now reports portfolio Code Scanning pressure at 0 critical and 2 high across 8 repos. `Calibrate` PR #5 also fixed an existing CI signing-profile blocker by adding `CODE_SIGNING_ALLOWED=NO`; its PR checks passed, it was merged, main-branch CI passed, and live code scanning now reports 0 open alerts. `Conductor` PR #6 passed PR checks, merged, passed main-branch CI, and live code scanning now reports 0 open alerts. A targeted read-only audit refresh with GHAS alerts for `Calibrate` and `Conductor` now reports portfolio Code Scanning pressure at 0 critical and 2 high across 6 repos. `Chromafield` PR #6 also merged after adding read-only workflow permissions, disabling CI signing, and repairing the Swift 6 export build issues in image/video Photos export paths. PR checks passed, main-branch CI and CodeQL passed on merge commit `b7e173e`, and live GitHub code scanning now reports 0 open alerts for `Chromafield`. A targeted read-only audit refresh with GHAS alerts reports portfolio Code Scanning pressure at 0 critical and 2 high across 5 repos, Dependabot pressure at 0 critical and 397 high across 69 repos, and keeps Security Review manual-only. -- CodeQL setup batch 1: reran a full read-only Security Review refresh with GHAS alerts after the Chromafield closeout. The fresh queue now leads with CodeQL setup gaps instead of stale warning-only code-scanning review items. Opened config-only CodeQL PRs for `EarthPulse`, `FreelanceInvoice`, and `LifeCadenceLedger`; the first attempt used an invalid branch family and was replaced with policy-compliant `codex/ci/...` branches. `FreelanceInvoice` PR #24 and `LifeCadenceLedger` PR #17 passed PR CodeQL, were squash-merged, and their main-branch CodeQL runs passed. `EarthPulse` PR #47 originally stayed open because CodeQL passed but existing `security-quality` checks failed on baseline dependency audits (`pnpm audit --audit-level=high` and Rust audit). The follow-up updated patched JavaScript transitive pins, refreshed `pnpm-lock.yaml`, updated `rustls-webpki` in `src-tauri/Cargo.lock`, added the required lockfile rationale, and merged PR #47 after all PR checks passed. Main-branch CodeQL and Artifact Hygiene passed after the merge, Dependabot update jobs completed successfully, and live GitHub code scanning now reports 0 open alerts for `EarthPulse`. -- Post-EarthPulse evidence note: targeted Security Review refresh is blocked until the portfolio baseline is refreshed because the live repo set expanded from 119 to 121 repos. A parallel full refresh was stopped after missing-checkout warnings; the safer single-worker full refresh was also stopped before completion because it is a long 121-repo run. The next Security Review evidence move should be a clean full read-only refresh before choosing the next CodeQL setup batch. -- `Construction` workflow-permissions closeout: live GitHub code scanning showed the prior queue item had narrowed to one medium `actions/missing-workflow-permissions` alert in `perf-enforced.yml`. Opened `Construction` PR #34 after closing PR #33, whose branch name failed the repo guard. PR #34 added top-level read-only workflow permissions, passed PR CodeQL, quality, performance, branch-name, commitlint, and secret checks, then was squash-merged. Main-branch `Push on main` and Dependabot update jobs passed after the merge, and live GitHub code scanning now reports 0 open alerts for `Construction`. -- Full Security Review evidence refresh after `Construction`: reran the 121-repo read-only audit with GHAS alerts and 8 analysis workers. The parallel path completed cleanly with `portfolio_baseline_size=121` and `total_repos=121`. GHAS pressure is now 0 critical and 2 high code-scanning alerts across 4 repos, 0 open secret-scanning alerts, and 0 critical / 385 high Dependabot alerts across 68 repos. The Security Review preview remains manual-only and now shows 20 actions across 20 repos, led by CodeQL setup gaps for `LegalDocsReview`, `IncidentReview`, `IncidentManagement`, `IncidentWorkbench`, and `LoreKeeper`. - -## 2026-05-09 Refresh - -A bounded current-state refresh was run after returning to the project: - -```bash -python3 -m src saagpatel --doctor -python3 -m src saagpatel --html --review-pack --badges --excel-mode standard -python3 -m src saagpatel --control-center -python3 -m src saagpatel --portfolio-truth --registry-output output/project-registry.md --portfolio-report-output output/PORTFOLIO-AUDIT-REPORT.md -python3 -m src saagpatel --approval-center -python3 -m src saagpatel --auto-apply-approved --dry-run -python3 -m src saagpatel --campaign security-review --writeback-target all -python3 -m src saagpatel --campaign promotion-push --writeback-target all -``` - -Observed results: - -- Doctor completed with no blocking errors. -- Doctor warnings were optional setup gaps: no `audit-config.yaml` and no `config/notion-config.json`. -- Full audit completed against 115 GitHub repos after the analysis path was changed to default to one visible worker. -- Fresh May 9 artifacts were generated, including audit report, workbook, HTML dashboard, badges, review pack, control center, weekly command center, approval center, portfolio truth, and warehouse outputs. -- Audit score summary: average score `0.70`; tiers reported as `59 functional`, `45 shipped`, `8 wip`, and `3 skeleton`. -- Portfolio truth regenerated for 116 projects. -- Portfolio truth still has one known warning: duplicate `OrbitForge` display names require path-qualified registry labels. -- Control center now reads from the fresh May 9 full audit and remains urgent/sticky, led by `AuraForge` momentum drift. -- Approval center shows no current approval needs review; approval remains local-only. -- Auto-apply dry run reports 2 opted-in repos, 2 baseline opted-in repos, and 0 full trust-bar repos because decision quality is still `use-with-review`. -- Safe campaign previews completed with no live GitHub writes: `security-review` produced 20 preview actions across 18 repos, and `promotion-push` produced 20 preview actions across 15 repos. - -Safety adjustment: - -- `mcpforge` was removed from automation eligibility because the refreshed truth layer now classifies it as elevated risk: weak active context, investigate override, and missing run instructions. -- Do not re-add `mcpforge` to automation eligibility until its context quality and path confidence are repaired. - -Current gate: - -- Phase 123 remains preview-ready, but live apply is not ready because there are no approved-manual campaign packets and no repo currently passes the full auto-apply trust bar. -- The full-audit stall path was narrowed by making repo analysis default to one visible worker; use `--analysis-workers ` or `GITHUB_REPO_AUDITOR_ANALYSIS_WORKERS=` only when intentionally opting back into parallel analysis. - -Follow-up current-state refresh after the security noise cleanup: - -- Control center now reports everything currently surfaced as safe to defer, with `0` blocked, `0` urgent, `0` ready, and `5` deferred queue items. -- Approval center still has no current approval needs review and no approved-manual packets. -- The latest Action Sync story remains preview-only: campaign previews are available, but no packet approval has been captured. -- The current strongest safe automation step is a preview of `security-review`; do not capture approval or run apply until a ready approval packet is visible. -- The manual approval-packet operating path is recorded in `docs/plans/2026-05-09-manual-approval-packet-workflow.md`. - -## Rehearsal Results - -Live weekly rehearsal was run from current `main` with the repo-native CLI entrypoint: - -```bash -python3 -m src saagpatel --doctor -python3 -m src saagpatel --html --review-pack --badges --excel-mode standard -python3 -m src saagpatel --control-center -make workbook-gate -python3 -m src saagpatel --portfolio-truth --registry-output output/project-registry.md --portfolio-report-output output/PORTFOLIO-AUDIT-REPORT.md -python3 -m src saagpatel --approval-center -python3 -m src saagpatel --auto-apply-approved --dry-run -``` - -Observed results: - -- Doctor completed with no blocking errors. -- Doctor warnings were expected optional-environment gaps: no `audit-config.yaml`, no `NOTION_TOKEN`, and no `config/notion-config.json`. -- Full audit completed against 114 GitHub repos. -- Fresh artifacts were generated for `2026-04-24`, including audit report, workbook, HTML dashboard, badges, review pack, control center, weekly command center, approval center, and portfolio truth. -- Audit score summary: average score `0.708`; tiers reported as `60 functional`, `44 shipped`, `7 wip`, and `3 skeleton`. -- Control center state is urgent/sticky, led by `AIGCCore shifted on momentum`. -- Workbook gate automated checks passed; manual desktop Excel signoff remains pending for this rehearsal. -- Portfolio truth generated for 115 projects. -- Approval center reported no current approval needs review. -- Auto-apply dry run reported no `approved-manual` campaign packets. - -## Current Gates - -Phase 123 is not ready for live automated apply. - -Reasons: - -- Portfolio truth currently has `0` automation-eligible projects. -- Approval center has no current approval needing review. -- Auto-apply dry run found no approved-manual campaign packets. - -The safe next Phase 123 preparation is to choose 2-3 low-risk candidate repos, make their catalog/truth state explicitly automation-eligible, approve a bounded campaign packet, then rerun the dry-run gate before any live apply. The current candidate shortlist and prep commands are recorded in `docs/plans/2026-04-24-phase-123-readiness-prep.md`. - -## Maintenance Findings - -The post-refactor helper split is green but now has visible sprawl: - -- `src/operator_trend*.py`: 30 files, about 15,217 lines. -- `src/excel*.py`: 49 files, about 15,440 lines including `src/excel_export.py`. -- The longest operator-trend module names are near 100 characters and encode too many lifecycle states in filenames. - -Recommended maintainability pass: - -1. Group `operator_trend_closure_forecast_*` helpers behind 3-5 conceptual modules instead of many recursively named stages. -2. Keep compatibility imports stable while consolidating names. -3. Preserve the scoped mypy command in `.github/workflows/ci.yml` during the consolidation. -4. Verify with full pytest, Ruff, scoped mypy, `python3 -m src --help`, `python3 -m src.cli --help`, and `make workbook-gate`. - -2026-05-09 implementation note: - -- The first closure-forecast modernization pass added four conceptual facade modules and routed `operator_resolution_trend.py` through them. -- Compatibility imports remain stable; the original `operator_trend_closure_forecast_*` modules were not removed. -- Details are recorded in `docs/plans/2026-05-09-closure-forecast-modernization.md`. - -2026-05-10 implementation note: - -- The closure-forecast sequence is complete through reset-family consolidation and wrapper-retirement audit. -- The first workbook-surface modernization pass moved `CORE_VISIBLE_SHEETS` from `src/excel_export.py` into `src/excel_workbook_helpers.py` while preserving compatibility through `src/excel_export.py`. -- The second workbook-surface modernization pass moved default workbook structure wiring into `src/excel_export_registry_helpers.py`; `src/excel_export.py` still re-exports the structure constants for compatibility. -- The third workbook-surface modernization pass moved default workbook build-step executor wiring into `src/excel_export_registry_helpers.py`. -- The fourth workbook-surface modernization pass moved default workbook finalization wiring into `src/excel_export_registry_helpers.py`. -- The workbook/exporter lane should pause unless future discovery finds another clear adapter boundary; broad sheet-rendering rewrites remain out of scope. -- Details are recorded in `docs/plans/2026-05-10-excel-workbook-contract-modernization.md`. - -2026-05-11 implementation note: - -- The recurring-review queue now supports operator acknowledgment capture: `--acknowledge-target --acknowledge-kind --acknowledge-reviewer --acknowledge-note ` writes to `output/operator-acknowledgments-.json` and filters the change from both `material_changes` and `review_targets` on the next read. -- The filter is applied on both the fresh-bundle path (`build_review_bundle` in `src/recurring_review.py`) and the cached-report early-return path (`normalize_review_state` in `src/operator_control_center.py`), so `--control-center` reflects new acknowledgments without requiring a fresh full audit. -- Each ack stores a directional signature (security old/new label, lens-delta sign, tier old/new) so a regression in the opposite direction still surfaces. -- Sibling-key suppression: a single security posture movement emits both a `security-change` and a `lens-delta` for `security_posture` with distinct `change_key`s; acknowledging either now also captures a paired ack for the sibling, so one CLI invocation clears one logical event. -- Incidental fix: `src/recurring_review._change` for lens-delta had `details={"lens": ..., "delta": lens_delta, **item}` where the spread clobbered `delta` with the parent's overall-score delta; reordering restores per-lens values. Signature derivation also falls back through `details.lens_deltas[lens]` so reports generated before the fix can still be acknowledged. -- Live verification: the residual GithubRepoAuditor lens-change item from the post-PR-#155/#156 healthy state was successfully acknowledged and dropped from the ready queue. -- Shipped via PR #157 (initial flag), PR #158 (sibling-key suppression), and a defensive-defaults follow-up that addresses the two Codex review comments left on PR #157: `directional_signature` now returns a stable details fingerprint for unhandled change kinds (hotspot-change, campaign-drift, governance-drift, rollback-exposure) instead of `{}`, so acknowledging one no longer silently suppresses materially different later events; `_apply_acknowledgment_filter` now keeps `review_targets` for repos that still have unacknowledged material_changes, only dropping targets when every change for the repo has been acknowledged. - -## Follow-Ups - -1. Complete manual desktop Excel signoff for the generated workbook if this rehearsal becomes a release record. -2. Reduce GitHub security endpoint warning noise; expected 403/404 responses from code/secret-scanning alert endpoints should be summarized or quieted without hiding real API outages. -3. Use `python3 -m src` or the installed `audit` console script after `pip install -e ".[dev,config]"`; PR #122 restored `python3 -m src.cli --help` behavior. -4. Start Phase 123 only after explicit catalog eligibility and approval-center readiness exist. diff --git a/docs/plans/2026-05-09-closure-forecast-modernization.md b/docs/plans/2026-05-09-closure-forecast-modernization.md deleted file mode 100644 index f5201aca..00000000 --- a/docs/plans/2026-05-09-closure-forecast-modernization.md +++ /dev/null @@ -1,107 +0,0 @@ -# Closure Forecast Modernization - 2026-05-09 - -## Status - -The first five closure-forecast modernization passes are implemented. - -These passes are intentionally behavior-preserving. The first pass added conceptual facade modules for the sprawling `operator_trend_closure_forecast_*` helper family and routed `operator_resolution_trend.py` through those facades. The second pass moved the core implementation behind the core facade. The third pass moved the freshness implementation behind the freshness controls facade. The fourth pass moved the reacquisition implementation behind the reacquisition controls facade. The fifth pass moved the reset-family implementation behind the reset controls facade. Existing module paths remain importable. - -## What Changed - -New facade modules: - -- `src/operator_trend_closure_forecast_core.py` for events, history, and reweighting helpers. -- `src/operator_trend_closure_forecast_freshness_controls.py` for freshness and evidence helpers. -- `src/operator_trend_closure_forecast_reacquisition_controls.py` for reacquisition and refresh helpers. -- `src/operator_trend_closure_forecast_reset_controls.py` for reset, reentry, rebuild, restore, and rerestore helpers. - -`src/operator_resolution_trend.py` now imports closure-forecast helpers from those conceptual modules instead of importing directly from each long lifecycle-stage module. - -Second-pass implementation move: - -- `src/operator_trend_closure_forecast_core.py` now owns the events, history, and reweighting implementation. -- `src/operator_trend_closure_forecast_events.py`, `src/operator_trend_closure_forecast_history.py`, and `src/operator_trend_closure_forecast_reweighting.py` are compatibility wrappers. -- Existing tests still import the old modules, and facade tests verify old and new import surfaces resolve to the same functions. - -Third-pass implementation move: - -- `src/operator_trend_closure_forecast_freshness_controls.py` now owns freshness, evidence, decay, and freshness-hotspot helpers. -- `src/operator_trend_closure_forecast_freshness.py` is a compatibility wrapper. -- Existing freshness, reacquisition, reset-reentry freshness, and facade tests still cover old import paths. - -Fourth-pass implementation move: - -- `src/operator_trend_closure_forecast_reacquisition_controls.py` now owns reacquisition, refresh recovery, persistence, churn, reacquisition freshness, and persistence-reset helpers. -- `src/operator_trend_closure_forecast_reacquisition.py` and `src/operator_trend_closure_forecast_reacquisition_freshness.py` are compatibility wrappers. -- Existing reacquisition, reacquisition freshness, and facade tests still cover old import paths. - -Fifth-pass implementation move: - -- `src/operator_trend_closure_forecast_reset_controls.py` now owns reset refresh, reset reentry freshness, reset reentry rebuild, rebuild freshness, rebuild persistence, reentry restore, rerestore, and rererestore helpers. -- The old reset-family modules are compatibility wrappers. -- Existing reset-family and facade tests still cover old import paths. - -## Compatibility Rule - -Do not remove the original `operator_trend_closure_forecast_*` modules in this pass. They are still the compatibility import surface for tests and any downstream callers. - -Future consolidation may move implementation bodies behind the facade modules, but only after: - -1. old imports are covered by compatibility tests, -2. full pytest and Ruff pass, -3. scoped mypy stays green or has a documented pre-existing advisory failure, -4. CLI help smoke checks pass, -5. and workbook gate passes. - -## Verification - -Completed during this pass: - -```bash -python3 -m pytest tests/test_operator_trend_closure_forecast*.py -q -p no:cacheprovider -ruff check src/operator_resolution_trend.py src/operator_trend_closure_forecast_*controls.py src/operator_trend_closure_forecast_core.py tests/test_operator_trend_closure_forecast_facades.py -python3 -m src --help -python3 -m src.cli --help -python3 -m pytest -q -p no:cacheprovider -ruff check src/ tests/ -mypy src/operator_resolution_trend.py src/operator_trend_closure_forecast_core.py src/operator_trend_closure_forecast_freshness_controls.py src/operator_trend_closure_forecast_reacquisition_controls.py src/operator_trend_closure_forecast_reset_controls.py --ignore-missing-imports -make workbook-gate -``` - -Required tests and Ruff passed. Scoped mypy passed for the changed closure-forecast surface. Repo-wide `mypy src/ --ignore-missing-imports` remains advisory and still fails on the pre-existing type backlog outside this change area. Workbook gate automated checks passed and still require manual desktop Excel signoff for release. - -Completed during the second core consolidation pass: - -```bash -python3 -m pytest tests/test_operator_trend_closure_forecast_events.py tests/test_operator_trend_closure_forecast_history.py tests/test_operator_trend_closure_forecast_reweighting.py tests/test_operator_trend_closure_forecast_facades.py -q -p no:cacheprovider -ruff check src/operator_trend_closure_forecast_core.py src/operator_trend_closure_forecast_events.py src/operator_trend_closure_forecast_history.py src/operator_trend_closure_forecast_reweighting.py tests/test_operator_trend_closure_forecast_events.py tests/test_operator_trend_closure_forecast_history.py tests/test_operator_trend_closure_forecast_reweighting.py tests/test_operator_trend_closure_forecast_facades.py -mypy src/operator_trend_closure_forecast_core.py src/operator_trend_closure_forecast_events.py src/operator_trend_closure_forecast_history.py src/operator_trend_closure_forecast_reweighting.py --ignore-missing-imports -``` - -Completed during the third freshness consolidation pass: - -```bash -python3 -m pytest tests/test_operator_trend_closure_forecast_freshness.py tests/test_operator_trend_closure_forecast_reacquisition.py tests/test_operator_trend_closure_forecast_reacquisition_freshness.py tests/test_operator_trend_closure_forecast_reset_reentry_freshness.py tests/test_operator_trend_closure_forecast_facades.py -q -p no:cacheprovider -ruff check src/operator_trend_closure_forecast_freshness.py src/operator_trend_closure_forecast_freshness_controls.py tests/test_operator_trend_closure_forecast_freshness.py tests/test_operator_trend_closure_forecast_facades.py -mypy src/operator_trend_closure_forecast_freshness.py src/operator_trend_closure_forecast_freshness_controls.py --ignore-missing-imports -``` - -Completed during the fourth reacquisition consolidation pass: - -```bash -python3 -m pytest tests/test_operator_trend_closure_forecast_reacquisition.py tests/test_operator_trend_closure_forecast_reacquisition_freshness.py tests/test_operator_trend_closure_forecast_facades.py -q -p no:cacheprovider -ruff check src/operator_trend_closure_forecast_reacquisition.py src/operator_trend_closure_forecast_reacquisition_freshness.py src/operator_trend_closure_forecast_reacquisition_controls.py tests/test_operator_trend_closure_forecast_reacquisition.py tests/test_operator_trend_closure_forecast_reacquisition_freshness.py tests/test_operator_trend_closure_forecast_facades.py -mypy src/operator_trend_closure_forecast_reacquisition.py src/operator_trend_closure_forecast_reacquisition_freshness.py src/operator_trend_closure_forecast_reacquisition_controls.py --ignore-missing-imports -``` - -Completed during the fifth reset-family consolidation pass: - -```bash -python3 -m pytest tests/test_operator_trend_closure_forecast_reset_refresh.py tests/test_operator_trend_closure_forecast_reset_reentry_freshness.py tests/test_operator_trend_closure_forecast_reset_reentry_rebuild.py tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_freshness.py tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_persistence.py tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore.py tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_freshness.py tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence.py tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_recovery.py tests/test_operator_trend_closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_persistence.py tests/test_operator_trend_closure_forecast_facades.py -q -p no:cacheprovider -ruff check src/operator_trend_closure_forecast_reset*.py tests/test_operator_trend_closure_forecast_reset*.py tests/test_operator_trend_closure_forecast_facades.py -mypy src/operator_trend_closure_forecast_reset*.py --ignore-missing-imports -``` - -## Next Consolidation Step - -The closure-forecast implementation bodies now sit behind the four conceptual facade modules. The wrapper-retirement audit in `docs/plans/2026-05-10-closure-forecast-wrapper-retirement-audit.md` found that the wrappers should stay for now because old-path imports remain part of the tested compatibility contract. diff --git a/docs/plans/2026-05-09-manual-approval-packet-workflow.md b/docs/plans/2026-05-09-manual-approval-packet-workflow.md deleted file mode 100644 index 7a5e5a66..00000000 --- a/docs/plans/2026-05-09-manual-approval-packet-workflow.md +++ /dev/null @@ -1,113 +0,0 @@ -# Manual Approval Packet Workflow - 2026-05-09 - -## Status - -This note defines the current manual approval-packet workflow for the next Action Sync lane. It is an operating contract, not an approval receipt. - -Current May 9 state: - -- The latest control-center output says everything currently surfaced is safe to defer. -- The approval center has no `ready-for-review`, `approved-manual`, `needs-reapproval`, or `blocked` packet approvals. -- Action Sync is preview-ready, not apply-ready. -- The current strongest safe automation step is a preview of `security-review`. -- No live writeback is authorized by this note. - -## Operating Boundary - -Keep these four steps separate: - -1. Preview builds the packet and writes local artifacts. -2. Local approval records a reviewer attestation for one current packet fingerprint. -3. Auto-apply dry run proves the approved packet still passes the trust bar. -4. Live apply requires a fresh, explicit operator decision and `--writeback-apply`. - -Local approval does not apply anything. It also does not grant background permission for future packets, changed fingerprints, or different campaigns. - -## When To Use This Workflow - -Use this workflow only after a current audit/control-center cycle shows: - -- no blocked or urgent operator queue items for the affected action lane -- a campaign preview exists for the exact campaign under review -- the approval center surfaces a specific `ready-for-review` packet -- the packet fingerprint is stable after the latest preview -- rollback/reconcile posture is visible in the preview artifacts -- the target systems are intentionally selected - -Prefer `--writeback-target github` for the first live lane unless Notion configuration is intentionally fixed and reviewed. `--writeback-target all` is useful for previewing the full story, but it should not be treated as live-apply permission. - -## Current Recommended Path - -Do not capture a packet approval yet. The latest approval center says no current approval needs review. - -The next safe move is another preview-only packet generation, starting with the currently recommended lane: - -```bash -python3 -m src saagpatel --campaign security-review --writeback-target all -python3 -m src saagpatel --approval-center --approval-view ready -``` - -If the approval center still shows no ready packet, stay local and keep the operator loop quiet. If it surfaces a ready packet, review the generated campaign artifacts before approval. - -2026-05-10 diagnostic update: - -- A campaign can be `apply-manual` without having an approval-center packet. That means the preview path is healthy, but the next step is a separate explicit manual apply decision, not `--approve-packet`. -- Approval center now distinguishes this state in its full view as `No Approval Needed` while keeping `--approval-view ready` empty until a true approval-gated packet exists. -- `security-review` is currently in that manual-apply-only lane; do not treat the empty ready queue as a stall. - -## Approval Evidence Checklist - -Before running `--approve-packet`, confirm: - -- the campaign name matches the packet being reviewed -- the approval center shows the packet as `ready-for-review` -- the preview action count, repo count, and top repos match expectations -- there is no unexpected drift, access blocker, or rollback blocker -- the writeback target is deliberate and bounded -- known optional setup gaps are understood -- the approval note names the exact packet and says no live apply is authorized - -Example approval shape, with campaign and note adjusted to the reviewed packet: - -```bash -python3 -m src saagpatel \ - --campaign security-review \ - --approve-packet \ - --approval-reviewer local-operator \ - --approval-note "Approved the current security-review packet fingerprint after preview review; no live apply authorized here." -``` - -## Post-Approval Gate - -After local approval, rerun the read-only checks: - -```bash -python3 -m src saagpatel --approval-center -python3 -m src saagpatel --auto-apply-approved --dry-run -``` - -Only consider live apply when the dry run shows exactly the expected eligible actions and no new blockers. The live command still requires a separate explicit operator decision. - -## Stop Conditions - -Do not approve or apply when: - -- the approval center has no ready packet -- the preview contains more repos or actions than expected -- the packet includes a repo that lacks the intended automation eligibility -- decision quality is `use-with-review` or otherwise below the trust bar -- drift/reopen/rollback-watch signals are present for the packet -- Notion is included in the live target while Notion setup remains unconfigured -- the operator cannot explain the expected external mutations in one sentence - -## Where This Heads - -The near-term goal is not broad automation. It is one clean preview-to-approval-to-dry-run rehearsal that proves the operator loop can carry a packet safely without widening write authority. - -Once that is stable, the next expansion path is: - -1. choose one low-risk campaign packet -2. capture one local approval for the current fingerprint -3. prove the auto-apply dry run selects only the expected actions -4. run live apply only after a fresh explicit decision -5. monitor the post-apply state before adding more campaigns or targets diff --git a/docs/plans/2026-05-10-arc-f-expansion-roadmap.md b/docs/plans/2026-05-10-arc-f-expansion-roadmap.md deleted file mode 100644 index e65d906f..00000000 --- a/docs/plans/2026-05-10-arc-f-expansion-roadmap.md +++ /dev/null @@ -1,569 +0,0 @@ -# 2026-05-10 — Arc F Expansion Roadmap - -**Status:** Active — Sprint 1 in progress -**Owner:** Solo operator -**Arc:** F (follows Arc D bounded automation and Arc E desktop shell concept) -**Reference window:** 90-day sequencing (≈ 2026-05-10 → 2026-08-10), with longer-horizon backlog - ---- - -## TL;DR - -GithubRepoAuditor has matured into a workbook-first portfolio operator with bounded automation behind a trust bar. The next arc of work is **not new analyzers** — it is tightening the loop: faster runs, an interactive UI, a semantic index that makes future AI features cheap, and platform-native data that GitHub now exposes for free. This plan defines four strategic themes, a feature inventory (~30 items), four 90-day sprints with five items each, plus an explicit backlog and deferred list. - -The single guiding principle: **every change in Arc F either (a) increases operator velocity per audit run, or (b) lays a foundation that several downstream features will share.** - ---- - -## Why this work, now - -1. **The platform caught up.** GitHub's free API surface in 2025-26 now includes SBOM export, Dependabot/CodeQL/Secret-scanning alert reads, GitHub Models inference, repo rulesets, code search, releases, and stargazer timelines. Several of GithubRepoAuditor's existing analyzers can be replaced or augmented with first-party data that's richer and cheaper to obtain. -2. **Trust-bar infrastructure is ready.** Arc D (Phases 119-122) shipped the `automation_eligible` + `baseline risk` + `trusted decision quality` gate. The approval ledger and campaign packet workflow are the right substrate for richer agentic features — proposing actions through the same lane that already exists. -3. **The bottlenecks are known.** Workbook generation is slow on 100+ repos, GitHub fetch is sequential, there is no interactive UI for browsing historical runs, and the CLI has 70+ flags in one flat namespace. Each has a clean fix. -4. **The single-paragraph `--narrative` is the only AI surface.** With Haiku, voyage-code-3 embeddings, and GitHub Models all viable, a single shared semantic index can unlock several features without recurring API spend. - ---- - -## Strategic themes - -### Theme 1 — Close the platform-native gap - -Move data acquisition from local clones + custom scrapers to GitHub's first-party APIs where they're now strictly better. Free, faster, richer. - -### Theme 2 — AI as analyst, not just narrator - -Promote AI from a one-paragraph generator to a portfolio analyst: semantic queries, weekly briefings, agentic README drafts that flow through the existing approval lane, and operator-preference memory so suggestions don't become noise. - -### Theme 3 — New analytical dimensions - -Borrow proven patterns from adjacent tools (OSSF Scorecard, Cortex maturity tiers, DORA-Lite, OpenSauced contributor signals). Each new dimension must be (a) cheap to compute, (b) actionable, and (c) surfaced in both Excel and the control center. - -### Theme 4 — Architecture, performance, distribution - -Fix the known bottlenecks. Move from "run it overnight" to "rerun during triage." Make the tool installable on a fresh machine in one command. Reduce the CLI's flag-soup problem. - ---- - -## Full feature inventory - -Status legend: ✅ in Arc F sprints · 📋 Arc F backlog · ⏸ deferred (with reason) - -### Theme 1 — Platform-native - -| # | Feature | Status | Sprint | -|---|---|---|---| -| 1.1 | Dependabot + CodeQL + Secret-scanning alerts in `risk_overlay` | ✅ Shipped | `2703fb4` (S1.3) | -| 1.2 | SBOM-based dependency fetching (`--sbom-source github`) | ✅ Shipped | `aa5dbee` (S2.3) | -| 1.3 | GitHub Models as alternate `--narrative-provider` | ✅ Shipped | `ae0a7c6` (S1.2) | -| 1.4 | OSSF Scorecard integration | ✅ Shipped | `aa5dbee` (S2.3, `--ossf-scorecard`) | -| 1.5 | Repo rulesets + signing as governance score | 📋 | post-S4 | -| 1.6 | CI health analyzer (workflow run metrics) | 📋 | post-S4 | -| 1.7 | Cross-repo code search (`--cross-repo-search`) | 📋 | post-S4 | -| 1.8 | Webhook daemon (`--serve-webhook`) | ⏸ | Operational complexity outweighs solo-operator value | - -### Theme 2 — AI as analyst - -| # | Feature | Status | Sprint | -|---|---|---|---| -| 2.1 | Portfolio semantic index (`--semantic-search`, `--ask`) | ✅ Shipped | `44839de` (S3.1) | -| 2.2 | Weekly Operator Briefing (`--briefing`) | ✅ Shipped | `0438428` (S3.2) | -| 2.3 | Operator preference memory | ✅ Shipped | `e0fec52` (S3.3) | -| 2.4 | Cross-repo duplication detector | ✅ Shipped | `03dc2bc` (S3.4) | -| 2.5 | Agentic README/description authoring (`--draft-readmes`) | 📋 | post-S4 | -| 2.6 | Planner agent for campaign authoring (`--plan-campaign`) | 📋 | post-S4 | -| 2.7 | Eval-driven scoring tuning (`--tune-scoring-profile`) | 📋 | post-S4 | -| 2.8 | LLM code-quality analyzer per file | ⏸ | Cost vs. signal-add not justified vs. deterministic analyzers | -| 2.9 | Local-first Ollama classification | ⏸ | Defer until API spend is a real constraint | - -### Theme 3 — New analytical dimensions - -| # | Feature | Status | Sprint | -|---|---|---|---| -| 3.1 | README staleness index | ✅ Shipped | `ab70a04` + `f2594a0` (S1.4) | -| 3.2 | Release-shipped signal (has-release + age + count) | ✅ Shipped | `ab70a04` (S1.4) | -| 3.3 | Star momentum (30d delta) | 📋 | post-S4 | -| 3.4 | Tiered maturity + Initiative tracker | 📋 | post-S4 | -| 3.5 | DORA-Lite metrics (release cadence, lead-time, change-failure proxy) | 📋 | post-S4 | -| 3.6 | Year-in-review report (`--year-review`) | 📋 | post-S4 | -| 3.7 | Cross-repo context-switching heatmap | 📋 | post-S4 | -| 3.8 | Commit-message hygiene | 📋 | post-S4 | -| 3.9 | Bus-factor / collaborator awareness | 📋 | post-S4 | -| 3.10 | Milestone hygiene | 📋 | post-S4 | -| 3.11 | Auto-topic suggestions via LLM (dry-run) | 📋 | post-S4 | - -### Theme 4 — Architecture, performance, distribution - -| # | Feature | Status | Sprint | -|---|---|---|---| -| 4.1 | xlsxwriter migration (`constant_memory=True`) | ⏹ Stopped | `9a68e1c` Phase 1 catalog; pivoted to S2.0 (profile-first) | -| 4.2 | mutmut pre-release gate on `auto_apply` + `scorer` | ✅ Shipped | `a7b6918` + `fce45dd` (S1.5) | -| 4.3 | Async fetch layer (`--fetch-workers`, httpx) | ✅ Shipped | `bc2f95f` (S2.1) | -| 4.4 | Per-(repo, sha, analyzer) cache in warehouse DB | ✅ Shipped | `0375053` + `ef038f6` (S2.2 + NamedStyle fix) | -| 4.5 | `audit serve` — FastAPI + HTMX local web UI | ✅ Shipped @ `4da1496` + `220a6fa` | S4.1 | -| 4.6 | PyPI publish + `shiv` binary | ✅ Shipped @ `1316aaf` | S4.2 | -| 4.7 | CLI subcommand restructure (`audit run/triage/report`) | ✅ Shipped @ `9ee3932` | S4.3 | -| 4.8 | structlog + per-phase timings to `run-telemetry.jsonl` | 📋 | post-S4 | -| 4.9 | Plugin architecture via entry-points | 📋 | post-S4 | -| 4.10 | Arc E desktop shell (Tauri 2 + React) | 📋 | post-S4 (after `audit serve` validates which views matter) | -| 4.11 | OpenTelemetry traces | ⏸ | Overkill for solo tool; JSONL telemetry is enough | - ---- - -## 90-day sequencing - -Each sprint is ≈ 2 weeks of focused work and ships behind a feature flag where applicable. Sprints are ordered to compound: Sprint 1's platform-native data (GHAS + releases + staleness) becomes the foundation other surfaces consume; Sprint 2's cache + async layer + workbook profiling make Sprint 3's AI iteration cheap; Sprint 4's UI exposes everything that came before. - -### Sprint 1 — Quick performance + platform wins (current) - -**Goal:** Within 2 weeks, GHAS + release-shipped + README-staleness data lands in the audit JSON, the narrative path works with no Anthropic key required, and the auto-apply path is hardened by mutation testing. Excel + control-center surfacing of the new fields is intentionally deferred to S2.4. Workbook-generation speedup originally scoped here is deferred to S2.0 (profile-first) after the Phase 1 investigation showed both candidate streaming engines were architecturally blocked. - -#### S1.1 — Excel write-path optimization (openpyxl write_only mode) - -- **Decision history:** Initial scope was a full xlsxwriter migration. Inspection of the actual surface (49 excel_*.py modules, 43 import sites, heavy use of `merge_cells`/`conditional_formatting.add`/`add_chart`/`Table`/`DefinedName`, plus `excel_template.py` calling `openpyxl.load_workbook` on a committed template) showed xlsxwriter is not a drop-in: it cannot read existing xlsx files (no `load_workbook`), and `constant_memory=True` forbids the back-reference patterns used in many helpers. Pivoted to openpyxl's own streaming `write_only=True` mode (2026-05-10). -- **Goal:** Reduce peak RAM during workbook generation on 100+ repo runs by switching the from-scratch standard-workbook path to openpyxl's streaming mode where viable, while keeping the template-driven path on regular openpyxl. -- **Constraints to validate first** (write_only mode): - - `WriteOnlyWorksheet.append(row)` only — no `ws.cell(...)`. - - `merge_cells`, `conditional_formatting.add`, and inline cell mutation are **not supported** on write-only sheets. - - Charts can be added at workbook close time, but data must be already written. - - Workbook-level features (defined names, hyperlinks, tables) still work. -- **Scope (likely phased):** - 1. **Phase 1 — Investigation pass.** Catalog every helper by which write_only-incompatible API it uses. Identify a subset of sheets that are pure tabular `append`-only (good streaming candidates) vs. sheets that genuinely need back-reference features (stay on regular openpyxl). - 2. **Phase 2 — Adapter introduction.** Add a thin `excel_engine` adapter giving each helper a `make_sheet(name, streaming: bool)` factory. Streaming sheets get write_only behavior; non-streaming keep the current pattern. No behavior change at this stage — wire the adapter, default everything to `streaming=False`. - 3. **Phase 3 — Flip streaming on for safe sheets.** Per the Phase-1 catalog, set `streaming=True` for tabular sheets (likely `All Repos`, `Portfolio Explorer`, `Run Changes`, `Historical Intelligence`, possibly `Repo Detail`). - 4. **Phase 4 — Validate.** Benchmark vs. baseline; run snapshot tests; if any sheet's output differs, document or revert. -- **Files likely affected:** `src/excel_export.py`, `src/excel_workbook_helpers.py`, new `src/excel_engine.py`, the helpers for the streaming-eligible sheets only, `pyproject.toml`, `tests/test_excel_*.py`. -- **Tests required:** - - Existing workbook tests pass unchanged. - - New benchmark test (gated by `-m benchmark`) timing workbook gen on a 100-repo fixture. - - Snapshot test confirming streaming-eligible sheets produce identical content (sheet name, row count, key cell values) before and after. -- **Effort (revised):** Medium (3-5 days), split into the four phases above. -- **Exit criteria:** Phase 1 catalog committed; at least one sheet converted to streaming with passing tests; benchmark shows a measurable peak-RAM drop on the 100-repo fixture; `--excel-engine` legacy escape hatch documented. -- **Stop condition:** If Phase 1 reveals that fewer than 3 sheets are streaming-eligible, S1.1 ships only the investigation report + adapter scaffolding, and the bulk RAM optimization moves to Sprint 2 with a different approach (likely: profile to find the real bottleneck, which may not be openpyxl at all). - -- **Phase 1 result (2026-05-10):** Stop condition triggered. The streaming catalog in `docs/plans/2026-05-10-s1.1-phase1-streaming-catalog.md` confirms: - 1. `write_only=True` is a workbook-level flag — `WriteOnlyWorksheet` and regular `Worksheet` cannot coexist in one `Workbook`. The pipeline passes a single shared `wb` through 49 helper modules, so splitting is impractical. - 2. **All 20 visible sheets** use at least one write-only-incompatible API (`ws.cell()`, `merge_cells`, `add_table`, `data_validation`, or `freeze_panes` post-write). Streaming-eligible count: **0**. - 3. Both candidate streaming engines (xlsxwriter constant_memory, openpyxl write_only) are therefore architecturally blocked. -- **S1.1 decision:** Ship the Phase 1 catalog doc only. Defer Excel write-path optimization to a **Sprint 2 profile-first work item** ("S2.0 — Profile workbook generation"), which will use `cProfile` + memory-profiler to identify the actual bottlenecks (suspected: column-width sizing loops, per-cell style instantiation, `clear_worksheet` calls). The real win likely comes from in-place algorithm changes inside the existing openpyxl path, not an engine swap. -- **S1.1 ships:** ✅ Phase 1 investigation report committed. Phases 2-4 cancelled. - -#### S1.2 — GitHub Models alternate narrative provider - -- **Goal:** Make `--narrative` work without an Anthropic key for anyone who already has a GitHub PAT. -- **Scope:** Refactor the narrative module to accept a provider strategy. Add `--narrative-provider {anthropic,github-models}` (default: `anthropic` if `ANTHROPIC_API_KEY` is set, else `github-models`). Add `--narrative-model` with sensible defaults per provider (`claude-haiku-*` for Anthropic, `gpt-4o-mini` for Models). -- **Endpoint:** `https://models.github.ai/inference`, OpenAI-compatible, auth via existing PAT with `models: read` scope. -- **Files likely affected:** Narrative module(s) under `src/`, `src/cli.py` (flags + defaults), config docs in `docs/`. -- **Tests required:** Unit tests on the provider-selection logic (no real API calls); mocked transport tests for both providers' happy path and failure cases (401, 429, missing scope). -- **Effort:** Small (≤ 1 day). -- **Exit criteria:** `audit --narrative --narrative-provider github-models` produces a non-empty narrative in CI with a fake transport; `--narrative-provider anthropic` continues to behave exactly as before; docs updated. - -#### S1.3 — Dependabot + CodeQL + Secret-scanning into `risk_overlay` - -- **Goal:** Replace partial OSV.dev-only coverage with first-party GHAS data where available, keeping OSV as a fallback for repos without GHAS access. -- **Scope:** New `SecurityAlertsAnalyzer` that issues three GET calls per repo: - - `/repos/{owner}/{repo}/dependabot/alerts` - - `/repos/{owner}/{repo}/code-scanning/alerts` - - `/repos/{owner}/{repo}/secret-scanning/alerts` - - Aggregate counts by severity (`critical`/`high`/`medium`/`low`) and state (`open`/`dismissed`/`fixed`). -- **JSON shape:** Add a `github_security` sub-key to `risk_overlay` next to the existing `osv_vulns` field. Do not remove `osv_vulns` — keep both for reconciliation. -- **Error handling:** 403 (no access), 404 (feature disabled), 410 (deprecated) all degrade gracefully to "data unavailable" with a single warning log. Do not retry on these; respect 429 with backoff. -- **Files likely affected:** New `src/analyzers/security_alerts.py`, `src/risk_overlay.py` (or wherever risk_overlay JSON is composed), Excel security-summary sheet wiring, control-center surface. -- **Tests required:** Per-endpoint mock responses (happy path, empty, 403, 404, 429), aggregation correctness on mixed-severity inputs, downstream Excel rendering with new field present and absent. -- **Effort:** Small (2 days). -- **Exit criteria:** Risk-overlay JSON contains `github_security` with full severity/state breakdown on test fixtures; control-center shows GHAS counts; OSV path unchanged. - -#### S1.4 — README staleness + release-shipped signal - -- **Goal:** Add two cheap, high-signal dimensions that close common portfolio failure modes ("docs lag the code" and "has commits but never shipped"). -- **Scope:** - - **README staleness:** During clone-aware analysis, compute `readme_last_touched_days` and `code_last_touched_days` from `git log -1` on the README path and on any tracked code file. Surface `readme_staleness_ratio = readme_days / max(code_days, 1)` and a boolean `readme_stale` flag (threshold: ratio < 0.2 AND `code_days < 90`). - - **Release-shipped signal:** Extend `ActivityAnalyzer` to call `/repos/{owner}/{repo}/releases?per_page=10` once per repo. Compute `has_any_release`, `release_count`, `latest_release_age_days`, `latest_prerelease`. Counts toward the `interest` and `completeness` dimensions in addition to standing alone. -- **Files likely affected:** `src/analyzers/readme.py`, `src/analyzers/activity.py`, `src/models.py` (new dataclass fields), Excel + control-center + portfolio-truth wiring. -- **Tests required:** Staleness math on synthetic fixtures (fresh README, ancient README, repo with no README); release endpoint mocked happy/empty/404; downstream Excel column presence. -- **Effort:** Small (1-2 days). -- **Exit criteria:** New fields land in `audit-report-*.json` and the workbook; portfolio-truth + control-center pick them up; tests cover the boundary cases. - -#### S1.5 — mutmut pre-release gate on `auto_apply` + `scorer` - -- **Goal:** Validate that the test suite actually catches logic regressions in the two highest-stakes modules (auto-apply touches real GitHub; the scorer drives every downstream lens). -- **Scope:** - - Add `mutmut>=2.5` to dev extras. - - Configure `[tool.mutmut]` with `paths_to_mutate = ["src/auto_apply.py", "src/scorer.py"]` and a `runner` of `python -m pytest -q -p no:cacheprovider -x` against the matching test files. - - Run once locally; for every surviving mutant, write a focused test that kills it. Target ≥ 85% kill rate. - - Document the workflow in `docs/release-gates.md` as a pre-release check (not on every push — too slow). -- **Files likely affected:** `pyproject.toml`, possibly new tests in `tests/test_auto_apply.py` and `tests/test_scorer.py`, new doc. -- **Effort:** Small (1 day for setup + iteration on surviving mutants). -- **Exit criteria:** First mutmut run completed and surviving mutants either killed by new tests or explicitly documented as equivalent mutants in `docs/release-gates.md`; kill rate ≥ 85% on both files. - -#### Sprint 1 success bar - -The audit JSON outputs contain GHAS alert counts (via `--ghas-alerts`), release-shipped signals, and README-staleness signals; `--narrative-provider github-models` works end-to-end without an Anthropic key; and `auto_apply.py` + `scorer.py` clear an 85% mutmut kill-rate gate. Workbook write-path speedup is **not** part of this sprint's success bar — that target moved to S2.0. - ---- - -### Sprint 2 — Fetch parallelism + cache + platform reads - -**Goal:** Cut the wall-clock of a full-portfolio run in half again, and start eliminating the shallow-clone for analyzers that can read from GitHub directly. - -#### S2.0 — Profile workbook generation (pulled from S1.1 stop condition) - -- **Scope:** Run `cProfile` + `memory_profiler` against `audit --html` on a 100-repo fixture. Identify the top 5 CPU hotspots and top 3 memory accumulators in the Excel write path. Likely suspects: column-width auto-sizing loops, per-cell style instantiation, `clear_worksheet` overhead, and unnecessary `NamedStyle` recreation across sheets. Produce a ranked findings report and an opportunistic-fix list. -- **Effort:** Small (1-2 days for profiling + report; implementation of fixes is a separate sized item depending on findings). -- **Exit criteria:** Profiling report committed, top-3 quick wins implemented if they're contained to single functions; larger structural changes added to the Sprint 2 backlog. - -#### S2.1 — Async fetch layer (`--fetch-workers N`, httpx) - -- **Scope:** New `src/github_client_async.py` using `httpx.AsyncClient`. Bound concurrency with `asyncio.Semaphore(N)` (default N=10) and per-request exponential backoff on 429/secondary-rate-limit responses. The synchronous `GithubClient` interface stays; `async_fetch_all(repos)` is the new bulk path. -- **Tests:** Concurrency-safe stub server, rate-limit simulation, ordering invariance. -- **Effort:** Medium (3-4 days). **Exit criteria:** Full-portfolio fetch phase wall-clock drops ≥ 5x on a 100-repo run with `--fetch-workers 10`. - -#### S2.2 — Per-(repo, sha, analyzer) cache in warehouse DB - -- **Scope:** New `analyzer_cache` table: `(repo_name, commit_sha, analyzer_name, inputs_hash, result_json, computed_at)`. Each analyzer declares an `inputs_hash` over its inputs (e.g., the dependency analyzer hashes the lockfile bytes). Lookup before running; insert after. -- **Tests:** Cache hit and miss paths, inputs-hash sensitivity, eviction policy on warehouse DB size. -- **Effort:** Medium (2-3 days). **Exit criteria:** A second consecutive full-portfolio run on the same SHAs runs analyzers in ≤ 30% of the first run's analyzer CPU time. - -#### S2.3 — SBOM-based dependency fetching + OSSF Scorecard - -- **Scope:** Combine two platform reads that complement the existing dependency + security data: - - `--sbom-source github` switches the dependency analyzer to `/repos/{owner}/{repo}/dependency-graph/sbom/generate-report` → `/fetch-report/{uuid}` (async polling). Parses SPDX 2.3 packages → existing `Dependency` dataclass. Eliminates the shallow-clone step for the dep pass. - - OSSF Scorecard data fetched from `api.securityscorecards.dev/projects/github.com/{owner}/{repo}`. Added as a sub-key in the audit JSON. -- **Tests:** SPDX parsing fidelity, polling-loop terminations, Scorecard 404 handling (private repos), end-to-end behavior with both data sources merged. -- **Effort:** Medium (3-4 days). **Exit criteria:** Repos audited without local clones still produce a complete dependency view; Scorecard sub-scores visible in workbook security sheet. - -#### S2.4 — Workbook + control-center surface wiring for new dimensions - -- **Scope:** Carry the new S1+S2 fields (GHAS counts, release signals, README staleness, Scorecard sub-scores) into the Excel `Security Summary`, `Repo Detail`, and control-center triage. Add filters/sorts where appropriate. -- **Effort:** Small-to-medium (2 days). -- **Exit criteria:** No new field is "JSON only" — everything from S1 and S2 has a surfaced view. - -#### S2.5 — Sprint 2 quality gate - -- **Scope:** Catch any regression introduced by the async fetch layer or cache. Add a `--reconcile-cache` flag that re-runs all analyzers ignoring the cache and diffs against cached results; CI runs this monthly on a fixed fixture. -- **Effort:** Small (1 day). - ---- - -### Sprint 3 — The AI loop - -**Goal:** Promote AI from a single narrative paragraph to a portfolio analyst, anchored by one shared semantic index and a preference-memory pre-filter so suggestions stay relevant. - -#### S3.1 — Portfolio semantic index - -- **Scope:** Embed each repo's `{name}\n{description}\n{README[:2000]}\n{top_files_list}` using `voyage-code-3` (512-dim int8) via the Voyage API, store in a new `repo_embeddings` table with `sqlite-vec`. Reindex only when `pushed_at` changes since last index. -- **Surface:** `--reindex` flag rebuilds; `--semantic-search "query"` and `--ask "question"` (top-K cosine retrieval, prints ranked results with score and a one-line justification from the stored doc snippet). -- **Tests:** Indexing pipeline, vector storage round-trip, query relevance against a labeled mini-set of ≤ 20 known repo/query pairs. -- **Effort:** Medium (3-4 days). **Exit criteria:** Index covers all audited repos, queries return correct top-3 on the labeled set, full reindex of 150 repos finishes in < 60 seconds with cached `pushed_at`. - -#### S3.2 — Weekly Operator Briefing (`--briefing`) - -- **Scope:** Replace the current paragraph-style `--narrative` output with a structured Markdown: - - **Shipped this week** — repos with commits in last 7 days, labeled by automation status. - - **Needs attention** — top-5 repos by completeness-vs-touch gap. - - **Portfolio health delta** — score-movers since last run. - - **Suggested next action** — one sentence per top-3 repos (Haiku or GitHub Models, low cost). - - Voice-readable plain-text variant (no tables, bullet sentences for TTS). -- **Files:** Extends the narrative module. Composes most content deterministically from warehouse data; LLM only for the suggested-action sentences. -- **Tests:** Section-presence assertions, fixture-based snapshot for Markdown structure. -- **Effort:** Small-to-medium (2 days). - -#### S3.3 — Operator preference memory - -- **Scope:** Post-process the approval/rejection ledger. When the same `(action_type, target_context)` is rejected ≥ 3 times in a row, write a suppression hint to `output/operator_prefs.json`. Future planner + drafter + briefing reads this file before proposing actions; suppressed actions get a `suppressed: true` flag in their proposal record so they're visible but de-emphasized. -- **Reset:** `audit --reset-prefs` clears suppression hints. -- **Tests:** Trigger threshold, suppression integration into briefing output, reset behavior. -- **Effort:** Small (1-2 days). - -#### S3.4 — Semantic index integrations - -- **Scope:** Two cheap wins built atop S3.1: - - **Cross-repo duplication detector** — pairs with cosine > 0.85 flagged in control-center. - - **Briefing enrichment** — when summarizing a repo, retrieve its nearest neighbors as "related repos" context for the LLM call. -- **Effort:** Small (1-2 days combined). - -#### S3.5 — Sprint 3 cost guard - -- **Scope:** Track per-run LLM spend in `run-telemetry.jsonl`. Add `--max-llm-spend USD` to halt runs that would exceed budget. Default disabled. -- **Effort:** Small (1 day). - ---- - -### Sprint 4 — UI + distribution + CLI restructure - -**Goal:** Make every artifact in `output/` and the warehouse browsable from a UI, make the tool one command to install on a fresh machine, and cluster the CLI so new operators don't drown in 70+ flags. - -#### S4.1 — `audit serve` (FastAPI + HTMX) - -- **Scope:** `audit serve --port 8080` starts an `uvicorn`-served FastAPI app. Routes: - - `/` — portfolio dashboard from latest `portfolio-truth-latest.json` - - `/repos/{name}` — per-repo drill-down (history, scores, alerts) - - `/runs` — historical run browser pulling from `portfolio-warehouse.db` - - `/approvals` — pending approval queue with form actions - - `/runs/new` — form that constructs a CLI invocation, streams stdout via SSE -- **HTMX** handles partial refreshes; no JS build step. -- **Tests:** Route smoke tests with a fixture warehouse, form validation, SSE happy-path. -- **Effort:** Medium (3-4 days). -- **Exit criteria:** Operator can complete a full triage cycle (browse latest run → drill into a flagged repo → approve a packet → trigger an apply run) entirely from the browser. - -#### S4.2 — PyPI publish + `shiv` binary - -- **Scope:** Drive `__version__` via `hatch-vcs`. Add `hatch build` + `twine upload` workflow. Build a `shiv` single-file `.pyz` and attach to GitHub Releases. Update README install snippet to `uv tool install githubrepooauditor`. -- **Effort:** Small (1 day). - -#### S4.3 — CLI subcommand restructure (`audit run / triage / report`) - -- **Scope:** Introduce three subparsers. Migrate flags into the appropriate subcommand. Keep the legacy flat invocation working via a compatibility shim for one major version with a deprecation warning. Update all docs and example invocations to the subcommand form. -- **Tests:** Both old and new invocation forms produce identical outputs on a fixture run; deprecation warning emitted on legacy form. -- **Effort:** Medium (2-3 days). -- **Exit criteria:** `audit triage --help` shows ≤ 15 flags; `audit run --help` shows ≤ 20; total surface area is unchanged but discoverable. - -#### S4.4 — Documentation refresh - -- **Scope:** Rewrite README intro to lead with `audit serve`. Update `docs/modes.md` with the new subcommand verbs. Add `docs/release-gates.md` (mutmut + workbook-signoff + manual-approval workflow in one place). -- **Effort:** Small (1 day). - -#### S4.5 — Arc F closeout - -- **Scope:** Capture an Arc F closeout doc summarizing what shipped vs. backlog vs. deferred. Update `docs/architecture.md` if surface area changed. Confirm the trust bar still holds after async/cache changes (re-run the full approval workflow end-to-end on a real repo opt-in). -- **Effort:** Small (1 day). - ---- - -## Backlog (post-90-day, ordered by readiness) - -These are documented and scoped — they just don't fit the 90-day window. They become candidate Sprint 5+ items. - -1. **Agentic README/description authoring** (`--draft-readmes`) — LLM-authored diff packets routed through the existing approval ledger. Builds on S3.1 + S3.3. -2. **Planner agent for campaign authoring** (`--plan-campaign "goal"`) — same approval-lane integration as `--draft-readmes`. -3. **Eval-driven scoring tuning** (`--tune-scoring-profile`) — operator labels small eval set; grid search over weights proposes `operator-tuned.json`. -4. **Tiered maturity + Initiative tracker** — Cortex-style 4 tiers with deadline-bound initiatives. -5. **DORA-Lite metrics** — release cadence, lead-time proxy, change-failure proxy. -6. **Star momentum + cross-repo context-switching heatmap** — visualization-heavy additions to the HTML dashboard. -7. **Year-in-review report** (`--year-review YYYY`). -8. **Cross-repo code search** (`--cross-repo-search "PATTERN"`). -9. **Repo rulesets + signing governance score expansion**. -10. **CI health analyzer** (workflow runs + cache stats). -11. **Auto-topic suggestions via LLM**. -12. **Bus-factor + commit-message hygiene + milestone hygiene** — three small dimension adds. -13. **Plugin architecture** (entry-points for `Analyzer`/`Exporter`/`Scorer`). -14. **structlog + per-phase timings** to `run-telemetry.jsonl`. -15. **Arc E desktop shell** (Tauri 2 + React) — build after `audit serve` validates which views are daily-driver material. - ---- - -## Explicitly deferred - -| Item | Reason | -|---|---| -| Webhook daemon (`--serve-webhook`) | Persistent server + GitHub App token management is operationally heavy. Only justified at portfolio scale we don't have. | -| LLM code-quality analyzer per file | Per-file LLM cost adds up across 100+ repos; existing deterministic analyzers cover ~80% of the signal. Revisit only if a specific gap is identified. | -| Local-first Ollama classification | API cost is not the constraint today. Keep simple. | -| OpenTelemetry traces | Overkill for solo tool; append-only JSONL telemetry is sufficient. | - ---- - -## Cross-sprint principles - -1. **Flag-gate every new behavior** for the first sprint it ships. Default-off until validated on real runs. -2. **No data loss on schema changes.** New JSON keys are additive; never rename or drop. Warehouse migrations are forward-only with backfill where needed. -3. **Trust bar is not bypassed.** Every new writeback path (S3 agentic drafts, S4 web UI form-triggered applies) flows through the existing approval ledger. Auto-apply still requires the three-part trust bar. -4. **Tests over benchmarks.** Performance changes (S1.1, S2.1, S2.2) add benchmark tests so we can verify the speedup claim, but a passing test suite is the hard gate. -5. **Conventional commits per logical unit**, not per sprint item — a sprint item may produce multiple commits if its concerns are independent. -6. **Demand-elegance pass before commit on items > 200 lines of diff**, per global rule. `/code-review` invoked for API contracts, auth/auth-adjacent flows, and migrations. - ---- - -## Open questions / decisions needed - -| Q | Decision needed by | Notes | -|---|---|---| -| Should `voyage-code-3` be the embedder, or a local sentence-transformer? | Sprint 3 kickoff | Voyage is best-in-class for code, but adds an API dependency. Local `all-MiniLM-L6-v2` is free but ~15% weaker. Lean Voyage; provide a `--embedder local` fallback. | -| Should `audit serve` ship behind a `--dev-only` flag in v1, or be the new default install story? | Sprint 4 kickoff | Probably ship as opt-in for a release, then promote in README once it has battle-tested under 100+-repo loads. | -| What's the acceptable mutmut kill-rate threshold? | Sprint 1 closeout | Plan says 85%. Confirm or adjust based on initial run. | -| Do we publish to PyPI under `githubrepoauditor` or rename for clarity (`gh-portfolio-auditor`)? | Sprint 4 kickoff | Current name is established locally; check PyPI availability before reserving. | - ---- - -## How to read this plan going forward - -- **Current state lives in this file.** When a sprint item ships, flip its row in the inventory from ✅ Sprint to ✅ Shipped and link the merge commit. -- **Each sprint produces a closeout entry** at the bottom of this file once complete: what shipped, what slipped, what we learned. No separate doc. -- **The backlog is reviewed at every sprint boundary.** Items can be promoted to the next sprint or demoted to deferred. The deferred list is sacred — items only leave it via a new ADR. -- **When in doubt, prefer scope reduction over silently descoping.** Per global scope-discipline rule: if a sprint item is too big, propose a phase split in this doc; do not ship a "v1" of it without saying so. - ---- - -## Sprint closeouts - -(Populated as sprints complete.) - -### Sprint 1 closeout (2026-05-11) - -**Shipped:** - -- **S1.2 — GitHub Models alternate narrative provider.** `--narrative-provider {anthropic,github-models}` + `--narrative-model` flags. Provider strategy pattern in `src/narrative.py`. 19 tests. Commit `ae0a7c6`. -- **S1.3 — GHAS alerts analyzer.** New `src/ghas_alerts.py`, `--ghas-alerts` flag. Open-alert counts from Dependabot, CodeQL, Secret-scanning. Writes `output/ghas-alerts-*.json` + terminal summary. 18 tests. Commit `2703fb4`. -- **S1.4 — README staleness + release-shipped signals.** New fields in `ReadmeAnalyzer` and `ActivityAnalyzer`. New `GithubClient.get_releases()`. 12 tests. Commits `ab70a04` + `f2594a0` (the second fixes an inverted threshold that shipped in the first). -- **S1.5 — mutmut pre-release gate.** `[tool.mutmut]` config, `release-gate` Makefile target, `docs/release-gates.md`. Initial run hit **92.9% kill rate** (above the 85% threshold), with 25 equivalent mutants documented. New mutmut-killing tests added to `test_auto_apply.py` and `test_scorer.py`. Commits `a7b6918` + `fce45dd`. - -**Stopped (with evidence, not silently descoped):** - -- **S1.1 — Excel write-path optimization.** Phase 1 investigation in commit `9a68e1c` proved both candidate streaming engines (xlsxwriter `constant_memory`, openpyxl `write_only`) were architecturally blocked: workbook-level mixing impossible, all 20 visible sheets use incompatible back-reference APIs. Phases 2-4 cancelled. Excel perf work pivoted to **S2.0 (profile-first)**. Decision recorded in commit `3cd2d80`. - -**Sprint 1 success-bar grading:** - -- GHAS + release + staleness data in audit JSON: ✅ shipped (Excel/control-center surfacing deferred to S2.4 as planned). -- `--narrative-provider github-models` works without Anthropic key: ✅ shipped. -- mutmut kill rate ≥ 85% on `auto_apply.py` + `scorer.py`: ✅ shipped (92.9%). -- Workbook speedup: not part of this success bar (moved to S2.0). - -**Gaps closed at sprint boundary (2026-05-11):** - -- README + `docs/modes.md` + `docs/security-model.md` + `docs/extending-analyzers.md` updated with the new flags and analyzer fields. -- Inventory table flipped to "✅ Shipped @ \" for shipped items and "⏹ Stopped" for S1.1. - -**Lessons:** - -1. The original "1-2 day xlsxwriter swap" estimate from the brainstorming research was wrong by ~10x — the live code surface had load_workbook templates, charts, conditional formatting, merges, and tables that no streaming engine supports. **Investigation before scoping is cheaper than scope creep mid-sprint.** -2. The S1.4 spec encoded an inverted threshold (`< 0.2` instead of `> 5.0`) which the subagent implemented faithfully. Caught at review time and fixed in a follow-up commit. **Specs with numeric thresholds should include a worked example.** -3. The TaskCompleted hook runs whole-repo mypy and trips on 372 pre-existing errors unrelated to in-sprint work, leaving some tasks visually "in_progress" despite the underlying work being complete. Known harness gotcha; future sessions can ignore. - -**Next:** Sprint 2 begins with **S2.0 — Profile workbook generation**, then S2.1 (async fetch), S2.2 (per-(repo, sha, analyzer) cache), S2.3 (SBOM + Scorecard), S2.4 (workbook + control-center surface wiring for the new Sprint 1 fields). - -### Sprint 2 closeout (2026-05-11) - -**Shipped (all 6 items):** - -- **S2.0 — Workbook profiling + quick wins.** Profile against 90-repo synthetic portfolio (`scripts/benchmark_large_portfolio.py`) identified `style_data_cell` (2.046s cumulative, IndexedList hash chain) as the dominant CPU hotspot. Three quick wins: (a) NamedStyle registration replacing 3-attribute style assignments; (b) skip zebra stripes on hidden sheets; (c) skip auto-width on hidden sheets. **Workbook build went from 3.201s → 0.396s (8.1x speedup).** Findings doc: `docs/plans/2026-05-11-s2.0-workbook-profile-findings.md`. Commits `2503660` + `2472c20` + follow-up fix `ef038f6` (see Lesson #1 below). -- **S2.1 — Async fetch layer.** `src/github_client_async.py` (327 lines) with `httpx.AsyncClient` + `asyncio.Semaphore`. Opt-in via `--fetch-mode async --fetch-workers N` (default still sync). Mock microbench: 70ms async at concurrency=10 vs 683ms sequential = **9.7x speedup**. 18 new tests. Commit `bc2f95f`. -- **S2.2 — Per-(repo, sha, analyzer) cache.** New `analyzer_cache` table in warehouse DB + `src/analyzer_cache.py` module. Three analyzers opted in (Dependencies, Readme, Structure). `BaseAnalyzer.cache_inputs_hash` is the opt-in contract. `--no-analyzer-cache` flag disables. +29 tests. Commit `0375053`. -- **S2.3 — SBOM + OSSF Scorecard.** `--sbom-source github` switches the dependencies analyzer to GitHub's SBOM endpoint (synchronous SPDX 2.3 JSON, not the planned async polling — the live API is direct GET). New `src/ossf_scorecard.py` module + `--ossf-scorecard` flag fetches from `api.securityscorecards.dev`. Misleading help text on the existing `--scorecard` flag fixed. Incidentally fixed a pre-existing bug in `libyears.compute_libyears()` that was silently overwriting lockfile-parsed `dep_count`. +26 tests. Commit `aa5dbee`. -- **S2.4 — Surface wiring for Sprint 1 fields.** GHAS counts, README staleness, release-shipped, and OSSF Scorecard score now appear in: Excel Security Summary (severity columns), Repo Detail (per-repo block), All Repos (narrow flags), and the control-center triage. New lane rules: `readme_stale → urgent`, `GHAS critical ≥ 1 → blocked`, `OSSF < 5.0 → ready` flag. Backward-compatible with older audit JSONs. +34 tests. Commit `ab2e0d4` + ruff fix `f83ca10`. -- **S2.5 — `--reconcile-cache` quality gate.** Re-runs analyzers with cache off, deep-compares against cached results (1e-6 float tolerance, recursive dict compare). Exits non-zero on divergence. Intended for CI release-gate use. +16 tests. Commit `54a78ea`. - -**Sprint 2 success-bar grading:** - -- Wall-clock fetch reduction: ✅ shipped (9.7x on mock benchmark; real-API estimate is 12s vs 120s on 100-repo). -- Workbook generation speedup: ✅ exceeded (8.1x vs original 3-5x target). -- Analyzer cache covers ≥30% CPU reduction on re-runs: ✅ shipped via 3 opted-in analyzers + reconcile gate. -- Sprint 1 fields visible in Excel + control center: ✅ shipped. -- SBOM + Scorecard data available: ✅ shipped. - -**Lessons:** - -1. **Module-level caches keyed by `id()` are unsafe across object lifetimes.** S2.0's NamedStyle quick-win cached registration state in `dict[id(wb), set]`. Python recycles `id()` values after GC, so a fresh `Workbook` could inherit a stale "already registered" marker from a GC'd predecessor and crash at `cell.style = "data_left"`. Caught only when S2.2's tests created additional short-lived workbooks, surfacing the bug as test-order-dependent. Fix: store the marker as an attribute on the workbook itself (`wb._gha_named_styles_registered = True`). Commit `ef038f6`. -2. **The live API can be simpler than the docs imply.** S2.3 was scoped around a SBOM async-polling flow (`generate-report` → `fetch-report/{uuid}`). The live endpoint is a single synchronous GET. The subagent correctly followed the live API and noted the deviation. **Future plans citing external APIs should mark the citation as "as of " rather than committed contract.** -3. **Pre-existing bugs surface when new tests exercise neglected paths.** S2.3 incidentally fixed `libyears.compute_libyears` silently zeroing `dep_count`. Fix was contained but is worth noting: every new test we write is also a regression detector for code that wasn't being exercised. - -**Plan housekeeping at sprint boundary:** - -- Inventory rows 1.2, 1.4, 4.3, 4.4 flipped to "✅ Shipped @ \". -- S2.0 (profile/quick wins), S2.4 (surface wiring), and S2.5 (reconcile gate) were not inventory items — they're sprint-level concerns. They appear in this closeout only. -- Docs not yet updated with the new flags (`--sbom-source`, `--ossf-scorecard`, `--fetch-mode`, `--fetch-workers`, `--reconcile-cache`, `--no-analyzer-cache`). Carry this as a gap to close before Sprint 3 starts. - -**Branch state:** `feat/arc-f-expansion-roadmap`, 18 commits ahead of `main`. 1285 tests pass. Ruff clean. Not pushed. - -**Next:** Sprint 3 begins with **S3.1 — Portfolio semantic index** (sqlite-vec + voyage-code-3 embeddings), then S3.2 (Weekly Operator Briefing), S3.3 (Operator preference memory), S3.4 (semantic index integrations — duplication detector + briefing enrichment), S3.5 (LLM spend cost guard). - -### Sprint 3 closeout (2026-05-11) - -**Shipped (all 5 items):** - -- **S3.1 — Portfolio semantic index.** New `src/semantic_index.py` (433 lines). `voyage-code-3` default embedder (512-dim, cosine distance), `sentence-transformers/all-MiniLM-L6-v2` local fallback (384-dim). `sqlite-vec` virtual table `repo_embeddings` + metadata table in the warehouse. `--reindex`, `--reindex-force`, `--semantic-search`, `--ask`, `--embedder {voyage,local}` flags. Reindex skips unchanged docs via `doc_sha256` comparison. `[semantic]` optional extra in `pyproject.toml`. +32 tests. Commit `44839de`. -- **S3.2 — Weekly Operator Briefing.** New `src/briefing.py` (586 lines). Sections: shipped-this-week, needs-attention (top-5 by gap heuristic), portfolio health delta (warehouse-historical), suggested next action (Haiku-equivalent LLM, 1 sentence per top-3 repos). Voice-readable plain-text variant. Reuses S1.2's narrative provider strategy. `--briefing` mutually exclusive with `--narrative`; `--briefing-voice` for TTS file. +31 tests. Commit `0438428`. -- **S3.3 — Operator preference memory.** New `src/operator_prefs.py` (365 lines) + `output/operator_prefs.json` schema. Detects 3+ consecutive rejections of `(action_type, target_context)` and writes auto suppression hints. `manual: true` entries are preserved across runs. Atomic tmp+rename writes. `--reset-prefs` flag. Briefing's suggestion generator consults prefs and records `suppressed_by_prefs` for observability. +12 tests. Commit `e0fec52`. -- **S3.4 — Semantic index integrations.** Two cheap wins atop S3.1: (a) `find_neighbors(repo_name, k)` and `find_duplicate_groups(threshold=0.85)` on `SemanticIndex` with union-find transitive closure. Control-center adds duplicate-group lane entries (`lane: ready`, `priority: 35`). (b) Briefing's suggestion prompt now includes `related_repos` context for cross-repo aware suggestions. Both integrations degrade gracefully when no index exists. `SEMANTIC_DUPLICATE_THRESHOLD` env override. +13 tests. Commit `03dc2bc`. -- **S3.5 — LLM spend cost guard.** New `src/llm_cost.py` (253 lines) with `CostTracker`, `BudgetExceededError`, per-model PRICES table (7 models documented, conservative `_UNKNOWN_PRICE` fallback). Wired into both `AnthropicProvider` and `GitHubModelsProvider` — each `generate()` records token usage automatically. `--max-llm-spend USD` flag halts the run if budget would be exceeded. Per-call records append to `output/run-telemetry.jsonl`. +14 tests. Commit `e661829`. - -**Sprint 3 success-bar grading:** - -- Portfolio semantic index queryable via `--ask`: ✅ shipped. -- Briefing replaces narrative as the structured weekly output: ✅ shipped. -- Suggestions no longer repeat what the operator has rejected: ✅ shipped via prefs. -- Duplicate groups surface in control-center: ✅ shipped. -- LLM spend is observable and budget-gateable: ✅ shipped. - -**Lessons:** - -1. **Subagent worktree branch lineage is brittle when worktrees are run in parallel and dispatched against a moving branch tip.** S3.1 and S3.5's commits ended up on different ancestor lines than expected (one auto-landed via the worktree merge, the other needed a manual cherry-pick). Detection: always run `git log --oneline -3` after each cherry-pick attempt to confirm what's on HEAD before claiming "shipped". -2. **Pricing tables for external APIs are maintenance debt.** S3.5's `PRICES` dict will go stale; the conservative `_UNKNOWN_PRICE` fallback (over-estimates cost) ensures stale data fails closed, not open. Worth a quarterly review reminder. -3. **Provider Protocol signature drift matters for subclass tests.** S3.2 set `generate(self, prompt, model, **kwargs)` while the Protocol said `generate(self, prompt, model, max_tokens)`. Mypy caught it post-hoc. Future Protocol changes should ripple-update the provider classes in the same commit. - -**Plan housekeeping:** - -- Inventory rows 2.1, 2.2, 2.3, 2.4 flipped to "✅ Shipped @ \". -- S3.5 (LLM cost guard) was not in the original inventory — appears in this closeout as a sprint-level addition. Add a row to Theme 2 in the next plan revision if Arc F continues. - -**Branch state:** `feat/arc-f-expansion-roadmap`, 27 commits ahead of `main`. 1388 tests pass. Ruff clean. Not pushed. - -**Next:** **Sprint 4 — UI + distribution + CLI restructure.** S4.1 (`audit serve` FastAPI + HTMX), S4.2 (PyPI + shiv binary), S4.3 (CLI subcommand restructure `audit run/triage/report`), S4.4 (docs refresh), S4.5 (Arc F closeout). The semantic index + briefing + cost guard from Sprint 3 are now the inputs to the live web UI's most important views. - -### Sprint 4 closeout (2026-05-11) - -**Shipped (all 5 items):** - -- **S4.1 — `audit serve` FastAPI + HTMX local web UI.** New `src/serve/` package (app/routes/runner/templates/static, 12 files). Routes: `/` (dashboard from `portfolio-truth-latest.json`), `/repos/{name}` (per-repo drill-down), `/runs` (warehouse-backed run browser), `/approvals` (approval queue with HTMX form actions), `/runs/new` (form + SSE-streamed stdout). HTMX 2.0.4 via CDN, Jinja2 templates, minimal hand-rolled CSS. Subprocess safety: flag allowlist + shell-metachar rejection + `subprocess.Popen([...])` list form. New `[serve]` optional extra in `pyproject.toml`. +36 tests. Commits `4da1496` (initial) + `220a6fa` (fix: make `username` positional optional so `--serve` works standalone). -- **S4.2 — PyPI publish + shiv binary.** `Makefile` with `build`/`dist-check`/`shiv`/`release` targets. `scripts/release.sh` for manual PyPI push (reads `TWINE_API_TOKEN`, never hardcoded). `.github/workflows/release.yml` for `v*`-tagged GitHub Releases (build + shiv + upload). `dist/audit.pyz` is a 16 MB single-file zipapp executable on any Python 3.11+ system. New `[build]` optional extra (`shiv`, `build`, `twine`). README install section leads with `uv tool install` / `pipx install` / `.pyz` download. Deferred: `hatch-vcs` migration (kept setuptools static `__version__` to avoid downstream churn — flagged as future work). Commit `1316aaf`. -- **S4.3 — CLI subcommand restructure (`audit run / triage / report / serve`).** New `build_subcommand_parser()` coexists with legacy `build_parser()`. Flag-family mapping hits the exit criteria: `run` 20 non-global flags (limit 20), `triage` 15 (limit 15), `report` 24 (limit 25). Legacy flat invocation (`audit --flag`) auto-detected via GitHub username regex (`[a-zA-Z0-9][a-zA-Z0-9-]{0,38}`), rewritten to subcommand form, emits one `DeprecationWarning` per process. `audit serve` available both as subcommand and as legacy `--serve` flag (preserves S4.1 surface). All ~103 flags remain accessible. +45 tests. Commit `9ee3932`. -- **S4.4 — Documentation refresh.** README now leads with the subcommand form, install via `uv tool install githubrepoauditor`, daily flow showing `audit serve` → browse → trigger. `docs/modes.md` gains four subcommand sub-sections with flag tables. `docs/release-gates.md` gains Distribution Gate and Web UI Gate sections (appended to existing S1.5 mutmut content). New `docs/audit-serve.md` (operator guide) and `docs/audit-cli-migration.md` (before→after flag mapping for the 8 most common invocations). Commit `93d827b`. -- **S4.5 — This closeout.** - -**Sprint 4 success-bar grading:** - -- Operator completes a full triage cycle in the browser: ✅ shipped via S4.1 (browse latest → drill repo → approve → trigger apply run, all via `audit serve`). -- One-command install on a fresh machine: ✅ shipped via S4.2 (`uv tool install githubrepoauditor` plus the `audit.pyz` download path). -- `audit triage --help` fits on one screen: ✅ shipped via S4.3 (15 non-global flags). -- README leads with `audit serve`: ✅ shipped via S4.4. -- Trust bar still holds after async/cache/CLI restructure: ✅ confirmed via 1469 passing tests at `93d827b`; no regression in approval-ledger or auto-apply paths. - -**Lessons:** - -1. **Subagent worktree branch-base brittleness — again.** S4.4's worktree branched from `d9cf875` (a `main` ancestor, pre-Arc-F) instead of `d7423c5` (the current Arc F tip). Surfaced as: agent reported "1000 tests pass" instead of 1469, and `docs/release-gates.md` cherry-picked with a conflict because the agent's base lacked the S1.5 mutmut content. Detection signal that worked: comparing the agent's reported test count to the expected count exposed the wrong-base problem before merge. **Action for Arc G:** assert the worktree's `git merge-base HEAD feat/` equals the expected tip before letting the subagent commit, or pass `--base ` to the worktree creator. -2. **Boot tests catch CLI wiring bugs the unit suite misses.** S4.1 shipped with `python -m src --serve` rejected because the `username` positional was still required (subagent never exercised the standalone serve path in a real subprocess — only via `TestClient`). The 5-line `curl localhost:/` boot test caught it in seconds. **Action:** every new top-level CLI mode should have a one-line "process boots, route returns 200" smoke baked into the verification step. -3. **Two-parser coexistence is a clean CLI-migration pattern.** S4.3's legacy + subcommand parser pair sidestepped the "rewrite or break tests" tradeoff entirely — the subcommand parser detects which mode the user invoked, then re-parses through the legacy parser for full flag validation. No existing tests touched. Worth remembering when a future arc needs to migrate a similarly-sized flag surface. - -**Plan housekeeping:** - -- Inventory rows 4.5, 4.6, 4.7 flipped to "✅ Shipped @ \". -- S4.4 (docs refresh) and S4.5 (this closeout) are sprint-level — they appear here only, not in the table. - -**Branch state:** `feat/arc-f-expansion-roadmap`, 37 commits ahead of `main`. 1469 tests pass. Ruff clean. Not pushed. - ---- - -## Arc F closeout (2026-05-11) - -Arc F shipped over four sprints, ~10 calendar days end to end, on a single feature branch (`feat/arc-f-expansion-roadmap`). 37 commits on top of `main`. 1469 tests pass at tip (up from 1285 at Arc F kickoff — +184 net new tests). Ruff clean throughout. No prod regressions; trust bar held. - -**Inventory tally (from the table at the top of this doc):** - -- 8 items shipped as planned. -- 1 item stopped (S1.1 — xlsxwriter migration; investigation revealed architectural blocker, pivoted to S2.0 profile-first which delivered 8.1x workbook speedup via NamedStyle registration). -- 4 sprint-level items not in inventory (S2.0, S2.4, S2.5, S3.5, S4.4, S4.5) — all shipped, captured in sprint closeouts above. -- 0 items deferred from the original plan. - -**What changed for the operator:** - -- **A new way in:** `audit serve` puts every artifact behind a local web UI — dashboard, per-repo drill-down, run history, approval queue, run trigger with live SSE output. The daily flow is now "open the URL" rather than "remember which Markdown to grep". -- **A new way to ask:** `audit run --briefing` produces a structured weekly briefing (shipped-this-week, needs-attention top-5, portfolio health delta, suggested next action) instead of the older free-form narrative; `audit triage --semantic-search` and `audit triage --ask` query the portfolio embedding index for cross-repo recall. -- **A new way to trust:** the suggestion engine reads `output/operator_prefs.json` and suppresses repeated rejected suggestions; LLM spend is observable via `output/run-telemetry.jsonl` and gateable via `--max-llm-spend`. -- **A new way to install:** `uv tool install githubrepoauditor`, `pipx install githubrepoauditor`, or the `audit.pyz` single-file binary download from GitHub Releases (no Python install required on the host). -- **A new way to invoke:** `audit run / triage / report / serve` subcommands replace 70 flat flags. Legacy form still works (with a one-time deprecation warning per process). -- **Faster underneath:** the per-(repo, sha, analyzer) cache (S2.2) plus the async fetch layer (S2.1) cut typical repeated-run time meaningfully (9.7x on the mock benchmark; real-world depends on cache hit rate). Workbook generation 8.1x faster (S2.0 NamedStyle fix). -- **More signal:** Dependabot/CodeQL/Secret-scanning alerts (S1.3), SBOM via GitHub API (S2.3), OSSF Scorecard scores (S2.3), README staleness (S1.4), release-shipped signals (S1.4), all surfaced through existing analyzer and workbook paths. - -**Backlog status:** The 15-item post-90-day backlog in the original plan is unchanged. Three items become candidate Sprint 5 starters once Arc F merges: - -1. Agentic README/description authoring (`--draft-readmes`) — builds directly on S3.1 (semantic index) + S3.3 (operator preferences). -2. Planner agent for campaign authoring (`--plan-campaign "goal"`). -3. Tiered maturity + Initiative tracker (4-tier Cortex-style with deadline-bound initiatives). - -The web UI (S4.1) is now the natural extension point for any of these — they can ship UI-first rather than CLI-first. - -**Three durable lessons captured across the arc:** - -1. **External-API specs go stale; live behavior is the contract.** S2.3 found the GitHub SBOM endpoint was a single sync GET rather than the documented async polling flow. Future plans citing external APIs should mark citations as "as of \" rather than committed contract. -2. **Module-level state keyed by `id(obj)` is unsafe across object lifetimes.** S2.0's NamedStyle bug taught us this — Python recycles `id()` after GC. Store registration markers as attributes on the object, not in module-level dicts. -3. **Subagent worktree branch-base brittleness is a real, repeatable hazard.** It bit us in S3.1/S3.5 (cherry-pick auto-landed vs manual) and again in S4.4 (wrong base entirely). Detection signal: compare the agent's reported test count to current tip. Action for Arc G: validate `merge-base` matches expected tip before allowing subagent commit, or pass `--base ` explicitly. - -**Ready to merge:** `feat/arc-f-expansion-roadmap` is mergeable to `main`. 1469 tests, ruff clean, no pushed remote. Suggest squash-merge to one or a small number of feat commits per sprint, preserving the closeouts in the squashed body so the Arc F narrative survives. Or land as four sprint-merge commits (one per sprint) plus a single docs commit for the plan doc itself. diff --git a/docs/plans/2026-05-10-closure-forecast-wrapper-retirement-audit.md b/docs/plans/2026-05-10-closure-forecast-wrapper-retirement-audit.md deleted file mode 100644 index 46c96c1b..00000000 --- a/docs/plans/2026-05-10-closure-forecast-wrapper-retirement-audit.md +++ /dev/null @@ -1,43 +0,0 @@ -# Closure Forecast Wrapper Retirement Audit - 2026-05-10 - -## Status - -Do not retire the closure-forecast compatibility wrappers yet. - -The closure-forecast implementation bodies now sit behind the conceptual facade modules, and production code imports those facades. The original long `operator_trend_closure_forecast_*` module paths remain useful as a compatibility contract and are still deliberately exercised by tests. - -## Evidence - -Audit scope: - -- Repo-local Python imports in `src/` and `tests/`. -- Local `/Users/d/Projects` text search for old closure-forecast wrapper names outside this repo. -- Current modernization record in `docs/plans/2026-05-09-closure-forecast-modernization.md`. - -Findings: - -- Product code has no imports from old closure-forecast wrapper modules. -- `src/operator_resolution_trend.py` imports closure-forecast helpers from the facade modules. -- Tests still include 24 old-path imports across 17 test files. -- Local `/Users/d/Projects` search found 0 old closure-forecast wrapper references outside `GithubRepoAuditor`. -- The wrapper modules themselves remain the repo's stable compatibility surface. - -## Decision - -Keep all compatibility wrappers for now. - -Do not remove the old module paths in a cleanup pass unless a future audit also updates or retires the old-path compatibility tests and explicitly accepts the downstream compatibility break. - -## Retirement Gate - -Wrapper removal is safe only after all of these are true: - -1. No product code imports old wrapper modules. -2. No repo tests require old wrapper imports as a compatibility guarantee. -3. A local workspace search finds no downstream callers outside this repo. -4. The repo docs explicitly announce the compatibility removal. -5. Full pytest, Ruff, scoped mypy, CLI help smokes, and workbook gate pass after removal. - -## Recommended Next Move - -Leave the wrappers in place. If more modernization is useful, apply the same behavior-preserving facade pattern to a different high-sprawl area instead of deleting this compatibility layer now. diff --git a/docs/plans/2026-05-10-excel-workbook-contract-modernization.md b/docs/plans/2026-05-10-excel-workbook-contract-modernization.md deleted file mode 100644 index e3afbab8..00000000 --- a/docs/plans/2026-05-10-excel-workbook-contract-modernization.md +++ /dev/null @@ -1,83 +0,0 @@ -# Excel Workbook Contract Modernization - 2026-05-10 - -## Status - -The first four workbook-surface modernization passes are implemented. - -These passes are intentionally behavior-preserving. They move stable workbook structure ownership out of `src/excel_export.py` and into the helper/runtime layer, where workbook ordering, visibility, and finalization helpers already live. - -## What Changed - -- `src/excel_workbook_helpers.py` now owns `CORE_VISIBLE_SHEETS`. -- `src/excel_export.py` imports and re-exports `CORE_VISIBLE_SHEETS` for compatibility with existing callers. -- `src/workbook_gate.py` now reads `CORE_VISIBLE_SHEETS` from the workbook helper layer instead of the exporter module. -- `src/excel_export_registry_helpers.py` now owns the default runtime wiring for `CORE_VISIBLE_SHEETS` and `DEFAULT_PREFERRED_SHEET_ORDER`. -- `src/excel_export_registry_helpers.py` also owns the default workbook build-step executor wiring. -- `src/excel_export_registry_helpers.py` also owns the default workbook finalization wiring. -- `src/excel_export.py` still re-exports the workbook structure constants, but no longer passes them through every runtime build call. -- `tests/test_excel_export_registry_helpers.py` protects the default and explicit runtime structure contracts. -- The exporter still owns workbook build adapters and the public `export_excel(...)` entrypoint. - -## Why This Boundary - -The Excel surface remains the next visible maintainability hotspot after closure-forecast consolidation. The safest first pass is to move a stable workbook contract, not sheet rendering logic. - -This keeps workbook behavior stable while making future cleanup easier: - -- workbook structure constants live beside workbook structure helpers, -- the release gate depends on the workbook contract layer, -- the workbook runtime helper owns the default structure wiring, -- the workbook runtime helper owns the default build-step executor wiring, -- the workbook runtime helper owns the default workbook finalization wiring, -- and `src/excel_export.py` remains a compatibility facade for existing tests and callers. - -## Verification - -Completed during this pass: - -```bash -python3 -m pytest tests/test_excel_enhanced.py tests/test_workbook_gate.py -q -p no:cacheprovider -ruff check src/excel_export.py src/excel_workbook_helpers.py src/workbook_gate.py tests/test_excel_enhanced.py tests/test_workbook_gate.py -mypy src/excel_export.py src/excel_workbook_helpers.py src/workbook_gate.py --ignore-missing-imports -``` - -Focused verification for the second pass: - -```bash -python3 -m pytest tests/test_excel_export_registry_helpers.py tests/test_excel_enhanced.py tests/test_workbook_gate.py -q -p no:cacheprovider -ruff check src/excel_export.py src/excel_export_registry_helpers.py src/excel_workbook_helpers.py src/workbook_gate.py tests/test_excel_export_registry_helpers.py tests/test_excel_enhanced.py tests/test_workbook_gate.py -mypy src/excel_export.py src/excel_export_registry_helpers.py src/excel_workbook_helpers.py src/workbook_gate.py --ignore-missing-imports -``` - -Focused verification for the third pass: - -```bash -python3 -m pytest tests/test_excel_export_registry_helpers.py tests/test_excel_enhanced.py tests/test_workbook_gate.py -q -p no:cacheprovider -ruff check src/excel_export.py src/excel_export_registry_helpers.py src/excel_workbook_helpers.py src/workbook_gate.py tests/test_excel_export_registry_helpers.py tests/test_excel_enhanced.py tests/test_workbook_gate.py -mypy src/excel_export.py src/excel_export_registry_helpers.py src/excel_workbook_helpers.py src/workbook_gate.py --ignore-missing-imports -``` - -Focused verification for the fourth pass: - -```bash -python3 -m pytest tests/test_excel_export_registry_helpers.py tests/test_excel_enhanced.py tests/test_workbook_gate.py -q -p no:cacheprovider -ruff check src/excel_export.py src/excel_export_registry_helpers.py src/excel_workbook_helpers.py src/workbook_gate.py tests/test_excel_export_registry_helpers.py tests/test_excel_enhanced.py tests/test_workbook_gate.py -mypy src/excel_export.py src/excel_export_registry_helpers.py src/excel_workbook_helpers.py src/workbook_gate.py --ignore-missing-imports -``` - -Closeout verification: - -```bash -python3 -m pytest -q -p no:cacheprovider -ruff check src/ tests/ -mypy src/excel_export.py src/excel_export_registry_helpers.py src/excel_workbook_helpers.py src/workbook_gate.py --ignore-missing-imports -python3 -m src --help -python3 -m src.cli --help -make workbook-gate -``` - -The workbook gate's automated checks passed. Manual desktop Excel signoff remains the normal release-only final step. - -## Next Step - -Checkpoint: the obvious low-risk workbook runtime wiring moves are now complete. Pause the workbook/exporter lane unless a future discovery pass finds another clear adapter boundary with strong tests. Do not rewrite sheet rendering or workbook generation in a broad pass. diff --git a/docs/plans/2026-05-10-s1.1-phase1-streaming-catalog.md b/docs/plans/2026-05-10-s1.1-phase1-streaming-catalog.md deleted file mode 100644 index 28443733..00000000 --- a/docs/plans/2026-05-10-s1.1-phase1-streaming-catalog.md +++ /dev/null @@ -1,111 +0,0 @@ -# S1.1 Phase 1: openpyxl write_only Streaming Catalog - -**Date:** 2026-05-10 -**Branch:** feat/arc-f-expansion-roadmap -**Status:** Investigation complete — Phase 1 conclusion reached - ---- - -## Section 1: write_only Fact-Check - -The following claims were verified with a live probe script (`tmp/openpyxl_probe.py`, since deleted) against openpyxl 3.x installed in this project. - -| Claim | Verdict | Evidence | -|---|---|---| -| `Workbook(write_only=True)` creates a write-only workbook | **CONFIRMED** | `type(wb.create_sheet())` returns `WriteOnlyWorksheet` | -| `ws.append(row_iterable)` is the only supported write method | **CONFIRMED** | `.cell()` raises `AttributeError`; `ws["A1"]` raises `TypeError` | -| `merge_cells` not available on write-only sheets | **CONFIRMED** | `AttributeError: 'WriteOnlyWorksheet' object has no attribute 'merge_cells'` | -| `conditional_formatting.add` works on write-only sheets | **CONFIRMED** (surprising) | No exception raised — attribute exists on `WriteOnlyWorksheet` | -| `freeze_panes` works on write-only sheets | **CONFIRMED** (surprising) | Assignment succeeds with no error | -| `.cell(row, col, value)` fails on write-only sheets | **CONFIRMED** | `AttributeError` as expected | -| `ws[index]` / `ws[row][col]` fails on write-only sheets | **CONFIRMED** | `TypeError: 'WriteOnlyWorksheet' object is not subscriptable` | - -### Critical question: can a single Workbook mix write-only and regular sheets? - -**NO — CONFIRMED INCOMPATIBLE.** - -`write_only=True` is a workbook-level flag. `WriteOnlyWorksheet` is a separate class (`openpyxl.worksheet._write_only.WriteOnlyWorksheet`) and the only way to produce one is via `Workbook(write_only=True)`. A `Workbook()` (normal mode) always creates `Worksheet` objects. There is no per-sheet flag. You cannot add a `WriteOnlyWorksheet` to a normal workbook, nor a regular `Worksheet` to a write-only workbook. - -**This is the Phase 1 conclusion.** The workbook pipeline loads a template via `openpyxl.load_workbook` in `excel_template.py`, which returns a normal `Workbook`. Every sheet in this pipeline is a regular `Worksheet`. Switching any sheet to write-only mode would require a full workbook split — separate file generation then zip-merge — which is not worth the complexity. - ---- - -## Section 2: Per-Sheet Catalog - -All 20 CORE_VISIBLE_SHEETS plus key hidden/extra sheets. Blockers: `merge_cells`, `.cell()`, `ws[index]`, `add_table`/`Table()`, `add_chart`, `data_validation`, `DefinedName`. (`freeze_panes` and `conditional_formatting` are NOT blockers per probe results above.) - -| Sheet | Builder Module | Blockers Present | Verdict | Rationale | -|---|---|---|---|---| -| Index | `excel_navigation_helpers` | merge, cell, ws_index | INCOMPATIBLE | Heavy merge layout, cell-by-cell nav grid | -| Dashboard | `excel_dashboard_helpers` | merge, chart, cell, ws_index | INCOMPATIBLE | Charts + merge spans; wb["Dashboard"] back-ref at export time | -| All Repos | `excel_all_repos_helpers` | cf, dv, table, cell | INCOMPATIBLE | conditional_formatting rules, data_validation dropdowns, Table | -| Portfolio Explorer | `excel_portfolio_sheet_helpers` | merge, table, cell, ws_index | INCOMPATIBLE | merge spans + structured Table | -| Portfolio Catalog | `excel_portfolio_sheet_helpers` | merge, table, cell, ws_index | INCOMPATIBLE | same module, same blockers | -| Scorecards | `excel_portfolio_sheet_helpers` | merge, table, cell, ws_index | INCOMPATIBLE | same module | -| Implementation Hotspots | `excel_hotspot_sheet_helpers` | merge, cf, table, cell, ws_index | INCOMPATIBLE | merge + conditional formatting + table | -| Operator Outcomes | `excel_operator_outcomes_helpers` | merge, cell, ws_index | INCOMPATIBLE | merge spans, ws_index writes | -| Approval Ledger | `excel_ledger_sheet_helpers` | merge, table, cell, ws_index | INCOMPATIBLE | merge + table + indexed writes | -| Historical Intelligence | `excel_ledger_sheet_helpers` | merge, table, cell, ws_index | INCOMPATIBLE | same module | -| Repo Detail | `excel_repo_detail_helpers` | merge, dv, cell, ws_index | INCOMPATIBLE | data_validation + indexed writes | -| By Lens | `excel_profile_trend_helpers` | merge, table, cell, ws_index | INCOMPATIBLE | merge + table | -| By Collection | `excel_profile_trend_helpers` | merge, table, cell, ws_index | INCOMPATIBLE | same module | -| Trend Summary | `excel_profile_trend_helpers` | merge, table, cell, ws_index | INCOMPATIBLE | same module | -| Run Changes | `excel_run_changes_helpers` | merge, cell, ws_index | INCOMPATIBLE | merge spans + indexed cell writes | -| Review Queue | `excel_review_queue_helpers` | merge, cell | INCOMPATIBLE | merge spans | -| Campaigns | `excel_campaign_governance_helpers` | merge, table, cell | INCOMPATIBLE | merge + table | -| Governance Controls | `excel_campaign_governance_helpers` | merge, table, cell | INCOMPATIBLE | same module | -| Executive Summary | `excel_executive_summary_helpers` | cell, ws_index | INCOMPATIBLE | indexed cell writes | -| Print Pack | `excel_print_pack_layout_helpers` | cell, ws_index | INCOMPATIBLE | indexed writes; layout helpers use cell() | -| Hidden data sheets | `excel_hidden_sheet_writer` / `excel_hidden_data_rows` | ws_index | INCOMPATIBLE | ws_index access in writer | - -**No sheet is ELIGIBLE or NEEDS-REFACTOR.** Every sheet in the workbook uses at least one of `merge_cells`, `.cell()`, or `ws[index]` — all of which are hard blockers on `WriteOnlyWorksheet`. - ---- - -## Section 3: Recommendation - -| Verdict | Count | -|---|---| -| ELIGIBLE (no changes needed) | 0 | -| NEEDS-REFACTOR (eligible after changes) | 0 | -| INCOMPATIBLE (hard blocker, structural) | 20 visible + ~5 hidden | - -**Do not proceed to Phase 2 of S1.1.** The `write_only=True` approach is unworkable for this codebase for two compounding reasons: - -1. **Mixing is impossible.** A single `Workbook` cannot contain both `WriteOnlyWorksheet` and regular `Worksheet` objects. The pipeline is built on a single shared `wb` passed through 49 helper modules. - -2. **100% of sheets have hard blockers.** Every sheet uses `.cell()`, `ws[index]`, `merge_cells`, `add_table`, or `data_validation` — none of which exist on `WriteOnlyWorksheet`. - -**Recommended pivot: profile-first, then targeted optimization.** - -Before any structural changes, run `cProfile` on a 100-repo workbook generation to identify where the time actually goes. Likely candidates that are safe to address without API changes: -- Column width auto-sizing loops (openpyxl iterates all cells) -- Repeated `get_or_create_sheet` lookups on large worksheets -- `clear_worksheet` calling `delete_rows` + `delete_cols` when a fresh workbook could be used instead -- Deferring style object creation (reuse `NamedStyle` instances across sheets) - -None of these require the write-only migration. They can be applied incrementally to normal `Worksheet` objects and are reversible. - ---- - -## Section 4: Probe Script Outputs - -Script was at `tmp/openpyxl_probe.py` (deleted after use). Key results: - -``` -FAIL: wb[name] works on write-only wb (should not) - → Surprising: key-access works on write-only WB (returns WriteOnlyWorksheet), - but this is not meaningful for mixing since you still can't add regular sheets. -CONFIRMED: freeze_panes works on write-only sheet -merge_cells on write-only sheet: FAILS - AttributeError -.cell() on write-only sheet: FAILS - AttributeError -ws[index] on write-only sheet: FAILS - TypeError: not subscriptable -CONFIRMED: conditional_formatting works on write-only sheet - -KEY: WriteOnlyWorksheet is separate class: openpyxl.worksheet._write_only -KEY: Only way to get WriteOnlyWorksheet is Workbook(write_only=True) -KEY: Regular Workbook(write_only=False) always creates Worksheet objects -KEY: Therefore CANNOT mix write-only and regular sheets in one Workbook -``` - -openpyxl version in use: installed at `/Users/d/.local/lib/python3.14/site-packages/openpyxl/`. diff --git a/docs/plans/2026-05-11-arc-g-draft-readmes.md b/docs/plans/2026-05-11-arc-g-draft-readmes.md deleted file mode 100644 index 525d3a02..00000000 --- a/docs/plans/2026-05-11-arc-g-draft-readmes.md +++ /dev/null @@ -1,136 +0,0 @@ -# Arc G — Sprint 5: Agentic README authoring (`--draft-readmes`) - -**Status:** Sprint 5 / Arc G kickoff. Drafted 2026-05-11, immediately after Arc F merge (`9e0c636`) and `v0.19.0` tag. - -**Why now:** Arc F shipped the semantic index (S3.1), operator preference memory (S3.3), LLM cost guard (S3.5), and the local web UI (S4.1) — all of which compose into the most natural next product step: have the LLM write draft READMEs for repos that need them, route the diff through the approval ledger, and let the operator review/edit/approve in the web UI. - -This is the first item from the Arc F backlog (line 336 of `docs/plans/2026-05-10-arc-f-expansion-roadmap.md`). Sprint 5 ships this end-to-end. Sprint 6+ items (planner agent, tier tracker) remain in backlog. - ---- - -## Scope - -A new flag `audit report --draft-readmes` (also accessible via legacy `audit --draft-readmes`) walks every repo where: - -1. README is **stale** (S1.4 `readme_stale=True` — README touched > 5x older than latest code, code touched < 90 days ago), OR -2. README is **missing** entirely, OR -3. README is **trivially short** (< 200 chars excluding badges/headings), OR -4. Operator explicitly opts the repo in via `--draft-readmes-repo ` (repeatable) - -For each qualifying repo, the LLM authors a draft README using: - -- **Repo metadata** — language, topics, stars, latest release notes, license -- **Top-level file tree** — first two directory levels -- **Semantic neighbors** — `SemanticIndex.find_neighbors(repo, k=3)` from S3.1 supplies stylistic context ("repos in this portfolio look like this") -- **Existing README** if present (the LLM should improve, not rewrite, where possible) -- **Recent commit messages** — last 10 commits, subject lines only - -The output is a **diff packet** (not a wholesale replacement file) recorded in the approval ledger with `action_type="draft-readme"`. Each packet contains: - -```json -{ - "repo_name": "...", - "action_type": "draft-readme", - "current_readme_sha": "...", - "proposed_readme": "...", - "diff_summary": "Added: install, usage, badges sections", - "llm_provider": "anthropic|github-models", - "llm_model": "...", - "llm_cost_usd": 0.0042, - "generated_at": "2026-05-11T...", - "context_repos": ["repo-a", "repo-b", "repo-c"] -} -``` - -Operator review path: - -1. CLI: `audit triage --approval-center --approval-view ready` lists the packets -2. Web UI: `/approvals` page shows each draft with an "expand diff" HTMX action that renders the proposed README beside the current one -3. Approve via existing `approval_request_approve` flow → packet moves to `approved-manual` state -4. Apply via `audit report --apply-readmes` (S4 already wires this for some action types — extend if needed) - ---- - -## Inventory - -| # | Item | Status | Notes | -|---|---|---|---| -| 5.1 | `src/draft_readmes.py` module with `qualify_repos()`, `build_context()`, `generate_draft()` | ✅ Shipped | Pure functions, easy to unit-test | -| 5.2 | Wire `--draft-readmes` flag into `audit report` subcommand + legacy flat path | ✅ Shipped | Use existing approval ledger writer (no schema changes) | -| 5.3 | Preference suppression — skip repos where the operator rejected a draft-readme 3+ times | ✅ Shipped | Reuses S3.3 `operator_prefs.detect_suppressions()` | -| 5.4 | Web UI: `/approvals` shows draft-readme packets with side-by-side diff partial | ✅ Shipped | HTMX partial swap, no JS | -| 5.5 | `audit report --apply-readmes` writes approved drafts back via GitHub Contents API | ✅ Shipped | Reuse existing apply path if present, else add minimal one | -| 5.6 | Cost guard integration — abort batch if cumulative LLM spend > `--max-llm-spend` | ✅ Shipped | S3.5 `CostTracker` already wired into providers | -| 5.7 | Tests + docs + Sprint 5 closeout | ✅ Shipped | Final | - ---- - -## Cross-arc principles inherited - -From Arc F's three durable lessons: - -1. **External-API specs go stale.** When using the GitHub Contents API for README writeback, treat the docs as "as-of-date" and validate against actual response shape. -2. **Module-level state keyed by `id()` is unsafe.** No registration caches keyed by object identity in this sprint. -3. **Subagent worktree branch-base brittleness.** When dispatching, verify the worktree base SHA matches `feat/arc-g-draft-readmes` tip before letting the subagent commit. If the agent reports a test count that doesn't match current, that's the signal of wrong base. - ---- - -## Open questions (resolve at kickoff) - -| Q | Default | Notes | -|---|---|---| -| Should we run all qualifying repos by default, or require explicit opt-in like Arc D's automation? | **Opt-in default.** Operator must pass `--draft-readmes-repo ` or `--draft-readmes-all`. | Safer; matches Arc D's trust-bar discipline. | -| Provider default — Anthropic or GitHub Models? | **Reuse existing `--narrative-provider` default** (Anthropic if API key set, else GitHub Models). | No new resolution logic. | -| Should drafts be diff-format or full-file replacement? | **Full file as proposed_readme, but show diff in UI.** | Easier to author; UI computes diff at render time. | - ---- - -## Exit criteria - -- `audit report --draft-readmes-repo ` generates a draft README and writes a packet to the approval ledger -- `audit triage --approval-center --approval-view ready` lists the packet -- Web UI `/approvals` renders the draft beside the current README -- Approve via existing flow → packet moves to `approved-manual` -- `audit report --apply-readmes` writes the approved draft to GitHub -- All 1469 existing tests still pass; +20-40 new tests for the new module -- Sprint 5 closeout appended to this doc with shipped/stopped/lessons - ---- - -## Sprint 5 closeout (2026-05-11) - -**Shipped (all 7 items):** - -- **5.1 — `src/draft_readmes.py` core module.** `DraftReadmePacket` dataclass; `qualify_repos()` with opt-in / all-qualifying / no-op modes; `build_context()` bundling metadata + tree + neighbors + recent commits; `generate_draft()` taking provider + cost_tracker, catching `BudgetExceededError` and re-raising with repo context; `write_packets_to_ledger()` adapting to the existing warehouse `approval_subject_type` / `subject_key` columns rather than the docstring's `action_type` / `target_context`. Commit `f0fa9c5`. -- **5.2 — `audit report --draft-readmes` CLI wiring.** Three new flags: `--draft-readmes` (opt-in mode), `--draft-readmes-all` (every qualifying repo), `--draft-readmes-repo REPO` (repeatable explicit list). New `_run_draft_readmes_mode()` dispatched above the `serve` check. Resolves provider via the existing `_resolve_provider()` (reuses S1.2). Commit `f0fa9c5`. -- **5.3 — Preference suppression.** `is_suppressed(prefs, action_type="draft-readme", target_context=repo_name)` consulted before each `generate_draft()` call. After the batch, `detect_suppressions(prefs_path, output_path)` refreshes auto-suppressions based on existing rejection counts. No new schema. Commit `f0fa9c5`. -- **5.4 — Web UI draft-readme diff view.** New `GET /approvals/{record_id}/draft-diff` handler returning an HTMX partial (`src/serve/templates/draft_diff.html`) with two-column CSS grid (Current / Proposed). "View diff" HTMX button on each draft-readme row in `approvals.html`. `.draft-diff*` CSS classes in `audit.css` with responsive collapse at 700px. Existing approve/reject handlers worked without modification — they don't inspect `approval_subject_type`. +6 tests. Commit `54c26b2`. -- **5.5 — Ledger-driven writeback.** `--apply-readmes` now reads approved packets from the approval ledger when `--improvements-file` is absent. New `load_approved_drafts()` (skips packets > 30 days), `mark_draft_applied()`, `record_draft_apply_failure()` in `src/draft_readmes.py`. `_run_apply_improvements_mode` rewritten to merge file + ledger sources. Dry-run delegates to `apply_readme_updates(dry_run=True)`. State transitions: successful apply → `applied`; GitHub API failure → stays `approved-manual` with a failure event. +9 tests. Commit `d462eb5`. **Option A** chosen (reuse `--apply-readmes`, no new flag). -- **5.6 — Cost guard wiring.** Delivered inside S5.1-5.3 — `_run_draft_readmes_mode` constructs `CostTracker(budget_usd=args.max_llm_spend, output_path=output_dir)` from `--max-llm-spend`, passes it into `generate_draft()`. `BudgetExceededError` halts the batch with telemetry written for the records that succeeded. Verified via the existing `--max-llm-spend 0.0001` smoke test in the agent's S5.1-5.3 verification. -- **5.7 — This closeout.** - -**Exit criteria verification:** - -- `audit report --draft-readmes-repo ` generates a draft + writes packet: ✅ -- `audit triage --approval-center --approval-view ready` lists the packet: ✅ (existing flow, packets are normal `approval_records` rows) -- Web UI `/approvals` renders the draft side-by-side with current README: ✅ -- Approve via existing flow → state `approved-manual`: ✅ (no handler changes) -- `audit report --apply-readmes` writes approved drafts via GitHub Contents API: ✅ -- 1469 → 1508 tests (+39 net new; spec range was +20-40): ✅ -- Ruff clean: ✅ -- Sprint 5 closeout appended (this section): ✅ - -**Lessons:** - -1. **Schema-as-implemented beats schema-as-specified.** I scoped 5.1 with a docstring schema of `action_type` / `target_context` for the packet. The actual warehouse columns are `approval_subject_type` / `subject_key`. The agent adapted in-place by storing both shapes (DB columns + nested in `details_json`) instead of trying to migrate the schema or rewrite my spec. **Future plans for ledger-adjacent work should `grep src/warehouse.py` for the actual column names before fixing schema in prose.** -2. **Subagent base-SHA verification + cwd discipline both matter.** The base-SHA gate caught the bad-base hazard cleanly (S5.1-5.3 worktree branched correctly from `136e7da`, my opening sanity check confirmed it). But the second hazard surfaced: when I cherry-picked S5.4, my shell cwd silently shifted into the agent's worktree directory after a hook-triggered cwd recovery, so my next "cherry-pick" landed on the wrong branch with no error. Cost: ~5 minutes of confused reflog spelunking. **Action:** prepend every cherry-pick with `cd /Users/d/Projects/GithubRepoAuditor &&` to force the cwd, and verify `git rev-parse HEAD` matches the expected feat-branch tip both before and after. -3. **In-scope creep can be net positive when the agent stays disciplined.** S5.1-5.3's spec said "wire CostTracker" but the agent delivered the full S5.6 budget gate inside the core module: `BudgetExceededError` catch, telemetry write, partial-batch persistence. I didn't have to spawn a separate agent for 5.6. The agent's report flagged the addition explicitly, so the scope decision was visible rather than smuggled. **Heuristic:** when a scope item is genuinely inseparable from another (cost tracking only makes sense inside the call site), let the agent fold them — but require the report to call it out. - -**Plan housekeeping:** - -- Inventory items 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7 all flipped to ⏳ → ✅ in the table at the top of this doc. (Done in this closeout commit.) -- `--apply-readmes-from-ledger` flag was considered (Option B) and rejected — Option A (reuse `--apply-readmes` with auto-detect) is in production. - -**Branch state:** `feat/arc-g-draft-readmes`, 4 commits ahead of `main`. 1508 tests pass. Ruff clean. Not pushed. - -**Next:** PR + merge, then either Sprint 6 (planner agent for campaign authoring — `--plan-campaign "goal"`) or the deferred Tiered maturity + Initiative tracker. Both build on Sprint 5's approval-ledger packet pattern. diff --git a/docs/plans/2026-05-11-s2.0-workbook-profile-findings.md b/docs/plans/2026-05-11-s2.0-workbook-profile-findings.md deleted file mode 100644 index 3124dd1e..00000000 --- a/docs/plans/2026-05-11-s2.0-workbook-profile-findings.md +++ /dev/null @@ -1,147 +0,0 @@ -# S2.0 Workbook Profile Findings - -**Date:** 2026-05-11 -**Sprint:** Arc F Sprint 2.0 - ---- - -## Methodology - -- **Fixture:** `scripts/benchmark_large_portfolio.py` using `fixtures/demo/sample-report.json` replicated to 90 repos (3-template cycle: `GithubRepoAuditor`, `Premise`, `Nocturne` variants, providing heterogeneous security posture, grade distribution, and badge profiles). -- **Profiler:** `cProfile -o /tmp/excel_profile.out` + `pstats`, sorted by cumulative time and self-time, top 30 each. -- **Memory:** `tracemalloc` (stdlib, always available), peak RSS via `resource.getrusage`. -- **Machine:** Python 3.14.3, Darwin 25.4.0, arm64 (Apple Silicon). - ---- - -## Baseline Numbers (pre-optimization) - -| Metric | Value | -|---|---| -| Total benchmark time (90 repos) | 3.507s | -| Workbook build time | 3.201s | -| HTML dashboard build | 0.069s | -| Peak RSS delta during workbook build | ~18.8 MB | -| Total cProfile function calls | 23,655,052 | - -### Top 30 by Cumulative Time (baseline) - -``` - ncalls tottime cumtime filename:lineno(function) - 133028 0.072 2.469 openpyxl/styles/styleable.py:22(__set__) - 172590 0.046 2.361 openpyxl/utils/indexed_list.py:47(add) - 949481/… 1.220 2.223 openpyxl/descriptors/serialisable.py:204(__hash__) - 35511 0.015 2.046 src/excel_styles.py:127(style_data_cell) - 1 0.000 1.628 src/excel_export.py:1169(_build_hidden_data_sheets) - 37 0.012 1.608 src/excel_detail_helpers.py:21(write_hidden_table_sheet) [43ms/call] - 1029244/… 0.128 1.226 {builtins.hash} - 172872 0.032 1.196 openpyxl/utils/indexed_list.py:42(append) - 1 0.000 0.385 openpyxl/workbook/workbook.py:373(save) - 62 0.004 0.237 src/excel_styles.py:134(apply_zebra_stripes) - 5 0.002 0.299 excel_portfolio_sheet_helpers.py:187(write_portfolio_table) - 1 0.000 0.274 src/excel_export.py:679(_build_all_repos) -``` - -### Top 30 by Self-Time (baseline) - -``` - ncalls tottime filename:lineno(function) - 949481/… 1.220 openpyxl/descriptors/serialisable.py:204(__hash__) -6153374/… 0.321 {builtins.isinstance} -6164018/… 0.317 {builtins.getattr} - 5799183 0.305 {list.append} - 1029244/… 0.128 {builtins.hash} - 39332 0.096 openpyxl/cell/_writer.py:89(lxml_write_cell) - 133028 0.072 openpyxl/styles/styleable.py:22(__set__) - 35511 0.015 src/excel_styles.py:127(style_data_cell) -``` - ---- - -## Top 5 CPU Hotspots - -| # | Function | File:Line | Cumulative | Self | Cause | Fix Size | -|---|---|---|---|---|---|---| -| 1 | `style_data_cell` | `src/excel_styles.py:127` | 2.046s | 0.015s | 35,511 calls × 3 style assignments each. Every `cell.font = X` assignment triggers `IndexedList.add()` → `Serialisable.__hash__()` (deeply nested object hash for `Font`, `Border` with 4 `Side` objects). | Single-function quick win | -| 2 | `write_hidden_table_sheet` → `apply_zebra_stripes` | `src/excel_styles.py:134` | 0.237s | 0.004s | 62 calls, each setting `ZEBRA_FILL` per even-row cell on hidden sheets. Same `PatternFill.__hash__` chain as #1, but for fill objects on sheets users never see. | Single-function quick win | -| 3 | `auto_width` on hidden sheets | `src/excel_styles.py:144` | 0.039s | 0.011s | 37 of 74 `auto_width` calls on hidden sheets. Reads up to 150 rows × N cols per sheet to measure string length — wasted on hidden content. | Single-function quick win | -| 4 | `save_workbook` (lxml serialization) | openpyxl internals | 0.385s | — | 77 sheets × XML serialization via lxml. Unavoidable without switching to an alternative serialization path (e.g., xlsxwriter constant_memory, already rejected in S1.1 due to architecture block). | Structural (>1 module, deferred) | -| 5 | `write_portfolio_table` | `src/excel_portfolio_sheet_helpers.py:187` | 0.299s | 0.002s | 5 calls × 60ms = 300ms. Style assignments for visible portfolio tables. Partially addressable by extending NamedStyle pattern, but tables have per-cell semantic coloring overrides that complicate full NamedStyle adoption. | Structural (deferred) | - ---- - -## Top 3 Memory Accumulators - -| # | Location | Allocation | Cause | -|---|---|---|---| -| 1 | `openpyxl/styles/cell_style.py:53` | 4,534 KiB (80,048 objects, avg 58 B) | A `CellStyleArray` entry is created for every styled cell. 80k entries = one per styled cell in the workbook. Structural — reducing styled cells is the only mitigation. | -| 2 | `openpyxl/worksheet/worksheet.py:272` | 4,505 KiB (43,871 objects, avg 105 B) | `Cell` object allocation. One object per cell written. Proportional to sheet density — unavoidable with openpyxl's cell model. | -| 3 | `openpyxl/worksheet/worksheet.py:260` | 4,448 KiB (43,798 objects, avg 104 B) | `_get_cell` path allocating internal cell coordinate/value dictionaries. Mirrors #2 — both are the same cell instantiation path. | - -Peak RSS delta was ~18.8 MB for 90 repos / 77 sheets. Memory is dominated by openpyxl's in-memory model, not our application code. - ---- - -## Quick Wins Implemented - -All three wins are contained within `src/excel_styles.py`, no other module touched, no public interface change. - -### QW-1: NamedStyle for `style_data_cell` - -**What:** Replace 3 per-attribute style assignments (`cell.font`, `cell.border`, `cell.alignment`) with a single `cell.style = "data_left|center|right"` NamedStyle lookup. NamedStyles are registered lazily on first call per workbook via `_ensure_data_named_styles(wb)` (uses `id(wb)` as cache key in module-level `_REGISTERED_WORKBOOKS` dict). - -**Why it works:** openpyxl's `styleable.__set__` calls `IndexedList.add()` which calls `Serialisable.__hash__()` on the style object — expensive for `Border` (4 nested `Side` objects). NamedStyle assignment bypasses this pipeline entirely after registration. - -**Micro-benchmark:** 5.9x speedup on a 3,000-cell test (0.0287s → 0.0049s). -**Macro impact:** workbook build 3.201s → 0.467s (6.8x). - -### QW-2: Skip zebra stripes on hidden sheets - -**What:** Added `if ws.sheet_state == "hidden": return` guard at the top of `apply_zebra_stripes`. - -**Why it works:** `apply_zebra_stripes` was called 62 times, ~37 on hidden sheets. Each `ZEBRA_FILL` per-cell assignment triggers the same `PatternFill.__hash__` chain as QW-1. - -**Macro impact:** workbook build 0.467s → 0.399s (additional 14% reduction). - -### QW-3: Skip `auto_width` scan on hidden sheets - -**What:** Added `if ws.sheet_state == "hidden": return` guard at the top of `auto_width`. - -**Why it works:** 37 of 74 `auto_width` calls scanned hidden sheets (up to 150 rows × N cols to measure string lengths). Column sizing is irrelevant on hidden sheets. - -**Macro impact:** workbook build 0.399s → 0.396s (minor, ~3ms, but eliminates ~37 unnecessary cell reads). - -### Combined result - -| Metric | Baseline | After all 3 QWs | Improvement | -|---|---|---|---| -| Workbook build time | 3.201s | 0.396s | **8.1x faster** | -| Total benchmark time | 3.507s | 0.458s | **7.7x faster** | -| cProfile total calls | 23,655,052 | ~7,000,000 | 70% reduction | -| Peak RSS delta | ~18.8 MB | ~18.8 MB | Unchanged (structural) | - ---- - -## Backlog (Structural Changes Deferred) - -Ordered by estimated ROI: - -1. **NamedStyle for `style_header_row`** (estimated 5-10% additional reduction) — same pattern as QW-1, but header rows are 1 row × N cols per sheet. The openpyxl `HEADER_FILL` + `HEADER_FONT` + `CENTER` + `THIN_BORDER` pattern could use a `"header"` NamedStyle. Low ROI since header rows are tiny vs data rows. `excel_styles.py` only, ~25 lines diff. - -2. **NamedStyle for `write_portfolio_table`** (estimated 10-15% additional reduction) — portfolio tables (5 visible tables, 60ms each) do per-cell style assignments with semantic color overrides (`color_grade_cell`, `color_tier_cell`). Partial NamedStyle adoption is possible for base cells, but overrides mean the fallback can't be eliminated. Touches `excel_portfolio_sheet_helpers.py` + `excel_styles.py`. - -3. **Streaming XML writer for hidden sheets** (estimated 30-50% additional reduction) — the lxml serialization cost (0.36s, ~45% of post-QW build time) scales with total cell count. For hidden sheets specifically, switching to openpyxl's `optimized_write` (write-only mode) could bypass the in-memory cell model entirely. Requires architectural change: hidden sheets would need to be built in a separate pass after the workbook is created. Touches `excel_detail_helpers.py`, `excel_hidden_sheet_writer.py`, `excel_workbook_helpers.py`. - -4. **Reduce hidden sheet count** — 37 hidden sheets for 90 repos is ~0.4 sheets/repo. At 200 repos, this becomes 80+ hidden sheets. Merging related hidden data into fewer sheets (with repo-name column as a discriminator) would reduce per-sheet overhead (Table registration, header row, lxml serialization). Structural change touching `excel_hidden_data_content_helpers.py` + consumers. - ---- - -## Recommendation - -**Stop Excel perf work in Sprint 2, pivot to other Arc F priorities.** - -The 8x speedup achieved in QW-1/2/3 takes 90-repo workbook generation from 3.2s to 0.4s. For a 200-repo real portfolio, extrapolating linearly: the baseline would have been ~7s, now ~0.9s — well within acceptable interactive range. - -The remaining cost (~0.4s at 90 repos) is 45% lxml serialization (structural, hard to move) and 45% openpyxl cell object creation (proportional to cell count, unavoidable in the current model without xlsxwriter — already rejected in S1.1 as architecturally blocked). - -Further Excel perf investment delivers diminishing returns: the next win (NamedStyle for header rows) is <10% improvement, and streaming writers require significant architectural surgery. The real-world bottleneck in a full portfolio run is GitHub API fetch latency (Sprint 2.1 async layer) and per-repo analysis time — not workbook generation. diff --git a/docs/plans/2026-05-12-arc-g-plan-campaign.md b/docs/plans/2026-05-12-arc-g-plan-campaign.md deleted file mode 100644 index 50a24142..00000000 --- a/docs/plans/2026-05-12-arc-g-plan-campaign.md +++ /dev/null @@ -1,144 +0,0 @@ -# Arc G — Sprint 6: Campaign planner agent (`--plan-campaign "goal"`) - -**Status:** Sprint 6 / Arc G. Drafted 2026-05-12, immediately after Sprint 5 (`--draft-readmes`, PR #162) and the post-merge serve bugfix (PR #164). - -**Why now:** Existing campaigns (`security-review`, `promotion-push`, `archive-sweep`, `showcase-publish`, `maintenance-cleanup`) are pre-defined — the operator picks one off a menu. Sprint 6 inverts this: the operator describes a goal in natural language, and the LLM authors a structured campaign of actions across qualifying repos. The packets flow through the existing approval ledger so trust-bar discipline is preserved end-to-end. - -This is item 2 from the Arc F backlog (line 337 of `docs/plans/2026-05-10-arc-f-expansion-roadmap.md`). Composes every prior Arc F+G capability: - -- Semantic index (S3.1) → relevant-repo retrieval for the goal -- Operator preference memory (S3.3) → suppress repeatedly-rejected action types -- LLM cost guard (S3.5) → batch halts on budget exceed -- Approval ledger (Arc D) → packets flow through standard approve/reject/apply -- Web UI (S4.1) → operator reviews the campaign in `/approvals` with action-by-action breakdown -- Writeback path (Arc D + S5.5) → approved actions apply via existing `--writeback-apply` - ---- - -## Scope - -A new flag `audit report --plan-campaign "GOAL"` walks the portfolio: - -1. **Goal parsing** — operator passes a freeform string: e.g. `"archive all dead Tauri experiments older than 1 year"` or `"add MIT LICENSE to every public repo that's missing one"`. -2. **Candidate retrieval** — the planner uses `SemanticIndex.search(goal, k=20)` (when an index exists) plus deterministic filters from the goal's parsed intent (language, age, license, tier) to narrow the candidate set. -3. **Per-repo evaluation** — for each candidate, the LLM is asked "does this repo qualify for the goal, and if so, what specific action should be taken?". Output is a structured `CampaignAction`. -4. **Plan packet** — the full list of `CampaignAction`s plus a goal summary, total cost, and run metadata is written to the approval ledger as a single packet with `approval_subject_type="campaign-plan"`. -5. **Review path** — `/approvals` shows the packet with a per-repo action breakdown (HTMX-expandable, like Sprint 5's draft-diff). Approve/reject is all-or-nothing for v1. -6. **Apply path** — approved packets feed into `--writeback-apply --campaign-from-ledger` which executes each action via the existing pre-built action handlers (archive via GitHub Archive API, README writeback via Contents API, etc.). - -**Important constraint:** action types are NOT free-form. They must map to one of the existing executable actions (`archive`, `unarchive`, `add_license`, `add_topics`, `update_description`, `apply_readme`, `add_codeowners`, `enable_dependabot`). If the LLM proposes anything else, it goes in the packet as `pending_human_action` (no auto-apply path, just a TODO for the operator). - ---- - -## Inventory - -| # | Item | Status | Notes | -|---|---|---|---| -| 6.1 | `src/plan_campaign.py` core module with `parse_goal()`, `narrow_candidates()`, `generate_plan()`, `write_packet()` | ✅ Shipped | Pure functions; mockable provider for tests | -| 6.2 | Wire `--plan-campaign "GOAL"` into `audit report` subcommand + legacy flat path | ✅ Shipped | Reuse `_resolve_provider()` from S1.2 | -| 6.3 | Web UI: `/approvals` shows campaign-plan packets with per-action HTMX-expandable breakdown | ✅ Shipped | Reuses S5.4 partial pattern | -| 6.4 | Apply path: `--writeback-apply --campaign-from-ledger` hook into existing action executors | ✅ Shipped | Each `CampaignAction` dispatched to its handler | -| 6.5 | Tests + Sprint 6 closeout | ✅ Shipped | Final | - ---- - -## Schema - -```python -@dataclass(frozen=True) -class CampaignAction: - repo_name: str - action_type: Literal["archive", "unarchive", "add_license", "add_topics", - "update_description", "apply_readme", "add_codeowners", - "enable_dependabot", "pending_human_action"] - target: str # e.g. license SPDX, topic list, description text, README path - rationale: str # ~1-sentence LLM explanation - expected_impact: str | None = None - -@dataclass(frozen=True) -class CampaignPlanPacket: - goal: str - actions: list[CampaignAction] - candidate_count: int - qualified_count: int - llm_provider: str - llm_model: str - llm_cost_usd: float - generated_at: str -``` - -Packet flows through the approval ledger via: -- `approval_subject_type = "campaign-plan"` -- `subject_key = ` (e.g. first 16 chars of `sha256(goal)`) -- `details_json` contains the full packet - ---- - -## Constraints inherited from prior arcs - -1. **Schema-as-implemented beats schema-as-specified** (S5 Lesson 1). Adapt to whatever `approval_subject_type` / `subject_key` columns the warehouse actually has. -2. **Subagent worktree base-SHA discipline** (Arc F Lesson 3 + S5 Lesson 2). Each subagent verifies base SHA before doing anything. -3. **Boot tests catch CLI wiring bugs** (S4 Lesson 2 + S5 reinforced after PR #164). Every new CLI mode gets a one-line "process boots, exit cleanly on missing inputs" smoke baked into the verification step. -4. **No auto-apply without explicit trust-bar gate.** Approved campaign packets still require `--writeback-apply` to actually execute. No silent application. - ---- - -## Open questions (resolve at kickoff) - -| Q | Default | Notes | -|---|---|---| -| Should approve/reject be all-or-nothing or per-action? | **All-or-nothing for v1.** Per-action review is Sprint 7. | Keep the v1 UI simple; if the plan has 30 actions and you only want 25, you reject and re-run with refinements. | -| Should `--plan-campaign` write a packet even if 0 actions qualified? | **Yes.** Writes packet with `qualified_count=0` so the operator can see what was attempted. | Useful negative signal. | -| Should the LLM be allowed to propose multiple actions per repo? | **One action per repo for v1.** Multi-action repos go in as `pending_human_action`. | Simpler routing; multi-action is Sprint 7. | -| If no semantic index exists, should the planner fall back to scanning all repos? | **Yes, with a `--max-repos N` cap (default 50)** to bound cost. | Same fallback pattern as Sprint 5's `qualify_repos`. | - ---- - -## Exit criteria - -- `audit report --plan-campaign "archive abandoned experiments"` generates a packet and writes it to the ledger -- `audit triage --approval-center --approval-view ready` lists the campaign-plan packet -- Web UI `/approvals` renders the packet with the goal text and per-action expandable list -- Approve via existing flow → packet moves to `approved-manual` -- `audit report --writeback-apply --campaign-from-ledger` executes each `CampaignAction` via the existing action handlers -- All 1508 existing tests still pass; +25-40 new tests -- Sprint 6 closeout appended to this doc -- Boot test included in verification: `audit report --plan-campaign "test" --dry-run someuser` exits cleanly when truth file is missing - ---- - -## Sprint 6 closeout (2026-05-12) - -**Shipped (all 5 items):** - -- **6.1 — `src/plan_campaign.py` core module.** `ACTION_TYPES` frozenset + `CampaignAction` and `CampaignPlanPacket` dataclasses; `narrow_candidates()` (semantic-index-first with alphabetical fallback capped at `max_repos`); `generate_action_for_repo()` (JSON-parsing the LLM response, forcing unknown action types to `pending_human_action`, catching `BudgetExceededError` and re-raising with repo context); `generate_plan()` (walks candidates, respects operator prefs); `write_packet_to_ledger()` (writes via `approval_subject_type="campaign-plan"`, `subject_key=sha256(goal)[:16]`). Followed S5 dataclass-+-pure-functions pattern verbatim. Commit `1ca5c8f`. -- **6.2 — `audit report --plan-campaign "GOAL"` CLI wiring.** Two new flags: `--plan-campaign GOAL` and `--max-repos N` (default 50). Wired into both the new subparser (S4.3) and the legacy flat parser. `_run_plan_campaign_mode()` mirrors `_run_draft_readmes_mode()` — truth load, optional SemanticIndex, provider via `_resolve_provider()` (S1.2), CostTracker from `--max-llm-spend` (S3.5), summary line at the end. Dispatched before `--draft-readmes` in `main()`. Commit `1ca5c8f`. -- **6.3 — Web UI campaign-plan view.** New `GET /approvals/{record_id}/campaign-plan` HTMX partial returning 404 for non-campaign-plan records (S5.4 pattern). New `src/serve/templates/campaign_plan.html` with goal heading, summary line (considered / qualified / pending / cost), and per-action table (Repo / Action / Target / Rationale). `pending_human_action` rows visually de-emphasized via `.campaign-plan-pending` class. "View plan" HTMX button on each campaign-plan row in `approvals.html`. 12 new `.campaign-plan*` CSS classes in `audit.css`. +7 tests. Commit `059b951`. -- **6.4 — Apply path for approved packets.** New `--campaign-from-ledger` flag pairs with `--writeback-apply`. `_run_campaign_from_ledger_mode()` loads approved packets, dispatches each `CampaignAction` via `dispatch_action()`. Existing handlers cover `archive`, `unarchive`, `update_description`, `add_topics`, `apply_readme`. Unimplemented (`add_license`, `add_codeowners`, `enable_dependabot`) and `pending_human_action` return `(False, message)` without penalty. Packet state transitions: `applied` only when no genuinely-supported action failed; mixed-result packets stay `approved-manual` with a failure event. `src/cli_mode_validation.py` carve-out: `--writeback-apply` doesn't require `--writeback-target` when paired with `--campaign-from-ledger`. +20 tests. Commit `565e0ed`. -- **6.5 — This closeout.** - -**Exit criteria verification:** - -- `audit report --plan-campaign "archive abandoned experiments"` writes a packet to the ledger: ✅ -- `audit triage --approval-center --approval-view ready` lists the packet: ✅ (standard ledger flow) -- Web UI `/approvals` renders the packet with goal + per-action breakdown: ✅ -- Approve via existing flow → state `approved-manual`: ✅ (no handler changes needed) -- `audit report --writeback-apply --campaign-from-ledger` executes via existing executors: ✅ -- 1508 → 1561 tests (+53 net new; spec range was +25-40): ✅ (over-delivered) -- Ruff clean: ✅ -- Sprint 6 closeout appended (this section): ✅ -- Boot test included: ✅ (S6.1-6.2 agent confirmed `audit report --plan-campaign "test goal" someuser --output-dir /tmp/empty_test_dir` exits cleanly) - -**Lessons:** - -1. **Subagents will sometimes auto-merge into the feat branch instead of waiting for cherry-pick.** S6.3's worktree branch was merged into `feat/arc-g-plan-campaign` by the agent itself (via its stop hook's reconciliation logic), not by my explicit cherry-pick. Result was equivalent (commit on the right branch), but the workflow surprise cost ~30 seconds of "wait, where did the commit go?" verification. **Action for Sprint 7+:** include an explicit instruction in the subagent prompt — "do not merge into the feat branch; leave your commit on the worktree branch and report the SHA". Or just expect this and check `git log feat/` rather than the worktree branch. -2. **Combining tightly-coupled items into one agent (6.1 + 6.2) avoided the integration friction Sprint 5 had with 5.5.** When the CLI wiring depends on the module's exact public surface, splitting them into separate agents creates a fake API/implementation handshake that costs time. Keep them combined when the interface isn't stable enough to spec ahead of implementation. -3. **`cli_mode_validation.py` carve-out is a repeatable pattern for new mode flags.** S6.4 needed `--writeback-apply` to NOT require `--writeback-target` when `--campaign-from-ledger` is set. The existing validator already had similar carve-outs for other modes (Sprint 5's `--apply-readmes` reading from ledger). Future sprints adding new ledger-driven apply modes will need the same carve-out — worth a 2-line comment in that validator pointing to the pattern. - -**Plan housekeeping:** - -- Inventory items 6.1, 6.2, 6.3, 6.4, 6.5 all flipped to ⏳ → ✅ in the table at the top of this doc. - -**Branch state:** `feat/arc-g-plan-campaign`, 4 commits ahead of `main`. 1561 tests pass. Ruff clean. Not pushed. - -**Next:** PR + merge, then either Sprint 7 (Tiered maturity + Initiative tracker — 4 tiers, deadline-bound initiatives) or pure-play per-action approval refinement of v1 plan-campaign + draft-readmes packets (since both currently approve all-or-nothing). Tier tracker is bigger; per-action approval is the kind of "polish what we just shipped" item that's worth doing while the code is fresh. diff --git a/docs/plans/2026-05-12-arc-g-sprint-10-closeout.md b/docs/plans/2026-05-12-arc-g-sprint-10-closeout.md deleted file mode 100644 index fa5743d4..00000000 --- a/docs/plans/2026-05-12-arc-g-sprint-10-closeout.md +++ /dev/null @@ -1,71 +0,0 @@ -# Arc G — Sprint 10 closeout - -**Status:** SHIPPED 2026-05-12. Sprint 10 ran as planned in `docs/plans/2026-05-12-arc-g-sprint-10-polish.md`. Two parallel subagents, four polish items. - -## Final state - -- Feat branch tip: `457d363` -- Tests: 1819 → **1841 passed** (+22), 2 skipped, ruff clean -- All four Sprint 9 follow-on items closed - -## Inventory - -| # | Item | Commit | Tests | Notes | -|---|---|---|---|---| -| 10.1 | Briefing render smoke test (`--include-suggestions`) | `aaf2873` | +3 | Scope-check confirmed render path was already wired; tests guard against regression. | -| 10.2 | Excel "(approx.)" hint in Initiative Tracker sheet | `aaf2873` | +7 | New `_format_missing_requirements(gap)` helper that consumes `TierGap.requirement_sources` and appends `" (approx.)"` to proxy-derived items. Legacy-empty-sources falls back gracefully. | -| 10.3 | `cache_key` parameter + module-level `_suggestion_cache` dict | `3cfc7b3` | +5 | In-process opt-in cache. Caller controls key cardinality. `clear_suggestion_cache()` exported for tests + invalidation. Route uses `f"{generated_at}\|target={target or 'auto'}"`. | -| 10.4 | `force_deterministic: bool = False` parameter | `3cfc7b3` | +5 | Bypasses LLM entirely; returns `(suggestions, 0.0)`. `accept_suggestion()` switched from `budget_usd=0.0`+broad-except to `force_deterministic=True`. | - -Tests landed: 1819 → 1841 (+22). - -## Boot-test results - -- `GET /initiatives/suggestions` → 200 -- `GET /initiatives` → 200 -- `generate_suggestions([], force_deterministic=True)` → `([], 0.0)` (no exception, no provider call) -- CLI surface check: `audit triage --help` does NOT mention `deterministic` or `force-deterministic` — internal API only, no leak. - -## Subagent dispatch retrospective - -Two Sonnet subagents, parallel Wave 1 (no overlap): - -- Agent A (`a202c160c9e27e0e3`): items 10.1 + 10.2 — touched `tests/test_briefing.py`, `src/excel_initiative_tracker_helpers.py`, `tests/test_excel_initiative_tracker.py`. ~6 min. -- Agent B (`af9f4a919f45aebe8`): items 10.3 + 10.4 — touched `src/suggest_initiatives.py`, `src/serve/routes.py`, `tests/test_suggest_initiatives.py`, `tests/test_initiatives_suggestions_route.py`. ~6 min. - -Both agents stayed inside their worktrees (cwd-discipline preamble continues to hold). - -## Lessons (recurring) - -### Cwd shift after worktree creation hit the LEAD this time - -When Agent B completed and the lead attempted to cherry-pick, the lead's bash session had silently cwd'd INTO Agent B's worktree directory. The cherry-pick ran on the wrong branch (the worktree's branch, where the commit already existed) and reported "empty cherry-pick". Recovery: -- `cd /Users/d/Projects/GithubRepoAuditor` -- `git switch feat/arc-g-sprint-10` (a different branch — `fix/diff-tier-promoted-flag` from a parallel session — had become active) -- Cherry-pick both commits cleanly - -**Action item:** every cherry-pick command should be prefixed with `cd /Users/d/Projects/GithubRepoAuditor && git switch feat/arc-g-sprint- && git cherry-pick ` to guarantee branch + cwd state. Add this as a checklist in the next Sprint plan template. - -### The "leaked!" false alarm - -Initial CLI-surface check used `grep ... | head -3 && echo "leaked!"`. The pipe's exit code reflected `head`, not `grep`, so the conditional was always truthy. Fixed by using `grep -c` and reading the count directly. Trivial bug but a useful reminder that pipelines + `&&` need careful exit-code reasoning. - -## Cross-arc constraint compliance - -- ✅ MUST NOT break 1819 existing tests — went 1819 → 1841, all pass. -- ✅ Cache is opt-in via `cache_key` param — existing callers without it see no behavior change. -- ✅ `force_deterministic` defaults to False — existing callers see no behavior change. -- ✅ Excel hint format matches web template's semantic ("(approx.)" suffix) but rendered as plain text since Excel doesn't render HTML. -- ✅ No new dependencies. - -## Out of scope (next sprint candidates) - -- **Persistent cache** (cross-process / cross-restart) — current cache is in-process only. If operator workflow benefits from surviving restarts, consider an on-disk SQLite-backed cache. -- **Cache eviction** — unbounded today. Cardinality is naturally low (truth `generated_at` changes infrequently), but if it grows past 100 entries, swap to `functools.lru_cache`-style bounded dict. -- **"Reject suggestion" workflow** — operator currently ignores noisy suggestions silently; an explicit `--dismiss-suggestion REPO` flag could suppress a repo from future suggestions (write to `output/dismissed_suggestions.json`). -- **Suggestions in `audit run --briefing` markdown output via stdout** — currently the briefing markdown lands in `output/briefing-*.md` but the CLI doesn't print it inline. Small UX improvement. -- **`(approx.)` hint in the JSON output** — `tier_gap` already carries `requirement_sources`; the JSON serializer of `TierGap` (if any) could expose it as a structured field for external consumers. - -## Next - -Push, open PR #172, merge with merge commit. diff --git a/docs/plans/2026-05-12-arc-g-sprint-10-polish.md b/docs/plans/2026-05-12-arc-g-sprint-10-polish.md deleted file mode 100644 index d679c36c..00000000 --- a/docs/plans/2026-05-12-arc-g-sprint-10-polish.md +++ /dev/null @@ -1,214 +0,0 @@ -# Arc G — Sprint 10: Suggestions polish + briefing render verify + cache - -**Status:** Drafted 2026-05-12 after Sprint 9 shipped (main `536349b`). Sprint 10 / Arc G handles four polish items surfaced in Sprint 9's closeout. - -## Context - -Sprint 9 closed the suggestion → initiative loop end-to-end. Closeout flagged four follow-on items: - -1. **Verify `--briefing --include-suggestions` renders the section** — scope-check found it IS already wired (`src/briefing.py` line 613). Needs a smoke test only. -2. **`(approx.)` hint in Excel "Initiative Tracker" sheet** — Sprint 9 added the hint to web `/initiatives`; Excel sheet still missing it. -3. **Cache `generate_suggestions` per portfolio-truth `generated_at`** — currently every `GET /initiatives/suggestions` hit triggers a fresh LLM call. Browser refresh = repeat cost. -4. **`force_deterministic: bool` parameter on `generate_suggestions`** — replaces the `budget_usd=0.0` workaround in `accept_suggestion()`. - -All four are small. Sprint 10 ships them together as polish. - -## Inventory - -| # | Item | Effort | Status | -|---|---|---|---| -| 10.1 | Smoke test that `audit triage --briefing --include-suggestions` actually renders "## Suggested Initiatives" in markdown | tiny | ⏳ | -| 10.2 | Thread `requirement_sources` into Excel "Initiative Tracker" sheet — render `(approx.)` next to proxy-derived gap requirements | small | ⏳ | -| 10.3 | Cache layer for `generate_suggestions` — keyed by truth `generated_at`, invalidated on schema change | small-medium | ⏳ | -| 10.4 | `force_deterministic: bool = False` parameter on `generate_suggestions` — bypasses LLM call without abusing budget | small | ⏳ | -| 10.5 | Closeout + PR | small | ⏳ | - -**Test count target:** 1819 → ~1850 (+25-35 new tests). - -## Subagent dispatch - -Two Sonnet subagents, parallel (no overlap): - -- **Agent A** — items 10.1 + 10.2. Touches `tests/test_briefing.py` + `src/excel_initiative_tracker_helpers.py` + `tests/test_excel_initiative_tracker.py`. No collision with B. -- **Agent B** — items 10.3 + 10.4. Touches `src/suggest_initiatives.py` + `src/serve/routes.py` + `tests/test_suggest_initiatives.py`. No collision with A. - -Wave 1 (parallel) → closeout. ~10-15 min wall-clock. - -**Brief discipline:** cwd preamble required (Sprint 8 retro lesson). - -## Schema + code references - -### 10.1 — Briefing smoke test - -Add 1-2 tests to `tests/test_briefing.py`: -- Build a briefing with `include_suggestions=True` and a mock provider returning canned suggestions -- Assert `render_markdown(briefing)` output contains `"## Suggested Initiatives"` and at least one bullet entry -- Negative test: with `include_suggestions=False`, the section is omitted - -No code changes needed in `src/briefing.py` — the render path already exists at line 613. - -### 10.2 — Excel "(approx.)" hint - -`src/excel_initiative_tracker_helpers.py` already renders per-row gap text. Find where `missing_requirements` is written to the cell (or wherever the gap content lives). Modify to consume `tier_gap(...).requirement_sources` (parallel-indexed) and append `" (approx.)"` to any requirement where the source is `"proxy"`. - -The data flow: -- The Excel helper currently calls `tier_gap(project, target_tier)` somewhere. Confirm by reading the file. -- Update the formatting code to walk `gap.missing_requirements` AND `gap.requirement_sources` together. - -Tests in `tests/test_excel_initiative_tracker.py`: -- Build a workbook with one initiative whose gap mixes strict + proxy requirements -- Open the workbook, read the relevant cell, assert "(approx.)" appears next to proxy items only - -### 10.3 — Cache for `generate_suggestions` - -New behavior: at module level in `src/suggest_initiatives.py`, add a simple cache: - -```python -_suggestion_cache: dict[str, tuple[list[InitiativeSuggestion], float]] = {} - -def _truth_cache_key(projects: list[dict], target_tier: int | None) -> str: - """Derive a cache key from a portfolio-truth-derived projects list + target_tier. - Use the truth's overall hash if available, else fall back to count+target.""" -``` - -Cache key options (pick the cleanest): -- Hash of sorted `[(p['identity']['display_name'], p['identity']['has_git']) for p in projects]` plus `target_tier`. This invalidates when ANY project's name or git status changes. -- Caller passes an explicit `cache_key: str | None = None`. Route handler passes `truth.get("generated_at")`. Simpler, gives callers control. - -Prefer the **caller-controlled** approach — clean separation of concerns: - -```python -def generate_suggestions( - projects: list[dict], - target_tier: int | None = None, - budget_usd: float = 0.10, - max_missing: int = 3, - cache_key: str | None = None, - force_deterministic: bool = False, # item 10.4 — fold in here -) -> tuple[list[InitiativeSuggestion], float]: - """... - - If cache_key is provided and a cached result exists for that key, return it. - Cache is in-process only (no persistence). Lifetime = process lifetime. - - force_deterministic=True bypasses the LLM entirely and uses _deterministic_rank. - Useful when the caller doesn't care about LLM rationale (e.g. accept_suggestion's - deadline derivation path). - """ -``` - -Cache lookup: -```python -if cache_key is not None and cache_key in _suggestion_cache: - return _suggestion_cache[cache_key] - -# ... do the work ... - -if cache_key is not None: - _suggestion_cache[cache_key] = (suggestions, cost) -return suggestions, cost -``` - -Route in `src/serve/routes.py`: -```python -cache_key = f"{truth.get('generated_at', '')}-target={target or 'auto'}" -suggestions, cost = generate_suggestions( - projects, target_tier=target, budget_usd=0.10, cache_key=cache_key -) -``` - -Cache size: bounded by distinct `(generated_at, target)` combinations. In practice <10. No eviction needed; if it grows past 100 entries, swap to `functools.lru_cache`-style bounded dict (out of scope for v1). - -### 10.4 — `force_deterministic` parameter - -Already covered in the 10.3 signature above. The semantics: - -- `force_deterministic=True` → skip `_resolve_provider()`, skip the LLM call, skip the CostTracker, go straight to `_deterministic_rank(candidates)`. Returns `(suggestions, 0.0)`. -- `force_deterministic=False` (default) → existing behavior. - -Update `accept_suggestion()` to use `force_deterministic=True` instead of `budget_usd=0.0`: - -```python -# Before (Sprint 9): -try: - suggestions, _ = generate_suggestions(projects, target_tier=target, budget_usd=0.0) -except Exception: - pass # fall back to "medium" - -# After (Sprint 10): -suggestions, _ = generate_suggestions(projects, target_tier=target, force_deterministic=True) -``` - -The broad `except Exception` becomes unnecessary. - -Tests for 10.4: -- `generate_suggestions(..., force_deterministic=True)` returns `(suggestions, 0.0)` without an LLM call (mock provider that would raise if called) -- `accept_suggestion()` uses force_deterministic path; verify the mock provider is NOT called - -## Tests target - -| Item | New tests | -|---|---| -| 10.1 | ~2-3 (positive + negative briefing render) | -| 10.2 | ~3-5 (mixed strict/proxy, all-strict, all-proxy) | -| 10.3 | ~5-8 (cache hit, miss, key variance, no-key skip) | -| 10.4 | ~5-8 (force_deterministic skips LLM, returns 0.0 cost, accept_suggestion uses new path) | -| **Total** | **~15-24 new tests** | - -## Exit criteria - -- 10.1: `pytest tests/test_briefing.py -k include_suggestions` shows ≥2 passing tests for positive + negative paths. -- 10.2: Open an Excel workbook with mixed gap sources; the proxy-derived requirements visibly carry "(approx.)" suffix. -- 10.3: A test using a mock provider that raises on second call passes — confirming the cache prevented the second call. -- 10.4: `generate_suggestions(..., force_deterministic=True)` returns `(suggestions, 0.0)` with no LLM call. `accept_suggestion()` no longer has a broad `except Exception` for the budget workaround. -- All exit: 1819 → ~1845+ tests pass; ruff clean. - -## Constraints - -1. MUST NOT break the existing 1819 tests. -2. Cache is in-process only — no persistence, no cross-request mutation guard needed for the single-operator deployment. -3. `force_deterministic` is opt-in; existing callers default to `False` and see no behavior change. -4. `(approx.)` hint format MUST match the web template (`(approx.)` rendered as just `(approx.)` in Excel since Excel doesn't render HTML). Suffix-append the literal `" (approx.)"` to the cell text. -5. No new dependencies. -6. Cwd discipline preamble in every subagent brief. - -## Critical files - -| File | Item(s) | -|---|---| -| `tests/test_briefing.py` | 10.1 | -| `src/excel_initiative_tracker_helpers.py` | 10.2 | -| `tests/test_excel_initiative_tracker.py` | 10.2 | -| `src/suggest_initiatives.py` | 10.3, 10.4 | -| `src/serve/routes.py` | 10.3 (route uses cache_key) | -| `tests/test_suggest_initiatives.py` | 10.3, 10.4 | -| `docs/plans/2026-05-12-arc-g-sprint-10-closeout.md` (new) | 10.5 | - -## Verification - -```bash -cd /Users/d/Projects/GithubRepoAuditor - -# 10.1 -python3 -m pytest tests/test_briefing.py -k suggestions -v -p no:cacheprovider | tail -10 - -# 10.2 — spot-check generated Excel (manual or test fixture) -python3 -m pytest tests/test_excel_initiative_tracker.py -v -p no:cacheprovider | tail -10 - -# 10.3 — cache hit (run a quick benchmark or check via test) -python3 -m pytest tests/test_suggest_initiatives.py -k cache -v -p no:cacheprovider | tail -10 - -# 10.4 — force_deterministic -python3 -m pytest tests/test_suggest_initiatives.py -k deterministic -v -p no:cacheprovider | tail -10 - -# Full suite -python3 -m pytest tests/ -q -p no:cacheprovider 2>&1 | tail -3 -python3 -m ruff check src/ tests/ 2>&1 | tail -3 -``` - -## Out of scope - -- Persistent cache (cross-process / cross-restart) — keep in-process for v1 -- Cache eviction policy — punt until cache grows past ~100 entries -- Briefing route caching (separate concern, lower priority) -- Excel hint styling beyond plain " (approx.)" suffix — keep simple diff --git a/docs/plans/2026-05-12-arc-g-sprint-11-closeout.md b/docs/plans/2026-05-12-arc-g-sprint-11-closeout.md deleted file mode 100644 index 94957058..00000000 --- a/docs/plans/2026-05-12-arc-g-sprint-11-closeout.md +++ /dev/null @@ -1,100 +0,0 @@ -# Arc G — Sprint 11 closeout - -**Status:** SHIPPED 2026-05-12. Sprint 11 ran as planned in `docs/plans/2026-05-12-arc-g-sprint-11-persistence-dismiss.md`. Two sequential Sonnet subagents, four polish items. - -## Final state - -- Feat branch tip: `068c221` -- Tests: 1841 → **1890 passed** (+49), 2 skipped, ruff clean -- All four Sprint 10 follow-on items closed - -## Inventory - -| # | Item | Commit | Tests | Notes | -|---|---|---|---|---| -| 11.1 | Persistent suggestion cache at `output/suggestion-cache.json` | `1c9d0f6` | (shared with 11.2+11.3) | Atomic tmp+rename mirroring `_write_atomic` from operator_prefs. Lazy load via `_loaded_from_disk` set. Schema versioned (`"version": 1`). | -| 11.2 | Bounded eviction via `OrderedDict` + `_CACHE_MAX_SIZE = 100` | `1c9d0f6` | (shared) | FIFO `popitem(last=False)`. Cache hits call `move_to_end()` for LRU semantics. Disk serialization also capped. | -| 11.3 | `TierGap.to_dict()` + `TierGap.from_dict()` | `1c9d0f6` | (shared) | Exposes `requirement_sources` in JSON for external consumers. Round-trip preserves all 4 fields. | -| 11.4 | `--dismiss-suggestion REPO [--reason TEXT]` + `--undo-dismiss` + `--list-dismissed` + web Dismiss button | `068c221` | +28 | Mirrors 11.1's atomic-write pattern. `narrow_candidates` filters dismissed when `dismissed` set arg provided (`generate_suggestions` threads it via `output_dir`). | - -Agent A combined 11.1 + 11.2 + 11.3 (+19 tests) since all touch persistence/serialization in `src/suggest_initiatives.py` + `src/maturity_tiers.py`. Agent B handled 11.4 (+28 tests) sequentially. - -**Tests:** 1841 → 1890 (+49 across 2 commits). - -## Boot-test results - -| Scenario | Result | -|---|---| -| CLI dismiss: `--dismiss-suggestion TestRepo --reason "noise"` | `✗ Dismissed: TestRepo — noise` | -| `--list-dismissed` post-dismiss | Shows TestRepo dismissed 2026-05-12 | -| JSON file format | Versioned, atomic, correct shape | -| `--undo-dismiss TestRepo` | `✓ Restored: TestRepo` | -| `--list-dismissed` after undo | "No dismissed suggestions." | -| `TierGap.to_dict()` round-trip | OK (preserves `requirement_sources`) | -| `GET /initiatives/suggestions` | 200 | -| `POST /initiatives/suggestions/dismiss` (valid) | 200 | -| `POST /initiatives/suggestions/dismiss` (empty repo_name) | 422 (FastAPI Form validation rejects empty string) | - -## Subagent dispatch retrospective - -Two Sonnet subagents, sequential (because both touch `src/suggest_initiatives.py`): - -- **Wave 1 Agent A** (items 11.1 + 11.2 + 11.3) — ~6 min. `pwd` discipline held. -- **Wave 2 Agent B** (item 11.4) — ~10 min. Built on Agent A's persistence pattern. - -Both agents stayed inside their worktrees throughout. Cwd-discipline preamble continues to hold across all sprints since Sprint 8 retro. - -## Lessons - -### `--reason` flag was free - -The brief flagged a concern that `--reason` might collide with another CLI flag. Agent B searched and confirmed no collision — kept the simple name. Reminder that the brief's defensive-naming alternatives should only be used after confirming the collision. - -### `dismissed_at` semantics: refresh on re-dismissal - -Agent B chose to refresh `dismissed_at` when re-dismissing a repo (existing entry removed, new entry appended with current timestamp). This matches operator intent: "I'm telling the system AGAIN to suppress this" → record the new decision time. Alternative (preserve original `dismissed_at`) would have been weird for audit trails. - -### FastAPI `Form(...)` rejects empty strings as 422, not 400 - -The Sprint 11 plan expected empty `repo_name` to return 400 (the route's explicit ValueError → 400 branch). In practice FastAPI's `Form(...)` validation layer rejects empty string before the route body runs, returning 422. Both are "client error" — semantically fine, just a 22-vs-00 mismatch with the plan. No fix needed. - -### Untracked closeout file vanished after worktree cleanup - -When writing the closeout doc, the file was written successfully but disappeared before commit. The most plausible explanation is a hook running during the `git worktree remove -f -f` step that scrubbed untracked files in the main repo. Re-wrote it; future sprints should `git add` the closeout doc immediately after writing rather than between boot tests and commit. - -## Cross-arc constraint compliance - -- ✅ MUST NOT break 1841 existing tests — went 1841 → 1890. -- ✅ Persistence is opt-in via `output_dir` parameter — existing callers without it see no behavior change. -- ✅ Atomic tmp+rename for both `suggestion-cache.json` and `dismissed-suggestions.json`. -- ✅ Both schemas versioned (`"version": 1`). -- ✅ `narrow_candidates` `dismissed` param defaults to `None` — backward-compat. -- ✅ Bounded eviction at 100 entries, FIFO. -- ✅ HTML-escape in web error paths. -- ✅ No new dependencies. - -## Cumulative state (Sprint 7A → 11) - -| Sprint | Main commit | Tests | Headline | -|---|---|---|---| -| 7B | `3b2dcb9` | 1561 → 1586 | Per-action approval for campaign-plan packets | -| 7A | `8eedaa3` | 1586 → 1677 | Tiered maturity + initiative tracker | -| 8 | `5750272` | 1677 → 1779 | setuptools-scm, strict tier signals, LLM suggestions, per-section drafts | -| 9 | `536349b` | 1779 → 1819 | Suggestions → initiative loop closure (CLI + web) | -| 10 | `0412464` | 1819 → 1841 | Polish: briefing test, Excel hint, cache, force_deterministic | -| 11 | (this PR) | 1841 → 1890 | Persistent cache + eviction + dismiss-suggestion + TierGap JSON | - -**Across six sprints: +329 tests, 6 PRs, complete maturity-tier + suggestion + initiative workflow stack with persistence and operator-controlled noise suppression.** - -## Out of scope (Sprint 12 candidates) - -- Auto-expire dismissals after N days (currently permanent until `--undo-dismiss`) -- Persistent dismissal audit trail (currently overwrites in place — no history of past dismiss/undo cycles) -- Web "Undo dismiss" button (operator must `audit triage --undo-dismiss REPO`) -- Cache compression for very old entries beyond the FIFO cap -- Surface dismissals in the briefing markdown ("N suggestions currently dismissed: REPO1, REPO2, ...") -- `tier_gap` JSON output via `audit report` or similar CLI surface (so external tooling can consume the structured data) - -## Next - -Push, open PR #173, merge with merge commit. diff --git a/docs/plans/2026-05-12-arc-g-sprint-11-persistence-dismiss.md b/docs/plans/2026-05-12-arc-g-sprint-11-persistence-dismiss.md deleted file mode 100644 index e9615cb9..00000000 --- a/docs/plans/2026-05-12-arc-g-sprint-11-persistence-dismiss.md +++ /dev/null @@ -1,367 +0,0 @@ -# Arc G — Sprint 11: Persistent cache + cache eviction + dismiss-suggestion + TierGap JSON - -**Status:** Drafted 2026-05-12 after Sprint 10 shipped (main `0412464`). Sprint 11 / Arc G addresses the four follow-on items surfaced in Sprint 10's closeout. - -## Context - -Sprint 10 added an in-process suggestion cache + `force_deterministic` parameter. Two limitations remain: -- Cache evaporates on process restart (every operator session re-pays the LLM cost on first `/initiatives/suggestions` hit). -- Cache is unbounded (cardinality is naturally low, but no safety net). - -Sprint 9 + 10 also surfaced operator workflow gaps: -- No way to suppress noisy/wrong suggestions — the LLM may consistently surface a repo the operator has explicitly decided not to invest in. -- `TierGap.requirement_sources` is consumed by web template and Excel sheet but isn't included in any external JSON output, so downstream tooling can't tell strict from proxy. - -Sprint 11 closes all four. - -## Inventory - -| # | Item | Effort | Depends on | Status | -|---|---|---|---|---| -| 11.1 | Persistent suggestion cache — write `_suggestion_cache` to `output/suggestion-cache.json` on update; load on import | small | none | ⏳ | -| 11.2 | Bounded cache eviction — LRU-style; default max 100 entries, evict oldest when exceeded | small | 11.1 | ⏳ | -| 11.3 | `(approx.)` hint in `TierGap` JSON serialization — add `to_dict()` method to `TierGap` exposing `requirement_sources` | tiny | none | ⏳ | -| 11.4 | `--dismiss-suggestion REPO [--reason TEXT]` CLI flag + dismissal list at `output/dismissed-suggestions.json`; `narrow_candidates()` filters dismissed repos; web "Dismiss" button on suggestion cards | medium | 11.1 (pattern reuse) | ⏳ | -| 11.5 | Sprint 11 closeout + PR | small | 11.1-11.4 | ⏳ | - -**Test count target:** 1841 → ~1885 (+35-50 new tests). - -## Subagent dispatch - -Two Sonnet subagents, sequential (because 11.1 + 11.2 + 11.3 all touch `src/suggest_initiatives.py` or `src/maturity_tiers.py`, and 11.4 builds on the persistence pattern from 11.1). - -- **Wave 1 Agent A** — items 11.1 + 11.2 + 11.3. Persistence + eviction + JSON-serialize the TierGap source list. -- **Wave 2 Agent B** — item 11.4. `--dismiss-suggestion` workflow. Reuses 11.1's atomic-write pattern. - -Closeout (lead): 11.5. - -**Brief discipline:** cwd preamble mandatory (Sprint 8/10 retro: cwd has shifted on the lead twice now during cherry-pick). - -## Schema + code references - -### 11.1 — Persistent suggestion cache - -`src/suggest_initiatives.py` currently has: -```python -_suggestion_cache: dict[str, tuple[list[InitiativeSuggestion], float]] = {} -``` - -Convert to disk-backed. Pattern mirrors `src/operator_prefs.py` (Sprint 3.3) and `src/initiatives.py` (Sprint 7A): - -- File path: `output/suggestion-cache.json`. Path is configurable via `output_dir` parameter; default falls back to `Path("output")`. -- File schema (versioned): - ```json - { - "version": 1, - "entries": [ - { - "cache_key": "2026-05-12T12:34:56Z|target=auto", - "suggestions": [], - "cost_usd": 0.0042, - "stored_at": "2026-05-12T12:35:00Z" - }, - ... - ] - } - ``` -- Atomic tmp+rename write (mirror `_write_atomic` from `src/operator_prefs.py`). -- Load on `generate_suggestions` first cache lookup (lazy, not at import time) so test isolation stays clean. - -`InitiativeSuggestion` (Sprint 8.4) is a frozen dataclass — add `to_dict()` and `from_dict()` methods for JSON serialization. Trivial. - -New API: -```python -def suggestion_cache_path(output_dir: Path) -> Path: ... -def load_suggestion_cache(path: Path) -> dict[str, tuple[list[InitiativeSuggestion], float]]: ... -def save_suggestion_cache(path: Path, cache: dict) -> None: ... -def clear_suggestion_cache(path: Path | None = None) -> None: - """Drop in-memory cache. If path is provided, also delete the file.""" -``` - -Update `generate_suggestions()`: -- Add `output_dir: Path | None = None` parameter (default None = no persistence; mirrors existing `cache_key` opt-in semantics). -- When `output_dir is not None and cache_key is not None`: load cache from disk on first miss, write to disk on every set. -- The in-memory dict is the primary cache. Disk is the warming layer that survives restarts. - -Route in `src/serve/routes.py`: -```python -suggestions, cost = generate_suggestions( - projects, - target_tier=target, - budget_usd=0.10, - cache_key=cache_key, - output_dir=output_dir, # NEW — pass through for persistence -) -``` - -### 11.2 — Bounded cache eviction - -When the in-memory `_suggestion_cache` grows past 100 entries, evict the OLDEST entry by insertion order (not LRU — simpler, and cardinality is bounded by `(generated_at, target)` tuples which churn slowly). - -Use `collections.OrderedDict`: -```python -from collections import OrderedDict -_suggestion_cache: OrderedDict[str, tuple[list[InitiativeSuggestion], float]] = OrderedDict() -_CACHE_MAX_SIZE = 100 - -# On insert: -if cache_key in _suggestion_cache: - _suggestion_cache.move_to_end(cache_key) -_suggestion_cache[cache_key] = (suggestions, cost) -if len(_suggestion_cache) > _CACHE_MAX_SIZE: - _suggestion_cache.popitem(last=False) # FIFO eviction -``` - -The on-disk cache file also has a soft cap — when serializing, only persist the most recent 100 entries. - -### 11.3 — `TierGap.to_dict()` with `requirement_sources` - -`src/maturity_tiers.py` `TierGap` is a frozen dataclass. Add: - -```python -@dataclass(frozen=True) -class TierGap: - current_tier: int - target_tier: int - missing_requirements: list[str] - requirement_sources: list[Literal["strict", "proxy"]] = field(default_factory=list) - - def to_dict(self) -> dict[str, Any]: - """JSON-safe representation including parallel-indexed requirement_sources.""" - return { - "current_tier": self.current_tier, - "target_tier": self.target_tier, - "missing_requirements": list(self.missing_requirements), - "requirement_sources": list(self.requirement_sources), - } -``` - -Also a `from_dict()` classmethod for symmetry (used by 11.1's serialization paths if any TierGap state crosses the cache boundary). - -### 11.4 — `--dismiss-suggestion REPO` flag + dismissal list - -New module data + functions in `src/suggest_initiatives.py` (or new `src/dismissed_suggestions.py` if it grows too large — start in same module, factor out if needed): - -```python -@dataclass(frozen=True) -class DismissedSuggestion: - repo_name: str - reason: str # operator-provided, default "" - dismissed_at: str # ISO timestamp - dismissed_by: str # operator_identity() - -def dismissed_path(output_dir: Path) -> Path: - """output_dir / 'dismissed-suggestions.json'.""" - -def load_dismissed(path: Path) -> list[DismissedSuggestion]: - """Read versioned JSON; missing/malformed → [].""" - -def save_dismissed(path: Path, items: list[DismissedSuggestion]) -> None: - """Atomic tmp+rename write. Schema {"version": 1, "items": [...]}.""" - -def dismiss_suggestion(path: Path, repo_name: str, reason: str = "") -> DismissedSuggestion: - """Add or replace by repo_name (idempotent). Returns the recorded entry.""" - -def undo_dismiss(path: Path, repo_name: str) -> bool: - """Remove dismissal entry for repo_name. Returns True if removed, False if not present.""" -``` - -**Integration with `narrow_candidates`:** -```python -def narrow_candidates( - projects: list[dict], - target_tier: int | None = None, - max_missing: int = 3, - dismissed: set[str] | None = None, # NEW — set of dismissed repo_names -) -> list[tuple[dict, int, TierGap]]: - """... existing logic ... - - If `dismissed` contains a project's repo_name, skip it. - """ -``` - -`generate_suggestions()` accepts an `output_dir` parameter (from 11.1); when set, it loads `dismissed-suggestions.json` and passes the resulting set to `narrow_candidates`. When `output_dir` is None, no dismissal filtering occurs (backward-compatible). - -**CLI flags in `src/cli.py`** (triage subparser + legacy build_parser, mirroring Sprint 9.1's dual registration): -```python -p.add_argument( - "--dismiss-suggestion", - type=str, - default=None, - metavar="REPO", - help="Suppress repo from future LLM-suggested initiatives", -) -p.add_argument( - "--reason", - type=str, - default="", - help="Reason for dismissal (with --dismiss-suggestion)", -) -p.add_argument( - "--undo-dismiss", - type=str, - default=None, - metavar="REPO", - help="Restore a dismissed repo to the suggestion pool", -) -p.add_argument( - "--list-dismissed", - action="store_true", - help="List currently dismissed suggestion repos", -) -``` - -Dispatcher near existing initiative handlers: -```python -if getattr(args, "dismiss_suggestion", None): - _run_dismiss_suggestion_mode(args) - return -if getattr(args, "undo_dismiss", None): - _run_undo_dismiss_mode(args) - return -if getattr(args, "list_dismissed", False): - _run_list_dismissed_mode(args) - return -``` - -**Web "Dismiss" button:** - -In `src/serve/templates/initiatives_suggestions.html` — add a small "Dismiss" link/button next to each suggestion card's Accept form: - -```html -
- - -
-``` - -New POST route in `src/serve/routes.py`: -```python -@router.post("/initiatives/suggestions/dismiss", response_class=HTMLResponse) -async def dismiss_suggestion_route( - request: Request, - repo_name: str = Form(...), - reason: str = Form(""), -) -> HTMLResponse: - """Dismiss a suggestion. Returns HTMX partial.""" - from src.suggest_initiatives import dismiss_suggestion, dismissed_path - - output_dir = _output_dir(request) - try: - entry = dismiss_suggestion(dismissed_path(output_dir), repo_name, reason) - except ValueError as exc: - import html as _html - return HTMLResponse( - f'
Error: {_html.escape(str(exc))}
', - status_code=400, - ) - - import html as _html - return HTMLResponse( - f'
' - f'✗ Dismissed: {_html.escape(entry.repo_name)}. ' - f'Refresh suggestions →' - f'
' - ) -``` - -After dismissing a repo, the operator can refresh `/initiatives/suggestions` and the repo will not reappear (filtered out by `narrow_candidates`). - -## Tests target - -| Item | New tests | -|---|---| -| 11.1 | ~10 (load/save round-trip, missing file, malformed JSON, atomic write, output_dir threading through generate_suggestions) | -| 11.2 | ~5 (cap at 100, FIFO eviction, move_to_end on hit) | -| 11.3 | ~3 (to_dict round-trip, requirement_sources preserved, empty TierGap shape) | -| 11.4 | ~15-20 (dismiss/undo/list CLI happy/error paths, narrow_candidates filters dismissed, web POST happy/error/HTML-escape, dismissed repo doesn't reappear after refresh) | -| **Total** | **~33-38 new tests** | - -## Exit criteria - -- 11.1: `output/suggestion-cache.json` exists after `audit triage --suggest-initiatives` (when run with `--output-dir` pointing to a writable dir). Subsequent runs in a new process read the cache and skip LLM calls for matching `cache_key`. -- 11.2: Cache holding >100 entries (forced via test) evicts oldest entry on each new insert. -- 11.3: `tier_gap(...).to_dict()` returns a dict containing `requirement_sources` list. Round-trips cleanly via `TierGap.from_dict()`. -- 11.4: `audit triage --dismiss-suggestion Wavelength` writes to `output/dismissed-suggestions.json`. Subsequent `--suggest-initiatives` invocation does NOT surface Wavelength. `--undo-dismiss Wavelength` restores it. `--list-dismissed` prints a table. -- 11.4 (web): `POST /initiatives/suggestions/dismiss` with valid `repo_name` returns 200 + dismissal partial. `GET /initiatives/suggestions` after a dismissal does not include the dismissed repo. -- All exit: 1841 → ~1885+ tests; ruff clean. - -## Constraints - -1. MUST NOT break the existing 1841 tests. All persistence changes are additive (file absent → empty list; pre-existing in-memory cache continues to work without `output_dir`). -2. Atomic file writes (tmp + rename) mirror Sprint 7A's pattern in `src/operator_prefs.py`. Never half-write JSON. -3. Cache file schema is versioned (`"version": 1`). Future schema changes are additive. -4. Dismissal is operator-scoped — `set_by = operator_identity()` (Sprint 7A pattern). Single-operator deployment; no cross-user logic needed. -5. Dismissed repos are filtered by `narrow_candidates` ONLY when `output_dir` is passed through. When `output_dir is None`, no filtering — preserves test isolation. -6. Cache + dismiss-list are separate files (`suggestion-cache.json` vs `dismissed-suggestions.json`). Independent invalidation paths. -7. **No CLI surface for `force_deterministic`** (lesson from Sprint 10) — same applies to `output_dir` and any cache-internal parameters. Keep internal-only. -8. Cwd discipline preamble in every subagent brief. - -## Critical files - -| File | Item(s) | -|---|---| -| `src/suggest_initiatives.py` | 11.1, 11.2, 11.4 | -| `src/maturity_tiers.py` | 11.3 | -| `src/cli.py` | 11.4 (CLI flags + dispatchers) | -| `src/serve/routes.py` | 11.1 (output_dir passthrough), 11.4 (dismiss route) | -| `src/serve/templates/initiatives_suggestions.html` | 11.4 (dismiss form) | -| `tests/test_suggest_initiatives.py` | 11.1, 11.2, 11.4 | -| `tests/test_maturity_tiers.py` | 11.3 | -| `tests/test_initiatives_suggestions_route.py` | 11.4 (web dismiss flow) | -| `docs/plans/2026-05-12-arc-g-sprint-11-closeout.md` (new) | 11.5 | - -## Verification - -```bash -cd /Users/d/Projects/GithubRepoAuditor - -# 11.1 — persistent cache round-trip -mkdir -p /tmp/sprint11-test/output -unset ANTHROPIC_API_KEY GITHUB_TOKEN -python3 -m src triage saagpatel --suggest-initiatives --output-dir /tmp/sprint11-test/output 2>&1 | head -5 -ls /tmp/sprint11-test/output/suggestion-cache.json -python3 -c " -import json -d = json.load(open('/tmp/sprint11-test/output/suggestion-cache.json')) -print(f'version={d[\"version\"]}, entries={len(d[\"entries\"])}')" - -# 11.4 — dismiss workflow -python3 -m src triage saagpatel --dismiss-suggestion FakeRepo --reason 'test' --output-dir /tmp/sprint11-test/output -python3 -m src triage saagpatel --list-dismissed --output-dir /tmp/sprint11-test/output -python3 -m src triage saagpatel --undo-dismiss FakeRepo --output-dir /tmp/sprint11-test/output - -# 11.3 — TierGap JSON -python3 -c " -from src.maturity_tiers import TierGap -g = TierGap(current_tier=1, target_tier=2, missing_requirements=['x', 'y'], requirement_sources=['strict', 'proxy']) -print(g.to_dict()) -g2 = TierGap.from_dict(g.to_dict()) -assert g == g2, 'round-trip failed' -print('TierGap round-trip OK')" - -# Web dismiss -python3 -m src serve --port 8765 --host 127.0.0.1 & -SERVER_PID=$! -sleep 4 -curl -s -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1:8765/initiatives/suggestions/dismiss -d "repo_name=Test" -d "reason=noise" -kill $SERVER_PID - -# Full suite -python3 -m pytest tests/ -q -p no:cacheprovider 2>&1 | tail -3 -python3 -m ruff check src/ tests/ 2>&1 | tail -3 -``` - -## Out of scope - -- Persistent dismissal "undo history" (currently overwrites in place — no audit trail) -- Auto-expire dismissals after N days -- Dismiss propagation to `accept_suggestion` (dismissed repos should NOT be accept-able either — but accept_suggestion is called manually; operator decides) -- Web "Undo dismiss" button (operator can `audit triage --undo-dismiss REPO`) -- Cache compression / pruning of very old entries beyond the 100-entry cap diff --git a/docs/plans/2026-05-12-arc-g-sprint-12-closeout.md b/docs/plans/2026-05-12-arc-g-sprint-12-closeout.md deleted file mode 100644 index 5498814e..00000000 --- a/docs/plans/2026-05-12-arc-g-sprint-12-closeout.md +++ /dev/null @@ -1,115 +0,0 @@ -# Arc G — Sprint 12 closeout - -**Status:** SHIPPED 2026-05-12. Sprint 12 ran as planned in `docs/plans/2026-05-12-arc-g-sprint-12-dismissal-polish.md`. Four Sonnet subagents across two waves + a `/code-review` fix-up commit. - -## Final state - -- Feat branch tip: `9095248` -- Tests: 1890 → **1965 passed** (+75), 2 skipped, ruff clean -- All four Sprint 11 follow-on items closed + `/verify` + `/code-review` skills exercised - -## Inventory - -| # | Item | Commit | Tests | Notes | -|---|---|---|---|---| -| 12.2 | `/initiatives/dismissed` web page + Undo + nav link | `78a11de` | +11 | Wave 1 Agent A. Route ordering correct (placed before `/initiatives/{repo_name}/gap`). | -| 12.3 | Briefing "Currently Dismissed" section + `DismissedRepoRow` | `665faf7` | +14 | Wave 1 Agent B. Initial implementation had a stale double-read (Agent B couldn't see Sprint 12.1's `expires_at` field); cleaned up in fix-up. | -| 12.4 | `audit report --tier-gaps [--format json\|markdown]` | `2a62dc1` | +24 | Wave 1 Agent C. 129 real gaps surfaced in boot test against actual portfolio-truth. Initial impl missed `choices=[2,3,4]` on `--tier-gaps-target`; added in fix-up. | -| 12.1 | Auto-expire + `DismissalEvent` audit trail + v1→v2 schema | `791540c` | +26 | Wave 2 Agent D. Backward-compat: v1 files load cleanly into v2 (no migration step required). | -| — | Code-review fix-ups | `9095248` | (no test change; all 1965 still pass) | Three findings from `/code-review`: web expired filter (critical), briefing double-read cleanup (major), `--tier-gaps-target` argparse choices (major). | - -**Tests:** 1890 → 1965 (+75 across 5 commits). - -## Boot-test results - -| Scenario | Result | -|---|---| -| CLI: `--dismiss-suggestion FakeRepo --dismiss-expires-days 7` | `✗ Dismissed: FakeRepo — test (expires 2026-05-19)` | -| CLI: `--dismissal-history` | Prints chronological table with event types | -| CLI: `--expire-dismissals` (no expired entries) | "No dismissals to expire." | -| `audit report --tier-gaps` | Valid JSON with `version: 1`, `generated_at`, `gaps: [...]` | -| `audit report --tier-gaps-target 1` | argparse rejects (now constrained to choices) | -| Briefing `render_markdown` with `dismissed_repos` set | Contains "## Currently Dismissed" section | -| `GET /initiatives/dismissed` | 200, table renders, expired entries filtered out (post-fix-up) | -| `POST /initiatives/dismissed/undo` (nonexistent) | 404 | -| Nav link count for "/initiatives/dismissed" on / | 1 | - -## Skills exercised - -### `/verify` skill (visual) - -Used Playwright MCP to navigate and screenshot: -- `/` (Dashboard) — nav order verified: Dashboard → Runs → Approvals → Initiatives → Suggestions → **Dismissed** → New Run ✓ -- `/initiatives/dismissed` — header, count subtitle, table with all 5 columns (Repo | Dismissed at | Expires at | Reason | Actions), Undo button per row, footer back-links ✓ -- Layout clean, no console errors, no overflow. - -Screenshots saved to: `sprint12-dashboard.png`, `sprint12-initiatives.png`, `sprint12-suggestions.png`, `sprint12-dismissed.png`. - -### `/code-review` skill (pre-merge gate) - -Per `demand-elegance` rule (diff >200 LoC), invoked code-reviewer subagent on the four-commit branch. Findings (paraphrased): - -- **Critical**: `GET /initiatives/dismissed` showed expired entries — the route handler didn't apply the same expiry filter that the briefing path used. Operator could see + Undo entries that should have been hidden. **Fixed in `9095248`.** -- **Major**: `_build_dismissed_repos` in `src/briefing.py` did a redundant double-read of the JSON file. Sprint 12.3 was written before Sprint 12.1 landed, so the helper read the raw JSON to recover `expires_at` even though Sprint 12.1 added it to the dataclass. **Fixed in `9095248`** — now uses `d.expires_at` directly, malformed dates now log a warning instead of silently passing. -- **Major**: `--tier-gaps-target` argparse declaration was missing `choices=[2, 3, 4]` on the subparser (the legacy flat parser had it; subparser didn't). Inconsistent CLI surface. **Fixed in `9095248`.** - -Praise from the reviewer: -- Schema v1→v2 migration correct + tested -- Atomic tmp+rename writes consistent with operator_prefs pattern -- Route ordering for `/initiatives/dismissed` correctly placed before `/initiatives/{repo_name}/gap` -- HTML-escaping in error responses (XSS-safe) -- CLI dual-registration (subparser + legacy `build_parser`) followed for all 6 new flags -- No new dependencies - -## Subagent dispatch retrospective - -Wave 1 (three parallel): 12.2 + 12.3 + 12.4 — all touching different subsystems, ran cleanly in parallel. -Wave 2 (one sequential): 12.1 — touched `src/suggest_initiatives.py` + `src/cli.py` after Wave 1's Agent C had landed CLI changes; no merge conflicts on cherry-pick. - -Total subagent runtime: ~32 min. Wall-clock with interleaved cherry-picks: ~40 min. **+75 tests across 5 commits.** - -All four agents stayed inside their worktrees (cwd-discipline preamble holds across all sprints since Sprint 8 retro). - -## Lessons - -### Sprint 12.3 was written against a stale view of Sprint 12.1 - -Agent B (briefing) ran in parallel with Agent D (schema). Agent B's brief documented that `DismissedSuggestion` had no `expires_at` field and instructed a raw-JSON workaround. But the brief was written before Agent D actually landed Sprint 12.1, and Agent D's `expires_at` addition was already merged when Agent B's cherry-pick happened. Agent B's defensive code became stale immediately. - -**Action item:** when waves are sequential, the LATER wave should refresh its brief from current state, not the brief author's view at planning time. Or — alternative pattern — have Wave 1 NOT reference fields that Wave 2 is adding, and let Wave 2 do the threading itself. - -### Code-review skill caught a critical bug the boot test missed - -Both the boot test and my manual CLI verification used a fresh dismissed-suggestions.json with a permanent entry. The expired-entry rendering bug only surfaces when the file contains an entry whose `expires_at` is strictly less than today. The code-reviewer's review of the diff (not behavior) caught it because the asymmetry between briefing-path (filtering) and route-path (not filtering) was visible on inspection. - -**Pattern reinforced:** the demand-elegance multi-agent review gate is worth its weight. The diff was 11 files; the reviewer flagged 1 critical + 2 majors + 1 minor in a single pass. - -### TaskCompleted hook continues to be ignored - -437 pre-existing mypy errors continue to block task status transitions. Known noise; will be cleaned up in a future hygiene sprint if it ever becomes worth it. - -## Cumulative state (Sprint 7A → 12) - -| Sprint | Main commit | Tests | Headline | -|---|---|---|---| -| 7B | `3b2dcb9` | 1561 → 1586 | Per-action approval (campaign-plan packets) | -| 7A | `8eedaa3` | 1586 → 1677 | Tiered maturity + initiative tracker | -| 8 | `5750272` | 1677 → 1779 | setuptools-scm + strict tier signals + LLM suggestions + per-section drafts | -| 9 | `536349b` | 1779 → 1819 | Suggestions → initiative loop closure | -| 10 | `0412464` | 1819 → 1841 | Polish: briefing test + Excel hint + cache + force_deterministic | -| 11 | `6aaf725` | 1841 → 1890 | Persistent cache + eviction + dismiss-suggestion + TierGap JSON | -| 12 | (this PR) | 1890 → 1965 | Dismissal lifecycle + web Undo + briefing surface + tier-gap JSON export | - -**+404 tests across 7 PRs.** - -## Out of scope (Sprint 13 candidates) - -- Web "Undo dismiss" history view (currently `--dismissal-history` is CLI-only) -- Auto-expire heuristics — opt-in expire-on-portfolio-truth-refresh (currently operator must run `--expire-dismissals` manually) -- Bulk operations: "Dismiss all proxy-only-gap suggestions" via a bulk endpoint -- Cross-link from briefing dismissals to web `/initiatives/dismissed` URL -- Auto-purge old `DismissalEvent` entries beyond N events (event log grows unbounded today) - -## Next - -Push, open PR #174, merge with merge commit. Tag v0.20.0 if releasing. diff --git a/docs/plans/2026-05-12-arc-g-sprint-12-dismissal-polish.md b/docs/plans/2026-05-12-arc-g-sprint-12-dismissal-polish.md deleted file mode 100644 index e63d2e4e..00000000 --- a/docs/plans/2026-05-12-arc-g-sprint-12-dismissal-polish.md +++ /dev/null @@ -1,352 +0,0 @@ -# Arc G — Sprint 12: Dismissal lifecycle + web Undo + briefing surface + tier_gap JSON - -**Status:** Drafted 2026-05-12 after Sprint 11 shipped (main `6aaf725`). Sprint 12 / Arc G closes the dismissal-lifecycle gaps and exposes `tier_gap` data to external tooling. - -## Context - -Sprint 11 landed the dismiss-suggestion workflow but left rough edges: -- Dismissals are permanent until `--undo-dismiss` is run — no auto-expiry, so the operator must remember to revisit them. -- The web UI has Accept + Dismiss buttons on the suggestions page but no way to LIST dismissed entries or UNDO from the browser. -- The briefing (`--briefing`) doesn't surface dismissed repos, so an operator generating their weekly digest has no visibility into what's currently suppressed. -- `tier_gap` data flows through web template and Excel sheet but has no CLI/JSON export surface for external tooling. - -Sprint 12 closes all four loops. - -## Inventory - -| # | Item | Effort | Depends on | Status | -|---|---|---|---|---| -| 12.1 | Auto-expire dismissals after N days + persistent audit trail | medium | none | ⏳ | -| 12.2 | Web `/initiatives/dismissed` page + per-row Undo button + nav link | medium | none | ⏳ | -| 12.3 | Briefing "Currently Dismissed" section | small-medium | none | ⏳ | -| 12.4 | `audit report --tier-gaps [--format json]` — dump per-repo tier gaps + sources for external tooling | medium | none | ⏳ | -| 12.5 | Sprint 12 closeout + PR | small | 12.1-12.4 | ⏳ | - -**Test count target:** 1890 → ~1950 (+50-70 new tests). - -## Subagent dispatch plan - -Two waves to avoid `src/cli.py` conflicts: - -### Wave 1 — three parallel Sonnet subagents - -- **Agent A — Item 12.2** (web Undo + list page). Touches `src/serve/routes.py`, `src/serve/templates/*.html`, `tests/test_initiatives_suggestions_route.py`. -- **Agent B — Item 12.3** (briefing dismissals section). Touches `src/briefing.py`, `tests/test_briefing.py`. -- **Agent C — Item 12.4** (`audit report --tier-gaps`). Touches `src/cli.py` (report subparser only), maybe new `src/tier_gaps_export.py`, `tests/`. - -Agent A + B + C have no file overlap. - -### Wave 2 — single Sonnet subagent - -- **Agent D — Item 12.1** (auto-expire + audit trail). Touches `src/suggest_initiatives.py` + `src/cli.py` (triage subparser). After Agent C lands, Agent D's `cli.py` patch will be against a slightly newer base. Cherry-pick will rebase cleanly because the two patches edit different subparsers (`report` vs `triage`). - -### Closeout (lead) - -- Item 12.5 — closeout doc + PR + merge. Apply `/verify` skill (visual UI check on dismissed page) and `/code-review` skill (mandatory for ~600+ LoC diff). - -**Skills usage:** -- `verify` skill (Playwright MCP) after Wave 1 lands — screenshot `/initiatives/dismissed` page and `/initiatives/suggestions` (now with Undo button on dismissed cards if any persist). -- `code-review` skill (multi-agent gate) before merging the Sprint 12 PR — diff exceeds 200 LoC threshold from demand-elegance rule. - -**Subagent brief preamble:** mandatory cwd discipline preamble in every brief (Sprint 8 / 10 / 11 retro lessons). Each brief also includes the "always `git add` closeout docs immediately" note from Sprint 11 retro. - -## Schema + code references - -### 12.1 — Auto-expire + audit trail - -**Schema additions to `DismissedSuggestion`** in `src/suggest_initiatives.py`: - -```python -@dataclass(frozen=True) -class DismissedSuggestion: - repo_name: str - reason: str - dismissed_at: str - dismissed_by: str - expires_at: str | None = None # NEW — ISO date or None for permanent - -@dataclass(frozen=True) -class DismissalEvent: - """Audit-trail entry for a dismiss/undo/expire action.""" - repo_name: str - event_type: str # "dismissed" | "undone" | "expired" - occurred_at: str # ISO timestamp - actor: str # operator_identity() or "system" (for auto-expire) - reason: str = "" # optional context -``` - -Persistence schema (versioned, additive): - -```json -{ - "version": 2, - "items": [ - {"repo_name": "...", "reason": "...", "dismissed_at": "...", "dismissed_by": "...", "expires_at": null} - ], - "events": [ - {"repo_name": "...", "event_type": "dismissed", "occurred_at": "...", "actor": "...", "reason": "..."} - ] -} -``` - -Sprint 11's v1 schema (no `expires_at`, no `events` array) MUST still load cleanly — pre-Sprint-12 entries default to `expires_at=None` and no events. - -**New functions:** - -```python -def dismiss_suggestion_record( - path: Path, - repo_name: str, - reason: str = "", - expires_days: int | None = None, # NEW -) -> DismissedSuggestion: - """... existing logic ... - If expires_days is set, set expires_at = today + expires_days days (ISO date). - Append a DismissalEvent of type 'dismissed' to events list.""" - -def expire_dismissals(path: Path, today: date | None = None) -> list[DismissedSuggestion]: - """Walk items; remove those whose expires_at < today. - For each expired entry, append a DismissalEvent of type 'expired' with actor='system'. - Save atomically. Return list of expired entries.""" - -def load_dismissal_events(path: Path) -> list[DismissalEvent]: - """Read events array from the file. Missing/old-schema → [].""" -``` - -**CLI flag updates** in `src/cli.py` triage subparser: - -```python -p.add_argument( - "--dismiss-expires-days", - type=int, - default=None, - metavar="N", - help="Auto-expire dismissal after N days (default: permanent)", -) -p.add_argument( - "--expire-dismissals", - action="store_true", - help="Run cleanup: remove dismissals whose expiry date has passed", -) -p.add_argument( - "--dismissal-history", - action="store_true", - help="Show audit trail of dismissal events", -) -``` - -`_run_dismiss_suggestion_mode(args)` passes `expires_days=getattr(args, "dismiss_expires_days", None)`. - -New mode functions: -- `_run_expire_dismissals_mode(args)` — calls `expire_dismissals()`, prints count + list -- `_run_dismissal_history_mode(args)` — calls `load_dismissal_events()`, prints chronological table - -**`load_dismissed` updates** — when reading v1 schema, default missing fields gracefully. When reading v2, read `items` + `events` arrays. `save_dismissed` ALWAYS writes v2 (no operator action needed to migrate). - -### 12.2 — Web `/initiatives/dismissed` page + Undo - -**New GET route** in `src/serve/routes.py`: - -```python -@router.get("/initiatives/dismissed", response_class=HTMLResponse) -async def initiatives_dismissed(request: Request) -> HTMLResponse: - """List currently dismissed suggestions with per-row Undo button.""" - from src.suggest_initiatives import load_dismissed, dismissed_path - - output_dir = _output_dir(request) - items = load_dismissed(dismissed_path(output_dir)) - - rows = [ - { - "repo_name": d.repo_name, - "reason": d.reason, - "dismissed_at": d.dismissed_at, - "dismissed_by": d.dismissed_by, - "expires_at": getattr(d, "expires_at", None), - } - for d in items - ] - - return templates.TemplateResponse( - request, "initiatives_dismissed.html", - {"rows": rows, "count": len(rows)}, - ) -``` - -**New POST route**: - -```python -@router.post("/initiatives/dismissed/undo", response_class=HTMLResponse) -async def undo_dismiss_route( - request: Request, - repo_name: str = Form(...), -) -> HTMLResponse: - """Restore a dismissed repo. HTMX swap-out the row.""" - from src.suggest_initiatives import undo_dismiss, dismissed_path - - output_dir = _output_dir(request) - removed = undo_dismiss(dismissed_path(output_dir), repo_name) - - import html as _html - if removed: - return HTMLResponse( - f'✓ Restored: {_html.escape(repo_name)}' - ) - else: - return HTMLResponse( - f'Error: {_html.escape(repo_name)} not currently dismissed', - status_code=404, - ) -``` - -**New template `src/serve/templates/initiatives_dismissed.html`** modeled after `initiatives.html` (Sprint 7A): - -- Header: "Dismissed Suggestions" + count -- Empty state: "No dismissed suggestions." -- Table columns: REPO | DISMISSED AT | EXPIRES AT | REASON | ACTIONS (Undo button) -- Per-row HTMX Undo button POSTing to `/initiatives/dismissed/undo` with `hx-confirm="Restore {repo_name} to suggestion pool?"` - -**Nav link** in `base.html`: add `
  • Dismissed
  • ` between "Suggestions" and "New Run". - -### 12.3 — Briefing dismissals section - -In `src/briefing.py`: - -1. Add a parameter to `build_briefing(..., output_dir: Path | None = None)` so it can load dismissed-suggestions.json. Mirror Sprint 11 pattern. -2. Add `dismissed_repos: list[str]` field to `Briefing` dataclass (`field(default_factory=list)`). -3. When `output_dir is not None`, load dismissed list via `load_dismissed(dismissed_path(output_dir))` and populate `dismissed_repos = [d.repo_name for d in items if d.expires_at is None or d.expires_at >= today]` (skip auto-expired). -4. In `render_markdown(briefing)`, add a section AFTER the "Suggested Initiatives" section (or near the end if no Suggested Initiatives present): - -```markdown -## Currently Dismissed - -3 repos are currently suppressed from suggestions: -- ToyProject — _too speculative_ -- OldFork — _abandoned_ -- ScratchPad — _no expiry_ -``` - -If `dismissed_repos` is empty, omit the section. - -Wire `output_dir` through `generate_briefing()` and the CLI invocation (in `src/cli.py` `_run_briefing_mode` or equivalent). - -Tests: existing `tests/test_briefing.py`. Add ~5 cases (empty, populated, mixed expiry). - -### 12.4 — `audit report --tier-gaps` - -In `src/cli.py` `_build_report_subparser` (the `audit report` subcommand), add: - -```python -p.add_argument( - "--tier-gaps", - action="store_true", - help="Dump per-repo TierGap data as JSON (use --format markdown for human-readable)", -) -p.add_argument( - "--tier-gaps-target", - type=int, - default=None, - metavar="TIER", - help="Override target tier for gap calculation (default: current+1 per repo)", -) -p.add_argument( - "--format", - choices=["json", "markdown"], - default="json", - help="Output format for --tier-gaps (default: json)", -) -``` - -New mode `_run_tier_gaps_export_mode(args)`: - -1. Load portfolio-truth-latest.json. -2. For each project: `compute_tier(repo)`, then `tier_gap(repo, target_tier)` where target = `args.tier_gaps_target` or `current+1`. -3. Skip repos at current_tier=0 (no git) or current_tier=4 (Platinum, no next tier). -4. Build a JSON/markdown output: - - JSON: `{"version": 1, "generated_at": "...", "gaps": [{"repo_name": ..., "current_tier": ..., "target_tier": ..., "missing_requirements": [...], "requirement_sources": [...]}, ...]}` - - Markdown: a table with one row per repo, columns: REPO | CURRENT → TARGET | MISSING | SOURCE -5. Print to stdout (operator can redirect). - -New module `src/tier_gaps_export.py` if it makes sense (small enough to inline if not). Tests in `tests/test_tier_gaps_export.py` or appended to `tests/test_cli_report.py`. - -## Tests target - -| Item | New tests | -|---|---| -| 12.1 | ~18-22 (expires_days, expire_dismissals, audit-trail event recording, schema migration v1→v2 read, CLI flags) | -| 12.2 | ~10-12 (GET dismissed page empty/populated, POST undo happy/404/HTML-escape, nav link present) | -| 12.3 | ~5-7 (briefing field populated, markdown render with/without dismissals, expired ones excluded) | -| 12.4 | ~10-15 (mode happy path, target override, JSON shape, markdown shape, missing portfolio-truth) | -| **Total** | **~45-55 new tests** | - -## Exit criteria - -- 12.1: `audit triage --dismiss-suggestion REPO --dismiss-expires-days 30` writes an entry with `expires_at` set. `audit triage --expire-dismissals` on a clock past the expiry removes the entry and logs an "expired" event. `audit triage --dismissal-history` prints a chronological table. -- 12.2: `GET /initiatives/dismissed` renders the dismissed table with Undo buttons. `POST /initiatives/dismissed/undo` removes the entry and returns a success row. Subsequent GET shows the entry gone. Nav link visible from any page. -- 12.3: `audit triage --briefing --include-suggestions` produces markdown containing "## Currently Dismissed" section when any non-expired dismissals exist. Section omitted when empty. -- 12.4: `audit report saagpatel --tier-gaps` prints valid JSON. `--format markdown` prints a readable table. Schema is versioned. -- All exit: 1890 → ~1945+ tests pass; ruff clean. - -## Constraints - -1. MUST NOT break the existing 1890 tests. -2. v2 schema must read v1 files cleanly. v1-only callers (briefing path that doesn't use `output_dir`, anything else) keep working. -3. Atomic tmp+rename for all file writes (Sprint 11 pattern). -4. Auto-expire is an explicit operator action (`--expire-dismissals`) NOT auto-run on every CLI invocation. The operator opts in. -5. Briefing dismissals section is opt-out by being part of `include_suggestions=True` path. If operator runs briefing WITHOUT `--include-suggestions`, no dismissals section either. -6. Web Undo path returns HTMX-friendly `` fragments; `hx-swap="outerHTML"` from the calling row. -7. `tier-gaps` export does NOT call the LLM (deterministic dump from portfolio-truth + maturity_tiers). -8. No new dependencies. -9. Cwd discipline preamble + "always `git add` closeout immediately" note in every subagent brief. - -## Critical files - -| File | Item(s) | -|---|---| -| `src/suggest_initiatives.py` | 12.1 | -| `src/cli.py` | 12.1 (triage subparser), 12.4 (report subparser) | -| `src/serve/routes.py` | 12.2 | -| `src/serve/templates/initiatives_dismissed.html` (new) | 12.2 | -| `src/serve/templates/base.html` | 12.2 (nav link) | -| `src/briefing.py` | 12.3 | -| `src/maturity_tiers.py` (read-only) | 12.4 (uses `tier_gap`) | -| `src/tier_gaps_export.py` (new, optional) | 12.4 | -| `tests/test_*.py` | all items | -| `docs/plans/2026-05-12-arc-g-sprint-12-closeout.md` (new) | 12.5 | - -## Verification - -```bash -cd /Users/d/Projects/GithubRepoAuditor - -# 12.1 — auto-expire workflow -mkdir -p /tmp/sprint12-test/output -unset ANTHROPIC_API_KEY GITHUB_TOKEN -python3 -m src triage saagpatel --dismiss-suggestion FakeRepo --dismiss-expires-days 0 --output-dir /tmp/sprint12-test/output -python3 -m src triage saagpatel --expire-dismissals --output-dir /tmp/sprint12-test/output # should expire it -python3 -m src triage saagpatel --dismissal-history --output-dir /tmp/sprint12-test/output - -# 12.2 — web dismissed page -python3 -m src serve --port 8765 --host 127.0.0.1 & -SERVER_PID=$!; sleep 4 -curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8765/initiatives/dismissed # 200 -curl -s http://127.0.0.1:8765/initiatives/dismissed | grep -c "/initiatives/dismissed/undo" # >= 0 -kill $SERVER_PID - -# 12.4 — tier-gaps JSON -python3 -m src report saagpatel --tier-gaps --output-dir output 2>&1 | head -20 - -# Full suite -python3 -m pytest tests/ -q -p no:cacheprovider 2>&1 | tail -3 -python3 -m ruff check src/ tests/ 2>&1 | tail -3 -``` - -Visual verification: `/verify` skill + Playwright MCP after Wave 1 merges. -Pre-merge: `/code-review` skill on the Sprint 12 diff. - -## Out of scope - -- Bulk dismiss / bulk undo from web (low operator value) -- Persistent storage for tier-gap snapshots over time (point-in-time export is enough for v1) -- Dismissed suggestions feeding back into `audit report` portfolio score (no operator demand) -- Auto-expire heuristics based on repo activity (small overlap, premature) diff --git a/docs/plans/2026-05-12-arc-g-sprint-13-closeout.md b/docs/plans/2026-05-12-arc-g-sprint-13-closeout.md deleted file mode 100644 index 05a7932c..00000000 --- a/docs/plans/2026-05-12-arc-g-sprint-13-closeout.md +++ /dev/null @@ -1,128 +0,0 @@ -# Arc G — Sprint 13 closeout - -**Status:** SHIPPED 2026-05-12. Sprint 13 ran as planned in `docs/plans/2026-05-12-arc-g-sprint-13-dismissal-followons.md`. One Haiku scope agent (Wave 1) + three parallel Sonnet subagents (Wave 2) + a `/code-review` fix-up commit + visual `/verify` pass. - -## Final state - -- Feat branch tip: `848c0a0` -- Tests: 1965 → **1997 passed** (+32), 2 skipped, ruff clean -- All four Sprint 12 follow-on items closed - -## Inventory - -| # | Item | Commit | Tests | Notes | -|---|---|---|---|---| -| 13.3 | Briefing cross-link to web pages | `3d6f308` | +5 | Wave 2 Agent C. One-line addition inside `if dismissed_repos:` block. `render_voice` left untouched. | -| 13.1 | `/initiatives/dismissal-history` web view | `8d38340` | +8 | Wave 2 Agent A. Route declared before parametric `/initiatives/{repo_name}/gap`. Event-type CSS allowlist added in fix-up. | -| 13.2 + 13.4 | Bounded event log + cache TTL | `5ddaeb5` | +19 | Wave 2 Agent B. `_MAX_DISMISSAL_EVENTS=1000`, `_CACHE_TTL_DAYS=30`, cache schema v1 → v2 (entries gain `timestamp`). v1 entries dropped on first v2 load with INFO log — operators will see one cold-cache load if upgrading from a pre-Sprint-13 file. | -| — | Code-review fix-ups | `a0f10cb` + `848c0a0` | (no test count change) | Two findings from `/code-review`: trim-cap off-by-one (Important) + CSS class injection vector (Important). Plus a chore commit removing Sprint 12 verify artifacts accidentally swept in. | - -**Tests:** 1965 → 1997 (+32 across 5 commits). - -## Boot-test results - -| Scenario | Result | -|---|---| -| `GET /initiatives/dismissal-history` | 200, empty-state renders cleanly | -| `GET /initiatives/dismissed` | 200, includes new "View dismissal history →" footer link | -| Briefing markdown with `dismissed_repos` set | Contains `/initiatives/dismissed` and `/initiatives/dismissal-history` paths | -| `_MAX_DISMISSAL_EVENTS = 1000` (sentinel-inclusive) | Writing 1005 events ⇒ on-disk size exactly 1000 (999 newest + 1 sentinel) | -| `_CACHE_TTL_DAYS = 30` | v2 entries strictly older than 30 days are dropped on load; file rewritten atomically only when something changed | -| v1 cache file load | Returns empty cache, logs at INFO, next save promotes to v2 | - -## Skills exercised - -### `/code-review` skill (pre-merge gate) - -Per `demand-elegance` rule (diff >200 LoC — Sprint 13 came in at ~1066 insertions across 11 files), invoked code-reviewer subagent on the four-commit branch. Findings (confidence in parentheses): - -- **Important (86)**: `_save_dismissed_full` trim arithmetic produced 1001 entries after each trim, not 1000. The sentinel was appended AFTER keeping the newest `_MAX_DISMISSAL_EVENTS` events, so the file grew by 1 each trim cycle and never settled at the cap. **Fixed in `a0f10cb`** — reserve a slot for the sentinel (`keep_count = _MAX - 1`); tests updated to match the new sentinel-inclusive semantics. -- **Important (82)**: `initiatives_dismissal_history.html` interpolated `row.event_type` directly into a `class="..."` attribute. While today the value originates from application code (not operator input), a corrupted JSON file could break out of the class context (Jinja's default autoescape doesn't cover HTML-attribute injection). **Fixed in `a0f10cb`** — switched to a Jinja allowlist lookup that falls back to `event-unknown` for unrecognized values. Also added the `event-log_trimmed` and `event-unknown` CSS classes. -- **Minor (77, below report threshold)**: No test coverage for the v1-cache-drop migration path. Noted as a small follow-up but not blocking. - -Praise from the reviewer: -- Route ordering correct (`dismissal-history` before `{repo_name}/gap`) -- Atomic tmp+rename pattern consistent across new write paths -- Sprint 12.1 v1→v2 roundtrip preserved (load v1, save v2, items lossless) -- TTL boundary semantics explicit + tested (exactly-TTL kept, TTL+1 dropped) -- Briefing cross-link correctly placed inside `if dismissed_repos:` guard -- XSS clean for text-content fields (Jinja default autoescape handles `repo_name`, `reason`, `actor`) -- No new dependencies -- `render_voice` untouched as specified - -### `/verify` skill (visual) - -Used Playwright MCP to navigate and screenshot: -- `/initiatives/dismissal-history` — empty state: heading "Dismissal History", "0 events recorded" subtitle, "No dismissal events recorded." message, back-links to Dismissed Suggestions + Suggestions ✓ -- `/initiatives/dismissed` — confirms new "View dismissal history →" footer link rendered alongside existing back-links ✓ -- Nav structure intact: Dashboard → Runs → Approvals → Initiatives → Suggestions → Dismissed → New Run ✓ - -Screenshots saved to: `sprint13-dismissal-history.png`, `sprint13-dismissed-with-history-link.png`. - -## Subagent dispatch retrospective - -Wave 1 (one Haiku, ~2 min): scope confirmation flagged ONE material discrepancy before Wave 2 — the persistent cache schema didn't have a `timestamp` field, so 13.4 required a v1 → v2 schema bump rather than a bolt-on TTL. Briefing for Agent B updated accordingly. Without the scope agent, Agent B would have hit this mid-implementation and either added the field silently or asked back, costing a round-trip. - -Wave 2 (three parallel Sonnets, ~7 min wall clock): -- Agent A (13.1): 8 tests, no surprises -- Agent B (13.2 + 13.4): 19 tests, schema v1 → v2 clean -- Agent C (13.3): 5 tests, single-line addition - -All three landed first-try with no merge conflicts on cherry-pick (different files: routes.py + template, suggest_initiatives.py + new tests, briefing.py only). The "Agent B owns both 13.2 + 13.4 to avoid conflicts" decision was correct — both items touch `src/suggest_initiatives.py` extensively. - -Total subagent runtime: ~13 min. Wall-clock with code-review + verify + closeout: ~35 min. - -## Lessons - -### Trim-with-sentinel arithmetic is a known foot-gun - -Sprint 13.2 documented the cap as `_MAX_DISMISSAL_EVENTS` and the spec said "sentinel counts toward the cap going forward", but Agent B's implementation kept `_MAX` events of history THEN appended the sentinel, ending at `_MAX + 1`. The unit test asserted `len == _MAX + 1`, locking in the bug. The code-reviewer caught this on the diff in seconds because the asymmetry between the loop invariant ("we have N+1 after trim") and the constant name (`_MAX = N`) was apparent on inspection — but the test author didn't see it because they wrote the test against the implementation, not against the spec. - -**Action item:** when reserving room for a sentinel/header/footer in a bounded structure, name the visible-to-disk constant accordingly (`_MAX_DISMISSAL_EVENTS` is fine as a sentinel-inclusive cap) AND write at least one test that asserts the on-disk count matches the constant, not `constant + N`. The reviewer's "your test validates the bug" framing was sharp. - -### CSS class injection is not covered by Jinja's default autoescape - -The reviewer flagged that `class="event-{{ row.event_type }}"` is not safe under Jinja's HTML autoescape (which protects text content but doesn't constrain attribute values). The fix (Jinja allowlist lookup) is cheap and reusable. Worth remembering for future templates that interpolate enum-like values into class/id/data-* attributes. - -**Pattern:** for any class/id/data-* attribute that takes its value from operator data (even data we wrote into the file), use an allowlist: -```jinja2 -{% set _allowed = ['a', 'b', 'c'] %} -{% set _class = 'prefix-' ~ value if value in _allowed else 'prefix-unknown' %} -{{ value }} -``` - -### Wave 1 Haiku scope agent earned its 2 minutes - -The cache-schema gap (no `timestamp` field) would have cost a round-trip if Agent B had discovered it mid-implementation. A 2-minute read-only scope sweep BEFORE the parallel write wave is cheap insurance. Pattern worth keeping: use Haiku read-only scope agents whenever a plan was drafted before the latest main state was deeply read. - -### TaskCompleted hook mypy noise persists - -437 pre-existing mypy errors in `tests/test_briefing.py` (NarrativeProvider Protocol vs `**kwargs: Any` mismatch) continue to block task status transitions through the entire Arc G run. Known noise; cleanup is a candidate for a future hygiene sprint. - -## Cumulative state (Sprint 7A → 13) - -| Sprint | Main commit | Tests | Headline | -|---|---|---|---| -| 7B | `3b2dcb9` | 1561 → 1586 | Per-action approval (campaign-plan packets) | -| 7A | `8eedaa3` | 1586 → 1677 | Tiered maturity + initiative tracker | -| 8 | `5750272` | 1677 → 1779 | setuptools-scm + strict tier signals + LLM suggestions + per-section drafts | -| 9 | `536349b` | 1779 → 1819 | Suggestions → initiative loop closure | -| 10 | `0412464` | 1819 → 1841 | Polish: briefing test + Excel hint + cache + force_deterministic | -| 11 | `6aaf725` | 1841 → 1890 | Persistent cache + eviction + dismiss-suggestion + TierGap JSON | -| 12 | `cc86269` | 1890 → 1965 | Dismissal lifecycle + web Undo + briefing surface + tier-gap JSON export | -| 13 | (this PR) | 1965 → 1997 | Dismissal-history web view + bounded event log + cache TTL + briefing cross-link | - -**+436 tests across 8 PRs.** - -## Out of scope (Sprint 14 candidates) - -- Bulk dismiss / bulk undo from web (still low operator value) -- CLI flags for `_MAX_DISMISSAL_EVENTS` and `_CACHE_TTL_DAYS` (no operator demand yet — revisit if asked) -- Auto-expire heuristics tied to repo activity -- v1-cache-drop test coverage (the Minor finding under threshold — Sprint 14 hygiene if anyone touches that code) -- Tier-gap snapshots over time (persistent point-in-time export) -- Dismissal event search / filtering on the new history page - -## Next - -Push, open PR, merge with merge commit. No tag — Sprint 13 is a follow-on polish PR, doesn't warrant a version bump. diff --git a/docs/plans/2026-05-12-arc-g-sprint-13-dismissal-followons.md b/docs/plans/2026-05-12-arc-g-sprint-13-dismissal-followons.md deleted file mode 100644 index 7b64aee6..00000000 --- a/docs/plans/2026-05-12-arc-g-sprint-13-dismissal-followons.md +++ /dev/null @@ -1,210 +0,0 @@ -# Arc G — Sprint 13: Dismissal follow-ons + suggestion cache TTL - -**Status:** Drafted 2026-05-12 after Sprint 12 shipped (main `cc86269`). Sprint 13 closes the four follow-on items deferred from Sprint 12 closeout. - -## Context - -Sprint 12 landed dismissal lifecycle (auto-expire + audit trail), web Undo, briefing surface, and tier-gap JSON export. Closeout flagged four follow-ons: - -- **13.1** Web `/initiatives/dismissal-history` view — `--dismissal-history` is CLI-only today; web parity helps operators who live in the dashboard. -- **13.2** Auto-purge old `DismissalEvent` entries beyond bounded log size — Sprint 12.1 introduced events but the log grows unbounded. -- **13.3** Briefing → web cross-link for the dismissals section — operators reading the briefing markdown have no path to the web Undo page. -- **13.4** Persistent suggestion cache TTL — Sprint 11.1 cache survives across runs indefinitely; cache entries older than N days should be evicted. - -Skip bulk-dismiss (low operator value, premature mass action). - -## Inventory - -| # | Item | Effort | Depends on | Status | -|---|---|---|---|---| -| 13.1 | Web `/initiatives/dismissal-history` view (CLI parity) | medium | none | ⏳ | -| 13.2 | Auto-purge old `DismissalEvent` entries beyond bounded log size | small-medium | none | ⏳ | -| 13.3 | Briefing → web cross-link for dismissals section | small | none | ⏳ | -| 13.4 | Persistent suggestion cache TTL | small-medium | none | ⏳ | -| 13.5 | Sprint 13 closeout + PR | small | 13.1-13.4 | ⏳ | - -**Test count target:** 1965 → ~2010 (+30-45 new tests). - -## Subagent dispatch plan - -### Wave 1 — one Haiku subagent (read-only) - -- **Agent S — scope confirmation.** Greps for: `DismissalEvent` persistence path in `src/suggest_initiatives.py`, current cache TTL/purge surface, `/initiatives/*` route ordering in `src/serve/routes.py`, briefing dismissals render block in `src/briefing.py`. Reports code locations + any surprises that would change the Wave 2 dispatch. - -### Wave 2 — three parallel Sonnet subagents (isolation: worktree) - -- **Agent A — Item 13.1** (web history view). Touches `src/serve/routes.py`, new `src/serve/templates/initiatives_dismissal_history.html`, `src/serve/templates/base.html` (nav). Tests in `tests/test_initiatives_dismissed_route.py` or similar. -- **Agent B — Items 13.2 + 13.4** (both modify `src/suggest_initiatives.py`). Bounded event log via `_MAX_DISMISSAL_EVENTS` constant + auto-trim on save. Cache TTL via new `_CACHE_TTL_DAYS` constant + eviction on load. Tests in `tests/test_suggest_initiatives.py`. -- **Agent C — Item 13.3** (briefing cross-link). Touches `src/briefing.py` only. Add web URL line to the "Currently Dismissed" markdown section. Tests in `tests/test_briefing.py`. - -Agent A + B + C have minimal file overlap (only `tests/` collisions, and those are separate test files). - -### Closeout (lead) - -- Item 13.5 — closeout doc + PR + merge. Apply `/verify` skill (Playwright MCP) for `/initiatives/dismissal-history` and `/code-review` skill (mandatory for ~400+ LoC diff). - -## Schema + code references - -### 13.1 — Web `/initiatives/dismissal-history` - -**New GET route** in `src/serve/routes.py`: - -```python -@router.get("/initiatives/dismissal-history", response_class=HTMLResponse) -async def initiatives_dismissal_history(request: Request) -> HTMLResponse: - """Show chronological audit trail of dismiss/undo/expire events (Arc G S13.1).""" - from src.suggest_initiatives import dismissed_path, load_dismissal_events - output_dir = _output_dir(request) - events = load_dismissal_events(dismissed_path(output_dir)) - # Newest first - rows = sorted( - ({"repo_name": e.repo_name, "event_type": e.event_type, - "occurred_at": e.occurred_at, "actor": e.actor, "reason": e.reason} - for e in events), - key=lambda r: r["occurred_at"], reverse=True, - ) - return templates.TemplateResponse( - request, "initiatives_dismissal_history.html", - {"rows": rows, "count": len(rows)}, - ) -``` - -Route MUST be declared BEFORE the parametric `/initiatives/{repo_name}/gap` route (Sprint 12.2 lesson). - -**New template `src/serve/templates/initiatives_dismissal_history.html`** modeled after `initiatives_dismissed.html`: -- Header: "Dismissal History" + count -- Empty state: "No dismissal events recorded." -- Table columns: REPO | EVENT | OCCURRED AT | ACTOR | REASON -- Back-links to `/initiatives/dismissed` and `/initiatives/suggestions` - -**Nav link** in `base.html`: optional — discuss with operator preference. Default: link from `/initiatives/dismissed` page footer rather than top-nav (top-nav already crowded). - -### 13.2 — Auto-purge old `DismissalEvent` entries - -In `src/suggest_initiatives.py`: - -```python -_MAX_DISMISSAL_EVENTS = 1000 # cap log size; oldest events trimmed first -``` - -`save_dismissed(path, items, events)` (or whatever helper writes the v2 schema): before serializing, if `len(events) > _MAX_DISMISSAL_EVENTS`, keep only the most recent N events (sort by `occurred_at`, take tail). Add a one-line audit event `{"event_type": "log_trimmed", "occurred_at": now(), "actor": "system", "reason": f"trimmed to {_MAX_DISMISSAL_EVENTS} events"}` so operators see when truncation happened — but trim BEFORE adding the log-trimmed event to avoid recursion. - -Tests: -- `_MAX_DISMISSAL_EVENTS + 5` events written → exactly `_MAX_DISMISSAL_EVENTS` events persisted + one `log_trimmed` event recorded. -- Trim preserves chronological newest-first; oldest 5 are gone. - -### 13.3 — Briefing → web cross-link - -In `src/briefing.py` `render_markdown(briefing)`, the existing "## Currently Dismissed" section. After the bullet list, add: - -```markdown -_See [web view] for Undo or [history view] for audit trail._ -``` - -Concrete URLs: `http://127.0.0.1:8765/initiatives/dismissed` and `http://127.0.0.1:8765/initiatives/dismissal-history` (using the default serve host/port — operators running on a different port will know to substitute). - -Alternative: skip absolute URLs and just write `/initiatives/dismissed` and `/initiatives/dismissal-history` as paths. Cleaner for operators who reverse-proxy the dashboard. **Choose this — paths only.** - -Update `render_voice` similarly if applicable, or leave voice format alone (it's terser by design). - -### 13.4 — Persistent suggestion cache TTL - -In `src/suggest_initiatives.py`, the persistent cache (Sprint 11.1) stores `{"version": 1, "entries": {cache_key: {"timestamp": ISO8601, "suggestions": [...]}, ...}}` in `output/suggestion-cache.json`. - -```python -_CACHE_TTL_DAYS = 30 # entries older than this are evicted on load -``` - -In `load_suggestion_cache(path)` (or wherever the persistent cache is read): after loading, walk entries, drop any whose `timestamp` is older than `today - _CACHE_TTL_DAYS`. If anything was evicted, save the trimmed cache back atomically. - -Alternative: lazy TTL check on `lookup` only. **Reject — purge on load keeps the file size bounded without requiring lookup pressure.** - -Tests: -- Cache with 5 fresh entries + 3 stale (35-day-old) entries → load returns 5 fresh, file rewritten with 5 fresh. -- All entries stale → load returns empty cache, file rewritten as empty. -- All fresh → no rewrite (avoid unnecessary disk write). - -## Tests target - -| Item | New tests | -|---|---| -| 13.1 | ~8-10 (route happy path, empty state, route ordering, template renders all event types) | -| 13.2 | ~6-8 (trim at boundary, log_trimmed event recorded, chronological preservation) | -| 13.3 | ~3-5 (markdown contains cross-link, paths not absolute URLs) | -| 13.4 | ~8-10 (TTL eviction on load, all-stale, all-fresh no-rewrite, file rewrite atomic) | -| **Total** | **~30-45 new tests** | - -## Exit criteria - -- 13.1: `GET /initiatives/dismissal-history` returns 200 with chronological event table. Empty when no events. Route declared before parametric routes. -- 13.2: After writing > `_MAX_DISMISSAL_EVENTS` events, file contains exactly that many + one `log_trimmed` event. Oldest events dropped. -- 13.3: `audit triage --briefing --include-suggestions` markdown contains `/initiatives/dismissed` and `/initiatives/dismissal-history` paths inside the "## Currently Dismissed" section. -- 13.4: `output/suggestion-cache.json` entries older than 30 days are evicted on next CLI invocation that loads the cache. File rewritten atomically. -- All exit: 1965 → ~2000+ tests pass; ruff clean. - -## Constraints - -1. MUST NOT break the existing 1965 tests. -2. Schema additions are additive — no breaking changes to `dismissed-suggestions.json` v2 or `suggestion-cache.json` v1. -3. Atomic tmp+rename for all file writes. -4. Auto-trim and auto-purge are silent on the happy path. No CLI output unless operator runs verbose mode. -5. Web Dismissal History route is read-only — no mutation buttons. Operators undo via `/initiatives/dismissed` (already shipped Sprint 12). -6. Briefing cross-link uses paths (not absolute URLs) — reverse-proxy compatibility. -7. Cache TTL = 30 days, event log cap = 1000. Both are constants in `src/suggest_initiatives.py`, not CLI-configurable in v1. -8. No new dependencies. -9. Cwd discipline preamble + "always `git add` closeout immediately" note in every subagent brief. -10. Route ordering: new `/initiatives/dismissal-history` BEFORE `/initiatives/{repo_name}/gap`. -11. Parallel-wave coordination: Agent B owns both 13.2 + 13.4 to avoid `src/suggest_initiatives.py` conflicts. - -## Critical files - -| File | Item(s) | -|---|---| -| `src/serve/routes.py` | 13.1 | -| `src/serve/templates/initiatives_dismissal_history.html` (new) | 13.1 | -| `src/serve/templates/initiatives_dismissed.html` | 13.1 (back-link from existing page) | -| `src/suggest_initiatives.py` | 13.2, 13.4 | -| `src/briefing.py` | 13.3 | -| `tests/test_*.py` | all items | -| `docs/plans/2026-05-12-arc-g-sprint-13-closeout.md` (new) | 13.5 | - -## Verification - -```bash -cd /Users/d/Projects/GithubRepoAuditor - -# 13.1 — web dismissal history page -python3 -m src serve --port 8765 --host 127.0.0.1 & -SERVER_PID=$!; sleep 4 -curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8765/initiatives/dismissal-history # 200 -kill $SERVER_PID - -# 13.2 — bounded event log -python3 -c " -from pathlib import Path -from src.suggest_initiatives import _MAX_DISMISSAL_EVENTS -print(f'cap = {_MAX_DISMISSAL_EVENTS}') -" - -# 13.3 — briefing cross-link -python3 -m src triage saagpatel --briefing --include-suggestions --output-dir output 2>&1 | grep -A2 "Currently Dismissed" - -# 13.4 — cache TTL -python3 -c " -from src.suggest_initiatives import _CACHE_TTL_DAYS -print(f'ttl_days = {_CACHE_TTL_DAYS}') -" - -# Full suite -python3 -m pytest tests/ -q -p no:cacheprovider 2>&1 | tail -3 -python3 -m ruff check src/ tests/ 2>&1 | tail -3 -``` - -Pre-merge: `/code-review` skill on Sprint 13 diff. `/verify` skill (Playwright MCP) for new page. - -## Out of scope - -- Bulk dismiss / bulk undo from web -- CLI flags for `_MAX_DISMISSAL_EVENTS` and `_CACHE_TTL_DAYS` (constants for v1; revisit if operators ask) -- Auto-expire heuristics based on repo activity -- Persistent storage for tier-gap snapshots over time diff --git a/docs/plans/2026-05-12-arc-g-sprint-7a-maturity-tiers.md b/docs/plans/2026-05-12-arc-g-sprint-7a-maturity-tiers.md deleted file mode 100644 index d6ae5c90..00000000 --- a/docs/plans/2026-05-12-arc-g-sprint-7a-maturity-tiers.md +++ /dev/null @@ -1,201 +0,0 @@ -# Arc G — Sprint 7A: Tiered maturity + Initiative tracker - -**Status:** Sprint 7A / Arc G. Drafted 2026-05-12. Ships immediately after Sprint 7B in the same session per operator instruction. - -**Why now:** Item 4 from the Arc F backlog. The portfolio has 100+ repos but operator has no formal way to commit to "I will get X to a higher quality bar by Y date". Sprint 7A introduces a 4-tier maturity model derived from existing analyzer output, plus a thin layer for deadline-bound initiatives. - ---- - -## Scope - -A formal 4-tier maturity model (Bronze / Silver / Gold / Platinum) computed from existing analyzer scores, plus an initiative tracker that lets the operator commit to a target tier by a deadline. - -### Tier definitions - -| Tier | Bar | Typical criteria | -|---|---|---| -| **Bronze (T1)** | Working code, no formal hygiene | Has README (any), at least one commit, public | -| **Silver (T2)** | Documented and tested | README ≥ 200 chars, has tests, has CI workflow, ≤ 365 days since last commit | -| **Gold (T3)** | Production-grade | All of T2 plus: shipped release, security-alerts clean (no high/critical), license present, README staleness ≤ 5x | -| **Platinum (T4)** | Mission-critical, maintained | All of T3 plus: ≤ 90 days since last commit, ≥ 2 releases in last 365 days, no abandoned dep flags | - -Tier criteria are deterministic — they map to analyzer fields already in the audit pipeline. No LLM involvement. - -### Initiative tracker - -- Operator commits to a target tier + deadline for a repo: `audit triage --set-initiative REPO --target-tier 3 --deadline 2026-06-15` -- Initiative status (derived): `on-track` if current tier == target OR delta is closing AND deadline > 14d, `at-risk` if delta is unchanged AND deadline ≤ 14d, `overdue` if deadline passed AND not met -- Briefing (S3.2) surfaces initiatives in their own top section -- Excel adds an "Initiative Tracker" sheet -- Web UI: `/initiatives` page with progress bars (current dimension scores vs target tier's bar) - ---- - -## Inventory - -| # | Item | Status | Notes | -|---|---|---|---| -| 7A.1 | `src/maturity_tiers.py` — `TierCriteria` dataclass, `compute_tier(repo)` returning 1-4, `tier_gap(repo, target)` returning per-criterion deltas | ⏳ | Pure function; reads existing analyzer fields | -| 7A.2 | `src/initiatives.py` — `Initiative` dataclass, `output/initiatives.json` persistence (atomic tmp+rename), `derive_status(initiative, repo)` returning {on-track, at-risk, overdue, met} | ⏳ | Pattern: `src/operator_prefs.py` | -| 7A.3 | CLI: `audit triage --set-initiative REPO --target-tier N --deadline YYYY-MM-DD`, `audit triage --initiatives` (list), `audit triage --close-initiative REPO` | ⏳ | Validate target-tier > current-tier | -| 7A.4 | Excel: new "Initiative Tracker" sheet with on-track / at-risk / overdue swimlanes, per-criterion gap visualization | ⏳ | openpyxl, reuse styling helpers | -| 7A.5 | Briefing integration (S3.2): top section "Initiatives this week" with status counts + per-initiative one-liner | ⏳ | Additive to existing briefing | -| 7A.6 | Web UI: `/initiatives` route + template; per-initiative progress bar per criterion; "View tier gap" HTMX partial showing which T3 bars aren't yet met | ⏳ | Reuses S4.1 + S6.3 partial pattern | -| 7A.7 | Tests + Sprint 7A closeout | ⏳ | Final | - ---- - -## Subagent dispatch plan - -Three subagents, parallel where possible: - -1. **Agent 1 — Core (7A.1 + 7A.2 + 7A.3)** — module + persistence + CLI. Foundation. Must land before others. -2. **Agent 2 — Excel + briefing (7A.4 + 7A.5)** — depends on Agent 1's CLI persistence. Pure read-side surfaces. -3. **Agent 3 — Web UI (7A.6)** — depends on Agent 1's persistence. Independent of Agent 2. - -Sequential: 1 → (2 ∥ 3 in parallel) → closeout. - -Estimated effort: 3-4 days. ~40-60 new tests. - ---- - -## Schema - -```python -@dataclass(frozen=True) -class TierCriteria: - tier: int # 1-4 - name: str # "Bronze" | "Silver" | "Gold" | "Platinum" - requirements: list[str] # human-readable bullets ("README ≥ 200 chars") - -@dataclass(frozen=True) -class TierGap: - current_tier: int - target_tier: int - missing_requirements: list[str] # what blocks the target tier today - -@dataclass(frozen=True) -class Initiative: - repo_name: str - target_tier: int - deadline: str # ISO date - set_at: str # ISO timestamp - set_by: str # operator identity (default: $USER or "operator") - closed_at: str | None # set when --close-initiative - closed_reason: str | None # "met" | "abandoned" | "deadline-extended" -``` - -Persistence in `output/initiatives.json`: - -```json -{ - "version": 1, - "initiatives": [ - { - "repo_name": "Wavelength", - "target_tier": 3, - "deadline": "2026-06-15", - "set_at": "2026-05-12T...", - "set_by": "operator", - "closed_at": null, - "closed_reason": null - } - ] -} -``` - -Atomic tmp+rename writes, same pattern as `src/operator_prefs.py` (S3.3). - ---- - -## Exit criteria - -- `audit triage --set-initiative Wavelength --target-tier 3 --deadline 2026-06-15` writes the initiative to `output/initiatives.json` -- `audit triage --initiatives` prints a status table for all open initiatives -- `audit triage --close-initiative Wavelength` marks the initiative `closed_at` + `closed_reason` -- Excel "Initiative Tracker" sheet renders with on-track / at-risk / overdue swimlanes -- `--briefing` includes the initiatives section -- Web UI `/initiatives` page shows progress bars -- 1585 → ~1640 tests (+40-60 new) -- Boot test: `audit triage --initiatives` with no `initiatives.json` exits cleanly with empty list -- Sprint 7A closeout appended - ---- - -## Constraints - -1. **MUST NOT break existing tests.** -2. **Tier criteria are deterministic** — derived from analyzer output already in `portfolio-truth-latest.json` or warehouse. No LLM calls. (Sprint 8 could add LLM-suggested initiatives, but not 7A.) -3. **`initiatives.json` schema is versioned.** v1 means missing fields default to safe values; future versions can extend additively. -4. **Tier upgrades require deliberate work — they don't auto-close.** Even if a repo hits the target tier organically (e.g. operator pushed a release), the initiative stays open until `--close-initiative` or until the deadline passes. This prevents silent "completion" claims. -5. **Subagent base-SHA discipline + cwd hygiene** as established. -6. **Briefing addition (7A.5) preserves existing briefing shape** — add a section, don't reshape. - ---- - -## Open question (resolve at kickoff) - -| Q | Default | Notes | -|---|---|---| -| When an initiative passes its deadline without meeting target tier, auto-mark `overdue` (visible flag) or auto-close with `reason=overdue`? | **Auto-mark `overdue` only; require explicit close.** | Forces the operator to decide: extend deadline, abandon, or admit the work is done. | -| Should Bronze (T1) be the implicit default for every repo, or do we require an initiative to "be in T1"? | **T1 is implicit; T1 has no initiatives.** Initiatives only target T2+. | Bronze means "exists and works enough"; you don't commit to staying Bronze. | -| Where does the operator identity (`set_by`) come from? | `$USER` env var, fallback to `"operator"`. | Same pattern as approval-ledger reviewer (Arc D). | - ---- - -## Closeout — 2026-05-12 - -**Status:** SHIPPED. Three Sonnet subagents on isolated worktrees, sequential→parallel pattern: - -| Agent | Items | Worktree commit | Cherry-picked as | New tests | -|---|---|---|---|---| -| Agent 1 (foundation) | 7A.1 + 7A.2 + 7A.3 | `b362b7f` | `16e5537` | +57 | -| Agent 2 (Excel + briefing) | 7A.4 + 7A.5 | `1433fdf` | `00b8c49` | +21 | -| Agent 3 (web UI) | 7A.6 | `a3b9b04` | `ddc98bc` | +13 | - -**Test count:** 1586 → 1677 (+91 across Sprint 7A; +25 in Sprint 7B before it). Ruff clean. - -**Inventory final:** - -| # | Item | Status | Notes | -|---|---|---|---| -| 7A.1 | `src/maturity_tiers.py` — `TierCriteria`/`TierGap` dataclasses, `compute_tier`, `tier_gap`, `tier_name`, `TIER_DEFINITIONS` | ✅ | Returns 0 for no-git repos, 1-4 for Bronze→Platinum | -| 7A.2 | `src/initiatives.py` — `Initiative` dataclass, atomic JSON persistence (`{"version": 1, ...}`), `derive_status` | ✅ | Pattern mirrors `src/operator_prefs.py` | -| 7A.3 | CLI: `audit triage --set-initiative REPO --target-tier N --deadline YYYY-MM-DD`, `--initiatives`, `--close-initiative REPO` | ✅ | Validates target > current tier | -| 7A.4 | Excel "Initiative Tracker" sheet via `src/excel_initiative_tracker_helpers.py` | ✅ | On-track / at-risk / overdue / met swimlanes | -| 7A.5 | Briefing top section "Initiatives this week" with status counts + per-initiative one-liner | ✅ | Additive — existing sections unchanged | -| 7A.6 | Web UI `/initiatives` page + `/initiatives/{repo_name}/gap?target=N` HTMX partial | ✅ | Nav link added between Approvals and New Run | -| 7A.7 | Tests + closeout | ✅ | Final | - -**Tier criteria degraded for v1** (portfolio-truth doesn't carry strict signals — proxies in module docstring + UI labels): - -| Strict criterion | Proxy used | Where | -|---|---|---| -| README ≥ 200 chars | `context_quality != "boilerplate"` | Silver+ | -| Has tests | `run_instructions_present == True` | Silver+ | -| Has CI workflow | `run_instructions_present AND risk.doctor_gap == False` | Silver+ | -| Shipped release | `context_quality in ("strong","operating","shipped")` | Gold+ | -| README staleness ≤ 5x | `activity_status != "stale"` | Gold+ | -| ≥ 2 releases / 365d | `activity_status == "active" AND Gold-level context_quality` | Platinum | - -Sprint 8 (or a future maturity-tier expansion) could thread the strict signals from the raw audit JSON into portfolio-truth, then tighten these checks. - -**Boot tests:** - -- `python3 -m src triage saagpatel --initiatives` → exits cleanly, prints empty table (no `initiatives.json` yet). ✅ -- `GET /` → 200; `GET /initiatives` → 200; `GET /approvals` → 200; `GET /initiatives/Nonexistent/gap?target=3` → 404. ✅ - -**Notes / deviations:** - -- Agent 1's `compute_tier` returns `0` (not `1`) for repos without `identity.has_git`. The plan called for "Bronze = exists and works enough"; treating no-git repos as "Untracked" is a strict improvement. `tier_name(0) == "Untracked"`. -- Agent 2's Excel helper integrates cleanly into the existing registry-builder pattern (no shortcuts needed). -- Agent 3 added a small inline `