codetour-cli 0.1.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. codetour_cli-0.3.0/PKG-INFO +138 -0
  2. codetour_cli-0.3.0/README.md +99 -0
  3. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/pyproject.toml +10 -1
  4. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/cli.py +142 -6
  5. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/config.py +14 -0
  6. codetour_cli-0.3.0/src/codetour_cli/mcp_server.py +279 -0
  7. codetour_cli-0.3.0/src/codetour_cli.egg-info/PKG-INFO +138 -0
  8. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli.egg-info/SOURCES.txt +3 -0
  9. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli.egg-info/entry_points.txt +1 -0
  10. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli.egg-info/requires.txt +3 -0
  11. codetour_cli-0.3.0/tests/test_mcp_server.py +253 -0
  12. codetour_cli-0.3.0/tests/test_review_workflow_options.py +170 -0
  13. codetour_cli-0.1.0/PKG-INFO +0 -47
  14. codetour_cli-0.1.0/README.md +0 -10
  15. codetour_cli-0.1.0/src/codetour_cli.egg-info/PKG-INFO +0 -47
  16. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/LICENSE +0 -0
  17. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/setup.cfg +0 -0
  18. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/__init__.py +0 -0
  19. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/description_validator.py +0 -0
  20. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/detection.py +0 -0
  21. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/discovery.py +0 -0
  22. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/lint.py +0 -0
  23. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/logging/__init__.py +0 -0
  24. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/logging/setup.py +0 -0
  25. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/migration/__init__.py +0 -0
  26. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/migration/direct.py +0 -0
  27. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/migration/git_diff.py +0 -0
  28. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/migration/stepwise.py +0 -0
  29. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/review_parser.py +0 -0
  30. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/schema_models.py +0 -0
  31. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/tour/__init__.py +0 -0
  32. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/tour/schema.py +0 -0
  33. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli/tour/updater.py +0 -0
  34. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli.egg-info/dependency_links.txt +0 -0
  35. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/src/codetour_cli.egg-info/top_level.txt +0 -0
  36. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_advanced_scenarios.py +0 -0
  37. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_apply_review_surgical_integration.py +0 -0
  38. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_description_validation.py +0 -0
  39. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_detection.py +0 -0
  40. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_direct_migration.py +0 -0
  41. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_fuzzy_repo_calibration.py +0 -0
  42. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_git_diff_parser.py +0 -0
  43. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_harness.py +0 -0
  44. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_integration.py +0 -0
  45. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_lint.py +0 -0
  46. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_lint_cli.py +0 -0
  47. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_logging_setup.py +0 -0
  48. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_pattern_validation.py +0 -0
  49. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_post_migration_lint.py +0 -0
  50. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_review_roundtrip.py +0 -0
  51. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_scenarios.py +0 -0
  52. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_schema_validation.py +0 -0
  53. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_stepwise_migration.py +0 -0
  54. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_synthetic_repo_migration_engine.py +0 -0
  55. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_templates.py +0 -0
  56. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_tour_schema.py +0 -0
  57. {codetour_cli-0.1.0 → codetour_cli-0.3.0}/tests/test_tour_updater.py +0 -0
@@ -0,0 +1,138 @@
1
+ Metadata-Version: 2.4
2
+ Name: codetour-cli
3
+ Version: 0.3.0
4
+ Summary: Algorithmic maintenance of CodeTour files — an unofficial companion CLI for the CodeTour VS Code extension
5
+ Author: Jérémie Lumbroso
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/jlumbroso/codetour-cli
8
+ Project-URL: Repository, https://github.com/jlumbroso/codetour-cli
9
+ Keywords: codetour,code-tour,vscode,git,cli
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Topic :: Software Development :: Version Control :: Git
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: gitpython
22
+ Requires-Dist: pydantic>=2.0
23
+ Requires-Dist: structlog
24
+ Requires-Dist: rich
25
+ Requires-Dist: python-dotenv
26
+ Requires-Dist: pyyaml
27
+ Requires-Dist: click>=8.0
28
+ Requires-Dist: jinja2>=3.0
29
+ Requires-Dist: json-source-edit<2,>=1.0
30
+ Provides-Extra: mcp
31
+ Requires-Dist: mcp>=1.2; extra == "mcp"
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest; extra == "dev"
34
+ Requires-Dist: pytest-cov; extra == "dev"
35
+ Requires-Dist: black; extra == "dev"
36
+ Requires-Dist: ruff; extra == "dev"
37
+ Requires-Dist: mypy; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # CodeTour CLI
41
+
42
+ [![Tests](https://github.com/jlumbroso/codetour-cli/actions/workflows/test.yml/badge.svg)](https://github.com/jlumbroso/codetour-cli/actions/workflows/test.yml)
43
+ [![Coverage ≥60%](https://img.shields.io/badge/coverage-%E2%89%A560%25-yellowgreen)](https://github.com/jlumbroso/codetour-cli/actions/workflows/test.yml)
44
+ [![PyPI](https://img.shields.io/pypi/v/codetour-cli)](https://pypi.org/project/codetour-cli/)
45
+ [![Python versions](https://img.shields.io/pypi/pyversions/codetour-cli)](https://pypi.org/project/codetour-cli/)
46
+ [![Downloads](https://static.pepy.tech/badge/codetour-cli)](https://pepy.tech/projects/codetour-cli)
47
+ [![License](https://img.shields.io/github/license/jlumbroso/codetour-cli)](https://github.com/jlumbroso/codetour-cli/blob/main/LICENSE)
48
+
49
+ **Algorithmic maintenance of [CodeTour](https://github.com/microsoft/codetour) files: keep `.tour` walkthroughs accurate as the code they narrate evolves.**
50
+
51
+ > **Unofficial companion tool.** `codetour-cli` is an independent,
52
+ > unofficial companion to [CodeTour](https://github.com/microsoft/codetour),
53
+ > the VS Code extension by Jonathan Carter (Microsoft). It is not
54
+ > affiliated with, endorsed by, or maintained by Microsoft or the CodeTour
55
+ > project. It exists to *serve* CodeTour and its users — keeping `.tour`
56
+ > files accurate as the code they walk through evolves.
57
+
58
+ ---
59
+
60
+ ## The problem
61
+
62
+ A CodeTour step pinned to `src/auth.py:42` is correct the day it's written. Twenty commits later, line 42 is something else entirely — and every tour in the repository is silently lying. Tours are the best onboarding artifact a codebase can have, *if* someone keeps them true. Nobody keeps them true by hand.
63
+
64
+ `codetour-cli` tracks each step across the commits between a tour's pinned `ref` and `HEAD` (git hunk mapping plus content heuristics), applies high-confidence line updates automatically with **byte-preserving surgical edits** (your diff shows the step change, not a rewritten file — via [`json-source-edit`](https://pypi.org/project/json-source-edit/)), and routes everything it isn't sure about to a review report designed to be worked by humans *and* AI agents alike.
65
+
66
+ ## Install
67
+
68
+ ```bash
69
+ pip install codetour-cli # Python ≥ 3.10
70
+ ```
71
+
72
+ ## Quickstart
73
+
74
+ ```bash
75
+ codetour-cli status # which tours lag HEAD?
76
+ codetour-cli migrate --dry-run # preview: what would move, what needs review
77
+ codetour-cli migrate # apply confident updates; write review report
78
+ codetour-cli lint # ground-truth check tours against the workspace
79
+ ```
80
+
81
+ ## The maintenance loop
82
+
83
+ ```
84
+ author tour ──► lint ──► commit, pin `ref`
85
+ ▲ │
86
+ │ ...code evolves...
87
+ │ │
88
+ │ status (tour lags HEAD?)
89
+ │ │
90
+ apply-review ◄── review report ◄── migrate [--dry-run]
91
+ (checked steps (auto-applies confident updates;
92
+ written back) low-confidence → the report)
93
+ ```
94
+
95
+ Steps the migration can't confidently place land in `MIGRATION-REVIEW-{tour}-{commit}.md`: each entry carries the step's original description (its *intent*), old → new location, why confidence dropped, and the actual code now at the proposed location. Check the boxes you approve — or correct the locations inline — then:
96
+
97
+ ```bash
98
+ codetour-cli apply-review MIGRATION-REVIEW-mytour-abc12345.md
99
+ ```
100
+
101
+ **The report is deliberately dual-audience**: an AI agent can read it, judge each proposed location against the step's stated intent, mark the checkboxes, and apply — the same workflow, no human bottleneck for the easy calls. (A companion [Claude skill](https://github.com/jlumbroso/codetour-skill/releases) teaches agents both tour authoring and this maintenance loop.)
102
+
103
+ ## Commands
104
+
105
+ | Command | Purpose |
106
+ |---|---|
107
+ | `init` | Set up `.codetour-cli.yml` configuration |
108
+ | `status` | Health of every tour: current vs. lagging HEAD |
109
+ | `check [tour]` | Validate tour structure without migrating |
110
+ | `lint [paths]` | Step-level checks against the workspace: missing files, out-of-range lines, non-matching or ambiguous patterns, broken `nextTour` links (`--format json`, `--strict`) |
111
+ | `migrate [tour]` | Track steps to HEAD; apply confident updates; report the rest |
112
+ | `apply-review <report>` | Write checked corrections back to the tour file |
113
+ | `undo [tour]` | Restore from the `.tour.backup` files migrate creates |
114
+
115
+ **Flags worth knowing** (`migrate`): `--threshold X` — the auto-apply confidence bar (note: confidence takes 5 exact values, not a smooth dial; see `docs/adr/0016-*.md`); `--clean-reviews` — drop stale review reports from earlier target commits; `--context-lines N` — code context in reports (default 7, config `review.context_lines`). (`apply-review`): `--auto-approve-above X` — also apply *unchecked* steps at or above that confidence; steps marked for deletion are **never** auto-approved.
116
+
117
+ ## MCP server
118
+
119
+ For agents on MCP-capable surfaces, the same operations are exposed as structured tools:
120
+
121
+ ```bash
122
+ pip install "codetour-cli[mcp]"
123
+ codetour-mcp # stdio MCP server
124
+ ```
125
+
126
+ Five tools — `tour_status`, `lint_tours`, `migrate_tour` (defaults to dry-run; a model-facing tool must not mutate by default), `read_review_report`, `apply_review` — each a thin wrapper calling the exact functions the CLI verbs call, with identical semantics (including: deletion-marked steps are never auto-approved). Register it e.g. in `.mcp.json`:
127
+
128
+ ```json
129
+ { "mcpServers": { "codetour": { "command": "codetour-mcp" } } }
130
+ ```
131
+
132
+ ## Design record
133
+
134
+ This tool is developed with the [ADRs4AI](https://github.com/jlumbroso) methodology — every architectural decision, including the ones that were later reversed, lives in `docs/adr/` as a first-class deliberation record: the byte-preserving editing contract, the confidence quantization study, the review-workflow design, and the retirement of ideas that eight months of shipped reality outvoted.
135
+
136
+ ## License
137
+
138
+ [MIT](LICENSE) — like CodeTour itself.
@@ -0,0 +1,99 @@
1
+ # CodeTour CLI
2
+
3
+ [![Tests](https://github.com/jlumbroso/codetour-cli/actions/workflows/test.yml/badge.svg)](https://github.com/jlumbroso/codetour-cli/actions/workflows/test.yml)
4
+ [![Coverage ≥60%](https://img.shields.io/badge/coverage-%E2%89%A560%25-yellowgreen)](https://github.com/jlumbroso/codetour-cli/actions/workflows/test.yml)
5
+ [![PyPI](https://img.shields.io/pypi/v/codetour-cli)](https://pypi.org/project/codetour-cli/)
6
+ [![Python versions](https://img.shields.io/pypi/pyversions/codetour-cli)](https://pypi.org/project/codetour-cli/)
7
+ [![Downloads](https://static.pepy.tech/badge/codetour-cli)](https://pepy.tech/projects/codetour-cli)
8
+ [![License](https://img.shields.io/github/license/jlumbroso/codetour-cli)](https://github.com/jlumbroso/codetour-cli/blob/main/LICENSE)
9
+
10
+ **Algorithmic maintenance of [CodeTour](https://github.com/microsoft/codetour) files: keep `.tour` walkthroughs accurate as the code they narrate evolves.**
11
+
12
+ > **Unofficial companion tool.** `codetour-cli` is an independent,
13
+ > unofficial companion to [CodeTour](https://github.com/microsoft/codetour),
14
+ > the VS Code extension by Jonathan Carter (Microsoft). It is not
15
+ > affiliated with, endorsed by, or maintained by Microsoft or the CodeTour
16
+ > project. It exists to *serve* CodeTour and its users — keeping `.tour`
17
+ > files accurate as the code they walk through evolves.
18
+
19
+ ---
20
+
21
+ ## The problem
22
+
23
+ A CodeTour step pinned to `src/auth.py:42` is correct the day it's written. Twenty commits later, line 42 is something else entirely — and every tour in the repository is silently lying. Tours are the best onboarding artifact a codebase can have, *if* someone keeps them true. Nobody keeps them true by hand.
24
+
25
+ `codetour-cli` tracks each step across the commits between a tour's pinned `ref` and `HEAD` (git hunk mapping plus content heuristics), applies high-confidence line updates automatically with **byte-preserving surgical edits** (your diff shows the step change, not a rewritten file — via [`json-source-edit`](https://pypi.org/project/json-source-edit/)), and routes everything it isn't sure about to a review report designed to be worked by humans *and* AI agents alike.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install codetour-cli # Python ≥ 3.10
31
+ ```
32
+
33
+ ## Quickstart
34
+
35
+ ```bash
36
+ codetour-cli status # which tours lag HEAD?
37
+ codetour-cli migrate --dry-run # preview: what would move, what needs review
38
+ codetour-cli migrate # apply confident updates; write review report
39
+ codetour-cli lint # ground-truth check tours against the workspace
40
+ ```
41
+
42
+ ## The maintenance loop
43
+
44
+ ```
45
+ author tour ──► lint ──► commit, pin `ref`
46
+ ▲ │
47
+ │ ...code evolves...
48
+ │ │
49
+ │ status (tour lags HEAD?)
50
+ │ │
51
+ apply-review ◄── review report ◄── migrate [--dry-run]
52
+ (checked steps (auto-applies confident updates;
53
+ written back) low-confidence → the report)
54
+ ```
55
+
56
+ Steps the migration can't confidently place land in `MIGRATION-REVIEW-{tour}-{commit}.md`: each entry carries the step's original description (its *intent*), old → new location, why confidence dropped, and the actual code now at the proposed location. Check the boxes you approve — or correct the locations inline — then:
57
+
58
+ ```bash
59
+ codetour-cli apply-review MIGRATION-REVIEW-mytour-abc12345.md
60
+ ```
61
+
62
+ **The report is deliberately dual-audience**: an AI agent can read it, judge each proposed location against the step's stated intent, mark the checkboxes, and apply — the same workflow, no human bottleneck for the easy calls. (A companion [Claude skill](https://github.com/jlumbroso/codetour-skill/releases) teaches agents both tour authoring and this maintenance loop.)
63
+
64
+ ## Commands
65
+
66
+ | Command | Purpose |
67
+ |---|---|
68
+ | `init` | Set up `.codetour-cli.yml` configuration |
69
+ | `status` | Health of every tour: current vs. lagging HEAD |
70
+ | `check [tour]` | Validate tour structure without migrating |
71
+ | `lint [paths]` | Step-level checks against the workspace: missing files, out-of-range lines, non-matching or ambiguous patterns, broken `nextTour` links (`--format json`, `--strict`) |
72
+ | `migrate [tour]` | Track steps to HEAD; apply confident updates; report the rest |
73
+ | `apply-review <report>` | Write checked corrections back to the tour file |
74
+ | `undo [tour]` | Restore from the `.tour.backup` files migrate creates |
75
+
76
+ **Flags worth knowing** (`migrate`): `--threshold X` — the auto-apply confidence bar (note: confidence takes 5 exact values, not a smooth dial; see `docs/adr/0016-*.md`); `--clean-reviews` — drop stale review reports from earlier target commits; `--context-lines N` — code context in reports (default 7, config `review.context_lines`). (`apply-review`): `--auto-approve-above X` — also apply *unchecked* steps at or above that confidence; steps marked for deletion are **never** auto-approved.
77
+
78
+ ## MCP server
79
+
80
+ For agents on MCP-capable surfaces, the same operations are exposed as structured tools:
81
+
82
+ ```bash
83
+ pip install "codetour-cli[mcp]"
84
+ codetour-mcp # stdio MCP server
85
+ ```
86
+
87
+ Five tools — `tour_status`, `lint_tours`, `migrate_tour` (defaults to dry-run; a model-facing tool must not mutate by default), `read_review_report`, `apply_review` — each a thin wrapper calling the exact functions the CLI verbs call, with identical semantics (including: deletion-marked steps are never auto-approved). Register it e.g. in `.mcp.json`:
88
+
89
+ ```json
90
+ { "mcpServers": { "codetour": { "command": "codetour-mcp" } } }
91
+ ```
92
+
93
+ ## Design record
94
+
95
+ This tool is developed with the [ADRs4AI](https://github.com/jlumbroso) methodology — every architectural decision, including the ones that were later reversed, lives in `docs/adr/` as a first-class deliberation record: the byte-preserving editing contract, the confidence quantization study, the review-workflow design, and the retirement of ideas that eight months of shipped reality outvoted.
96
+
97
+ ## License
98
+
99
+ [MIT](LICENSE) — like CodeTour itself.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codetour-cli"
7
- version = "0.1.0"
7
+ version = "0.3.0"
8
8
  description = "Algorithmic maintenance of CodeTour files — an unofficial companion CLI for the CodeTour VS Code extension"
9
9
  authors = [{name = "Jérémie Lumbroso"}]
10
10
  readme = "README.md"
@@ -41,6 +41,11 @@ dependencies = [
41
41
  ]
42
42
 
43
43
  [project.optional-dependencies]
44
+ # MCP server (ADR-0017): thin wrapper over the same machinery the CLI
45
+ # verbs call, exposed as `codetour-mcp` (stdio).
46
+ mcp = [
47
+ "mcp>=1.2",
48
+ ]
44
49
  dev = [
45
50
  "pytest",
46
51
  "pytest-cov",
@@ -51,6 +56,10 @@ dev = [
51
56
 
52
57
  [project.scripts]
53
58
  codetour-cli = "codetour_cli.cli:main"
59
+ # Installed unconditionally; the mcp SDK lives in the [mcp] extra, and the
60
+ # launcher answers a bare install with an install hint, not a traceback
61
+ # (the json-source-edit playground-launcher pattern).
62
+ codetour-mcp = "codetour_cli.mcp_server:main"
54
63
 
55
64
  [project.urls]
56
65
  Homepage = "https://github.com/jlumbroso/codetour-cli"
@@ -319,6 +319,77 @@ def load_parsing_schema(report_content: str) -> ParsingSchema:
319
319
  return schema
320
320
 
321
321
 
322
+ def select_review_steps(review, auto_approve_above: Optional[float] = None):
323
+ """
324
+ Decide which parsed review steps to apply.
325
+
326
+ Base set: steps the human checked (the report workflow's contract).
327
+ With auto_approve_above set (0.0-1.0), UNCHECKED steps whose confidence
328
+ clears the threshold are promoted in as well -- except steps marked for
329
+ deletion, which are never auto-approved: removing a step from a tour is
330
+ destructive and stays behind an explicit human checkbox regardless of
331
+ confidence (the same never-auto principle the migrate path applies).
332
+
333
+ Returns (selected_steps, promoted_steps) -- promoted ⊆ selected.
334
+ """
335
+ selected = list(review.get_corrected_steps())
336
+ promoted = []
337
+ if auto_approve_above is not None:
338
+ cutoff = auto_approve_above * 100.0
339
+ already = {s.step_number for s in selected}
340
+ for r in review.step_reviews:
341
+ if r.step_number in already or r.status_checked:
342
+ continue
343
+ if r.is_marked_for_deletion():
344
+ continue
345
+ if r.confidence is not None and r.confidence >= cutoff:
346
+ promoted.append(r)
347
+ selected.extend(promoted)
348
+ selected.sort(key=lambda s: s.step_number)
349
+ return selected, promoted
350
+
351
+
352
+ def clean_stale_review_reports(
353
+ tours_dir: Path,
354
+ tour_name: str,
355
+ keep: Optional[Path],
356
+ dry_run: bool = False,
357
+ ) -> list:
358
+ """
359
+ Remove MIGRATION-REVIEW-{tour_name}-*.md reports for OTHER target
360
+ commits than the current run's (whose report, if any, is `keep`).
361
+
362
+ Matching is by literal prefix/suffix on the filename, deliberately not
363
+ glob (tour names are user-controlled; glob metacharacters in a name
364
+ must not widen the match). In dry_run, returns the candidates without
365
+ deleting. Returns the list of removed (or would-remove) paths.
366
+ """
367
+ prefix = f"MIGRATION-REVIEW-{tour_name}-"
368
+ removed = []
369
+ if not tours_dir.is_dir():
370
+ return removed
371
+ for p in sorted(tours_dir.iterdir()):
372
+ if not (p.is_file() and p.name.startswith(prefix) and p.name.endswith(".md")):
373
+ continue
374
+ if keep is not None and p.name == keep.name:
375
+ continue
376
+ if not dry_run:
377
+ p.unlink()
378
+ removed.append(p)
379
+ return removed
380
+
381
+
382
+ def resolve_context_lines(args, config: dict) -> int:
383
+ """CLI flag wins over config review.context_lines; default 7; floor 0."""
384
+ cli_val = getattr(args, 'context_lines', None)
385
+ if cli_val is not None:
386
+ return max(0, cli_val)
387
+ try:
388
+ return max(0, int(config.get('review', {}).get('context_lines', 7)))
389
+ except (TypeError, ValueError):
390
+ return 7
391
+
392
+
322
393
  def generate_review_report(
323
394
  tour_path: Path,
324
395
  result,
@@ -327,7 +398,8 @@ def generate_review_report(
327
398
  repo_root: Path,
328
399
  is_dry_run: bool = False,
329
400
  no_guidance: bool = False,
330
- threshold: Optional[float] = None
401
+ threshold: Optional[float] = None,
402
+ context_lines: int = 7
331
403
  ) -> Optional[Path]:
332
404
  """
333
405
  Generate a markdown review report for migrations with low-confidence steps.
@@ -469,8 +541,8 @@ def generate_review_report(
469
541
  new_line = None
470
542
 
471
543
  # Get code context for old and new locations (from specific commits)
472
- old_context = get_code_context(repo_root, old_file, old_line, source_commit) if old_line else "(No line number)"
473
- new_context = get_code_context(repo_root, new_file, new_line, target_commit) if new_line and update.new_location else "(File deleted)"
544
+ old_context = get_code_context(repo_root, old_file, old_line, source_commit, context_lines=context_lines) if old_line else "(No line number)"
545
+ new_context = get_code_context(repo_root, new_file, new_line, target_commit, context_lines=context_lines) if new_line and update.new_location else "(File deleted)"
474
546
 
475
547
  # Quote the description with chevrons
476
548
  quoted_description = quote_with_chevrons(step.description)
@@ -794,6 +866,22 @@ Examples:
794
866
  dest='auto_approve',
795
867
  help='Automatically approve all changes (skip confirmation prompts)'
796
868
  )
869
+ apply_review_parser.add_argument(
870
+ '--auto-approve-above',
871
+ type=float,
872
+ dest='auto_approve_above',
873
+ metavar='X',
874
+ help=(
875
+ 'Additionally apply UNCHECKED steps whose confidence >= X '
876
+ '(0.0-1.0), on top of the checked ones. Steps marked for '
877
+ 'deletion are never auto-approved -- destructive actions stay '
878
+ 'behind an explicit human checkbox. Note: confidence only ever '
879
+ 'takes 5 exact values (see docs/adr/0016-*.md), so a threshold '
880
+ 'between quanta behaves like the nearest quantum above it. '
881
+ 'ADR-0003\'s "batch approve all above confidence X", in the '
882
+ 'report-based workflow that ADR-0007 shipped.'
883
+ )
884
+ )
797
885
 
798
886
  # migrate command
799
887
  migrate_parser = subparsers.add_parser(
@@ -860,6 +948,27 @@ Examples:
860
948
  dest='no_lint',
861
949
  help='Skip the advisory post-migration lint pass (ADR-0014)'
862
950
  )
951
+ migrate_parser.add_argument(
952
+ '--clean-reviews',
953
+ action='store_true',
954
+ dest='clean_reviews',
955
+ help=(
956
+ 'After migrating each tour, delete stale MIGRATION-REVIEW-'
957
+ '{tour}-*.md reports left over from earlier target commits '
958
+ '(the report for THIS run, if any, is kept). In --dry-run, '
959
+ 'lists what would be cleaned without deleting.'
960
+ )
961
+ )
962
+ migrate_parser.add_argument(
963
+ '--context-lines',
964
+ type=int,
965
+ dest='context_lines',
966
+ metavar='N',
967
+ help=(
968
+ 'Lines of code context above/below each reviewed line in the '
969
+ 'review report (default: from config review.context_lines, 7)'
970
+ )
971
+ )
863
972
 
864
973
  args = parser.parse_args()
865
974
 
@@ -1451,13 +1560,24 @@ def apply_review_command(args, config: dict) -> int:
1451
1560
  print(f"❌ Failed to load tour: {e}", file=sys.stderr)
1452
1561
  return 2
1453
1562
 
1454
- # Get only reviewed steps (checkbox checked)
1455
- corrected_steps = review.get_corrected_steps()
1563
+ # Reviewed steps (checkbox checked), plus -- with --auto-approve-above X --
1564
+ # unchecked steps whose confidence clears the threshold (never deletions).
1565
+ auto_above = getattr(args, 'auto_approve_above', None)
1566
+ if auto_above is not None and not (0.0 <= auto_above <= 1.0):
1567
+ print(f"❌ --auto-approve-above must be between 0.0 and 1.0 (got {auto_above})", file=sys.stderr)
1568
+ return 2
1569
+
1570
+ corrected_steps, promoted_steps = select_review_steps(review, auto_above)
1571
+ if promoted_steps:
1572
+ nums = ", ".join(str(s.step_number) for s in promoted_steps)
1573
+ print(f"🔓 Auto-approving {len(promoted_steps)} unchecked step(s) at confidence ≥ {auto_above:.0%}: {nums}")
1456
1574
 
1457
1575
  if not corrected_steps:
1458
1576
  print("ℹ️ No corrections found in review report")
1459
1577
  print(" The report may not have been edited, or no checkboxes were marked")
1460
1578
  print(" Tip: Check the 'Status' checkbox for steps you want to apply")
1579
+ if auto_above is not None:
1580
+ print(f" (--auto-approve-above {auto_above} promoted nothing: no unchecked, non-deletion step at that confidence)")
1461
1581
  return 0
1462
1582
 
1463
1583
  # Apply corrections
@@ -1849,6 +1969,7 @@ def migrate_all_tours(args, config: dict) -> int:
1849
1969
  print_migration_summary(result, tour_name, is_dry_run)
1850
1970
 
1851
1971
  # Generate review report if needed
1972
+ report_path = None
1852
1973
  if result.num_needs_review > 0:
1853
1974
  report_path = generate_review_report(
1854
1975
  tour_path=tour_info.path,
@@ -1858,7 +1979,8 @@ def migrate_all_tours(args, config: dict) -> int:
1858
1979
  repo_root=repo_info.repo_path,
1859
1980
  is_dry_run=is_dry_run,
1860
1981
  no_guidance=getattr(args, 'no_guidance', False),
1861
- threshold=threshold
1982
+ threshold=threshold,
1983
+ context_lines=resolve_context_lines(args, config)
1862
1984
  )
1863
1985
  if report_path:
1864
1986
  review_reports.append(report_path)
@@ -1867,6 +1989,20 @@ def migrate_all_tours(args, config: dict) -> int:
1867
1989
  else:
1868
1990
  print(f" 📄 Review report: {report_path.relative_to(repo_info.repo_path)}")
1869
1991
 
1992
+ # Optionally clean stale reports from earlier target commits
1993
+ # (keyed on the just-generated report's actual filename -- never
1994
+ # re-derived, since to_commit may be symbolic like "HEAD").
1995
+ if getattr(args, 'clean_reviews', False):
1996
+ stale = clean_stale_review_reports(
1997
+ tours_dir=tour_info.path.parent,
1998
+ tour_name=tour_info.path.stem,
1999
+ keep=report_path,
2000
+ dry_run=is_dry_run,
2001
+ )
2002
+ if stale:
2003
+ verb = "Would clean" if is_dry_run else "Cleaned"
2004
+ print(f" 🧹 {verb} {len(stale)} stale review report(s): " + ", ".join(p.name for p in stale))
2005
+
1870
2006
  except Exception as e:
1871
2007
  log.error("migration_failed", tour=tour_name, error=str(e))
1872
2008
  print(f" ❌ Failed: {e}\n")
@@ -24,6 +24,14 @@ DEFAULT_CONFIG = {
24
24
  "display": {
25
25
  "color": True,
26
26
  "verbosity": "normal",
27
+ },
28
+ "review": {
29
+ # Lines of code context above/below each reviewed line in review
30
+ # reports. Ratified at 7 (the shipped default) over ADR-0007's
31
+ # original ±3 recommendation, made configurable per Jérémie's
32
+ # 2026-08-29 answer ("so we don't leave people... without a way
33
+ # to tweak the tool to their context").
34
+ "context_lines": 7,
27
35
  }
28
36
  }
29
37
 
@@ -57,6 +65,12 @@ display:
57
65
 
58
66
  # Verbosity level (quiet, normal, verbose)
59
67
  verbosity: normal
68
+
69
+ # Review report preferences
70
+ review:
71
+ # Lines of code context above/below each reviewed line (override per
72
+ # run with `migrate --context-lines N`)
73
+ context_lines: 7
60
74
  """
61
75
 
62
76