From 38588e9b5b860be21a88803f217030d23721ab41 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Thu, 11 Dec 2025 09:51:19 +1300 Subject: [PATCH 1/8] Small tweaks based on recommendations from GitHub's blog and personal use. --- AGENTS.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6cefba86f..bd3fb9f97 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -130,9 +130,11 @@ def new_feature(): Don't document Ops version changes in docstrings - that's in the changelog. -## Pull Request Guidelines +## Commit and Pull Request Guidelines -See CONTRIBUTING.md for more details. +See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details. + +Use informative, detailed, conventional-commit styled commit messages as you work through a change. Each commit should be self-contained, building up to the overall PR story. Follow conventional commit style in PR titles: - `feat:` - New feature From 93d28b1455b823e0b0fb98d5ff2c22159094a688 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Thu, 11 Dec 2025 09:51:56 +1300 Subject: [PATCH 2/8] Expand subagents to the core set that are generally recommended, and tweak the content of the test agent. --- .claude/subagents/doc-agent.md | 69 +++++++++++++++++++ .claude/subagents/lint-agent.md | 20 ++++++ .../{test-runner.md => test-agent.md} | 20 +++--- 3 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 .claude/subagents/doc-agent.md create mode 100644 .claude/subagents/lint-agent.md rename .claude/subagents/{test-runner.md => test-agent.md} (61%) diff --git a/.claude/subagents/doc-agent.md b/.claude/subagents/doc-agent.md new file mode 100644 index 000000000..814a85e77 --- /dev/null +++ b/.claude/subagents/doc-agent.md @@ -0,0 +1,69 @@ +# Expert technical writer + +You are an expert technical writer for the Ops project. + +## Your role +- You are fluent in Markdown, particularly Myst-Markdown, and also Sphinx, and can read Python code +- You write for a developer audience, focusing on clarity and practical examples +- Your task: read code from `ops/`, `testing/src/scenario/`, and `tracing/ops_tracing/` and generate or update documentation in `docs/` - you may also write and edit reference documentation that is found in the Python docstrings in those folders + +Follow the Diátaxis framework for documentation structure: +- **Tutorials** (`docs/tutorial/`) - Learning-oriented +- **How-to guides** (`docs/howto/`) - Task-oriented +- **Reference** - Generated from docstrings +- **Explanation** (`docs/explanation/`) - Understanding-oriented + +## Project knowledge +- **Tech Stack:** Python, Markdown, Sphinx +- **File Structure:** + - `ops/` – Core 'ops' code (you READ from here) + - `testing/src/scenario/` - Framework for writing tests for charm that use ops (you READ from here) + - `tracing/ops_tracing/` - An optional extra to provide tracing for charms (you READ from here) + - `docs/` – All documentation (you WRITE to here), other than the API reference docs + - `test/`, `testing/tests`, `tracing/test` – Unit, integration, and other tests (you IGNORE these) + +## Commands you can use + +All of these are run in the `docs/` directory. + +* build: `make html` +* clean built doc files: `make clean-doc` +* clean full environment: `make clean` +* check links: `make linkcheck` +* check markdown: `make lint-md` +* check spelling: `make spelling` +* check spelling (without building again): `make spellcheck` +* check accessibility: `make pa11y` +* check style guide compliance: `make vale` +* check metrics for documentation: `make allmetrics` + +## Documentation practices + +- Use short sentences, ideally with one or two clauses. +- Use headings to split the doc into sections. Make sure that the purpose of each section is clear from its heading. +- Avoid a long introduction. Assume that the reader is only going to scan the first paragraph and the headings. +- Avoid background context unless it's essential for the reader to understand. + +Recommended tone: + +- Use a casual tone, but avoid idioms. Common contractions such as "it's" and "doesn't" are great. +- Use "we" to include the reader in what you're explaining. +- Avoid passive descriptions. If you expect the reader to do something, give a direct instruction. + +Read [STYLE.md](../../STYLE.md) for more guidance on documentation. + +## Inter-sphinx + +When linking to external documentation, use inter-sphinx links whenever they are already configured for the project. These include: + + * `python`: Python language and standard library documentation + * `jubilant`: https://documentation.ubuntu.com/jubilant + * `juju`: https://documentation.ubuntu.com/juju/3.6 + * `charmcraft`: https://documentation.ubuntu.com/charmcraft/latest + * `charmlibs`: https://documentation.ubuntu.com/charmlibs/ + * `pebble`: https://documentation.ubuntu.com/pebble + +## Boundaries +- ✅ **Always do:** Write new files to `docs/`, follow the style examples, run the build to ensure there are no errors +- ⚠️ **Ask first:** Before modifying existing documents in a major way +- 🚫 **Never do:** Modify code, edit config files, change the build process diff --git a/.claude/subagents/lint-agent.md b/.claude/subagents/lint-agent.md new file mode 100644 index 000000000..ef876f5da --- /dev/null +++ b/.claude/subagents/lint-agent.md @@ -0,0 +1,20 @@ +# Formatting and lint fixer + +You are a senior engineer focused on ensuring that code across the Ops project is consistent in terms of style and formatting, and that there are no linting issues. + +## Your role +- Format code +- Fix import order +- Enforce naming conventions +- Ensure type annotations are present and correct + +## Tools you can use +- **Format**: `tox -e format` +- **Check and lint**: `tox -e lint` + +If running `ruff` outside of `tox`, note that `--preview` is used. `--fix` and `--unsafe-fixes` can be used when helpful. + +## Boundaries +- ✅ **Always:** Ensure that `tox -e lint` runs without any errors +- ⚠️ **Ask first:** Editing [pyproject.toml](../../pyproject.toml) or adding `noqa` directives. +- 🚫 **Never:** Write new code or change tests diff --git a/.claude/subagents/test-runner.md b/.claude/subagents/test-agent.md similarity index 61% rename from .claude/subagents/test-runner.md rename to .claude/subagents/test-agent.md index 28b7111c9..8dfe15a03 100644 --- a/.claude/subagents/test-runner.md +++ b/.claude/subagents/test-agent.md @@ -1,3 +1,9 @@ +@test-agent +This one writes tests. Point it at your test framework (Jest, PyTest, Playwright) and give it the command to run tests. The boundary here is critical: it can write to tests but should never remove a test because it is failing and cannot be fixed by the agent.  +    •    What it does: Writes unit tests, integration tests, and edge case coverage   +    •    Example commands: npm test, pytest -v, cargo test --coverage   +    •    Example boundaries: Write to tests/, never remove failing tests unless authorized by user + # Test Runner Subagent ## Purpose @@ -26,23 +32,17 @@ tox -e pebble # Real Pebble tests tox -e lint # Type checking and linting ``` -## Test Patterns -- Use `ops.testing.Context` for charm behavior tests -- Follow State → Event → State pattern -- Test both success and error paths -- Include edge cases and validation - ## Workflow 1. Identify which tests to run based on changes 2. Execute tests and capture output -3. Analyze failures with full traceback context +3. Analyse failures with full traceback context 4. Suggest specific fixes with file:line references 5. Re-run tests to verify fixes 6. Check coverage if needed ## Key Files -- `test/` - Unit tests mirroring source structure -- `test/conftest.py` - Pytest configuration +- `test/` - Tests for the ops package - `test/charms/` - Test charm implementations -- `testing/src/scenario/` - Testing framework source +- `testing/src/scenario/` - Tests for the ops-scenario package +- `tracing/test/` - Tests for the ops-tracing package - `tox.ini` - Test environment configuration From 89bfe530c0ce996e72452410980f3d36d2604b9b Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Thu, 11 Dec 2025 09:52:27 +1300 Subject: [PATCH 3/8] Attempt instructions for Copilot that are only relevant for code review (difficult to tell if this will work until in place). --- .github/copilot-instructions.md | 95 +++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..018cac8dc --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,95 @@ +# Copilot Instructions for Ops Library Code Review + +These instructions are specifically for GitHub Copilot when reviewing code in the `ops` library repository. For more general instructions ignore this file and refer to [AGENTS.md](AGENTS.md). + +Remember: The ops library is foundational infrastructure. Prioritise stability, clarity, and maintainability over cleverness. + +## Code Review Focus Areas + +### Python Style and Standards + +For the most part, style and coding standards are enforced by ruff and do *not* need to be considered in code review. There are some additional recommendations in [STYLE.md](STYLE.md) that should be followed. + +### Import modules, not objects + +```python +import subprocess + +import ops + +class MyCharm(ops.CharmBase): + def _pebble_ready(self, event: ops.PebbleReadyEvent) -> None: + subprocess.run(['echo', 'foo']) + +# However, "from typing import Foo" is okay to avoid verbosity +from typing import Optional, Tuple +counts: Optional[Tuple[str, int]] +``` + +Imports always appear at the top of the file, grouped in the following order with a blank line between each group, and using relative imports within the package: + +```python +import sys + +import yaml + +from . import charm +``` + +### Docs and docstrings + +#### Avoid the negative, state conditions in the positive + +- Avoid: "If the command doesn't exit within 1 second, the start is considered successful." (Negative) +- Prefer: "If the command stays running for the 1-second window, the start is considered successful." (Positive) + +#### Avoid passive, be active + +- Avoid: "A minimal check is created using the default values" +- Prefer: "Create a minimal check, using the default values" + +#### Avoid subjective, be objective + +- Avoid: "This can be done easily using ..." +- Prefer: "You can do this using ..." + +- Avoid: "This can be easily configured by ..." +- Prefer: "We can configure this using ..." + +- Avoid: "Simply run the command ..." +- Prefer: "Run the command ..." + +#### Use British English + +For example: "colour" rather than "color", "labelled" rather than "labeled", "serialise" rather than "serialize". + +#### Spell out abbreviations + +- "for example" rather than "e.g." +- "that is" rather than "i.e." +- "and so on" rather than "etc" +- "unit testing" rather than UT, and so on + +However, it's okay to use acronyms that are very well known in our domain, like HTTP or JSON or RPC. + +#### Use sentence case in headings + +- Prefer: `## Use sentence case for headings` +- Avoid: `## Use Title Case for Headings`. + +#### YAML + +- Use quotes for strings: This is especially important if a string contains special characters or starts with a number. +- Indentation: Always use spaces and be consistent with the number of spaces throughout the same file (two, unless the file already uses a different number). +- Use comments: Comments will help you and others understand what that data is used for. + +## Common Review Patterns + +What to Look For: + +- **Missing or inadequate tests** for new functionality +- **Breaking changes** (almost never appropriate and should always be called out) +- **Performance regressions** in critical paths +- **Resource leaks** or missing cleanup +- **Security vulnerabilities** in input handling +- **Inconsistent error handling** patterns From 23021603514925332683970af93046ac11a99ea2 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Thu, 11 Dec 2025 09:52:47 +1300 Subject: [PATCH 4/8] Attempt to support agents for both Copilot and Claude via symlinks. --- .github/agents/doc-agent.md | 69 ++++++++++++++++++++++++++++++++++++ .github/agents/lint-agent.md | 20 +++++++++++ .github/agents/test-agent.md | 48 +++++++++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 .github/agents/doc-agent.md create mode 100644 .github/agents/lint-agent.md create mode 100644 .github/agents/test-agent.md diff --git a/.github/agents/doc-agent.md b/.github/agents/doc-agent.md new file mode 100644 index 000000000..814a85e77 --- /dev/null +++ b/.github/agents/doc-agent.md @@ -0,0 +1,69 @@ +# Expert technical writer + +You are an expert technical writer for the Ops project. + +## Your role +- You are fluent in Markdown, particularly Myst-Markdown, and also Sphinx, and can read Python code +- You write for a developer audience, focusing on clarity and practical examples +- Your task: read code from `ops/`, `testing/src/scenario/`, and `tracing/ops_tracing/` and generate or update documentation in `docs/` - you may also write and edit reference documentation that is found in the Python docstrings in those folders + +Follow the Diátaxis framework for documentation structure: +- **Tutorials** (`docs/tutorial/`) - Learning-oriented +- **How-to guides** (`docs/howto/`) - Task-oriented +- **Reference** - Generated from docstrings +- **Explanation** (`docs/explanation/`) - Understanding-oriented + +## Project knowledge +- **Tech Stack:** Python, Markdown, Sphinx +- **File Structure:** + - `ops/` – Core 'ops' code (you READ from here) + - `testing/src/scenario/` - Framework for writing tests for charm that use ops (you READ from here) + - `tracing/ops_tracing/` - An optional extra to provide tracing for charms (you READ from here) + - `docs/` – All documentation (you WRITE to here), other than the API reference docs + - `test/`, `testing/tests`, `tracing/test` – Unit, integration, and other tests (you IGNORE these) + +## Commands you can use + +All of these are run in the `docs/` directory. + +* build: `make html` +* clean built doc files: `make clean-doc` +* clean full environment: `make clean` +* check links: `make linkcheck` +* check markdown: `make lint-md` +* check spelling: `make spelling` +* check spelling (without building again): `make spellcheck` +* check accessibility: `make pa11y` +* check style guide compliance: `make vale` +* check metrics for documentation: `make allmetrics` + +## Documentation practices + +- Use short sentences, ideally with one or two clauses. +- Use headings to split the doc into sections. Make sure that the purpose of each section is clear from its heading. +- Avoid a long introduction. Assume that the reader is only going to scan the first paragraph and the headings. +- Avoid background context unless it's essential for the reader to understand. + +Recommended tone: + +- Use a casual tone, but avoid idioms. Common contractions such as "it's" and "doesn't" are great. +- Use "we" to include the reader in what you're explaining. +- Avoid passive descriptions. If you expect the reader to do something, give a direct instruction. + +Read [STYLE.md](../../STYLE.md) for more guidance on documentation. + +## Inter-sphinx + +When linking to external documentation, use inter-sphinx links whenever they are already configured for the project. These include: + + * `python`: Python language and standard library documentation + * `jubilant`: https://documentation.ubuntu.com/jubilant + * `juju`: https://documentation.ubuntu.com/juju/3.6 + * `charmcraft`: https://documentation.ubuntu.com/charmcraft/latest + * `charmlibs`: https://documentation.ubuntu.com/charmlibs/ + * `pebble`: https://documentation.ubuntu.com/pebble + +## Boundaries +- ✅ **Always do:** Write new files to `docs/`, follow the style examples, run the build to ensure there are no errors +- ⚠️ **Ask first:** Before modifying existing documents in a major way +- 🚫 **Never do:** Modify code, edit config files, change the build process diff --git a/.github/agents/lint-agent.md b/.github/agents/lint-agent.md new file mode 100644 index 000000000..ef876f5da --- /dev/null +++ b/.github/agents/lint-agent.md @@ -0,0 +1,20 @@ +# Formatting and lint fixer + +You are a senior engineer focused on ensuring that code across the Ops project is consistent in terms of style and formatting, and that there are no linting issues. + +## Your role +- Format code +- Fix import order +- Enforce naming conventions +- Ensure type annotations are present and correct + +## Tools you can use +- **Format**: `tox -e format` +- **Check and lint**: `tox -e lint` + +If running `ruff` outside of `tox`, note that `--preview` is used. `--fix` and `--unsafe-fixes` can be used when helpful. + +## Boundaries +- ✅ **Always:** Ensure that `tox -e lint` runs without any errors +- ⚠️ **Ask first:** Editing [pyproject.toml](../../pyproject.toml) or adding `noqa` directives. +- 🚫 **Never:** Write new code or change tests diff --git a/.github/agents/test-agent.md b/.github/agents/test-agent.md new file mode 100644 index 000000000..8dfe15a03 --- /dev/null +++ b/.github/agents/test-agent.md @@ -0,0 +1,48 @@ +@test-agent +This one writes tests. Point it at your test framework (Jest, PyTest, Playwright) and give it the command to run tests. The boundary here is critical: it can write to tests but should never remove a test because it is failing and cannot be fixed by the agent.  +    •    What it does: Writes unit tests, integration tests, and edge case coverage   +    •    Example commands: npm test, pytest -v, cargo test --coverage   +    •    Example boundaries: Write to tests/, never remove failing tests unless authorized by user + +# Test Runner Subagent + +## Purpose +Specialised agent for running tests, interpreting test failures, and debugging test issues in Ops. + +## Capabilities +- Run appropriate test suites (unit, integration, pebble, coverage) +- Interpret pytest failures and tracebacks +- Suggest fixes for failing tests +- Create new tests following project patterns +- Verify test coverage for changes + +## When to Use +- After making code changes that need testing +- When tests are failing and you need help debugging +- When adding new features that need test coverage +- For running specific test subsets efficiently + +## Testing Commands +```bash +tox # Run linting and unit tests +tox -e unit # Unit tests only +tox -e coverage # With coverage report +tox -e integration # Integration tests (slow) +tox -e pebble # Real Pebble tests +tox -e lint # Type checking and linting +``` + +## Workflow +1. Identify which tests to run based on changes +2. Execute tests and capture output +3. Analyse failures with full traceback context +4. Suggest specific fixes with file:line references +5. Re-run tests to verify fixes +6. Check coverage if needed + +## Key Files +- `test/` - Tests for the ops package +- `test/charms/` - Test charm implementations +- `testing/src/scenario/` - Tests for the ops-scenario package +- `tracing/test/` - Tests for the ops-tracing package +- `tox.ini` - Test environment configuration From bdd07404d5ac06fbfe7c440c637fa2404336ac0e Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Thu, 11 Dec 2025 09:53:23 +1300 Subject: [PATCH 5/8] Add two skills: tmux (borrowed and tested and works well) and Juju (new and seems to work ok but will need tweaking). --- .claude/skills/juju/SKILL.md | 49 +++++++++ .claude/skills/tmux/SKILL.md | 106 +++++++++++++++++++ .claude/skills/tmux/tools/find-sessions.sh | 112 +++++++++++++++++++++ .claude/skills/tmux/tools/wait-for-text.sh | 83 +++++++++++++++ 4 files changed, 350 insertions(+) create mode 100644 .claude/skills/juju/SKILL.md create mode 100644 .claude/skills/tmux/SKILL.md create mode 100644 .claude/skills/tmux/tools/find-sessions.sh create mode 100644 .claude/skills/tmux/tools/wait-for-text.sh diff --git a/.claude/skills/juju/SKILL.md b/.claude/skills/juju/SKILL.md new file mode 100644 index 000000000..f44a80090 --- /dev/null +++ b/.claude/skills/juju/SKILL.md @@ -0,0 +1,49 @@ +--- +name: juju +description: "Develop and test against a real Juju controller" +--- + +# Juju Skill + +Use Juju to develop and test changes in the Ops project. Works on Linux, ideally in a sandboxed environment. + +## Quickstart + +```bash +sudo concierge prepare -p dev # Installs all the required packages and bootstraps local clouds and Juju itself +juju switch concierge-lxd # For a machine charm +juju switch concierge-k8s # For a Kubernetes charm +juju add-model claude-[random ID] # Use a consistent prefix +``` + +After adding a model ALWAYS tell the user the name of the model and how they can inspect the status, filling in the details: + +``` +To inspect the status of the model: + juju status -m [contoller-name]:[model-name] + +Or to see the Juju logs: + juju debug-log -m [controller-name]:[model-name] +``` + +This must ALWAYS be printed right after a session was started and once again at the end of the tool loop. But the earlier you send it, the happier the user will be. + +## Watching output + +Use `juju debug-log -m [controller-name]:[model-name]`. See `juju debug-log --help` for details. + +## Important commands + +- `charmcraft pack` - use this to pack a charm so that it can be deployed +- `juju deploy -m [controller-name]:[model-name] ./path-to-charm.charm` - deploy a charm -- see `--help` for information about providing resources +- `juju status --format json -m [controller-name]:[model-name]` - get information about the status of the deployed charms +- `juju actions` and `juju run` - run an action that a charm defines +- `juju config` - set configuration for a charm +- `juju integrate` - create a relation between two charms +- `juju ssh` - open a SSH connection to a charm container, see `--help` for details + +## Cleanup + +- Remove a model when done: `juju destroy-model [controller-name]:[model-name]` +- Do *not* remove the controller unless explicitly requested +- Do *not* remove any of the packages installed by Concierge unless explicitly requested diff --git a/.claude/skills/tmux/SKILL.md b/.claude/skills/tmux/SKILL.md new file mode 100644 index 000000000..af85a2751 --- /dev/null +++ b/.claude/skills/tmux/SKILL.md @@ -0,0 +1,106 @@ +--- +name: tmux +description: "Remote control tmux sessions for interactive CLIs (python, gdb, etc.) by sending keystrokes and scraping pane output." +license: Vibecoded +source: https://github.com/mitsuhiko/agent-commands/blob/main/skills/tmux/SKILL.md +--- + +# tmux Skill + +Use tmux as a programmable terminal multiplexer for interactive work. Works on Linux and macOS with stock tmux; avoid custom config by using a private socket. + +## Quickstart (isolated socket) + +```bash +SOCKET_DIR=${TMPDIR:-/tmp}/claude-tmux-sockets # well-known dir for all agent sockets +mkdir -p "$SOCKET_DIR" +SOCKET="$SOCKET_DIR/claude.sock" # keep agent sessions separate from your personal tmux +SESSION=claude-python # slug-like names; avoid spaces +tmux -S "$SOCKET" new -d -s "$SESSION" -n shell +tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 -- 'python3 -q' Enter +tmux -S "$SOCKET" capture-pane -p -J -t "$SESSION":0.0 -S -200 # watch output +tmux -S "$SOCKET" kill-session -t "$SESSION" # clean up +``` + +After starting a session ALWAYS tell the user how to monitor the session by giving them a command to copy paste: + +``` +To monitor this session yourself: + tmux -S "$SOCKET" attach -t claude-lldb + +Or to capture the output once: + tmux -S "$SOCKET" capture-pane -p -J -t claude-lldb:0.0 -S -200 +``` + +This must ALWAYS be printed right after a session was started and once again at the end of the tool loop. But the earlier you send it, the happier the user will be. + +## Socket convention + +- Agents MUST place tmux sockets under `CLAUDE_TMUX_SOCKET_DIR` (defaults to `${TMPDIR:-/tmp}/claude-tmux-sockets`) and use `tmux -S "$SOCKET"` so we can enumerate/clean them. Create the dir first: `mkdir -p "$CLAUDE_TMUX_SOCKET_DIR"`. +- Default socket path to use unless you must isolate further: `SOCKET="$CLAUDE_TMUX_SOCKET_DIR/claude.sock"`. + +## Targeting panes and naming + +- Target format: `{session}:{window}.{pane}`, defaults to `:0.0` if omitted. Keep names short (e.g., `claude-py`, `claude-gdb`). +- Use `-S "$SOCKET"` consistently to stay on the private socket path. If you need user config, drop `-f /dev/null`; otherwise `-f /dev/null` gives a clean config. +- Inspect: `tmux -S "$SOCKET" list-sessions`, `tmux -S "$SOCKET" list-panes -a`. + +## Finding sessions + +- List sessions on your active socket with metadata: `./tools/find-sessions.sh -S "$SOCKET"`; add `-q partial-name` to filter. +- Scan all sockets under the shared directory: `./tools/find-sessions.sh --all` (uses `CLAUDE_TMUX_SOCKET_DIR` or `${TMPDIR:-/tmp}/claude-tmux-sockets`). + +## Sending input safely + +- Prefer literal sends to avoid shell splitting: `tmux -L "$SOCKET" send-keys -t target -l -- "$cmd"` +- When composing inline commands, use single quotes or ANSI C quoting to avoid expansion: `tmux ... send-keys -t target -- $'python3 -m http.server 8000'`. +- To send control keys: `tmux ... send-keys -t target C-c`, `C-d`, `C-z`, `Escape`, etc. + +## Watching output + +- Capture recent history (joined lines to avoid wrapping artifacts): `tmux -L "$SOCKET" capture-pane -p -J -t target -S -200`. +- For continuous monitoring, poll with the helper script (below) instead of `tmux wait-for` (which does not watch pane output). +- You can also temporarily attach to observe: `tmux -L "$SOCKET" attach -t "$SESSION"`; detach with `Ctrl+b d`. +- When giving instructions to a user, **explicitly print a copy/paste monitor command** alongside the action don't assume they remembered the command. + +## Spawning Processes + +Some special rules for processes: + +- when asked to debug, use lldb by default +- when starting a python interactive shell, always set the `PYTHON_BASIC_REPL=1` environment variable. This is very important as the non-basic console interferes with your send-keys. + +## Synchronizing / waiting for prompts + +- Use timed polling to avoid races with interactive tools. Example: wait for a Python prompt before sending code: + ```bash + ./tools/wait-for-text.sh -t "$SESSION":0.0 -p '^>>>' -T 15 -l 4000 + ``` +- For long-running commands, poll for completion text (`"Type quit to exit"`, `"Program exited"`, etc.) before proceeding. + +## Interactive tool recipes + +- **Python REPL**: `tmux ... send-keys -- 'python3 -q' Enter`; wait for `^>>>`; send code with `-l`; interrupt with `C-c`. Always with `PYTHON_BASIC_REPL`. +- **gdb**: `tmux ... send-keys -- 'gdb --quiet ./a.out' Enter`; disable paging `tmux ... send-keys -- 'set pagination off' Enter`; break with `C-c`; issue `bt`, `info locals`, etc.; exit via `quit` then confirm `y`. +- **Other TTY apps** (ipdb, psql, mysql, node, bash): same pattern—start the program, poll for its prompt, then send literal text and Enter. + +## Cleanup + +- Kill a session when done: `tmux -S "$SOCKET" kill-session -t "$SESSION"`. +- Kill all sessions on a socket: `tmux -S "$SOCKET" list-sessions -F '#{session_name}' | xargs -r -n1 tmux -S "$SOCKET" kill-session -t`. +- Remove everything on the private socket: `tmux -S "$SOCKET" kill-server`. + +## Helper: wait-for-text.sh + +`./tools/wait-for-text.sh` polls a pane for a regex (or fixed string) with a timeout. Works on Linux/macOS with bash + tmux + grep. + +```bash +./tools/wait-for-text.sh -t session:0.0 -p 'pattern' [-F] [-T 20] [-i 0.5] [-l 2000] +``` + +- `-t`/`--target` pane target (required) +- `-p`/`--pattern` regex to match (required); add `-F` for fixed string +- `-T` timeout seconds (integer, default 15) +- `-i` poll interval seconds (default 0.5) +- `-l` history lines to search from the pane (integer, default 1000) +- Exits 0 on first match, 1 on timeout. On failure prints the last captured text to stderr to aid debugging. diff --git a/.claude/skills/tmux/tools/find-sessions.sh b/.claude/skills/tmux/tools/find-sessions.sh new file mode 100644 index 000000000..cc3cdb100 --- /dev/null +++ b/.claude/skills/tmux/tools/find-sessions.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'USAGE' +Usage: find-sessions.sh [-L socket-name|-S socket-path|-A] [-q pattern] + +List tmux sessions on a socket (default tmux socket if none provided). + +Options: + -L, --socket tmux socket name (passed to tmux -L) + -S, --socket-path tmux socket path (passed to tmux -S) + -A, --all scan all sockets under CLAUDE_TMUX_SOCKET_DIR + -q, --query case-insensitive substring to filter session names + -h, --help show this help +USAGE +} + +socket_name="" +socket_path="" +query="" +scan_all=false +socket_dir="${CLAUDE_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/claude-tmux-sockets}" + +while [[ $# -gt 0 ]]; do + case "$1" in + -L|--socket) socket_name="${2-}"; shift 2 ;; + -S|--socket-path) socket_path="${2-}"; shift 2 ;; + -A|--all) scan_all=true; shift ;; + -q|--query) query="${2-}"; shift 2 ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1" >&2; usage; exit 1 ;; + esac +done + +if [[ "$scan_all" == true && ( -n "$socket_name" || -n "$socket_path" ) ]]; then + echo "Cannot combine --all with -L or -S" >&2 + exit 1 +fi + +if [[ -n "$socket_name" && -n "$socket_path" ]]; then + echo "Use either -L or -S, not both" >&2 + exit 1 +fi + +if ! command -v tmux >/dev/null 2>&1; then + echo "tmux not found in PATH" >&2 + exit 1 +fi + +list_sessions() { + local label="$1"; shift + local tmux_cmd=(tmux "$@") + + if ! sessions="$("${tmux_cmd[@]}" list-sessions -F '#{session_name}\t#{session_attached}\t#{session_created_string}' 2>/dev/null)"; then + echo "No tmux server found on $label" >&2 + return 1 + fi + + if [[ -n "$query" ]]; then + sessions="$(printf '%s\n' "$sessions" | grep -i -- "$query" || true)" + fi + + if [[ -z "$sessions" ]]; then + echo "No sessions found on $label" + return 0 + fi + + echo "Sessions on $label:" + printf '%s\n' "$sessions" | while IFS=$'\t' read -r name attached created; do + attached_label=$([[ "$attached" == "1" ]] && echo "attached" || echo "detached") + printf ' - %s (%s, started %s)\n' "$name" "$attached_label" "$created" + done +} + +if [[ "$scan_all" == true ]]; then + if [[ ! -d "$socket_dir" ]]; then + echo "Socket directory not found: $socket_dir" >&2 + exit 1 + fi + + shopt -s nullglob + sockets=("$socket_dir"/*) + shopt -u nullglob + + if [[ "${#sockets[@]}" -eq 0 ]]; then + echo "No sockets found under $socket_dir" >&2 + exit 1 + fi + + exit_code=0 + for sock in "${sockets[@]}"; do + if [[ ! -S "$sock" ]]; then + continue + fi + list_sessions "socket path '$sock'" -S "$sock" || exit_code=$? + done + exit "$exit_code" +fi + +tmux_cmd=(tmux) +socket_label="default socket" + +if [[ -n "$socket_name" ]]; then + tmux_cmd+=(-L "$socket_name") + socket_label="socket name '$socket_name'" +elif [[ -n "$socket_path" ]]; then + tmux_cmd+=(-S "$socket_path") + socket_label="socket path '$socket_path'" +fi + +list_sessions "$socket_label" "${tmux_cmd[@]:1}" diff --git a/.claude/skills/tmux/tools/wait-for-text.sh b/.claude/skills/tmux/tools/wait-for-text.sh new file mode 100644 index 000000000..56354be83 --- /dev/null +++ b/.claude/skills/tmux/tools/wait-for-text.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'USAGE' +Usage: wait-for-text.sh -t target -p pattern [options] + +Poll a tmux pane for text and exit when found. + +Options: + -t, --target tmux target (session:window.pane), required + -p, --pattern regex pattern to look for, required + -F, --fixed treat pattern as a fixed string (grep -F) + -T, --timeout seconds to wait (integer, default: 15) + -i, --interval poll interval in seconds (default: 0.5) + -l, --lines number of history lines to inspect (integer, default: 1000) + -h, --help show this help +USAGE +} + +target="" +pattern="" +grep_flag="-E" +timeout=15 +interval=0.5 +lines=1000 + +while [[ $# -gt 0 ]]; do + case "$1" in + -t|--target) target="${2-}"; shift 2 ;; + -p|--pattern) pattern="${2-}"; shift 2 ;; + -F|--fixed) grep_flag="-F"; shift ;; + -T|--timeout) timeout="${2-}"; shift 2 ;; + -i|--interval) interval="${2-}"; shift 2 ;; + -l|--lines) lines="${2-}"; shift 2 ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1" >&2; usage; exit 1 ;; + esac +done + +if [[ -z "$target" || -z "$pattern" ]]; then + echo "target and pattern are required" >&2 + usage + exit 1 +fi + +if ! [[ "$timeout" =~ ^[0-9]+$ ]]; then + echo "timeout must be an integer number of seconds" >&2 + exit 1 +fi + +if ! [[ "$lines" =~ ^[0-9]+$ ]]; then + echo "lines must be an integer" >&2 + exit 1 +fi + +if ! command -v tmux >/dev/null 2>&1; then + echo "tmux not found in PATH" >&2 + exit 1 +fi + +# End time in epoch seconds (integer, good enough for polling) +start_epoch=$(date +%s) +deadline=$((start_epoch + timeout)) + +while true; do + # -J joins wrapped lines, -S uses negative index to read last N lines + pane_text="$(tmux capture-pane -p -J -t "$target" -S "-${lines}" 2>/dev/null || true)" + + if printf '%s\n' "$pane_text" | grep $grep_flag -- "$pattern" >/dev/null 2>&1; then + exit 0 + fi + + now=$(date +%s) + if (( now >= deadline )); then + echo "Timed out after ${timeout}s waiting for pattern: $pattern" >&2 + echo "Last ${lines} lines from $target:" >&2 + printf '%s\n' "$pane_text" >&2 + exit 1 + fi + + sleep "$interval" +done From a7e1e545e040c468cc8e06ad369ce8223999c4bc Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Thu, 11 Dec 2025 09:59:47 +1300 Subject: [PATCH 6/8] Remove placeholder text. --- .claude/subagents/test-agent.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.claude/subagents/test-agent.md b/.claude/subagents/test-agent.md index 8dfe15a03..4bf5fb53c 100644 --- a/.claude/subagents/test-agent.md +++ b/.claude/subagents/test-agent.md @@ -1,9 +1,3 @@ -@test-agent -This one writes tests. Point it at your test framework (Jest, PyTest, Playwright) and give it the command to run tests. The boundary here is critical: it can write to tests but should never remove a test because it is failing and cannot be fixed by the agent.  -    •    What it does: Writes unit tests, integration tests, and edge case coverage   -    •    Example commands: npm test, pytest -v, cargo test --coverage   -    •    Example boundaries: Write to tests/, never remove failing tests unless authorized by user - # Test Runner Subagent ## Purpose From 6279f292a520f881dae4fbce32e608c39dec5b91 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Thu, 11 Dec 2025 10:05:32 +1300 Subject: [PATCH 7/8] Use symlinks. --- .github/agents/doc-agent.md | 70 +----------------------------------- .github/agents/lint-agent.md | 21 +---------- .github/agents/test-agent.md | 49 +------------------------ 3 files changed, 3 insertions(+), 137 deletions(-) mode change 100644 => 120000 .github/agents/doc-agent.md mode change 100644 => 120000 .github/agents/lint-agent.md mode change 100644 => 120000 .github/agents/test-agent.md diff --git a/.github/agents/doc-agent.md b/.github/agents/doc-agent.md deleted file mode 100644 index 814a85e77..000000000 --- a/.github/agents/doc-agent.md +++ /dev/null @@ -1,69 +0,0 @@ -# Expert technical writer - -You are an expert technical writer for the Ops project. - -## Your role -- You are fluent in Markdown, particularly Myst-Markdown, and also Sphinx, and can read Python code -- You write for a developer audience, focusing on clarity and practical examples -- Your task: read code from `ops/`, `testing/src/scenario/`, and `tracing/ops_tracing/` and generate or update documentation in `docs/` - you may also write and edit reference documentation that is found in the Python docstrings in those folders - -Follow the Diátaxis framework for documentation structure: -- **Tutorials** (`docs/tutorial/`) - Learning-oriented -- **How-to guides** (`docs/howto/`) - Task-oriented -- **Reference** - Generated from docstrings -- **Explanation** (`docs/explanation/`) - Understanding-oriented - -## Project knowledge -- **Tech Stack:** Python, Markdown, Sphinx -- **File Structure:** - - `ops/` – Core 'ops' code (you READ from here) - - `testing/src/scenario/` - Framework for writing tests for charm that use ops (you READ from here) - - `tracing/ops_tracing/` - An optional extra to provide tracing for charms (you READ from here) - - `docs/` – All documentation (you WRITE to here), other than the API reference docs - - `test/`, `testing/tests`, `tracing/test` – Unit, integration, and other tests (you IGNORE these) - -## Commands you can use - -All of these are run in the `docs/` directory. - -* build: `make html` -* clean built doc files: `make clean-doc` -* clean full environment: `make clean` -* check links: `make linkcheck` -* check markdown: `make lint-md` -* check spelling: `make spelling` -* check spelling (without building again): `make spellcheck` -* check accessibility: `make pa11y` -* check style guide compliance: `make vale` -* check metrics for documentation: `make allmetrics` - -## Documentation practices - -- Use short sentences, ideally with one or two clauses. -- Use headings to split the doc into sections. Make sure that the purpose of each section is clear from its heading. -- Avoid a long introduction. Assume that the reader is only going to scan the first paragraph and the headings. -- Avoid background context unless it's essential for the reader to understand. - -Recommended tone: - -- Use a casual tone, but avoid idioms. Common contractions such as "it's" and "doesn't" are great. -- Use "we" to include the reader in what you're explaining. -- Avoid passive descriptions. If you expect the reader to do something, give a direct instruction. - -Read [STYLE.md](../../STYLE.md) for more guidance on documentation. - -## Inter-sphinx - -When linking to external documentation, use inter-sphinx links whenever they are already configured for the project. These include: - - * `python`: Python language and standard library documentation - * `jubilant`: https://documentation.ubuntu.com/jubilant - * `juju`: https://documentation.ubuntu.com/juju/3.6 - * `charmcraft`: https://documentation.ubuntu.com/charmcraft/latest - * `charmlibs`: https://documentation.ubuntu.com/charmlibs/ - * `pebble`: https://documentation.ubuntu.com/pebble - -## Boundaries -- ✅ **Always do:** Write new files to `docs/`, follow the style examples, run the build to ensure there are no errors -- ⚠️ **Ask first:** Before modifying existing documents in a major way -- 🚫 **Never do:** Modify code, edit config files, change the build process diff --git a/.github/agents/doc-agent.md b/.github/agents/doc-agent.md new file mode 120000 index 000000000..906460906 --- /dev/null +++ b/.github/agents/doc-agent.md @@ -0,0 +1 @@ +.claude/subagents/doc-agent.md \ No newline at end of file diff --git a/.github/agents/lint-agent.md b/.github/agents/lint-agent.md deleted file mode 100644 index ef876f5da..000000000 --- a/.github/agents/lint-agent.md +++ /dev/null @@ -1,20 +0,0 @@ -# Formatting and lint fixer - -You are a senior engineer focused on ensuring that code across the Ops project is consistent in terms of style and formatting, and that there are no linting issues. - -## Your role -- Format code -- Fix import order -- Enforce naming conventions -- Ensure type annotations are present and correct - -## Tools you can use -- **Format**: `tox -e format` -- **Check and lint**: `tox -e lint` - -If running `ruff` outside of `tox`, note that `--preview` is used. `--fix` and `--unsafe-fixes` can be used when helpful. - -## Boundaries -- ✅ **Always:** Ensure that `tox -e lint` runs without any errors -- ⚠️ **Ask first:** Editing [pyproject.toml](../../pyproject.toml) or adding `noqa` directives. -- 🚫 **Never:** Write new code or change tests diff --git a/.github/agents/lint-agent.md b/.github/agents/lint-agent.md new file mode 120000 index 000000000..1ffca5b2d --- /dev/null +++ b/.github/agents/lint-agent.md @@ -0,0 +1 @@ +.claude/subagents/lint-agent.md \ No newline at end of file diff --git a/.github/agents/test-agent.md b/.github/agents/test-agent.md deleted file mode 100644 index 8dfe15a03..000000000 --- a/.github/agents/test-agent.md +++ /dev/null @@ -1,48 +0,0 @@ -@test-agent -This one writes tests. Point it at your test framework (Jest, PyTest, Playwright) and give it the command to run tests. The boundary here is critical: it can write to tests but should never remove a test because it is failing and cannot be fixed by the agent.  -    •    What it does: Writes unit tests, integration tests, and edge case coverage   -    •    Example commands: npm test, pytest -v, cargo test --coverage   -    •    Example boundaries: Write to tests/, never remove failing tests unless authorized by user - -# Test Runner Subagent - -## Purpose -Specialised agent for running tests, interpreting test failures, and debugging test issues in Ops. - -## Capabilities -- Run appropriate test suites (unit, integration, pebble, coverage) -- Interpret pytest failures and tracebacks -- Suggest fixes for failing tests -- Create new tests following project patterns -- Verify test coverage for changes - -## When to Use -- After making code changes that need testing -- When tests are failing and you need help debugging -- When adding new features that need test coverage -- For running specific test subsets efficiently - -## Testing Commands -```bash -tox # Run linting and unit tests -tox -e unit # Unit tests only -tox -e coverage # With coverage report -tox -e integration # Integration tests (slow) -tox -e pebble # Real Pebble tests -tox -e lint # Type checking and linting -``` - -## Workflow -1. Identify which tests to run based on changes -2. Execute tests and capture output -3. Analyse failures with full traceback context -4. Suggest specific fixes with file:line references -5. Re-run tests to verify fixes -6. Check coverage if needed - -## Key Files -- `test/` - Tests for the ops package -- `test/charms/` - Test charm implementations -- `testing/src/scenario/` - Tests for the ops-scenario package -- `tracing/test/` - Tests for the ops-tracing package -- `tox.ini` - Test environment configuration diff --git a/.github/agents/test-agent.md b/.github/agents/test-agent.md new file mode 120000 index 000000000..a4b7f3028 --- /dev/null +++ b/.github/agents/test-agent.md @@ -0,0 +1 @@ +.claude/subagents/test-agent.md \ No newline at end of file From 33a6969c77d41b7d97c255aab39d3842c5da6f5a Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Thu, 11 Dec 2025 10:07:34 +1300 Subject: [PATCH 8/8] Fix link location. --- .github/copilot-instructions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 018cac8dc..e94a957aa 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,6 +1,6 @@ # Copilot Instructions for Ops Library Code Review -These instructions are specifically for GitHub Copilot when reviewing code in the `ops` library repository. For more general instructions ignore this file and refer to [AGENTS.md](AGENTS.md). +These instructions are specifically for GitHub Copilot when reviewing code in the `ops` library repository. For more general instructions ignore this file and refer to [AGENTS.md](../AGENTS.md). Remember: The ops library is foundational infrastructure. Prioritise stability, clarity, and maintainability over cleverness. @@ -8,7 +8,7 @@ Remember: The ops library is foundational infrastructure. Prioritise stability, ### Python Style and Standards -For the most part, style and coding standards are enforced by ruff and do *not* need to be considered in code review. There are some additional recommendations in [STYLE.md](STYLE.md) that should be followed. +For the most part, style and coding standards are enforced by ruff and do *not* need to be considered in code review. There are some additional recommendations in [STYLE.md](../STYLE.md) that should be followed. ### Import modules, not objects