diff --git a/CHANGELOG.md b/CHANGELOG.md index a5c34c8..b0f391d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. Format follows [Keep a Changelog](https://keepachangelog.com/). This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.1] - 2026-06-13 + +### Fixed + +- **Console script renamed `tensor` → `tensor-cli`** in `[project.scripts]`. The scaffold bound the executable to `tensor` while the `prog` name, `--help`, explain catalog, tests, and README all used `tensor-cli` — so the installed command didn't match the docs, and the agent-first rubric gate (`teken cli doctor`, which derives the tool name from the first `[project.scripts]` entry) failed `explain_self` (`tensor explain tensor` had no catalog entry). The command is now `tensor-cli` everywhere. + +### Changed + +- **`/init`: expanded `CLAUDE.md` from the bootstrap seed into a full runtime guide.** Documents that the repo is still the `culture-agent-template` scaffold (no tensor-domain logic yet), the CLI dispatch/`register()` architecture, the `CliError` / stdout-stderr / `--json` contracts, the explain catalog, and the load-bearing agent-first rubric (`teken cli doctor . --strict`). Captures the console-script gotcha (the executable is `tensor`, not `tensor-cli`), the version-bump-every-PR + SonarCloud conventions, the cite-don't-import skill-kit rule, and the clone/rename procedure. + ## [0.2.0] - 2026-06-06 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index 95c832e..f269ffe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,28 +1,160 @@ -# CLAUDE.md — seed / bootstrap placeholder +# CLAUDE.md -> **This is a self-initializing seed, not a finished runtime prompt.** -> Run `/init` (or describe the agent's domain to your AI assistant) to -> re-initialize this file into a full runtime prompt, using the description -> below and the scaffolded repo as context. +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Agent +## What this repo actually is -This repository hosts the **tensor-cli** agent. +`tensor-cli` is a **clonable AgentCulture mesh-agent template**, scaffolded from +`culture-agent-template`. Despite the description "Agent/CLI for tensor +operations and ML tensor manipulation," **no tensor logic exists yet** — the +codebase is the template baseline: an agent-first CLI (the introspection verbs +below), a mesh identity, the vendored skill kit, and CI/deploy wiring. The +tensor domain is the *intended* build, not the current state. Treat domain work +as greenfield; add it as new noun groups (see "Adding a verb or noun" below). -## Description +The runtime package (`tensor/`) has **zero third-party dependencies** — keep it +that way. The CLI is cited (cite-don't-import) from the `teken` `python-cli` +reference; new code should follow the same patterns rather than pull deps. -Agent/CLI for tensor operations and ML tensor manipulation +## Commands -## Re-init instruction +```bash +uv sync # create .venv + install (incl. dev group) +uv run pytest -n auto # full suite (xdist parallel) +uv run pytest tests/test_cli.py::test_whoami_json # a single test +uv run pytest -n auto --cov=tensor --cov-report=term # with coverage (CI gate: 60%) +uv run teken cli doctor . --strict # the agent-first rubric gate CI enforces +``` -This file is a seed. To expand it into your full runtime prompt: +Lint (all must pass — CI runs each): -1. Open this repo in Claude Code (or your preferred AI assistant). -2. Run `/init` — the assistant will read the repo, incorporate the description - above, and replace this seed with a complete `CLAUDE.md`. -3. Commit the result. +```bash +uv run black --check tensor tests +uv run isort --check-only tensor tests +uv run flake8 tensor tests +uv run bandit -c pyproject.toml -r tensor +markdownlint-cli2 "**/*.md" "#node_modules" "#.local" "#.claude/skills" "#.teken" +``` -Until you run `/init`, `tensor-cli` satisfies the `steward doctor` -`prompt-file-present` and `backend-consistency` invariants (a `CLAUDE.md` -exists and `culture.yaml` declares `backend: claude`) but the prompt is not -yet tailored to this agent's domain. +`markdownlint-cli2` is a Node tool, not part of the uv dev group — install it +separately (CI pins `npm install -g markdownlint-cli2@0.21.0`). The other four +come from `uv sync`. + +### Running the CLI + +The console command, the distribution name, the argparse `prog`, the explain +catalog keys, and `[project.scripts]` all agree on **`tensor-cli`**: + +```bash +uv run tensor-cli whoami # installed console script +uv run python -m tensor whoami # equivalent (module entry point) +``` + +Note the import package is still `tensor/` (so `python -m tensor`), while the +*command* is `tensor-cli`. The agent-first rubric (`teken cli doctor`) derives +the tool name from the first `[project.scripts]` entry and runs +` explain ` against the catalog — so that script name **must** match +a catalog key in `tensor/explain/catalog.py`. If you rename the command, rename +the catalog key (and `prog`) in the same change or the rubric gate fails. + +## Architecture + +A single argparse tree with a uniform plugin-style registration contract. + +- **`tensor/cli/__init__.py`** — `main()` → `_build_parser()` → `_dispatch()`. + Each command module exposes a `register(sub)` that adds its subparser and sets + `func`. To add a command you import it in `_build_parser()` and call + `register(sub)` — nothing else is wired. +- **Error contract (`tensor/cli/_errors.py`)** — every failure raises + `CliError(code, message, remediation)`. `_dispatch()` catches it (and wraps any + stray exception) so **no Python traceback ever reaches stderr**. Argparse + errors are also routed through this: `_CliArgumentParser.error()` overrides the + default `prog: error:` / exit-2 behaviour and emits the structured form with + exit 1. Subparsers inherit it via `parser_class=_CliArgumentParser`. +- **Output contract (`tensor/cli/_output.py`)** — **results to stdout, + errors/diagnostics to stderr, never mixed.** Text errors render as + `error: …\nhint: …` (the `hint:` prefix is required by the agent-first rubric). + `--json` routes structured payloads to the same streams. Every verb takes + `--json`; honour both modes in any new command. +- **Exit codes** — `0` success, `1` user error, `2` environment error, `3+` + reserved. Centralised in `_errors.py`; don't invent ad-hoc codes. +- **`--json` parse-time peek** — JSON-mode errors can fire *before* `args.json` + exists (argparse failures). `main()` pre-scans raw argv and sets + `_CliArgumentParser._json_hint` so even parse errors honour `--json`. Preserve + this if you touch arg parsing. +- **Explain catalog (`tensor/explain/`)** — `tensor/explain/catalog.py` is a + `dict[tuple[str,...], str]` of verbatim markdown keyed by command path; + `resolve()` looks it up or raises `CliError`. Every noun/verb you add should + get a matching catalog entry. Note the coverage gap: + `test_every_catalog_path_resolves` only asserts that the *existing* catalog + keys resolve — it does **not** verify that every registered command has an + entry, so a command added without one won't fail that test. Only the rubric's + `explain_self` (the root command) is enforced in CI; entries for other verbs + are a convention you must uphold by hand. + +### The agent-first rubric (load-bearing) + +CI gates on `teken cli doctor . --strict`. This is why the introspection verbs +exist and have specific shapes — don't delete them casually: + +- `learn` must be ≥200 chars and mention purpose, command map, exit codes, + `--json`, and `explain`. +- Any noun with action-verbs must also expose `overview` (this is the entire + reason the `cli` noun + `cli overview` exist today). +- Descriptive verbs (`overview`) must **never hard-fail on a bad target** — + `overview` accepts and ignores a stray positional path so it always exits 0. +- `doctor` returns the rubric shape `{healthy, checks:[{id,passed,severity, + message,remediation}]}` and mirrors the invariants `steward doctor` checks + (prompt-file-present, backend-consistency) plus a skills-present check. + +### Adding a verb or noun + +1. Create `tensor/cli/_commands/.py` with `register(sub)` + a + `cmd_(args)->int` handler. Add `--json`; raise `CliError` on failure; + emit via `_output` helpers. +2. Import + `register()` it in `_build_parser()`. +3. Add a `catalog.py` entry for its path(s). +4. If it's a *noun* with action-verbs, give it an `overview` sub-verb (reuse + `overview.py`'s `emit_overview` / section helpers, as `cli.py` does). +5. Add tests under `tests/` (smoke + `--json` shape). + +## Identity & the skill kit + +- **`culture.yaml`** declares the mesh agent (`suffix: tensor-cli`, + `backend: claude`). `whoami`/`doctor` parse it *without* a YAML dep by walking + up from `__file__` and reading the first agent block — keep the parser tolerant + if you touch `whoami.py`. `backend: claude` requires `CLAUDE.md` to exist + (the backend-consistency invariant). +- **`.claude/skills/`** is vendored **cite-don't-import** from `guildmaster` + (provenance + re-sync procedure in `docs/skill-sources.md`). **Do not edit + vendored script bodies** — only the documented identifier-only adaptations in + `SKILL.md`. To update a skill, re-vendor per `docs/skill-sources.md`. Some + skills need external tools on PATH (`devex`, `agtag`, optionally `colleague`). + +## PR / release conventions + +- **Every PR bumps the version** — even docs/config/CI. CI (`version-check` job) + blocks merge if `pyproject.toml` version equals `origin/main`. Use the + `version-bump` skill (updates `pyproject.toml` + prepends to `CHANGELOG.md`). +- The `cicd` skill is the PR lane (layered on `devex pr`); `cicd await` polls the + SonarCloud gate + unresolved threads. +- **SonarCloud coverage requires `relative_files = true`** (already set in + `[tool.coverage.run]`) so `coverage.xml` paths match `sonar.sources=tensor`; + absolute paths silently report 0% coverage. +- Publishing is automatic via PyPI Trusted Publishing on push to `main` + (`.github/workflows/publish.yml`); PRs publish a `.devN` build to TestPyPI. + +## Renaming the template (when cloning to a real agent) + +The name `tensor-cli` / package `tensor/` is hard-coded in ~100 places. Discover +every occurrence first: + +```bash +git grep -n 'tensor-cli\|agentculture_tensor-cli\|tensor\b' +``` + +Then rename: the `tensor/` package dir, `pyproject.toml` (`name`, +`[project.scripts]`, `[tool.hatch...]`, coverage `source`, urls), `tests/`, +`sonar-project.properties` (`projectKey`), all docstrings/prose, and +`culture.yaml` (`suffix`, `backend`). Re-run `/init` afterward and re-vendor only +the skills you need. diff --git a/pyproject.toml b/pyproject.toml index c69d265..2eb0648 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "tensor-cli" -version = "0.2.0" +version = "0.2.1" description = "Agent/CLI for tensor operations and ML tensor manipulation" readme = "README.md" license = "MIT" @@ -20,7 +20,7 @@ Homepage = "https://github.com/agentculture/tensor-cli" Issues = "https://github.com/agentculture/tensor-cli/issues" [project.scripts] -tensor = "tensor.cli:main" +tensor-cli = "tensor.cli:main" [build-system] requires = ["hatchling"] diff --git a/uv.lock b/uv.lock index 7b541c1..0534408 100644 --- a/uv.lock +++ b/uv.lock @@ -154,37 +154,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/61/e8/cb8e80d6f9f55b99588625062822bf946cf03ed06315df4bd8397f5632a1/coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", size = 211764, upload-time = "2026-05-10T18:02:29.538Z" }, ] -[[package]] -name = "tensor-cli" -version = "0.2.0" -source = { editable = "." } - -[package.dev-dependencies] -dev = [ - { name = "bandit" }, - { name = "black" }, - { name = "flake8" }, - { name = "isort" }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "pytest-xdist" }, - { name = "teken" }, -] - -[package.metadata] - -[package.metadata.requires-dev] -dev = [ - { name = "bandit", specifier = ">=1.7.5" }, - { name = "black", specifier = ">=23.7.0" }, - { name = "flake8", specifier = ">=6.1" }, - { name = "isort", specifier = ">=5.12.0" }, - { name = "pytest", specifier = ">=8.0" }, - { name = "pytest-cov", specifier = ">=4.1" }, - { name = "pytest-xdist", specifier = ">=3.0" }, - { name = "teken", specifier = ">=0.8" }, -] - [[package]] name = "execnet" version = "2.1.2" @@ -476,3 +445,34 @@ sdist = { url = "https://files.pythonhosted.org/packages/25/95/3c3eff8e550c1347c wheels = [ { url = "https://files.pythonhosted.org/packages/00/2c/ca005d32831d883b10215a1ea31c32197a3a410f6d4ea3516b3d3c6a1183/teken-0.8.0-py3-none-any.whl", hash = "sha256:385a529df5a76adfff56ba5ae191a1bf8a5573f3698da4cc5bf7a2b5ca7cc311", size = 73189, upload-time = "2026-05-22T19:47:53.959Z" }, ] + +[[package]] +name = "tensor-cli" +version = "0.2.1" +source = { editable = "." } + +[package.dev-dependencies] +dev = [ + { name = "bandit" }, + { name = "black" }, + { name = "flake8" }, + { name = "isort" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-xdist" }, + { name = "teken" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "bandit", specifier = ">=1.7.5" }, + { name = "black", specifier = ">=23.7.0" }, + { name = "flake8", specifier = ">=6.1" }, + { name = "isort", specifier = ">=5.12.0" }, + { name = "pytest", specifier = ">=8.0" }, + { name = "pytest-cov", specifier = ">=4.1" }, + { name = "pytest-xdist", specifier = ">=3.0" }, + { name = "teken", specifier = ">=0.8" }, +]