From 37d25d04b62c17c5f820d20f994d122f31945f54 Mon Sep 17 00:00:00 2001 From: Ori Nachum Date: Sat, 13 Jun 2026 18:36:51 +0300 Subject: [PATCH 1/3] docs: expand CLAUDE.md from seed into full runtime guide Run /init to replace the bootstrap seed CLAUDE.md with a complete guidance file: 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. Captures the console-script gotcha (executable is `tensor`, not `tensor-cli`), the version-bump-every-PR + SonarCloud conventions, the cite-don't-import skill rule, and the rename procedure. Bumps version 0.2.0 -> 0.2.1 (CI version-check gate). Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 6 ++ CLAUDE.md | 159 +++++++++++++++++++++++++++++++++++++++++++------ pyproject.toml | 2 +- uv.lock | 62 +++++++++---------- 4 files changed, 178 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5c34c8..2fa7b3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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 + +### 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..73be330 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,28 +1,149 @@ -# 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. +### Running the CLI — name gotcha + +The installed console command is **`tensor`**, not `tensor-cli`: + +```bash +uv run tensor whoami # correct +uv run python -m tensor whoami # equivalent +``` + +`tensor-cli` is the **distribution/package name** and the `prog` shown in +`--help` and all docs — but `[project.scripts]` in `pyproject.toml` binds the +executable to `tensor`. The README's `uv run tensor-cli …` examples will fail. +If the gap is intentional, leave it; if you "fix" it, change `[project.scripts]`, +not the prose. + +## 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 must get + a matching catalog entry — `test_every_catalog_path_resolves` walks all keys. + +### 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..a0c5a0a 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" 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" }, +] From 24bedba8df3b9f6e27a0dea5f3cd50bdab626e57 Mon Sep 17 00:00:00 2001 From: Ori Nachum Date: Sat, 13 Jun 2026 18:42:07 +0300 Subject: [PATCH 2/3] fix: rename console script tensor -> tensor-cli to match prog/catalog The scaffold bound [project.scripts] to `tensor` while the argparse prog, --help, explain catalog keys, tests, and README all use `tensor-cli`. The agent-first rubric gate (teken cli doctor) derives the tool name from the first [project.scripts] entry and runs ` explain `, so it ran `tensor explain tensor` against a catalog that only has `tensor-cli` -> explain_self FAILED (CI lint job red). Renaming the script aligns the installed command with everything else; the rubric now passes 26/26. Updates the CLAUDE.md "Running the CLI" section (the gotcha is resolved) and records the fix in CHANGELOG 0.2.1. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 4 ++++ CLAUDE.md | 20 +++++++++++--------- pyproject.toml | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fa7b3f..b0f391d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ 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. diff --git a/CLAUDE.md b/CLAUDE.md index 73be330..312d829 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,20 +36,22 @@ uv run bandit -c pyproject.toml -r tensor markdownlint-cli2 "**/*.md" "#node_modules" "#.local" "#.claude/skills" "#.teken" ``` -### Running the CLI — name gotcha +### Running the CLI -The installed console command is **`tensor`**, not `tensor-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 whoami # correct -uv run python -m tensor whoami # equivalent +uv run tensor-cli whoami # installed console script +uv run python -m tensor whoami # equivalent (module entry point) ``` -`tensor-cli` is the **distribution/package name** and the `prog` shown in -`--help` and all docs — but `[project.scripts]` in `pyproject.toml` binds the -executable to `tensor`. The README's `uv run tensor-cli …` examples will fail. -If the gap is intentional, leave it; if you "fix" it, change `[project.scripts]`, -not the prose. +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 diff --git a/pyproject.toml b/pyproject.toml index a0c5a0a..2eb0648 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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"] From 799620e639689312168b6e40f6d42157c37f2878 Mon Sep 17 00:00:00 2001 From: Ori Nachum Date: Sat, 13 Jun 2026 18:45:25 +0300 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20address=20qodo=20review=20=E2=80=94?= =?UTF-8?q?=20markdownlint=20install=20+=20explain-test=20scope?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Note markdownlint-cli2 is a Node tool outside the uv dev group (CI pins npm install -g markdownlint-cli2@0.21.0); the other four linters come from uv sync. (qodo #2) - Correct the explain-catalog claim: test_every_catalog_path_resolves only asserts existing keys resolve, it does not enforce that every registered command has an entry — only the rubric's explain_self (root) is CI-enforced. (qodo #3) Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 312d829..f269ffe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,6 +36,10 @@ uv run bandit -c pyproject.toml -r tensor markdownlint-cli2 "**/*.md" "#node_modules" "#.local" "#.claude/skills" "#.teken" ``` +`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 @@ -80,8 +84,13 @@ A single argparse tree with a uniform plugin-style registration contract. 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 must get - a matching catalog entry — `test_every_catalog_path_resolves` walks all keys. + `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)