codeforerunner 0.4.4__tar.gz → 0.4.6__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.
- {codeforerunner-0.4.4/src/codeforerunner.egg-info → codeforerunner-0.4.6}/PKG-INFO +16 -7
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/README.md +15 -6
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/pyproject.toml +2 -2
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/cli.py +54 -45
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/config.py +1 -1
- codeforerunner-0.4.6/src/codeforerunner/distribution.py +56 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/doctor.py +25 -44
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/installer.py +128 -126
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/mcp_server.py +30 -29
- codeforerunner-0.4.6/src/codeforerunner/prompt_session.py +93 -0
- codeforerunner-0.4.6/src/codeforerunner/prompts/tasks/arch-review.md +121 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/tasks/init-agent-onboarding.md +14 -1
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/tasks/scan.md +2 -0
- codeforerunner-0.4.6/src/codeforerunner/release_surfaces.json +77 -0
- codeforerunner-0.4.6/src/codeforerunner/release_surfaces.py +115 -0
- codeforerunner-0.4.6/src/codeforerunner/skill_parity.py +95 -0
- codeforerunner-0.4.6/src/codeforerunner/tasks.json +30 -0
- codeforerunner-0.4.6/src/codeforerunner/tasks.py +59 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6/src/codeforerunner.egg-info}/PKG-INFO +16 -7
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner.egg-info/SOURCES.txt +16 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/tests/test_check.py +3 -2
- codeforerunner-0.4.6/tests/test_check_versions.py +126 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/tests/test_cli.py +57 -4
- codeforerunner-0.4.6/tests/test_distribution.py +54 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/tests/test_doctor.py +1 -1
- codeforerunner-0.4.6/tests/test_inspect_npm_package.py +122 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/tests/test_installer.py +78 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/tests/test_mcp_server.py +88 -26
- codeforerunner-0.4.6/tests/test_package_metadata.py +35 -0
- codeforerunner-0.4.6/tests/test_prompt_session.py +80 -0
- codeforerunner-0.4.6/tests/test_release_surfaces.py +87 -0
- codeforerunner-0.4.6/tests/test_skill_parity.py +100 -0
- codeforerunner-0.4.6/tests/test_tasks.py +85 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/tests/test_workflows_yaml.py +76 -1
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/LICENSE.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/setup.cfg +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/__init__.py +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/bundle.py +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/check.py +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/partials/context-format.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/partials/output-rules.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/partials/stack-hints.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/system/base.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/tasks/api-docs.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/tasks/audit.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/tasks/changelog.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/tasks/check.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/tasks/diagrams.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/tasks/flows.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/tasks/readme.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/tasks/refresh.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/tasks/review.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/tasks/stack-docs.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner/prompts/tasks/version-audit.md +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner.egg-info/dependency_links.txt +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner.egg-info/entry_points.txt +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner.egg-info/requires.txt +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/src/codeforerunner.egg-info/top_level.txt +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/tests/test_bundle.py +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/tests/test_check_config_integration.py +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/tests/test_config.py +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/tests/test_examples.py +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/tests/test_hooks_manifest.py +0 -0
- {codeforerunner-0.4.4 → codeforerunner-0.4.6}/tests/test_validate_codex_marketplace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeforerunner
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.6
|
|
4
4
|
Summary: Model-agnostic repository documentation tooling (prompt-first; thin CLI).
|
|
5
5
|
Author: Derek Palmer
|
|
6
6
|
License-Expression: LicenseRef-Codeforerunner-SAL-0.1
|
|
@@ -29,7 +29,7 @@ Dynamic: license-file
|
|
|
29
29
|
|
|
30
30
|
# codeForerunner
|
|
31
31
|
|
|
32
|
-
[](https://socket.dev/npm/package/codeforerunner)
|
|
33
33
|
|
|
34
34
|
Model-agnostic repository documentation tooling. Ships a prompt pack for codebase analysis and doc generation, a thin Python CLI, an MCP server, drift-detection rules that keep docs honest — and native slash-command skills for Claude Code, Codex, Gemini CLI, and other agent CLIs.
|
|
35
35
|
|
|
@@ -44,10 +44,15 @@ Install forerunner's prompt pack as skills into your agent CLI. Each documentati
|
|
|
44
44
|
# One-liner (auto-detects Claude Code, Codex, Gemini CLI)
|
|
45
45
|
curl -fsSL https://raw.githubusercontent.com/derek-palmer/codeforerunner/main/install.sh | bash
|
|
46
46
|
|
|
47
|
+
# npm
|
|
48
|
+
npx -y codeforerunner
|
|
49
|
+
npm install -g codeforerunner
|
|
50
|
+
|
|
47
51
|
# Windows
|
|
48
52
|
irm https://raw.githubusercontent.com/derek-palmer/codeforerunner/main/install.ps1 | iex
|
|
49
53
|
|
|
50
|
-
# Via
|
|
54
|
+
# Via Python CLI
|
|
55
|
+
pip install codeforerunner
|
|
51
56
|
forerunner install --all claude
|
|
52
57
|
forerunner install --all codex
|
|
53
58
|
```
|
|
@@ -70,6 +75,7 @@ Then in your agent:
|
|
|
70
75
|
| `/forerunner-api-docs` | `api-docs` | Generate API reference docs |
|
|
71
76
|
| `/forerunner-diagrams` | `diagrams` | Generate Mermaid architecture diagrams |
|
|
72
77
|
| `/forerunner-flows` | `flows` | Document system flows |
|
|
78
|
+
| `/forerunner-arch-review` | `arch-review` | Rank architecture improvement candidates, inspired by Matt Pocock's [`/improve-codebase-architecture`](https://github.com/mattpocock/skills/tree/main/skills/engineering/improve-codebase-architecture) |
|
|
73
79
|
| `/forerunner-stack-docs` | `stack-docs` | Stack-specific developer docs |
|
|
74
80
|
| `/forerunner-version-audit` | `version-audit` | Audit pinned versions vs EOL |
|
|
75
81
|
| `/forerunner-check` | `check` | Check docs for staleness |
|
|
@@ -118,6 +124,9 @@ pip install codeforerunner
|
|
|
118
124
|
# Install skills into Claude Code
|
|
119
125
|
curl -fsSL https://raw.githubusercontent.com/derek-palmer/codeforerunner/main/install.sh | bash
|
|
120
126
|
|
|
127
|
+
# Or via npm
|
|
128
|
+
# npx -y codeforerunner
|
|
129
|
+
|
|
121
130
|
# In Claude Code:
|
|
122
131
|
# /forerunner-scan → scans your repo
|
|
123
132
|
# /forerunner-readme → generates README.md
|
|
@@ -139,7 +148,7 @@ jobs:
|
|
|
139
148
|
runs-on: ubuntu-latest
|
|
140
149
|
steps:
|
|
141
150
|
- uses: actions/checkout@v6.0.2
|
|
142
|
-
- uses: derek-palmer/codeforerunner@v0.4.
|
|
151
|
+
- uses: derek-palmer/codeforerunner@v0.4.6
|
|
143
152
|
with:
|
|
144
153
|
fail-on-drift: "true" # set "false" to warn-only
|
|
145
154
|
```
|
|
@@ -224,13 +233,13 @@ prompts/
|
|
|
224
233
|
├── scan.md api-docs.md audit.md
|
|
225
234
|
├── readme.md diagrams.md changelog.md
|
|
226
235
|
├── check.md flows.md version-audit.md
|
|
227
|
-
├── review.md stack-docs.md
|
|
236
|
+
├── review.md stack-docs.md arch-review.md
|
|
237
|
+
├── refresh.md
|
|
228
238
|
└── init-agent-onboarding.md
|
|
229
239
|
```
|
|
230
240
|
|
|
231
|
-
## Docs
|
|
241
|
+
## Docs
|
|
232
242
|
|
|
233
|
-
- `SPEC.md` — canonical phase/task tracker
|
|
234
243
|
- `docs/getting-started.md` — manual prompt use
|
|
235
244
|
- `docs/prompt-guide.md` — how system, partial, and task prompts compose
|
|
236
245
|
- `docs/editor-agent-setup.md` — adapting prompts to local agents
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# codeForerunner
|
|
4
4
|
|
|
5
|
-
[](https://socket.dev/npm/package/codeforerunner)
|
|
6
6
|
|
|
7
7
|
Model-agnostic repository documentation tooling. Ships a prompt pack for codebase analysis and doc generation, a thin Python CLI, an MCP server, drift-detection rules that keep docs honest — and native slash-command skills for Claude Code, Codex, Gemini CLI, and other agent CLIs.
|
|
8
8
|
|
|
@@ -17,10 +17,15 @@ Install forerunner's prompt pack as skills into your agent CLI. Each documentati
|
|
|
17
17
|
# One-liner (auto-detects Claude Code, Codex, Gemini CLI)
|
|
18
18
|
curl -fsSL https://raw.githubusercontent.com/derek-palmer/codeforerunner/main/install.sh | bash
|
|
19
19
|
|
|
20
|
+
# npm
|
|
21
|
+
npx -y codeforerunner
|
|
22
|
+
npm install -g codeforerunner
|
|
23
|
+
|
|
20
24
|
# Windows
|
|
21
25
|
irm https://raw.githubusercontent.com/derek-palmer/codeforerunner/main/install.ps1 | iex
|
|
22
26
|
|
|
23
|
-
# Via
|
|
27
|
+
# Via Python CLI
|
|
28
|
+
pip install codeforerunner
|
|
24
29
|
forerunner install --all claude
|
|
25
30
|
forerunner install --all codex
|
|
26
31
|
```
|
|
@@ -43,6 +48,7 @@ Then in your agent:
|
|
|
43
48
|
| `/forerunner-api-docs` | `api-docs` | Generate API reference docs |
|
|
44
49
|
| `/forerunner-diagrams` | `diagrams` | Generate Mermaid architecture diagrams |
|
|
45
50
|
| `/forerunner-flows` | `flows` | Document system flows |
|
|
51
|
+
| `/forerunner-arch-review` | `arch-review` | Rank architecture improvement candidates, inspired by Matt Pocock's [`/improve-codebase-architecture`](https://github.com/mattpocock/skills/tree/main/skills/engineering/improve-codebase-architecture) |
|
|
46
52
|
| `/forerunner-stack-docs` | `stack-docs` | Stack-specific developer docs |
|
|
47
53
|
| `/forerunner-version-audit` | `version-audit` | Audit pinned versions vs EOL |
|
|
48
54
|
| `/forerunner-check` | `check` | Check docs for staleness |
|
|
@@ -91,6 +97,9 @@ pip install codeforerunner
|
|
|
91
97
|
# Install skills into Claude Code
|
|
92
98
|
curl -fsSL https://raw.githubusercontent.com/derek-palmer/codeforerunner/main/install.sh | bash
|
|
93
99
|
|
|
100
|
+
# Or via npm
|
|
101
|
+
# npx -y codeforerunner
|
|
102
|
+
|
|
94
103
|
# In Claude Code:
|
|
95
104
|
# /forerunner-scan → scans your repo
|
|
96
105
|
# /forerunner-readme → generates README.md
|
|
@@ -112,7 +121,7 @@ jobs:
|
|
|
112
121
|
runs-on: ubuntu-latest
|
|
113
122
|
steps:
|
|
114
123
|
- uses: actions/checkout@v6.0.2
|
|
115
|
-
- uses: derek-palmer/codeforerunner@v0.4.
|
|
124
|
+
- uses: derek-palmer/codeforerunner@v0.4.6
|
|
116
125
|
with:
|
|
117
126
|
fail-on-drift: "true" # set "false" to warn-only
|
|
118
127
|
```
|
|
@@ -197,13 +206,13 @@ prompts/
|
|
|
197
206
|
├── scan.md api-docs.md audit.md
|
|
198
207
|
├── readme.md diagrams.md changelog.md
|
|
199
208
|
├── check.md flows.md version-audit.md
|
|
200
|
-
├── review.md stack-docs.md
|
|
209
|
+
├── review.md stack-docs.md arch-review.md
|
|
210
|
+
├── refresh.md
|
|
201
211
|
└── init-agent-onboarding.md
|
|
202
212
|
```
|
|
203
213
|
|
|
204
|
-
## Docs
|
|
214
|
+
## Docs
|
|
205
215
|
|
|
206
|
-
- `SPEC.md` — canonical phase/task tracker
|
|
207
216
|
- `docs/getting-started.md` — manual prompt use
|
|
208
217
|
- `docs/prompt-guide.md` — how system, partial, and task prompts compose
|
|
209
218
|
- `docs/editor-agent-setup.md` — adapting prompts to local agents
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codeforerunner"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.6"
|
|
8
8
|
description = "Model-agnostic repository documentation tooling (prompt-first; thin CLI)."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -50,7 +50,7 @@ forerunner = "codeforerunner.cli:main"
|
|
|
50
50
|
where = ["src"]
|
|
51
51
|
|
|
52
52
|
[tool.setuptools.package-data]
|
|
53
|
-
codeforerunner = ["py.typed", "prompts/**/*.md"]
|
|
53
|
+
codeforerunner = ["py.typed", "prompts/**/*.md", "tasks.json", "release_surfaces.json"]
|
|
54
54
|
|
|
55
55
|
[tool.pytest.ini_options]
|
|
56
56
|
testpaths = ["tests"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Thin CLI orchestration. Product logic lives in prompts/.
|
|
1
|
+
"""Thin CLI orchestration. Product logic lives in prompts/."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -8,77 +8,86 @@ import sys
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Sequence
|
|
10
10
|
|
|
11
|
-
from codeforerunner.bundle import find_prompts_root
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
from codeforerunner.bundle import find_prompts_root
|
|
12
|
+
from codeforerunner.prompt_session import OutcomeKind, PromptSession
|
|
13
|
+
from codeforerunner.tasks import refresh_tasks as _refresh_tasks
|
|
14
14
|
SCAN_DONE_ENV = "FORERUNNER_SCAN_DONE"
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def
|
|
18
|
-
"""
|
|
17
|
+
def _scan_satisfied(repo_root: Path) -> bool:
|
|
18
|
+
"""CLI scan-first signal: scan artifact present, env override set, or no config to gate."""
|
|
19
|
+
return (
|
|
20
|
+
(repo_root / ".forerunner" / "scan.md").is_file()
|
|
21
|
+
or bool(os.environ.get(SCAN_DONE_ENV))
|
|
22
|
+
or not (repo_root / "forerunner.config.yaml").is_file()
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _resolve_bundle(repo, task: str) -> tuple[str, int]:
|
|
27
|
+
"""Resolve bundle text for *task* under *repo*. Returns (text, exit_code).
|
|
28
|
+
|
|
29
|
+
Encodes the session's closed Outcome into CLI exit codes; the gate/order
|
|
30
|
+
lives in the Prompt Session, this is just the encoder.
|
|
31
|
+
"""
|
|
19
32
|
try:
|
|
20
|
-
prompts_root = find_prompts_root(
|
|
33
|
+
prompts_root = find_prompts_root(repo)
|
|
21
34
|
except FileNotFoundError as e:
|
|
22
35
|
print(f"error: {e}", file=sys.stderr)
|
|
23
36
|
return "", 2
|
|
24
37
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
repo_root = Path(repo).resolve() if repo else Path.cwd()
|
|
39
|
+
session = PromptSession(prompts_root, _scan_satisfied(repo_root))
|
|
40
|
+
outcome = session.resolve(task)
|
|
41
|
+
if outcome.kind is OutcomeKind.ALLOWED:
|
|
42
|
+
return outcome.text, 0
|
|
43
|
+
if outcome.kind is OutcomeKind.UNKNOWN_TASK:
|
|
44
|
+
print(f"error: unknown task '{task}'", file=sys.stderr)
|
|
28
45
|
return "", 2
|
|
29
|
-
|
|
30
|
-
repo_root = Path(args.repo) if args.repo else Path.cwd()
|
|
31
|
-
if (
|
|
32
|
-
args.task not in SCAN_EXEMPT_TASKS
|
|
33
|
-
and (repo_root / "forerunner.config.yaml").is_file()
|
|
34
|
-
and not os.environ.get(SCAN_DONE_ENV)
|
|
35
|
-
):
|
|
46
|
+
if outcome.kind is OutcomeKind.SCAN_REQUIRED:
|
|
36
47
|
print(
|
|
37
|
-
f"
|
|
38
|
-
f"
|
|
48
|
+
f"error: scan-first required — run `forerunner scan` first "
|
|
49
|
+
f"(writes .forerunner/scan.md). Set {SCAN_DONE_ENV}=1 to skip.",
|
|
39
50
|
file=sys.stderr,
|
|
40
51
|
)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
print(f"error: {e}", file=sys.stderr)
|
|
46
|
-
return "", 2
|
|
52
|
+
return "", 1
|
|
53
|
+
# MISSING
|
|
54
|
+
print(f"error: {outcome.message}", file=sys.stderr)
|
|
55
|
+
return "", 2
|
|
47
56
|
|
|
48
57
|
|
|
49
|
-
def
|
|
50
|
-
"""Resolve
|
|
51
|
-
bundle, rc =
|
|
58
|
+
def _emit_task(repo, task: str) -> int:
|
|
59
|
+
"""Resolve *task* under *repo* and write its bundle to stdout. Returns rc."""
|
|
60
|
+
bundle, rc = _resolve_bundle(repo, task)
|
|
52
61
|
if rc != 0:
|
|
53
62
|
return rc
|
|
54
63
|
sys.stdout.write(bundle)
|
|
55
64
|
return 0
|
|
56
65
|
|
|
57
66
|
|
|
58
|
-
def
|
|
59
|
-
"""
|
|
60
|
-
|
|
61
|
-
return cmd_doc(ns)
|
|
67
|
+
def cmd_doc(args: argparse.Namespace) -> int:
|
|
68
|
+
"""Resolve base + partials + task bundle to stdout."""
|
|
69
|
+
return _emit_task(args.repo, args.task)
|
|
62
70
|
|
|
63
71
|
|
|
64
72
|
def cmd_init(args: argparse.Namespace) -> int:
|
|
65
73
|
"""Emit onboarding bundle; prepend scan bundle when --full is given."""
|
|
74
|
+
repo = getattr(args, "repo", None)
|
|
66
75
|
if getattr(args, "full", False):
|
|
67
76
|
sys.stdout.write("<!-- forerunner init --full: section 1/2 (scan) -->\n")
|
|
68
|
-
rc =
|
|
77
|
+
rc = _emit_task(repo, "scan")
|
|
69
78
|
if rc != 0:
|
|
70
79
|
return rc
|
|
71
80
|
sys.stdout.write("\n<!-- forerunner init --full: section 2/2 (onboarding) -->\n")
|
|
72
|
-
return
|
|
81
|
+
return _emit_task(repo, "init-agent-onboarding")
|
|
73
82
|
|
|
74
83
|
|
|
75
84
|
def cmd_scan(args: argparse.Namespace) -> int:
|
|
76
|
-
"""Emit the scan prompt bundle and hint about
|
|
77
|
-
rc =
|
|
85
|
+
"""Emit the scan prompt bundle and hint about scan artifact."""
|
|
86
|
+
rc = _emit_task(getattr(args, "repo", None), "scan")
|
|
78
87
|
if rc == 0:
|
|
79
88
|
print(
|
|
80
|
-
|
|
81
|
-
"scan-first
|
|
89
|
+
"hint: write the scan result to .forerunner/scan.md to satisfy the "
|
|
90
|
+
f"scan-first gate on follow-up calls. Or set {SCAN_DONE_ENV}=1 to skip.",
|
|
82
91
|
file=sys.stderr,
|
|
83
92
|
)
|
|
84
93
|
return rc
|
|
@@ -111,19 +120,19 @@ def cmd_mcp_server(args: argparse.Namespace) -> int:
|
|
|
111
120
|
except FileNotFoundError as e:
|
|
112
121
|
print(f"mcp_server: {e}", file=sys.stderr)
|
|
113
122
|
return 2
|
|
114
|
-
|
|
123
|
+
repo_root = Path(args.repo).resolve() if args.repo else Path.cwd()
|
|
124
|
+
return mcp_server.serve(prompts_root, repo_root=repo_root)
|
|
115
125
|
|
|
116
126
|
|
|
117
127
|
def cmd_refresh(args: argparse.Namespace) -> int:
|
|
118
128
|
"""Emit scan + check + all doc-task bundles to stdout for a full doc refresh."""
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
for i, task in enumerate(
|
|
122
|
-
|
|
123
|
-
rc = cmd_doc(ns)
|
|
129
|
+
repo = getattr(args, "repo", None)
|
|
130
|
+
task_names = [t.name for t in _refresh_tasks()]
|
|
131
|
+
for i, task in enumerate(task_names):
|
|
132
|
+
rc = _emit_task(repo, task)
|
|
124
133
|
if rc != 0:
|
|
125
134
|
return rc
|
|
126
|
-
if i < len(
|
|
135
|
+
if i < len(task_names) - 1:
|
|
127
136
|
sys.stdout.write("\n---\n\n")
|
|
128
137
|
return 0
|
|
129
138
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Distribution Inventory — single source of truth for distribution artifact
|
|
2
|
+
identity and install policy.
|
|
3
|
+
|
|
4
|
+
Owns the packaging facts that were previously duplicated across the installer,
|
|
5
|
+
doctor, and validation scripts: the canonical skill path, its distributed copy
|
|
6
|
+
paths, the Codex marketplace manifest path, the managed-region markers, and the
|
|
7
|
+
default install-destination templates. Consumers consult this module instead of
|
|
8
|
+
re-declaring constants, so a packaging change is one edit here.
|
|
9
|
+
|
|
10
|
+
Mirrors the Task Registry (``tasks.py``) and Release Surface Manifest
|
|
11
|
+
(``release_surfaces.py``) single-source pattern.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
# --- artifact identity (repo-relative) ------------------------------------
|
|
19
|
+
|
|
20
|
+
# Source of truth for the codeforerunner skill body; copies derive from it.
|
|
21
|
+
CANONICAL_SKILL_REL = Path("agent/codeforerunner.skill.md")
|
|
22
|
+
|
|
23
|
+
# Distributed copies whose bodies must match the canonical (V10 body parity).
|
|
24
|
+
DISTRIBUTED_SKILL_COPIES_REL: tuple[Path, ...] = (
|
|
25
|
+
Path("plugins/codeforerunner/skills/codeforerunner/SKILL.md"),
|
|
26
|
+
Path("skills/codeforerunner/SKILL.md"),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Codex marketplace manifest shipped as a release asset.
|
|
30
|
+
MARKETPLACE_MANIFEST_REL = Path("plugins/codex/marketplace.json")
|
|
31
|
+
|
|
32
|
+
# --- managed-region markers -----------------------------------------------
|
|
33
|
+
|
|
34
|
+
# Delimit the installer-owned region in a destination file so re-runs are
|
|
35
|
+
# idempotent and unmanaged content is never clobbered.
|
|
36
|
+
MARKER_BEGIN = "<!-- forerunner:begin managed=codeforerunner.skill -->"
|
|
37
|
+
MARKER_END = "<!-- forerunner:end -->"
|
|
38
|
+
|
|
39
|
+
# --- install-destination templates ----------------------------------------
|
|
40
|
+
|
|
41
|
+
# Agents whose default skill destination the inventory can resolve.
|
|
42
|
+
SKILL_DEST_AGENTS: tuple[str, ...] = ("codex", "claude")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def skill_destination(agent: str, slug: str, home: Path) -> Path:
|
|
46
|
+
"""Default install path for skill ``slug`` under ``agent``, relative to ``home``."""
|
|
47
|
+
if agent == "codex":
|
|
48
|
+
return home / f".codex/skills/{slug}/SKILL.md"
|
|
49
|
+
if agent == "claude":
|
|
50
|
+
return home / f".claude/plugins/codeforerunner/skills/{slug}/SKILL.md"
|
|
51
|
+
raise ValueError(f"no default skill destination for agent {agent!r} (expected: {', '.join(SKILL_DEST_AGENTS)})")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def marketplace_destination(home: Path) -> Path:
|
|
55
|
+
"""Default install path for the Codex marketplace manifest, relative to ``home``."""
|
|
56
|
+
return home / ".codex/marketplaces/codeforerunner.json"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""`forerunner doctor` — single-screen health report.
|
|
1
|
+
"""`forerunner doctor` — single-screen health report."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -11,17 +11,18 @@ from dataclasses import dataclass
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Callable
|
|
13
13
|
|
|
14
|
+
from codeforerunner import distribution as _dist
|
|
15
|
+
from codeforerunner import skill_parity as _parity
|
|
14
16
|
from codeforerunner.config import CONFIG_FILENAME, ConfigError, load_from_repo
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
MARKETPLACE_REL = Path("plugins/codex/marketplace.json")
|
|
18
|
+
# Distribution artifact identity and markers come from the Distribution
|
|
19
|
+
# Inventory; re-exported here for callers/tests that import them off doctor.
|
|
20
|
+
CANONICAL_REL = _dist.CANONICAL_SKILL_REL
|
|
21
|
+
SKILL_COPIES_REL: tuple[Path, ...] = _dist.DISTRIBUTED_SKILL_COPIES_REL
|
|
22
|
+
MARKETPLACE_REL = _dist.MARKETPLACE_MANIFEST_REL
|
|
22
23
|
|
|
23
|
-
MARKER_BEGIN =
|
|
24
|
-
MARKER_END =
|
|
24
|
+
MARKER_BEGIN = _dist.MARKER_BEGIN
|
|
25
|
+
MARKER_END = _dist.MARKER_END
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
@dataclass(frozen=True)
|
|
@@ -37,14 +38,14 @@ def _installed_skill_destinations() -> list[Path]:
|
|
|
37
38
|
"""Return default install paths for the codeforerunner skill across supported agents."""
|
|
38
39
|
home = Path(os.path.expanduser("~"))
|
|
39
40
|
return [
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
_dist.skill_destination(agent, "codeforerunner", home)
|
|
42
|
+
for agent in _dist.SKILL_DEST_AGENTS
|
|
42
43
|
]
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
def _installed_marketplace_destination() -> Path:
|
|
46
47
|
"""Return default install path for the Codex marketplace manifest."""
|
|
47
|
-
return Path(os.path.expanduser("~"))
|
|
48
|
+
return _dist.marketplace_destination(Path(os.path.expanduser("~")))
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
def _load_script_module(repo: Path, relpath: str, module_name: str):
|
|
@@ -62,25 +63,14 @@ def _load_script_module(repo: Path, relpath: str, module_name: str):
|
|
|
62
63
|
|
|
63
64
|
|
|
64
65
|
def _check_skill_body_parity(repo: Path, run_scripts: bool = False) -> list[Finding]:
|
|
65
|
-
"""Verify that all distributed skill copies match the canonical body.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
]
|
|
74
|
-
try:
|
|
75
|
-
skill_mod = _load_script_module(
|
|
76
|
-
repo, "scripts/validate_skill_copies.py", "_forerunner_doctor_skill_copies"
|
|
77
|
-
)
|
|
78
|
-
strip_frontmatter: Callable[[str], str] = skill_mod.strip_frontmatter
|
|
79
|
-
except Exception as exc: # pragma: no cover - defensive
|
|
80
|
-
return [Finding("error", "skill-body-parity", f"loader failure: {exc}")]
|
|
81
|
-
|
|
82
|
-
canonical_path = repo / CANONICAL_REL
|
|
83
|
-
if not canonical_path.is_file():
|
|
66
|
+
"""Verify that all distributed skill copies match the canonical body.
|
|
67
|
+
|
|
68
|
+
Body parity is owned by the Skill Body Parity module, which only reads
|
|
69
|
+
files (no target-repo code is executed), so this runs regardless of
|
|
70
|
+
``run_scripts`` — that flag still gates checks that load repo scripts.
|
|
71
|
+
"""
|
|
72
|
+
result = _parity.check_skill_body_parity(repo)
|
|
73
|
+
if result.missing_canonical:
|
|
84
74
|
return [
|
|
85
75
|
Finding(
|
|
86
76
|
"error",
|
|
@@ -88,21 +78,12 @@ def _check_skill_body_parity(repo: Path, run_scripts: bool = False) -> list[Find
|
|
|
88
78
|
f"canonical skill missing: {CANONICAL_REL}",
|
|
89
79
|
)
|
|
90
80
|
]
|
|
91
|
-
canonical_body = strip_frontmatter(canonical_path.read_text(encoding="utf-8"))
|
|
92
81
|
|
|
93
82
|
findings: list[Finding] = []
|
|
94
|
-
for rel in
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
Finding("error", "skill-body-parity", f"copy missing: {rel}")
|
|
99
|
-
)
|
|
100
|
-
continue
|
|
101
|
-
body = strip_frontmatter(p.read_text(encoding="utf-8"))
|
|
102
|
-
if body != canonical_body:
|
|
103
|
-
findings.append(
|
|
104
|
-
Finding("error", "skill-body-parity", f"body drift in {rel}")
|
|
105
|
-
)
|
|
83
|
+
for rel in result.missing_copies:
|
|
84
|
+
findings.append(Finding("error", "skill-body-parity", f"copy missing: {rel}"))
|
|
85
|
+
for rel in result.drifted_copies:
|
|
86
|
+
findings.append(Finding("error", "skill-body-parity", f"body drift in {rel}"))
|
|
106
87
|
if not findings:
|
|
107
88
|
findings.append(
|
|
108
89
|
Finding(
|