Update instructions, agents, skills - part 1
Quality Gate / gate (push) Successful in 2m41s

This commit is contained in:
Jim Lancaster
2026-09-02 14:05:41 -05:00
parent 88cef169c4
commit e5ef4d4422
8 changed files with 94 additions and 20 deletions
+4 -6
View File
@@ -1,12 +1,6 @@
--- ---
name: Python Architect Reviewer name: Python Architect Reviewer
description: Evidence-based senior architect reviewer for FastAPI, NiceGUI, and SQLModel codebases. description: Evidence-based senior architect reviewer for FastAPI, NiceGUI, and SQLModel codebases.
tools:
- read_file
- list_dir
- file_search
- grep_search
- run_in_terminal
skills: skills:
- python-code-reviewer - python-code-reviewer
--- ---
@@ -15,6 +9,10 @@ skills:
You are a Senior Python Architect performing an evidence-based, read-only code review. You are a Senior Python Architect performing an evidence-based, read-only code review.
> No `tools:` allowlist is declared here on purpose. Tool identifiers differ between the runtimes
> this agent is invoked from, so a hard-coded list silently under-tools the agent in one of them.
> Read-only discipline is enforced by the **Read-Only Scope** rule below, not by the frontmatter.
## Operating Principles ## Operating Principles
- **Stack Context:** Python 3.12+, FastAPI, NiceGUI, SQLModel, SQLAlchemy (SQLite/PostgreSQL), Pydantic V2, asyncio workers, and OpenRouter adapters. - **Stack Context:** Python 3.12+, FastAPI, NiceGUI, SQLModel, SQLAlchemy (SQLite/PostgreSQL), Pydantic V2, asyncio workers, and OpenRouter adapters.
@@ -7,6 +7,11 @@ applyTo: 'src/transcription/**/*.py'
Keep docs in sync in the same change whenever implementation alters a documented contract, behavior, or roadmap decision. Keep docs in sync in the same change whenever implementation alters a documented contract, behavior, or roadmap decision.
Documentation targets below always refer to the **current** baseline. `docs/index.md` states which
baseline that is; resolve any version-specific document from there. Never cite a superseded version
tree by name in this file or in the docs you update — retired revision trees are not authority, and
`tests/test_meta_contract_guards.py` fails active contract files that route authority through them.
## Update documentation when any of these change ## Update documentation when any of these change
1. **Schema/Data contract** 1. **Schema/Data contract**
@@ -27,7 +32,8 @@ Keep docs in sync in the same change whenever implementation alters a documented
5. **Roadmap/scope decisions** 5. **Roadmap/scope decisions**
- Version targets, sequencing, deferrals, and accepted alternatives. - Version targets, sequencing, deferrals, and accepted alternatives.
- **Required doc update:** `docs/roadmap_plan.md` and related backlog docs (for example `docs/ver4.8/feature_backlog_v4_8.md`). - **Required doc update:** `docs/roadmap_plan.md`, plus any backlog or feature document for the
current baseline. Locate it through `docs/index.md` rather than assuming a version-named path.
## Working rule ## Working rule
@@ -75,6 +75,28 @@ Required internal -> canonical mapping:
- Include actionable remediation guidance aligned to category. - Include actionable remediation guidance aligned to category.
- Keep envelope structure consistent across API endpoints. - Keep envelope structure consistent across API endpoints.
### `AppError.message` vs `AppError.detail`
`AppError` carries two texts with different audiences, and they must not be collapsed. Getting this
wrong has already caused a real defect in this repository, in both directions.
| Attribute | Audience | Reaches | Rule |
| :--- | :--- | :--- | :--- |
| `message` | User and API clients | `ErrorEnvelope.message`, UI notifications | Stays generic. Never embed exception text, provider payloads, or filesystem paths. |
| `detail` | Internal only | Logs, and `format_error_detail` -> `ExecutionAttempt.error_detail` and `MaintenanceRun.error_detail` | Carries the root cause. Never rendered to users or serialized into an envelope. |
- Putting root-cause data in `message` leaks infrastructure detail to users.
- Omitting it from `detail` silently degrades the provenance record this system exists to preserve —
a failed attempt whose `error_detail` says nothing is an attempt that cannot be diagnosed later.
- When you raise from a caught exception, populate **both**: a generic `message` and a `detail`
carrying `type(exc).__name__` and the exception text, with `raise ... from exc`.
- `detail` is optional (`None`). A read path that assumes it is populated must handle its absence.
- Before changing either attribute, or any helper that formats them, enumerate every consumer —
evidence writes, maintenance runs, logging, API envelopes, and UI presentation all read these
fields, and tests assert on the persisted text.
Canonical definitions live in `src/transcription/errors.py`; see also `docs/error_handling.md`.
## Logging and Diagnostics ## Logging and Diagnostics
- Log operation identifiers and error IDs where available. - Log operation identifiers and error IDs where available.
+47 -7
View File
@@ -17,9 +17,17 @@ applyTo: 'src/transcription/services/*.py'
- **A service module must not import another service module.** This is enforced by - **A service module must not import another service module.** This is enforced by
[test_service_boundaries](../../tests/test_service_boundaries.py). Shared types go in a [test_service_boundaries](../../tests/test_service_boundaries.py). Shared types go in a
neutral module that defines no service class (see [errors](../../src/transcription/services/errors.py)). neutral module that defines no service class (see [errors](../../src/transcription/services/errors.py)).
- Not every module in this package is a service. Helper modules that define no `*Service` - Not every module in this package is a service. Modules fall into three kinds:
class (`base`, `errors`, `normalization`, `prompts`, `quality`, `media_storage`, - **Aggregate services** own models and define a `*Service` class: `documents.py`, `sources.py`,
`source_media`) are free-function modules and are exempt from the service rules below. `jobs.py`, `people.py`, `photos.py`, `maintenance.py`, and `evidence.py` (read/projection only,
owns nothing).
- **Orchestration modules** define no service class and compose writes across aggregates:
`store.py`, `workflows.py`. They are the sanctioned place to create or delete rows owned by more
than one service — see [Service Composition](#service-composition).
- **Shared infrastructure and free-function helpers** are exempt from the service rules below:
`base.py` (`ServiceBase`), `registry.py` (`RegistryService`, a generic base for lookup tables —
not an aggregate owner itself), `unit_of_work.py`, `errors.py`, `normalization.py`, `prompts.py`,
`quality.py`, `media_storage.py`, `source_media.py`. `__init__.py` exposes `ServiceBundle`.
- Cross-cutting error behavior must follow - Cross-cutting error behavior must follow
[error-handling instructions](./error-handling.instructions.md). [error-handling instructions](./error-handling.instructions.md).
@@ -30,11 +38,37 @@ is the only service that may **create or delete** its rows.
| Model | Owner | | Model | Owner |
| --- | --- | | --- | --- |
| `Document`, `DocumentType` | `DocumentService` | | `Document`, `DocumentType`, `DocumentTag` | `DocumentService` |
| `Source`, `JobSource` | `SourceService` | | `Source`, `JobSource` | `SourceService` |
| `Job` | `JobService` | | `Job` | `JobService` |
| `Person`, `PersonRole`, `DocumentPerson` | `PeopleService` | | `Person`, `PersonRole`, `DocumentPerson`, `PersonTag` | `PeopleService` |
| `Photo` | `PhotosService` |
| `MaintenanceRun` | `MaintenanceService` |
| `ExecutionAttempt` | `SourceService` | | `ExecutionAttempt` | `SourceService` |
| `Tag` | shared — see below |
Keep this table complete: every table in `src/transcription/db/models.py` appears exactly once,
except `Tag`. When you add a model, add its owner here in the same change.
### `Tag` is deliberately shared
`Tag` is one table reached through two `RegistryService[Tag]` facades that differ only in the
reference model they count usage through: `TagRegistry` (`documents.py`, via `DocumentTag`) and
`PersonTagRegistry` (`people.py`, via `PersonTag`). Both create and delete `Tag` rows through the
generic registry. This is the single sanctioned exception to one-owner-per-model — do not "fix" it by
assigning `Tag` to one service, because the other facade would then be creating rows it does not own.
Any change to `Tag` semantics, labels, or normalization must be validated against **both** facades
and the junction table each one counts.
`DocumentTag` and `PersonTag` follow the junction rule below: each is created and deleted only by the
service on its own side.
### Registries
`DocumentTypeRegistry`, `TagRegistry`, `PersonRoleRegistry`, and `PersonTagRegistry` are
`RegistryService` subclasses, not independent services. A registry belongs to the aggregate service
whose module declares it and shares that service's ownership. Registry CRUD uses `<operation>_entry`
naming (see [CRUD Methods](#crud-methods)).
### Junction tables ### Junction tables
@@ -50,8 +84,14 @@ lifecycle owner. The service on the other side may read through the junction (vi
Two consequences follow, and both are deliberate: Two consequences follow, and both are deliberate:
- **Cascade deletion is not a violation.** A service deleting the aggregate root it owns - **Cascade deletion is not a violation.** A service deleting the aggregate root it owns
may delete junction rows referencing that root, because they cannot outlive it may delete rows referencing that root which cannot outlive it
(`JobService.delete_job_with_guardrails`). (`JobService.delete_job_with_guardrails` deletes the job's `job_source` rows).
- **Evidence deletion is an explicit workflow, not a runtime path.** `JobService.delete_job_and_evidence`
deletes `ExecutionAttempt` rows owned by `SourceService`. That is sanctioned because it is the
named retention workflow that `delete_job_with_guardrails` refuses to perform implicitly — that
method *blocks* deletion when attempts exist. Append-only means runtime code never rewrites or
removes history to represent a new outcome; it does not forbid a deliberate, operator-invoked
retention operation. Do not add a second path that deletes attempts.
- **Ownership governs creation and deletion, not every state transition.** `job_source` is - **Ownership governs creation and deletion, not every state transition.** `job_source` is
both a link and the transcription work queue. `JobService.cancel_job` and both a link and the transcription work queue. `JobService.cancel_job` and
`resubmit_failed_sources` transition `job_source.status` across a whole job, because that `resubmit_failed_sources` transition `job_source.status` across a whole job, because that
@@ -9,7 +9,7 @@ agent: Python Architect Reviewer
Execute a comprehensive, evidence-based code review of the target codebase. Execute a comprehensive, evidence-based code review of the target codebase.
## Target Scope ## Target Scope
- **Review Target:** ${{input:target_path:./}} - **Review Target:** the repository root, unless the invoker names a narrower path; review that path instead.
- **Source Root:** `src/` - **Source Root:** `src/`
- **Docs Root:** `docs/` - **Docs Root:** `docs/`
- **Focus Areas:** FastAPI endpoints, NiceGUI components, SQLModel persistence, asyncio workers, Pydantic V2 models, and OpenRouter provider adapters. - **Focus Areas:** FastAPI endpoints, NiceGUI components, SQLModel persistence, asyncio workers, Pydantic V2 models, and OpenRouter provider adapters.
@@ -17,7 +17,7 @@ Execute a comprehensive, evidence-based code review of the target codebase.
## Execution Rules ## Execution Rules
1. Map repository layout, dependency manifests, and configuration files from the project root before inspecting modules. 1. Map repository layout, dependency manifests, and configuration files from the project root before inspecting modules.
2. Read real code modules under `src/` (or the specified target path); cite exact file paths and line ranges for every finding. 2. Read real code modules under `src/` (or the specified target path); cite exact file paths and line ranges for every finding.
3. Validate issues using terminal tools (`ruff check`, `pytest`, `ty`) where appropriate. 3. Validate issues by running `uv run ruff check .`, `uv run ty check`, and `uv run pytest -q -m "not external"`. Nothing is on `PATH` in this `uv` project, so bare `ruff`/`ty`/`pytest` will fail.
4. Check for duplication, divergent implementations, and extractable helpers. 4. Check for duplication, divergent implementations, and extractable helpers.
5. Format the entire review following the standardized 6-section template defined in the `python-code-reviewer` skill. 5. Format the entire review following the standardized 10-section template defined in the `python-code-reviewer` skill.
6. Write the final report as a Markdown file to `./docs/code-review-${{current_date}}.md`. 6. Write the final report to `./docs/reviews/<YYYY-MM-DD>-code-review.md`, using today's date. This path is defined by the skill; do not write the report anywhere else.
+1 -1
View File
@@ -31,7 +31,7 @@ upgrade policy") — do not report it as a defect or recommend widening it.
## Review Workflow ## Review Workflow
1. **Map the Repository First:** Inspect entry points, package layout, configurations, dependency manifests, and any project-specific rule files (`AGENTS.md`, `CLAUDE.md`, `.github/instructions/`). Project-specific conventions override generic advice. 1. **Map the Repository First:** Inspect entry points, package layout, configurations, dependency manifests, and any project-specific rule files (`AGENTS.md`, `.github/instructions/`, `.github/skills/`). Project-specific conventions override generic advice.
2. **Establish Canonical Authority First:** Read architecture/contracts (`docs/*`, `docs/invariant/*`, UI docs) and active instructions/skills before evaluating source behavior. 2. **Establish Canonical Authority First:** Read architecture/contracts (`docs/*`, `docs/invariant/*`, UI docs) and active instructions/skills before evaluating source behavior.
3. **Read Representative Modules:** Sample across all layers (routes/pages, UI components, services, workers, persistence, provider adapters, settings, tests) before drawing conclusions. 3. **Read Representative Modules:** Sample across all layers (routes/pages, UI components, services, workers, persistence, provider adapters, settings, tests) before drawing conclusions.
4. **Run Drift Analysis:** Compare documented intended behavior versus repository ground truth; identify both implementation drift and undocumented-but-repeatable conventions that should be formalized. 4. **Run Drift Analysis:** Compare documented intended behavior versus repository ground truth; identify both implementation drift and undocumented-but-repeatable conventions that should be formalized.
@@ -64,7 +64,9 @@ Run a deterministic audit of test usefulness. Focus on whether tests catch real
## Output Format ## Output Format
Produce a Markdown report in `docs/`: Produce a Markdown report at `docs/reviews/<YYYY-MM-DD>-test-effectiveness.md`, using today's date.
Like code review reports, it is a dated, non-canonical artifact: `docs/reviews/**` is not part of the
canonical authority set.
```markdown ```markdown
# Test Effectiveness Audit Report # Test Effectiveness Audit Report
+6
View File
@@ -89,6 +89,12 @@ Non-obvious things that have already caused real bugs here:
- **Shared symbols have more consumers than the obvious one.** Before changing a model field, - **Shared symbols have more consumers than the obvious one.** Before changing a model field,
exception attribute, or helper return value, grep for every consumer including tests. exception attribute, or helper return value, grep for every consumer including tests.
Evidence and logging paths frequently read the same fields the UI does. Evidence and logging paths frequently read the same fields the UI does.
- **`Tag` is owned by two services, on purpose.** Every other model has exactly one owning
service, so the ownership rule reads as absolute — it isn't. `Tag` is a single table reached
through two `RegistryService[Tag]` facades, `TagRegistry` (documents) and `PersonTagRegistry`
(people), which count usage through `DocumentTag` and `PersonTag` respectively. Changing tag
semantics through one facade silently changes the other. Consolidating them under one service
is not a cleanup; it makes the other side a cross-aggregate writer.
- **Import style:** ruff `isort` runs with `force-single-line = true`. One import per line. - **Import style:** ruff `isort` runs with `force-single-line = true`. One import per line.
- **Latent defects have ordering constraints.** Some code is unreachable only because of a - **Latent defects have ordering constraints.** Some code is unreachable only because of a
current setting or single-instance deployment. Fix it *before* the change that unblocks it, current setting or single-instance deployment. Fix it *before* the change that unblocks it,