diff --git a/CHANGELOG.md b/CHANGELOG.md index 72690e5..54ca6cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ 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.4.1] - 2026-07-08 + +### Changed + +- Re-initialized CLAUDE.md from the bootstrap seed into a full runtime prompt (via /init): the harmonics non-TTS-audio mission and intent -> sound design-spine table from issue #1, the agent-first CLI architecture (register(sub) command pattern, stdout/stderr + CliError + --json output contract, the tuple-keyed explain catalog, doctor rubric invariants), the uv build/test/lint commands, and the identity/version-bump/cite-dont-import conventions. Documented the command/dist name split (command `harmonics`, dist/identity `harmonics-cli`). +- Made the CLI command name consistent so `harmonics ` is what the docs actually show. The console script stays `harmonics` (matching the `culture`/`teken`/`devex` short-name convention); the runnable-command examples that previously read `harmonics-cli ` — in the README, `learn` output, the `explain` catalog (headings + usage), argparse `prog`, `doctor`'s status header, `cli overview`'s subject, and the module docstrings — now read `harmonics `. Bare product/identity labels (root `explain`/`overview` page titles, the culture nick, `learn`'s JSON `tool` field, the PyPI dist name) stay `harmonics-cli`. Added `harmonics` as a root `explain` topic alias so `harmonics explain harmonics` resolves alongside `harmonics explain harmonics-cli`. + +### Fixed + +- Re-synced `uv.lock`, which was stale at `0.3.4` on main, up to the `0.4.1` release, and recorded resolved dev-dependency entries (execnet, flake8). + ## [0.4.0] - 2026-06-23 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index c43b241..81bc329 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,28 +1,183 @@ -# 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 is -This repository hosts the **harmonics-cli** agent. +**harmonics-cli** is an agent + CLI that gives agents and robots **non-TTS +audio**: it expresses meaning through pleasant sonic signatures — chimes, +flutes, pulses, tonal motifs — mapped to *intent, confidence, urgency, state, +and identity*. It is the inverse of text-to-speech: turn text/sentences into +**notes** and **audio** (text-to-notes / text-to-audio), not words. -## Description +> **Status: scaffold, not yet the harmonics agent.** The code today is the +> AgentCulture *culture-agent-template* — a generic agent-first CLI (`whoami`, +> `learn`, `explain`, `overview`, `doctor`, `cli`). **None of the audio domain +> exists yet.** The self-description strings (in `_build_parser`, `learn`, the +> explain catalog, `overview`) still literally say "a clonable template for +> AgentCulture mesh agents." Your job when building the domain is to (a) add the +> harmonics verbs and synthesis, and (b) replace those placeholder +> self-descriptions with the real mission. The build brief is +> [issue #1](https://github.com/agentculture/harmonics-cli/issues/1) — treat it +> as the source of truth for the domain. -An agent + CLI giving agents and robots non-TTS (non-speech) audio: express meaning through pleasant sonic signatures — chimes, flutes, pulses, and tonal motifs mapped to intent, confidence, urgency, state, and identity. Turns text/sentences into notes and audio (text-to-notes / text-to-audio). +## The design spine — intent → sound mapping -## Re-init instruction +This table is the heart of the project. Everything the domain builds resolves to +it. Finalize and document it before/while shipping the first audio verb. -This file is a seed. To expand it into your full runtime prompt: +| Axis | Example values | Sonic mapping | +|------|---|---| +| **intent** | ack, question, success, failure, thinking, handoff | timbre / motif family (chime vs. flute vs. pulse) | +| **confidence** | low → high | consonance, pitch stability, resolved vs. suspended cadence | +| **urgency** | calm → urgent | tempo, attack sharpness, repetition | +| **state** | idle, working, blocked, done | sustained pad vs. discrete events | +| **identity** | which agent | signature motif / key / instrument per agent | -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. +Two text-to-audio pathways (both target verbs, neither built yet): -Until you run `/init`, `harmonics-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. +- **Explicit axes** — `harmonics play --intent success --confidence high --urgency low` +- **Inferred from a sentence** — `harmonics say "done, tests all green"` parses + the sentence into axes, then renders. + +Output targets: WAV/OGG files, live playback (portaudio / sounddevice / +simpleaudio), and a MIDI / note-sequence representation for robot/synth +consumption. Planned discovery verbs: `motifs` / `signatures` (list the palette +and per-agent identities), and `explain` for the sonic mappings. + +**Domain build rules (from issue #1):** + +- Keep synthesis **unit-testable offline** — assert on note sequences, not on a + speaker. The pure text→notes core must run with no audio device and no + third-party import in the test path. +- Audio-producing verbs default to **dry-run** (emit the note sequence); + producing sound or a file requires an explicit flag (`--play` / `--out` / + `--apply`). Read/describe verbs never need it. +- Keep the palette **pleasant and non-fatiguing** — it plays repeatedly next to + a human. + +## Commands + +The dev toolchain is **uv**. Python **3.12+**. + +```bash +uv sync # install (incl. dev group) +uv run pytest -n auto # full test suite (xdist parallel) +uv run pytest tests/test_cli.py::test_whoami_json # a single test +uv run pytest -k doctor # tests matching a name +uv run harmonics whoami # run the CLI (see note below) +uv run teken cli doctor . --strict # the agent-first rubric gate CI enforces +``` + +Lint (all four gate merges in CI — run them before opening a PR): + +```bash +uv run black --check harmonics tests +uv run isort --check-only harmonics tests +uv run flake8 harmonics tests +uv run bandit -c pyproject.toml -r harmonics +markdownlint-cli2 "**/*.md" "#node_modules" "#.local" "#.claude/skills" "#.teken" +``` + +**Name split — the command is `harmonics`; the dist/identity is `harmonics-cli`.** +The PyPI distribution, the culture nick, and product titles are `harmonics-cli` +(`uv tool install harmonics-cli`); the console script you actually run is +`harmonics` (`uv run harmonics whoami`, later `harmonics play …`), matching the +workspace short-name convention (`culture`, `teken`, `devex`). Convention across +code and docs: runnable examples and argparse's `prog` use `harmonics`; bare +product/identity labels (the root `explain`/`overview` page titles, the nick, +`learn`'s JSON `tool` field) stay `harmonics-cli`. The root `explain` topic +resolves under **both** names — `harmonics explain harmonics` (canonical) and +`harmonics explain harmonics-cli` (dist-name alias). + +## Architecture + +A single argparse tree with a strict machine-first output contract. Read +`harmonics/cli/__init__.py` first — it is the spine. + +### Command registration + +Every verb/noun lives in its own module under `harmonics/cli/_commands/` and +exposes a `register(sub)` function that adds its subparser and sets +`func=` via `set_defaults`. `_build_parser()` in +`harmonics/cli/__init__.py` calls each module's `register()`. **To add a verb:** +create the module, implement `cmd_x(args) -> int | None`, add a `register()`, +and wire one `_x_cmd.register(sub)` line into `_build_parser`. Nouns with +sub-verbs nest their own `add_subparsers` (see `_commands/cli.py`) and **must** +pass `parser_class=type(p)` so nested parse errors route through the structured +error contract instead of argparse's default `stderr` + exit 2. + +### Output & error contract (stable — agents parse this) + +- **Results → stdout, diagnostics/errors → stderr. Never mixed.** All output + goes through `harmonics/cli/_output.py` (`emit_result`, `emit_error`, + `emit_diagnostic`). Don't `print()`. +- **Every verb supports `--json`.** Text and JSON go to the *same* stream; JSON + mode just serializes the payload. +- **Failures raise `CliError`** (`harmonics/cli/_errors.py`) carrying + `{code, message, remediation}`. `main()`/`_dispatch` catch it, format it, and + return the code. Any *other* exception is wrapped so **no Python traceback + ever leaks**. Text-mode errors render as `error: ` + `hint: ` + — the `hint:` prefix is required by the rubric. +- **Exit codes:** `0` success, `1` user-input error, `2` environment/setup + error, `3+` reserved. Centralized in `_errors.py`. +- **Argparse errors** honor `--json` via a pre-parse trick: `main()` scans raw + argv for `--json` and sets `_CliArgumentParser._json_hint` *before* + `parse_args`, because at parse-error time `args.json` doesn't exist yet. + +### The `explain` catalog + +`harmonics/explain/catalog.py` holds verbatim markdown keyed by command-path +**tuples** (`("whoami",)`, `("cli", "overview")`; `()` and `("harmonics-cli",)` +both map to root). `resolve()` looks up the tuple or raises `CliError`. The test +`test_every_catalog_path_resolves` walks `known_paths()`, so **every catalog +entry must resolve** — and every new verb should get a catalog entry. + +### `doctor` and the agent-first rubric + +`doctor` mirrors the invariants `steward doctor` verifies for a mesh agent and +emits the rubric-shaped `{healthy, checks: [{id, passed, severity, message, +remediation}]}`: + +- **prompt-file-present / backend-consistency** — the `backend` in `culture.yaml` + must have its matching prompt file on disk. Mapping: `claude → CLAUDE.md`, + `colleague → AGENTS.colleague.md`, `acp → AGENTS.md`, `gemini → GEMINI.md`. +- **skills-present** — the vendored `.claude/skills/` kit exists. + +CI runs `teken cli doctor . --strict` (the "afi rubric gate"). It enforces, among +others: `learn` output must be ≥200 chars and mention purpose, the command map, +exit codes, `--json`, and `explain`; any noun exposing action-verbs must also +expose `overview` (why `cli overview` exists); descriptive verbs (`overview`) +must not hard-fail on a stray path argument. Keep these contracts intact. + +## Identity & conventions + +- **Backend is `colleague`, not `claude`.** `culture.yaml` declares + `backend: colleague` (model `Qwen3.6-27B-...`), so the **resident/runtime** + prompt the mesh daemon loads is `AGENTS.colleague.md` — *this* `CLAUDE.md` is + read by Claude Code only, and does **not** satisfy `doctor`'s + backend-consistency check (`AGENTS.colleague.md` does). (The seed's claim that + it declares `backend: claude` was stale; see CHANGELOG 0.3.4.) +- **`whoami` reads identity from `culture.yaml`** by walking *up from + `__file__`* (not CWD) so it reports this agent's own identity; a wheel install + with no `culture.yaml` falls back to literal defaults. Parsed without a YAML + dependency on purpose. +- **Runtime dependencies stay empty.** `pyproject.toml` `dependencies = []` — the + CLI core is cited from `teken` (`afi-cli`), not installed. Keep the pure + text→notes core dependency-free and offline-testable; isolate optional audio + I/O (playback, encoders) so importing the package never requires a sound stack. +- **`.claude/skills/` is vendored cite-don't-import** from *guildmaster* (with a + few tracked divergences). Do **not** hand-edit skill script bodies to "fix" + them here; re-sync per `docs/skill-sources.md`. That file is the provenance + ledger and re-sync procedure. +- **Version-bump every PR.** CI's `version-check` job fails a PR whose + `pyproject.toml` version equals `main`'s — *even for docs/config/CI-only + changes*. Use `.claude/skills/version-bump/` (or `/version-bump patch|minor| + major`); it also prepends a Keep-a-Changelog entry to `CHANGELOG.md`. +- **PR lane:** the `cicd` skill (layered on `devex pr`) creates PRs, handles + review feedback, and gates on the SonarCloud quality gate + unresolved + threads. SonarCloud project key is `agentculture_harmonics-cli`; `sonar.sources + = harmonics`, coverage floor is 60% (`fail_under`). +- **Publishing** is PyPI Trusted Publishing (OIDC): push to `main` touching + `pyproject.toml` or `harmonics/**` publishes to PyPI; PRs publish a + `.devN` build to TestPyPI. No tokens in the repo. diff --git a/README.md b/README.md index c355c60..b7fdf35 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,16 @@ An agent + CLI giving agents and robots non-TTS (non-speech) audio: express mean ```bash uv sync uv run pytest -n auto # run the test suite -uv run harmonics-cli whoami # identity from culture.yaml -uv run harmonics-cli learn # self-teaching prompt (add --json) +uv run harmonics whoami # identity from culture.yaml +uv run harmonics learn # self-teaching prompt (add --json) uv run teken cli doctor . --strict # the agent-first rubric gate CI runs ``` ## CLI +Installed from PyPI as `harmonics-cli` (`uv tool install harmonics-cli`); the +command you run is `harmonics`. + | Verb | What it does | |------|--------------| | `whoami` | Report this agent's nick, version, backend, and model from `culture.yaml`. | diff --git a/harmonics/cli/__init__.py b/harmonics/cli/__init__.py index d11f0f5..0a91e17 100644 --- a/harmonics/cli/__init__.py +++ b/harmonics/cli/__init__.py @@ -70,7 +70,7 @@ def _build_parser() -> argparse.ArgumentParser: from harmonics.cli._commands import whoami as _whoami_cmd parser = _CliArgumentParser( - prog="harmonics-cli", + prog="harmonics", description="harmonics-cli — a clonable template for AgentCulture mesh agents.", ) parser.add_argument( diff --git a/harmonics/cli/_commands/cli.py b/harmonics/cli/_commands/cli.py index 3dce27b..33778e2 100644 --- a/harmonics/cli/_commands/cli.py +++ b/harmonics/cli/_commands/cli.py @@ -1,4 +1,4 @@ -"""``harmonics-cli cli`` — noun grouping CLI-surface introspection. +"""``harmonics cli`` — noun grouping CLI-surface introspection. Exists to satisfy the agent-first rubric's ``overview_cli_noun_exists`` check: any noun with action-verbs must also expose ``overview``. There are no @@ -15,7 +15,7 @@ def cmd_cli_overview(args: argparse.Namespace) -> int: emit_overview( - "harmonics-cli cli", + "harmonics cli", cli_sections(), json_mode=bool(getattr(args, "json", False)), ) @@ -23,14 +23,14 @@ def cmd_cli_overview(args: argparse.Namespace) -> int: def _no_verb(args: argparse.Namespace) -> int: - # `harmonics-cli cli` with no sub-verb prints the noun's overview. + # `harmonics cli` with no sub-verb prints the noun's overview. return cmd_cli_overview(args) def register(sub: argparse._SubParsersAction) -> None: p = sub.add_parser( "cli", - help="CLI-surface introspection (see 'harmonics-cli cli overview').", + help="CLI-surface introspection (see 'harmonics cli overview').", ) p.add_argument("--json", action="store_true", help="Emit structured JSON.") p.set_defaults(func=_no_verb, json=False) diff --git a/harmonics/cli/_commands/doctor.py b/harmonics/cli/_commands/doctor.py index f63fe83..2e0a017 100644 --- a/harmonics/cli/_commands/doctor.py +++ b/harmonics/cli/_commands/doctor.py @@ -1,4 +1,4 @@ -"""``harmonics-cli doctor`` — check the agent-identity invariants. +"""``harmonics doctor`` — check the agent-identity invariants. Mirrors the two invariants ``steward doctor`` verifies for a mesh agent: @@ -105,7 +105,7 @@ def cmd_doctor(args: argparse.Namespace) -> int: emit_result(report, json_mode=True) else: status = "healthy" if report["healthy"] else "unhealthy" - lines = [f"harmonics-cli doctor: {status}", ""] + lines = [f"harmonics doctor: {status}", ""] for check in report["checks"]: mark = "ok" if check["passed"] else "FAIL" lines.append(f"[{mark}] {check['id']}: {check['message']}") diff --git a/harmonics/cli/_commands/explain.py b/harmonics/cli/_commands/explain.py index 9f0cabb..ce2eae6 100644 --- a/harmonics/cli/_commands/explain.py +++ b/harmonics/cli/_commands/explain.py @@ -1,4 +1,4 @@ -"""``harmonics-cli explain ...`` — global markdown catalog lookup (stable-contract). +"""``harmonics explain ...`` — global markdown catalog lookup (stable-contract). ``explain`` is global (not nested under a noun). It takes zero or more path tokens and resolves them via the catalog in :mod:`harmonics.explain`. @@ -32,7 +32,7 @@ def register(sub: argparse._SubParsersAction) -> None: p.add_argument( "path", nargs="*", - help="Command path tokens; empty = root (same as 'harmonics-cli').", + help="Command path tokens; empty = root (same as 'harmonics').", ) p.add_argument("--json", action="store_true", help="Emit structured JSON.") p.set_defaults(func=cmd_explain) diff --git a/harmonics/cli/_commands/learn.py b/harmonics/cli/_commands/learn.py index ca7f2bc..ab9c0fa 100644 --- a/harmonics/cli/_commands/learn.py +++ b/harmonics/cli/_commands/learn.py @@ -1,4 +1,4 @@ -"""``harmonics-cli learn`` — the learnability affordance. +"""``harmonics learn`` — the learnability affordance. Prints a structured self-teaching prompt. Must satisfy the agent-first rubric: >=200 chars and mention purpose, command map, exit codes, --json, and explain. @@ -13,6 +13,7 @@ _TEXT = """\ harmonics-cli — a clonable template for AgentCulture mesh agents. +Installed from PyPI as harmonics-cli; the command you run is `harmonics`. Purpose ------- @@ -23,12 +24,12 @@ Commands -------- - harmonics-cli whoami Identity from culture.yaml. - harmonics-cli learn This self-teaching prompt. - harmonics-cli explain ... Markdown docs for any noun/verb path. - harmonics-cli overview Descriptive snapshot of the agent. - harmonics-cli doctor Check the agent-identity invariants. - harmonics-cli cli overview Describe the CLI surface itself. + harmonics whoami Identity from culture.yaml. + harmonics learn This self-teaching prompt. + harmonics explain ... Markdown docs for any noun/verb path. + harmonics overview Descriptive snapshot of the agent. + harmonics doctor Check the agent-identity invariants. + harmonics cli overview Describe the CLI surface itself. Machine-readable output ----------------------- @@ -44,7 +45,7 @@ More detail ----------- - harmonics-cli explain harmonics-cli + harmonics explain harmonics """ @@ -67,7 +68,7 @@ def _as_json_payload() -> dict[str, object]: "2": "environment/setup error", }, "json_support": True, - "explain_pointer": "harmonics-cli explain ", + "explain_pointer": "harmonics explain ", } diff --git a/harmonics/cli/_commands/overview.py b/harmonics/cli/_commands/overview.py index f509075..94b1658 100644 --- a/harmonics/cli/_commands/overview.py +++ b/harmonics/cli/_commands/overview.py @@ -1,4 +1,4 @@ -"""``harmonics-cli overview`` — read-only descriptive snapshot of the agent. +"""``harmonics overview`` — read-only descriptive snapshot of the agent. Describes the agent to an agent reader: identity (from culture.yaml), the verb surface, and the sibling-pattern artifacts this template carries. The shared diff --git a/harmonics/cli/_commands/whoami.py b/harmonics/cli/_commands/whoami.py index 501738d..9751cdf 100644 --- a/harmonics/cli/_commands/whoami.py +++ b/harmonics/cli/_commands/whoami.py @@ -1,4 +1,4 @@ -"""``harmonics-cli whoami`` — the smallest identity probe. +"""``harmonics whoami`` — the smallest identity probe. Reports the agent's identity as declared in ``culture.yaml``: its nick (``suffix``), the backend it runs on, and the served model (if any) — plus the diff --git a/harmonics/cli/_errors.py b/harmonics/cli/_errors.py index 85769e7..c44656f 100644 --- a/harmonics/cli/_errors.py +++ b/harmonics/cli/_errors.py @@ -13,7 +13,7 @@ from dataclasses import dataclass -# Exit-code policy. Documented in ``harmonics-cli learn`` output. +# Exit-code policy. Documented in ``harmonics learn`` output. # 0 = success # 1 = user-input error (bad flag, missing required arg, unknown path) # 2 = environment / setup error (tool not installed, file unreadable) diff --git a/harmonics/explain/__init__.py b/harmonics/explain/__init__.py index 0e33b95..ab74811 100644 --- a/harmonics/explain/__init__.py +++ b/harmonics/explain/__init__.py @@ -16,7 +16,7 @@ def resolve(path: tuple[str, ...]) -> str: raise CliError( code=EXIT_USER_ERROR, message=f"no explain entry for: {display}", - remediation="list entries with: harmonics-cli explain harmonics-cli", + remediation="list entries with: harmonics explain harmonics", ) diff --git a/harmonics/explain/catalog.py b/harmonics/explain/catalog.py index 117e5a6..6622757 100644 --- a/harmonics/explain/catalog.py +++ b/harmonics/explain/catalog.py @@ -1,10 +1,15 @@ -"""Markdown catalog for ``harmonics-cli explain ``. +"""Markdown catalog for ``harmonics explain ``. -Each entry is verbatim markdown. Keys are command-path tuples. The empty tuple -and ``("harmonics-cli",)`` both resolve to the root entry. +Each entry is verbatim markdown. Keys are command-path tuples. The empty tuple, +``("harmonics",)`` (the command name), and ``("harmonics-cli",)`` (the dist +name) all resolve to the root entry. Keep bodies self-contained: an agent reading one entry should get enough context without chaining reads. + +Naming: the command is ``harmonics`` (what you type); ``harmonics-cli`` is the +PyPI/dist name and identity. Runnable examples use ``harmonics``; the root page +title and the root topic token stay ``harmonics-cli`` (the product itself). """ from __future__ import annotations @@ -18,14 +23,16 @@ buildable/deployable package baseline. Clone it, rename the package, edit `culture.yaml`, and you have a new agent. +Installed from PyPI as `harmonics-cli`; the command you run is `harmonics`. + ## Verbs -- `harmonics-cli whoami` — identity probe from `culture.yaml`. -- `harmonics-cli learn` — structured self-teaching prompt. -- `harmonics-cli explain ` — markdown docs for any noun/verb. -- `harmonics-cli overview` — descriptive snapshot of the agent. -- `harmonics-cli doctor` — check the agent-identity invariants. -- `harmonics-cli cli overview` — describe the CLI surface. +- `harmonics whoami` — identity probe from `culture.yaml`. +- `harmonics learn` — structured self-teaching prompt. +- `harmonics explain ` — markdown docs for any noun/verb. +- `harmonics overview` — descriptive snapshot of the agent. +- `harmonics doctor` — check the agent-identity invariants. +- `harmonics cli overview` — describe the CLI surface. ## Exit-code policy @@ -36,49 +43,49 @@ ## See also -- `harmonics-cli explain whoami` -- `harmonics-cli explain doctor` +- `harmonics explain whoami` +- `harmonics explain doctor` """ _WHOAMI = """\ -# harmonics-cli whoami +# harmonics whoami Reports the agent's identity from `culture.yaml`: nick (`suffix`), backend, served model, and the package version. Read-only. ## Usage - harmonics-cli whoami - harmonics-cli whoami --json + harmonics whoami + harmonics whoami --json """ _LEARN = """\ -# harmonics-cli learn +# harmonics learn Prints a structured self-teaching prompt covering purpose, command map, exit-code policy, `--json` support, and the `explain` pointer. ## Usage - harmonics-cli learn - harmonics-cli learn --json + harmonics learn + harmonics learn --json """ _EXPLAIN = """\ -# harmonics-cli explain +# harmonics explain Prints markdown documentation for any noun/verb path. Unlike `--help` (terse, positional), `explain` is global and addressable by path. ## Usage - harmonics-cli explain harmonics-cli - harmonics-cli explain whoami - harmonics-cli explain --json + harmonics explain harmonics + harmonics explain whoami + harmonics explain --json """ _OVERVIEW = """\ -# harmonics-cli overview +# harmonics overview Read-only descriptive snapshot of the agent: identity (from `culture.yaml`), the verb surface, and the sibling-pattern artifacts the template carries. Accepts an @@ -86,12 +93,12 @@ ## Usage - harmonics-cli overview - harmonics-cli overview --json + harmonics overview + harmonics overview --json """ _DOCTOR = """\ -# harmonics-cli doctor +# harmonics doctor Checks the agent-identity invariants `steward doctor` verifies: prompt-file-present and backend-consistency (`colleague` → `AGENTS.colleague.md`), plus a @@ -99,25 +106,26 @@ ## Usage - harmonics-cli doctor - harmonics-cli doctor --json + harmonics doctor + harmonics doctor --json """ _CLI = """\ -# harmonics-cli cli +# harmonics cli Noun group for CLI-surface introspection. `cli overview` describes the CLI itself (distinct from the global `overview`, which describes the agent). ## Usage - harmonics-cli cli overview - harmonics-cli cli overview --json + harmonics cli overview + harmonics cli overview --json """ ENTRIES: dict[tuple[str, ...], str] = { (): _ROOT, + ("harmonics",): _ROOT, ("harmonics-cli",): _ROOT, ("whoami",): _WHOAMI, ("learn",): _LEARN, diff --git a/pyproject.toml b/pyproject.toml index fd7eb6f..2791092 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "harmonics-cli" -version = "0.4.0" +version = "0.4.1" description = "An agent + CLI giving agents and robots non-TTS (non-speech) audio: express meaning through pleasant sonic signatures — chimes, flutes, pulses, and tonal motifs mapped to intent, confidence, urgency, state, and identity. Turns text/sentences into notes and audio (text-to-notes / text-to-audio)." readme = "README.md" license = "Apache-2.0" diff --git a/tests/test_cli.py b/tests/test_cli.py index 51fc3a5..60dace4 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -21,7 +21,7 @@ def test_version_flag(capsys: pytest.CaptureFixture[str]) -> None: def test_no_args_prints_help(capsys: pytest.CaptureFixture[str]) -> None: rc = main([]) assert rc == 0 - assert "usage: harmonics-cli" in capsys.readouterr().out + assert "usage: harmonics" in capsys.readouterr().out def test_unknown_command_errors(capsys: pytest.CaptureFixture[str]) -> None: @@ -97,7 +97,7 @@ def test_explain_json(capsys: pytest.CaptureFixture[str]) -> None: assert rc == 0 payload = json.loads(capsys.readouterr().out) assert payload["path"] == ["whoami"] - assert "harmonics-cli whoami" in payload["markdown"] + assert "harmonics whoami" in payload["markdown"] def test_explain_unknown_path_errors(capsys: pytest.CaptureFixture[str]) -> None: diff --git a/tests/test_cli_introspection.py b/tests/test_cli_introspection.py index e47ecfc..93c119c 100644 --- a/tests/test_cli_introspection.py +++ b/tests/test_cli_introspection.py @@ -41,14 +41,14 @@ def test_overview_graceful_on_bad_path(capsys: pytest.CaptureFixture[str]) -> No def test_cli_overview_text(capsys: pytest.CaptureFixture[str]) -> None: rc = main(["cli", "overview"]) assert rc == 0 - assert "# harmonics-cli cli" in capsys.readouterr().out + assert "# harmonics cli" in capsys.readouterr().out def test_cli_overview_json_shape(capsys: pytest.CaptureFixture[str]) -> None: rc = main(["cli", "overview", "--json"]) assert rc == 0 payload = json.loads(capsys.readouterr().out) - assert payload["subject"] == "harmonics-cli cli" + assert payload["subject"] == "harmonics cli" assert isinstance(payload["sections"], list) @@ -77,7 +77,7 @@ def test_cli_overview_unknown_flag_structured_error( def test_doctor_text(capsys: pytest.CaptureFixture[str]) -> None: rc = main(["doctor"]) assert rc in (0, 1) - assert "harmonics-cli doctor" in capsys.readouterr().out + assert "harmonics doctor" in capsys.readouterr().out def test_doctor_json_shape(capsys: pytest.CaptureFixture[str]) -> None: diff --git a/uv.lock b/uv.lock index 846bbbc..7c08db6 100644 --- a/uv.lock +++ b/uv.lock @@ -154,9 +154,32 @@ 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 = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + +[[package]] +name = "flake8" +version = "7.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, +] + [[package]] name = "harmonics-cli" -version = "0.3.4" +version = "0.4.1" source = { editable = "." } [package.dev-dependencies] @@ -185,29 +208,6 @@ dev = [ { name = "teken", specifier = ">=0.8" }, ] -[[package]] -name = "execnet" -version = "2.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, -] - -[[package]] -name = "flake8" -version = "7.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mccabe" }, - { name = "pycodestyle" }, - { name = "pyflakes" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, -] - [[package]] name = "iniconfig" version = "2.3.0"