french-cli 0.4.0__tar.gz → 0.4.1__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 (76) hide show
  1. {french_cli-0.4.0 → french_cli-0.4.1}/CHANGELOG.md +65 -0
  2. french_cli-0.4.1/CLAUDE.md +207 -0
  3. {french_cli-0.4.0 → french_cli-0.4.1}/PKG-INFO +21 -21
  4. {french_cli-0.4.0 → french_cli-0.4.1}/README.md +20 -20
  5. {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/__init__.py +5 -2
  6. {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_commands/cli.py +3 -3
  7. {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_commands/doctor.py +1 -1
  8. {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_commands/explain.py +2 -2
  9. {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_commands/learn.py +19 -15
  10. {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_commands/overview.py +1 -1
  11. {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_commands/whoami.py +1 -1
  12. {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_errors.py +1 -1
  13. {french_cli-0.4.0 → french_cli-0.4.1}/french/explain/__init__.py +1 -1
  14. {french_cli-0.4.0 → french_cli-0.4.1}/french/explain/catalog.py +45 -35
  15. {french_cli-0.4.0 → french_cli-0.4.1}/pyproject.toml +1 -1
  16. {french_cli-0.4.0 → french_cli-0.4.1}/tests/test_cli.py +27 -6
  17. {french_cli-0.4.0 → french_cli-0.4.1}/uv.lock +1 -1
  18. french_cli-0.4.0/CLAUDE.md +0 -28
  19. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/agent-config/SKILL.md +0 -0
  20. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
  21. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/agent-config/scripts/show.sh +0 -0
  22. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/ask-colleague/SKILL.md +0 -0
  23. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
  24. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/ask-colleague/prompts/review.md +0 -0
  25. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/ask-colleague/prompts/write.md +0 -0
  26. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +0 -0
  27. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  28. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
  29. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/cicd/SKILL.md +0 -0
  30. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
  31. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
  32. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
  33. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
  34. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  35. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/communicate/SKILL.md +0 -0
  36. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  37. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  38. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  39. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  40. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
  41. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
  42. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
  43. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
  44. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
  45. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
  46. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/recall/SKILL.md +0 -0
  47. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/recall/scripts/recall.sh +0 -0
  48. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/remember/SKILL.md +0 -0
  49. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/remember/scripts/remember.sh +0 -0
  50. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/run-tests/SKILL.md +0 -0
  51. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/run-tests/scripts/test.sh +0 -0
  52. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/sonarclaude/SKILL.md +0 -0
  53. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
  54. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/spec-to-plan/SKILL.md +0 -0
  55. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
  56. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/think/SKILL.md +0 -0
  57. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/think/scripts/think.sh +0 -0
  58. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/version-bump/SKILL.md +0 -0
  59. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/version-bump/scripts/bump.py +0 -0
  60. {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills.local.yaml.example +0 -0
  61. {french_cli-0.4.0 → french_cli-0.4.1}/.flake8 +0 -0
  62. {french_cli-0.4.0 → french_cli-0.4.1}/.github/workflows/publish.yml +0 -0
  63. {french_cli-0.4.0 → french_cli-0.4.1}/.github/workflows/tests.yml +0 -0
  64. {french_cli-0.4.0 → french_cli-0.4.1}/.gitignore +0 -0
  65. {french_cli-0.4.0 → french_cli-0.4.1}/.markdownlint-cli2.yaml +0 -0
  66. {french_cli-0.4.0 → french_cli-0.4.1}/AGENTS.colleague.md +0 -0
  67. {french_cli-0.4.0 → french_cli-0.4.1}/LICENSE +0 -0
  68. {french_cli-0.4.0 → french_cli-0.4.1}/culture.yaml +0 -0
  69. {french_cli-0.4.0 → french_cli-0.4.1}/docs/skill-sources.md +0 -0
  70. {french_cli-0.4.0 → french_cli-0.4.1}/french/__init__.py +0 -0
  71. {french_cli-0.4.0 → french_cli-0.4.1}/french/__main__.py +0 -0
  72. {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_commands/__init__.py +0 -0
  73. {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_output.py +0 -0
  74. {french_cli-0.4.0 → french_cli-0.4.1}/sonar-project.properties +0 -0
  75. {french_cli-0.4.0 → french_cli-0.4.1}/tests/__init__.py +0 -0
  76. {french_cli-0.4.0 → french_cli-0.4.1}/tests/test_cli_introspection.py +0 -0
@@ -5,6 +5,71 @@ All notable changes to this project will be documented in this file.
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/). This project
6
6
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.1] - 2026-07-10
9
+
10
+ ### Changed
11
+
12
+ - **Re-initialized `CLAUDE.md` from the bootstrap seed into a full runtime
13
+ prompt** (`/init`). The seed only carried the agent description and a
14
+ "run `/init`" instruction; it is now a working prompt derived from the code
15
+ as it actually behaves. Documents: that the French-tutor domain is **not yet
16
+ implemented** (every verb is still generic template introspection, and the
17
+ package docstrings still describe the repo as a clonable template); the
18
+ agent-first contract enforced by `teken cli doctor . --strict` and which
19
+ apparently-dead code it makes load-bearing (`overview`'s ignored `target`
20
+ positional, the `cli` noun group, `_CliArgumentParser._json_hint`); the
21
+ `backend: colleague` → `AGENTS.colleague.md` coupling that `doctor.py`'s
22
+ `_PROMPT_FILE` map and `test_doctor_recognizes_declared_backend` enforce
23
+ together, so a backend promotion cannot silently skip the mapping; the
24
+ cite-don't-import rules for `.claude/skills/` (including that `type: command`
25
+ is load-bearing — `core.skill_loader` silently skips a `SKILL.md` without it);
26
+ and the version-bump-every-PR gate.
27
+
28
+ The seed also re-introduced the stale `backend: claude` / `CLAUDE.md` claim
29
+ that 0.3.4 had already corrected elsewhere; the new file states the actual
30
+ `colleague` / `AGENTS.colleague.md` identity, and notes that `CLAUDE.md`
31
+ guides Claude Code sessions while `AGENTS.colleague.md` is the resident
32
+ prompt this agent runs on.
33
+
34
+ - **Settled the CLI's name as a deliberate two-name split**, and documented the
35
+ rule in `CLAUDE.md`: **invocations say `french`** (the console script, the
36
+ argparse `prog`, usage lines, command maps, `explain` paths, help text) while
37
+ **identity says `french-cli`** (the distribution / PyPI name, the repo, the
38
+ `culture.yaml` mesh nick, `_FALLBACK_NICK`, `_pkg_version`, the `overview`
39
+ subject, the `doctor` report header). `learn --json` now exposes both: the
40
+ existing `tool` field carries the distribution, and a new `command` field
41
+ carries the invocation. `README.md`'s CLI table and quickstart were rewritten
42
+ to the real command.
43
+
44
+ - **`README.md`: replaced the vestigial "Make it your own" section.** It was
45
+ cloner boilerplate — this repo *is* the clone — and its step 1 pointed at a
46
+ `git grep` discovery command "in `CLAUDE.md`" that never existed there. It is
47
+ now a "Status" section stating plainly that the French-tutor domain is not
48
+ built yet.
49
+
50
+ ### Fixed
51
+
52
+ - **The CI `lint` job was red on a clean checkout**, and had been since the
53
+ template rename: `uv run teken cli doctor . --strict` exited 1 with
54
+ ``FAIL (error) explain_self: `explain french` exit=1``. The agent-first rubric
55
+ resolves the tool's own name from `[project.scripts]` (→ `french`) and
56
+ requires `explain <that name>` to resolve, but `french/explain/catalog.py`
57
+ keyed its root entry only on `("french-cli",)`, and the argparse `prog` also
58
+ disagreed with the installed script. The catalog root is now reachable under
59
+ **both** `("french",)` and `("french-cli",)`, `prog` is `french`, and every
60
+ usage string in the package (`learn.py`'s `_TEXT` *and* `_as_json_payload`,
61
+ the `explain` remediation hint, the command docstrings and `--help` text) was
62
+ moved onto the `french` invocation. The rubric now passes 26/26.
63
+
64
+ `test_explain_self` was widened to assert **both** names resolve, and a new
65
+ `test_explain_self_matches_console_script` pins the catalog key to the
66
+ console script so this specific regression cannot recur silently.
67
+
68
+ - `uv.lock` had drifted out of sync with `pyproject.toml` — the lock still
69
+ pinned `french-cli 0.3.4` while the project declared `0.4.0` (the 0.4.0 bump
70
+ never re-locked). `uv sync` refreshed the pin and re-sorted the package
71
+ blocks into alphabetical order.
72
+
8
73
  ## [0.4.0] - 2026-06-23
9
74
 
10
75
  ### Added
@@ -0,0 +1,207 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What this repo is right now
6
+
7
+ `french-cli` is intended to become an **agent + CLI that turns Claude into a
8
+ private French tutor** (track progress, get an overview, get advice, read
9
+ stories, learn & practice French written and spoken from your phone).
10
+
11
+ **None of that is implemented yet.** What exists today is the
12
+ `culture-agent-template` scaffold, renamed: an agent-first CLI skeleton, a mesh
13
+ identity, a vendored skill kit, and a CI/publish baseline. Every verb
14
+ (`whoami`, `learn`, `explain`, `overview`, `doctor`, `cli overview`) is generic
15
+ introspection — no French domain code exists. `learn` and the `explain` root
16
+ entry now describe the tutor *intent*, but nothing behind them teaches French.
17
+ Expect to write the domain from scratch. Some prose still carries the template's
18
+ framing (`french/__init__.py`'s docstring, `overview`'s "sibling-pattern
19
+ artifacts" section); update it as the real surface lands.
20
+
21
+ ## Commands
22
+
23
+ ```bash
24
+ uv sync # install (creates .venv)
25
+ uv run french whoami # run the CLI — the command is `french`
26
+ uv run pytest -n auto # full suite (23 tests, ~1s)
27
+ uv run pytest tests/test_cli.py::test_whoami_text -v # a single test
28
+ ```
29
+
30
+ The four gates CI runs, in the order they'll bite you:
31
+
32
+ ```bash
33
+ uv run black --check french tests
34
+ uv run isort --check-only french tests
35
+ uv run flake8 french tests
36
+ uv run bandit -c pyproject.toml -r french
37
+ npx markdownlint-cli2@0.21.0 "**/*.md" "#node_modules" "#.local" "#.claude/skills" "#.teken"
38
+ uv run teken cli doctor . --strict # the agent-first rubric gate
39
+ ```
40
+
41
+ CI's coverage invocation (`fail_under = 60`):
42
+
43
+ ```bash
44
+ uv run pytest -n auto --cov=french --cov-report=xml:coverage.xml --cov-report=term -v
45
+ ```
46
+
47
+ ## Naming: `french` is the command, `french-cli` is the package
48
+
49
+ Two names coexist **on purpose**. Don't "fix" one into the other.
50
+
51
+ | Thing | Value |
52
+ |-------|-------|
53
+ | Console script / argparse `prog` — what you type | `french` |
54
+ | Distribution, PyPI name, repo, mesh nick (`culture.yaml` `suffix`) | `french-cli` |
55
+ | Import package | `french` |
56
+
57
+ The rule when you add a string: **invocations say `french`** (usage lines,
58
+ command maps, `explain` paths, help text); **identity says `french-cli`**
59
+ (`_FALLBACK_NICK`, `learn --json`'s `tool` field, `_pkg_version("french-cli")`,
60
+ the `overview` subject, the `doctor` report header). `learn --json` exposes both
61
+ — `tool` is the distribution, `command` is what to invoke.
62
+
63
+ The agent-first rubric resolves the tool's own name from `[project.scripts]`
64
+ and requires `explain <that name>` to resolve, so `french/explain/catalog.py`
65
+ keys its root entry on **both** `("french",)` and `("french-cli",)`. Dropping
66
+ the `("french",)` key turns the CI `lint` job red with
67
+ `FAIL explain_self: explain french exit=1` — that was a real defect, fixed in
68
+ 0.4.1, and `test_explain_self_matches_console_script` now guards it.
69
+
70
+ If you ever rename the console script, the argparse `prog`, the catalog keys,
71
+ `learn.py`'s hand-maintained `_TEXT` **and** `_as_json_payload`, and the README
72
+ must all move together.
73
+
74
+ ## Architecture: the agent-first contract
75
+
76
+ The CLI is cited (not imported) from [teken](https://github.com/agentculture/teken)'s
77
+ `python-cli` reference. Its shape is enforced by an external rubric —
78
+ `teken cli doctor . --strict`, run in CI — so the structure below is a
79
+ **contract, not a style preference**. The runtime has zero third-party
80
+ dependencies (`dependencies = []`); `teken` is dev-only.
81
+
82
+ **Output discipline** (`french/cli/_output.py`) — results go to stdout,
83
+ errors and diagnostics go to stderr, and they never mix. Every command takes
84
+ `--json`. Agents parse this; don't break it.
85
+
86
+ **Errors** (`french/cli/_errors.py`) — every failure raises
87
+ `CliError(code, message, remediation)`. `main()` catches it, renders
88
+ `error: <message>` + `hint: <remediation>` to stderr, and exits with the code.
89
+ Any other exception is wrapped so no Python traceback ever escapes. Exit codes:
90
+ `0` success, `1` user error, `2` environment error, `3+` reserved.
91
+
92
+ Two subtleties in `french/cli/__init__.py` that look odd until you know why:
93
+
94
+ - `_CliArgumentParser` overrides argparse's `.error()` so parse failures
95
+ (unknown verb, bad flag) route through the same `error:`/`hint:` contract and
96
+ exit `1` — argparse's default writes its own format and exits `2`.
97
+ - Parse errors happen *before* `args.json` exists, so `main()` pre-scans raw
98
+ argv for `--json` and stashes the answer on the class-level `_json_hint`.
99
+ `parser_class=_CliArgumentParser` is passed to `add_subparsers` so every
100
+ subparser (including nested noun groups) inherits both behaviors.
101
+
102
+ **Adding a verb.** Create `french/cli/_commands/<verb>.py` exposing
103
+ `register(sub)`, call it from `_build_parser()`, and **add a matching entry to
104
+ `french/explain/catalog.py`** — the rubric fails if a registered path has no
105
+ `explain` entry. Give it `--json`. Noun groups follow the same pattern; see
106
+ `_commands/cli.py`, which exists solely because the rubric requires any noun
107
+ with action-verbs to expose `overview`.
108
+
109
+ Some non-obvious rubric requirements already encoded in the code, which will
110
+ look like dead weight if you refactor blindly:
111
+
112
+ - `overview` accepts an **ignored** `target` positional — descriptive verbs must
113
+ never hard-fail on a missing path (`overview /no/such/path` exits 0).
114
+ - `learn` must be ≥200 chars and mention purpose, the command map, exit codes,
115
+ `--json`, and `explain`. Its text and JSON payload are maintained by hand and
116
+ drift from reality easily — update both when you add a verb.
117
+ - `doctor` must emit `{healthy, checks: [{id, passed, severity, message, remediation}]}`.
118
+
119
+ ## Identity and the mesh
120
+
121
+ `culture.yaml` declares this agent to the AgentCulture IRC mesh:
122
+
123
+ ```yaml
124
+ agents:
125
+ - suffix: french-cli
126
+ backend: colleague
127
+ model: sakamakismile/Qwen3.6-27B-Text-NVFP4-MTP
128
+ ```
129
+
130
+ The backend is **`colleague`**, so the resident prompt file this agent runs on
131
+ is **`AGENTS.colleague.md`** — not this file. `CLAUDE.md` guides Claude Code
132
+ sessions; `AGENTS.colleague.md` is what the colleague backend loads. Keep both
133
+ in mind when you change how the agent describes itself.
134
+
135
+ `french/cli/_commands/doctor.py` enforces the `backend` → prompt-file mapping
136
+ (`claude`→`CLAUDE.md`, `colleague`→`AGENTS.colleague.md`, `acp`→`AGENTS.md`,
137
+ `gemini`→`GEMINI.md`), mirroring `steward doctor`'s invariants. **Changing
138
+ `backend:` in `culture.yaml` requires updating `_PROMPT_FILE` in `doctor.py`**,
139
+ or `test_doctor_recognizes_declared_backend` fails. That test exists precisely
140
+ to catch a backend promotion that forgets the mapping.
141
+
142
+ `whoami.py` parses `culture.yaml` with hand-rolled line scanning rather than a
143
+ YAML library, to keep runtime dependencies empty. It walks up from `__file__`
144
+ (not the CWD) so the identity is always *this agent's*, not whatever
145
+ `culture.yaml` sits in the caller's working directory. In a wheel install no
146
+ `culture.yaml` ships, and both `whoami` and `doctor` degrade to defaults.
147
+
148
+ ## Vendored skills — cite, don't import
149
+
150
+ `.claude/skills/` holds 14 skills, vendored verbatim: 11 from
151
+ [guildmaster](https://github.com/agentculture/guildmaster), plus `ask-colleague`
152
+ from `colleague` and `remember`/`recall` from `eidetic-cli`. Provenance and the
153
+ re-sync procedure live in [`docs/skill-sources.md`](docs/skill-sources.md).
154
+
155
+ - **Don't hand-edit script bodies.** Re-sync from upstream instead. Where an
156
+ in-place patch was unavoidable (the `agex`→`devex` rename, the
157
+ `outsource`→`ask-colleague` rename) it is recorded as a tracked divergence in
158
+ `docs/skill-sources.md` with an upstream issue. Follow that pattern.
159
+ - Every `SKILL.md` must carry `type: command` in its frontmatter. This is
160
+ **load-bearing**: `core.skill_loader` silently skips any `SKILL.md` without a
161
+ `type:`, even where guildmaster's upstream copy omits it.
162
+ - `markdownlint` and `sonar.exclusions` both skip `.claude/skills/**` — vendored
163
+ copies are cited verbatim and must not be reformatted.
164
+
165
+ Optional runtime tools these skills shell out to: `devex` (>=0.21, the `cicd` PR
166
+ lane), `agtag` (>=0.1, `communicate` issue I/O), `colleague` and `eidetic`
167
+ (optional; the wrappers exit with an install hint when absent).
168
+
169
+ **Reach for `ask-colleague` reflexively.** The read-only verbs (`review`,
170
+ `explore`) run isolated in a throwaway git worktree with zero side effects — run
171
+ `review` for a diverse second opinion before opening a PR on a non-trivial diff,
172
+ and `explore` for a fresh read of an unfamiliar area. The side-effecting
173
+ `write --apply` / `write --pr` needs the user's go-ahead first.
174
+
175
+ ## Conventions
176
+
177
+ **Every PR bumps the version — even docs-only, config-only, or CI-only PRs.**
178
+ The `version-check` CI job diffs `pyproject.toml`'s version against `main`,
179
+ comments on the PR, and fails the build if they match. Use the skill:
180
+
181
+ ```bash
182
+ python3 .claude/skills/version-bump/scripts/bump.py patch # or minor / major
183
+ ```
184
+
185
+ It updates `pyproject.toml` and prepends a [Keep a Changelog](https://keepachangelog.com/)
186
+ entry to `CHANGELOG.md`. Write the changelog entry properly — this repo's
187
+ history uses it as the real record of *why* a change happened, and existing
188
+ entries are detailed prose, not one-liners.
189
+
190
+ Formatting is line-length 100 across `black`, `isort` (profile `black`), and
191
+ `flake8` (which extends-ignores `E203`/`W503` to stop fighting `black`).
192
+
193
+ `[tool.coverage.run] relative_files = true` is not decorative: without it,
194
+ `coverage.xml` emits absolute paths that SonarCloud cannot map to
195
+ `sonar.sources=french`, and coverage silently reports as empty.
196
+
197
+ Use the `cicd` skill for the PR lifecycle (`create PR`, `review comments`,
198
+ `address feedback`, `status`, `await`). It wraps `devex pr` and appends the
199
+ `- french-cli (Claude)` signature automatically via `_resolve-nick.sh` — don't
200
+ sign PR replies by hand when using it.
201
+
202
+ ## Publishing
203
+
204
+ `.github/workflows/publish.yml` fires only on changes to `pyproject.toml` or
205
+ `french/**`. Pull requests publish a `.dev<run_number>` build to TestPyPI;
206
+ pushes to `main` publish to PyPI. Both use Trusted Publishing (OIDC), so fork
207
+ PRs skip the publish step rather than failing.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: french-cli
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: Agent + CLI that turns Claude into a private French tutor: track progress, get an overview, get advice, read stories, and learn & practice French (written and spoken) online from your phone.
5
5
  Project-URL: Homepage, https://github.com/agentculture/french-cli
6
6
  Project-URL: Issues, https://github.com/agentculture/french-cli/issues
@@ -36,40 +36,40 @@ Agent + CLI that turns Claude into a private French tutor: track progress, get a
36
36
  ```bash
37
37
  uv sync
38
38
  uv run pytest -n auto # run the test suite
39
- uv run french-cli whoami # identity from culture.yaml
40
- uv run french-cli learn # self-teaching prompt (add --json)
39
+ uv run french whoami # identity from culture.yaml
40
+ uv run french learn # self-teaching prompt (add --json)
41
41
  uv run teken cli doctor . --strict # the agent-first rubric gate CI runs
42
42
  ```
43
43
 
44
+ The installed command is **`french`**; the PyPI package and repo are
45
+ **`french-cli`**. `french explain french` and `french explain french-cli` both
46
+ resolve to the root doc entry.
47
+
44
48
  ## CLI
45
49
 
46
50
  | Verb | What it does |
47
51
  |------|--------------|
48
- | `whoami` | Report this agent's nick, version, backend, and model from `culture.yaml`. |
49
- | `learn` | Print a structured self-teaching prompt. |
50
- | `explain <path>` | Markdown docs for any noun/verb path. |
51
- | `overview` | Read-only descriptive snapshot of the agent. |
52
- | `doctor` | Check the agent-identity invariants (prompt-file-present, backend-consistency). |
53
- | `cli overview` | Describe the CLI surface itself. |
52
+ | `french whoami` | Report this agent's nick, version, backend, and model from `culture.yaml`. |
53
+ | `french learn` | Print a structured self-teaching prompt. |
54
+ | `french explain <path>` | Markdown docs for any noun/verb path. |
55
+ | `french overview` | Read-only descriptive snapshot of the agent. |
56
+ | `french doctor` | Check the agent-identity invariants (prompt-file-present, backend-consistency). |
57
+ | `french cli overview` | Describe the CLI surface itself. |
54
58
 
55
59
  Every command supports `--json`. Results go to stdout, errors/diagnostics to
56
60
  stderr (never mixed). Exit codes: `0` success, `1` user error, `2` environment
57
61
  error, `3+` reserved.
58
62
 
59
- ## Make it your own
63
+ ## Status
60
64
 
61
- 1. Rename the package `french/` and the `french-cli`
62
- CLI/dist name throughout `pyproject.toml`, the package, `tests/`,
63
- `sonar-project.properties`, and this `README.md`. The name is hard-coded in
64
- ~100 places, so list every occurrence first see the `git grep` discovery
65
- command in [`CLAUDE.md`](CLAUDE.md), the authoritative rename procedure.
66
- 2. Edit `culture.yaml` with your `suffix` and `backend`.
67
- 3. Rewrite `CLAUDE.md` for your agent and run `/init`.
68
- 4. Re-vendor only the skills you need from guildmaster (see
69
- [`docs/skill-sources.md`](docs/skill-sources.md)).
65
+ **The French-tutor domain is not implemented yet.** The scaffold — identity,
66
+ skill kit, CI, and the agent-first CLI contract — is in place, but every verb
67
+ today is generic introspection (`whoami`, `learn`, `explain`, `overview`,
68
+ `doctor`, `cli overview`). Progress tracking, advice, stories, and written /
69
+ spoken practice are still to be built.
70
70
 
71
- See [`CLAUDE.md`](CLAUDE.md) for the full conventions (version-bump-every-PR,
72
- the `cicd` PR lane, deploy setup).
71
+ See [`CLAUDE.md`](CLAUDE.md) for the architecture and the full conventions
72
+ (version-bump-every-PR, the `cicd` PR lane, deploy setup).
73
73
 
74
74
  ## License
75
75
 
@@ -19,40 +19,40 @@ Agent + CLI that turns Claude into a private French tutor: track progress, get a
19
19
  ```bash
20
20
  uv sync
21
21
  uv run pytest -n auto # run the test suite
22
- uv run french-cli whoami # identity from culture.yaml
23
- uv run french-cli learn # self-teaching prompt (add --json)
22
+ uv run french whoami # identity from culture.yaml
23
+ uv run french learn # self-teaching prompt (add --json)
24
24
  uv run teken cli doctor . --strict # the agent-first rubric gate CI runs
25
25
  ```
26
26
 
27
+ The installed command is **`french`**; the PyPI package and repo are
28
+ **`french-cli`**. `french explain french` and `french explain french-cli` both
29
+ resolve to the root doc entry.
30
+
27
31
  ## CLI
28
32
 
29
33
  | Verb | What it does |
30
34
  |------|--------------|
31
- | `whoami` | Report this agent's nick, version, backend, and model from `culture.yaml`. |
32
- | `learn` | Print a structured self-teaching prompt. |
33
- | `explain <path>` | Markdown docs for any noun/verb path. |
34
- | `overview` | Read-only descriptive snapshot of the agent. |
35
- | `doctor` | Check the agent-identity invariants (prompt-file-present, backend-consistency). |
36
- | `cli overview` | Describe the CLI surface itself. |
35
+ | `french whoami` | Report this agent's nick, version, backend, and model from `culture.yaml`. |
36
+ | `french learn` | Print a structured self-teaching prompt. |
37
+ | `french explain <path>` | Markdown docs for any noun/verb path. |
38
+ | `french overview` | Read-only descriptive snapshot of the agent. |
39
+ | `french doctor` | Check the agent-identity invariants (prompt-file-present, backend-consistency). |
40
+ | `french cli overview` | Describe the CLI surface itself. |
37
41
 
38
42
  Every command supports `--json`. Results go to stdout, errors/diagnostics to
39
43
  stderr (never mixed). Exit codes: `0` success, `1` user error, `2` environment
40
44
  error, `3+` reserved.
41
45
 
42
- ## Make it your own
46
+ ## Status
43
47
 
44
- 1. Rename the package `french/` and the `french-cli`
45
- CLI/dist name throughout `pyproject.toml`, the package, `tests/`,
46
- `sonar-project.properties`, and this `README.md`. The name is hard-coded in
47
- ~100 places, so list every occurrence first see the `git grep` discovery
48
- command in [`CLAUDE.md`](CLAUDE.md), the authoritative rename procedure.
49
- 2. Edit `culture.yaml` with your `suffix` and `backend`.
50
- 3. Rewrite `CLAUDE.md` for your agent and run `/init`.
51
- 4. Re-vendor only the skills you need from guildmaster (see
52
- [`docs/skill-sources.md`](docs/skill-sources.md)).
48
+ **The French-tutor domain is not implemented yet.** The scaffold — identity,
49
+ skill kit, CI, and the agent-first CLI contract — is in place, but every verb
50
+ today is generic introspection (`whoami`, `learn`, `explain`, `overview`,
51
+ `doctor`, `cli overview`). Progress tracking, advice, stories, and written /
52
+ spoken practice are still to be built.
53
53
 
54
- See [`CLAUDE.md`](CLAUDE.md) for the full conventions (version-bump-every-PR,
55
- the `cicd` PR lane, deploy setup).
54
+ See [`CLAUDE.md`](CLAUDE.md) for the architecture and the full conventions
55
+ (version-bump-every-PR, the `cicd` PR lane, deploy setup).
56
56
 
57
57
  ## License
58
58
 
@@ -69,9 +69,12 @@ def _build_parser() -> argparse.ArgumentParser:
69
69
  from french.cli._commands import overview as _overview_cmd
70
70
  from french.cli._commands import whoami as _whoami_cmd
71
71
 
72
+ # prog is the installed console script (`french`), not the distribution
73
+ # name (`french-cli`). The agent-first rubric resolves the tool's own name
74
+ # from [project.scripts] and requires `explain <that name>` to work.
72
75
  parser = _CliArgumentParser(
73
- prog="french-cli",
74
- description="french-cli — a clonable template for AgentCulture mesh agents.",
76
+ prog="french",
77
+ description="french — a private French tutor (PyPI package: french-cli).",
75
78
  )
76
79
  parser.add_argument(
77
80
  "--version",
@@ -1,4 +1,4 @@
1
- """``french-cli cli`` — noun grouping CLI-surface introspection.
1
+ """``french cli`` — noun grouping CLI-surface introspection.
2
2
 
3
3
  Exists to satisfy the agent-first rubric's ``overview_cli_noun_exists`` check:
4
4
  any noun with action-verbs must also expose ``overview``. There are no
@@ -23,14 +23,14 @@ def cmd_cli_overview(args: argparse.Namespace) -> int:
23
23
 
24
24
 
25
25
  def _no_verb(args: argparse.Namespace) -> int:
26
- # `french-cli cli` with no sub-verb prints the noun's overview.
26
+ # `french cli` with no sub-verb prints the noun's overview.
27
27
  return cmd_cli_overview(args)
28
28
 
29
29
 
30
30
  def register(sub: argparse._SubParsersAction) -> None:
31
31
  p = sub.add_parser(
32
32
  "cli",
33
- help="CLI-surface introspection (see 'french-cli cli overview').",
33
+ help="CLI-surface introspection (see 'french cli overview').",
34
34
  )
35
35
  p.add_argument("--json", action="store_true", help="Emit structured JSON.")
36
36
  p.set_defaults(func=_no_verb, json=False)
@@ -1,4 +1,4 @@
1
- """``french-cli doctor`` — check the agent-identity invariants.
1
+ """``french doctor`` — check the agent-identity invariants.
2
2
 
3
3
  Mirrors the two invariants ``steward doctor`` verifies for a mesh agent:
4
4
 
@@ -1,4 +1,4 @@
1
- """``french-cli explain <path>...`` — global markdown catalog lookup (stable-contract).
1
+ """``french explain <path>...`` — global markdown catalog lookup (stable-contract).
2
2
 
3
3
  ``explain`` is global (not nested under a noun). It takes zero or more path
4
4
  tokens and resolves them via the catalog in :mod:`french.explain`.
@@ -32,7 +32,7 @@ def register(sub: argparse._SubParsersAction) -> None:
32
32
  p.add_argument(
33
33
  "path",
34
34
  nargs="*",
35
- help="Command path tokens; empty = root (same as 'french-cli').",
35
+ help="Command path tokens; empty = root (same as 'french').",
36
36
  )
37
37
  p.add_argument("--json", action="store_true", help="Emit structured JSON.")
38
38
  p.set_defaults(func=cmd_explain)
@@ -1,4 +1,4 @@
1
- """``french-cli learn`` — the learnability affordance.
1
+ """``french learn`` — the learnability affordance.
2
2
 
3
3
  Prints a structured self-teaching prompt. Must satisfy the agent-first rubric:
4
4
  >=200 chars and mention purpose, command map, exit codes, --json, and explain.
@@ -12,23 +12,24 @@ from french import __version__
12
12
  from french.cli._output import emit_result
13
13
 
14
14
  _TEXT = """\
15
- french-cli — a clonable template for AgentCulture mesh agents.
15
+ french — a private French tutor (PyPI package: french-cli).
16
16
 
17
17
  Purpose
18
18
  -------
19
- Scaffold for a new Culture mesh agent: an agent-first CLI (cited from the teken
20
- `python-cli` reference), an identity (culture.yaml + CLAUDE.md), the canonical
21
- guildmaster skill kit under .claude/skills/, and a deploy/CI baseline. Clone it,
22
- rename the package, and edit culture.yaml to mint a new agent.
19
+ Track progress, get an overview, get advice, read stories, and learn & practice
20
+ French (written and spoken) online from your phone. Built on an agent-first CLI
21
+ (cited from the teken `python-cli` reference) with a Culture mesh identity
22
+ (culture.yaml + AGENTS.colleague.md) and the guildmaster skill kit under
23
+ .claude/skills/.
23
24
 
24
25
  Commands
25
26
  --------
26
- french-cli whoami Identity from culture.yaml.
27
- french-cli learn This self-teaching prompt.
28
- french-cli explain <path>... Markdown docs for any noun/verb path.
29
- french-cli overview Descriptive snapshot of the agent.
30
- french-cli doctor Check the agent-identity invariants.
31
- french-cli cli overview Describe the CLI surface itself.
27
+ french whoami Identity from culture.yaml.
28
+ french learn This self-teaching prompt.
29
+ french explain <path>... Markdown docs for any noun/verb path.
30
+ french overview Descriptive snapshot of the agent.
31
+ french doctor Check the agent-identity invariants.
32
+ french cli overview Describe the CLI surface itself.
32
33
 
33
34
  Machine-readable output
34
35
  -----------------------
@@ -44,15 +45,18 @@ Exit-code policy
44
45
 
45
46
  More detail
46
47
  -----------
47
- french-cli explain french-cli
48
+ french explain french
48
49
  """
49
50
 
50
51
 
51
52
  def _as_json_payload() -> dict[str, object]:
52
53
  return {
54
+ # `tool` is the distribution / mesh nick; `command` is what you invoke.
53
55
  "tool": "french-cli",
56
+ "command": "french",
54
57
  "version": __version__,
55
- "purpose": "Clonable scaffold for a new AgentCulture mesh agent.",
58
+ "purpose": "A private French tutor: track progress, read stories, and "
59
+ "practice French written and spoken.",
56
60
  "commands": [
57
61
  {"path": ["whoami"], "summary": "Identity probe from culture.yaml."},
58
62
  {"path": ["learn"], "summary": "Self-teaching prompt."},
@@ -67,7 +71,7 @@ def _as_json_payload() -> dict[str, object]:
67
71
  "2": "environment/setup error",
68
72
  },
69
73
  "json_support": True,
70
- "explain_pointer": "french-cli explain <path>",
74
+ "explain_pointer": "french explain <path>",
71
75
  }
72
76
 
73
77
 
@@ -1,4 +1,4 @@
1
- """``french-cli overview`` — read-only descriptive snapshot of the agent.
1
+ """``french overview`` — read-only descriptive snapshot of the agent.
2
2
 
3
3
  Describes the agent to an agent reader: identity (from culture.yaml), the verb
4
4
  surface, and the sibling-pattern artifacts this template carries. The shared
@@ -1,4 +1,4 @@
1
- """``french-cli whoami`` — the smallest identity probe.
1
+ """``french whoami`` — the smallest identity probe.
2
2
 
3
3
  Reports the agent's identity as declared in ``culture.yaml``: its nick
4
4
  (``suffix``), the backend it runs on, and the served model (if any) — plus the
@@ -13,7 +13,7 @@ from __future__ import annotations
13
13
 
14
14
  from dataclasses import dataclass
15
15
 
16
- # Exit-code policy. Documented in ``french-cli learn`` output.
16
+ # Exit-code policy. Documented in ``french learn`` output.
17
17
  # 0 = success
18
18
  # 1 = user-input error (bad flag, missing required arg, unknown path)
19
19
  # 2 = environment / setup error (tool not installed, file unreadable)
@@ -16,7 +16,7 @@ def resolve(path: tuple[str, ...]) -> str:
16
16
  raise CliError(
17
17
  code=EXIT_USER_ERROR,
18
18
  message=f"no explain entry for: {display}",
19
- remediation="list entries with: french-cli explain french-cli",
19
+ remediation="list entries with: french explain french",
20
20
  )
21
21
 
22
22
 
@@ -1,7 +1,11 @@
1
- """Markdown catalog for ``french-cli explain <path>``.
1
+ """Markdown catalog for ``french explain <path>``.
2
2
 
3
3
  Each entry is verbatim markdown. Keys are command-path tuples. The empty tuple
4
- and ``("french-cli",)`` both resolve to the root entry.
4
+ and both spellings of the tool's name — ``("french",)`` (the installed console
5
+ script) and ``("french-cli",)`` (the distribution / repo / mesh nick) — resolve
6
+ to the root entry. The agent-first rubric resolves the tool's own name from
7
+ ``[project.scripts]`` and requires ``explain <that name>`` to work; the
8
+ distribution alias is kept because that is the name on PyPI and in the mesh.
5
9
 
6
10
  Keep bodies self-contained: an agent reading one entry should get enough
7
11
  context without chaining reads.
@@ -10,22 +14,23 @@ context without chaining reads.
10
14
  from __future__ import annotations
11
15
 
12
16
  _ROOT = """\
13
- # french-cli
17
+ # french
14
18
 
15
- A clonable template for AgentCulture mesh agents. It carries an agent-first CLI
16
- (cited from the teken `python-cli` reference), a mesh identity (`culture.yaml` +
17
- `CLAUDE.md`), the canonical guildmaster skill kit under `.claude/skills/`, and a
18
- buildable/deployable package baseline. Clone it, rename the package, edit
19
- `culture.yaml`, and you have a new agent.
19
+ A private French tutor: track progress, get an overview, get advice, read
20
+ stories, and learn & practice French (written and spoken) online from your
21
+ phone.
22
+
23
+ Installed command: `french`. PyPI package and repo: `french-cli`. Mesh nick:
24
+ `french-cli`.
20
25
 
21
26
  ## Verbs
22
27
 
23
- - `french-cli whoami` — identity probe from `culture.yaml`.
24
- - `french-cli learn` — structured self-teaching prompt.
25
- - `french-cli explain <path>` — markdown docs for any noun/verb.
26
- - `french-cli overview` — descriptive snapshot of the agent.
27
- - `french-cli doctor` — check the agent-identity invariants.
28
- - `french-cli cli overview` — describe the CLI surface.
28
+ - `french whoami` — identity probe from `culture.yaml`.
29
+ - `french learn` — structured self-teaching prompt.
30
+ - `french explain <path>` — markdown docs for any noun/verb.
31
+ - `french overview` — descriptive snapshot of the agent.
32
+ - `french doctor` — check the agent-identity invariants.
33
+ - `french cli overview` — describe the CLI surface.
29
34
 
30
35
  ## Exit-code policy
31
36
 
@@ -36,49 +41,52 @@ buildable/deployable package baseline. Clone it, rename the package, edit
36
41
 
37
42
  ## See also
38
43
 
39
- - `french-cli explain whoami`
40
- - `french-cli explain doctor`
44
+ - `french explain whoami`
45
+ - `french explain doctor`
41
46
  """
42
47
 
43
48
  _WHOAMI = """\
44
- # french-cli whoami
49
+ # french whoami
45
50
 
46
51
  Reports the agent's identity from `culture.yaml`: nick (`suffix`), backend,
47
52
  served model, and the package version. Read-only.
48
53
 
49
54
  ## Usage
50
55
 
51
- french-cli whoami
52
- french-cli whoami --json
56
+ french whoami
57
+ french whoami --json
53
58
  """
54
59
 
55
60
  _LEARN = """\
56
- # french-cli learn
61
+ # french learn
57
62
 
58
63
  Prints a structured self-teaching prompt covering purpose, command map,
59
64
  exit-code policy, `--json` support, and the `explain` pointer.
60
65
 
61
66
  ## Usage
62
67
 
63
- french-cli learn
64
- french-cli learn --json
68
+ french learn
69
+ french learn --json
65
70
  """
66
71
 
67
72
  _EXPLAIN = """\
68
- # french-cli explain <path>
73
+ # french explain <path>
69
74
 
70
75
  Prints markdown documentation for any noun/verb path. Unlike `--help` (terse,
71
76
  positional), `explain` is global and addressable by path.
72
77
 
78
+ Both `french explain french` and `french explain french-cli` resolve to this
79
+ root entry — the command is `french`, the distribution is `french-cli`.
80
+
73
81
  ## Usage
74
82
 
75
- french-cli explain french-cli
76
- french-cli explain whoami
77
- french-cli explain --json <path>
83
+ french explain french
84
+ french explain whoami
85
+ french explain --json <path>
78
86
  """
79
87
 
80
88
  _OVERVIEW = """\
81
- # french-cli overview
89
+ # french overview
82
90
 
83
91
  Read-only descriptive snapshot of the agent: identity (from `culture.yaml`), the
84
92
  verb surface, and the sibling-pattern artifacts the template carries. Accepts an
@@ -86,12 +94,12 @@ ignored `target` so a stray path never hard-fails.
86
94
 
87
95
  ## Usage
88
96
 
89
- french-cli overview
90
- french-cli overview --json
97
+ french overview
98
+ french overview --json
91
99
  """
92
100
 
93
101
  _DOCTOR = """\
94
- # french-cli doctor
102
+ # french doctor
95
103
 
96
104
  Checks the agent-identity invariants `steward doctor` verifies:
97
105
  prompt-file-present and backend-consistency (`colleague` → `AGENTS.colleague.md`), plus a
@@ -99,25 +107,27 @@ skills-present check. Exits 1 when unhealthy.
99
107
 
100
108
  ## Usage
101
109
 
102
- french-cli doctor
103
- french-cli doctor --json
110
+ french doctor
111
+ french doctor --json
104
112
  """
105
113
 
106
114
  _CLI = """\
107
- # french-cli cli
115
+ # french cli
108
116
 
109
117
  Noun group for CLI-surface introspection. `cli overview` describes the CLI
110
118
  itself (distinct from the global `overview`, which describes the agent).
111
119
 
112
120
  ## Usage
113
121
 
114
- french-cli cli overview
115
- french-cli cli overview --json
122
+ french cli overview
123
+ french cli overview --json
116
124
  """
117
125
 
118
126
 
119
127
  ENTRIES: dict[tuple[str, ...], str] = {
120
128
  (): _ROOT,
129
+ # Both the console script and the distribution name resolve to the root.
130
+ ("french",): _ROOT,
121
131
  ("french-cli",): _ROOT,
122
132
  ("whoami",): _WHOAMI,
123
133
  ("learn",): _LEARN,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "french-cli"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  description = "Agent + CLI that turns Claude into a private French tutor: track progress, get an overview, get advice, read stories, and learn & practice French (written and spoken) online from your phone."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -21,7 +21,7 @@ def test_version_flag(capsys: pytest.CaptureFixture[str]) -> None:
21
21
  def test_no_args_prints_help(capsys: pytest.CaptureFixture[str]) -> None:
22
22
  rc = main([])
23
23
  assert rc == 0
24
- assert "usage: french-cli" in capsys.readouterr().out
24
+ assert "usage: french" in capsys.readouterr().out
25
25
 
26
26
 
27
27
  def test_unknown_command_errors(capsys: pytest.CaptureFixture[str]) -> None:
@@ -72,7 +72,9 @@ def test_learn_json(capsys: pytest.CaptureFixture[str]) -> None:
72
72
  rc = main(["learn", "--json"])
73
73
  assert rc == 0
74
74
  payload = json.loads(capsys.readouterr().out)
75
+ # `tool` is the distribution / mesh nick; `command` is what you invoke.
75
76
  assert payload["tool"] == "french-cli"
77
+ assert payload["command"] == "french"
76
78
  assert payload["version"] == __version__
77
79
  assert payload["json_support"] is True
78
80
 
@@ -83,13 +85,32 @@ def test_learn_json(capsys: pytest.CaptureFixture[str]) -> None:
83
85
  def test_explain_root(capsys: pytest.CaptureFixture[str]) -> None:
84
86
  rc = main(["explain"])
85
87
  assert rc == 0
86
- assert "# french-cli" in capsys.readouterr().out
88
+ out = capsys.readouterr().out
89
+ # Titled by the command; names the distribution so both are discoverable.
90
+ assert out.startswith("# french\n")
91
+ assert "french-cli" in out
87
92
 
88
93
 
89
94
  def test_explain_self(capsys: pytest.CaptureFixture[str]) -> None:
90
- rc = main(["explain", "french-cli"])
91
- assert rc == 0
92
- assert capsys.readouterr().out.startswith("#")
95
+ # The agent-first rubric resolves the tool's name from [project.scripts]
96
+ # and requires `explain <that name>` to work. The console script is
97
+ # `french`; `french-cli` is the distribution / mesh nick. Both resolve.
98
+ for name in ("french", "french-cli"):
99
+ rc = main(["explain", name])
100
+ assert rc == 0, f"explain {name} failed"
101
+ assert capsys.readouterr().out.startswith("#")
102
+
103
+
104
+ def test_explain_self_matches_console_script() -> None:
105
+ """`explain <console-script>` must resolve — this is what the rubric checks.
106
+
107
+ Guards the defect that made `teken cli doctor --strict` fail: the catalog
108
+ keyed its root on the distribution name while the script was named `french`.
109
+ """
110
+ from french.explain import known_paths
111
+
112
+ assert ("french",) in known_paths()
113
+ assert ("french-cli",) in known_paths()
93
114
 
94
115
 
95
116
  def test_explain_json(capsys: pytest.CaptureFixture[str]) -> None:
@@ -97,7 +118,7 @@ def test_explain_json(capsys: pytest.CaptureFixture[str]) -> None:
97
118
  assert rc == 0
98
119
  payload = json.loads(capsys.readouterr().out)
99
120
  assert payload["path"] == ["whoami"]
100
- assert "french-cli whoami" in payload["markdown"]
121
+ assert "french whoami" in payload["markdown"]
101
122
 
102
123
 
103
124
  def test_explain_unknown_path_errors(capsys: pytest.CaptureFixture[str]) -> None:
@@ -179,7 +179,7 @@ wheels = [
179
179
 
180
180
  [[package]]
181
181
  name = "french-cli"
182
- version = "0.4.0"
182
+ version = "0.4.1"
183
183
  source = { editable = "." }
184
184
 
185
185
  [package.dev-dependencies]
@@ -1,28 +0,0 @@
1
- # CLAUDE.md — seed / bootstrap placeholder
2
-
3
- > **This is a self-initializing seed, not a finished runtime prompt.**
4
- > Run `/init` (or describe the agent's domain to your AI assistant) to
5
- > re-initialize this file into a full runtime prompt, using the description
6
- > below and the scaffolded repo as context.
7
-
8
- ## Agent
9
-
10
- This repository hosts the **french-cli** agent.
11
-
12
- ## Description
13
-
14
- Agent + CLI that turns Claude into a private French tutor: track progress, get an overview, get advice, read stories, and learn & practice French (written and spoken) online from your phone.
15
-
16
- ## Re-init instruction
17
-
18
- This file is a seed. To expand it into your full runtime prompt:
19
-
20
- 1. Open this repo in Claude Code (or your preferred AI assistant).
21
- 2. Run `/init` — the assistant will read the repo, incorporate the description
22
- above, and replace this seed with a complete `CLAUDE.md`.
23
- 3. Commit the result.
24
-
25
- Until you run `/init`, `french-cli` satisfies the `steward doctor`
26
- `prompt-file-present` and `backend-consistency` invariants (a `CLAUDE.md`
27
- exists and `culture.yaml` declares `backend: claude`) but the prompt is not
28
- yet tailored to this agent's domain.
File without changes
File without changes
File without changes
File without changes
File without changes