codetour-cli 0.2.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.
- codetour_cli-0.3.0/PKG-INFO +138 -0
- codetour_cli-0.3.0/README.md +99 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/pyproject.toml +10 -1
- codetour_cli-0.3.0/src/codetour_cli/mcp_server.py +279 -0
- codetour_cli-0.3.0/src/codetour_cli.egg-info/PKG-INFO +138 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli.egg-info/SOURCES.txt +2 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli.egg-info/entry_points.txt +1 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli.egg-info/requires.txt +3 -0
- codetour_cli-0.3.0/tests/test_mcp_server.py +253 -0
- codetour_cli-0.2.0/PKG-INFO +0 -47
- codetour_cli-0.2.0/README.md +0 -10
- codetour_cli-0.2.0/src/codetour_cli.egg-info/PKG-INFO +0 -47
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/LICENSE +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/setup.cfg +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/__init__.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/cli.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/config.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/description_validator.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/detection.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/discovery.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/lint.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/logging/__init__.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/logging/setup.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/migration/__init__.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/migration/direct.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/migration/git_diff.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/migration/stepwise.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/review_parser.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/schema_models.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/tour/__init__.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/tour/schema.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli/tour/updater.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli.egg-info/dependency_links.txt +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/src/codetour_cli.egg-info/top_level.txt +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_advanced_scenarios.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_apply_review_surgical_integration.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_description_validation.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_detection.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_direct_migration.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_fuzzy_repo_calibration.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_git_diff_parser.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_harness.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_integration.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_lint.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_lint_cli.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_logging_setup.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_pattern_validation.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_post_migration_lint.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_review_roundtrip.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_review_workflow_options.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_scenarios.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_schema_validation.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_stepwise_migration.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_synthetic_repo_migration_engine.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_templates.py +0 -0
- {codetour_cli-0.2.0 → codetour_cli-0.3.0}/tests/test_tour_schema.py +0 -0
- {codetour_cli-0.2.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
|
+
[](https://github.com/jlumbroso/codetour-cli/actions/workflows/test.yml)
|
|
43
|
+
[](https://github.com/jlumbroso/codetour-cli/actions/workflows/test.yml)
|
|
44
|
+
[](https://pypi.org/project/codetour-cli/)
|
|
45
|
+
[](https://pypi.org/project/codetour-cli/)
|
|
46
|
+
[](https://pepy.tech/projects/codetour-cli)
|
|
47
|
+
[](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
|
+
[](https://github.com/jlumbroso/codetour-cli/actions/workflows/test.yml)
|
|
4
|
+
[](https://github.com/jlumbroso/codetour-cli/actions/workflows/test.yml)
|
|
5
|
+
[](https://pypi.org/project/codetour-cli/)
|
|
6
|
+
[](https://pypi.org/project/codetour-cli/)
|
|
7
|
+
[](https://pepy.tech/projects/codetour-cli)
|
|
8
|
+
[](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.
|
|
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"
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""MCP server for codetour-cli — structured tour operations for AI agents.
|
|
2
|
+
|
|
3
|
+
ADR-0017 (Accepted 2026-08-30): a THIN WRAPPER over the exact machinery the
|
|
4
|
+
CLI verbs call — `discovery.discover_repository`, `lint.lint_collection`,
|
|
5
|
+
`cli.migrate_command`, `review_parser.parse_review_report`,
|
|
6
|
+
`cli.apply_review_command`. Any behavioral drift between a CLI verb and its
|
|
7
|
+
MCP tool is a bug by definition; nothing here reimplements an operation.
|
|
8
|
+
|
|
9
|
+
Entry-point seam (same pattern as json-source-edit's playground launcher):
|
|
10
|
+
setuptools installs console scripts unconditionally, so a bare
|
|
11
|
+
`pip install codetour-cli` puts `codetour-mcp` on PATH while the `mcp` SDK
|
|
12
|
+
lives in the `[mcp]` extra. `main()` is deliberately import-light — nothing
|
|
13
|
+
above it may import outside the standard library and this package — so the
|
|
14
|
+
first thing a stranger types answers with an install hint, not a traceback.
|
|
15
|
+
|
|
16
|
+
Two deliberate, documented parameter-default choices (not behavior drift —
|
|
17
|
+
per-operation behavior is identical to the CLI at equal flag values):
|
|
18
|
+
- `migrate_tour` defaults to `dry_run=True`: a model-facing tool must not
|
|
19
|
+
mutate files by default; pass dry_run=False for the CLI's default-live
|
|
20
|
+
behavior.
|
|
21
|
+
- `apply_review` exposes the CLI's `--yes` as `approve_warned_descriptions`
|
|
22
|
+
and feeds declined prompts a "n" stream, because an MCP server has no
|
|
23
|
+
interactive stdin — skip-on-warning is exactly what pressing enter at the
|
|
24
|
+
CLI's [y/N] prompt does.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import io
|
|
30
|
+
import sys
|
|
31
|
+
from contextlib import redirect_stdout
|
|
32
|
+
from dataclasses import asdict
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from types import SimpleNamespace
|
|
35
|
+
from typing import Any, Optional
|
|
36
|
+
|
|
37
|
+
_INSTALL_HINT = (
|
|
38
|
+
"codetour-mcp is the optional MCP server, and its dependency is not "
|
|
39
|
+
"installed (missing: {missing}).\n"
|
|
40
|
+
"Install it with:\n\n"
|
|
41
|
+
' pip install "codetour-cli[mcp]"\n'
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ── tool implementations (plain functions; registered onto FastMCP in main) ──
|
|
46
|
+
# Kept SDK-free so tests can exercise them without the [mcp] extra installed.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def tour_status_impl() -> dict:
|
|
50
|
+
"""Structured health of every tour in the repository."""
|
|
51
|
+
from codetour_cli.discovery import discover_repository, is_tour_up_to_date
|
|
52
|
+
|
|
53
|
+
info = discover_repository()
|
|
54
|
+
tours = []
|
|
55
|
+
for t in info.tours:
|
|
56
|
+
tours.append({
|
|
57
|
+
"file": str(t.path),
|
|
58
|
+
"title": getattr(t.tour, "title", None),
|
|
59
|
+
"steps": len(getattr(t.tour, "steps", []) or []),
|
|
60
|
+
"source_commit": t.source_commit,
|
|
61
|
+
"source_confidence": t.source_confidence,
|
|
62
|
+
"source_method": t.source_method,
|
|
63
|
+
"commits_behind": t.commits_behind,
|
|
64
|
+
"up_to_date": is_tour_up_to_date(t),
|
|
65
|
+
})
|
|
66
|
+
return {
|
|
67
|
+
"repo_path": str(info.repo_path),
|
|
68
|
+
"tours_dir": str(info.tours_dir),
|
|
69
|
+
"tours": tours,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def lint_tours_impl(paths: Optional[list[str]] = None, strict: bool = False) -> dict:
|
|
74
|
+
"""Ground-truth lint findings, structured (same checks as `codetour-cli lint`)."""
|
|
75
|
+
from codetour_cli.discovery import find_repo_root, find_tours_directory, scan_tours
|
|
76
|
+
from codetour_cli.lint import has_errors, lint_collection
|
|
77
|
+
|
|
78
|
+
repo_root = find_repo_root()
|
|
79
|
+
if paths:
|
|
80
|
+
tour_paths = []
|
|
81
|
+
for raw in paths:
|
|
82
|
+
p = Path(raw)
|
|
83
|
+
if not p.is_absolute():
|
|
84
|
+
p = repo_root / p
|
|
85
|
+
if p.is_dir():
|
|
86
|
+
tour_paths.extend(scan_tours(p))
|
|
87
|
+
else:
|
|
88
|
+
tour_paths.append(p)
|
|
89
|
+
else:
|
|
90
|
+
tours_dir = find_tours_directory(repo_root)
|
|
91
|
+
tour_paths = scan_tours(tours_dir) if tours_dir else []
|
|
92
|
+
|
|
93
|
+
findings = lint_collection(tour_paths, repo_root)
|
|
94
|
+
errors = sum(1 for f in findings if f.severity == "error")
|
|
95
|
+
return {
|
|
96
|
+
"tours_checked": len(tour_paths),
|
|
97
|
+
"errors": errors,
|
|
98
|
+
"warnings": len(findings) - errors,
|
|
99
|
+
"clean": not has_errors(findings, strict=strict),
|
|
100
|
+
"findings": [asdict(f) if hasattr(f, "__dataclass_fields__") else vars(f)
|
|
101
|
+
for f in findings],
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _run_command(command_fn, args: SimpleNamespace, stdin_text: str = "") -> dict:
|
|
106
|
+
"""Run a CLI command function verbatim, capturing stdout; feed a scripted
|
|
107
|
+
stdin so interactive prompts resolve to their conservative default."""
|
|
108
|
+
from codetour_cli.config import load_config
|
|
109
|
+
|
|
110
|
+
config = load_config()
|
|
111
|
+
buf = io.StringIO()
|
|
112
|
+
old_stdin = sys.stdin
|
|
113
|
+
sys.stdin = io.StringIO(stdin_text)
|
|
114
|
+
try:
|
|
115
|
+
with redirect_stdout(buf):
|
|
116
|
+
exit_code = command_fn(args, config)
|
|
117
|
+
finally:
|
|
118
|
+
sys.stdin = old_stdin
|
|
119
|
+
return {"exit_code": exit_code, "output": buf.getvalue()}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def migrate_tour_impl(
|
|
123
|
+
tour_file: Optional[str] = None,
|
|
124
|
+
dry_run: bool = True,
|
|
125
|
+
threshold: Optional[float] = None,
|
|
126
|
+
to_commit: str = "HEAD",
|
|
127
|
+
from_commit: Optional[str] = None,
|
|
128
|
+
clean_reviews: bool = False,
|
|
129
|
+
context_lines: Optional[int] = None,
|
|
130
|
+
) -> dict:
|
|
131
|
+
"""Migrate a tour (or all out-of-date tours) via the CLI's own command path."""
|
|
132
|
+
from codetour_cli.cli import migrate_command
|
|
133
|
+
|
|
134
|
+
args = SimpleNamespace(
|
|
135
|
+
tour_file=tour_file,
|
|
136
|
+
from_commit=from_commit,
|
|
137
|
+
to_commit=to_commit,
|
|
138
|
+
threshold=threshold,
|
|
139
|
+
no_backup=False,
|
|
140
|
+
interactive=False,
|
|
141
|
+
dry_run=dry_run,
|
|
142
|
+
no_guidance=False,
|
|
143
|
+
no_lint=False,
|
|
144
|
+
clean_reviews=clean_reviews,
|
|
145
|
+
context_lines=context_lines,
|
|
146
|
+
)
|
|
147
|
+
result = _run_command(migrate_command, args)
|
|
148
|
+
result["reports"] = [
|
|
149
|
+
line.split("Review report", 1)[1].split(":", 1)[1].strip()
|
|
150
|
+
for line in result["output"].splitlines()
|
|
151
|
+
if "Review report" in line and ":" in line.split("Review report", 1)[1]
|
|
152
|
+
]
|
|
153
|
+
result["dry_run"] = dry_run
|
|
154
|
+
return result
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def read_review_report_impl(report_file: str) -> dict:
|
|
158
|
+
"""Parse a MIGRATION-REVIEW-*.md report into structured step reviews."""
|
|
159
|
+
from codetour_cli.review_parser import parse_review_report
|
|
160
|
+
|
|
161
|
+
review = parse_review_report(Path(report_file))
|
|
162
|
+
return {
|
|
163
|
+
"tour_name": review.tour_name,
|
|
164
|
+
"source_commit": review.source_commit,
|
|
165
|
+
"target_commit": review.target_commit,
|
|
166
|
+
"mode": review.mode,
|
|
167
|
+
"step_reviews": [asdict(r) for r in review.step_reviews],
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def apply_review_impl(
|
|
172
|
+
report_file: str,
|
|
173
|
+
auto_approve_above: Optional[float] = None,
|
|
174
|
+
approve_warned_descriptions: bool = False,
|
|
175
|
+
) -> dict:
|
|
176
|
+
"""Apply a review report via the CLI's own command path."""
|
|
177
|
+
from codetour_cli.cli import apply_review_command
|
|
178
|
+
|
|
179
|
+
args = SimpleNamespace(
|
|
180
|
+
report_file=report_file,
|
|
181
|
+
auto_approve=approve_warned_descriptions,
|
|
182
|
+
auto_approve_above=auto_approve_above,
|
|
183
|
+
)
|
|
184
|
+
# "n\n" stream: any description-warning prompt resolves to skip — the
|
|
185
|
+
# CLI's own conservative default at its [y/N] prompt.
|
|
186
|
+
return _run_command(apply_review_command, args, stdin_text="n\n" * 1000)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# ── server assembly ──────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def build_server() -> Any:
|
|
193
|
+
"""Construct the MCP server (requires the [mcp] extra).
|
|
194
|
+
|
|
195
|
+
SDK v2 renamed FastMCP → MCPServer (same tool/run surface, verified
|
|
196
|
+
empirically 2026-08-30 — the stdio smoke test caught the v1 prior
|
|
197
|
+
before it shipped); import v2 first, fall back to v1 for pinned
|
|
198
|
+
environments."""
|
|
199
|
+
try:
|
|
200
|
+
from mcp.server.mcpserver import MCPServer as _Server # mcp >= 2
|
|
201
|
+
except ImportError:
|
|
202
|
+
from mcp.server.fastmcp import FastMCP as _Server # mcp 1.x
|
|
203
|
+
|
|
204
|
+
server = _Server("codetour-cli")
|
|
205
|
+
|
|
206
|
+
server.tool(
|
|
207
|
+
name="tour_status",
|
|
208
|
+
description=(
|
|
209
|
+
"Structured health of every CodeTour in the repository: per tour, "
|
|
210
|
+
"its pinned source commit, detection confidence/method, how many "
|
|
211
|
+
"commits behind HEAD it is, and whether it is up to date."
|
|
212
|
+
),
|
|
213
|
+
)(tour_status_impl)
|
|
214
|
+
|
|
215
|
+
server.tool(
|
|
216
|
+
name="lint_tours",
|
|
217
|
+
description=(
|
|
218
|
+
"Lint tours against the actual workspace (missing files, "
|
|
219
|
+
"out-of-range lines, non-matching or ambiguous patterns, broken "
|
|
220
|
+
"nextTour links, duplicate isPrimary). Structured findings; "
|
|
221
|
+
"same checks as `codetour-cli lint`."
|
|
222
|
+
),
|
|
223
|
+
)(lint_tours_impl)
|
|
224
|
+
|
|
225
|
+
server.tool(
|
|
226
|
+
name="migrate_tour",
|
|
227
|
+
description=(
|
|
228
|
+
"Migrate a tour (or all out-of-date tours) toward HEAD via the "
|
|
229
|
+
"CLI's own pipeline. DEFAULTS TO dry_run=true — pass "
|
|
230
|
+
"dry_run=false to actually write. Confident step updates are "
|
|
231
|
+
"applied (byte-preserving edits); low-confidence steps land in a "
|
|
232
|
+
"review report (paths returned in `reports`). Note: `threshold` "
|
|
233
|
+
"confidence only takes 5 exact values — values in (0.5, 0.95) "
|
|
234
|
+
"behave like the default; see that repo's ADR-0016 before tuning."
|
|
235
|
+
),
|
|
236
|
+
)(migrate_tour_impl)
|
|
237
|
+
|
|
238
|
+
server.tool(
|
|
239
|
+
name="read_review_report",
|
|
240
|
+
description=(
|
|
241
|
+
"Parse a MIGRATION-REVIEW-*.md report into structured step "
|
|
242
|
+
"reviews: per step, its checkbox state, tentative file/line, "
|
|
243
|
+
"confidence, and any edited description. Read this, judge each "
|
|
244
|
+
"proposed location against the step's original intent, then use "
|
|
245
|
+
"apply_review."
|
|
246
|
+
),
|
|
247
|
+
)(read_review_report_impl)
|
|
248
|
+
|
|
249
|
+
server.tool(
|
|
250
|
+
name="apply_review",
|
|
251
|
+
description=(
|
|
252
|
+
"Apply corrections from a review report to the tour file "
|
|
253
|
+
"(byte-preserving edits, backup created). Applies checkbox-"
|
|
254
|
+
"checked steps; `auto_approve_above` (0.0-1.0) additionally "
|
|
255
|
+
"applies UNCHECKED steps at/above that confidence — but steps "
|
|
256
|
+
"marked for deletion are NEVER auto-approved (destructive stays "
|
|
257
|
+
"behind an explicit checkbox). Confidence takes 5 exact values "
|
|
258
|
+
"(ADR-0016). `approve_warned_descriptions` maps to the CLI's "
|
|
259
|
+
"--yes for description-edit warnings; when false, warned edits "
|
|
260
|
+
"are skipped (the CLI prompt's own default)."
|
|
261
|
+
),
|
|
262
|
+
)(apply_review_impl)
|
|
263
|
+
|
|
264
|
+
return server
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def main() -> None:
|
|
268
|
+
try:
|
|
269
|
+
import mcp # noqa: F401
|
|
270
|
+
except ImportError as exc:
|
|
271
|
+
missing = getattr(exc, "name", None) or str(exc)
|
|
272
|
+
print(_INSTALL_HINT.format(missing=missing), file=sys.stderr)
|
|
273
|
+
raise SystemExit(1)
|
|
274
|
+
|
|
275
|
+
build_server().run()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
if __name__ == "__main__":
|
|
279
|
+
main()
|
|
@@ -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
|
+
[](https://github.com/jlumbroso/codetour-cli/actions/workflows/test.yml)
|
|
43
|
+
[](https://github.com/jlumbroso/codetour-cli/actions/workflows/test.yml)
|
|
44
|
+
[](https://pypi.org/project/codetour-cli/)
|
|
45
|
+
[](https://pypi.org/project/codetour-cli/)
|
|
46
|
+
[](https://pepy.tech/projects/codetour-cli)
|
|
47
|
+
[](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.
|
|
@@ -8,6 +8,7 @@ src/codetour_cli/description_validator.py
|
|
|
8
8
|
src/codetour_cli/detection.py
|
|
9
9
|
src/codetour_cli/discovery.py
|
|
10
10
|
src/codetour_cli/lint.py
|
|
11
|
+
src/codetour_cli/mcp_server.py
|
|
11
12
|
src/codetour_cli/review_parser.py
|
|
12
13
|
src/codetour_cli/schema_models.py
|
|
13
14
|
src/codetour_cli.egg-info/PKG-INFO
|
|
@@ -37,6 +38,7 @@ tests/test_integration.py
|
|
|
37
38
|
tests/test_lint.py
|
|
38
39
|
tests/test_lint_cli.py
|
|
39
40
|
tests/test_logging_setup.py
|
|
41
|
+
tests/test_mcp_server.py
|
|
40
42
|
tests/test_pattern_validation.py
|
|
41
43
|
tests/test_post_migration_lint.py
|
|
42
44
|
tests/test_review_roundtrip.py
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""Tests for the MCP server (ADR-0017, v0.3.0).
|
|
2
|
+
|
|
3
|
+
The tool implementations are deliberately SDK-free plain functions, so
|
|
4
|
+
everything except the stdio smoke test runs without the [mcp] extra --
|
|
5
|
+
and everything runs against REAL machinery (real git repos, real tour
|
|
6
|
+
files, the real command functions), never mocks: the wrapper-not-fork
|
|
7
|
+
constraint means the thing worth testing is that the wrappers drive the
|
|
8
|
+
same behavior the CLI does.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from codetour_cli.mcp_server import (
|
|
18
|
+
apply_review_impl,
|
|
19
|
+
lint_tours_impl,
|
|
20
|
+
migrate_tour_impl,
|
|
21
|
+
read_review_report_impl,
|
|
22
|
+
tour_status_impl,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _init_git_repo(path):
|
|
27
|
+
subprocess.run(["git", "init", "-q"], cwd=path, check=True)
|
|
28
|
+
subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=path, check=True)
|
|
29
|
+
subprocess.run(["git", "config", "user.name", "Test"], cwd=path, check=True)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _review_report(tour_name, entries):
|
|
33
|
+
"""Same shape the apply-review integration tests use, plus a `checked`
|
|
34
|
+
knob so auto-approve-above has genuinely unchecked steps to promote."""
|
|
35
|
+
steps_md = []
|
|
36
|
+
for e in entries:
|
|
37
|
+
checked = "[x] Reviewed" if e.get("checked", True) else "[ ] Not reviewed"
|
|
38
|
+
block = f"""### Step {e['step_number']}: placeholder → placeholder ({e.get('confidence', 80)}% confidence)
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
#### Review Status
|
|
43
|
+
|
|
44
|
+
- **Status**: {checked}
|
|
45
|
+
- **Tentative file**: `{e['tentative_file']}`
|
|
46
|
+
- **Tentative line**: `{e['tentative_line']}`
|
|
47
|
+
- **Confidence**: {e.get('confidence', 80)}%
|
|
48
|
+
- **Modified Description**: [ ]
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
"""
|
|
52
|
+
steps_md.append(block)
|
|
53
|
+
|
|
54
|
+
return f"""---
|
|
55
|
+
schema_version: '1.0'
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
# Migration Review Report
|
|
59
|
+
|
|
60
|
+
- **Generated**: 2026-08-30 00:00:00
|
|
61
|
+
- **Source commit**: abc12345
|
|
62
|
+
- **Target commit**: def67890
|
|
63
|
+
- **Mode**: live
|
|
64
|
+
- **Tour**: {tour_name}
|
|
65
|
+
- **Total steps**: {len(entries)}
|
|
66
|
+
|
|
67
|
+
## Steps Needing Review
|
|
68
|
+
|
|
69
|
+
""" + "\n".join(steps_md)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
TOUR_JSON = """{
|
|
73
|
+
"$schema": "https://aka.ms/codetour-schema",
|
|
74
|
+
"title": "Rust Basics",
|
|
75
|
+
"steps": [
|
|
76
|
+
{
|
|
77
|
+
"file": "src/main.rs",
|
|
78
|
+
"line": 3,
|
|
79
|
+
"description": "Entry point"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"file": "src/lib.rs",
|
|
83
|
+
"line": 2,
|
|
84
|
+
"description": "Library definition"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@pytest.fixture
|
|
92
|
+
def repo_with_tour(tmp_path, monkeypatch):
|
|
93
|
+
_init_git_repo(tmp_path)
|
|
94
|
+
(tmp_path / "src").mkdir()
|
|
95
|
+
(tmp_path / "src" / "main.rs").write_text("fn a() {}\nfn b() {}\nfn main() {}\n", encoding="utf-8")
|
|
96
|
+
(tmp_path / "src" / "lib.rs").write_text("mod a;\npub fn lib() {}\n", encoding="utf-8")
|
|
97
|
+
tours_dir = tmp_path / ".tours"
|
|
98
|
+
tours_dir.mkdir()
|
|
99
|
+
tour_path = tours_dir / "rust-basics.tour"
|
|
100
|
+
tour_path.write_text(TOUR_JSON, encoding="utf-8")
|
|
101
|
+
subprocess.run(["git", "add", "."], cwd=tmp_path, check=True)
|
|
102
|
+
subprocess.run(["git", "commit", "-q", "-m", "initial"], cwd=tmp_path, check=True)
|
|
103
|
+
monkeypatch.chdir(tmp_path)
|
|
104
|
+
return tmp_path, tour_path
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class TestTourStatus:
|
|
108
|
+
def test_structured_status_for_real_repo(self, repo_with_tour):
|
|
109
|
+
tmp_path, _ = repo_with_tour
|
|
110
|
+
result = tour_status_impl()
|
|
111
|
+
assert result["repo_path"] == str(tmp_path)
|
|
112
|
+
assert len(result["tours"]) == 1
|
|
113
|
+
t = result["tours"][0]
|
|
114
|
+
assert t["title"] == "Rust Basics"
|
|
115
|
+
assert t["steps"] == 2
|
|
116
|
+
assert isinstance(t["commits_behind"], int)
|
|
117
|
+
assert isinstance(t["up_to_date"], bool)
|
|
118
|
+
assert t["source_commit"]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TestLintTours:
|
|
122
|
+
def test_reports_real_problems(self, repo_with_tour):
|
|
123
|
+
tmp_path, tour_path = repo_with_tour
|
|
124
|
+
# A real defect a user would ship: a step pointing at a missing file.
|
|
125
|
+
broken = TOUR_JSON.replace("src/lib.rs", "src/gone.rs")
|
|
126
|
+
tour_path.write_text(broken, encoding="utf-8")
|
|
127
|
+
result = lint_tours_impl()
|
|
128
|
+
assert result["tours_checked"] == 1
|
|
129
|
+
assert result["errors"] >= 1
|
|
130
|
+
assert result["clean"] is False
|
|
131
|
+
assert any("gone.rs" in str(f.get("message", "")) for f in result["findings"])
|
|
132
|
+
|
|
133
|
+
def test_clean_tour_is_clean(self, repo_with_tour):
|
|
134
|
+
result = lint_tours_impl()
|
|
135
|
+
assert result["errors"] == 0
|
|
136
|
+
assert result["clean"] is True
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class TestReadReviewReport:
|
|
140
|
+
def test_parses_structured(self, repo_with_tour, tmp_path):
|
|
141
|
+
report = tmp_path / "MIGRATION-REVIEW-rust-basics-def67890.md"
|
|
142
|
+
report.write_text(
|
|
143
|
+
_review_report("rust-basics.tour", [
|
|
144
|
+
{"step_number": 1, "tentative_file": "src/main.rs",
|
|
145
|
+
"tentative_line": "2", "confidence": 50, "checked": False},
|
|
146
|
+
]),
|
|
147
|
+
encoding="utf-8",
|
|
148
|
+
)
|
|
149
|
+
result = read_review_report_impl(str(report))
|
|
150
|
+
assert result["tour_name"] == "rust-basics.tour"
|
|
151
|
+
assert result["target_commit"] == "def67890"
|
|
152
|
+
(r,) = result["step_reviews"]
|
|
153
|
+
assert r["step_number"] == 1
|
|
154
|
+
assert r["status_checked"] is False
|
|
155
|
+
assert r["confidence"] == 50
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class TestApplyReview:
|
|
159
|
+
def test_applies_checked_step_to_real_tour(self, repo_with_tour, tmp_path):
|
|
160
|
+
_, tour_path = repo_with_tour
|
|
161
|
+
report = tmp_path / "MIGRATION-REVIEW-rust-basics-def67890.md"
|
|
162
|
+
report.write_text(
|
|
163
|
+
_review_report("rust-basics.tour", [
|
|
164
|
+
{"step_number": 2, "tentative_file": "src/lib.rs",
|
|
165
|
+
"tentative_line": "1", "checked": True},
|
|
166
|
+
]),
|
|
167
|
+
encoding="utf-8",
|
|
168
|
+
)
|
|
169
|
+
result = apply_review_impl(str(report))
|
|
170
|
+
assert result["exit_code"] == 0
|
|
171
|
+
data = json.loads(tour_path.read_text(encoding="utf-8"))
|
|
172
|
+
assert data["steps"][1]["line"] == 1
|
|
173
|
+
assert data["steps"][0]["line"] == 3 # untouched neighbor
|
|
174
|
+
|
|
175
|
+
def test_auto_approve_above_promotes_unchecked_high_confidence(self, repo_with_tour, tmp_path):
|
|
176
|
+
_, tour_path = repo_with_tour
|
|
177
|
+
report = tmp_path / "MIGRATION-REVIEW-rust-basics-def67890.md"
|
|
178
|
+
report.write_text(
|
|
179
|
+
_review_report("rust-basics.tour", [
|
|
180
|
+
{"step_number": 1, "tentative_file": "src/main.rs",
|
|
181
|
+
"tentative_line": "1", "confidence": 90, "checked": False},
|
|
182
|
+
{"step_number": 2, "tentative_file": "src/lib.rs",
|
|
183
|
+
"tentative_line": "1", "confidence": 50, "checked": False},
|
|
184
|
+
]),
|
|
185
|
+
encoding="utf-8",
|
|
186
|
+
)
|
|
187
|
+
result = apply_review_impl(str(report), auto_approve_above=0.8)
|
|
188
|
+
assert result["exit_code"] == 0
|
|
189
|
+
data = json.loads(tour_path.read_text(encoding="utf-8"))
|
|
190
|
+
assert data["steps"][0]["line"] == 1 # 90% ≥ 80%: promoted, applied
|
|
191
|
+
assert data["steps"][1]["line"] == 2 # 50% < 80%: untouched
|
|
192
|
+
|
|
193
|
+
def test_without_flag_unchecked_steps_do_nothing(self, repo_with_tour, tmp_path):
|
|
194
|
+
_, tour_path = repo_with_tour
|
|
195
|
+
before = tour_path.read_bytes()
|
|
196
|
+
report = tmp_path / "MIGRATION-REVIEW-rust-basics-def67890.md"
|
|
197
|
+
report.write_text(
|
|
198
|
+
_review_report("rust-basics.tour", [
|
|
199
|
+
{"step_number": 1, "tentative_file": "src/main.rs",
|
|
200
|
+
"tentative_line": "1", "confidence": 90, "checked": False},
|
|
201
|
+
]),
|
|
202
|
+
encoding="utf-8",
|
|
203
|
+
)
|
|
204
|
+
result = apply_review_impl(str(report))
|
|
205
|
+
assert result["exit_code"] == 0
|
|
206
|
+
assert tour_path.read_bytes() == before
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class TestMigrateTour:
|
|
210
|
+
def test_dry_run_is_the_default_and_mutates_nothing(self, repo_with_tour):
|
|
211
|
+
_, tour_path = repo_with_tour
|
|
212
|
+
before = tour_path.read_bytes()
|
|
213
|
+
result = migrate_tour_impl()
|
|
214
|
+
assert result["dry_run"] is True
|
|
215
|
+
assert result["exit_code"] in (0, 1)
|
|
216
|
+
assert tour_path.read_bytes() == before
|
|
217
|
+
assert result["output"] # the CLI's own narration, captured
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class TestStdioServer:
|
|
221
|
+
def test_smoke_initialize_and_tools_list(self):
|
|
222
|
+
# A real (minimal) MCP client over stdio: the v2 SDK enforces the
|
|
223
|
+
# session lifecycle, so batch-piping all messages races the
|
|
224
|
+
# initialize handshake and tools/list gets dropped — write, read
|
|
225
|
+
# the response, then proceed, like an actual client.
|
|
226
|
+
pytest.importorskip("mcp")
|
|
227
|
+
proc = subprocess.Popen(
|
|
228
|
+
[sys.executable, "-m", "codetour_cli.mcp_server"],
|
|
229
|
+
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
230
|
+
stderr=subprocess.PIPE, text=True,
|
|
231
|
+
)
|
|
232
|
+
try:
|
|
233
|
+
def send(msg):
|
|
234
|
+
proc.stdin.write(json.dumps(msg) + "\n")
|
|
235
|
+
proc.stdin.flush()
|
|
236
|
+
|
|
237
|
+
send({"jsonrpc": "2.0", "id": 1, "method": "initialize",
|
|
238
|
+
"params": {"protocolVersion": "2025-06-18", "capabilities": {},
|
|
239
|
+
"clientInfo": {"name": "smoke", "version": "0"}}})
|
|
240
|
+
init_resp = json.loads(proc.stdout.readline())
|
|
241
|
+
assert init_resp["id"] == 1
|
|
242
|
+
assert init_resp["result"]["serverInfo"]["name"] == "codetour-cli"
|
|
243
|
+
|
|
244
|
+
send({"jsonrpc": "2.0", "method": "notifications/initialized"})
|
|
245
|
+
send({"jsonrpc": "2.0", "id": 2, "method": "tools/list"})
|
|
246
|
+
tools_resp = json.loads(proc.stdout.readline())
|
|
247
|
+
assert tools_resp["id"] == 2
|
|
248
|
+
names = {t["name"] for t in tools_resp["result"]["tools"]}
|
|
249
|
+
assert names == {"tour_status", "lint_tours", "migrate_tour",
|
|
250
|
+
"read_review_report", "apply_review"}
|
|
251
|
+
finally:
|
|
252
|
+
proc.kill()
|
|
253
|
+
proc.wait(timeout=10)
|
codetour_cli-0.2.0/PKG-INFO
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: codetour-cli
|
|
3
|
-
Version: 0.2.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: dev
|
|
31
|
-
Requires-Dist: pytest; extra == "dev"
|
|
32
|
-
Requires-Dist: pytest-cov; extra == "dev"
|
|
33
|
-
Requires-Dist: black; extra == "dev"
|
|
34
|
-
Requires-Dist: ruff; extra == "dev"
|
|
35
|
-
Requires-Dist: mypy; extra == "dev"
|
|
36
|
-
Dynamic: license-file
|
|
37
|
-
|
|
38
|
-
# CodeTour CLI
|
|
39
|
-
|
|
40
|
-
A CLI to reliably migrate CodeTours in the background, and prevent drift.
|
|
41
|
-
|
|
42
|
-
> **Unofficial companion tool.** `codetour-cli` is an independent,
|
|
43
|
-
> unofficial companion to [CodeTour](https://github.com/microsoft/codetour),
|
|
44
|
-
> the VS Code extension by Jonathan Carter (Microsoft). It is not
|
|
45
|
-
> affiliated with, endorsed by, or maintained by Microsoft or the CodeTour
|
|
46
|
-
> project. It exists to *serve* CodeTour and its users — keeping `.tour`
|
|
47
|
-
> files accurate as the code they walk through evolves.
|
codetour_cli-0.2.0/README.md
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# CodeTour CLI
|
|
2
|
-
|
|
3
|
-
A CLI to reliably migrate CodeTours in the background, and prevent drift.
|
|
4
|
-
|
|
5
|
-
> **Unofficial companion tool.** `codetour-cli` is an independent,
|
|
6
|
-
> unofficial companion to [CodeTour](https://github.com/microsoft/codetour),
|
|
7
|
-
> the VS Code extension by Jonathan Carter (Microsoft). It is not
|
|
8
|
-
> affiliated with, endorsed by, or maintained by Microsoft or the CodeTour
|
|
9
|
-
> project. It exists to *serve* CodeTour and its users — keeping `.tour`
|
|
10
|
-
> files accurate as the code they walk through evolves.
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: codetour-cli
|
|
3
|
-
Version: 0.2.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: dev
|
|
31
|
-
Requires-Dist: pytest; extra == "dev"
|
|
32
|
-
Requires-Dist: pytest-cov; extra == "dev"
|
|
33
|
-
Requires-Dist: black; extra == "dev"
|
|
34
|
-
Requires-Dist: ruff; extra == "dev"
|
|
35
|
-
Requires-Dist: mypy; extra == "dev"
|
|
36
|
-
Dynamic: license-file
|
|
37
|
-
|
|
38
|
-
# CodeTour CLI
|
|
39
|
-
|
|
40
|
-
A CLI to reliably migrate CodeTours in the background, and prevent drift.
|
|
41
|
-
|
|
42
|
-
> **Unofficial companion tool.** `codetour-cli` is an independent,
|
|
43
|
-
> unofficial companion to [CodeTour](https://github.com/microsoft/codetour),
|
|
44
|
-
> the VS Code extension by Jonathan Carter (Microsoft). It is not
|
|
45
|
-
> affiliated with, endorsed by, or maintained by Microsoft or the CodeTour
|
|
46
|
-
> project. It exists to *serve* CodeTour and its users — keeping `.tour`
|
|
47
|
-
> files accurate as the code they walk through evolves.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|