From 67f8056e0b1c25d3971102ff5181a1843e334a0b Mon Sep 17 00:00:00 2001 From: reachableceo Date: Mon, 10 Aug 2026 09:53:42 -0500 Subject: [PATCH] refactor(tooling): merge redmine-cli into tooling-cli/redmine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidate the redmine-cli source into tooling-cli/redmine/ (CLI source, Dockerfile, README, AGENTS.md) with all documentation rewritten to invoke the container via raw docker run and credentials from the centralized ~/.creds/redmine.env store. No bin/ wrapper, no system-dependent paths in the docs. Removes the old redmine-cli/ subdirectory, updates the KNELCredsManager consumer table, and updates STATUS.md + AGENTS.md to reference the new location and registry image. 💘 Generated with Crush Assisted-by: Crush:glm-5.2 --- AGENTS.md | 4 +- STATUS.md | 2 +- tooling-cli/KNELCredsManager/README.md | 2 +- .../redmine}/.env.example | 0 tooling-cli/redmine/.gitignore | 8 + tooling-cli/redmine/AGENTS.md | 240 ++++++++++++++++++ .../redmine}/Dockerfile | 0 .../redmine}/README.md | 43 ++-- .../redmine}/requirements.txt | 0 .../redmine}/src/redmine_cli.py | 0 10 files changed, 276 insertions(+), 23 deletions(-) rename {redmine-cli => tooling-cli/redmine}/.env.example (100%) create mode 100644 tooling-cli/redmine/.gitignore create mode 100644 tooling-cli/redmine/AGENTS.md rename {redmine-cli => tooling-cli/redmine}/Dockerfile (100%) rename {redmine-cli => tooling-cli/redmine}/README.md (70%) rename {redmine-cli => tooling-cli/redmine}/requirements.txt (100%) rename {redmine-cli => tooling-cli/redmine}/src/redmine_cli.py (100%) diff --git a/AGENTS.md b/AGENTS.md index 815c664..285bd48 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,8 +51,8 @@ ALL work performed on this project MUST be documented in [JOURNAL.md](JOURNAL.md - **Custom images**: publish to the Gitea container registry at `git.knownelement.com/reachableceo/:` (Docker is pre-authenticated). - **Standalone CLI tools**: containerized CLI tools (not MCP/LSP servers) live - in their own top-level directory (e.g. `redmine-cli/`). Invoked on-demand via - `docker run --env-file .env `, not as long-running services. + under `tooling-cli/` (e.g. `tooling-cli/redmine/`). Invoked on-demand via + `docker run --env-file ~/.creds/.env `, not as long-running services. ## Crush Configuration diff --git a/STATUS.md b/STATUS.md index 15b07c2..43dd6af 100644 --- a/STATUS.md +++ b/STATUS.md @@ -217,4 +217,4 @@ Standalone containerized CLI tools (not MCP/LSP servers). Invoked on-demand via | Tool | Image | Status | Notes | |------|-------|--------|-------| -| redmine-cli | kneldevstack-aimiddleware-redmine-cli | Working | Custom CLI (python-redmine) to list/show/create/update/close Redmine issues. Verified end-to-end against projects.knownelement.com. Requires REDMINE_URL and REDMINE_API_KEY. Source in `redmine-cli/`. | +| redmine-cli | git.knownelement.com/reachableceo/redmine-cli:latest | Working | Custom CLI (python-redmine) to list/show/create/update/close Redmine issues. Verified end-to-end against projects.knownelement.com. Requires REDMINE_URL and REDMINE_API_KEY. Source in `tooling-cli/redmine/`. | diff --git a/tooling-cli/KNELCredsManager/README.md b/tooling-cli/KNELCredsManager/README.md index 237f1c2..4821166 100644 --- a/tooling-cli/KNELCredsManager/README.md +++ b/tooling-cli/KNELCredsManager/README.md @@ -26,7 +26,7 @@ All credential consumers source from `~/.creds/`: | Service | Wrapper / consumer | Mechanism | |---|---|---| -| Redmine | `~/daytoday/redmine/bin/redmine` | `--env-file ~/.creds/redmine.env` | +| Redmine | `redmine-cli` container (`tooling-cli/redmine/`) | `docker run --env-file ~/.creds/redmine.env` | | Redmine MCP | `mcp-redmine-wrapper.sh` | `set -a; . ~/.creds/redmine.env; set +a` | | Discourse | `discourse-cli` container (`tooling-cli/discourse/`) | `docker run --env-file ~/.creds/discourse.env` | | Discourse MCP | `mcp-discourse-wrapper.sh` | `set -a; . ~/.creds/discourse.env; set +a` | diff --git a/redmine-cli/.env.example b/tooling-cli/redmine/.env.example similarity index 100% rename from redmine-cli/.env.example rename to tooling-cli/redmine/.env.example diff --git a/tooling-cli/redmine/.gitignore b/tooling-cli/redmine/.gitignore new file mode 100644 index 0000000..78099e7 --- /dev/null +++ b/tooling-cli/redmine/.gitignore @@ -0,0 +1,8 @@ +# Secrets - never commit +.env + +# OS / editor cruft +.DS_Store +*.swp +*.swo +*~ diff --git a/tooling-cli/redmine/AGENTS.md b/tooling-cli/redmine/AGENTS.md new file mode 100644 index 0000000..1da5dd8 --- /dev/null +++ b/tooling-cli/redmine/AGENTS.md @@ -0,0 +1,240 @@ +# AGENTS.md — Redmine CLI tooling + +This directory holds the `redmine-cli` Docker container — a thin Python CLI +over `python-redmine` for Redmine issue tracking. Every Crush session that +works on or with this tool should read this file first: how to invoke it, +what commands exist, what gotchas to avoid, and the patterns to follow for +common ticket operations. + +## Connection & invocation + +Invoke the real container with `docker run`. Credentials come from the +centralized credential store (see `tooling-cli/KNELCredsManager`): + +```bash +docker run --rm --env-file ~/.creds/redmine.env \ + git.knownelement.com/reachableceo/redmine-cli:latest +``` + +There is intentionally **no `bin/` wrapper** — invoke the real container, as +the project-wide rules require. For readability in an interactive session you +may define a throwaway shell alias (do not commit one): + +```bash +alias redmine='docker run --rm --env-file ~/.creds/redmine.env \ + git.knownelement.com/reachableceo/redmine-cli:latest' +``` + +The examples below write `redmine ` for brevity; expand it to the full +`docker run` line (or set the alias) before running. + +| Item | Value | +|------|-------| +| Instance | `https://projects.knownelement.com` | +| API user | Charles N (id 5, login `reachableceo`) | +| Credentials | `~/.creds/redmine.env` (`REDMINE_URL`, `REDMINE_API_KEY`) | +| CLI image | `git.knownelement.com/reachableceo/redmine-cli:latest` | +| Source | `src/redmine_cli.py` in this directory | + +To use a locally built image instead of the registry image, either set +`REDMINE_CLI_IMAGE` or swap the tag for `kneldevstack-aimiddleware-redmine-cli:latest`. + +## Quick start + +```bash +# Connection sanity check (run this first in any session): +redmine whoami + +# Look at a ticket: +redmine show 314 + +# Your queue: +redmine list --assigned-to-me +``` + +## Command reference + +| Command | Description | +|---------|-------------| +| `whoami` | Authenticated user + connection test. | +| `projects` | List projects (id, identifier, name). | +| `statuses` | List issue statuses + which are "closed". | +| `list` (`ls`) | List issues. Filters below. | +| `show ` | Full issue detail incl. note history. | +| `create` | Create an issue. | +| `update ` | Update an issue (status, notes, assignee, done, ...). | +| `close ` | Close (first closed status, done ratio 100%). | + +### `list` filters +- `-m, --assigned-to-me` — only issues assigned to you +- `-a, --assigned-to ` — filter by assignee user id +- `-p, --project ` — filter by project +- `-s, --status ` — filter by status +- `-l, --limit ` — max results (default 50) +- `--sort ` — Redmine sort spec (default `priority:desc,updated_on:desc`) + +### `create` options +- `-p, --project ` **required** +- `-s, --subject` **required** +- `-d, --description` +- `-a, --assigned-to ` +- `-t, --tracker ` +- `--priority ` +- `--status ` + +### `update` options +- `-s, --status ` +- `-n, --notes ` +- `-a, --assigned-to ` +- `--done-ratio <0-100>` +- `--subject` +- `--priority ` + +## Key project / tracker / status IDs + +### Most-used projects +| ID | Identifier | Name | +|----|------------|------| +| 55 | `technicaloperations` | Known Element Enterprises - Technology & Facility Services | +| 62 | `business-operations` | Known Element Enterprises - Business Services | +| 77 | `tsys-group` | TSYS Group | + +Run `redmine projects` for the full list. + +### Statuses +| ID | Name | Closed? | +|----|------|---------| +| 1 | New | no | +| 2 | In Progress | no | +| 3 | Resolved | yes | +| 4 | Feedback | no | +| 5 | Closed | yes | +| 6 | Rejected | no | + +### Trackers +Tracker 3 = **Support** (the most common one in project 55). Inspect a +parent issue to inherit its exact tracker. + +## Gotcha: creating subtasks + +`redmine create` has **no `--parent` flag**. To create a subtask of an +existing issue you must use `python-redmine` directly inside the container +(via `--entrypoint python`). + +Pattern (mount a script and run it in the same image): + +```bash +cat > /tmp/script.py <<'PY' +import os +from redminelib import Redmine + +rm = Redmine(os.environ["REDMINE_URL"].rstrip("/"), key=os.environ["REDMINE_API_KEY"]) + +issue = rm.issue.create( + project_id=55, # inherit from parent + tracker_id=3, # inherit from parent + priority_id=2, # inherit from parent + status_id=4, # Feedback = freshly created, awaiting work + assigned_to_id=5, # inherit from parent + parent_issue_id=314, # THE KEY FIELD + subject="Your subject", + description="Your description", +) +print(f"Created #{issue.id}") +PY + +docker run --rm --env-file ~/.creds/redmine.env \ + --entrypoint python \ + -v /tmp/script.py:/tmp/script.py \ + git.knownelement.com/reachableceo/redmine-cli:latest \ + /tmp/script.py +``` + +**Before creating subtasks**, inspect the parent to inherit its attributes: + +```bash +docker run --rm --env-file ~/.creds/redmine.env \ + --entrypoint python \ + git.knownelement.com/reachableceo/redmine-cli:latest -c " +import os +from redminelib import Redmine +rm = Redmine(os.environ['REDMINE_URL'].rstrip('/'), key=os.environ['REDMINE_API_KEY']) +i = rm.issue.get(PARENT_ID) +print('project_id:', i.project.id) +print('tracker_id:', i.tracker.id, i.tracker.name) +print('priority_id:', i.priority.id, i.priority.name) +print('assigned_to_id:', getattr(i.assigned_to,'id',None)) +print('status_id:', i.status.id, i.status.name) +" +``` + +## Patterns + +### Pattern: review & split a ticket +1. `redmine show ` — read the full ticket (description + history). +2. Identify the **distinct bodies of work**. Each should be independently + trackable and assignable. +3. Map out **dependencies** between the pieces (what blocks what). +4. Propose a split to the user as a table: proposed subject, scope, and + dependencies. Wait for approval before creating anything. +5. On approval: create subtasks (see "Gotcha: creating subtasks" above), + inheriting project/tracker/priority/assignee from the parent. +6. Update the parent's **description** to an index table of children + + recommended execution order. Add a note explaining the split. +7. Verify with `redmine show `. + +### Pattern: ticket conventions +- **Subject prefix:** OAM-pool tickets use `OAM: `. Match the parent's + naming convention if one exists. +- **Description header:** always start with `Parent/umbrella: # ().` +- **Scope section:** bullet list of concrete deliverables. Use `- [ ]` for + checklist items within a subtask. +- **Dependencies section:** list what the ticket depends on and what it blocks, + referencing ticket numbers once they exist. +- **Newly created children** go to status **Feedback (4)** so they're visible + but not yet "in progress". +- **Tables** in descriptions render in Redmine's Markdown pipeline (`| a | b |`). + +### Pattern: bulk operations +For anything beyond a single `create`/`update`/`close`, write a Python script +and run it inside the container as shown in the subtask gotcha above. This +applies to: batch status changes, bulk ticket creation, inspecting parent +attributes, relationship wiring, etc. + +### Pattern: before you act +- Always `redmine show ` before updating — confirm current status, + assignee, and existing notes so you don't clobber context. +- Never delete or close a ticket unless the user explicitly asks. +- When in doubt about project/tracker/priority, inherit from the parent or + ask the user. + +## Git workflow + +### Atomic commits +Each commit is **one logical change** — one feature, one fix, one doc update. +If you're tempted to write "and also..." in a commit message, that's a sign +to split it into two commits. Stage precisely (`git add `, not +`git add -A`) so unrelated changes don't get bundled. + +### Conventional commit messages +Use the [Conventional Commits](https://www.conventionalcommits.org/) format: + +``` +(): + + +``` + +Types used in this repo: `feat`, `fix`, `docs`, `refactor`, `chore`, `style`. + +Rules: +- Subject line **under 72 chars**, lowercase, imperative mood. +- No period at end of subject. +- Body wrapped at 72 chars, explains **why** the change exists. + +### Commit cadence +- **Commit early and often.** Don't accumulate a pile of unrelated changes. +- Commit **without asking** — if you made a coherent change, commit it. +- Every task (ticket review, split, script addition, doc update) ends with + the relevant files committed. +- Run `git status` before committing to stage only what belongs together. diff --git a/redmine-cli/Dockerfile b/tooling-cli/redmine/Dockerfile similarity index 100% rename from redmine-cli/Dockerfile rename to tooling-cli/redmine/Dockerfile diff --git a/redmine-cli/README.md b/tooling-cli/redmine/README.md similarity index 70% rename from redmine-cli/README.md rename to tooling-cli/redmine/README.md index 35fc234..eb7a9fa 100644 --- a/redmine-cli/README.md +++ b/tooling-cli/redmine/README.md @@ -15,19 +15,21 @@ wrapper. ## Configuration -Copy the env template and fill in real values (the `.env` is gitignored): +Credentials live in the **centralized credential store** at +`~/.creds/redmine.env` (see `tooling-cli/KNELCredsManager`). It holds: -```bash -cp .env.example .env -# then edit .env: -# REDMINE_URL=https://your-redmine.example.com -# REDMINE_API_KEY=abc123... ``` +REDMINE_URL=https://your-redmine.example.com +REDMINE_API_KEY=abc123... +``` + +Use `.env.example` in this directory as a template if you need to create one. +Permissions: directory `700`, the env file `600` (owner read/write only). ## Build ```bash -docker build -t kneldevstack-aimiddleware-redmine-cli . +docker build -t kneldevstack-aimiddleware-redmine-cli:latest . ``` A prebuilt image is also available in the Gitea registry: @@ -38,21 +40,25 @@ git.knownelement.com/reachableceo/redmine-cli:latest ## Usage -### Via the repo wrapper (loads `.env` automatically) +Invoke the container directly with `docker run`. Pass credentials from the +centralized store via `--env-file`: ```bash -bin/redmine whoami -bin/redmine list --assigned-to-me -bin/redmine show 123 -bin/redmine close 123 --notes "work complete" +docker run --rm --env-file ~/.creds/redmine.env \ + git.knownelement.com/reachableceo/redmine-cli:latest whoami + +docker run --rm --env-file ~/.creds/redmine.env \ + git.knownelement.com/reachableceo/redmine-cli:latest list --assigned-to-me + +docker run --rm --env-file ~/.creds/redmine.env \ + git.knownelement.com/reachableceo/redmine-cli:latest show 123 + +docker run --rm --env-file ~/.creds/redmine.env \ + git.knownelement.com/reachableceo/redmine-cli:latest close 123 --notes "work complete" ``` -### Via docker directly (from the repo root) - -```bash -docker run --rm --env-file .env kneldevstack-aimiddleware-redmine-cli whoami -docker run --rm --env-file .env kneldevstack-aimiddleware-redmine-cli list --assigned-to-me -``` +To use a locally built image instead of the registry image, either set +`REDMINE_CLI_IMAGE` or swap the image tag for `kneldevstack-aimiddleware-redmine-cli:latest`. ## Commands @@ -94,4 +100,3 @@ docker run --rm --env-file .env kneldevstack-aimiddleware-redmine-cli list --ass | ----------------- | -------- | ------------------------------------ | | `REDMINE_URL` | yes | Base URL of the Redmine instance. | | `REDMINE_API_KEY` | yes | API key of the acting user. | -| `REDMINE_CLI_IMAGE` | no | Override the image tag used by `bin/redmine` (default `git.knownelement.com/reachableceo/redmine-cli:latest`). | diff --git a/redmine-cli/requirements.txt b/tooling-cli/redmine/requirements.txt similarity index 100% rename from redmine-cli/requirements.txt rename to tooling-cli/redmine/requirements.txt diff --git a/redmine-cli/src/redmine_cli.py b/tooling-cli/redmine/src/redmine_cli.py similarity index 100% rename from redmine-cli/src/redmine_cli.py rename to tooling-cli/redmine/src/redmine_cli.py