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.
- {french_cli-0.4.0 → french_cli-0.4.1}/CHANGELOG.md +65 -0
- french_cli-0.4.1/CLAUDE.md +207 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/PKG-INFO +21 -21
- {french_cli-0.4.0 → french_cli-0.4.1}/README.md +20 -20
- {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/__init__.py +5 -2
- {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_commands/cli.py +3 -3
- {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_commands/doctor.py +1 -1
- {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_commands/explain.py +2 -2
- {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_commands/learn.py +19 -15
- {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_commands/overview.py +1 -1
- {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_commands/whoami.py +1 -1
- {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_errors.py +1 -1
- {french_cli-0.4.0 → french_cli-0.4.1}/french/explain/__init__.py +1 -1
- {french_cli-0.4.0 → french_cli-0.4.1}/french/explain/catalog.py +45 -35
- {french_cli-0.4.0 → french_cli-0.4.1}/pyproject.toml +1 -1
- {french_cli-0.4.0 → french_cli-0.4.1}/tests/test_cli.py +27 -6
- {french_cli-0.4.0 → french_cli-0.4.1}/uv.lock +1 -1
- french_cli-0.4.0/CLAUDE.md +0 -28
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/agent-config/SKILL.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/agent-config/scripts/show.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/ask-colleague/SKILL.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/ask-colleague/prompts/review.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/ask-colleague/prompts/write.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/cicd/SKILL.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/communicate/SKILL.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/recall/SKILL.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/recall/scripts/recall.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/remember/SKILL.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/remember/scripts/remember.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/run-tests/SKILL.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/sonarclaude/SKILL.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/spec-to-plan/SKILL.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/think/SKILL.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/think/scripts/think.sh +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/version-bump/SKILL.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/version-bump/scripts/bump.py +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills.local.yaml.example +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.flake8 +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.github/workflows/publish.yml +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.github/workflows/tests.yml +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.gitignore +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/.markdownlint-cli2.yaml +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/AGENTS.colleague.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/LICENSE +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/culture.yaml +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/docs/skill-sources.md +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/french/__init__.py +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/french/__main__.py +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_commands/__init__.py +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/french/cli/_output.py +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/sonar-project.properties +0 -0
- {french_cli-0.4.0 → french_cli-0.4.1}/tests/__init__.py +0 -0
- {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.
|
|
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
|
|
40
|
-
uv run french
|
|
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
|
-
##
|
|
63
|
+
## Status
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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
|
|
23
|
-
uv run french
|
|
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
|
-
##
|
|
46
|
+
## Status
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
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
|
|
74
|
-
description="french
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
15
|
+
french — a private French tutor (PyPI package: french-cli).
|
|
16
16
|
|
|
17
17
|
Purpose
|
|
18
18
|
-------
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
27
|
-
french
|
|
28
|
-
french
|
|
29
|
-
french
|
|
30
|
-
french
|
|
31
|
-
french
|
|
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
|
|
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": "
|
|
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
|
|
74
|
+
"explain_pointer": "french explain <path>",
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""``french
|
|
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
|
|
@@ -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
|
|
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
|
|
19
|
+
remediation="list entries with: french explain french",
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
"""Markdown catalog for ``french
|
|
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
|
|
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
|
|
17
|
+
# french
|
|
14
18
|
|
|
15
|
-
A
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
`
|
|
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
|
|
24
|
-
- `french
|
|
25
|
-
- `french
|
|
26
|
-
- `french
|
|
27
|
-
- `french
|
|
28
|
-
- `french
|
|
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
|
|
40
|
-
- `french
|
|
44
|
+
- `french explain whoami`
|
|
45
|
+
- `french explain doctor`
|
|
41
46
|
"""
|
|
42
47
|
|
|
43
48
|
_WHOAMI = """\
|
|
44
|
-
# french
|
|
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
|
|
52
|
-
french
|
|
56
|
+
french whoami
|
|
57
|
+
french whoami --json
|
|
53
58
|
"""
|
|
54
59
|
|
|
55
60
|
_LEARN = """\
|
|
56
|
-
# french
|
|
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
|
|
64
|
-
french
|
|
68
|
+
french learn
|
|
69
|
+
french learn --json
|
|
65
70
|
"""
|
|
66
71
|
|
|
67
72
|
_EXPLAIN = """\
|
|
68
|
-
# french
|
|
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
|
|
76
|
-
french
|
|
77
|
-
french
|
|
83
|
+
french explain french
|
|
84
|
+
french explain whoami
|
|
85
|
+
french explain --json <path>
|
|
78
86
|
"""
|
|
79
87
|
|
|
80
88
|
_OVERVIEW = """\
|
|
81
|
-
# french
|
|
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
|
|
90
|
-
french
|
|
97
|
+
french overview
|
|
98
|
+
french overview --json
|
|
91
99
|
"""
|
|
92
100
|
|
|
93
101
|
_DOCTOR = """\
|
|
94
|
-
# french
|
|
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
|
|
103
|
-
french
|
|
110
|
+
french doctor
|
|
111
|
+
french doctor --json
|
|
104
112
|
"""
|
|
105
113
|
|
|
106
114
|
_CLI = """\
|
|
107
|
-
# french
|
|
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
|
|
115
|
-
french
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
121
|
+
assert "french whoami" in payload["markdown"]
|
|
101
122
|
|
|
102
123
|
|
|
103
124
|
def test_explain_unknown_path_errors(capsys: pytest.CaptureFixture[str]) -> None:
|
french_cli-0.4.0/CLAUDE.md
DELETED
|
@@ -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
|
{french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/agent-config/data/backend-fingerprints.yaml
RENAMED
|
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
|
{french_cli-0.4.0 → french_cli-0.4.1}/.claude/skills/pypi-maintainer/scripts/switch-source.sh
RENAMED
|
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
|