From 6c64cd722e9bd58c063c97d56bb2836ff301cf49 Mon Sep 17 00:00:00 2001 From: Kennet Dahl Kusk Date: Sun, 17 May 2026 22:02:38 +0200 Subject: [PATCH 1/5] v5.0.0: rewrite doctrine + commands for TS deep-modules workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace v4's Rust Clean Architecture (4-crate workspace, layer-enforced dep table) with TypeScript deep modules — small interfaces with a lot of behaviour behind them, no fixed taxonomy. Vocabulary (module / interface / seam / adapter / depth / leverage / locality) is standard software-engineering terminology from Ousterhout's A Philosophy of Software Design (deep modules) and Feathers' Working Effectively with Legacy Code (seams). Doctrine (docs/): - architecture.md — deep modules, dependency categories (in-process / local-substitutable / remote-but-owned / true-external), seam discipline. Composition root in src/main.ts. - anti-slop.md — 4 elements (shallow modules, duplication, defensive over-programming, drift), 5 categories, 8 hard rules. Deletion test is the controlling rule for new extractions. - testing.md — interface-as-test-surface; module-interface / HTTP-seam / e2e patterns with bun test; mirror-test ban. Commands rewritten lean (declarative prose, no procedural ceremony): - start.md — Bun + Hono + Drizzle scaffold; Postgres + web are manual add-ons rather than flags (decisions stay explicit). - fix.md — single-bug intake; Task Brief carries metadata.module (free-form lowercase) instead of v4's enforced layer enum. - plan.md — refined brief → PRD on disk → vertical-slice tasks. - ship.md — parallel worktree agents + audit (biome / tsc / bun audit --audit-level=high / bun test) + 1 auto-retry. - review.md — pre-merge gate; delegates to engineering plugin's code-review skill when installed. - install-ci.md — drop the audit workflow into an existing TS repo. Co-Authored-By: Claude Opus 4.7 (1M context) --- code-et-implementer/commands/fix.md | 138 ++++------- code-et-implementer/commands/install-ci.md | 45 +++- code-et-implementer/commands/plan.md | 134 +++++------ code-et-implementer/commands/review.md | 33 ++- code-et-implementer/commands/ship.md | 221 +++++++++-------- code-et-implementer/commands/start.md | 120 +++++----- code-et-implementer/docs/anti-slop.md | 111 ++++----- code-et-implementer/docs/architecture.md | 259 +++++++++++--------- code-et-implementer/docs/testing.md | 263 ++++++++++----------- 9 files changed, 641 insertions(+), 683 deletions(-) diff --git a/code-et-implementer/commands/fix.md b/code-et-implementer/commands/fix.md index 4eaa474..557eb83 100644 --- a/code-et-implementer/commands/fix.md +++ b/code-et-implementer/commands/fix.md @@ -1,100 +1,48 @@ --- -tools: Read, Grep, Glob, Bash, Agent, LSP -description: "Single-bug intake — scope work into a Task Brief. You implement directly. Generates/updates FILE-REFERENCE.md." -argument-hint: "[bug description] or 'update' to refresh FILE-REFERENCE.md" +tools: Read, Grep, Glob, Bash, Agent +description: "Single-bug intake — scope work into a Task Brief. You implement directly." +argument-hint: "[bug description]" effort: high --- -# Fix — Single-Bug Intake +# Fix — Single-bug intake -You are an intake assistant. Your job is to scope **one bug fix** into a precise Task Brief — exact app, exact files (with `Layer`), nothing more. The user takes the brief and implements directly. +You are an intake assistant. Your job is to scope **one bug fix** into a precise Task Brief — exact module, exact files, observable success criterion. The user takes the brief and implements directly. -This is a pure-Rust workflow: every project follows the four-crate Clean Architecture (`domain` | `application` | `infrastructure` | `interface`) from `code-et-implementer/docs/architecture.md`. Each touched file declares its layer. +This is a TypeScript workflow built on deep modules (no fixed layer taxonomy). The Task Brief names the **module** each touched file belongs to. -**Scope guard.** `/code:fix` is for single, contained bug fixes (1-3 file edits). If the work spans multiple coherent vertical slices (UI + logic + API + DB layered for a real feature), stop and route to `/code:plan`. Do not auto-chain. +**Scope guard.** `/code:fix` is for single, contained bug fixes (1–3 file edits). If the work spans multiple coherent vertical slices (HTTP route + module + DB migration for a real feature), stop and route to `/code:plan`. Do not auto-chain. -## Step 0 — Ensure FILE-REFERENCE.md exists +## Step 1 — Read the orientation -Check `FILE-REFERENCE.md` at the project root. It holds **non-derivable knowledge only** — apps overview, hot paths, landmines, module invariants, schema purposes, domain rules. File inventories (routes, components, screens) are reachable via `Glob` on demand; do not enumerate. +Read these once if present (they're cheap and constrain the fix): -**If missing**, or if `$ARGUMENTS` contains "update": +- `CONTEXT.md` at repo root — domain glossary. Use its vocabulary in the brief. +- `FILE-REFERENCE.md` at repo root — non-derivable knowledge (hot paths, landmines, module invariants). Skip if missing. +- `docs/adr/` — ADRs in the area you're touching. Respect decisions; flag if the bug suggests revisiting one. -1. Scan only the non-derivable parts: - - **Apps Overview** (≤5 lines): one-line purpose per app under `apps/*` from its `CLAUDE.md` or `README`. - - **Hot Paths**: entry points — files that run on every primary user action vs once at startup. 3-5 max per bucket. - - **Landmines**: top-level `CLAUDE.md` and per-directory `CLAUDE.md` for "never"/"do not" rules; Grep for `@deprecated`, `// DO NOT USE`, `// LEGACY`. One row per rule + reason. - - **Module Invariants**: top-of-file docstrings for non-obvious constraints (per top-level module/crate). - - **Database Schema** (if applicable): `migrations/**` — one row per table, purposes only (names are derivable). - - **Domain Rules / DSL** (optional): ≤10-line summary, link to source. - -2. Build `FILE-REFERENCE.md` with the structure below. **Skip any section with no content.** - -```markdown -# FILE-REFERENCE.md - -Non-derivable project knowledge for `/code:fix` intake and `/code:plan` context. -**File inventories live in the filesystem.** Glob for routes, components, schemas on demand. - -## Apps Overview - -| App | Purpose | Root path | -|-----|---------|-----------| - -## Hot Paths - -| Path type | Files | -|-----------|-------| -| Per-request | … | -| Startup-only | … | - -## Landmines - -| Rule | Why | -|------|-----| - -## Module Invariants - -| Module | Invariant | -|--------|-----------| - - - -## Database Schema - -| Table | Purpose | Key relations | -|-------|---------|---------------| - -## Domain Rules / Grammar - -≤10 lines. Link to source. -``` - -3. Write the file, tell the user: *"Created FILE-REFERENCE.md — review and let me know if anything is missing."* On `update`, preserve hand-edited sections; refresh only re-derivable parts. -4. If this was an `update` request, stop. Otherwise continue to Step 1. - -**If it exists**, read it and continue. - -## Step 1 — Read the reference - -Read `FILE-REFERENCE.md`. +Do not enumerate file inventories — Glob/Grep on demand. ## Step 2 — Understand the request Identify: -- **Bug class**: visual regression, broken behaviour, API error, data inconsistency, perf issue, etc. -- **Which app**: pick from Apps Overview (don't guess). -- **Which area**: handler, component, repo, use case. -If the request describes a multi-slice feature, stop and route to `/code:plan`. +- **Bug class:** broken behaviour, perf regression, data inconsistency, type error, visual regression. +- **Module:** which `src/modules//` (or `src/http/routes//`) owns the affected behaviour. +- **Trigger:** the call site or HTTP route that reproduces it. + +If the request actually describes a multi-slice feature, stop and route to `/code:plan`. ## Step 3 — Ask clarifying questions -Numbered list, max 3-4 questions: +Numbered list, max 3–4 questions. Each with a recommended answer and 1-sentence rationale. + +Typical questions: -1. **Which app?** (only if ambiguous — use Apps Overview) -2. **Which file/area?** Once the app is picked, `Glob 'crates/*/src/**/*.rs'` or `Glob 'apps//**/*.rs'` to surface concrete options. -3. **What exactly should change?** (behavior, visual, data, API) -4. **Any related areas that might be affected?** +1. **Which module?** (only if ambiguous — Glob `src/modules/*/index.ts` to list options.) +2. **What exactly should change?** (the observable behaviour: status code, returned shape, side-effect.) +3. **Trigger to reproduce?** (HTTP route + body, or a test fixture.) +4. **Any related modules that might be affected?** (Optional.) Skip whatever the user already answered. @@ -103,33 +51,33 @@ Skip whatever the user already answered. ``` ## Task Brief -**Type:** [bug fix / styling / refactor / API change] -**App:** [app name from Apps Overview] -**Area:** [crate or module] -**Description:** [1-2 sentence summary] +**Type:** [bug fix | type fix | perf | refactor] +**Module:** [src/modules/] (or [src/http/routes/]) +**Description:** [1–2 sentence summary in CONTEXT.md vocabulary] **Goal:** [observable success criterion — what's true after the fix that wasn't before] -**Verification:** `` — [expected outcome on green; for visual fixes: manual repro steps] +**Verification:** `` — [expected outcome] ### Files to touch -| File | Layer | Why | -|------|-------|-----| -| `crates//src/...rs` | domain/application/infrastructure/interface | reason | +| File | Module | Why | +|---|---|---| +| `src/modules//...ts` | | reason ≤6 words | ### Related files (check for impact) -| File | Layer | Why | -|------|-------|-----| +| File | Module | Why | +|---|---|---| ``` -The `Layer` column is mandatory. Use file paths discovered via `Glob`/`Grep`. Reference FILE-REFERENCE for app names, hot paths, landmines that touch the affected files. +Rules for the Task Brief: -**LSP precision (only if user named a symbol):** if the request references a specific function/type/component by name, use `LSP definition`/`references` once to pin `file:line`. Skip otherwise — Glob paths are enough. Never bulk-scan with LSP. +- **Module column** is mandatory — every touched file belongs to exactly one module. +- File paths come from `Glob`/`Grep`, not guesses. +- `Description` ≤ 2 sentences. "Why" cells ≤ 6 words. +- **Goal + Verification are non-negotiable.** Goal = one observable sentence. Verification = the command that proves it (`bun test src/modules/`, `bun run typecheck`, or a curl with expected output). If you can't state Verification, the bug isn't scoped tightly enough — ask another question. ## Rules - Concise — don't dump the reference back at the user. -- ≤4 questions, not a wall. -- If the user already gave enough context, skip straight to the Task Brief. -- Reference concrete paths (from Glob) so the user can point and say "that one". -- Description ≤2 sentences using fragments. File "Why" column ≤6 words. No hedging or filler. -- **Goal + Verification are mandatory.** Goal is the testable outcome (one sentence, observable). Verification is the cmd that proves it (`cargo nextest run -p ` for unit, `cargo clippy --all-targets -- -D warnings` for lint regressions, manual repro steps for visual/UI). If you can't state Verification, the bug isn't scoped tightly enough — ask another clarifying question. -- **Context budget**: FILE-REFERENCE = constraints + orientation. Glob/Grep = file discovery. LSP = scalpel for named symbols. Never read whole files in `/code:fix` — that's `/code:plan`'s job. +- ≤ 4 questions, not a wall. +- If the user already gave enough context, jump to the Task Brief. +- Use `Glob` for discovery, `Grep` for symbols, `Read(offset, limit)` for slices. Never read whole files in `/code:fix` — that's `/code:plan`'s job. +- **Deletion test reminder.** If the fix is "extract X into a helper", apply the deletion test: would deleting the extracted helper make complexity vanish or reappear across callers? If vanish, don't extract — inline. diff --git a/code-et-implementer/commands/install-ci.md b/code-et-implementer/commands/install-ci.md index ed55623..b8ae09e 100644 --- a/code-et-implementer/commands/install-ci.md +++ b/code-et-implementer/commands/install-ci.md @@ -1,36 +1,59 @@ --- tools: Read, Bash, Glob -description: "Copy code-et's CI audit workflow + layer-deps validator into an existing Rust repo." +description: "Copy code-et's CI audit workflow into an existing TypeScript / Bun repo." argument-hint: "[--force]" -effort: high +effort: medium --- -Install code-et's CI audit gate (`.github/workflows/code-et-audit.yml` + `scripts/layer-deps-validator.sh`) into the current Rust project. Idempotent — re-running overwrites only with `--force`. +# Install CI — Drop the audit workflow into an existing repo + +Adds `.github/workflows/code-et-audit.yml` and an `audit` script entry (if `package.json` is missing one). Idempotent — re-running overwrites only with `--force`. + +The audit runs `biome check`, `tsc --noEmit`, `bun audit`, `bun test`. See [`docs/anti-slop.md`](../docs/anti-slop.md) §"4-stage verification loop". ## Procedure 1. **Pre-flight.** + ``` - Bash('test -f Cargo.toml && echo OK || echo "Not a Rust project (no Cargo.toml)"') + Bash('test -f package.json && echo OK || echo "Not a TS/Node project (no package.json)"') ``` - If not Rust, stop. -2. **Detect existing CI.** + If not a TS project, stop with: *"This is for TypeScript / Bun projects. For a Rust project, install code-et v4.x."* + +2. **Detect existing workflow.** + ``` Bash('test -f .github/workflows/code-et-audit.yml && echo EXISTS || echo MISSING') ``` + If `EXISTS` and not `--force`, stop with: *"`.github/workflows/code-et-audit.yml` already present. Re-run with `--force` to overwrite."* -3. **Copy workflow + validator.** +3. **Copy workflow.** + + ``` + Bash('mkdir -p .github/workflows && cp "${CLAUDE_PLUGIN_ROOT}/templates/shared/.github/workflows/code-et-audit.yml" .github/workflows/') + ``` + +4. **Add an `audit` npm script** if `package.json` doesn't have one. Read `package.json`, add to `scripts`: + + ```json + "audit": "biome check . && tsc --noEmit && bun audit --audit-level=high && bun test" + ``` + + Skip if the project already defines `audit` differently — print a note instead. + +5. **Recommend doctrine adoption.** Print: + ``` - Bash('mkdir -p .github/workflows scripts && cp "${CLAUDE_PLUGIN_ROOT}/templates/shared/.github/workflows/code-et-audit.yml" .github/workflows/ && cp "${CLAUDE_PLUGIN_ROOT}/templates/shared/scripts/layer-deps-validator.sh" scripts/ && chmod +x scripts/layer-deps-validator.sh') + The audit assumes deep-modules architecture (no fixed layers). + See ${CLAUDE_PLUGIN_ROOT}/docs/architecture.md for the vocabulary. ``` -4. **Recommend doctrine adoption.** Print a one-line note pointing the user at `code-et-implementer/docs/architecture.md` if their project doesn't yet have a 4-crate workspace. The validator is a no-op on projects without `crates//` dirs — exits 0 with `"layer-deps-validator: clean"`. +6. **Recommend dev-dep installs** if missing: -5. **Recommend tool installs.** Print: ``` - cargo install --locked cargo-machete cargo-audit cargo-deny cargo-nextest + bun add -d @biomejs/biome typescript ``` ## Output diff --git a/code-et-implementer/commands/plan.md b/code-et-implementer/commands/plan.md index 9a420a1..8ab2a77 100644 --- a/code-et-implementer/commands/plan.md +++ b/code-et-implementer/commands/plan.md @@ -1,40 +1,43 @@ --- -tools: Read, Write, Edit, Grep, Glob, Bash, LSP, Agent, TaskCreate, TaskUpdate, TaskList, TaskGet, AskUserQuestion -description: "Refine an idea, write a PRD, and decompose into vertical-slice tasks. One extended turn — three checkpoints (brief, PRD on disk, tasks)." +tools: Read, Write, Edit, Grep, Glob, Bash, Agent, TaskCreate, TaskUpdate, TaskList, TaskGet, AskUserQuestion +description: "Refine an idea, write a PRD, decompose into vertical-slice tasks. One extended turn — three checkpoints (brief, PRD on disk, tasks)." argument-hint: "[rough idea | @path/to/brief.md]" effort: xhigh --- # Plan — Idea → PRD → Tasks -A single command that walks the feature lane end-to-end. Three checkpoints — at each, the artifact lands on disk before the next phase starts so you can interrupt, edit, and resume. +One command, three checkpoints. At each, the artifact lands on disk before the next phase starts — you can interrupt, edit, and resume. ``` -Phase 1: Refined Brief → print to chat -Phase 2: PRD → write plans/YYYY-MM-DD-.md, ask "continue?" -Phase 3: Tasks → TaskCreate with metadata.layer + rationale +Phase 1: Refined Brief → printed to chat +Phase 2: PRD → written to plans/YYYY-MM-DD-.md, ask "continue?" +Phase 3: Vertical-slice tasks → TaskCreate with metadata.module + rationale ``` -Pure-Rust four-crate Clean Architecture from `code-et-implementer/docs/architecture.md`. Each task carries `metadata.layer ∈ {domain, application, infrastructure, interface, chore}`. The Dependency Rule is enforced at the `Cargo.toml` level — flag layer-violating import directions at planning time, not at `cargo build`. +Architecture vocabulary: **module / interface / seam / adapter** (see [`docs/architecture.md`](../docs/architecture.md)). Each task carries `metadata.module` — the deep module the slice mostly lives in. No fixed layer taxonomy; modules grow around interfaces. ## Phase 1 — Refined Brief (interrogation) -Goal: every open decision resolved before any document is written. Cost: ~10 questions max, almost always fewer when the codebase has the answer. +Resolve every open decision *before* writing anything to disk. Cost: ~10 questions max, usually fewer when the codebase has the answer. **Rules:** -1. **If a question can be answered from the codebase, answer it yourself.** Run `Read`/`Grep`/`Glob`/`git log`. Do not ask the user. +1. **If a question can be answered from the codebase, answer it yourself.** Run `Read`/`Grep`/`Glob`/`git log`. Don't ask the user. 2. **One question per message.** Number it. Include a recommended answer + 1-sentence rationale. -3. **Accept "you decide"** — record the recommendation as the decision; do not re-ask. +3. **Accept "you decide"** — record the recommendation as the decision; don't re-ask. 4. **Accept "defer: "** — mark deferred, move on. 5. **Stop rule:** when every ledger item is `answered | recommended-accepted | deferred`, print the refined brief and proceed to Phase 2. -**Decisions ledger** (in-session). Each entry: `id` (D-1, D-2, …), `question`, `recommendation`, `state`, `final_answer`. Show ledger summary every 3 answered items. +Show ledger summary every 3 answered items. **Process:** + - Parse `$ARGUMENTS`. If `@path`, read the file. Otherwise treat as the rough idea. -- Glob `plans/**/*.md` for prior plans, `git log --oneline -20` for recent work. -- Seed 5-10 open decisions across: scope, actors, data model, UI surface, integration points, non-goals, success criteria. +- Glob `plans/**/*.md` for prior plans; `git log --oneline -20` for recent work. +- Read `CONTEXT.md` (domain glossary) if present — use its vocabulary throughout. +- Read any `docs/adr/*.md` near the area being touched — respect ADR decisions; flag a conflict only when the friction is real enough to warrant revisiting. +- Seed 5–10 open decisions across: scope, actors, data model, the seam this lives on, dependencies (in-process / local-substitutable / remote-but-owned / true-external — see `docs/architecture.md`), non-goals, success criteria. - Interrogate. Resolve from the codebase first. **Output of Phase 1** (printed to chat): @@ -42,7 +45,7 @@ Goal: every open decision resolved before any document is written. Cost: ~10 que ``` ## Refined Brief -**Idea:** <1-sentence> +**Idea:** <1 sentence> **Actors:** **Scope:** **Key decisions:** @@ -52,16 +55,17 @@ Goal: every open decision resolved before any document is written. Cost: ~10 que **Suggested slug:** ``` -Pause. Confirm the slug with the user (one `AskUserQuestion`) unless `$ARGUMENTS` already supplied one. +Confirm the slug with one `AskUserQuestion` unless `$ARGUMENTS` already supplied one. ## Phase 2 — PRD on disk 1. **Branch.** If on `main` or `master`, create `feature/`: + ``` Bash('branch="$(git rev-parse --abbrev-ref HEAD)" && [ "$branch" = "main" ] || [ "$branch" = "master" ] && git checkout -b "feature/"') ``` -2. **Write** `plans/$(date +%Y-%m-%d)-.md` using this template — replace every `<…>` placeholder; do **not** leave TBDs: +2. **Write** `plans/$(date +%Y-%m-%d)-.md`. Replace every `<…>`; no TBDs. ```markdown # @@ -72,11 +76,11 @@ Pause. Confirm the slug with the user (one `AskUserQuestion`) unless `$ARGUMENTS ## Problem Statement - + ## Solution - + ## User Stories @@ -92,13 +96,21 @@ Pause. Confirm the slug with the user (one `AskUserQuestion`) unless `$ARGUMENTS ### US-2 - AC-2.1: … +## Modules + +A list of the deep modules involved. For each new or modified module: + +- **** — interface shape (top-level exports), what sits behind the seam, dependency category (in-process / local-substitutable / remote-but-owned / true-external). + +Apply the deletion test before proposing any new module: would deleting it concentrate complexity, or just move it? + ## Implementation Decisions - +Module-level. No file paths. No code snippets. Cover: schema changes, ports + adapters, integration points, error modes at seams. Exception: a snippet that encodes a decision more precisely than prose can (state machine, reducer, schema, type shape) is allowed — trim to the decision-rich parts. ## Testing Decisions - +Per the test matrix in [`docs/testing.md`](../../code-et-implementer/docs/testing.md): module-interface / HTTP-seam / e2e. Reference similar tests in the codebase by module name. External behaviour only. ## Out of Scope @@ -111,9 +123,7 @@ Pause. Confirm the slug with the user (one `AskUserQuestion`) unless `$ARGUMENTS … ``` -3. **Set session title** by emitting `{"sessionTitle": "feat:"}` to stdout (Claude Code's `UserPromptSubmit` JSON channel). - -4. **Pause and ask** the user via `AskUserQuestion`: *"PRD written to ``. Continue to task decomposition, or pause to edit?"* Choices: `Continue | Pause`. On `Pause`, print *"Resume with `/code:plan` once edits are saved."* and stop. +3. **Pause and ask** via `AskUserQuestion`: *"PRD written to ``. Continue to task decomposition, or pause to edit?"* Choices: `Continue | Pause`. On `Pause`: *"Resume with `/code:plan` once edits are saved."* and stop. ## Phase 3 — Vertical-slice task decomposition @@ -121,35 +131,30 @@ Read the PRD (it is the authoritative spec). ### Decomposition rules -**Vertical slicing — non-negotiable.** Each task implements **one full vertical slice** UI ↔ logic ↔ API ↔ DB, end-to-end and testable. A task that touches only one layer is wrong — split or merge. +**Vertical slicing — non-negotiable.** Each task implements **one full vertical slice**: HTTP seam → module → DB (or whatever the slice's path actually traverses), end-to-end and testable. A task that touches only one shallow concern is wrong — split or merge. - ✗ "Add API endpoint" + "Wire UI button" + "Migrate schema" — three half-tasks -- ✓ "Submit-feedback flow: form → API → `feedback` row → email confirmation" — one slice +- ✓ "Submit-feedback flow: form → POST /feedback → `feedback` row → email confirmation" — one slice `metadata.verification` exercises the full slice end-to-end. **Replace, don't accumulate.** When a slice supersedes existing logic, the task scope **includes deletion of the superseded code**. State the `path:line` being replaced in `metadata.rationale`. No parallel utilities, no `// TODO: remove old X`. -**LSP for symbols.** Use `documentSymbol` / `findReferences` / `definition` to resolve each US/AC to a `{path, symbol, line, op}` entry — persist the result in `metadata.files[]` (schema below). Do not throw away the symbol name; that's the contract the subagent edits against if `line` drifts. Grep/Glob for discovery; LSP for precision. Never use LSP to enumerate the project. For 3+ independent areas, spawn parallel `Agent(subagent_type: "Explore", model: "haiku")` queries in a single message — Haiku 4.5 is the right tier for breadth scans. - -**Path validation.** Before `TaskCreate`, validate every `files[].path`: -- `op ∈ {modify, replace, delete}` → path must appear in `git ls-files`. If not, the symbol moved or was deleted — re-resolve via LSP or drop the entry. -- `op = add` → path must either appear in `git ls-files` (append to existing file) or, if `FILE-REFERENCE.md` exists at repo root, sit under a documented top-level area there. Reject paths under undocumented top-level directories when `FILE-REFERENCE.md` is present; otherwise accept any path the workspace `Cargo.toml` covers. - -Path drift caught at plan time is one less wasted subagent dispatch. +**Module ownership.** Each task names the **primary module** the slice mostly lives in (`metadata.module`). Slices typically touch the HTTP seam + 1–2 modules; `metadata.files[]` carries per-file paths. ### Anti-slop self-critique (before TaskCreate) After drafting tasks, walk the list once and **reject** any task that: -- Touches only one layer → split or merge into a vertical slice. -- Has rationale "because the PRD says so" → restate the underlying constraint. +- Touches only one shallow concern → split or merge into a vertical slice. +- Rationale "because the PRD says so" → restate the underlying constraint. - Adds a duplicate utility instead of extracting (Rule of Three) → the third occurrence triggers refactor in the same task. -- Adds defensive validation between trusted modules (interface↔application is the only validation boundary) → drop. -- Adds a mirror test (`assert_eq!(format!("{:?}", err), "DomainError::NotFound")` style) → reframe to assert observable behaviour. +- Adds defensive validation between trusted modules (HTTP-seam is the only validation boundary) → drop. +- Adds a mirror test (assert on internal call shapes / `toBeCalledWith` on a stand-in) → reframe to assert observable behaviour. - Has no test for at least one acceptance criterion → fix. +- Proposes a new extracted helper without applying the deletion test → re-evaluate. -The list lives in `code-et-implementer/docs/anti-slop.md`; the inline summary above is enough for plan-time review. +The full list lives in [`docs/anti-slop.md`](../docs/anti-slop.md); the summary above is enough for plan-time review. ### TaskCreate metadata @@ -157,52 +162,37 @@ The list lives in `code-et-implementer/docs/anti-slop.md`; the inline summary ab { "verification": "", "files": [ - {"path": "crates//src/path.rs", "symbol": "Type::method", "line": 42, "op": "modify"}, - {"path": "crates//src/new_file.rs", "symbol": "NewType", "op": "add"}, - {"path": "crates//src/legacy.rs", "symbol": "deprecated_fn", "line": 89, "op": "delete"} + {"path": "src/modules//index.ts", "symbol": "Orders.place", "line": 42, "op": "modify"}, + {"path": "src/http/routes/orders.ts", "symbol": "placeOrder", "op": "add"}, + {"path": "src/modules//legacy.ts", "symbol": "oldPlace", "line": 89, "op": "delete"} ], "expected_outcome": "", - "rationale": "<1-2 sentences: why this slice exists, the constraint driving it.>", + "rationale": "<1–2 sentences: why this slice exists, the constraint driving it>", "user_story": "US-1", - "layer": "interface" + "module": "orders" } ``` -**`user_story` — pick exactly one tag, no concatenation.** Allowed forms: +**`user_story` — pick exactly one tag.** Allowed forms: -- `US-` — the primary user story this slice delivers (e.g. `"US-1"`). -- `AC-.` — a single acceptance criterion when the slice is narrower than a full story (e.g. `"AC-1.2"`). -- `chore:` — non-PRD work (e.g. `"chore:bump-deps"`). +- `US-` — the primary user story this slice delivers. +- `AC-.` — a single acceptance criterion when the slice is narrower than a full story. +- `chore:` — non-PRD work. -Do **not** emit values like `"US-1 | AC-1.1, AC-1.2"` or `"US-1, US-2"`. The pipes/commas in this doc are reading aids, not value separators — the `PreToolUse(TaskCreate)` hook regex matches a single tag and rejects anything else (see `scripts/task-created-tag-check.sh`). If one slice satisfies multiple ACs of the same story, tag it with the story (`"US-"`); the ACs it covers belong in `expected_outcome` / tests, not the tag. +Do **not** emit alternation values like `"US-1 | AC-1.1, AC-1.2"`. The pipes/commas in this doc are reading aids, not value separators. -**`layer` — pick exactly one.** Allowed values: `"domain"`, `"application"`, `"infrastructure"`, `"interface"`, `"chore"`. A vertical slice may touch multiple layers via `files[]`, but the task's *primary* layer (the one this tag names) is the innermost layer the slice modifies. +**`module` — free-form, lowercase.** The primary module name (e.g. `orders`, `payments`, `auth`). For chores, `module: "chore"`. There is no enforced taxonomy — pick the module the slice mostly lives in and name it the same way it's named in `src/modules/`. **`files[]` entry shape:** | Field | Required | Notes | |---|---|---| -| `path` | always | Workspace-relative. Validated against `git ls-files` (`modify\|replace\|delete`) or `FILE-REFERENCE.md` modules (`add`). | +| `path` | always | Workspace-relative. | | `op` | always | `add` (create symbol), `modify` (edit body), `replace` (full rewrite — pair with sibling `delete` if cross-file supersession), `delete` (remove symbol). | -| `symbol` | for `modify\|replace\|delete`; recommended for `add` | Qualified Rust path: `User::validate`, `db::pool`, `routes::auth::login`. Resolved via LSP `documentSymbol`. | -| `line` | optional hint | Current line at plan time. Implementer re-resolves via LSP if it drifts. Omit for `add` on a new file. | +| `symbol` | for `modify\|replace\|delete`; recommended for `add` | Qualified TS path: `Orders.place`, `placeOrder`, `module#export`. | +| `line` | optional hint | Current line at plan time; drift-tolerant — `symbol` is the contract. | -`rationale` is mandatory — the subagent starts cold and needs the *why*. `layer` is mandatory; the per-file layer also feeds the validator on every file the task touches. Deletion of superseded code is encoded as explicit `op: "delete"` entries in `files[]`, not prose in `rationale`. `verification` exercises the full slice — `cargo nextest run -p ` for unit, `cargo nextest run --workspace` for cross-layer. - -Set dependencies with `TaskUpdate(addBlockedBy)`. Independent slices stay parallel. - -Save manifest to `.claude/${CLAUDE_CODE_TASK_LIST_ID}.json`. - -### On `TaskCreate` rejection - -The `PreToolUse(TaskCreate)` hook (`scripts/task-created-tag-check.sh`) validates `metadata.user_story` and, on Rust projects, `metadata.layer` before the task is created. **A rejection means the task does NOT exist.** Treat this as a hard stop on that call, never narrate forward: - -1. Read the dump at `${TMPDIR:-/tmp}/code-et-task-hook/last-rejected.json` — the `extracted` field shows what the hook actually parsed. -2. Most-common cause: `user_story` contains alternation copied from this doc (`"US-1 | AC-1.1, AC-1.2"`) instead of one tag. Pick a single allowed form (above). -3. Re-issue the same `TaskCreate` with corrected metadata. Do not call `TaskUpdate` on a phantom id — the previous call returned no task. -4. Only proceed to the next slice once the prior `TaskCreate` returned a real task id. - -If three retries in a row reject for the same field, stop and surface the rejection payload to the user — the planner has misread something structural in the PRD, not a typo. +`rationale` is mandatory — the implementer subagent starts cold. Set dependencies with `TaskUpdate(addBlockedBy)`. Independent slices stay parallel. ### Output @@ -210,16 +200,12 @@ If three retries in a row reject for the same field, stop and surface the reject Plan complete: N tasks created. Run /code:ship to execute. ``` -``` -Bash("command -v cmux &>/dev/null && [ -n \"$CMUX_SOCKET_PATH\" ] && cmux notify --title 'Plan ready' --subtitle 'N tasks' || true") -``` - ## Brevity -Drop filler ("just", "simply"), hedging ("perhaps", "maybe"), pleasantries ("Sure!"). Fragments over sentences when meaning is clear. Pattern: `[thing] [action] [reason]. [next].` Question messages ≤2 sentences. Recommendation rationale ≤1 sentence. +Drop filler ("just", "simply"), hedging ("perhaps", "maybe"), pleasantries. Fragments over sentences when meaning is clear. Question messages ≤ 2 sentences. Recommendation rationale ≤ 1 sentence. ## When to skip phases -- User pasted a refined brief / linked an existing PRD → skip Phase 1, jump to Phase 2 confirmation step (or Phase 3 if PRD already on disk). -- User says "just give me the tasks for ``" → resolve PRD via `Bash("${CLAUDE_PLUGIN_ROOT}/scripts/resolve-prd.sh")`, jump to Phase 3. +- User pasted a refined brief / linked an existing PRD → skip Phase 1, jump to Phase 2. +- User says "just give me the tasks for ``" → read the PRD, jump to Phase 3. - Bug-shaped request — route to `/code:fix`. Don't write a PRD for a 2-file change. diff --git a/code-et-implementer/commands/review.md b/code-et-implementer/commands/review.md index b21cb8a..de639de 100644 --- a/code-et-implementer/commands/review.md +++ b/code-et-implementer/commands/review.md @@ -9,37 +9,38 @@ effort: high Run before opening a PR (or after `/code:ship`). Two steps: -1. **Static gate** — full local audit pipeline (mirrors CI). Same as `/code:ship`'s tail step. Skipped with `--no-audit` if you just ran ship and the report is fresh. -2. **Diff review** — capture `git diff ..HEAD` and route to the engineering plugin's `code-review` skill for a human-judgment pass over the change set. +1. **Static gate** — `bun run audit` (mirror of CI). Skipped with `--no-audit` when ship just ran. +2. **Diff review** — `git diff ..HEAD` routed to the engineering plugin's `code-review` skill (or an inline fallback). -This command does **not** push or open the PR — that's `/commit-push-pr`. It is a local gate that mirrors what the cloud reviewer would catch. +This command does **not** push or open the PR — that's `/commit-push-pr`. It's a local gate that mirrors what the cloud reviewer would catch. ## Procedure ### Step 1 — Static gate (skip with `--no-audit`) ``` -Bash('bash "${CLAUDE_PLUGIN_ROOT}/scripts/audit.sh" "$PWD"') +Bash('bun run audit') ``` -This runs the same seven-stage pipeline as CI (`fmt`, `clippy -D warnings`, `layer-deps-validator.sh`, `cargo machete`, `cargo audit`, `cargo deny check`, `cargo nextest`). Report at `.claude/audit-.md`. If it exits non-zero, surface the highest finding from the report and **stop** — fix the static gate before running Step 2 (a noisy diff review on top of broken static checks wastes everyone's time). +The audit runs `biome check`, `tsc --noEmit`, `bun audit`, `bun test`. Report at `.claude/audit-.md`. If it exits non-zero, surface the highest finding and **stop** — fix the static gate before running Step 2. ### Step 2 — Code-review -If the engineering plugin's `code-review` skill is installed, delegate to it: +If the engineering plugin's `code-review` skill is installed, delegate: ``` Skill("code-review") ``` -The skill reads `git diff ..HEAD` (and the audit report from Step 1 if present) and returns findings ordered by confidence × severity. Feed the output back to the user. +The skill reads `git diff ..HEAD` (plus the audit report from Step 1) and returns findings ordered by confidence × severity. Feed the output back to the user. If the engineering plugin is **not** installed, run a 5-area inline review: -1. **Layer compliance** — does any new file violate the inward dependency rule? (`code-et-implementer/docs/architecture.md` §"The Dependency Rule") -2. **Anti-slop** — Rule of Three duplicates, mirror tests, defensive validation, dead re-exports, complexity ≥ 15. (`code-et-implementer/docs/anti-slop.md` §"Hard rules") -3. **Test coverage** — every acceptance criterion has a corresponding test. (`code-et-implementer/docs/testing.md` §"Per-layer test matrix") -4. **Security** — `cargo audit` clean; secrets in `secrecy::Secret`; auth at every interface entry point. (`code-et-implementer/docs/architecture.md` §"Rust security checklist") -5. **Slice integrity** — each commit (US-N / AC-N.M / chore) is a coherent vertical slice; superseded code deleted in same commit; no `// TODO: remove old X`. + +1. **Deep-module shape.** Does any new module pass the deletion test? Anything extracted as a shallow pass-through? (See [`docs/architecture.md`](../docs/architecture.md).) +2. **Anti-slop.** Rule of Three, mirror tests, defensive validation between trusted modules, dead re-exports. (See [`docs/anti-slop.md`](../docs/anti-slop.md) §"Hard rules".) +3. **Test coverage.** Every acceptance criterion has a test; tests assert through the interface. (See [`docs/testing.md`](../docs/testing.md).) +4. **Security.** Zod parsing at every HTTP seam; secrets validated at boot; auth on mutating routes; no raw SQL string-concat. +5. **Slice integrity.** Each commit is a coherent vertical slice; superseded code deleted in the same commit; no `// TODO: remove old X`. Report findings with `severity | path:line | issue | suggested fix`. Group by severity (CRITICAL > HIGH > MEDIUM > LOW). Cite from the audit report and the diff. @@ -53,11 +54,7 @@ Review complete. Next: /commit-push-pr to ship, or fix and re-run /code:review. ``` -``` -Bash("command -v cmux &>/dev/null && [ -n \"$CMUX_SOCKET_PATH\" ] && cmux notify --title 'Review done' --subtitle 'See chat for findings' || true") -``` - ## Notes -- Use this in parallel with the cloud `/ultrareview` (built-in research preview). `/code:review` is the local gate; `/ultrareview` is the multi-agent cloud pass. Both are safe to run; they catch different things. -- If `--no-audit` is passed and no fresh `.claude/audit-*.md` exists, the diff review is still useful but lacks the static-gate context. +- Use in parallel with the cloud `/ultrareview` if available. `/code:review` is the local gate; `/ultrareview` is the multi-agent cloud pass. Both catch different things. +- If `--no-audit` is passed and no fresh `.claude/audit-*.md` exists, the diff review is still useful but lacks static-gate context. diff --git a/code-et-implementer/commands/ship.md b/code-et-implementer/commands/ship.md index b1bf555..9d0b7b3 100644 --- a/code-et-implementer/commands/ship.md +++ b/code-et-implementer/commands/ship.md @@ -8,81 +8,80 @@ effort: xhigh # Ship — Execute + Audit -Loads pending tasks from `TaskList` (or `.claude/${CLAUDE_CODE_TASK_LIST_ID}.json`), dispatches them as parallel worktree-isolated subagents, then runs the local audit gate. On CRITICAL or HIGH findings, dispatches one fix-pass subagent and re-audits. After 1 retry the chain halts and surfaces findings. +Loads pending tasks from `TaskList`, dispatches them as parallel worktree-isolated subagents, then runs the local audit gate. On CRITICAL or HIGH findings, dispatches one fix-pass and re-audits. After 1 retry the chain halts and surfaces findings. If the current branch is `main` or `master`, create `feature/` first. -## Pre-dispatch: scope the queue to this branch's PRD +## Pre-dispatch — scope the queue to this branch's PRD -`TaskList` is global across the project, not branch-scoped — pending tasks from prior PRDs leak into a fresh branch and would otherwise re-execute against stale spec. Before dispatching, scope the queue: +`TaskList` is global across the project, not branch-scoped — pending tasks from prior PRDs leak in and would otherwise re-execute against stale spec. -1. Resolve the active PRD: - ``` - Bash('"${CLAUDE_PLUGIN_ROOT}/scripts/resolve-prd.sh"') - ``` - Exit 1 = no PRD for this branch → bug lane: ship whatever pending tasks exist (their tags should be `chore:*` from `/code:fix`). Otherwise capture the path. -2. Parse the PRD's `## Story Checklist` to enumerate the US tags that belong to it (`US-1`, `US-2`, …). A pending task belongs to this branch iff one of: +1. **Find the active PRD.** Glob `plans/*.md` whose filename slug matches the current branch's slug (after the `feature/`/`fix/`/`chore/` prefix). If exactly one match: that's the PRD. Multiple matches: pick the most recent date. Zero matches: bug lane → ship whatever pending tasks exist (their tags should be `chore:*`). +2. **Parse the PRD's `## Story Checklist`** to enumerate US tags (`US-1`, `US-2`, …). A pending task belongs to this branch iff one of: - `metadata.user_story` matches a `US-` in the set, or - `metadata.user_story` is `AC-.` whose `` is in the set, or - - `metadata.user_story` starts with `chore:` (chores during a feature are this-branch work — `/code:fix` runs against the current tree, not the previous PRD). + - `metadata.user_story` starts with `chore:`. - All other pending tasks are stale-from-another-branch and **must be skipped** — do not dispatch them, do not mark them completed; leave them for their owning branch. + All other pending tasks are stale-from-another-branch — **skip** them; don't dispatch, don't mark completed; leave them for their owning branch. 3. Build the dispatch queue from the scoped subset only. -**Empty-queue diagnostics — never exit silently.** If after scoping the queue is empty, the situation is one of three, each with a distinct message: +**Empty-queue diagnostics — never exit silently.** | Condition | Surface | |---|---| -| PRD resolved, no pending tasks match its US tags | `Active PRD: . 0 tasks tied to its user stories. Run /code:plan to decompose the PRD before /code:ship.` | -| PRD resolved, pending tasks exist but all match a *different* PRD | `Active PRD: . Pending tasks () belong to a different PRD (). Either switch branch or run /code:plan on this branch.` | +| PRD found, no pending tasks match its US tags | `Active PRD: . 0 tasks tied to its user stories. Run /code:plan to decompose the PRD before /code:ship.` | +| PRD found, pending tasks belong to a *different* PRD | `Active PRD: . Pending tasks () belong to a different PRD (). Switch branch or run /code:plan on this branch.` | | No PRD, no pending tasks | `No PRD for this branch and no pending tasks. Nothing to ship — run /code:fix or /code:plan first.` | -In every case, **stop**. Do not invent tasks, do not dispatch the prior branch's queue. +In every case, **stop**. ## Dispatch -Every task runs as a forked subagent in its own worktree. Use `Agent` with `isolation: "worktree"`, `subagent_type: "general-purpose"`, and `model: "sonnet"` (Sonnet 4.6 — routine coding tier). **Do not** shell out to `git worktree add` — `isolation: "worktree"` handles it (requires `CLAUDE_CODE_FORK_SUBAGENT=1` on external builds; default-on inside this harness). +Every task runs as a forked subagent in its own worktree: `Agent(isolation: "worktree", subagent_type: "general-purpose", model: "sonnet")`. Sonnet 4.6 — routine vertical-slice coding from a complete brief. -**Model assignments across the swarm:** +Independent tasks **must** dispatch in a single message with multiple `Agent` calls so they run concurrently — never serialize what could fan out. Dependency graph from `TaskGet` drives order. + +### Model assignments | Role | Model | Why | |---|---|---| -| Orchestrator (this skill) | inherits (Opus 4.7) | Multi-step coordination + decisions on partial failures. | -| Per-task implementer | `sonnet` (4.6) | Routine vertical-slice coding from a complete brief. | -| Per-task reviewer fork | `opus` (4.7) | Catching bugs the implementer missed is high-leverage — an 8-pt SWE-bench gap on the reviewer pays for itself. Reviewer errors fail silently; implementer errors get caught downstream. | -| Per-task review fix-pass | `opus` (4.7) | Applies reviewer findings — same model as the reviewer to keep judgment consistent across the find/fix pair. | -| Post-merge audit fix-pass | `opus` (4.7) | Judgment call on the audit gate — layer slips, dependency advisories. | -| Explore (when delegated for breadth) | `haiku` (4.5) | Cheap breadth searches for cold areas. Implementer/reviewer prompt may request this. | - -Dependency graph drives order. Independent tasks **must** dispatch in a single message with multiple `Agent` calls so they run concurrently — never serialize what could fan out. +| Orchestrator (this skill) | inherits (Opus 4.7) | Multi-step coordination + partial-failure decisions. | +| Per-task implementer | `sonnet` (4.6) | Routine slice coding from a complete brief. | +| Per-task reviewer fork | `opus` (4.7) | Catching missed bugs is high-leverage; reviewer errors fail silently. | +| Per-task review fix-pass | `opus` (4.7) | Same judgment as the reviewer for find/fix consistency. | +| Post-audit fix-pass | `opus` (4.7) | Audit-gate findings often need judgment. | +| Explore (when delegated) | `haiku` (4.5) | Cheap breadth searches for cold areas. | ### Dispatch prompt template -Each subagent starts cold. Send one comprehensive first turn — intent, constraints, acceptance criteria, `file:line` anchors, verification, and rationale — so it operates autonomously without back-and-forth. Use this template verbatim (fill `<…>` from task metadata + active PRD): +Each subagent starts cold. Send one comprehensive first turn — intent, constraints, acceptance criteria, `file:line` anchors, verification, rationale. ``` # Task : ## Tag -<metadata.user_story — one of: US-N | AC-N.M | chore:<reason> | none> +<metadata.user_story — one of: US-N | AC-N.M | chore:<reason>> ## PRD context (if US-N or AC-N.M) -<Paste the matching US-N / AC-N.M block from the active PRD, resolved via ${CLAUDE_PLUGIN_ROOT}/scripts/resolve-prd.sh. Include the parent User Story and all its ACs so the agent sees full intent.> +<Paste the matching US-N / AC-N.M block from the active PRD. Include the parent +User Story and all its ACs so the agent sees full intent.> ## Rationale -<metadata.rationale — verbatim from plan. The why, not the what.> +<metadata.rationale — verbatim. The why, not the what.> ## Files to touch -<For each metadata.files[] entry, render one bullet: - "- <op> <path>[:<line>] → <symbol>" -omitting ":<line>" if absent and "→ <symbol>" if absent. Examples: - - modify crates/domain/src/user.rs:42 → User::validate - - add crates/infrastructure/src/db/users.rs → UserRepository - - delete crates/infrastructure/src/legacy.rs:89 → old_validate_fn -Read each entry's file (sliced) before editing. `line` is a hint — if the symbol has moved, re-resolve via LSP `documentSymbol`; `symbol` is the contract. Apply each op exactly: `add` creates, `modify` edits in place, `replace` full-rewrites the symbol, `delete` removes it (plus all references).> - -## Layer -<metadata.layer — domain | application | infrastructure | interface | chore. Imports point inward; `cargo build` enforces this.> +<For each metadata.files[] entry, render: + - <op> <path>[:<line>] → <symbol> +Examples: + - modify src/modules/orders/index.ts:42 → Orders.place + - add src/http/routes/orders.ts → placeOrder + - delete src/modules/orders/legacy.ts:89 → oldPlace +Read each entry's file (sliced) before editing. `line` is a hint — if the symbol +moved, re-resolve via Grep/LSP; `symbol` is the contract. Apply each op exactly.> + +## Module +<metadata.module — the primary module this slice mostly lives in. Free-form; +matches src/modules/<name>/.> ## Expected outcome <metadata.expected_outcome — observable success criterion.> @@ -91,56 +90,63 @@ Read each entry's file (sliced) before editing. `line` is a hint — if the symb Run `<metadata.verification>`. Must exit 0. All existing tests must still pass. ## Constraints -- Follow rules in `code-et-implementer/CLAUDE.md` (Brevity, Context Hygiene, Clean Architecture controlling rules, ≤600 lines/file). -- Layer rules: `code-et-implementer/docs/architecture.md`. Anti-slop hard rules: `code-et-implementer/docs/anti-slop.md`. Test matrix: `code-et-implementer/docs/testing.md`. -- Read in slices: `Read(offset, limit)` for files >200 lines; never re-read the same file twice for different blocks. -- Delegate breadth to `Agent(subagent_type: "Explore")` if the fix path is unclear — do not Grep-and-Read your way through unknown territory. -- Every acceptance criterion must have a corresponding test. -- No scope expansion — implement exactly what the task specifies; flag adjacent issues instead of fixing inline. -- If the slice supersedes existing code, delete the superseded code in the same commit. No parallel utilities, no `// TODO: remove old X`. New code obsoletes old. -- All SQL via `sqlx::query!` / `query_as!` (compile-time-checked). Raw `sqlx::query` is forbidden. -- Commit format: `<prefix>: <subject>` where prefix is US-N | AC-N.M | chore (or no prefix if tag is `none`). - -## Deliverables (subagent reports back) +- Architecture: deep modules (see code-et-implementer/docs/architecture.md). No + fixed layer taxonomy; modules grow around interfaces. The interface is the + test surface — assert behaviour through it, not past it. +- Anti-slop hard rules: code-et-implementer/docs/anti-slop.md. Apply the deletion + test before any new extraction. +- Test matrix: code-et-implementer/docs/testing.md. +- Read in slices (Read(offset, limit)) for files > 200 lines. +- Delegate breadth: Agent(subagent_type: "Explore", model: "haiku") for unknown + territory instead of Grep-and-Read tours. +- Every acceptance criterion gets a corresponding test. +- No scope expansion — implement exactly what the task specifies; flag adjacent + issues, don't fix inline. +- Supersession deletion in the same commit. No parallel utilities, no `// TODO: + remove old X`. +- HTTP input parsed with Zod at the seam. Modules trust their callers within the + process boundary. +- Commit format: `<prefix>: <subject>` where prefix is US-N | AC-N.M | chore. + +## Deliverables 1. Code changes committed in the isolated worktree. 2. `metadata.verification` exits 0. 3. Single commit with correct prefix. -4. PRD checkbox ticked (if `US-N`) — flip `- [ ] US-N` to `- [x] US-N` and stage with the commit. -5. Final report: commit SHA, branch name, worktree path (returned in the `Agent` tool result). +4. PRD checkbox ticked (if `US-N`) — flip `- [ ] US-N` to `- [x] US-N` and stage + with the commit. +5. Final report: commit SHA, branch name, worktree path. -Do not merge back to the parent feature branch — the orchestrator handles that. Do not ask clarifying questions. If blocked, flag in the final report with a specific file:line reference. +Do not merge back; the orchestrator handles that. Do not ask clarifying questions. +If blocked, flag in the final report with a file:line reference. ``` ### Per-subagent contract -1. Implement the task. Every acceptance criterion has a corresponding test. +1. Implement the task. Every acceptance criterion has a test. 2. Run `metadata.verification`. Must compile, tests must pass. -3. Commit with the right prefix: +3. Single commit with the right prefix: - `US-N: <subject>` when tag is `US-N` - `AC-N.M: <subject>` when tag is `AC-N.M` - `chore: <subject>` when tag starts with `chore:` - - No prefix when tag is `none` or absent. -4. **Tick the PRD checkbox.** If `metadata.user_story` is `US-N`, resolve the PRD via `${CLAUDE_PLUGIN_ROOT}/scripts/resolve-prd.sh` and `Edit` to flip `- [ ] US-N` → `- [x] US-N`. Stage with the commit. +4. **Tick the PRD checkbox** if `metadata.user_story` is `US-N`: `Edit` the PRD to flip the checkbox; stage with the commit. -The subagent stops after step 4 and returns. It must **not** merge or remove its own worktree — it has no view of the parent feature branch. +The subagent stops after step 4 and returns. It must **not** merge or remove its worktree. ## Per-task review (before merge) -Code review happens twice in v4.1+: once per task before merge (this section, shift-left), and once across the full feature branch at `/code:review` (pre-PR gate). Per-task review catches logic bugs at the smallest possible diff — task 1's bug never gets to pollute task 2's foundation. +Two review passes: per-task (here, shift-left) and across the full branch at `/code:review`. Per-task review catches logic bugs at the smallest possible diff. ### Step 1 — Capture the diff -After the implementer subagent returns successfully: - ``` diff="$(git -C <worktree_path> diff $(git merge-base HEAD <subagent_branch>)..<subagent_branch>)" ``` -Empty diff = implementer didn't write code. Halt that task and surface to the user; do not dispatch a reviewer. +Empty diff = implementer didn't write code. Halt that task and surface to the user. ### Step 2 — Dispatch the reviewer (Opus 4.7) -Reviewer is a fork — `Agent(model: "opus")` with no `subagent_type` and no `isolation`. It works against the diff payload, not the worktree. +Reviewer is a fork — `Agent(model: "opus")` with no `subagent_type` and no `isolation`. Works against the diff payload, not the worktree. If the diff exceeds **1500 lines**, halt this task and surface a "task too large — split or escalate to `/code:review` only" warning instead of dispatching. A vertical slice that big is almost always two slices in disguise. @@ -152,14 +158,14 @@ Reviewer prompt: ## Diff (against parent feature branch) <diff content — full payload, ≤1500 lines> -## Rationale (why this task exists) +## Rationale <metadata.rationale> ## Expected outcome <metadata.expected_outcome> -## Layer -<metadata.layer> +## Module +<metadata.module> Review the diff against the rationale + expected outcome. @@ -167,84 +173,95 @@ Review the diff against the rationale + expected outcome. ``` Skill("code-review") ``` -If it returns findings, use them. If the skill is not installed (the call errors with "skill not found"), fall back to Step B. +If it returns findings, use them. If the skill is not installed, fall back to Step B. -**Step B — Inline 5-area review** (mirror of `/code:review` Step 2): -1. **Layer compliance** — does any new file violate the inward dependency rule? (`code-et-implementer/docs/architecture.md` §"The Dependency Rule") -2. **Anti-slop** — Rule of Three duplicates, mirror tests, defensive validation, dead re-exports. (`code-et-implementer/docs/anti-slop.md` §"Hard rules") -3. **Test coverage** — every acceptance criterion has a corresponding test. (`code-et-implementer/docs/testing.md` §"Per-layer test matrix") -4. **Security** — secrets in `secrecy::Secret<T>`; auth at every interface entry point. (`code-et-implementer/docs/architecture.md` §"Rust security checklist") -5. **Slice integrity** — coherent vertical slice; superseded code deleted in same commit; no `// TODO: remove old X`. +**Step B — Inline 5-area review:** +1. **Deep-module shape** — does any new module pass the deletion test? Is anything + extracted as a shallow pass-through? (code-et-implementer/docs/architecture.md) +2. **Anti-slop** — Rule of Three duplicates, mirror tests, defensive validation, + dead re-exports. (code-et-implementer/docs/anti-slop.md §"Hard rules") +3. **Test coverage** — every acceptance criterion has a test; tests assert through + the interface, not past it. (code-et-implementer/docs/testing.md) +4. **Security** — Zod at HTTP seams; secrets not in logs; auth on mutating routes. +5. **Slice integrity** — coherent vertical slice; superseded code deleted same + commit; no `// TODO: remove old X`. -**Output format — strict.** Output ONLY the JSON array as your final message. No preamble, no code fences, no explanation. If no CRITICAL or HIGH findings, output exactly: `[]` +**Output format — strict.** Output ONLY the JSON array. No preamble, no code +fences. If no CRITICAL or HIGH findings, output exactly: `[]` -Schema: -[{"severity": "CRITICAL|HIGH", "file": "path:line", "issue": "<one sentence>"}] +Schema: [{"severity": "CRITICAL|HIGH", "file": "path:line", "issue": "<one sentence>"}] -Drop MEDIUM and LOW findings — those are for `/code:review` to catch later. Do not modify code. You are a reviewer, not a fixer. +Drop MEDIUM/LOW — those are for /code:review. Do not modify code. ``` ### Step 3 — On CRITICAL/HIGH findings, dispatch ONE review fix-pass -Spawn `Agent(subagent_type: "general-purpose", model: "opus")` with no isolation. Prompt directs it to operate via `git -C <worktree_path>` and explicit file paths inside `<worktree_path>`: +`Agent(subagent_type: "general-purpose", model: "opus")` with no isolation. Prompt directs it to operate via `git -C <worktree_path>` and explicit paths inside `<worktree_path>`: ``` # Review fix-pass for <task-id> -The per-task reviewer flagged the following CRITICAL/HIGH findings on the diff in worktree <worktree_path>: +The per-task reviewer flagged these CRITICAL/HIGH findings on the diff in +worktree <worktree_path>: <findings JSON> -Fix each finding. Use absolute paths or `git -C <worktree_path>` for git operations. After fixing, re-run `<metadata.verification>` from inside `<worktree_path>` (must exit 0). Commit the fix-up with subject "fix-up: <task tag>". Return the new HEAD SHA. +Fix each finding. After fixing, re-run `<metadata.verification>` from inside +<worktree_path> (must exit 0). Commit the fix-up with subject "fix-up: <task tag>". +Return the new HEAD SHA. -Constraints: same as the implementer (Brevity, Context Hygiene, Clean Architecture rules). No scope expansion — fix the findings only. +No scope expansion — fix the findings only. ``` -After the review fix-pass returns, **do not re-review**. One cycle max — same retry budget as the post-merge audit. If the fix-pass returns without a new commit or `verification` fails, halt that task and surface findings to the user (leave the worktree in place for inspection). +After the fix-pass returns, **do not re-review**. One cycle max. If `verification` still fails, halt that task and surface to the user (leave the worktree in place). ## Orchestrator (this skill) After each `Agent` call returns: -1. Read the returned worktree path and branch from the tool result. -2. Run **Per-task review** (above). On CRITICAL/HIGH, dispatch one review fix-pass and continue. + +1. Read the worktree path and branch from the tool result. +2. Run **Per-task review** (above). On CRITICAL/HIGH, dispatch one review fix-pass. 3. From the parent feature branch: `git merge --no-ff <subagent-branch>`. -4. `git worktree remove <path>` (the harness auto-cleans empty worktrees, but populated ones need explicit removal). +4. `git worktree remove <path>` (auto-cleaned if empty, but populated worktrees need explicit removal). 5. Mark the task completed via `TaskUpdate` only after the merge lands. -If a subagent reports failure, or the review fix-pass cannot resolve findings, leave the worktree in place for inspection — do not auto-discard. +On a failure (subagent reports blocked, or fix-pass can't resolve), leave the worktree in place — do not auto-discard. ## After all tasks land — audit -Run `Skill("simplify")` first (changed-code refactor pass). Then run the local audit: - ``` -Bash('bash "${CLAUDE_PLUGIN_ROOT}/scripts/audit.sh" "$PWD"') +Bash('bun run audit') ``` -The audit mirrors the v4.0 CI gate: `cargo fmt --check`, `cargo clippy -D warnings`, `scripts/layer-deps-validator.sh`, `cargo machete`, `cargo audit`, `cargo deny check`, `cargo nextest run --workspace`. Report at `.claude/audit-<UTC>.md`. +The audit mirrors the CI gate: `biome check`, `tsc --noEmit`, `bun audit`, `bun test`. Report appended to `.claude/audit-<UTC>.md`. + +Optionally run `Skill("simplify")` first if the engineering plugin's simplify skill is installed — a changed-code refactor pass before the static gate. ### Auto-retry on CRITICAL/HIGH (max 1 pass) -If audit exits non-zero with CRITICAL or HIGH findings (the typical: layer violation, dependency advisory, clippy lint, test failure): +If audit exits non-zero with CRITICAL or HIGH findings: + +1. Read `.claude/audit-<UTC>.md` — extract highest-severity `path:line` + message. +2. Dispatch **one** fix-pass via `Agent(subagent_type: "general-purpose", model: "opus")` (no isolation — work on the feature branch since tasks merged): -1. Read `.claude/audit-<UTC>.md` — extract the highest-severity finding's `path:line` + message. -2. Dispatch **one** fix-pass subagent via `Agent(subagent_type: "general-purpose", model: "opus")` (no worktree isolation — work directly on the feature branch since the task swarm already merged; Opus 4.7 here because audit-gate findings often require judgment — layer slips, dependency advisories, real test failures vs flakes): ``` # Audit fix-pass The post-implement audit returned <severity> at <path:line>: <message>. Read the report at .claude/audit-<UTC>.md for full context. - Fix the finding(s). Then re-run `bash "${CLAUDE_PLUGIN_ROOT}/scripts/audit.sh" "$PWD"`. - Constraints: same as task subagents (see ship.md). No scope expansion — fix the audit findings, nothing else. + Fix the finding(s). Then re-run `bun run audit`. + No scope expansion — fix the audit findings, nothing else. ``` -3. Wait for the fix-pass to return. -4. Re-run audit once. If still failing, **stop the chain** and surface: + +3. Wait for fix-pass to return. +4. Re-run audit once. If still failing, **stop**: + ``` Audit still failing after 1 fix-pass. Latest report: .claude/audit-<UTC>.md Highest finding: <severity> <path:line> — <message> Inspect, fix manually, then re-run /code:ship to retry the audit step only. ``` -Never loop more than once — repeated AI fix-passes on a stuck audit waste tokens and hide the real issue. +Never loop more than once. ### On clean audit @@ -253,12 +270,6 @@ Never loop more than once — repeated AI fix-passes on a stuck audit waste toke Next: /code:review (or /commit-push-pr to ship). ``` -``` -Bash("command -v cmux &>/dev/null && [ -n \"$CMUX_SOCKET_PATH\" ] && cmux notify --title 'Ship done' --subtitle 'Audit clean' || true") -``` - ## Notes -- The `SubagentStop` hook (`scripts/verify-gate.sh`) runs `cargo test` + `audit --fast` after each subagent — that's the inner loop. The full audit at the end is the outer gate. -- `--fast` runs only fmt + clippy. The post-tasks pass runs the full seven stages. -- The audit is **the** anti-slop enforcement. Never tweak its findings to make it pass — fix the code, or accept a `LOW` finding when a tool is genuinely missing on the host. +- The audit is **the** anti-slop enforcement. Never tweak findings to make it pass — fix the code, or accept a `LOW` when a tool is genuinely missing on the host. diff --git a/code-et-implementer/commands/start.md b/code-et-implementer/commands/start.md index 6ac6db4..a101e33 100644 --- a/code-et-implementer/commands/start.md +++ b/code-et-implementer/commands/start.md @@ -1,102 +1,104 @@ --- tools: Read, Bash, Glob, AskUserQuestion -description: "Scaffold a new pure-Rust Clean Architecture project (axum + sqlx + Dioxus 0.7+ + tokio). Always-latest deps." -argument-hint: "<project-name> [--targets web,desktop,mobile,server] [--db sqlite|postgres] [--install-tools]" -effort: xhigh +description: "Scaffold a Bun + Hono + Drizzle TypeScript project shaped around deep modules." +argument-hint: "<project-name> [--force]" +effort: high --- -Scaffold a fresh pure-Rust full-stack project from `${CLAUDE_PLUGIN_ROOT}/templates/rust/dioxus-fullstack/`. +# Start — Scaffold a new TypeScript project -**The whole stack is fixed:** `axum + sqlx + Dioxus 0.7+ + tokio`, four-crate Clean Architecture workspace (`domain`, `application`, `infrastructure`, `interface`), apps under `apps/` (`server`, `web`, `desktop`, `mobile`). The CI gate (`.github/workflows/code-et-audit.yml` + `scripts/layer-deps-validator.sh`) is copied in. Always-latest semver-compatible deps via post-scaffold `cargo update`. +Copies `${CLAUDE_PLUGIN_ROOT}/templates/typescript/` into a new directory, fills `{{name}}`, installs deps, generates an initial Drizzle migration, and runs `bun run audit` to verify a green start. -**Refuses to run** if CWD has `Cargo.toml` or `package.json` unless `--force` is passed. **Never** scaffolds inside this plugin's repo. +Stack: **Bun + Hono + Drizzle + Biome**. Database: **SQLite** for local + dev (Bun's built-in). Architecture: deep modules — see [`docs/architecture.md`](../docs/architecture.md). No fixed layer taxonomy; modules grow around interfaces. + +**Postgres + a web frontend are manual add-ons,** not flags. The template ships the simplest viable stack; users layer on dependencies as needed: + +- **Postgres in production** (sketch, untested in v5 template — exercise before relying on it): + 1. `bun add pg && bun add -d @types/pg` + 2. In `src/db/index.ts`, swap `drizzle-orm/bun-sqlite` for `drizzle-orm/node-postgres`; build a `Pool` from `DATABASE_URL` instead of opening a SQLite file. + 3. In `drizzle.config.ts`, change `dialect: "sqlite"` → `dialect: "postgresql"`. + 4. Regenerate migrations: `bun run db:generate`. The Drizzle schema in `src/db/schema.ts` ports unchanged for simple column types; review dialect-specific types (e.g. SQLite's `integer({ mode: "timestamp" })`) before assuming portability. +- **Web frontend.** Add Vite + React (or Solid, Svelte, etc.) as a subdirectory; wire its dev server proxy through Hono. + +Both are common, and both are short. Putting them behind flags hides decisions the user should make explicitly. ## Inputs Parse `$ARGUMENTS`: -- **project name** (positional, required) — `^[a-z][a-z0-9-]{1,40}$` -- `--targets <list>` — comma-separated subset of `web,desktop,mobile,server`. Default: all four. `server` always stays — it is the SSR + API root. -- `--db sqlite|postgres` — default `sqlite`. -- `--install-tools` — also `cargo install` the audit toolchain. Default: print checklist. + +- **project name** — positional, required. `^[a-z][a-z0-9-]{1,40}$`. - `--force` — overlay onto a non-empty CWD. -- `--owner <gh-user>` — used to substitute `{{owner}}` in `Cargo.toml`'s `repository` field. Default: `git config user.name` slugified, else `your-org`. -If a required input is missing, ask via `AskUserQuestion` (one focused question per missing field, max two questions total). +If the project name is missing, one focused `AskUserQuestion` to collect it. ## Procedure -1. **Pre-flight.** - ``` - Bash('test -f Cargo.toml || test -f package.json && echo CONFLICT || true') - ``` - On `CONFLICT` without `--force`, stop with: *"This directory already has a Rust or TS project. Re-run with `--force` to overlay."* Refuse if `pwd` contains `code-et-implementer` (don't scaffold inside the plugin repo). +1. **Pre-flight.** Refuse if CWD already has `package.json` or `Cargo.toml` without `--force`. Refuse if `pwd` contains `code-et-implementer` — never scaffold inside the plugin repo. -2. **Copy template + shared assets.** ``` - Bash('TPL="${CLAUDE_PLUGIN_ROOT}/templates/rust/dioxus-fullstack" && SHARED="${CLAUDE_PLUGIN_ROOT}/templates/shared" && mkdir -p "<name>" && cp -R "$TPL"/. "<name>/" && cp -R "$SHARED/.github" "<name>/.github" && mkdir -p "<name>/scripts" && cp "$SHARED/scripts/layer-deps-validator.sh" "<name>/scripts/" && cp "$SHARED/CLAUDE.md.template" "<name>/CLAUDE.md"') + Bash('test -f package.json || test -f Cargo.toml && echo CONFLICT || true') ``` -3. **Filter targets.** Remove unselected app dirs and their workspace member lines. Keep `apps/server` always. +2. **Copy template.** + ``` - Bash('cd "<name>" && for t in web desktop mobile; do - case ",<targets>," in *",$t,"*) ;; *) rm -rf "apps/$t"; sed -i.bak "/\\\"apps\\/$t\\\"/d" Cargo.toml && rm Cargo.toml.bak;; esac - done') + Bash('TPL="${CLAUDE_PLUGIN_ROOT}/templates/typescript" && SHARED="${CLAUDE_PLUGIN_ROOT}/templates/shared" && mkdir -p "<name>" && cp -R "$TPL"/. "<name>/" && cp -R "$SHARED/.github" "<name>/.github" && cp "$SHARED/CLAUDE.md.template" "<name>/CLAUDE.md"') ``` -4. **Substitute placeholders** (`{{name}}`, `{{owner}}`, `{{db}}`) across `Cargo.toml`, `apps/**/Cargo.toml`, `crates/**/Cargo.toml`, `Dioxus.toml`, `CLAUDE.md`, `README.md`, `justfile`, `.env.example`, `scripts/deploy.sh`, `scripts/upload.sh`. Use `find -print0 | xargs -0 sed -i.bak …` and clean `.bak`. +3. **Substitute placeholders** (`{{name}}` only) across `package.json`, `README.md`, `CLAUDE.md`, `.env.example`. Use `find -print0 | xargs -0 sed -i.bak …` and clean `.bak`. -5. **Switch DB.** If `--db postgres`, edit `.env.example` to uncomment the postgres line, comment out the sqlite default. The template's `infrastructure` supports both at runtime. +4. **Install deps.** -6. **chmod the scripts.** `chmod +x "<name>/scripts/"*.sh` — covers `layer-deps-validator.sh`, `deploy.sh`, `upload.sh`. - -7. **Latest deps.** Pull every workspace dep to its latest semver-compatible patch. Non-fatal — a transient yank or registry hiccup should not abort the bootstrap; `cargo check` (next step) is the actual gate: ``` - Bash('cd "<name>" && cargo update 2>&1 | tail -20 || echo "cargo update warned — see output above; bootstrap continues"') + Bash('cd "<name>" && bun install 2>&1 | tail -10') ``` - Caret pins (`dioxus = "0.7"`, `axum = "0.8"`, `sqlx = "0.8"`, `tokio = "1"`, etc.) deliver latest minor/patch automatically. **Major bumps** (e.g., dioxus 0.7→0.8) need a manual `cargo upgrade` (cargo-edit) and a smoke test — flag this on first `just audit` failure rather than gambling here. -8. **Compile-check.** +5. **Generate initial Drizzle migration.** The template ships a `greetings` example schema; this generates `src/db/migrations/0000_*.sql` so the bundled tests run. + ``` - Bash('cd "<name>" && cargo check --workspace 2>&1 | tail -40 || echo "cargo check failed — see output above"') + Bash('cd "<name>" && bun run db:generate 2>&1 | tail -5') ``` - Template's `infrastructure` uses runtime-checked queries, so check passes without a DB. If check fails, the template is broken — surface and stop. -9. **Install tools.** If `--install-tools`: +6. **Run the audit gate.** + ``` - Bash('cargo install --locked cargo-machete cargo-audit cargo-deny cargo-nextest sqlx-cli dioxus-cli') + Bash('cd "<name>" && bun run audit 2>&1 | tail -20') ``` - Otherwise print checklist: + + Exit non-zero → template is broken; surface and stop. + +7. **Init git** if the parent dir has no `.git`: + ``` - cargo install --locked cargo-machete cargo-audit cargo-deny cargo-nextest sqlx-cli dioxus-cli + Bash('cd "<name>" && [ -d ../.git ] || (git init -q && git add . && git commit -q -m "chore: scaffold <name> with code-et v5")') ``` -10. **Print next steps.** - ``` - ✓ Bootstrapped <name> at $(pwd)/<name> +8. **Print next steps.** - Next: - cd <name> - cp .env.example .env - just db-migrate - just run-server # axum + dioxus-fullstack SSR on :3000 - just audit # local mirror of CI + ``` + ✓ Scaffolded <name> at $(pwd)/<name> - Publish: - gh repo create <name> --public --source=. --remote=origin --push - # CI runs on first push; clean run = ready for /code:plan or /code:fix - ``` + Next: + cd <name> + cp .env.example .env + bun run db:migrate + bun run dev # Hono server with hot-reload on :3000 + bun run audit # local mirror of CI -## Output + Add Postgres: + bun add pg && bun add -d @types/pg + # then swap drizzle-orm/bun-sqlite → drizzle-orm/node-postgres in src/db/index.ts + + Publish: + gh repo create <name> --public --source=. --remote=origin --push + ``` -`"Bootstrapped <name> with targets <list>, db <db>. cd <name> && just audit to verify."` +## Output -``` -Bash("command -v cmux &>/dev/null && [ -n \"$CMUX_SOCKET_PATH\" ] && cmux notify --title 'Project ready' --subtitle '<name>' || true") -``` +`"Scaffolded <name>. cd <name> && bun run audit to verify."` ## Notes -- **Never** auto-run `git init` if the user's CWD is already a git repo (sub-project layouts are valid). Run `git init` only when the new project's parent dir has no `.git`. -- **Never** install tools without `--install-tools`. Print the checklist and wait. -- The CI workflow lives at `.github/workflows/code-et-audit.yml` after step 2 — no separate copy. -- Apply rules from `code-et-implementer/CLAUDE.md` and `code-et-implementer/docs/architecture.md`. Dioxus 0.7+ for one-codebase web/desktop/mobile. `sqlx::query!` macros for compile-time-checked SQL. Forward-only migrations. +- The CI workflow lives at `.github/workflows/code-et-audit.yml` after step 2 — no separate install. +- Major version bumps to Bun/Hono/Drizzle ride on `package.json` caret ranges; `bun update` is a manual smoke step, not part of `/code:start`. +- Apply the rules in [`docs/architecture.md`](../docs/architecture.md), [`docs/anti-slop.md`](../docs/anti-slop.md), [`docs/testing.md`](../docs/testing.md). diff --git a/code-et-implementer/docs/anti-slop.md b/code-et-implementer/docs/anti-slop.md index 36f007c..6d78e24 100644 --- a/code-et-implementer/docs/anti-slop.md +++ b/code-et-implementer/docs/anti-slop.md @@ -1,106 +1,91 @@ --- name: anti-slop -description: Anti-slop framework + 5 slop categories + 4-stage verification loop. The CI gate enforces the deterministic stages; the engineering plugin's code-review skill catches the rest. -applies_to: rust +description: Anti-slop framework for TS deep-modules — 4 elements, 5 categories, 4-stage verification, hard rules. The CI gate enforces the deterministic stages; the engineering plugin's code-review skill catches the rest. +applies_to: typescript --- -# Anti-Slop (code-et) +# Anti-Slop (code-et v5) -AI-generated code accumulates structural debt that file-local linters miss: re-export cascades, orphaned modules, duplicated logic, defensive over-programming, mirror tests, architecture drift. This doctrine codifies what code-et's CI gate enforces and what reviewers catch by hand. +AI-generated code accumulates structural debt that file-local linters miss: shallow modules extracted "for testability", duplicate utilities, defensive over-programming, mirror tests, drift away from the deep-module shape. This doctrine names the patterns and the gates. ## The 4 elements -### 1. Global dead code & dependency pruning +### 1. Shallow modules -**Slop:** Functionally extinct code still reachable by the compiler — re-export cascades, orphaned modules, unused crates in `Cargo.toml`. +**Slop:** Modules whose interface is nearly as complex as their implementation. Most often a pure function extracted for "testability" while the real bugs live in how it's called. -**How to detect.** -- Unused dependencies: `cargo machete` (fast, manifest-level). For deeper unused-code-path detection: `cargo +nightly udeps --workspace --all-targets`. -- Dead code in source: `cargo clippy --workspace --all-targets -- -D warnings -W dead_code -W unreachable_pub`. +**How to detect.** Apply the **deletion test** from [`architecture.md`](architecture.md): imagine deleting the module. If complexity vanishes, it was a pass-through — fold it back into the caller. If complexity reappears across N callers, the module earns its keep. -**How to fix.** Delete the dead code in the same PR. Don't leave `// TODO: remove` markers. If a re-export exists for backwards compatibility, gate it behind a `#[deprecated]` attribute with a target removal version. - -**Goal:** code relevance, faster builds, fewer attack surfaces. +**How to fix.** Merge the shallow module into the caller. If it shouldn't go away, deepen it — move more behaviour behind the same interface so callers get **leverage**. ### 2. Duplication -**Slop:** "Logic spread" — one bug must be fixed in many places. Exact clones (copy-paste) and semantic clones (same logic, renamed variables). - -**How to detect.** -- `clippy` catches some patterns (`clippy::clone_on_ref_ptr`, `clippy::needless_collect`, etc.). -- Optional: `jscpd --languages rust --min-tokens 50 --threshold 0` for cross-file copy-paste. - -**Rule of Three.** Two duplicates is a coincidence; three is a pattern. **The third occurrence triggers a refactor in the same PR.** Extract a function, a trait, or a shared module. +**Slop:** One bug must be fixed in many places. Exact clones, semantic clones, "I'll copy this helper into the new module". -**How to fix.** Extract. If the duplication is across layers, the extracted helper goes in the *innermost* layer that all callers can reach — usually `domain` (pure logic) or `application` (orchestration helpers). +**Rule of Three.** Two duplicates is a coincidence; three is a pattern. **The third occurrence triggers a refactor in the same PR.** Extract into the module whose interface naturally owns the concept. -### 3. Complexity hotspots +**How to detect.** Biome catches some patterns. For cross-file copy-paste: `npx jscpd src --min-tokens 50` (run on demand, not in CI by default). -**Slop:** High cognitive load — deeply nested `match`, complex lifetime constraints, functions doing five things. +### 3. Defensive over-programming -**How to detect.** -- `clippy::cognitive_complexity` (warn at 15 by default in `clippy.toml`; deny at 25). Cognitive complexity is preferred over cyclomatic — it weights nested logic and control-flow breaks more heavily. -- `clippy::cyclomatic_complexity` as a secondary gate. -- Manual: identify "critical hotspots" — files where high complexity intersects high change frequency (`git log --since=3.months --name-only --pretty=format: | sort | uniq -c | sort -rn`). Refactor those first. +**Slop:** Excessive `Optional`/`Result` plumbing for inputs already validated upstream. Re-validating in `application` what `interface` already parsed with Zod. `if (!x) throw new Error("x is required")` on a non-null parameter. -**How to fix.** Split the function. Extract the inner `match` arms into named helpers. If lifetimes are tangled, often the underlying problem is a layer violation (a `domain` type holding a `&'_ infrastructure::Foo`); fix the dependency direction first. +**How to fix.** Validate at the seam (HTTP route, queue handler, file parser). Trust internal calls. Pre-conditions in types: if `Order` exists in memory, its fields are valid — don't re-check them on every method. -### 4. Architecture drift +### 4. Drift from the deep-module shape -**Slop:** Divergence from the layered design — circular dependencies, `domain` reaching into `infrastructure`, "convenience" re-exports that paper over a missing abstraction. +**Slop:** Modules acquiring "convenience" exports that paper over a missing abstraction. Implementation details leaking through the interface (returning the Drizzle row instead of the domain DTO). Routes reaching past a module to its private files. -**How to detect.** -- **Compiler:** the workspace `Cargo.toml` `[dependencies]` table is the primary gate. Adding a forbidden dep makes `cargo build` fail. -- **CI validator:** `scripts/layer-deps-validator.sh` (30 lines, ships in every project from `/code:start`) re-asserts the layer rule explicitly. -- **Circular deps:** `cargo` already forbids them at the crate level. Within a crate, large modules can still cycle; if it gets bad, restructure into sub-crates. +**How to detect.** `eslint-plugin-import` or Biome import rules can enforce "no relative imports past `index.ts`". Code review on PRs that touch `index.ts` of multiple modules — when one module's interface grows, ask: does the caller need the new export, or is the module the wrong shape? -**How to fix.** Move the type to the inner layer. Introduce a port (trait) in `application` and implement it in `infrastructure`. Never collapse layers to "make it compile" — that's the slop. +**How to fix.** Move the leaked behaviour behind the existing interface. If the interface can't absorb it cleanly, the module is mis-named — rename to what it actually does, *then* see whether the leakage was inevitable. -## The 5 slop categories (spotting it in review) +## The 5 slop categories — spotting it in review -When the CI gate is green but something still feels wrong, look for these patterns. The engineering plugin's `code-review` skill catches most; reviewers catch the rest. +When CI is green but something still feels wrong, look for these. The engineering plugin's `code-review` skill catches most; reviewers catch the rest. | Category | Looks like | Fix | |---|---|---| -| **Superficial Competence** | Code that follows common patterns but ignores the specific business rule. Example: a `validate_order` that checks for null and length but not the actual domain invariant. | Re-read the use case. Validation belongs in `application`, framed as "what makes this domain operation valid". | -| **Unnecessary Complexity** | Manual loops, manual builders, hand-rolled state machines where a stdlib function exists. Example: a 12-line `for` loop building a `Vec<String>` that's just `.iter().map(...).collect()`. | Use the stdlib. If the stdlib doesn't fit, write a clear named helper, not an inline loop. | -| **Defensive Over-Programming** | Excessive `Option`/`Result` plumbing for inputs already validated upstream. Example: an internal `application` use case re-validating fields the `interface` parsed with serde. | Trust internal callers. Validate at boundaries (interface → application), not between trusted modules. Pre-conditions in types: if `User` exists, its fields are valid. | -| **Mirror Tests** | Tests that replay the implementation. Example: `#[test] fn test_add() { assert_eq!(add(2,3), 2 + 3); }`. The test asserts what the implementation will compute, not what callers expect. | Tests assert observable behaviour: inputs at the public API, outputs at the public API. If a test passes for two different correct implementations, it's a real test. | -| **Inconsistent Styling** | Inline styles in dioxus components instead of class names, comments restating the obvious (`// Call stop` before `stop()`), naming that drifts (`user_id` here, `userId` there). | `rustfmt --check`, `clippy::doc_markdown`, project-wide naming convention enforced by review. | +| **Superficial Competence** | Code follows common patterns but ignores the specific business rule. Example: a `validateOrder` that checks for null and length but not the actual domain invariant. | Re-read the use case. Validation belongs at the seam, framed as "what makes this operation valid in *this* domain". | +| **Unnecessary Complexity** | Hand-rolled loops, hand-rolled state machines where stdlib or a small lib exists. 12-line `for` loop building an array that's `.map(...)` + `.filter(...)`. | Use the language. If stdlib doesn't fit, a named helper, not inline. | +| **Defensive Over-Programming** | Internal modules re-validating fields the seam already parsed with Zod. | Validate at the seam, not between trusted modules. Types carry the post-condition. | +| **Mirror Tests** | Tests replay the implementation. `expect(add(2, 3)).toBe(2 + 3)`. The test asserts what the implementation will compute, not what callers expect. | Tests assert observable behaviour through the public interface. A test that passes for two different correct implementations is a real test. | +| **Inconsistent Styling** | Mixed quote styles, mixed `===` / `==`, naming drift (`userId` here, `user_id` there), comments restating the obvious. | `biome check --apply`. Project-wide naming enforced in review. | -## The 4-stage verification loop (what the CI gate runs) +## The 4-stage verification loop — what CI runs -The `.github/workflows/code-et-audit.yml` workflow shipped by `/code:start` and `/code:install-ci` runs these stages on every PR. The same pipeline runs locally via `just audit` (in scaffolded projects) and as the tail step of `/code:ship`. +The `.github/workflows/code-et-audit.yml` shipped by `/code:start` and `/code:install-ci` runs these stages on every PR. The same pipeline runs locally via `bun run audit` and as the tail step of `/code:ship`. | Stage | What | Tool | |---|---|---| -| 1 — Static validation | Format, dead code, dead exports | `cargo fmt --check`, `cargo clippy -D warnings -W dead_code -W unreachable_pub`. Optional: `cargo +nightly udeps`. | -| 2 — Architectural check | Layer-direction enforcement | `scripts/layer-deps-validator.sh` (defence-in-depth; the `cargo` build is the primary). | -| 3 — Dependency audit | Unused + vulnerable + license/source bans | `cargo machete`, `cargo audit`, `cargo deny check`. | -| 4 — Complexity & duplication | Cognitive complexity, Rule of Three duplication | `cargo clippy` with `clippy.toml` thresholds. Optional: `jscpd` for explicit duplication detection. | +| 1 — Static validation | Format, lint, dead exports | `biome check .` | +| 2 — Type safety | TypeScript checks across the workspace | `tsc --noEmit` | +| 3 — Dependency audit | Vulnerable + unused deps | `bun audit`. Optional: `npx knip` for unused exports/files. | +| 4 — Tests | Unit + integration | `bun test` | A finding is **CRITICAL** if it falls into one of: -- Layer violation (stage 2) -- Known security advisory in a dependency (stage 3 — `cargo audit`) -- Rule-of-Three duplication group (stage 4) -- Cognitive complexity above the deny threshold (stage 4) +- Type error (stage 2) +- Known security advisory in a dependency (stage 3 — `bun audit`) +- Test failure (stage 4) -The CI workflow exits non-zero on any CRITICAL finding. Other findings are warnings and don't block merge but are reviewed. +CI exits non-zero on any CRITICAL finding. **HIGH** findings (Biome errors, type warnings escalated to errors) also block merge. MEDIUM/LOW (Biome warnings, unused exports from `knip`) are reviewed but don't block. ## Hard rules -These are non-negotiable — they end up in `code-et-implementer/CLAUDE.md` so they apply to every plan, every implementation, every review. +These are non-negotiable — they live in `code-et-implementer/CLAUDE.md` so they apply to every plan, every implementation, every review. -1. **Rule of Three.** Third duplicate triggers a refactor in the same PR. -2. **No mirror tests.** Tests assert observable behaviour, not implementation calls. -3. **No defensive validation at trusted boundaries.** Validate at interface→application; trust internal calls. -4. **No `// TODO: remove old X`.** When a slice supersedes existing code, deletion is part of the same commit. -5. **No re-exports for convenience.** A re-export is documentation that a type belongs to two modules; if that's not what you meant, refactor. -6. **Cognitive complexity ceiling: 15.** Override only with `#[allow(clippy::cognitive_complexity)]` + a one-line justification comment. +1. **Deletion test before extraction.** Before extracting a helper into its own module, mentally delete it. If complexity vanishes, don't extract — inline it. Extract only when the deletion test says complexity would reappear across N callers. +2. **Rule of Three.** Third duplicate triggers a refactor in the same PR. +3. **No mirror tests.** Tests assert observable behaviour, not implementation calls. See [`testing.md`](testing.md) §"Mirror-test ban". +4. **No defensive validation at trusted seams.** Validate at HTTP/queue/file seams (with Zod). Trust internal calls. +5. **No `// TODO: remove old X`.** When a new module supersedes existing code, deletion is part of the same commit. New code obsoletes old in one step. +6. **No re-exports for convenience.** A re-export is documentation that a type belongs to two modules. If that's not what you meant, refactor. +7. **One adapter = hypothetical seam. Two adapters = real seam.** Don't introduce a port unless something actually varies across it. +8. **Trust the types.** Don't sprinkle runtime `if (!foo) throw` for fields the type system already proves non-null. Validate at the seam, then trust. ## See also -- [`docs/architecture.md`](architecture.md) — Rust Clean Architecture (the structural target the anti-slop rules defend). -- [`docs/testing.md`](testing.md) — per-layer test matrix; the mirror-test ban is enforced there. -- Engineering plugin's `tech-debt` skill — for prioritising slop fixes via Impact × Risk × Effort. -- Engineering plugin's `code-review` skill — for the human-judgment pass after the CI gate. +- [`architecture.md`](architecture.md) — deep-modules architecture; dependency categories; the seam vocabulary anti-slop defends. +- [`testing.md`](testing.md) — interface-as-test-surface; mirror-test ban examples. +- Engineering plugin's `tech-debt` skill — prioritising slop fixes via Impact × Risk × Effort. +- Engineering plugin's `code-review` skill — the human-judgment pass after CI. diff --git a/code-et-implementer/docs/architecture.md b/code-et-implementer/docs/architecture.md index 293975b..67e47e0 100644 --- a/code-et-implementer/docs/architecture.md +++ b/code-et-implementer/docs/architecture.md @@ -1,162 +1,185 @@ --- name: architecture -description: Rust Clean Architecture doctrine for code-et. Loaded on demand by /code:start, /code:fix, /code:plan, /code:ship. -applies_to: rust +description: Deep-modules TypeScript architecture for code-et. Loaded on demand by /code:start, /code:fix, /code:plan, /code:ship. +applies_to: typescript --- -# Rust Clean Architecture (code-et) +# Architecture — Deep Modules (code-et v5) -The single architecture this plugin scaffolds and enforces. Project shape: `axum + sqlx + dioxus + tokio` full-stack. Frontend: **Dioxus 0.7+ for web, desktop, and mobile from one component tree.** Database: **PostgreSQL on GCP Cloud SQL** for production, **SQLite** for local. Layer enforcement is at the `cargo` level — violating imports fail at `cargo build`, not at runtime. +code-et builds **deep modules** on a TypeScript stack: `Bun + Hono + Drizzle + Vite + React`. There is no fixed layer taxonomy. Modules grow organically around interfaces; the goal is **leverage at the interface** and **locality** for the maintainer. -## Layer model — the four crates +> The vocabulary in this document — *module / interface / seam / adapter / depth / leverage / locality* — is established software-engineering terminology. **Deep modules** trace to Ousterhout's *A Philosophy of Software Design*; **seams** to Feathers' *Working Effectively with Legacy Code*. The terms are used here exactly as those sources define them so plan, ship, and review share a stable language. -``` -crates/ - domain/ Entities, value objects, domain errors. Pure logic. - deps: serde, thiserror, uuid, time. NO workspace deps. - application/ Use cases + ports (traits). Orchestrates domain. - deps: domain. async-trait, anyhow. - infrastructure/ Adapters: sqlx repos, HTTP clients, GCP secret manager. - Implements application's port traits. - deps: application + domain. sqlx, reqwest, tokio. - interface/ Dioxus components + axum handlers. Composition lives in apps/. - deps: application + domain. dioxus, axum, tower. - NEVER depends on infrastructure — apps/ wire the two. -apps/ - server/ bin: axum + dioxus-fullstack SSR. - desktop/ bin: dioxus-desktop renderer. - web/ bin: dioxus-web (WASM). - mobile/ bin: dioxus-mobile (iOS + Android). -``` +## Stack — what every code-et project ships -Each `apps/<name>/main.rs` is the **composition root** — the only place that instantiates concrete `infrastructure` types and wires them into `interface` ports. The Dependency Rule is enforced because `interface/Cargo.toml` does not list `infrastructure` as a dependency. Adding it makes `cargo build` fail. +| Concern | Choice | Why | +|---|---|---| +| Runtime | **Bun** | Single binary; built-in test runner, package manager, bundler. Fast cold-starts, native TypeScript. | +| HTTP server | **Hono** | Tiny, type-safe router. Runs on Bun, Node, Workers, Deno. | +| DB access | **Drizzle ORM** | Type-safe queries; SQL-first. SQLite for local + dev, Postgres for prod. | +| Migrations | **drizzle-kit** | Schema → migration. Forward-only. | +| Web UI (optional) | **Vite + React 19** | Dev-server speed; same TS types shared with the server module. | +| Validation | **Zod** | Single source of truth at the interface seam (HTTP, queue, file). | +| Tests | **Bun test** | In-runtime. Same `expect`/`describe`/`it` shape as Vitest/Jest. | +| Lint + format | **Biome** | One binary; replaces ESLint+Prettier. | -## The Dependency Rule (from Uncle Bob, verbatim) +Desktop and mobile are **out of scope**. If you need them, fork the template; code-et does not maintain a multi-target frontend story. -> The overriding rule that makes this architecture work is *The Dependency Rule*. This rule says that *source code dependencies* can only point *inwards*. Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in the an inner circle. That includes, functions, classes. variables, or any other named software entity. -> -> By the same token, data formats used in an outer circle should not be used by an inner circle, especially if those formats are generate by a framework in an outer circle. We don't want anything in an outer circle to impact the inner circles. +## Vocabulary — use these terms exactly -Mapped to our four crates: +- **Module** — anything with an interface and an implementation. Scale-agnostic: a function, a file, a folder, a workspace package. +- **Interface** — everything a caller must know to use the module correctly: types, invariants, error modes, ordering, config. *Not* just the type signature. +- **Implementation** — the body of code inside. +- **Depth** — leverage at the interface. A module is **deep** when a lot of behaviour sits behind a small interface. **Shallow** = interface nearly as complex as the implementation. +- **Seam** — where an interface lives; a place where behaviour can be altered without editing in place. (Term from Feathers, *Working Effectively with Legacy Code*.) +- **Adapter** — a concrete thing that satisfies an interface at a seam. +- **Leverage** — what callers get from depth. **Locality** — what maintainers get from depth. -| Layer (inner → outer) | May depend on | Must not import | -|---|---|---| -| `domain` | (nothing in workspace) | any other workspace crate | -| `application` | `domain` | `infrastructure`, `interface` | -| `infrastructure` | `application`, `domain` | `interface` | -| `interface` | `application`, `domain` | `infrastructure` | +Do **not** drift into "component", "service", "API", or "boundary". Consistent language is the whole point. -The `Cargo.toml` `[dependencies]` table is the enforcement mechanism. The CI workflow's `layer-deps-validator.sh` adds a defence-in-depth check; the compiler is the primary gate. +## Three load-bearing principles -## Crossing boundaries +1. **The deletion test.** Imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, the module earned its keep. Use this when deciding whether to extract. +2. **The interface is the test surface.** Tests cross the same seam callers do. If you find yourself wanting to test *past* the interface, the module is the wrong shape — fix the shape, not the test. +3. **One adapter means a hypothetical seam. Two adapters means a real seam.** Don't introduce a port unless something actually varies across it (typically production + a test stand-in). -**DTOs only.** When data crosses a boundary, it is a plain struct or function argument — never a `domain::Entity` and never a `sqlx::Row`. `interface` accepts a JSON request, parses it into an `application::Command` DTO, hands it to a use case. The use case returns a `application::Response` DTO that `interface` serialises out. +## Project shape — start flat, deepen as you go -**DIP at the boundary.** Use cases in `application` declare traits (ports). `infrastructure` implements them. The composition root (`apps/<name>/main.rs`) injects the concrete impl. Example: +A fresh code-et project starts as one workspace, one package: -```rust -// crates/application/src/ports.rs -#[async_trait] -pub trait UserRepo { - async fn by_id(&self, id: UserId) -> Result<Option<User>, RepoError>; -} +``` +src/ + modules/ One folder per deep module. No fixed taxonomy. + <module-name>/ + index.ts The interface. Public exports + types only. + <impl>.ts The implementation. Can be many files. + <module-name>.test.ts Tests cross the same seam callers do. + db/ Drizzle schema + migrations. + schema.ts + migrations/ + http/ + app.ts Hono app. Wires modules to routes. + routes/<route>.ts One file per resource. Calls module interfaces. + config.ts Typed env loading (Zod). Read once at boot. + main.ts Composition root. Imports concrete adapters, + wires them into modules + routes, starts server. +web/ Optional Vite + React frontend (drop if API-only). + src/... +docs/ + adr/ Architecture Decision Records. Lazy-created. +CONTEXT.md Domain glossary. Lazy-created. +``` -// crates/application/src/use_cases/get_user.rs -pub struct GetUser<R: UserRepo> { repo: R } +`src/main.ts` is the **composition root** — the only place that instantiates concrete adapters (DB pool, HTTP clients) and injects them into module factories. No module reaches into another module's implementation; all communication is via interfaces exported from `<module>/index.ts`. -// crates/infrastructure/src/repos/postgres_user_repo.rs -pub struct PostgresUserRepo { pool: PgPool } -#[async_trait] -impl UserRepo for PostgresUserRepo { /* ... */ } +## Dependency categories — how to deepen each kind of module -// apps/server/src/main.rs -let repo = PostgresUserRepo::new(pool); -let use_case = GetUser::new(repo); -let app = interface::http::router(use_case); -``` +The category a module's dependencies fall into determines how it's tested across its seam. + +| Category | Examples | How to deepen / test | +|---|---|---| +| **In-process** | Pure computation, in-memory state. | Always deepenable. Merge shallow helpers; test through the new interface directly. No adapter needed. | +| **Local-substitutable** | DB (SQLite/Postgres), filesystem. | Deepenable when a local stand-in exists. For DB: run the same Drizzle schema against in-process SQLite for tests. Seam is *internal*; no port at the module's external interface. | +| **Remote-but-owned** | Your other services across a network. | Define a **port** at the seam. Implement an HTTP/queue adapter for production, an in-memory adapter for tests. The logic sits in one deep module even though it's deployed across a network. | +| **True external** | Stripe, Twilio, third-party APIs. | Module takes the external dependency as an injected port. Tests provide a mock adapter. | -## Frontend — Dioxus everywhere +**One adapter = hypothetical seam. Two adapters = real seam.** Don't introduce a port for a single-adapter case — it's just indirection. -`crates/interface/src/components/` holds Dioxus components. **One UI codebase, three render targets** via Cargo features: +## Crossing seams — DTOs only -| Target | Crate feature | App | Build | -|---|---|---|---| -| Web (WASM) | `interface/web` | `apps/web` | `dx build --platform web` | -| Desktop | `interface/desktop` | `apps/desktop` | `dx build --platform desktop` | -| Mobile | `interface/mobile` | `apps/mobile` | `dx build --platform mobile` | -| SSR (axum) | `interface/server` | `apps/server` | `cargo run -p server` | +Data crossing a module's interface is a plain object (or a Zod-parsed type), never a Drizzle row, ORM entity, or framework type. The HTTP route parses the request with Zod, calls the module interface, serialises the result back out. -Server-side rendering uses `dioxus-fullstack` mounted into the axum router. Server components live in `interface::http::ssr`; client islands hydrate from `interface::components`. +```ts +// src/modules/orders/index.ts — the interface +import type { OrderId, Money } from "./types"; +export type { OrderId, Money }; -**Mobile is best-effort.** Dioxus mobile (iOS/Android) is newer than web/desktop. CI tests web + desktop builds on every PR. Mobile builds run locally when xcode/android-ndk are present; opt into CI mobile builds with a separate workflow once the project's mobile surface is stable. +export interface Orders { + place(input: PlaceOrderInput): Promise<OrderId>; + byId(id: OrderId): Promise<Order | null>; +} +export type PlaceOrderInput = { customerId: string; lines: OrderLine[] }; +export type Order = { id: OrderId; total: Money; status: OrderStatus }; + +// src/modules/orders/impl.ts — the implementation (internal) +export function makeOrders(deps: { db: Database; clock: Clock }): Orders { + // ... single deep module. Tests in orders.test.ts go through `Orders`. +} + +// src/http/routes/orders.ts — adapter at the HTTP seam +const placeOrderBody = z.object({ customerId: z.string().uuid(), lines: z.array(orderLine) }); +app.post("/orders", async c => { + const body = placeOrderBody.parse(await c.req.json()); + const id = await orders.place(body); + return c.json({ id }, 201); +}); + +// src/main.ts — composition root +const db = drizzle(new Database(env.DATABASE_URL)); +const orders = makeOrders({ db, clock: systemClock }); +const app = buildHttp({ orders }); +``` ## Database -### Production: PostgreSQL on GCP Cloud SQL +### Local + small projects: SQLite -- **Connection.** Use the **Cloud SQL Auth Proxy** + **IAM database authentication** — never raw username/password in env vars. The proxy gives short-lived OAuth tokens; the IAM principal is the workload identity bound to the service account. -- **Pool sizing.** `sqlx::PgPool` with `max_connections = min(num_cpus × 2, 25)` for typical Cloud SQL `db-custom` tiers. Tune from `pg_stat_activity`. -- **Migrations.** `sqlx::migrate!("./migrations")` from `apps/server/src/main.rs` at boot, **after** acquiring the IAM token. Forward-only; rollback is an *additional* migration that undoes the previous step. Each migration ships with a `recovery.md` next to the SQL file describing how to manually reverse it if the rollback migration itself is faulty. -- **Compile-time safety.** All queries use `sqlx::query!` / `query_as!` (compile-time-checked against the live schema via `DATABASE_URL` or against a committed `sqlx-data.json` for offline builds). Raw `sqlx::query` (no macro) is forbidden in production code. +```ts +import { drizzle } from "drizzle-orm/bun-sqlite"; +import { Database } from "bun:sqlite"; +const db = drizzle(new Database(env.DATABASE_URL)); // file:./dev.db or :memory: +``` + +In-memory (`:memory:`) for tests. The same schema and queries run against Postgres in prod — Drizzle abstracts the dialect. -### Local & small projects: SQLite +### Production: Postgres -- Same `sqlx` interface; `sqlx::Pool<Sqlite>`. SQLite file path is `DATABASE_URL=sqlite:./dev.db`. In-memory for tests: `DATABASE_URL=sqlite::memory:`. -- Migrations dir is shared. Write SQL that is portable (`TEXT`, `INTEGER`, `REAL`, `BLOB` types; avoid `SERIAL`, use `INTEGER PRIMARY KEY AUTOINCREMENT` with a Postgres-compatible CTE pattern, or split into per-engine migrations under `migrations/postgres/` and `migrations/sqlite/` — choose one approach, document it). -- Switching engines is a config change: set `DATABASE_URL`, re-run `cargo sqlx prepare` against the target. +```ts +import { drizzle } from "drizzle-orm/node-postgres"; +import { Pool } from "pg"; +const pool = new Pool({ connectionString: env.DATABASE_URL }); +const db = drizzle(pool); +``` + +- **Migrations.** `drizzle-kit generate` from `db/schema.ts` → `db/migrations/`. Forward-only. Each rollback is its own forward migration. Apply at boot in `main.ts` via `migrate(db, { migrationsFolder })`. +- **Pool sizing.** Default `max` ≈ `min(num_cpus × 2, 25)` for managed Postgres tiers. Tune from `pg_stat_activity`. ### Repo placement -`infrastructure/repos/` is the **only** module that imports `sqlx`. Use cases see ports (traits) only. Tests for repos use `#[sqlx::test]` with the appropriate engine. +Only the module that owns persistence for a concept imports Drizzle. Other modules see the interface (a TypeScript type), not the schema. Tests use an in-memory SQLite Drizzle, exercising the real query layer. -## Secrets baseline +## Secrets -- **Production:** GCP Secret Manager. Service-account-scoped access via workload identity. Secrets are fetched at boot into a typed `Config` struct in `infrastructure/config/`; never re-fetched on the hot path. -- **Local:** `.env` file loaded with `dotenvy` only when `cfg!(debug_assertions)`. `.env` is gitignored. `.env.example` ships with placeholder names and no real values. -- **CI:** Secrets via GitHub Actions encrypted secrets — never echoed in workflow logs. The `code-et-audit.yml` workflow uses `DATABASE_URL=sqlite::memory:` for tests; production secrets stay in deployment workflows. -- **Never:** secrets in code, in `Cargo.toml`, in `Dioxus.toml`, in `tracing` logs (use `secrecy::Secret<T>` to get redaction in `Debug`). +- **Production.** Read from env at boot in `src/config.ts`. Validate with Zod; missing/invalid env crashes at start, not at first request. +- **Local.** `.env` loaded by Bun automatically (`bun --env-file=.env` or `Bun.env`). `.env` is gitignored. `.env.example` ships with placeholder names and no values. +- **CI.** GitHub Actions encrypted secrets. Tests run with `DATABASE_URL=":memory:"`; production secrets stay in deployment workflows. +- **Never.** Secrets in source, `package.json`, or logs. Wrap sensitive strings in a `Secret<T>` newtype with a `toString()` that redacts. -## Rust security checklist +## Security checklist -Run through this list at PR time. The CI gate catches the deterministic items; the human pass (engineering plugin's `code-review` skill) catches the rest. +Run through this list at PR time. CI catches the deterministic items; reviewers (or the engineering plugin's `code-review` skill) catch the rest. -| # | Check | Tool / How | -|---|---|---| -| 1 | No `unsafe` without justification comment | `grep -r "unsafe " crates/` — every block has a `// SAFETY:` line above it explaining the invariant. `cargo-geiger` for project-wide unsafe count. | -| 2 | All `serde::Deserialize` of untrusted input has bounded sizes | Code review. `serde_json::from_str` on request bodies has `axum::extract::Json<T>` with `T` bounded by deny-on-overflow types (e.g. `String` in DTOs is wrapped in a `BoundedString<N>`). | -| 3 | No raw SQL — `query!` / `query_as!` only | `grep -rn "sqlx::query(" crates/infrastructure/` should return nothing. | -| 4 | No FFI without `extern "C"` audit | Code review; if any FFI exists, document the foreign contract in a `// FFI CONTRACT:` block. | -| 5 | Secrets wrapped in `secrecy::Secret<T>` | `grep -rn "Secret<" crates/infrastructure/config/`. | -| 6 | Dependency advisories clean | `cargo audit` (CI). | -| 7 | License + source bans clean | `cargo deny check` (CI, with `deny.toml`). | -| 8 | No floating dependencies (every dep pinned to a major or minor) | Visible in `Cargo.toml` review. | -| 9 | Auth at every interface entry point | `axum` route table review: every route has either a public marker or a middleware that asserts auth. | -| 10 | Input validation lives in `application` (use case), not `interface` | `interface` parses, `application` validates business rules. Code review. | - -## Where things live — quick reference - -| Concern | Crate | Module | +| # | Check | How | |---|---|---| -| Entities (`User`, `Order`, …) | `domain` | `entities/` | -| Value objects (`Email`, `Money`, …) | `domain` | `value_objects/` | -| Domain errors | `domain` | `errors.rs` | -| Use cases (`CreateUser`, `GetOrder`, …) | `application` | `use_cases/` | -| Ports (traits) | `application` | `ports.rs` | -| Application errors (mapped to HTTP) | `application` | `errors.rs` | -| Repository implementations | `infrastructure` | `repos/` | -| HTTP clients (third-party APIs) | `infrastructure` | `http_clients/` | -| Configuration loading + secrets | `infrastructure` | `config/` | -| HTTP handlers (axum) | `interface` | `http/handlers/` | -| Routes + middleware | `interface` | `http/router.rs` | -| Dioxus components | `interface` | `components/` | -| Composition root | `apps/<name>/` | `main.rs` | - -## When to deviate - -The four-crate split is the default. Add more crates when a clear sub-bounded-context emerges (`crates/billing/{domain,application,…}`). Never add fewer — collapsing `application` into `interface` means losing the test seam at the most valuable boundary. +| 1 | All HTTP input parsed with Zod | Grep for `await c.req.json()` not paired with `.parse(`. | +| 2 | All DB writes go through Drizzle (no raw SQL string-concatenation) | Grep for `db.execute(` with template-literal interpolation. | +| 3 | Secrets validated at boot, not on the hot path | One Zod schema in `config.ts`. | +| 4 | No `eval`, no `new Function(...)` on untrusted input | Grep + review. | +| 5 | Dependency advisories clean | `bun audit` (CI). | +| 6 | Locked dependencies | `bun.lock` committed; CI does `bun install --frozen-lockfile`. | +| 7 | Auth at every mutating HTTP route | Hono middleware (`requireAuth`) asserted in route review. | +| 8 | Input validation lives at the HTTP seam | Modules trust their callers within the process boundary. | + +## When to add layers + +The flat `src/modules/<name>/` shape is the default. When a module's interface grows past ~5–7 exports, or when an obvious sub-bounded-context emerges, split the module — same shape, recursively. Resist the urge to introduce framework-style folders (`controllers/`, `services/`, `repositories/`); they pre-commit to seams that may not exist. + +The **deletion test** is the decision rule: would removing this folder make complexity vanish (delete it) or reappear across callers (it earns its keep)? ## See also -- [`docs/anti-slop.md`](anti-slop.md) — the anti-slop framework + 5 categories the CI gate enforces. -- [`docs/testing.md`](testing.md) — per-layer test matrix (domain unit / application use-case / infrastructure integration / interface e2e). +- [`anti-slop.md`](anti-slop.md) — 4 elements, 5 categories, hard rules. +- [`testing.md`](testing.md) — testing through the interface; deep-module test patterns. +- Ousterhout, *A Philosophy of Software Design* — the source for **deep modules** and the leverage-vs-shallow framing. +- Feathers, *Working Effectively with Legacy Code* — the source for **seams** as a design primitive. - Engineering plugin's `system-design` skill — for ADR / trade-off framing when a major decision is on the table. diff --git a/code-et-implementer/docs/testing.md b/code-et-implementer/docs/testing.md index d6a96c8..fb65986 100644 --- a/code-et-implementer/docs/testing.md +++ b/code-et-implementer/docs/testing.md @@ -1,196 +1,179 @@ --- name: testing -description: Per-layer Rust testing matrix. Mirror-test ban. Contract + security tests at boundaries. cargo-nextest as the runner. -applies_to: rust +description: Testing doctrine for code-et v5 — interface-as-test-surface, deep-module test patterns, mirror-test ban, vertical-slice integration tests with Bun test. +applies_to: typescript --- -# Rust Testing Doctrine (code-et) +# Testing — through the interface (code-et v5) -Tests assert **observable behaviour at boundaries**, not implementation calls. The pyramid is steep: many fast unit tests in `domain` and `application`, fewer integration tests in `infrastructure`, very few e2e tests in `interface`. Mirror tests are banned. +**The interface is the test surface.** Callers and tests cross the same seam. If you want to test *past* the interface, the module is the wrong shape — fix the shape, not the test. -For the general pyramid + what-to-cover guidance, see the engineering plugin's `testing-strategy` skill. This document is the *Rust-specific* delta. +The pyramid is steep: many fast tests through deep-module interfaces, a thin layer of HTTP-seam integration tests, very few full-stack e2e tests. Mirror tests are banned. -## Per-layer test matrix +For the general pyramid + what-to-cover guidance, see the engineering plugin's `testing-strategy` skill. This document is the *TypeScript-specific* delta. -| Layer | Test type | Where | Runner | What to assert | What NOT to assert | -|---|---|---|---|---|---| -| `domain` | Unit | `crates/domain/src/**` `#[cfg(test)] mod tests` | `cargo nextest` | Pure-logic invariants, value-object construction, error variants. | Anything that requires a runtime, DB, network. | -| `application` | Use-case | `crates/application/tests/` (integration test crate) or `#[cfg(test)]` | `cargo nextest` | Use-case behaviour with `mockall` fakes for ports. Inputs at use-case API → outputs at use-case API. | Repository SQL. HTTP handlers. Dioxus rendering. | -| `infrastructure` | Integration | `crates/infrastructure/tests/` | `cargo nextest` + `#[sqlx::test]` | Repository methods against a real DB (SQLite for unit, Postgres in CI service container). HTTP clients against a recorded fixture (`wiremock`). | Use-case logic. UI behaviour. | -| `interface` | E2E | `apps/server/tests/` for HTTP, `crates/interface/tests/` for dioxus components | `cargo nextest` + `axum-test` + `dioxus-testing` | Round-trip: HTTP request → router → use case → repo → response. Dioxus component renders the right tree given props. | Internal call shapes. Error stack traces verbatim. | +## Test matrix -## Concrete patterns +| Test type | Where | What to assert | What NOT to assert | +|---|---|---|---| +| **Module-interface** | `src/modules/<m>/<m>.test.ts` | Behaviour through the module's exported interface. Inputs at the interface → observable outputs at the interface. | Internal call shapes; private functions; how many times a dependency was called. | +| **HTTP-seam** | `src/http/routes/<r>.test.ts` | Round-trip: HTTP request → router → module → DB stand-in → response. Status codes, response shapes, auth gates. | Module internal logic (covered above); database SQL syntax. | +| **E2E (sparse)** | `tests/e2e/*.test.ts` | Critical happy paths end-to-end with the real server bound to an ephemeral port. | Edge cases the lower tiers already cover. | -### `domain` — pure unit tests +Run all of it with `bun test`. The runner picks up `*.test.ts` everywhere; co-locate tests next to the module they test. -```rust -// crates/domain/src/value_objects/email.rs -#[cfg(test)] -mod tests { - use super::*; +## Module-interface tests — the workhorse - #[test] - fn rejects_missing_at_sign() { - assert!(Email::new("not-an-email").is_err()); - } +A deep module exports an interface. Tests build the module with stand-in dependencies, then exercise it through the same interface a real caller uses. - #[test] - fn accepts_simple_address() { - let e = Email::new("user@example.com").unwrap(); - assert_eq!(e.as_str(), "user@example.com"); - } -} -``` +```ts +// src/modules/orders/orders.test.ts +import { describe, it, expect } from "bun:test"; +import { makeOrders } from "./impl"; +import { inMemoryDb, fixedClock } from "../../test/stand-ins"; -No fixtures, no mocks, no async. If a domain test needs setup beyond `let x = Foo::new(...)`, the abstraction is wrong. +describe("orders", () => { + it("places an order and returns its id", async () => { + const orders = makeOrders({ db: inMemoryDb(), clock: fixedClock("2026-01-01") }); -### `application` — use cases with `mockall` + const id = await orders.place({ + customerId: "c-1", + lines: [{ sku: "A", qty: 2, unitPrice: 500 }], + }); -```rust -// crates/application/src/use_cases/get_user.rs -#[cfg(test)] -mod tests { - use super::*; - use crate::ports::MockUserRepo; - use mockall::predicate::eq; + const found = await orders.byId(id); + expect(found?.total).toEqual({ currency: "USD", amount: 1000 }); + expect(found?.status).toBe("pending"); + }); +}); +``` - #[tokio::test] - async fn returns_user_when_repo_finds_one() { - let mut repo = MockUserRepo::new(); - repo.expect_by_id() - .with(eq(UserId::from(42))) - .returning(|_| Ok(Some(User::sample()))); - let use_case = GetUser::new(repo); +Notes: - let result = use_case.execute(UserId::from(42)).await.unwrap(); +- **No mocking the implementation under test.** `makeOrders` is called with real stand-ins for its dependencies; the orders module itself is whole. +- **Stand-ins live in `src/test/stand-ins.ts`** (or per-module if specific). `inMemoryDb()` is a Drizzle SQLite `:memory:` instance running the real schema and migrations — same query layer as production. +- **Assert observable outputs.** `place` returns an id; `byId` returns the order. Don't assert that `db.insert` was called. - assert_eq!(result.id, UserId::from(42)); - } -} -``` +## HTTP-seam tests — via Hono's `app.fetch` -`mockall` generates the mock from the trait. **Assert observable inputs/outputs of the use case** — never `verify` that a specific repo method was called N times. If you find yourself doing that, the use case is leaking implementation detail. +Hono's `app` is a `fetch`-compatible function. Build the app with stand-in modules, then call it directly — no server required. -### `infrastructure` — `#[sqlx::test]` against a real DB +```ts +// src/http/routes/orders.test.ts +import { describe, it, expect } from "bun:test"; +import { buildHttp } from "../app"; +import { stubOrders } from "../../test/stand-ins"; -```rust -// crates/infrastructure/src/repos/postgres_user_repo.rs -#[cfg(test)] -mod tests { - use super::*; +describe("POST /orders", () => { + it("returns 201 with the new order id", async () => { + const app = buildHttp({ orders: stubOrders({ placeReturns: "ord-1" }) }); - #[sqlx::test] - async fn round_trips_a_user(pool: PgPool) { - let repo = PostgresUserRepo::new(pool); - let user = User::sample(); + const res = await app.fetch( + new Request("http://x/orders", { + method: "POST", + headers: { "content-type": "application/json", authorization: "Bearer test" }, + body: JSON.stringify({ customerId: "c-1", lines: [] }), + }), + ); - repo.save(&user).await.unwrap(); - let fetched = repo.by_id(user.id).await.unwrap().unwrap(); + expect(res.status).toBe(201); + expect(await res.json()).toEqual({ id: "ord-1" }); + }); - assert_eq!(fetched, user); - } -} + it("rejects an unauthenticated request with 401", async () => { + const app = buildHttp({ orders: stubOrders() }); + const res = await app.fetch(new Request("http://x/orders", { method: "POST", body: "{}" })); + expect(res.status).toBe(401); + }); +}); ``` -`#[sqlx::test]` provisions a fresh test database for each test (in Postgres) or an in-memory file (in SQLite). Run with `DATABASE_URL=sqlite::memory:` for unit-speed; against a Postgres service container in CI. +These tests cover the things only the seam knows: parsing, auth, error mapping. They don't re-cover what the module-interface tests already proved. -### `interface` — HTTP e2e via `axum-test` +## E2E — keep it sparse -```rust -// apps/server/tests/users_api.rs -use axum_test::TestServer; +```ts +// tests/e2e/orders-flow.test.ts +import { describe, it, expect, beforeAll, afterAll } from "bun:test"; +import { start } from "../../src/main"; -#[tokio::test] -async fn get_user_returns_200_with_user_json() { - let app = test_app().await; - let server = TestServer::new(app).unwrap(); +let server: { url: string; stop: () => Promise<void> }; +beforeAll(async () => { server = await start({ port: 0, database: ":memory:" }); }); +afterAll(async () => { await server.stop(); }); - let response = server.get("/users/42").await; - - response.assert_status_ok(); - response.assert_json(&serde_json::json!({ "id": 42, "email": "..." })); -} +it("a customer places and retrieves an order", async () => { + const place = await fetch(`${server.url}/orders`, { method: "POST", /* ... */ }); + expect(place.status).toBe(201); + const { id } = await place.json(); + const get = await fetch(`${server.url}/orders/${id}`); + expect(get.status).toBe(200); +}); ``` -`test_app()` is a helper that builds the full router with **fake repos** (mockall) for fast tests, or **real repos** (in-memory SQLite) for round-trip tests. Both are valuable; favour the fast variant for breadth, the round-trip for the happy path. +Two or three of these per major user flow. Not one per acceptance criterion — the lower tiers do that work. -### `interface` — Dioxus component tests +## Contract tests at seams with multiple adapters -```rust -// crates/interface/src/components/user_card.rs -#[cfg(test)] -mod tests { - use super::*; - use dioxus_testing::*; +When a module declares a port and has more than one adapter (e.g. an in-memory adapter for tests and an HTTP adapter for production), write a **contract test** that runs against every adapter: - #[test] - fn renders_user_email_when_present() { - let dom = render(UserCard { - user: User::sample_with_email("alice@example.com"), - }); - assert!(dom.text().contains("alice@example.com")); - } +```ts +// src/modules/payments/contract.test.ts +import { describe } from "bun:test"; +import { paymentsContract } from "./contract"; +import { makeInMemoryPayments } from "./adapters/in-memory"; +import { makeStripePayments } from "./adapters/stripe"; + +describe("in-memory payments adapter", () => paymentsContract(() => makeInMemoryPayments())); + +if (process.env.STRIPE_TEST_KEY) { + describe("stripe payments adapter", () => paymentsContract(() => makeStripePayments(env.STRIPE_TEST_KEY))); } ``` -Test props in, rendered text/structure out. Don't assert on internal hook order. +The `paymentsContract` function (in `src/modules/payments/contract.ts`) is a `describe`-builder that takes a factory and asserts the port's behavioural contract — nothing implementation-specific. If both adapters pass, you can swap them in production. -## Contract tests at boundaries - -Every port (trait in `application`) has a **contract test** that runs against every implementation. The trait-level test lives in `crates/application/tests/contracts/<port>.rs`; each `infrastructure` impl includes the test. +## Mirror-test ban -```rust -// crates/application/tests/contracts/user_repo.rs -pub fn user_repo_contract<R: UserRepo + Clone>(repo: R) { - // Exercises: save → by_id → modify → save → by_id again. - // Asserts only the trait's behavioural contract, not impl detail. -} +A mirror test's pass condition mirrors the implementation rather than the caller's contract. They pass for any code that compiles and break only when the implementation is rewritten — making the test useless during refactors. -// crates/infrastructure/tests/postgres_user_repo.rs -#[sqlx::test] -async fn obeys_user_repo_contract(pool: PgPool) { - user_repo_contract(PostgresUserRepo::new(pool)); -} +| ✗ Mirror | ✓ Behavioural | +|---|---| +| `expect(add(2, 3)).toBe(2 + 3);` | `expect(add(2, 3)).toBe(5);` | +| `expect(repo.save).toHaveBeenCalledWith(user);` | `expect(await useCase.execute(user)).toEqual(user);` | +| `expect(err.toString()).toBe("DomainError: NotFound");` | `expect(err).toBeInstanceOf(NotFoundError);` | +| `expect(JSON.stringify(result)).toMatchSnapshot();` *(on internal data)* | `expect(result.status).toBe("ok"); expect(result.total).toBe(42);` | -// crates/infrastructure/tests/sqlite_user_repo.rs -#[sqlx::test] -async fn obeys_user_repo_contract(pool: SqlitePool) { - user_repo_contract(SqliteUserRepo::new(pool)); -} -``` +If a test is hard to write without referencing implementation detail, the implementation is wrong (too coupled, too leaky) — fix the code, not the test. -If both impls pass the contract, you can swap them in production. This is the test seam Clean Architecture exists to give you. +## Stand-ins, not mocks -## Security test cases +A **stand-in** is a real implementation of a small interface; a **mock** is a recorded set of return values. Prefer stand-ins. -Each `interface` boundary gets at least these tests: +- `inMemoryDb()` — a Drizzle SQLite `:memory:` running the real schema. The same `db.query.orders.findFirst(...)` calls execute against it. Tests assert through the module's interface; the stand-in is invisible. +- `fixedClock(iso)` — `{ now: () => Date }` returning a fixed time. +- `stubOrders({ placeReturns: "ord-1" })` — a hand-written tiny impl of the `Orders` interface for HTTP-seam tests where the module under test is the route, not orders. -| Boundary | Test | -|---|---| -| HTTP routes that mutate | An unauthenticated request returns 401; an authenticated request as the wrong user returns 403; a malformed body returns 400 with no leaked internal detail. | -| HTTP routes that read | The same auth checks. Sensitive fields (password hashes, secrets) are never in response JSON. | -| SQL queries | A test passes a string with `'; DROP TABLE …` characters as a parameter. The query treats it as data. (`sqlx::query!` makes this impossible structurally — the test is documentation more than enforcement.) | -| Deserialization | A request body 10× the expected size is rejected at the parsing layer with 400, not OOM. | -| File uploads (if any) | A path-traversal filename (`../../etc/passwd`) is rejected. | +When you genuinely cannot stand in (true-external dependency: Stripe, Twilio), inject a mock. Restrict mocks to the **outermost** seam; never mock a module from inside its own tests. -## Mirror-test ban +## The runner: `bun test` -A mirror test is one whose pass condition mirrors the implementation rather than the caller's contract. They pass for any code that compiles and break only when the implementation is rewritten — making the test useless during refactors. +`bun test` is the default. It auto-discovers `*.test.ts`, runs in parallel processes, supports `describe`/`it`/`expect`, and is fast enough that watch-mode (`bun test --watch`) is the inner-loop tool. -| ✗ Mirror | ✓ Behavioural | -|---|---| -| `assert_eq!(add(2, 3), 2 + 3);` | `assert_eq!(add(2, 3), 5);` | -| `verify(repo.save_called_with(&user));` | `assert_eq!(use_case.execute(user.clone()).await?, user);` | -| `assert_eq!(format!("{:?}", err), "DomainError::NotFound");` | `assert!(matches!(err, DomainError::NotFound));` | +In CI: `bun test --coverage` if a coverage gate matters; the audit workflow keeps it simple and runs `bun test`. -If a test is hard to write without referencing implementation detail, the implementation is wrong (too coupled, too leaky) — fix the code, not the test. +## Security test cases -## The runner: `cargo-nextest` +Every HTTP seam gets at least these: -`cargo nextest run --workspace` is the default. It runs tests in parallel processes (faster than `cargo test`), retries flaky tests with the right config, and emits machine-readable output for CI. The CI workflow uses it; local `justfile` exposes `just test`. +| Boundary | Test | +|---|---| +| Mutating routes | Unauth → 401; authed-wrong-actor → 403; malformed body → 400 with no leaked internal detail. | +| Read routes | Same auth checks. Sensitive fields (password hashes, secret tokens) never in response JSON. | +| Query parameters | Zod-parsed; oversized inputs rejected with 400, not OOM. | +| File uploads (if any) | A path-traversal filename (`../../etc/passwd`) is rejected. | ## See also -- [`docs/architecture.md`](architecture.md) — the layer model the test matrix mirrors. -- [`docs/anti-slop.md`](anti-slop.md) — Rule of Three, mirror-test ban (cross-referenced here). -- Engineering plugin's `testing-strategy` skill — for the pyramid + general what-to-cover. +- [`architecture.md`](architecture.md) — the seam vocabulary; dependency categories drive test strategy. +- [`anti-slop.md`](anti-slop.md) — Rule of Three, mirror-test ban (cross-referenced here). +- Engineering plugin's `testing-strategy` skill — pyramid + what-to-cover for the bigger picture. From 00050294ddbf061a6a5678bad8b29fbd4bc945b2 Mon Sep 17 00:00:00 2001 From: Kennet Dahl Kusk <kennet.dahl.kusk@visma.com> Date: Sun, 17 May 2026 22:03:17 +0200 Subject: [PATCH 2/5] v5.0.0: replace Rust template with TypeScript template (Bun + Hono + Drizzle) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop templates/rust/dioxus-fullstack/ — 4-crate workspace + 4 apps (server/web/desktop/mobile) + 50+ files including a committed Cargo.lock. Desktop/mobile dropped entirely: no TypeScript story matches Dioxus's one-tree-three-renderers, and chasing it would contradict v5's "lightweight" goal. Add templates/typescript/ — 12 files for the simplest viable stack: - package.json — Bun + Hono + Drizzle ORM + Zod runtime; Biome + drizzle-kit + TypeScript dev. Audit script: biome check . && tsc --noEmit && bun audit --audit-level=high && bun test - tsconfig.json — strict + noUncheckedIndexedAccess; verbatimModuleSyntax. - biome.json — v2 schema; excludes src/db/migrations/ from formatter (Drizzle-generated meta files). - src/modules/greetings/ — the canary deep module: interface in index.ts, module-interface test exercising the real Drizzle SQLite schema. - src/http/routes/greetings.ts — Zod-parsed seam; HTTP-seam test via app.fetch with a stub Greetings. - src/main.ts — composition root (the only place adapters are wired in). - src/config.ts — Zod-validated env loading at boot. CI workflow (templates/shared/.github/workflows/code-et-audit.yml) rewritten for Bun + Biome + tsc + bun audit + bun test. Uses --audit-level=high so dev-only moderate advisories (e.g. esbuild via drizzle-kit) don't block merges. Smoke-tested end-to-end against /tmp/code-et-smoke3: - bun install + bun run db:generate + bun run audit all green - bun src/main.ts boots; curl /health → 200; curl POST /greetings → 201 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --- .../rust/dioxus-fullstack/.editorconfig | 12 - .../rust/dioxus-fullstack/.env.example | 6 - .../rust/dioxus-fullstack/.gitignore | 17 - .../rust/dioxus-fullstack/Cargo.lock | 7681 ----------------- .../rust/dioxus-fullstack/Cargo.toml | 55 - .../rust/dioxus-fullstack/Dioxus.toml | 9 - .../templates/rust/dioxus-fullstack/README.md | 90 - .../dioxus-fullstack/apps/desktop/Cargo.toml | 14 - .../dioxus-fullstack/apps/desktop/src/main.rs | 8 - .../dioxus-fullstack/apps/mobile/Cargo.toml | 14 - .../dioxus-fullstack/apps/mobile/src/main.rs | 8 - .../dioxus-fullstack/apps/server/Cargo.toml | 26 - .../dioxus-fullstack/apps/server/src/main.rs | 40 - .../rust/dioxus-fullstack/apps/web/Cargo.toml | 14 - .../dioxus-fullstack/apps/web/src/main.rs | 7 - .../rust/dioxus-fullstack/audit.toml | 17 - .../rust/dioxus-fullstack/clippy.toml | 6 - .../crates/application/Cargo.toml | 18 - .../crates/application/src/errors.rs | 12 - .../crates/application/src/lib.rs | 9 - .../crates/application/src/ports.rs | 15 - .../crates/application/src/use_cases.rs | 5 - .../application/src/use_cases/create_user.rs | 63 - .../application/src/use_cases/get_user.rs | 11 - .../dioxus-fullstack/crates/domain/Cargo.toml | 14 - .../crates/domain/src/errors.rs | 10 - .../dioxus-fullstack/crates/domain/src/lib.rs | 9 - .../crates/domain/src/user.rs | 74 - .../crates/infrastructure/Cargo.toml | 27 - .../crates/infrastructure/src/config.rs | 27 - .../crates/infrastructure/src/lib.rs | 7 - .../crates/infrastructure/src/repos.rs | 9 - .../src/repos/sqlite_user_repo.rs | 65 - .../crates/interface/Cargo.toml | 35 - .../crates/interface/src/components.rs | 7 - .../crates/interface/src/components/app.rs | 14 - .../interface/src/components/user_card.rs | 10 - .../crates/interface/src/http.rs | 6 - .../crates/interface/src/http/handlers.rs | 52 - .../crates/interface/src/http/router.rs | 22 - .../crates/interface/src/lib.rs | 9 - .../templates/rust/dioxus-fullstack/deny.toml | 72 - .../templates/rust/dioxus-fullstack/justfile | 68 - .../migrations/20250101000000_init.sql | 5 - .../rust/dioxus-fullstack/rust-toolchain.toml | 4 - .../rust/dioxus-fullstack/scripts/deploy.sh | 96 - .../rust/dioxus-fullstack/scripts/upload.sh | 67 - .../.github/workflows/code-et-audit.yml | 84 +- .../templates/shared/CLAUDE.md.template | 77 +- .../templates/shared/UPDATING.md | 44 +- .../shared/scripts/layer-deps-validator.sh | 64 - .../templates/typescript/.env.example | 5 + .../templates/typescript/.gitignore | 19 + .../templates/typescript/README.md | 36 + .../templates/typescript/biome.json | 31 + .../templates/typescript/drizzle.config.ts | 8 + .../templates/typescript/package.json | 29 + .../templates/typescript/src/config.ts | 17 + .../templates/typescript/src/db/index.ts | 12 + .../templates/typescript/src/db/migrate.ts | 8 + .../templates/typescript/src/db/schema.ts | 10 + .../templates/typescript/src/http/app.ts | 16 + .../src/http/routes/greetings.test.ts | 39 + .../typescript/src/http/routes/greetings.ts | 25 + .../templates/typescript/src/main.ts | 28 + .../src/modules/greetings/greetings.test.ts | 39 + .../typescript/src/modules/greetings/index.ts | 36 + .../templates/typescript/tsconfig.json | 22 + 68 files changed, 461 insertions(+), 9054 deletions(-) delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/.editorconfig delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/.env.example delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/.gitignore delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/Cargo.lock delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/Cargo.toml delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/Dioxus.toml delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/README.md delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/apps/desktop/Cargo.toml delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/apps/desktop/src/main.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/apps/mobile/Cargo.toml delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/apps/mobile/src/main.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/apps/server/Cargo.toml delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/apps/server/src/main.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/apps/web/Cargo.toml delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/apps/web/src/main.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/audit.toml delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/clippy.toml delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/application/Cargo.toml delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/errors.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/lib.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/ports.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/use_cases.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/use_cases/create_user.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/use_cases/get_user.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/Cargo.toml delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/src/errors.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/src/lib.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/src/user.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/Cargo.toml delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/config.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/lib.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/repos.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/repos/sqlite_user_repo.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/Cargo.toml delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/components.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/components/app.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/components/user_card.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/http.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/http/handlers.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/http/router.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/lib.rs delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/deny.toml delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/justfile delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/migrations/20250101000000_init.sql delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/rust-toolchain.toml delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/scripts/deploy.sh delete mode 100644 code-et-implementer/templates/rust/dioxus-fullstack/scripts/upload.sh delete mode 100755 code-et-implementer/templates/shared/scripts/layer-deps-validator.sh create mode 100644 code-et-implementer/templates/typescript/.env.example create mode 100644 code-et-implementer/templates/typescript/.gitignore create mode 100644 code-et-implementer/templates/typescript/README.md create mode 100644 code-et-implementer/templates/typescript/biome.json create mode 100644 code-et-implementer/templates/typescript/drizzle.config.ts create mode 100644 code-et-implementer/templates/typescript/package.json create mode 100644 code-et-implementer/templates/typescript/src/config.ts create mode 100644 code-et-implementer/templates/typescript/src/db/index.ts create mode 100644 code-et-implementer/templates/typescript/src/db/migrate.ts create mode 100644 code-et-implementer/templates/typescript/src/db/schema.ts create mode 100644 code-et-implementer/templates/typescript/src/http/app.ts create mode 100644 code-et-implementer/templates/typescript/src/http/routes/greetings.test.ts create mode 100644 code-et-implementer/templates/typescript/src/http/routes/greetings.ts create mode 100644 code-et-implementer/templates/typescript/src/main.ts create mode 100644 code-et-implementer/templates/typescript/src/modules/greetings/greetings.test.ts create mode 100644 code-et-implementer/templates/typescript/src/modules/greetings/index.ts create mode 100644 code-et-implementer/templates/typescript/tsconfig.json diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/.editorconfig b/code-et-implementer/templates/rust/dioxus-fullstack/.editorconfig deleted file mode 100644 index 0899ee9..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/.editorconfig +++ /dev/null @@ -1,12 +0,0 @@ -root = true - -[*] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -indent_style = space -indent_size = 4 -trim_trailing_whitespace = true - -[*.{md,yml,yaml,toml,json}] -indent_size = 2 diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/.env.example b/code-et-implementer/templates/rust/dioxus-fullstack/.env.example deleted file mode 100644 index 6ac2594..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -# Local dev — SQLite file. Copy to .env and edit. -DATABASE_URL=sqlite:./dev.db -BIND_ADDR=127.0.0.1:3000 - -# Production — switch to Postgres on GCP Cloud SQL via Cloud SQL Proxy + IAM. -# DATABASE_URL=postgres://USER@127.0.0.1:5432/DBNAME?sslmode=disable diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/.gitignore b/code-et-implementer/templates/rust/dioxus-fullstack/.gitignore deleted file mode 100644 index 3c9e710..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -/target -/.env -/dev.db -/dev.db-journal -/dist -/.dioxus -/Cargo.lock.bak -/.claude/audit-*.md - -# IDE -/.idea -/.vscode -*.swp -*.swo - -# macOS -.DS_Store diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/Cargo.lock b/code-et-implementer/templates/rust/dioxus-fullstack/Cargo.lock deleted file mode 100644 index 262b95f..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/Cargo.lock +++ /dev/null @@ -1,7681 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "aligned" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" -dependencies = [ - "as-slice", -] - -[[package]] -name = "aligned-vec" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" -dependencies = [ - "equator", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "application" -version = "0.1.0" -dependencies = [ - "async-trait", - "domain", - "mockall", - "serde", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" - -[[package]] -name = "arg_enum_proc_macro" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "as-slice" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" -dependencies = [ - "stable_deref_trait", -] - -[[package]] -name = "askama_escape" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df27b8d5ddb458c5fb1bbc1ce172d4a38c614a97d550b0ac89003897fb01de4" - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "async-tungstenite" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee88b4c88ac8c9ea446ad43498955750a4bbe64c4392f21ccfe5d952865e318f" -dependencies = [ - "atomic-waker", - "futures-core", - "futures-io", - "futures-task", - "futures-util", - "log", - "pin-project-lite", - "tungstenite 0.27.0", -] - -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "av-scenechange" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" -dependencies = [ - "aligned", - "anyhow", - "arg_enum_proc_macro", - "arrayvec", - "log", - "num-rational", - "num-traits", - "pastey", - "rayon", - "thiserror 2.0.18", - "v_frame", - "y4m", -] - -[[package]] -name = "av1-grain" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" -dependencies = [ - "anyhow", - "arrayvec", - "log", - "nom", - "num-rational", - "v_frame", -] - -[[package]] -name = "avif-serialize" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "axum" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" -dependencies = [ - "axum-core", - "axum-macros", - "base64", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "multer", - "percent-encoding", - "pin-project-lite", - "serde_core", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sha1", - "sync_wrapper", - "tokio", - "tokio-tungstenite 0.29.0", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-macros" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "base16" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - -[[package]] -name = "bit_field" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" -dependencies = [ - "serde_core", -] - -[[package]] -name = "bitstream-io" -version = "4.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" -dependencies = [ - "no_std_io2", -] - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - -[[package]] -name = "built" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" - -[[package]] -name = "bumpalo" -version = "3.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" - -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -dependencies = [ - "serde", -] - -[[package]] -name = "cairo-rs" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.11.1", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "cc" -version = "1.2.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "charset" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f927b07c74ba84c7e5fe4db2baeb3e996ab2688992e39ac68ce3220a677c7e" -dependencies = [ - "base64", - "encoding_rs", -] - -[[package]] -name = "chrono" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-link 0.2.1", -] - -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "cocoa" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" -dependencies = [ - "bitflags 2.11.1", - "block", - "cocoa-foundation", - "core-foundation 0.10.1", - "core-graphics 0.24.0", - "foreign-types 0.5.0", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" -dependencies = [ - "bitflags 2.11.1", - "block", - "core-foundation 0.10.1", - "core-graphics-types", - "objc", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "const-str" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0664d2867b4a32697dfe655557f5c3b187e9b605b38612a748e5ec99811d160" - -[[package]] -name = "const_format" -version = "0.2.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" -dependencies = [ - "const_format_proc_macros", - "konst", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "content_disposition" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc14a88e1463ddd193906285abe5c360c7e8564e05ccc5d501755f7fbc9ca9c" -dependencies = [ - "charset", -] - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "convert_case" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[package]] -name = "cookie_store" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" -dependencies = [ - "cookie", - "document-features", - "idna", - "log", - "publicsuffix", - "serde", - "serde_derive", - "serde_json", - "time", - "url", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" -dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.10.1", - "core-graphics-types", - "foreign-types 0.5.0", - "libc", -] - -[[package]] -name = "core-graphics" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" -dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.10.1", - "core-graphics-types", - "foreign-types 0.5.0", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.10.1", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "cssparser" -version = "0.29.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "matches", - "phf 0.10.1", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.117", -] - -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "dashmap" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - -[[package]] -name = "data-encoding" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.117", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case 0.10.0", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.117", - "unicode-xid", -] - -[[package]] -name = "desktop" -version = "0.1.0" -dependencies = [ - "dioxus", - "interface", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "dioxus" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daed3da3e7678d7267bc8523c607dbd19c970f4154cef30b9a58acf35e0a44d5" -dependencies = [ - "dioxus-asset-resolver", - "dioxus-cli-config", - "dioxus-config-macro", - "dioxus-config-macros", - "dioxus-core", - "dioxus-core-macro", - "dioxus-desktop", - "dioxus-fullstack", - "dioxus-hooks", - "dioxus-html", - "dioxus-server", - "dioxus-signals", - "dioxus-stores", - "dioxus-web", - "serde", - "subsecond", -] - -[[package]] -name = "dioxus-asset-resolver" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d28cf8859bac5946200df9dc0de8bdc3df60bec007e465dbd3684dbd05fc3ac7" -dependencies = [ - "dioxus-cli-config", - "http", - "infer", - "jni 0.21.1", - "js-sys", - "ndk", - "ndk-context", - "ndk-sys", - "percent-encoding", - "thiserror 2.0.18", - "tokio", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "dioxus-cli-config" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef4c22f0a239158f77966d7e7d39b637a10cb374cbd85f881704a336fd3f5c8" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "dioxus-config-macro" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a57abdf7bf60e22732a76588e75274ca4eb5d6399b716c735d187d743060b9" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "dioxus-config-macros" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b571d361abed46996a489e88ced2a335f0ca608305e238859a1a6cca1d85fb15" - -[[package]] -name = "dioxus-core" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1ff62d7073a4dd48670093469e2c099ef3c895345998a064554274eb39dc91" -dependencies = [ - "anyhow", - "const_format", - "dioxus-core-types", - "futures-channel", - "futures-util", - "generational-box", - "longest-increasing-subsequence", - "rustc-hash 2.1.2", - "rustversion", - "serde", - "slab", - "slotmap", - "subsecond", - "tracing", -] - -[[package]] -name = "dioxus-core-macro" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e22483ccaaf037e600683f25a6114754b9610e85e6fafb11adf45ccc2bd7116" -dependencies = [ - "convert_case 0.8.0", - "dioxus-rsx", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "dioxus-core-types" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a47a4908cc9680a27b314aa8c3832a517274f896a31b0cce7d7adee57eacbb" - -[[package]] -name = "dioxus-desktop" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3fc8ad2cb85e7810e35f69eccd31ad1cc715f2718f33b3e6b84ff5c794c6236" -dependencies = [ - "anyhow", - "async-trait", - "base64", - "bytes", - "cocoa", - "core-foundation 0.10.1", - "dioxus-asset-resolver", - "dioxus-cli-config", - "dioxus-core", - "dioxus-devtools", - "dioxus-document", - "dioxus-history", - "dioxus-hooks", - "dioxus-html", - "dioxus-interpreter-js", - "dioxus-signals", - "dunce", - "futures-channel", - "futures-util", - "generational-box", - "global-hotkey", - "image", - "infer", - "jni 0.21.1", - "lazy-js-bundle", - "libc", - "muda", - "ndk", - "ndk-context", - "ndk-sys", - "objc", - "objc_id", - "percent-encoding", - "rand 0.9.4", - "rfd", - "rustc-hash 2.1.2", - "serde", - "serde_json", - "signal-hook", - "slab", - "subtle", - "tao", - "thiserror 2.0.18", - "tokio", - "tracing", - "tray-icon", - "tungstenite 0.28.0", - "webbrowser", - "wry", -] - -[[package]] -name = "dioxus-devtools" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58abe580e75d1e6bbab658681b126d4a79df3e9c9fd66d0a0627206d1669f65d" -dependencies = [ - "dioxus-cli-config", - "dioxus-core", - "dioxus-devtools-types", - "dioxus-signals", - "futures-channel", - "futures-util", - "serde", - "serde_json", - "subsecond", - "thiserror 2.0.18", - "tracing", - "tungstenite 0.28.0", -] - -[[package]] -name = "dioxus-devtools-types" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5678f9f53962936765d0d767c13200bc6911a943492b8614d666425da62662d2" -dependencies = [ - "dioxus-core", - "serde", - "subsecond-types", -] - -[[package]] -name = "dioxus-document" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28eea4bea227f6eb0852ab67ed97a20474f234e8bb2a6abab17401d443aa746c" -dependencies = [ - "dioxus-core", - "dioxus-core-macro", - "dioxus-core-types", - "dioxus-html", - "futures-channel", - "futures-util", - "generational-box", - "lazy-js-bundle", - "serde", - "serde_json", - "tracing", -] - -[[package]] -name = "dioxus-fullstack" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75d9db5bb54c6305ed3f1cd71ea245a9935126f7586faf92950b40be76be5ae" -dependencies = [ - "anyhow", - "async-stream", - "async-tungstenite", - "axum", - "axum-core", - "base64", - "bytes", - "ciborium", - "const-str", - "const_format", - "content_disposition", - "derive_more 2.1.1", - "dioxus-asset-resolver", - "dioxus-cli-config", - "dioxus-core", - "dioxus-fullstack-core", - "dioxus-fullstack-macro", - "dioxus-hooks", - "dioxus-html", - "dioxus-signals", - "form_urlencoded", - "futures", - "futures-channel", - "futures-util", - "gloo-net", - "headers", - "http", - "http-body", - "http-body-util", - "js-sys", - "mime", - "pin-project", - "reqwest", - "rustversion", - "send_wrapper", - "serde", - "serde_json", - "serde_qs", - "serde_urlencoded", - "thiserror 2.0.18", - "tokio-util", - "tracing", - "tungstenite 0.27.0", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "xxhash-rust", -] - -[[package]] -name = "dioxus-fullstack-core" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a1d1f1ff784a78709f95b5021888bcd6bb2a4acf972213acd23c1a8a69701b1" -dependencies = [ - "anyhow", - "axum-core", - "base64", - "ciborium", - "dioxus-core", - "dioxus-document", - "dioxus-history", - "dioxus-hooks", - "dioxus-signals", - "futures-channel", - "futures-util", - "generational-box", - "http", - "inventory", - "parking_lot", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "tracing", -] - -[[package]] -name = "dioxus-fullstack-macro" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8927db4a9d939677a921eccf2ed36762f05b2e624787f2ef3c095fabc6e6c1c4" -dependencies = [ - "const_format", - "convert_case 0.8.0", - "proc-macro2", - "quote", - "syn 2.0.117", - "xxhash-rust", -] - -[[package]] -name = "dioxus-history" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e304644c2131e00c4a58ecd91e8f7f86dba007532732bd1a391506b75a974a9" -dependencies = [ - "dioxus-core", - "tracing", -] - -[[package]] -name = "dioxus-hooks" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50506e568a9786247993a29dd3b06fd84f30605312beea2b5f4e51f4ab543b0c" -dependencies = [ - "dioxus-core", - "dioxus-signals", - "futures-channel", - "futures-util", - "generational-box", - "rustversion", - "slab", - "tracing", -] - -[[package]] -name = "dioxus-html" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c52bdcc2355437ca8bd9986aa1c43c5439af459a2c16012d949b6a35092ae2" -dependencies = [ - "async-trait", - "bytes", - "dioxus-core", - "dioxus-core-macro", - "dioxus-core-types", - "dioxus-hooks", - "dioxus-html-internal-macro", - "enumset", - "euclid", - "futures-channel", - "futures-util", - "generational-box", - "keyboard-types", - "lazy-js-bundle", - "rustversion", - "serde", - "serde_json", - "serde_repr", - "tracing", -] - -[[package]] -name = "dioxus-html-internal-macro" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a48657a6a20a65894abba7e7876937e37e924ecaed63e8b654b364c4ffa133" -dependencies = [ - "convert_case 0.8.0", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "dioxus-interpreter-js" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56a36364418afcb181e19c67d9dbea766dbc2e6bfc0751365a60727ebe665362" -dependencies = [ - "dioxus-core", - "dioxus-core-types", - "dioxus-html", - "js-sys", - "lazy-js-bundle", - "rustc-hash 2.1.2", - "serde", - "sledgehammer_bindgen", - "sledgehammer_utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "dioxus-logger" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32720fddff9b73266502b15b2c36b139bd8fbfd5b85b67d29ba06d5b4ed60669" -dependencies = [ - "dioxus-cli-config", - "tracing", - "tracing-subscriber", - "tracing-wasm", -] - -[[package]] -name = "dioxus-router" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74cbe73ab4e815aacaa41726d25b84a2debacade3f35db357495dd1d5ed14949" -dependencies = [ - "dioxus-cli-config", - "dioxus-core", - "dioxus-core-macro", - "dioxus-fullstack-core", - "dioxus-history", - "dioxus-hooks", - "dioxus-html", - "dioxus-router-macro", - "dioxus-signals", - "percent-encoding", - "rustversion", - "tracing", - "url", -] - -[[package]] -name = "dioxus-router-macro" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b91aa613f584c2dd598436c33d22b94b85b132b449eae8f81e538300980222" -dependencies = [ - "base16", - "digest", - "proc-macro2", - "quote", - "sha2", - "slab", - "syn 2.0.117", -] - -[[package]] -name = "dioxus-rsx" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "767e37207d120b978643f5d1f68675984dfa68d44ceb4dab3d4337b36695d339" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "rustversion", - "syn 2.0.117", -] - -[[package]] -name = "dioxus-server" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba9bd5435d3b92a77f1f6ccb175b009efbb708c02247a786bd5bff4bf1253f3" -dependencies = [ - "anyhow", - "async-trait", - "axum", - "base64", - "bytes", - "chrono", - "ciborium", - "dashmap", - "dioxus-cli-config", - "dioxus-core", - "dioxus-core-macro", - "dioxus-devtools", - "dioxus-document", - "dioxus-fullstack-core", - "dioxus-history", - "dioxus-hooks", - "dioxus-html", - "dioxus-interpreter-js", - "dioxus-logger", - "dioxus-router", - "dioxus-signals", - "dioxus-ssr", - "enumset", - "futures", - "futures-channel", - "futures-util", - "generational-box", - "http", - "http-body-util", - "hyper", - "hyper-util", - "inventory", - "lru", - "parking_lot", - "pin-project", - "rustc-hash 2.1.2", - "serde", - "serde_json", - "serde_qs", - "subsecond", - "thiserror 2.0.18", - "tokio", - "tokio-tungstenite 0.28.0", - "tokio-util", - "tower", - "tower-http", - "tracing", - "tracing-futures", - "url", - "walkdir", -] - -[[package]] -name = "dioxus-signals" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef8e274214375597ad26a2e4407b681de4f876785724171ab605a51b272c51e" -dependencies = [ - "dioxus-core", - "futures-channel", - "futures-util", - "generational-box", - "parking_lot", - "rustc-hash 2.1.2", - "tracing", - "warnings", -] - -[[package]] -name = "dioxus-ssr" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aa8abccb3124dd16ff0e557c5659fbe00f8ae3b6a29c1be01e4e440fb69f7c7" -dependencies = [ - "askama_escape", - "dioxus-core", - "dioxus-core-types", - "rustc-hash 2.1.2", -] - -[[package]] -name = "dioxus-stores" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a6a8e92a6df3b3e875f268a2f20d2aeaf017fc952af86810b4d4e435b9a8c1" -dependencies = [ - "dioxus-core", - "dioxus-signals", - "dioxus-stores-macro", - "generational-box", -] - -[[package]] -name = "dioxus-stores-macro" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb44211a301fd4ddcb21b9c46807658690c2d5a52a7313393001090764ca1ab" -dependencies = [ - "convert_case 0.8.0", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "dioxus-web" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0e3fcfea97e9a1755d06a38c05e9d8b19fd09228ede36f2c9cfab69891c628" -dependencies = [ - "dioxus-cli-config", - "dioxus-core", - "dioxus-core-types", - "dioxus-devtools", - "dioxus-document", - "dioxus-fullstack-core", - "dioxus-history", - "dioxus-html", - "dioxus-interpreter-js", - "dioxus-signals", - "futures-channel", - "futures-util", - "generational-box", - "gloo-timers", - "js-sys", - "lazy-js-bundle", - "rustc-hash 2.1.2", - "send_wrapper", - "serde", - "serde-wasm-bindgen", - "serde_json", - "tracing", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", -] - -[[package]] -name = "dispatch2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags 2.11.1", - "block2", - "libc", - "objc2", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "dlopen2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - -[[package]] -name = "domain" -version = "0.1.0" -dependencies = [ - "serde", - "thiserror 2.0.18", - "uuid", -] - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "downcast" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" - -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" - -[[package]] -name = "dtoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -dependencies = [ - "serde", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "enumset" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b07a8dfbbbfc0064c0a6bdf9edcf966de6b1c33ce344bdeca3b41615452634" -dependencies = [ - "enumset_derive", -] - -[[package]] -name = "enumset_derive" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43e744e4ea338060faee68ed933e46e722fb7f3617e722a5772d7e856d8b3ce" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "equator" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" -dependencies = [ - "equator-macro", -] - -[[package]] -name = "equator-macro" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - -[[package]] -name = "euclid" -version = "0.22.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" -dependencies = [ - "num-traits", - "serde", -] - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "exr" -version = "1.74.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" -dependencies = [ - "bit_field", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", -] - -[[package]] -name = "fastrand" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" - -[[package]] -name = "fax" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared 0.3.1", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fragile" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - -[[package]] -name = "futures" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-executor" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkx11-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps", - "x11", -] - -[[package]] -name = "generational-box" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "155c81d7c7cae205289a26248ede524fdb1b7266b1fdb0a479b57bd3a6db2235" -dependencies = [ - "parking_lot", - "tracing", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "gethostname" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" -dependencies = [ - "rustix", - "windows-link 0.2.1", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "wasip2", - "wasip3", -] - -[[package]] -name = "gif" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "gio" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.11.1", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "global-hotkey" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7" -dependencies = [ - "crossbeam-channel", - "keyboard-types", - "objc2", - "objc2-app-kit", - "once_cell", - "thiserror 2.0.18", - "windows-sys 0.59.0", - "x11rb", - "xkeysym", -] - -[[package]] -name = "gloo-net" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" -dependencies = [ - "futures-channel", - "futures-core", - "futures-sink", - "gloo-utils", - "http", - "js-sys", - "pin-project", - "serde", - "serde_json", - "thiserror 1.0.69", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "gloo-utils" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" -dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" -dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk3-macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "h2" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.1.5", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", -] - -[[package]] -name = "hashbrown" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" - -[[package]] -name = "hashlink" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] - -[[package]] -name = "headers" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" -dependencies = [ - "base64", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "html5ever" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" -dependencies = [ - "log", - "mac", - "markup5ever", - "match_token", -] - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "http-range-header" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots 1.0.7", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-layer", - "tower-service", - "tracing", - "windows-registry", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.2", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "image" -version = "0.25.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" -dependencies = [ - "bytemuck", - "byteorder-lite", - "color_quant", - "exr", - "gif", - "image-webp", - "moxcms", - "num-traits", - "png 0.18.1", - "qoi", - "ravif", - "rayon", - "rgb", - "tiff", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "image-webp" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" -dependencies = [ - "byteorder-lite", - "quick-error", -] - -[[package]] -name = "imgref" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2" - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown 0.17.0", - "serde", - "serde_core", -] - -[[package]] -name = "infer" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" -dependencies = [ - "cfb", -] - -[[package]] -name = "infrastructure" -version = "0.1.0" -dependencies = [ - "anyhow", - "application", - "async-trait", - "domain", - "dotenvy", - "secrecy", - "serde", - "sqlx", - "tokio", - "uuid", -] - -[[package]] -name = "interface" -version = "0.1.0" -dependencies = [ - "application", - "axum", - "dioxus", - "domain", - "serde", - "serde_json", - "tokio", - "tower", - "tower-http", - "uuid", -] - -[[package]] -name = "interpolate_name" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "inventory" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" -dependencies = [ - "rustversion", -] - -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "javascriptcore-rs" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if", - "combine", - "jni-macros", - "jni-sys 0.4.1", - "log", - "simd_cesu8", - "thiserror 2.0.18", - "walkdir", - "windows-link 0.2.1", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn 2.0.117", -] - -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn 2.0.117", -] - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.97" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" -dependencies = [ - "cfg-if", - "futures-util", - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.11.1", - "serde", - "unicode-segmentation", -] - -[[package]] -name = "konst" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" -dependencies = [ - "konst_macro_rules", -] - -[[package]] -name = "konst_macro_rules" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" - -[[package]] -name = "kuchikiki" -version = "0.8.8-speedreader" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" -dependencies = [ - "cssparser", - "html5ever", - "indexmap", - "selectors", -] - -[[package]] -name = "lazy-js-bundle" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76317b9348f1c069d7e652722c3fa86683ec047ba4c1551bde1daa576ce3807e" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "lebe" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" - -[[package]] -name = "libappindicator" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" -dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", -] - -[[package]] -name = "libappindicator-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" -dependencies = [ - "gtk-sys", - "libloading 0.7.4", - "once_cell", -] - -[[package]] -name = "libc" -version = "0.2.186" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "libfuzzer-sys" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" -dependencies = [ - "arbitrary", - "cc", -] - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link 0.2.1", -] - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "libredox" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" -dependencies = [ - "bitflags 2.11.1", - "libc", - "plain", - "redox_syscall 0.7.5", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libxdo" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00333b8756a3d28e78def82067a377de7fa61b24909000aeaa2b446a948d14db" -dependencies = [ - "libxdo-sys", -] - -[[package]] -name = "libxdo-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212" -dependencies = [ - "libc", - "x11", -] - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "longest-increasing-subsequence" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86" - -[[package]] -name = "loop9" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" -dependencies = [ - "imgref", -] - -[[package]] -name = "lru" -version = "0.16.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" -dependencies = [ - "hashbrown 0.16.1", -] - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "markup5ever" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" -dependencies = [ - "log", - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "match_token" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - -[[package]] -name = "maybe-rayon" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" -dependencies = [ - "cfg-if", - "rayon", -] - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "memfd" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" -dependencies = [ - "rustix", -] - -[[package]] -name = "memmap2" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.61.2", -] - -[[package]] -name = "mobile" -version = "0.1.0" -dependencies = [ - "dioxus", - "interface", -] - -[[package]] -name = "mockall" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" -dependencies = [ - "cfg-if", - "downcast", - "fragile", - "mockall_derive", - "predicates", - "predicates-tree", -] - -[[package]] -name = "mockall_derive" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "moxcms" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" -dependencies = [ - "num-traits", - "pxfm", -] - -[[package]] -name = "muda" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9fec5a4e89860383d778d10563a605838f8f0b2f9303868937e5ff32e86177" -dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "libxdo", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "once_cell", - "png 0.17.16", - "thiserror 2.0.18", - "windows-sys 0.60.2", -] - -[[package]] -name = "multer" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "memchr", - "mime", - "spin", - "version_check", -] - -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.11.1", - "jni-sys 0.3.1", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle 0.6.2", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys 0.3.1", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "no_std_io2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" -dependencies = [ - "memchr", -] - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "nom" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" -dependencies = [ - "memchr", -] - -[[package]] -name = "noop_proc_macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.6", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-conv" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_enum" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" -dependencies = [ - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - -[[package]] -name = "objc2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" -dependencies = [ - "objc2-encode", - "objc2-exception-helper", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.11.1", - "block2", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.11.1", - "dispatch2", - "objc2", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.11.1", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-exception-helper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" -dependencies = [ - "cc", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.11.1", - "block2", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-web-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" -dependencies = [ - "bitflags 2.11.1", - "block2", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "openssl" -version = "0.10.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" -dependencies = [ - "bitflags 2.11.1", - "cfg-if", - "foreign-types 0.3.2", - "libc", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "openssl-sys" -version = "0.9.115" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "pango" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" -dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.18", - "smallvec", - "windows-link 0.2.1", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pastey" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_shared 0.8.0", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_macros", - "phf_shared 0.10.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.6", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.6", -] - -[[package]] -name = "phf_macros" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher 1.0.3", -] - -[[package]] -name = "pin-project" -version = "1.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pkg-config" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "png" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags 2.11.1", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "pollster" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "predicates" -version = "3.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" -dependencies = [ - "anstyle", - "predicates-core", -] - -[[package]] -name = "predicates-core" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" - -[[package]] -name = "predicates-tree" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" -dependencies = [ - "predicates-core", - "termtree", -] - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.117", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "version_check", -] - -[[package]] -name = "profiling" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" -dependencies = [ - "profiling-procmacros", -] - -[[package]] -name = "profiling-procmacros" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" -dependencies = [ - "quote", - "syn 2.0.117", -] - -[[package]] -name = "psl-types" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" - -[[package]] -name = "publicsuffix" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" -dependencies = [ - "idna", - "psl-types", -] - -[[package]] -name = "pxfm" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" - -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash 2.1.2", - "rustls", - "socket2", - "thiserror 2.0.18", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.4", - "ring", - "rustc-hash 2.1.2", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - -[[package]] -name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rav1e" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" -dependencies = [ - "aligned-vec", - "arbitrary", - "arg_enum_proc_macro", - "arrayvec", - "av-scenechange", - "av1-grain", - "bitstream-io", - "built", - "cfg-if", - "interpolate_name", - "itertools", - "libc", - "libfuzzer-sys", - "log", - "maybe-rayon", - "new_debug_unreachable", - "noop_proc_macro", - "num-derive", - "num-traits", - "paste", - "profiling", - "rand 0.9.4", - "rand_chacha 0.9.0", - "simd_helpers", - "thiserror 2.0.18", - "v_frame", - "wasm-bindgen", -] - -[[package]] -name = "ravif" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" -dependencies = [ - "avif-serialize", - "imgref", - "loop9", - "quick-error", - "rav1e", - "rayon", - "rgb", -] - -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "rayon" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.11.1", -] - -[[package]] -name = "redox_syscall" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" -dependencies = [ - "bitflags 2.11.1", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.18", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64", - "bytes", - "cookie", - "cookie_store", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "mime_guess", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots 1.0.7", -] - -[[package]] -name = "rfd" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220" -dependencies = [ - "block2", - "dispatch2", - "js-sys", - "libc", - "log", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "percent-encoding", - "pollster", - "raw-window-handle 0.6.2", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rgb" -version = "0.8.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags 2.11.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "secrecy" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" -dependencies = [ - "serde", - "zeroize", -] - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "selectors" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" -dependencies = [ - "bitflags 1.3.2", - "cssparser", - "derive_more 0.99.20", - "fxhash", - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc", - "smallvec", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" -dependencies = [ - "futures-core", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde-wasm-bindgen" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - -[[package]] -name = "serde_qs" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" -dependencies = [ - "percent-encoding", - "serde", - "thiserror 2.0.18", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "server" -version = "0.1.0" -dependencies = [ - "anyhow", - "axum", - "infrastructure", - "interface", - "secrecy", - "sqlx", - "tokio", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "servo_arc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - -[[package]] -name = "simd-adler32" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" - -[[package]] -name = "simd_cesu8" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" -dependencies = [ - "rustc_version", - "simdutf8", -] - -[[package]] -name = "simd_helpers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" -dependencies = [ - "quote", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "siphasher" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "sledgehammer_bindgen" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49e83e178d176459c92bc129cfd0958afac3ced925471b889b3a75546cfc4133" -dependencies = [ - "sledgehammer_bindgen_macro", - "wasm-bindgen", -] - -[[package]] -name = "sledgehammer_bindgen_macro" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb251b407f50028476a600541542b605bb864d35d9ee1de4f6cab45d88475e6d" -dependencies = [ - "quote", - "syn 2.0.117", -] - -[[package]] -name = "sledgehammer_utils" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "debdd4b83524961983cea3c55383b3910fd2f24fd13a188f5b091d2d504a61ae" -dependencies = [ - "rustc-hash 1.1.0", -] - -[[package]] -name = "slotmap" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" -dependencies = [ - "serde", - "version_check", -] - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -dependencies = [ - "serde", -] - -[[package]] -name = "socket2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "soup3" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" -dependencies = [ - "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", -] - -[[package]] -name = "soup3-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sqlx" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" -dependencies = [ - "base64", - "bytes", - "crc", - "crossbeam-queue", - "either", - "event-listener", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.15.5", - "hashlink", - "indexmap", - "log", - "memchr", - "once_cell", - "percent-encoding", - "rustls", - "serde", - "serde_json", - "sha2", - "smallvec", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tracing", - "url", - "uuid", - "webpki-roots 0.26.11", -] - -[[package]] -name = "sqlx-macros" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 2.0.117", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" -dependencies = [ - "dotenvy", - "either", - "heck 0.5.0", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn 2.0.117", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" -dependencies = [ - "atoi", - "base64", - "bitflags 2.11.1", - "byteorder", - "bytes", - "crc", - "digest", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand 0.8.6", - "rsa", - "serde", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" -dependencies = [ - "atoi", - "base64", - "bitflags 2.11.1", - "byteorder", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand 0.8.6", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" -dependencies = [ - "atoi", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "thiserror 2.0.18", - "tracing", - "url", - "uuid", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - -[[package]] -name = "subsecond" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a046b8921cd8a8b3bbeba6339d4ea8cc687653b30c73a158ed4d730d402199db" -dependencies = [ - "js-sys", - "libc", - "libloading 0.8.9", - "memfd", - "memmap2", - "serde", - "subsecond-types", - "thiserror 2.0.18", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "subsecond-types" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3c20eb7361e08d071ee249a687cddf16a1ae9d36f9bcd92481f31d6ac17eee" -dependencies = [ - "serde", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml", - "version-compare", -] - -[[package]] -name = "tao" -version = "0.34.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" -dependencies = [ - "bitflags 2.11.1", - "block2", - "core-foundation 0.10.1", - "core-graphics 0.25.0", - "crossbeam-channel", - "dispatch2", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni 0.21.1", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "once_cell", - "parking_lot", - "raw-window-handle 0.5.2", - "raw-window-handle 0.6.2", - "tao-macros", - "unicode-segmentation", - "url", - "windows", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.2", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - -[[package]] -name = "termtree" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tiff" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" -dependencies = [ - "fax", - "flate2", - "half", - "quick-error", - "weezl", - "zune-jpeg", -] - -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.52.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite 0.28.0", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite 0.29.0", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-io", - "futures-sink", - "futures-util", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.25.11+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" -dependencies = [ - "indexmap", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "winnow 1.0.2", -] - -[[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" -dependencies = [ - "winnow 1.0.2", -] - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" -dependencies = [ - "bitflags 2.11.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "http-range-header", - "httpdate", - "mime", - "mime_guess", - "percent-encoding", - "pin-project-lite", - "tokio", - "tokio-util", - "tower", - "tower-layer", - "tower-service", - "tracing", - "url", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "tracing-wasm" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" -dependencies = [ - "tracing", - "tracing-subscriber", - "wasm-bindgen", -] - -[[package]] -name = "tray-icon" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" -dependencies = [ - "crossbeam-channel", - "dirs", - "libappindicator", - "muda", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "once_cell", - "png 0.17.16", - "thiserror 2.0.18", - "windows-sys 0.60.2", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "tungstenite" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.9.4", - "sha1", - "thiserror 2.0.18", - "utf-8", -] - -[[package]] -name = "tungstenite" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "native-tls", - "rand 0.9.4", - "rustls", - "sha1", - "thiserror 2.0.18", - "utf-8", -] - -[[package]] -name = "tungstenite" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.9.4", - "sha1", - "thiserror 2.0.18", -] - -[[package]] -name = "typenum" -version = "1.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" - -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" - -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-normalization" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" - -[[package]] -name = "unicode-segmentation" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" -dependencies = [ - "getrandom 0.4.2", - "js-sys", - "serde_core", - "wasm-bindgen", -] - -[[package]] -name = "v_frame" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" -dependencies = [ - "aligned-vec", - "num-traits", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version-compare" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "warnings" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f68998838dab65727c9b30465595c6f7c953313559371ca8bf31759b3680ad" -dependencies = [ - "pin-project", - "tracing", - "warnings-macro", -] - -[[package]] -name = "warnings-macro" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59195a1db0e95b920366d949ba5e0d3fc0e70b67c09be15ce5abb790106b0571" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.3+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" -dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", -] - -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - -[[package]] -name = "wasm-bindgen" -version = "0.2.120" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.120" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.120" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 2.0.117", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.120" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - -[[package]] -name = "web" -version = "0.1.0" -dependencies = [ - "dioxus", - "interface", -] - -[[package]] -name = "web-sys" -version = "0.3.97" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webbrowser" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc95580916af1e68ff6a7be07446fc5db73ebf71cf092de939bbf5f7e189f72" -dependencies = [ - "core-foundation 0.10.1", - "jni 0.22.4", - "log", - "ndk-context", - "objc2", - "objc2-foundation", - "url", - "web-sys", -] - -[[package]] -name = "webkit2gtk" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup3", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", -] - -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.7", -] - -[[package]] -name = "webpki-roots" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webview2-com" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "webview2-com-sys" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" -dependencies = [ - "thiserror 2.0.18", - "windows", - "windows-core 0.61.2", -] - -[[package]] -name = "weezl" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" - -[[package]] -name = "whoami" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core 0.61.2", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-version" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.1", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "wry" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2" -dependencies = [ - "base64", - "block2", - "cookie", - "crossbeam-channel", - "dirs", - "dpi", - "dunce", - "gtk", - "html5ever", - "http", - "javascriptcore-rs", - "jni 0.21.1", - "kuchikiki", - "libc", - "ndk", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle 0.6.2", - "sha2", - "soup3", - "tao-macros", - "thiserror 2.0.18", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows", - "windows-core 0.61.2", - "windows-version", -] - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "x11rb" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" -dependencies = [ - "gethostname", - "rustix", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" - -[[package]] -name = "xkeysym" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" - -[[package]] -name = "xxhash-rust" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" - -[[package]] -name = "y4m" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" - -[[package]] -name = "yoke" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "zerofrom" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[package]] -name = "zune-core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" - -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "zune-jpeg" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" -dependencies = [ - "zune-core", -] diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/Cargo.toml b/code-et-implementer/templates/rust/dioxus-fullstack/Cargo.toml deleted file mode 100644 index 4fdaf78..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/Cargo.toml +++ /dev/null @@ -1,55 +0,0 @@ -[workspace] -resolver = "2" -members = [ - "crates/domain", - "crates/application", - "crates/infrastructure", - "crates/interface", - "apps/server", - "apps/desktop", - "apps/web", - "apps/mobile", -] - -[workspace.package] -edition = "2024" -rust-version = "1.85" -version = "0.1.0" -license = "MIT OR Apache-2.0" -repository = "https://github.com/{{owner}}/{{name}}" - -[workspace.dependencies] -# Layer crates (intentional: every app/crate references these by workspace path). -# `interface` ships with no default features — each app opts into its render target -# (server / desktop / web / mobile) explicitly. -domain = { path = "crates/domain" } -application = { path = "crates/application" } -infrastructure = { path = "crates/infrastructure" } -interface = { path = "crates/interface", default-features = false } - -# Shared third-party — pinned to minor; patches via cargo update -serde = { version = "1", features = ["derive"] } -serde_json = "1" -thiserror = "2" -uuid = { version = "1", features = ["v4", "serde"] } -time = { version = "0.3", features = ["serde", "macros"] } -async-trait = "0.1" -anyhow = "1" -mockall = "0.13" -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } -sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio-rustls", "macros", "migrate"] } -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } -secrecy = { version = "0.10", features = ["serde"] } -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -dotenvy = "0.15" - -dioxus = { version = "0.7", default-features = false, features = ["macro", "html", "hooks", "signals", "launch"] } -axum = "0.8" -tower = "0.5" -tower-http = { version = "0.6", features = ["trace"] } - -[profile.release] -lto = "thin" -codegen-units = 1 -strip = "symbols" diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/Dioxus.toml b/code-et-implementer/templates/rust/dioxus-fullstack/Dioxus.toml deleted file mode 100644 index 094b4aa..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/Dioxus.toml +++ /dev/null @@ -1,9 +0,0 @@ -[application] -name = "{{name}}" - -[web.app] -title = "{{name}}" - -[web.watcher] -reload_html = true -watch_path = ["crates/interface/src", "apps/web/src"] diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/README.md b/code-et-implementer/templates/rust/dioxus-fullstack/README.md deleted file mode 100644 index e7785f5..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# {{name}} - -Pure-Rust full-stack project bootstrapped with [code-et](https://github.com/Emerging-Tech-Visma/code-et) v4.0+. - -**Stack:** axum + sqlx + Dioxus 0.7+ + tokio. **Frontend:** Dioxus on web, desktop, and mobile from one component tree. **Database:** SQLite for local, PostgreSQL on GCP Cloud SQL for production. - -## Layout - -``` -crates/ - domain/ Entities, value objects, errors. Pure logic. No workspace deps. - application/ Use cases + ports (traits). Depends on domain. - infrastructure/ sqlx repos, HTTP clients, config. Depends on application + domain. - interface/ Dioxus components + axum handlers. Depends on application + domain. -apps/ - server/ axum + dioxus-fullstack SSR server. - desktop/ dioxus-desktop window. - web/ dioxus-web (WASM). - mobile/ dioxus-mobile (iOS + Android). -migrations/ sqlx::migrate! source. Forward-only. SQLite + Postgres compatible. -.github/workflows/ CI gate (clippy, layer validator, machete, audit, deny, nextest). -scripts/ layer-deps-validator.sh. -``` - -The Dependency Rule is enforced by `Cargo.toml` workspace deps. Adding `infrastructure` to `crates/domain/Cargo.toml` makes `cargo build` fail. The validator script is defence-in-depth. - -## Quick start - -```bash -cp .env.example .env -just db-migrate -just run-server # axum + dioxus-fullstack on :3000 -just run-web # dioxus-web (WASM) on :8080 -just run-desktop # dioxus-desktop window -just test -just audit # local mirror of the CI gate -``` - -## Required tools (one-time) - -```bash -cargo install cargo-machete cargo-audit cargo-deny cargo-nextest sqlx-cli -cargo install dioxus-cli -# optional, requires nightly toolchain -cargo install cargo-udeps -``` - -If `/code:start` was invoked with `--install-tools`, these are already on your machine. - -## Daily workflow - -``` -# Bug -/code:fix "<one-line bug>" # intake → Task Brief → you implement → /commit-push-pr - -# Feature -/code:plan "<idea>" # refined brief → PRD on disk → vertical-slice tasks -/code:ship # parallel worktree agents + post-merge audit (1 auto-retry) -/code:review # full audit + diff review (pre-merge gate) -/commit-push-pr # PR; CI runs the same audit pipeline -``` - -Each task carries `metadata.layer ∈ {domain, application, infrastructure, interface, chore}`. Vertical slices may span layers; each *file* belongs to exactly one. Imports point inward. - -## Deploy & upload — always via scripts - -**Discipline:** never deploy or upload {{name}} via raw `cargo run`, `docker push`, `gcloud run deploy`, `gsutil cp`, `scp`, or any other ad-hoc command typed into a shell. All paths route through: - -``` -just deploy staging # bash scripts/deploy.sh staging -just deploy prod # bash scripts/deploy.sh prod -just upload web staging # bash scripts/upload.sh web staging -just upload desktop prod # bash scripts/upload.sh desktop prod -``` - -The scripts are starting points — host-specific commands are marked `# TODO:` blocks (Cloud Run / GKE / Fly / your VM). Fill them in once for your project; after that, every deploy is: - -1. **Pre-flight:** clean tree, audit gate green, required tools on PATH. -2. **Build:** container image tagged with `git rev-parse --short HEAD`. -3. **Migrate:** `sqlx migrate run` against the target DB (Cloud SQL Auth Proxy + IAM token in prod). -4. **Roll out:** push image, update Cloud Run revision (or your equivalent). -5. **Smoke check:** curl `/healthz`, expect 200. - -This is the only sane way to ship Rust workspaces with `sqlx::query!` macros — the build needs the right `DATABASE_URL` (or offline `sqlx-data.json`), and the deploy needs migrations to land before the new image gets traffic. A script enforces that order; ad-hoc commands forget it. - -## Doctrine - -- [`docs/architecture.md`](https://github.com/Emerging-Tech-Visma/code-et/blob/main/code-et-implementer/docs/architecture.md) — Clean Architecture details -- [`docs/anti-slop.md`](https://github.com/Emerging-Tech-Visma/code-et/blob/main/code-et-implementer/docs/anti-slop.md) — what the audit catches -- [`docs/testing.md`](https://github.com/Emerging-Tech-Visma/code-et/blob/main/code-et-implementer/docs/testing.md) — per-layer test matrix diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/apps/desktop/Cargo.toml b/code-et-implementer/templates/rust/dioxus-fullstack/apps/desktop/Cargo.toml deleted file mode 100644 index 9a2002d..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/apps/desktop/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "desktop" -edition.workspace = true -rust-version.workspace = true -version.workspace = true -license.workspace = true - -[[bin]] -name = "desktop" -path = "src/main.rs" - -[dependencies] -interface = { workspace = true, features = ["desktop"] } -dioxus = { workspace = true, features = ["desktop"] } diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/apps/desktop/src/main.rs b/code-et-implementer/templates/rust/dioxus-fullstack/apps/desktop/src/main.rs deleted file mode 100644 index cc02d4a..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/apps/desktop/src/main.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Desktop renderer — Dioxus 0.7 desktop. The composition root for desktop is trivial: -//! the `interface::components::App` is rendered by the dioxus-desktop launcher. - -use interface::components::App; - -fn main() { - dioxus::launch(App); -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/apps/mobile/Cargo.toml b/code-et-implementer/templates/rust/dioxus-fullstack/apps/mobile/Cargo.toml deleted file mode 100644 index 3e07f4f..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/apps/mobile/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "mobile" -edition.workspace = true -rust-version.workspace = true -version.workspace = true -license.workspace = true - -[[bin]] -name = "mobile" -path = "src/main.rs" - -[dependencies] -interface = { workspace = true, features = ["mobile"] } -dioxus = { workspace = true, features = ["mobile"] } diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/apps/mobile/src/main.rs b/code-et-implementer/templates/rust/dioxus-fullstack/apps/mobile/src/main.rs deleted file mode 100644 index 4d2d709..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/apps/mobile/src/main.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Mobile (iOS + Android) renderer — Dioxus 0.7 mobile. Build with `dx build --platform mobile`. -//! Mobile is best-effort: requires xcode (iOS) or android-ndk locally; CI builds web + desktop only. - -use interface::components::App; - -fn main() { - dioxus::launch(App); -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/apps/server/Cargo.toml b/code-et-implementer/templates/rust/dioxus-fullstack/apps/server/Cargo.toml deleted file mode 100644 index bfad322..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/apps/server/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "server" -edition.workspace = true -rust-version.workspace = true -version.workspace = true -license.workspace = true - -[[bin]] -name = "server" -path = "src/main.rs" - -[dependencies] -infrastructure = { workspace = true } -interface = { workspace = true, features = ["server"] } -axum = { workspace = true } -tokio = { workspace = true } -sqlx = { workspace = true, features = ["sqlite", "postgres"] } -anyhow = { workspace = true } -tracing = { workspace = true } -tracing-subscriber = { workspace = true } -secrecy = { workspace = true } -# As the composition root grows to instantiate use cases directly, add: -# domain = { workspace = true } -# application = { workspace = true } -# These are removed for now to keep the audit gate clean — `interface::http::router` -# already wires the sample CreateUser path. diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/apps/server/src/main.rs b/code-et-implementer/templates/rust/dioxus-fullstack/apps/server/src/main.rs deleted file mode 100644 index b5304e0..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/apps/server/src/main.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Composition root for the axum + dioxus-fullstack server. -//! Wires `infrastructure` impls into `interface` ports. - -use std::sync::Arc; - -use anyhow::Context; -use secrecy::ExposeSecret; -use sqlx::SqlitePool; -use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; - -use infrastructure::{config::Config, repos::SqliteUserRepo}; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - init_tracing(); - let cfg = Config::from_env().context("config")?; - - let pool = SqlitePool::connect(cfg.database_url.expose_secret()) - .await - .context("db connect")?; - sqlx::migrate!("../../migrations") - .run(&pool) - .await - .context("migrate")?; - - let repo = Arc::new(SqliteUserRepo::new(pool)); - let app = interface::http::router(repo); - - let listener = tokio::net::TcpListener::bind(&cfg.bind_addr).await?; - tracing::info!(addr = %cfg.bind_addr, "server listening"); - axum::serve(listener, app).await?; - Ok(()) -} - -fn init_tracing() { - tracing_subscriber::registry() - .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"))) - .with(tracing_subscriber::fmt::layer()) - .init(); -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/apps/web/Cargo.toml b/code-et-implementer/templates/rust/dioxus-fullstack/apps/web/Cargo.toml deleted file mode 100644 index 7bef7a3..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/apps/web/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "web" -edition.workspace = true -rust-version.workspace = true -version.workspace = true -license.workspace = true - -[[bin]] -name = "web" -path = "src/main.rs" - -[dependencies] -interface = { workspace = true, features = ["web"] } -dioxus = { workspace = true, features = ["web"] } diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/apps/web/src/main.rs b/code-et-implementer/templates/rust/dioxus-fullstack/apps/web/src/main.rs deleted file mode 100644 index 8559496..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/apps/web/src/main.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Web (WASM) renderer — Dioxus 0.7 web. Build with `dx build --platform web`. - -use interface::components::App; - -fn main() { - dioxus::launch(App); -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/audit.toml b/code-et-implementer/templates/rust/dioxus-fullstack/audit.toml deleted file mode 100644 index 7e36ad7..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/audit.toml +++ /dev/null @@ -1,17 +0,0 @@ -# cargo-audit configuration. -# Format: https://docs.rs/cargo-audit/latest/cargo_audit/config/struct.AuditConfig.html -# -# Each ignore must include a rationale comment. Re-evaluate every time we -# bump deps. Unmaintained / unsound warnings (gtk-rs 0.18 chain via -# dioxus-desktop, etc.) exit 0 by default and don't need ignores — they -# show up in the cargo-audit output as informational warnings. - -[advisories] -ignore = [ - # RUSTSEC-2023-0071: rsa 0.9.x Marvin Attack (timing side-channel, no fixed upgrade). - # Reason: pulled in transitively by sqlx-macros 0.8.x, which compiles all DB - # backend support for compile-time `query!` checking. We use sqlite + postgres - # only — the rsa crate is never reached at runtime. Re-check on every sqlx bump - # and remove this ignore once sqlx-macros decouples mysql. - "RUSTSEC-2023-0071", -] diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/clippy.toml b/code-et-implementer/templates/rust/dioxus-fullstack/clippy.toml deleted file mode 100644 index 3e6d6fc..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/clippy.toml +++ /dev/null @@ -1,6 +0,0 @@ -# code-et anti-slop thresholds. -# See code-et-implementer/docs/anti-slop.md §"Complexity hotspots". -cognitive-complexity-threshold = 15 -type-complexity-threshold = 250 -too-many-arguments-threshold = 6 -too-many-lines-threshold = 100 diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/Cargo.toml b/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/Cargo.toml deleted file mode 100644 index 887214f..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "application" -edition.workspace = true -rust-version.workspace = true -version.workspace = true -license.workspace = true - -[dependencies] -domain = { workspace = true } -async-trait = { workspace = true } -thiserror = { workspace = true } -serde = { workspace = true } -# Add `anyhow = { workspace = true }` if you need glue-error wrapping in a -# use case (rare — application errors should be `thiserror`-derived). - -[dev-dependencies] -mockall = { workspace = true } -tokio = { workspace = true } diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/errors.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/errors.rs deleted file mode 100644 index bbdbbd9..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/errors.rs +++ /dev/null @@ -1,12 +0,0 @@ -use thiserror::Error; - -use domain::DomainError; - -#[derive(Debug, Error)] -pub enum ApplicationError { - #[error(transparent)] - Domain(#[from] DomainError), - - #[error("repository error: {0}")] - Repo(String), -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/lib.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/lib.rs deleted file mode 100644 index 1a71fda..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Application layer — use cases + ports (traits). Orchestrates domain. -//! -//! See `code-et-implementer/docs/architecture.md` §"Crossing boundaries" for DTO/DIP rules. - -pub mod errors; -pub mod ports; -pub mod use_cases; - -pub use errors::ApplicationError; diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/ports.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/ports.rs deleted file mode 100644 index 0e071b1..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/ports.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Ports — traits implemented by `infrastructure`, used by `use_cases`. -//! `mockall` generates fakes for tests. - -use async_trait::async_trait; - -use domain::{User, UserId}; - -use crate::ApplicationError; - -#[cfg_attr(test, mockall::automock)] -#[async_trait] -pub trait UserRepo: Send + Sync { - async fn save(&self, user: &User) -> Result<(), ApplicationError>; - async fn by_id(&self, id: UserId) -> Result<Option<User>, ApplicationError>; -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/use_cases.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/use_cases.rs deleted file mode 100644 index afac66a..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/use_cases.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod create_user; -pub mod get_user; - -pub use create_user::{CreateUserInput, CreateUserOutput, create_user}; -pub use get_user::get_user; diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/use_cases/create_user.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/use_cases/create_user.rs deleted file mode 100644 index 04e1dc4..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/use_cases/create_user.rs +++ /dev/null @@ -1,63 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use domain::{Email, User}; - -use crate::{ApplicationError, ports::UserRepo}; - -#[derive(Debug, Clone, Deserialize)] -pub struct CreateUserInput { - pub email: String, -} - -#[derive(Debug, Clone, Serialize)] -pub struct CreateUserOutput { - pub user: User, -} - -/// Create a new user. Pure orchestration over `domain` + a `UserRepo` port. -pub async fn create_user<R: UserRepo + ?Sized>( - repo: &R, - input: CreateUserInput, -) -> Result<CreateUserOutput, ApplicationError> { - let email = Email::new(input.email)?; - let user = User::new(email); - repo.save(&user).await?; - Ok(CreateUserOutput { user }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ports::MockUserRepo; - - #[tokio::test] - async fn creates_user_with_valid_email() { - let mut repo = MockUserRepo::new(); - repo.expect_save().times(1).returning(|_| Ok(())); - - let out = create_user( - &repo, - CreateUserInput { - email: "alice@example.com".into(), - }, - ) - .await - .unwrap(); - - assert_eq!(out.user.email.as_str(), "alice@example.com"); - } - - #[tokio::test] - async fn rejects_invalid_email() { - let repo = MockUserRepo::new(); - let err = create_user( - &repo, - CreateUserInput { - email: "not-an-email".into(), - }, - ) - .await - .unwrap_err(); - assert!(matches!(err, ApplicationError::Domain(_))); - } -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/use_cases/get_user.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/use_cases/get_user.rs deleted file mode 100644 index 32b63db..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/application/src/use_cases/get_user.rs +++ /dev/null @@ -1,11 +0,0 @@ -use domain::{User, UserId}; - -use crate::{ApplicationError, ports::UserRepo}; - -/// Fetch a user by id. Pure orchestration over `domain` + a `UserRepo` port. -pub async fn get_user<R: UserRepo + ?Sized>( - repo: &R, - id: UserId, -) -> Result<Option<User>, ApplicationError> { - repo.by_id(id).await -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/Cargo.toml b/code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/Cargo.toml deleted file mode 100644 index 6334eca..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "domain" -edition.workspace = true -rust-version.workspace = true -version.workspace = true -license.workspace = true - -# Dependency Rule: domain has NO workspace dependencies. Pure logic only. -[dependencies] -serde = { workspace = true } -thiserror = { workspace = true } -uuid = { workspace = true } -# Add `time = { workspace = true }` when you introduce a value object that -# needs DateTime/Duration. The workspace already pins it. diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/src/errors.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/src/errors.rs deleted file mode 100644 index 1655bbd..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/src/errors.rs +++ /dev/null @@ -1,10 +0,0 @@ -use thiserror::Error; - -#[derive(Debug, Error, PartialEq, Eq)] -pub enum DomainError { - #[error("invalid email: {0}")] - InvalidEmail(String), - - #[error("user not found")] - UserNotFound, -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/src/lib.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/src/lib.rs deleted file mode 100644 index fec119b..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Domain layer — entities, value objects, errors. Pure logic. Zero workspace deps. -//! -//! See `code-et-implementer/docs/architecture.md` §"Layer model" for the full rules. - -pub mod errors; -pub mod user; - -pub use errors::DomainError; -pub use user::{Email, User, UserId}; diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/src/user.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/src/user.rs deleted file mode 100644 index 97c0fdb..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/domain/src/user.rs +++ /dev/null @@ -1,74 +0,0 @@ -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -use crate::DomainError; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct UserId(pub Uuid); - -impl UserId { - pub fn new() -> Self { - Self(Uuid::new_v4()) - } -} - -impl Default for UserId { - fn default() -> Self { - Self::new() - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Email(String); - -impl Email { - pub fn new(raw: impl Into<String>) -> Result<Self, DomainError> { - let raw = raw.into(); - if raw.contains('@') && raw.len() <= 254 { - Ok(Self(raw)) - } else { - Err(DomainError::InvalidEmail(raw)) - } - } - - pub fn as_str(&self) -> &str { - &self.0 - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct User { - pub id: UserId, - pub email: Email, -} - -impl User { - pub fn new(email: Email) -> Self { - Self { - id: UserId::new(), - email, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn email_rejects_missing_at_sign() { - assert!(Email::new("not-an-email").is_err()); - } - - #[test] - fn email_accepts_simple_address() { - let e = Email::new("user@example.com").unwrap(); - assert_eq!(e.as_str(), "user@example.com"); - } - - #[test] - fn email_rejects_oversized_input() { - let huge = "a".repeat(300) + "@example.com"; - assert!(Email::new(huge).is_err()); - } -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/Cargo.toml b/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/Cargo.toml deleted file mode 100644 index e189ac6..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "infrastructure" -edition.workspace = true -rust-version.workspace = true -version.workspace = true -license.workspace = true - -[dependencies] -domain = { workspace = true } -application = { workspace = true } -async-trait = { workspace = true } -anyhow = { workspace = true } -serde = { workspace = true } -secrecy = { workspace = true } -dotenvy = { workspace = true } -uuid = { workspace = true } -sqlx = { workspace = true, features = ["sqlite", "postgres", "uuid"] } -# Add when first used: -# thiserror — for explicit error enums in adapters (use case errors should be -# thiserror-derived; adapter-internal can use anyhow). -# serde_json — for HTTP client request/response bodies. -# tokio = { workspace = true } — for async helpers beyond what sqlx exposes. -# tracing — for adapter-level instrumentation. -# reqwest — for HTTP-client adapters (http_clients/). - -[dev-dependencies] -tokio = { workspace = true } diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/config.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/config.rs deleted file mode 100644 index d3e9cc8..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/config.rs +++ /dev/null @@ -1,27 +0,0 @@ -use secrecy::SecretString; -use serde::Deserialize; - -/// Application config loaded once at boot. Secrets wrapped in `SecretString` -/// so they redact in `Debug` and `tracing` output. -#[derive(Debug, Clone, Deserialize)] -pub struct Config { - pub database_url: SecretString, - pub bind_addr: String, -} - -impl Config { - /// Load from environment. In production: secrets come from GCP Secret Manager - /// via the deployment workflow, exported into the env. Locally: `dotenvy` for `.env`. - pub fn from_env() -> anyhow::Result<Self> { - if cfg!(debug_assertions) { - // Best-effort .env load; ignore missing file in dev. - let _ = dotenvy::dotenv(); - } - Ok(Self { - database_url: std::env::var("DATABASE_URL") - .map(SecretString::from) - .map_err(|_| anyhow::anyhow!("DATABASE_URL is required"))?, - bind_addr: std::env::var("BIND_ADDR").unwrap_or_else(|_| "127.0.0.1:3000".into()), - }) - } -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/lib.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/lib.rs deleted file mode 100644 index ea2e0a2..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Infrastructure layer — adapters: sqlx repos, HTTP clients, config. -//! Implements `application` ports. Composition lives in `apps/<name>/main.rs`. -//! -//! See `code-et-implementer/docs/architecture.md` §"Database" for the sqlx rules. - -pub mod config; -pub mod repos; diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/repos.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/repos.rs deleted file mode 100644 index 7212dd3..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/repos.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Repository implementations. The only place `sqlx` is imported. -//! -//! Doctrine: parameter binding is mandatory; `query!` / `query_as!` macros are the -//! goal for compile-time schema check. The template ships with `query_as` (runtime-checked) -//! so it compiles before `cargo sqlx prepare` is run; migrate per repo as the schema stabilises. - -pub mod sqlite_user_repo; - -pub use sqlite_user_repo::SqliteUserRepo; diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/repos/sqlite_user_repo.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/repos/sqlite_user_repo.rs deleted file mode 100644 index 03138de..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/infrastructure/src/repos/sqlite_user_repo.rs +++ /dev/null @@ -1,65 +0,0 @@ -use async_trait::async_trait; -use sqlx::SqlitePool; - -use application::{ApplicationError, ports::UserRepo}; -use domain::{Email, User, UserId}; - -pub struct SqliteUserRepo { - pool: SqlitePool, -} - -impl SqliteUserRepo { - pub fn new(pool: SqlitePool) -> Self { - Self { pool } - } -} - -#[async_trait] -impl UserRepo for SqliteUserRepo { - async fn save(&self, user: &User) -> Result<(), ApplicationError> { - // Goal: migrate to `sqlx::query!` once `cargo sqlx prepare` has run against the schema. - sqlx::query("INSERT INTO users (id, email) VALUES (?1, ?2)") - .bind(user.id.0.to_string()) - .bind(user.email.as_str()) - .execute(&self.pool) - .await - .map_err(|e| ApplicationError::Repo(e.to_string()))?; - Ok(()) - } - - async fn by_id(&self, id: UserId) -> Result<Option<User>, ApplicationError> { - let row: Option<(String, String)> = - sqlx::query_as("SELECT id, email FROM users WHERE id = ?1") - .bind(id.0.to_string()) - .fetch_optional(&self.pool) - .await - .map_err(|e| ApplicationError::Repo(e.to_string()))?; - - match row { - Some((id_str, email_str)) => { - let parsed_id = uuid::Uuid::parse_str(&id_str) - .map_err(|e: uuid::Error| ApplicationError::Repo(e.to_string()))?; - let email = Email::new(email_str)?; - Ok(Some(User { - id: UserId(parsed_id), - email, - })) - } - None => Ok(None), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[sqlx::test(migrations = "../../migrations")] - async fn round_trips_a_user(pool: SqlitePool) { - let repo = SqliteUserRepo::new(pool); - let user = User::new(Email::new("alice@example.com").unwrap()); - repo.save(&user).await.unwrap(); - let fetched = repo.by_id(user.id).await.unwrap().unwrap(); - assert_eq!(fetched, user); - } -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/Cargo.toml b/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/Cargo.toml deleted file mode 100644 index 90fbfc8..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "interface" -edition.workspace = true -rust-version.workspace = true -version.workspace = true -license.workspace = true - -[dependencies] -domain = { workspace = true } -application = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -uuid = { workspace = true } - -# UI -dioxus = { workspace = true } - -# HTTP (axum) — gated to keep WASM builds light -axum = { workspace = true, optional = true } -tower = { workspace = true, optional = true } -tower-http = { workspace = true, optional = true } -tokio = { workspace = true, optional = true } - -# cargo-machete misses feature-gated optional deps; tower/tower-http/tokio -# are wired into the `server` feature even when not directly imported by -# the current handler set. Remove ignores once a handler imports them. -[package.metadata.cargo-machete] -ignored = ["tokio", "tower", "tower-http"] - -[features] -default = ["server"] -server = ["dep:axum", "dep:tower", "dep:tower-http", "dep:tokio", "dioxus/fullstack"] -desktop = ["dioxus/desktop"] -web = ["dioxus/web"] -mobile = ["dioxus/mobile"] diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/components.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/components.rs deleted file mode 100644 index f428cf7..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/components.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Dioxus components — render on web, desktop, mobile from one tree. - -pub mod app; -pub mod user_card; - -pub use app::App; -pub use user_card::UserCard; diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/components/app.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/components/app.rs deleted file mode 100644 index 651ee25..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/components/app.rs +++ /dev/null @@ -1,14 +0,0 @@ -use dioxus::prelude::*; - -use crate::components::UserCard; - -#[component] -pub fn App() -> Element { - rsx! { - div { class: "app", - h1 { "{{name}}" } - p { "Pure-Rust full-stack — Dioxus 0.7+ on web, desktop, mobile." } - UserCard { email: "alice@example.com".to_string() } - } - } -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/components/user_card.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/components/user_card.rs deleted file mode 100644 index bae4c86..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/components/user_card.rs +++ /dev/null @@ -1,10 +0,0 @@ -use dioxus::prelude::*; - -#[component] -pub fn UserCard(email: String) -> Element { - rsx! { - article { class: "user-card", - span { class: "user-card__email", "{email}" } - } - } -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/http.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/http.rs deleted file mode 100644 index 3086ae1..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/http.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! axum handlers — the HTTP boundary. Composition wires repos at startup. - -pub mod handlers; -pub mod router; - -pub use router::router; diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/http/handlers.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/http/handlers.rs deleted file mode 100644 index 85f1778..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/http/handlers.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::sync::Arc; - -use axum::{ - Json, - extract::{Path, State}, - http::StatusCode, -}; -use serde::Deserialize; - -use application::{ - ApplicationError, - ports::UserRepo, - use_cases::{CreateUserInput, create_user, get_user}, -}; -use domain::UserId; - -#[derive(Debug, Deserialize)] -pub struct CreateUserBody { - pub email: String, -} - -pub async fn health() -> &'static str { - "ok" -} - -pub async fn create_user_handler<R: UserRepo + 'static>( - State(repo): State<Arc<R>>, - Json(body): Json<CreateUserBody>, -) -> Result<Json<serde_json::Value>, (StatusCode, String)> { - let out = create_user(repo.as_ref(), CreateUserInput { email: body.email }) - .await - .map_err(map_err)?; - Ok(Json(serde_json::to_value(out.user).unwrap())) -} - -pub async fn get_user_handler<R: UserRepo + 'static>( - State(repo): State<Arc<R>>, - Path(id): Path<uuid::Uuid>, -) -> Result<Json<serde_json::Value>, (StatusCode, String)> { - let user = get_user(repo.as_ref(), UserId(id)) - .await - .map_err(map_err)? - .ok_or((StatusCode::NOT_FOUND, "user not found".into()))?; - Ok(Json(serde_json::to_value(user).unwrap())) -} - -fn map_err(e: ApplicationError) -> (StatusCode, String) { - match e { - ApplicationError::Domain(d) => (StatusCode::BAD_REQUEST, d.to_string()), - ApplicationError::Repo(r) => (StatusCode::INTERNAL_SERVER_ERROR, r), - } -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/http/router.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/http/router.rs deleted file mode 100644 index 3af5022..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/http/router.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::sync::Arc; - -use axum::{ - Router, - routing::{get, post}, -}; - -use application::ports::UserRepo; - -use crate::http::handlers; - -/// Composition entrypoint for axum routes. The repo is injected by `apps/server/main.rs`. -pub fn router<R>(repo: Arc<R>) -> Router -where - R: UserRepo + 'static, -{ - Router::new() - .route("/health", get(handlers::health)) - .route("/users", post(handlers::create_user_handler::<R>)) - .route("/users/{id}", get(handlers::get_user_handler::<R>)) - .with_state(repo) -} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/lib.rs b/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/lib.rs deleted file mode 100644 index cefc8d3..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/crates/interface/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Interface layer — Dioxus components + axum handlers. -//! -//! Depends on `application` + `domain` only. NEVER on `infrastructure`. -//! Composition root (`apps/<name>/main.rs`) injects concrete impls. - -pub mod components; - -#[cfg(feature = "server")] -pub mod http; diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/deny.toml b/code-et-implementer/templates/rust/dioxus-fullstack/deny.toml deleted file mode 100644 index df29209..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/deny.toml +++ /dev/null @@ -1,72 +0,0 @@ -# cargo-deny config. See https://embarkstudios.github.io/cargo-deny/. - -[graph] -all-features = true - -[advisories] -version = 2 -yanked = "deny" -# `unmaintained` defaults to "all" (error on any unmaintained crate) in -# cargo-deny v2. The dioxus-desktop stack drags in 10+ unmaintained -# gtk-rs 0.18 / fxhash crates that we cannot fix without dioxus migrating -# upstream. Set to "workspace" — only error if one of OUR workspace crates -# becomes unmaintained. cargo-audit (stage 5) still surfaces every transitive -# advisory in its report and CI logs. -unmaintained = "workspace" -# `unsound` — keep as error; if a real unsoundness lands, we want to fail. -# Each ignore below needs a one-line rationale. See audit.toml for long-form. -# Re-evaluate every time deps bump. -ignore = [ - # rsa Marvin Attack — transitive via sqlx-macros (compile-time only), - # no mysql at runtime. Remove once sqlx-macros decouples mysql. - "RUSTSEC-2023-0071", - # glib 0.18 unsound — transitive via dioxus-desktop's gtk-rs 0.18 chain. - # Removed once dioxus-desktop migrates off the unmaintained stack. - "RUSTSEC-2024-0429", - # rand 0.7 unsound (custom logger) — transitive via dioxus-desktop's - # kuchikiki/selectors. Removed once dioxus-desktop deps modernise. - "RUSTSEC-2026-0097", -] - -[licenses] -version = 2 -allow = [ - "MIT", - "Apache-2.0", - "Apache-2.0 WITH LLVM-exception", - "BSD-2-Clause", - "BSD-3-Clause", - "ISC", - "Unicode-3.0", - "Unicode-DFS-2016", - "Zlib", - "MPL-2.0", - "CC0-1.0", - # NCSA: University of Illinois / NCSA Open Source License. OSI-approved - # permissive, equivalent in scope to MIT/BSD. Required by libfuzzer-sys - # (transitive via dioxus-desktop → image → ravif → rav1e). - "NCSA", - # CDLA-Permissive-2.0: Community Data License Agreement Permissive 2.0. - # Used by webpki-roots. Permissive, equivalent in scope to MIT/BSD. - "CDLA-Permissive-2.0", - # BSL-1.0: Boost Software License 1.0. OSI-approved, very permissive - # (no attribution required for binary distributions). Used by xxhash-rust. - "BSL-1.0", -] -confidence-threshold = 0.8 - -[bans] -multiple-versions = "warn" -# Workspace path deps (`domain = { workspace = true }`) carry no version and -# read as wildcards to cargo-deny. `allow-wildcard-paths` only suppresses this -# for crates marked `publish = false`; we don't enforce that across every -# member, so we downgrade wildcards to warn — the workspace path deps are -# internal-only by construction. Use review to catch real `serde = "*"` slips. -wildcards = "warn" -allow-wildcard-paths = true -deny = [] - -[sources] -unknown-registry = "deny" -unknown-git = "deny" -allow-registry = ["https://github.com/rust-lang/crates.io-index"] diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/justfile b/code-et-implementer/templates/rust/dioxus-fullstack/justfile deleted file mode 100644 index e362cc2..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/justfile +++ /dev/null @@ -1,68 +0,0 @@ -# {{name}} — code-et bootstrapped pure-Rust full-stack project. - -set shell := ["bash", "-uc"] - -default: - @just --list - -# Quick check of the whole workspace. -check: - cargo check --workspace --all-features - -# Run all tests via cargo-nextest. -test: - cargo nextest run --workspace --all-features - -# Format check (CI-equivalent). -fmt: - cargo fmt --all -- --check - -# Auto-format. -fmt-fix: - cargo fmt --all - -# Clippy with deny-warnings (CI-equivalent). -lint: - cargo clippy --workspace --all-targets --all-features -- -D warnings - -# Local mirror of the CI audit pipeline. -audit: - just fmt - just lint - bash scripts/layer-deps-validator.sh - cargo machete - cargo audit - cargo deny check - just test - -# Apply migrations. -db-migrate: - sqlx migrate run - -# Run the axum + dioxus-fullstack server. -run-server: - cargo run -p server - -# Run the dioxus-desktop renderer. -run-desktop: - cargo run -p desktop - -# Serve the WASM web app on :8080. -run-web: - dx serve --platform web - -# Build the mobile app (requires xcode for iOS, android-ndk for Android). -build-mobile: - dx build --platform mobile - -# Regenerate sqlx offline cache after schema or query changes. -sqlx-prepare: - cargo sqlx prepare --workspace - -# Ship the server to staging or prod. Always via scripts/deploy.sh — never raw cargo/gcloud. -deploy env="staging" *FLAGS="": - bash scripts/deploy.sh {{env}} {{FLAGS}} - -# Upload static artifacts (web bundle / desktop / mobile) to CDN or release bucket. -upload kind="web" env="staging": - bash scripts/upload.sh {{kind}} {{env}} diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/migrations/20250101000000_init.sql b/code-et-implementer/templates/rust/dioxus-fullstack/migrations/20250101000000_init.sql deleted file mode 100644 index a293c3e..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/migrations/20250101000000_init.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Portable schema for SQLite + Postgres. Forward-only. -CREATE TABLE IF NOT EXISTS users ( - id TEXT PRIMARY KEY NOT NULL, - email TEXT NOT NULL UNIQUE -); diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/rust-toolchain.toml b/code-et-implementer/templates/rust/dioxus-fullstack/rust-toolchain.toml deleted file mode 100644 index 85f3606..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/rust-toolchain.toml +++ /dev/null @@ -1,4 +0,0 @@ -[toolchain] -channel = "stable" -components = ["rustfmt", "clippy"] -profile = "minimal" diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/scripts/deploy.sh b/code-et-implementer/templates/rust/dioxus-fullstack/scripts/deploy.sh deleted file mode 100644 index 90bacee..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/scripts/deploy.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env bash -# deploy.sh — single entry point for shipping {{name}} to a server. -# -# Discipline: NEVER deploy {{name}} via raw `cargo`, `docker`, or `gcloud` -# commands typed into a shell. All deploy paths go through this script so -# the steps are deterministic, reviewable, and reproducible across machines. -# -# Usage: -# bash scripts/deploy.sh <env> [--skip-migrate] [--skip-build] -# -# Environments: -# staging — staging Cloud Run / staging Cloud SQL -# prod — production Cloud Run / production Cloud SQL -# -# Required env vars (set in your shell or via 1Password / GCP Secret Manager): -# GCP_PROJECT_ID — target GCP project -# GCP_REGION — e.g. europe-north1 -# CLOUD_RUN_SERVICE — service name in Cloud Run -# ARTIFACT_REGISTRY_REPO — Artifact Registry repo for the image -# DATABASE_URL — Cloud SQL connection string (postgres://…) for migrations -# -# This script is a STARTING POINT — wire your actual hosting (Cloud Run, -# GKE, Fly, Render, raw VM) where the placeholder blocks are. The structure -# is fixed; the host-specific commands are the part you fill in. - -set -euo pipefail - -ENV="${1:?usage: deploy.sh <staging|prod> [--skip-migrate] [--skip-build]}" -shift || true - -SKIP_MIGRATE=0 -SKIP_BUILD=0 -for arg in "$@"; do - case "$arg" in - --skip-migrate) SKIP_MIGRATE=1 ;; - --skip-build) SKIP_BUILD=1 ;; - *) echo "deploy.sh: unknown flag: $arg" >&2; exit 2 ;; - esac -done - -case "$ENV" in - staging|prod) ;; - *) echo "deploy.sh: env must be staging|prod, got: $ENV" >&2; exit 2 ;; -esac - -# 1. Pre-flight: clean tree, on main (or release branch), tools present. -git diff --quiet || { echo "deploy.sh: working tree dirty — commit or stash first" >&2; exit 1; } -GIT_SHA="$(git rev-parse --short HEAD)" -GIT_BRANCH="$(git rev-parse --abbrev-ref HEAD)" -echo "deploy.sh: shipping {{name}} @$GIT_SHA from branch $GIT_BRANCH to $ENV" - -for tool in cargo gcloud docker; do - command -v "$tool" >/dev/null 2>&1 || { echo "deploy.sh: $tool not on PATH" >&2; exit 1; } -done - -# 2. Audit gate — never deploy a workspace that fails CI locally. -echo "deploy.sh: running just audit..." -just audit - -# 3. Build container image. -if [ "$SKIP_BUILD" -eq 0 ]; then - IMAGE_TAG="${GCP_REGION:?}-docker.pkg.dev/${GCP_PROJECT_ID:?}/${ARTIFACT_REGISTRY_REPO:?}/{{name}}-server:$GIT_SHA" - echo "deploy.sh: building $IMAGE_TAG" - # TODO: replace with your actual build. Two common patterns: - # (a) Dockerfile in repo root, multi-stage cargo build → distroless - # (b) cargo build --release + custom buildpack - docker build -t "$IMAGE_TAG" . - echo "deploy.sh: pushing $IMAGE_TAG" - docker push "$IMAGE_TAG" -fi - -# 4. Run migrations against the target database. -if [ "$SKIP_MIGRATE" -eq 0 ]; then - echo "deploy.sh: running sqlx migrations against $ENV database" - # TODO: configure DATABASE_URL for $ENV (Cloud SQL Auth Proxy + IAM token). - # Example via Cloud SQL Auth Proxy: - # cloud-sql-proxy --port 5433 "$GCP_PROJECT_ID:$GCP_REGION:$DB_INSTANCE" & - # PROXY_PID=$! - # trap 'kill $PROXY_PID' EXIT - # DATABASE_URL="postgres://app@127.0.0.1:5433/{{name}}?sslmode=disable" sqlx migrate run - sqlx migrate run -fi - -# 5. Roll out the new revision. -echo "deploy.sh: deploying $IMAGE_TAG to Cloud Run service ${CLOUD_RUN_SERVICE:?}" -# TODO: replace with your actual deploy. Common patterns: -# gcloud run deploy "$CLOUD_RUN_SERVICE" --image "$IMAGE_TAG" --region "$GCP_REGION" --project "$GCP_PROJECT_ID" -# kubectl set image deployment/{{name}}-server server="$IMAGE_TAG" -# flyctl deploy --image "$IMAGE_TAG" - -# 6. Smoke check. -echo "deploy.sh: post-deploy smoke check" -# TODO: curl the health endpoint, expect 200. -# curl -fsS "https://${CLOUD_RUN_SERVICE}.run.app/healthz" >/dev/null - -echo "deploy.sh: ✓ {{name}} @$GIT_SHA shipped to $ENV" diff --git a/code-et-implementer/templates/rust/dioxus-fullstack/scripts/upload.sh b/code-et-implementer/templates/rust/dioxus-fullstack/scripts/upload.sh deleted file mode 100644 index 5331bd1..0000000 --- a/code-et-implementer/templates/rust/dioxus-fullstack/scripts/upload.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash -# upload.sh — push static assets (web bundle, desktop binaries, mobile builds) to a CDN/object store. -# -# Discipline: NEVER upload {{name}} artifacts via raw `gsutil`/`aws s3`/`scp` -# commands. All uploads route through this script so the artifacts, paths, -# and cache rules are deterministic. -# -# Usage: -# bash scripts/upload.sh <kind> <env> -# -# Kinds: -# web — Dioxus web bundle (WASM + JS + assets) → CDN/object store -# desktop — desktop release binaries → release bucket -# mobile — mobile release builds → app store / TestFlight upload -# -# Required env vars: -# GCS_BUCKET — destination bucket (or your CDN's equivalent) -# GCS_PREFIX — path prefix inside the bucket (e.g. "{{name}}/web") -# CDN_INVALIDATE_PATHS — optional: comma-separated paths to invalidate after upload - -set -euo pipefail - -KIND="${1:?usage: upload.sh <web|desktop|mobile> <staging|prod>}" -ENV="${2:?usage: upload.sh <web|desktop|mobile> <staging|prod>}" - -case "$ENV" in - staging|prod) ;; - *) echo "upload.sh: env must be staging|prod, got: $ENV" >&2; exit 2 ;; -esac - -git diff --quiet || { echo "upload.sh: working tree dirty — commit first" >&2; exit 1; } -GIT_SHA="$(git rev-parse --short HEAD)" -DEST="gs://${GCS_BUCKET:?}/${GCS_PREFIX:?}/$ENV/$GIT_SHA" - -echo "upload.sh: $KIND artifacts → $DEST" - -case "$KIND" in - web) - echo "upload.sh: building Dioxus web bundle" - dx build --platform web --release - # TODO: configure your destination. Example with gsutil: - # gsutil -m -h "Cache-Control:public,max-age=31536000,immutable" rsync -r dist/ "$DEST/" - # (then upload index.html separately with Cache-Control: no-cache) - ;; - desktop) - echo "upload.sh: building desktop release" - cargo build -p desktop --release - # TODO: tar + gsutil cp to release bucket - ;; - mobile) - echo "upload.sh: building mobile release (best-effort)" - dx build --platform mobile --release - # TODO: app store / TestFlight upload (fastlane, eas, etc.) - ;; - *) - echo "upload.sh: unknown kind: $KIND (web|desktop|mobile)" >&2 - exit 2 - ;; -esac - -# Optional CDN invalidate. -if [ -n "${CDN_INVALIDATE_PATHS:-}" ]; then - echo "upload.sh: invalidating CDN paths: $CDN_INVALIDATE_PATHS" - # TODO: gcloud compute url-maps invalidate-cdn-cache, or your CDN equivalent -fi - -echo "upload.sh: ✓ $KIND @$GIT_SHA uploaded to $ENV ($DEST)" diff --git a/code-et-implementer/templates/shared/.github/workflows/code-et-audit.yml b/code-et-implementer/templates/shared/.github/workflows/code-et-audit.yml index 7f0a518..fcb2b44 100644 --- a/code-et-implementer/templates/shared/.github/workflows/code-et-audit.yml +++ b/code-et-implementer/templates/shared/.github/workflows/code-et-audit.yml @@ -9,77 +9,41 @@ jobs: audit: runs-on: ubuntu-latest - services: - postgres: - image: postgres:16 - env: - POSTGRES_PASSWORD: postgres - POSTGRES_DB: code_et_audit - ports: - - 5432:5432 - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - env: - CARGO_TERM_COLOR: always - RUSTFLAGS: -D warnings + DATABASE_URL: "file::memory:?cache=shared" + NODE_ENV: "test" steps: - uses: actions/checkout@v4 - - name: install GTK + WebKit + xdo system libs (dioxus-desktop) - # dioxus-desktop on Linux uses wry → webkit2gtk → glib/gtk + xdo for - # tray-icon. Without these, `cargo build` fails on apps/desktop. Skip - # this step (and remove libxdo-dev) if your project's `--targets` - # excludes desktop. - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends \ - libgtk-3-dev libglib2.0-dev libsoup-3.0-dev \ - libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev \ - libxdo-dev - - - name: install rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt, clippy - - - uses: Swatinem/rust-cache@v2 - - - name: install cargo-nextest - uses: taiki-e/install-action@v2 + - name: install bun + uses: oven-sh/setup-bun@v2 with: - tool: cargo-nextest + bun-version: latest - - name: install cargo-audit - uses: taiki-e/install-action@v2 + - name: cache bun deps + uses: actions/cache@v4 with: - tool: cargo-audit - - - name: fmt - run: cargo fmt --all -- --check + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock', '**/bun.lockb') }} + restore-keys: | + ${{ runner.os }}-bun- - - name: clippy (deny warnings) - run: cargo clippy --workspace --all-targets --all-features -- -D warnings + - name: install + run: bun install --frozen-lockfile - - name: layer-deps validator - run: bash scripts/layer-deps-validator.sh + - name: generate drizzle migrations + run: bun run db:generate || echo "no schema changes" - - name: cargo-machete (unused deps) - uses: bnjbvr/cargo-machete@main + - name: lint + run: bunx biome check . - # cargo-audit ignores: each must have a rationale comment in audit.toml. - # Re-evaluate every time deps bump. - - name: cargo-audit (advisories) - run: cargo audit --ignore RUSTSEC-2023-0071 + - name: typecheck + run: bunx tsc --noEmit - - name: cargo-deny (license + bans + sources + advisories) - uses: EmbarkStudios/cargo-deny-action@v1 + - name: dependency audit + run: bun audit --audit-level=high + continue-on-error: false - - name: tests - run: cargo nextest run --workspace --all-features - env: - DATABASE_URL: "sqlite::memory:" + - name: test + run: bun test diff --git a/code-et-implementer/templates/shared/CLAUDE.md.template b/code-et-implementer/templates/shared/CLAUDE.md.template index 962c5c3..cd9b3f8 100644 --- a/code-et-implementer/templates/shared/CLAUDE.md.template +++ b/code-et-implementer/templates/shared/CLAUDE.md.template @@ -1,68 +1,61 @@ # {{name}} -Pure-Rust full-stack project bootstrapped with [code-et](https://github.com/Emerging-Tech-Visma/code-et). Architecture: Clean Architecture (4 crates: `domain`, `application`, `infrastructure`, `interface`). Frontend: Dioxus 0.7+ for web/desktop/mobile. Database: {{db}} (`sqlite` for local, switch via `DATABASE_URL=postgres://…` for Cloud SQL). +TypeScript project scaffolded with **code-et v5**. Stack: **Bun + Hono + Drizzle**. Architecture: deep modules — small interfaces with a lot of behaviour behind them; no fixed layer taxonomy. ## Quick start ```bash -cp .env.example .env # set DATABASE_URL -just db:migrate # apply migrations -just run:server # axum + dioxus-fullstack SSR on :3000 -just run:web # dioxus-web (WASM) on :8080 -just run:desktop # dioxus-desktop window -just test # cargo nextest run --workspace -just audit # local mirror of CI audit +cp .env.example .env +bun install +bun run db:migrate +bun run dev # Hono server on :3000 +bun test # bun test runner +bun run audit # local mirror of CI (biome + tsc + bun audit + bun test) ``` ## Architecture rules -This project follows the doctrine in code-et's [`docs/architecture.md`](https://github.com/Emerging-Tech-Visma/code-et/blob/main/code-et-implementer/docs/architecture.md). +This project follows the doctrine in code-et's `code-et-implementer/docs/architecture.md` (loaded via `Skill` when working with the plugin installed). -- Imports point inward: `domain` ← `application` ← `infrastructure`/`interface`. The `Cargo.toml` workspace deps enforce this. -- DTOs cross boundaries; `domain::Entity` and `sqlx::Row` do not. -- Dioxus components live in `crates/interface/src/components/` and render on web, desktop, and mobile. -- All SQL goes through `sqlx::query!` (compile-time-checked); raw `sqlx::query` is forbidden in production code. -- Forward-only migrations under `migrations/`. Each rollback is its own forward migration. +- One folder per **module** under `src/modules/<name>/`. The `index.ts` *is* the interface — public exports + types only. +- Apply the **deletion test** before any new extraction. If deleting the module makes complexity vanish, it was a pass-through — fold it back. +- DTOs cross seams; Drizzle rows and framework types do not. +- All HTTP input parsed with Zod at the route seam. +- All DB access through Drizzle — no raw SQL string concatenation. +- Forward-only migrations under `src/db/migrations/`. Each rollback is its own forward migration. -## Recommended companions +## Vocabulary — use these terms exactly -- [`knowledge-work-plugins/engineering`](https://claude.com/product/cowork) — provides `code-review`, `tech-debt`, `testing-strategy`, `system-design` skills. CLAUDE.md (the plugin's, not this one) has the delegation map. -- `rust-analyzer-lsp` — symbol-level precision for `/code:plan` and `/code:fix` (used by code-et). +- **Module** — anything with an interface and an implementation. +- **Interface** — everything a caller must know: types, invariants, error modes, ordering. +- **Seam** — where an interface lives. +- **Adapter** — a concrete thing that satisfies an interface at a seam. +- **Depth / Leverage / Locality** — what makes a module worth its keep. -## Deploy & upload rule +Don't drift into "component", "service", "API", or "boundary". Terminology drawn from Ousterhout's *A Philosophy of Software Design* (deep modules) and Feathers' *Working Effectively with Legacy Code* (seams). -**Never** deploy or upload {{name}} via raw shell commands. All paths route through: +## CI gate -- `just deploy <env>` → `scripts/deploy.sh` (audit gate → build → migrate → roll out → smoke). -- `just upload <kind> <env>` → `scripts/upload.sh` (web/desktop/mobile artifacts → CDN/object store). +`.github/workflows/code-et-audit.yml` runs on every PR + push to main: -If you find yourself typing `gcloud run deploy …`, `docker push …`, or `gsutil cp …` directly, stop — add the missing step to the script instead. The discipline keeps deploys deterministic and reviewable. +1. `biome check .` — lint + format +2. `tsc --noEmit` — type safety +3. `bun audit --audit-level=high` — dependency advisories +4. `bun test` — unit + integration + http-seam tests -## CI gate +A failing CI is the merge blocker. Local mirror: `bun run audit`. -`.github/workflows/code-et-audit.yml` runs on every PR + push to main: -1. `cargo fmt --check` -2. `cargo clippy --workspace --all-targets --all-features -- -D warnings` -3. `scripts/layer-deps-validator.sh` (defence-in-depth on the layer rule) -4. `cargo-machete` (unused deps) -5. `cargo-audit` (security advisories) -6. `cargo-deny check` (license + bans) -7. `cargo nextest run --workspace --all-features` +## Recommended companions -A failing CI is the merge blocker. Local feedback: `just audit`. +- `engineering` (community plugin) — provides `code-review`, `tech-debt`, `testing-strategy`, `system-design` skills. +- `commit-commands` — `/commit`, `/commit-push-pr`, `/clean_gone`. -## Code-et workflow +## code-et workflow | Task | Command | |---|---| -| Single bug fix | `/code:fix` (scope) → implement → `/commit-push-pr` | -| Feature | `/code:plan` (idea → PRD → tasks) → `/code:ship` (parallel + audit) → `/code:review` → `/commit-push-pr` | +| Single bug fix | `/code:fix` → implement → `/commit-push-pr` | +| Feature | `/code:plan` → `/code:ship` → `/code:review` → `/commit-push-pr` | | Add CI to existing repo | `/code:install-ci` | -Each task carries `metadata.layer ∈ {domain, application, infrastructure, interface, chore}` (per-file). Vertical slices may span layers; each *file* belongs to exactly one. - -## See also - -- [code-et docs/architecture.md](https://github.com/Emerging-Tech-Visma/code-et/blob/main/code-et-implementer/docs/architecture.md) -- [code-et docs/anti-slop.md](https://github.com/Emerging-Tech-Visma/code-et/blob/main/code-et-implementer/docs/anti-slop.md) -- [code-et docs/testing.md](https://github.com/Emerging-Tech-Visma/code-et/blob/main/code-et-implementer/docs/testing.md) +Each task carries `metadata.module` (free-form module name) — vertical slices typically touch the HTTP seam plus one or two modules. diff --git a/code-et-implementer/templates/shared/UPDATING.md b/code-et-implementer/templates/shared/UPDATING.md index 1856d49..1ace8d5 100644 --- a/code-et-implementer/templates/shared/UPDATING.md +++ b/code-et-implementer/templates/shared/UPDATING.md @@ -1,45 +1,45 @@ # Template Updating Checklist -How to keep `templates/rust/dioxus-fullstack/` and `templates/shared/` aligned with the upstream Rust ecosystem. +How to keep `templates/typescript/` and `templates/shared/` aligned with the upstream TS ecosystem. ## When to update -- A pinned crate has a new minor (axum 0.8 → 0.9, dioxus 0.7 → 0.8, sqlx 0.8 → 0.9). +- A pinned dependency has a new minor (Hono 4.6 → 4.7, Drizzle 0.36 → 0.37). +- Bun has a new minor with a behaviour change relevant to the template. +- Biome has a new major (config schema changes). - A pinned GitHub Action has a new major (`actions/checkout@v4` → `@v5`). -- The Rust edition advances (2024 → 2027). - A security advisory affects a templated dependency. -Cadence: review every quarter even if nothing has shipped. Rust crate ecosystems move fast, especially Dioxus. +Cadence: review every quarter even if nothing has shipped. ## Update procedure 1. **Branch.** `feature/templates-refresh-YYYY-MM`. -2. **Bump versions.** Update each `Cargo.toml` under `templates/rust/dioxus-fullstack/`: - - `crates/domain/Cargo.toml` — `serde`, `thiserror`, `uuid`, `time`. - - `crates/application/Cargo.toml` — `async-trait`, `anyhow`, `mockall` (dev). - - `crates/infrastructure/Cargo.toml` — `sqlx`, `reqwest`, `tokio`, `secrecy`. - - `crates/interface/Cargo.toml` — `dioxus`, `axum`, `tower`. - - `apps/server/Cargo.toml` — `dioxus-fullstack`, `tokio`. - - `apps/desktop/Cargo.toml` — `dioxus-desktop`. - - `apps/web/Cargo.toml` — `dioxus-web`. - - `apps/mobile/Cargo.toml` — `dioxus-mobile`. +2. **Bump versions** in `templates/typescript/package.json`: + - `hono`, `drizzle-orm`, `zod` (runtime) + - `@biomejs/biome`, `drizzle-kit`, `typescript`, `@types/bun` (dev) 3. **Bump GitHub Actions** in `templates/shared/.github/workflows/code-et-audit.yml`. Pin to majors (`@v4`); avoid floating `@latest`. -4. **Smoke-test.** Scaffold a fresh project from the updated template into `/tmp/upgrade-smoke`, run `cargo check --workspace --all-features` and `cargo nextest run`. Both must pass. -5. **CI smoke.** Push to a temp repo or run `act` against the workflow; the audit job must pass clean. -6. **Validator.** `bash templates/shared/scripts/layer-deps-validator.sh` (in the smoke project) must exit 0. -7. **Bump plugin patch version** (e.g. `3.9.x → 3.9.x+1`), update `CHANGELOG.md`. The user-facing change is "templates refreshed for axum 0.9 / dioxus 0.8". +4. **Smoke-test.** Scaffold a fresh project from the updated template into `/tmp/upgrade-smoke`: + ``` + bun install + bun run db:generate + bun run audit + ``` + All four audit stages (biome, tsc, bun audit, bun test) must pass. +5. **CI smoke.** Push to a temp repo or run `act` against the workflow. +6. **Bump plugin patch version** (e.g. `5.0.x → 5.0.x+1`), update `CHANGELOG.md`. ## Pinning policy -- **Crates:** pin to minor (`"^0.8"` for sqlx, `"^0.7"` for dioxus). Patch updates flow through `cargo update`. +- **Dependencies:** pin to minor with caret (`^4.6.0`). Patches flow through `bun update`. - **Actions:** pin to major (`@v4`). Major bumps require manual smoke + a CHANGELOG note. -- **Rust toolchain:** `rust-toolchain.toml` pins to `stable` channel; nightly is gated on `cargo-udeps` opt-in. +- **Bun:** `oven-sh/setup-bun@v2` with `bun-version: latest` — Bun's API is stable enough at minor cadence. ## Risk register | Risk | Mitigation | |---|---| -| Dioxus 0.x breaking changes between minors | The smoke project's `apps/{web,desktop,mobile}/main.rs` is the canary; if any fails to build, hold the bump until reviewed. | -| `sqlx` schema incompatibility on bump | The smoke project's `migrations/20250101000000_init.sql` runs against both Postgres and SQLite in CI; if either fails, fix the migration before merging the template bump. | +| Drizzle ORM breaking changes between minors | The smoke project's `greetings` schema is the canary; if migration generation fails, hold the bump. | +| Hono API drift | Two HTTP routes (`POST /greetings`, `GET /greetings`) exercise routing + Zod parsing + DI; if `app.fetch` test breaks, fix before merging. | +| Biome rule churn | Pin to a specific minor; review the changelog before bumping. | | GHA action removal | Pin majors; check the action's repo for archived/deprecated status before bumping. | -| Mobile target moves faster than web/desktop | If the bump breaks only mobile, gate the mobile app behind a feature flag in the bump PR. The `--targets` flag in `/code:start` already supports excluding mobile. | diff --git a/code-et-implementer/templates/shared/scripts/layer-deps-validator.sh b/code-et-implementer/templates/shared/scripts/layer-deps-validator.sh deleted file mode 100755 index b7fef5a..0000000 --- a/code-et-implementer/templates/shared/scripts/layer-deps-validator.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash -# layer-deps-validator.sh — code-et Clean Architecture defence-in-depth check. -# The cargo build is the primary gate; this script makes the violation message -# explicit when a [dependencies] section drifts in CI. -# -# Layer rules (inward dependency only): -# domain → (no workspace deps) -# application → domain -# infrastructure → application, domain -# interface → application, domain (NOT infrastructure) -# apps/* → may depend on any crate (composition root) -# -# Usage: bash scripts/layer-deps-validator.sh -# Exits 0 on clean, 1 on violations. Prints offending crate + dep on stderr. - -set -euo pipefail - -ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" -cd "$ROOT" - -declare -A ALLOWED -ALLOWED[domain]="" -ALLOWED[application]="domain" -ALLOWED[infrastructure]="application domain" -ALLOWED[interface]="application domain" - -violations=0 - -check_crate() { - local layer="$1" cargo="$2" - local allowed="${ALLOWED[$layer]:-}" - # Extract workspace-member dep names from [dependencies] / [dev-dependencies] only. - # A workspace dep looks like: name = { path = "../other" } or name.workspace = true - while IFS= read -r dep; do - [ -z "$dep" ] && continue - # accept the layer's own crate name (rare; sub-modules) - [ "$dep" = "$layer" ] && continue - if ! echo " $allowed " | grep -q " $dep "; then - # Only flag if the dep is itself a workspace crate (lives under crates/) - if [ -d "crates/$dep" ]; then - echo "::error file=$cargo::layer violation: crates/$layer must not depend on crates/$dep" >&2 - violations=$((violations + 1)) - fi - fi - done < <(awk ' - /^\[dependencies\]|^\[dev-dependencies\]/ { in_deps=1; next } - /^\[/ { in_deps=0; next } - in_deps && /^[a-zA-Z0-9_-]+[[:space:]]*=/ { - sub(/[[:space:]]*=.*/, "", $0); print - }' "$cargo") -} - -for layer in domain application infrastructure interface; do - cargo="crates/$layer/Cargo.toml" - [ -f "$cargo" ] || continue - check_crate "$layer" "$cargo" -done - -if [ "$violations" -gt 0 ]; then - echo "layer-deps-validator: $violations violation(s)" >&2 - exit 1 -fi - -echo "layer-deps-validator: clean" diff --git a/code-et-implementer/templates/typescript/.env.example b/code-et-implementer/templates/typescript/.env.example new file mode 100644 index 0000000..5969bd5 --- /dev/null +++ b/code-et-implementer/templates/typescript/.env.example @@ -0,0 +1,5 @@ +PORT=3000 +NODE_ENV=development + +# SQLite database file (Bun's built-in sqlite). Use file::memory: for tests. +DATABASE_URL=file:./dev.db diff --git a/code-et-implementer/templates/typescript/.gitignore b/code-et-implementer/templates/typescript/.gitignore new file mode 100644 index 0000000..9567c86 --- /dev/null +++ b/code-et-implementer/templates/typescript/.gitignore @@ -0,0 +1,19 @@ +node_modules/ +dist/ +.env +.env.local +*.log +.DS_Store + +# SQLite local dev databases +dev.db +dev.db-* +*.sqlite +*.sqlite-* + +# Build outputs +.cache/ +.bun/ + +# Audit reports +.claude/audit-*.md diff --git a/code-et-implementer/templates/typescript/README.md b/code-et-implementer/templates/typescript/README.md new file mode 100644 index 0000000..bd4e144 --- /dev/null +++ b/code-et-implementer/templates/typescript/README.md @@ -0,0 +1,36 @@ +# {{name}} + +TypeScript project scaffolded by **code-et v5**. Stack: **Bun + Hono + Drizzle**. Architecture: deep modules — see the plugin's `code-et-implementer/docs/architecture.md` (loaded via `Skill` when working in this repo). + +## Quick start + +```bash +cp .env.example .env +bun install +bun run db:migrate +bun run dev # Hono server on :3000 +bun test # bun test runner +bun run audit # local mirror of CI: biome + tsc + bun audit + bun test +``` + +## Project shape + +``` +src/ + modules/ One folder per deep module. Interface in index.ts. + db/ Drizzle schema + migrations. + http/ + app.ts Hono app — wires modules to routes. + routes/ One file per resource. + config.ts Zod-validated env loading. + main.ts Composition root. +``` + +Modules grow around interfaces, not framework-imposed folders. The deletion test (would removing this make complexity vanish, or reappear across callers?) decides whether something earns its place. + +## Workflow + +| Task | Command | +|---|---| +| Single bug fix | `/code:fix` → implement → `/commit-push-pr` | +| Feature | `/code:plan` → `/code:ship` → `/code:review` → `/commit-push-pr` | diff --git a/code-et-implementer/templates/typescript/biome.json b/code-et-implementer/templates/typescript/biome.json new file mode 100644 index 0000000..3c991ee --- /dev/null +++ b/code-et-implementer/templates/typescript/biome.json @@ -0,0 +1,31 @@ +{ + "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, + "files": { + "ignoreUnknown": false, + "includes": ["**", "!**/dist", "!**/node_modules", "!**/*.d.ts", "!**/src/db/migrations"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "javascript": { "formatter": { "quoteStyle": "double", "semicolons": "always" } }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { "noNonNullAssertion": "warn", "useImportType": "error" }, + "suspicious": { + "noExplicitAny": "warn", + "noConsole": { "level": "warn", "options": { "allow": ["error", "warn", "info"] } } + }, + "correctness": { + "useExhaustiveDependencies": "warn", + "noUnusedImports": "error", + "noUnusedVariables": "error" + }, + "complexity": { "noUselessConstructor": "error" } + } + } +} diff --git a/code-et-implementer/templates/typescript/drizzle.config.ts b/code-et-implementer/templates/typescript/drizzle.config.ts new file mode 100644 index 0000000..51a4dd2 --- /dev/null +++ b/code-et-implementer/templates/typescript/drizzle.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + schema: "./src/db/schema.ts", + out: "./src/db/migrations", + dialect: "sqlite", + dbCredentials: { url: process.env.DATABASE_URL ?? "file:./dev.db" }, +}); diff --git a/code-et-implementer/templates/typescript/package.json b/code-et-implementer/templates/typescript/package.json new file mode 100644 index 0000000..e6cde1e --- /dev/null +++ b/code-et-implementer/templates/typescript/package.json @@ -0,0 +1,29 @@ +{ + "name": "{{name}}", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "bun --hot src/main.ts", + "start": "bun src/main.ts", + "typecheck": "tsc --noEmit", + "lint": "biome check .", + "lint:fix": "biome check --write .", + "test": "bun test", + "test:watch": "bun test --watch", + "db:generate": "drizzle-kit generate", + "db:migrate": "bun src/db/migrate.ts", + "audit": "biome check . && tsc --noEmit && bun audit --audit-level=high && bun test" + }, + "dependencies": { + "drizzle-orm": "^0.45.2", + "hono": "^4.12.0", + "zod": "^4.0.0" + }, + "devDependencies": { + "@biomejs/biome": "^2.4.0", + "@types/bun": "latest", + "drizzle-kit": "^0.31.0", + "typescript": "^5.6.0" + } +} diff --git a/code-et-implementer/templates/typescript/src/config.ts b/code-et-implementer/templates/typescript/src/config.ts new file mode 100644 index 0000000..e39d7c2 --- /dev/null +++ b/code-et-implementer/templates/typescript/src/config.ts @@ -0,0 +1,17 @@ +import { z } from "zod"; + +const envSchema = z.object({ + PORT: z.coerce.number().int().positive().default(3000), + NODE_ENV: z.enum(["development", "test", "production"]).default("development"), + DATABASE_URL: z.string().min(1), +}); + +export type Config = z.infer<typeof envSchema>; + +export function loadConfig(env: NodeJS.ProcessEnv = process.env): Config { + const parsed = envSchema.safeParse(env); + if (!parsed.success) { + throw new Error(`Invalid environment: ${parsed.error.message}`); + } + return parsed.data; +} diff --git a/code-et-implementer/templates/typescript/src/db/index.ts b/code-et-implementer/templates/typescript/src/db/index.ts new file mode 100644 index 0000000..478eb5d --- /dev/null +++ b/code-et-implementer/templates/typescript/src/db/index.ts @@ -0,0 +1,12 @@ +import { Database } from "bun:sqlite"; +import { drizzle } from "drizzle-orm/bun-sqlite"; +import * as schema from "./schema"; + +export type DB = ReturnType<typeof connect>; + +export function connect(url: string) { + const file = url.startsWith("file:") ? url.slice("file:".length) : url; + const sqlite = new Database(file); + sqlite.exec("PRAGMA journal_mode = WAL;"); + return drizzle(sqlite, { schema }); +} diff --git a/code-et-implementer/templates/typescript/src/db/migrate.ts b/code-et-implementer/templates/typescript/src/db/migrate.ts new file mode 100644 index 0000000..c3c4309 --- /dev/null +++ b/code-et-implementer/templates/typescript/src/db/migrate.ts @@ -0,0 +1,8 @@ +import { migrate } from "drizzle-orm/bun-sqlite/migrator"; +import { loadConfig } from "../config"; +import { connect } from "./index"; + +const config = loadConfig(); +const db = connect(config.DATABASE_URL); +migrate(db, { migrationsFolder: "./src/db/migrations" }); +console.info(`migrations applied against ${config.DATABASE_URL}`); diff --git a/code-et-implementer/templates/typescript/src/db/schema.ts b/code-et-implementer/templates/typescript/src/db/schema.ts new file mode 100644 index 0000000..3f0fc88 --- /dev/null +++ b/code-et-implementer/templates/typescript/src/db/schema.ts @@ -0,0 +1,10 @@ +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; + +export const greetings = sqliteTable("greetings", { + id: integer("id").primaryKey({ autoIncrement: true }), + message: text("message").notNull(), + createdAt: integer("created_at", { mode: "timestamp" }).notNull(), +}); + +export type Greeting = typeof greetings.$inferSelect; +export type NewGreeting = typeof greetings.$inferInsert; diff --git a/code-et-implementer/templates/typescript/src/http/app.ts b/code-et-implementer/templates/typescript/src/http/app.ts new file mode 100644 index 0000000..7f1b671 --- /dev/null +++ b/code-et-implementer/templates/typescript/src/http/app.ts @@ -0,0 +1,16 @@ +import { Hono } from "hono"; +import { logger } from "hono/logger"; +import type { Greetings } from "../modules/greetings"; +import { greetingsRoutes } from "./routes/greetings"; + +export interface AppDeps { + greetings: Greetings; +} + +export function buildHttp(deps: AppDeps) { + const app = new Hono(); + app.use("*", logger()); + app.get("/health", (c) => c.json({ status: "ok" })); + app.route("/greetings", greetingsRoutes(deps.greetings)); + return app; +} diff --git a/code-et-implementer/templates/typescript/src/http/routes/greetings.test.ts b/code-et-implementer/templates/typescript/src/http/routes/greetings.test.ts new file mode 100644 index 0000000..3f846f2 --- /dev/null +++ b/code-et-implementer/templates/typescript/src/http/routes/greetings.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from "bun:test"; +import type { Greetings } from "../../modules/greetings"; +import { buildHttp } from "../app"; + +function stubGreetings(overrides: Partial<Greetings> = {}): Greetings { + return { + record: async (message) => ({ id: 1, message, createdAt: new Date("2026-05-17T00:00:00Z") }), + recent: async () => [], + ...overrides, + }; +} + +describe("POST /greetings", () => { + it("returns 201 with the recorded greeting", async () => { + const app = buildHttp({ greetings: stubGreetings() }); + const res = await app.fetch( + new Request("http://test/greetings", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ message: "hi" }), + }), + ); + expect(res.status).toBe(201); + const body = (await res.json()) as { message: string }; + expect(body.message).toBe("hi"); + }); + + it("rejects an empty message with 400", async () => { + const app = buildHttp({ greetings: stubGreetings() }); + const res = await app.fetch( + new Request("http://test/greetings", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ message: "" }), + }), + ); + expect(res.status).toBe(400); + }); +}); diff --git a/code-et-implementer/templates/typescript/src/http/routes/greetings.ts b/code-et-implementer/templates/typescript/src/http/routes/greetings.ts new file mode 100644 index 0000000..4af67ed --- /dev/null +++ b/code-et-implementer/templates/typescript/src/http/routes/greetings.ts @@ -0,0 +1,25 @@ +import { Hono } from "hono"; +import { z } from "zod"; +import type { Greetings } from "../../modules/greetings"; + +const recordBody = z.object({ message: z.string().min(1).max(280) }); + +export function greetingsRoutes(greetings: Greetings) { + const app = new Hono(); + + app.post("/", async (c) => { + const parsed = recordBody.safeParse(await c.req.json().catch(() => null)); + if (!parsed.success) { + return c.json({ error: "invalid body", details: parsed.error.flatten() }, 400); + } + const recorded = await greetings.record(parsed.data.message); + return c.json(recorded, 201); + }); + + app.get("/", async (c) => { + const limit = Number(c.req.query("limit") ?? 10); + return c.json(await greetings.recent(limit)); + }); + + return app; +} diff --git a/code-et-implementer/templates/typescript/src/main.ts b/code-et-implementer/templates/typescript/src/main.ts new file mode 100644 index 0000000..06a802d --- /dev/null +++ b/code-et-implementer/templates/typescript/src/main.ts @@ -0,0 +1,28 @@ +import { loadConfig } from "./config"; +import { connect } from "./db"; +import { buildHttp } from "./http/app"; +import { makeGreetings } from "./modules/greetings"; + +export async function start(overrides?: { port?: number; database?: string }) { + const config = loadConfig({ + ...process.env, + ...(overrides?.port ? { PORT: String(overrides.port) } : {}), + ...(overrides?.database ? { DATABASE_URL: overrides.database } : {}), + }); + const db = connect(config.DATABASE_URL); + const app = buildHttp({ + greetings: makeGreetings({ db, clock: { now: () => new Date() } }), + }); + const server = Bun.serve({ port: config.PORT, fetch: app.fetch }); + return { + url: `http://${server.hostname}:${server.port}`, + async stop() { + server.stop(); + }, + }; +} + +if (import.meta.main) { + const { url } = await start(); + console.info(`server listening at ${url}`); +} diff --git a/code-et-implementer/templates/typescript/src/modules/greetings/greetings.test.ts b/code-et-implementer/templates/typescript/src/modules/greetings/greetings.test.ts new file mode 100644 index 0000000..17be561 --- /dev/null +++ b/code-et-implementer/templates/typescript/src/modules/greetings/greetings.test.ts @@ -0,0 +1,39 @@ +import { Database } from "bun:sqlite"; +import { afterEach, beforeEach, describe, expect, it } from "bun:test"; +import { drizzle } from "drizzle-orm/bun-sqlite"; +import { migrate } from "drizzle-orm/bun-sqlite/migrator"; +import * as schema from "../../db/schema"; +import { type Clock, makeGreetings } from "./index"; + +function fixedClock(iso: string): Clock { + return { now: () => new Date(iso) }; +} + +describe("greetings module", () => { + let sqlite: Database; + let db: ReturnType<typeof drizzle<typeof schema>>; + + beforeEach(() => { + sqlite = new Database(":memory:"); + db = drizzle(sqlite, { schema }); + migrate(db, { migrationsFolder: "./src/db/migrations" }); + }); + + afterEach(() => sqlite.close()); + + it("records a greeting and returns it from recent()", async () => { + const greetings = makeGreetings({ db, clock: fixedClock("2026-05-17T00:00:00Z") }); + + const recorded = await greetings.record("hello, deep modules"); + expect(recorded.message).toBe("hello, deep modules"); + + const recent = await greetings.recent(); + expect(recent).toHaveLength(1); + expect(recent[0]?.id).toBe(recorded.id); + }); + + it("rejects an empty greeting", async () => { + const greetings = makeGreetings({ db, clock: fixedClock("2026-05-17T00:00:00Z") }); + await expect(greetings.record(" ")).rejects.toThrow(/empty/); + }); +}); diff --git a/code-et-implementer/templates/typescript/src/modules/greetings/index.ts b/code-et-implementer/templates/typescript/src/modules/greetings/index.ts new file mode 100644 index 0000000..9e1df03 --- /dev/null +++ b/code-et-implementer/templates/typescript/src/modules/greetings/index.ts @@ -0,0 +1,36 @@ +import { desc } from "drizzle-orm"; +import type { DB } from "../../db"; +import { greetings } from "../../db/schema"; + +export interface Greetings { + record(message: string): Promise<{ id: number; message: string; createdAt: Date }>; + recent(limit?: number): Promise<ReadonlyArray<{ id: number; message: string; createdAt: Date }>>; +} + +export interface Clock { + now(): Date; +} + +export function makeGreetings(deps: { db: DB; clock: Clock }): Greetings { + const { db, clock } = deps; + return { + async record(message) { + const trimmed = message.trim(); + if (trimmed.length === 0) { + throw new Error("greeting must not be empty"); + } + const [row] = await db + .insert(greetings) + .values({ message: trimmed, createdAt: clock.now() }) + .returning(); + if (!row) throw new Error("insert did not return a row"); + return row; + }, + async recent(limit = 10) { + return db.query.greetings.findMany({ + orderBy: [desc(greetings.createdAt)], + limit, + }); + }, + }; +} diff --git a/code-et-implementer/templates/typescript/tsconfig.json b/code-et-implementer/templates/typescript/tsconfig.json new file mode 100644 index 0000000..790f4b6 --- /dev/null +++ b/code-et-implementer/templates/typescript/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "noEmit": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "lib": ["ES2022", "DOM"], + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true + }, + "include": ["src/**/*.ts", "drizzle.config.ts"], + "exclude": ["node_modules", "dist"] +} From 6ac696556c3db49cd2b72f5598759a243e72924f Mon Sep 17 00:00:00 2001 From: Kennet Dahl Kusk <kennet.dahl.kusk@visma.com> Date: Sun, 17 May 2026 22:04:12 +0200 Subject: [PATCH 3/5] v5.0.0: drop legacy hooks/scripts/tests; refresh meta + CHANGELOG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hooks (hooks/hooks.json): v4 carried six hooks — SubagentStop audit, PreToolUse(TaskCreate) regex validator, SessionStart PRD-detect, TaskCompleted notifier, PreCompact PRD-resume, plus PermissionRequest. v5 keeps only the PermissionRequest auto-approve for read-only tools; everything else is handled by skill prose (trust-the-model). Scripts (scripts/): Drop the Rust-specific audit/verify/PRD helpers (audit.sh + audit-stages.sh + audit-report.sh, run-tests.sh, verify-gate.sh, task-created-tag-check.sh, task-complete.sh, session-start-prd.sh, resolve-prd.sh, pre-compact-prd.sh). The single auto-approve-readonly.sh remains. Local audit is now just `bun run audit` (defined in the template's package.json), invoked directly by /code:ship and /code:review. Tests (tests/): Drop the bats suite that covered the Rust-targeted hooks. No TS-targeted suite yet; manual smoke test exercised the template end-to-end before commit. Meta: - plugin.json + marketplace.json — version 5.0.0; description rewritten for the TS/deep-modules stack; author scrubbed. - settings.json — add Bash(bun:*), Bash(bunx:*) to allow list; refresh spinner tips. - CLAUDE.md — TS code standards, deep-modules vocabulary, metadata.module (free-form) replaces metadata.layer (enforced enum). - README.md — workflow diagram, deep-modules architecture, "how it stays simple" updated. - FILE-REFERENCE.md — inventory matches the new tree (commands, doctrine, hooks, single script, templates). - CHANGELOG.md — v5.0.0 entry covers stack, architecture, skill style, hooks dropped, tag schema, CI gate, template, migration path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --- .claude-plugin/marketplace.json | 8 +- CHANGELOG.md | 30 +- FILE-REFERENCE.md | 84 +++-- README.md | 172 +++------ .../.claude-plugin/plugin.json | 17 +- .../.claude-plugin/settings.json | 5 +- code-et-implementer/CLAUDE.md | 100 +++--- code-et-implementer/hooks/hooks.json | 60 ---- code-et-implementer/scripts/audit-report.sh | 151 -------- code-et-implementer/scripts/audit-stages.sh | 104 ------ code-et-implementer/scripts/audit.sh | 325 ------------------ .../scripts/pre-compact-prd.sh | 27 -- code-et-implementer/scripts/resolve-prd.sh | 25 -- code-et-implementer/scripts/run-tests.sh | 132 ------- .../scripts/session-start-prd.sh | 21 -- code-et-implementer/scripts/task-complete.sh | 27 -- .../scripts/task-created-tag-check.sh | 127 ------- code-et-implementer/scripts/verify-gate.sh | 16 - code-et-implementer/tests/README.md | 9 - .../tests/audit-review-with-plugin.sh | 96 ------ .../tests/audit-review-without-plugin.sh | 76 ---- .../tests/audit-skips-missing-tools.sh | 129 ------- .../tests/audit-skips-non-rust.sh | 38 -- .../tests/audit-stage-list-matches-yaml.sh | 90 ----- .../tests/audit-summary-line-on-failure.sh | 149 -------- code-et-implementer/tests/doctrine-links.bats | 128 ------- .../tests/fixtures/plans/.keep | 0 .../implement-chain-halts-on-audit-failure.sh | 134 -------- .../tests/layer-deps-validator.bats | 92 ----- .../tests/layer-tag-enforcement.bats | 85 ----- .../tests/pre-compact-prd.bats | 49 --- code-et-implementer/tests/resolve-prd.bats | 69 ---- code-et-implementer/tests/run-tests.sh | 4 - .../tests/session-start-prd.bats | 52 --- .../tests/task-created-tag-check.bats | 116 ------- .../tests/verify-gate-runs-audit-on-rust.sh | 86 ----- .../verify-gate-skips-audit-on-non-rust.sh | 75 ---- 37 files changed, 193 insertions(+), 2715 deletions(-) delete mode 100755 code-et-implementer/scripts/audit-report.sh delete mode 100755 code-et-implementer/scripts/audit-stages.sh delete mode 100755 code-et-implementer/scripts/audit.sh delete mode 100755 code-et-implementer/scripts/pre-compact-prd.sh delete mode 100755 code-et-implementer/scripts/resolve-prd.sh delete mode 100755 code-et-implementer/scripts/run-tests.sh delete mode 100755 code-et-implementer/scripts/session-start-prd.sh delete mode 100755 code-et-implementer/scripts/task-complete.sh delete mode 100755 code-et-implementer/scripts/task-created-tag-check.sh delete mode 100755 code-et-implementer/scripts/verify-gate.sh delete mode 100644 code-et-implementer/tests/README.md delete mode 100755 code-et-implementer/tests/audit-review-with-plugin.sh delete mode 100755 code-et-implementer/tests/audit-review-without-plugin.sh delete mode 100755 code-et-implementer/tests/audit-skips-missing-tools.sh delete mode 100755 code-et-implementer/tests/audit-skips-non-rust.sh delete mode 100755 code-et-implementer/tests/audit-stage-list-matches-yaml.sh delete mode 100755 code-et-implementer/tests/audit-summary-line-on-failure.sh delete mode 100644 code-et-implementer/tests/doctrine-links.bats delete mode 100644 code-et-implementer/tests/fixtures/plans/.keep delete mode 100755 code-et-implementer/tests/implement-chain-halts-on-audit-failure.sh delete mode 100644 code-et-implementer/tests/layer-deps-validator.bats delete mode 100644 code-et-implementer/tests/layer-tag-enforcement.bats delete mode 100644 code-et-implementer/tests/pre-compact-prd.bats delete mode 100644 code-et-implementer/tests/resolve-prd.bats delete mode 100755 code-et-implementer/tests/run-tests.sh delete mode 100644 code-et-implementer/tests/session-start-prd.bats delete mode 100644 code-et-implementer/tests/task-created-tag-check.bats delete mode 100755 code-et-implementer/tests/verify-gate-runs-audit-on-rust.sh delete mode 100755 code-et-implementer/tests/verify-gate-skips-audit-on-non-rust.sh diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index f5c5eed..4c59af1 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,17 +1,17 @@ { "name": "code-et", "owner": { - "name": "Kennet Kusk" + "name": "code-et" }, "metadata": { - "description": "Pure-Rust Clean Architecture workflow. Six commands (start, fix, plan, ship, review, install-ci) for axum + sqlx + Dioxus 0.7+ + tokio. Always-latest deps, CI audit gate, anti-slop enforced.", - "version": "4.3.1" + "description": "Lightweight TypeScript workflow built on deep modules. Six commands (start, fix, plan, ship, review, install-ci) for Bun + Hono + Drizzle + Biome. CI audit gate; deep-modules vocabulary from Ousterhout and Feathers.", + "version": "5.0.0" }, "plugins": [ { "name": "code", "source": "./code-et-implementer", - "description": "Pure-Rust Clean Architecture workflow. /code:start scaffolds axum+sqlx+Dioxus, /code:plan synthesises PRD + tasks, /code:ship runs parallel agents and audits, /code:review is the pre-merge gate." + "description": "Lightweight TypeScript workflow. /code:start scaffolds Bun+Hono+Drizzle around deep modules; /code:plan synthesises PRD + vertical-slice tasks; /code:ship runs parallel worktree agents + audit; /code:review is the pre-merge gate." } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index d312137..db90a68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ All notable changes to the code-et plugin will be documented in this file. +## [5.0.0] - 2026-05-17 + +### Changed — TypeScript stack + deep-modules architecture + +v5 is a deliberate rewrite. v4.x was Rust + Clean Architecture (4-crate workspace); v5 is TypeScript + deep modules. The six-command surface (`/code:start`, `/code:install-ci`, `/code:fix`, `/code:plan`, `/code:ship`, `/code:review`) is preserved; the underlying doctrine and template are new. + +**Stack.** `Bun + Hono + Drizzle + Biome` for the server; optional `Vite + React` for a web frontend. SQLite local + dev, Postgres for prod. Desktop and mobile targets are removed — no TypeScript equivalent of the Dioxus one-tree-three-renderers story matches the "lightweight" goal. + +**Architecture.** Replaces the 4-crate Clean Architecture mandate with **deep modules**. Vocabulary — *module / interface / seam / adapter / depth / leverage / locality* — is standard software-engineering terminology from Ousterhout's *A Philosophy of Software Design* (deep modules) and Feathers' *Working Effectively with Legacy Code* (seams). The **deletion test** is now the controlling rule for whether a new module earns its place. Dependency categories (in-process / local-substitutable / remote-but-owned / true-external) replace the per-crate dep table. + +**Skill style.** Commands rewritten lean — declarative prose, no procedural ceremony. Slash-command invocation is retained because users invoke these deliberately; the prose underneath now reads like a SKILL.md. + +**Hooks dropped.** v4.x carried six hooks (`SubagentStop`/audit, `PreToolUse(TaskCreate)`/regex, `SessionStart`/PRD-detect, `TaskCompleted`, `PreCompact`/PRD-resume, plus permission-approve). v5 keeps only the read-only-tool auto-approve hook. The model handles task metadata, PRD resume, and post-subagent verification through skill prose instead — trust-the-model. + +**Tag schema.** `metadata.layer` (`domain|application|infrastructure|interface|chore`, regex-enforced) is replaced by `metadata.module` (free-form lowercase, matches `src/modules/<name>/`). No taxonomy enforcement; the model picks a sensible module name and the reviewer catches drift. + +**CI gate.** `cargo fmt / clippy / machete / audit / deny / nextest + layer-deps-validator` is replaced by `biome check / tsc --noEmit / bun audit / bun test`. Four stages, all run by Bun without external action installs beyond `oven-sh/setup-bun@v2`. + +**Template.** `templates/rust/dioxus-fullstack/` (4 crates, 4 apps, ~50 files including a `Cargo.lock`) is replaced by `templates/typescript/` (~12 files: `package.json`, `tsconfig`, `biome.json`, `drizzle.config.ts`, an example `greetings` module + HTTP route + tests, and a composition-root `main.ts`). + +**Migration.** v4's final state is commit `c5bad00` on `main`; pin or branch from there for existing Rust projects. v5 only scaffolds new TS projects; there is no automatic v4→v5 codebase migration. + +Files touched: every command + doctrine doc rewritten; full template tree replaced; plugin.json, marketplace.json, CLAUDE.md, README.md, hooks.json, settings.json, CHANGELOG. + ## [4.3.1] - 2026-05-17 ### Fixed — `/code:plan` → `/code:ship` handoff: schema trap, silent rejection, stale-task pollution @@ -12,7 +36,7 @@ A live `/code:plan` session emitted `metadata.user_story = "US-1 | AC-1.1, AC-1. **Bug 2 — no recovery from hook rejection (`commands/plan.md` §"On TaskCreate rejection").** Phase 3 had no instruction for handling exit-2 from the hook. The orchestrator narrated forward (`"Now wiring task dependencies…"`) on phantom task ids and `TaskUpdate(addBlockedBy)` did nothing visible. Fix: explicit guidance — read `${TMPDIR}/code-et-task-hook/last-rejected.json`, identify the bad field, re-issue the same TaskCreate with corrected metadata, do not call `TaskUpdate` on phantom ids. Three retries on the same field escalates to the user (structural PRD misread, not a typo). -**Bug 3 — silent empty queue in `/code:ship` + cross-branch stale-task pollution (`commands/ship.md` §"Pre-dispatch").** `TaskList` is project-global, not branch-scoped — pending tasks from a merged PRD persist as `pending` on every subsequent branch (the live session showed #33–#37 from `visma-agentic-platform-shell` still pending after that PR's merge in `4f98ac3`). `/code:ship` was dispatching whatever TaskList returned, with no awareness that those tasks belonged to a different PRD; conversely, when *no* tasks tied to the current PRD existed, it exited with "no queue" instead of diagnosing the gap. Fix: resolve the active PRD, parse its `## Story Checklist` for US tags, and scope the dispatch queue to tasks whose `metadata.user_story` matches one of those US/AC tags **or** starts with `chore:` (chores during a feature are this-branch work via `/code:fix`, never stale). The empty-queue branch now distinguishes three cases (PRD exists / no tasks tied; PRD exists / tasks belong to a different PRD; no PRD / no tasks) and emits a specific next-step message for each. Stale tasks from prior branches are left untouched — they belong to their owning branch, not this one. +**Bug 3 — silent empty queue in `/code:ship` + cross-branch stale-task pollution (`commands/ship.md` §"Pre-dispatch").** `TaskList` is project-global, not branch-scoped — pending tasks from a merged PRD persist as `pending` on every subsequent branch (the live session showed #33–#37 from a prior feature branch still pending after that PR's merge in `4f98ac3`). `/code:ship` was dispatching whatever TaskList returned, with no awareness that those tasks belonged to a different PRD; conversely, when *no* tasks tied to the current PRD existed, it exited with "no queue" instead of diagnosing the gap. Fix: resolve the active PRD, parse its `## Story Checklist` for US tags, and scope the dispatch queue to tasks whose `metadata.user_story` matches one of those US/AC tags **or** starts with `chore:` (chores during a feature are this-branch work via `/code:fix`, never stale). The empty-queue branch now distinguishes three cases (PRD exists / no tasks tied; PRD exists / tasks belong to a different PRD; no PRD / no tasks) and emits a specific next-step message for each. Stale tasks from prior branches are left untouched — they belong to their owning branch, not this one. **Known follow-up.** This release prevents *dispatch* of stale tasks but does not *clear* them — #33–#37 will keep appearing on every `/code:ship` until manually `TaskUpdate`'d to completed. A v4.3.2 task-completion-on-PR-merge hook is the proper fix; until then, run `TaskUpdate(id, status: completed)` on any task whose owning PR has merged. @@ -268,7 +292,7 @@ PRD files under `plans/YYYY-MM-DD-<slug>.md` continue to work with `/code:plan` ### Changed — `/code:go` portability -- **`/code:go` no longer hardcodes Visma-specific app names.** Step 2 ("Which app(s)") and the Step 4 Task Brief template now reference the dynamic `Apps Overview` from `FILE-REFERENCE.md` instead of `CMS / Content Studio / Course Studio / Survey Studio`. The plugin is general-purpose and used across multiple repos; the hardcoded list contradicted Step 0's dynamic-discovery design. +- **`/code:go` no longer hardcodes project-specific app names.** Step 2 ("Which app(s)") and the Step 4 Task Brief template now reference the dynamic `Apps Overview` from `FILE-REFERENCE.md` instead of a hardcoded list. The plugin is general-purpose and used across multiple repos; the hardcoded list contradicted Step 0's dynamic-discovery design. - **Removed `feature` from the Task Brief Type list.** The scope guard at the top of `/code:go` already routes multi-slice features to `/code:prd → /code:plan-issue → /code:implement`, but the output template still listed `feature` as a valid type, contradicting the guard. Type list is now `[bug fix / styling / refactor / API change]`. - **Synced `marketplace.json` version** with `plugin.json` (was stale at 3.7.4). @@ -647,7 +671,7 @@ PRD files under `plans/YYYY-MM-DD-<slug>.md` continue to work with `/code:plan` ### Added -- Document `@ref` version-pinning syntax in README install section — users can now pin to a specific version with `/plugin marketplace add Emerging-Tech-Visma/code-et@v1.18.4` +- Document `@ref` version-pinning syntax in README install section — users can now pin to a specific version with `/plugin marketplace add <owner>/code-et@v1.18.4` ## [1.18.3] - 2026-03-07 diff --git a/FILE-REFERENCE.md b/FILE-REFERENCE.md index c74e667..3761ee7 100644 --- a/FILE-REFERENCE.md +++ b/FILE-REFERENCE.md @@ -1,91 +1,84 @@ # FILE-REFERENCE.md -Map of every component in the code-et plugin. Used by `/code:fix` for intake scoping -and as primary context for `/code:plan` (so plans can skip a full codebase sweep). +Map of every component in the code-et plugin (v5). Used by `/code:fix` for intake scoping and as primary context for `/code:plan` (so plans can skip a full codebase sweep). ## Project Overview | Area | Description | Root path | |------|-------------|-----------| -| Plugin | Claude Code plugin (commands, hooks, scripts) | `code-et-implementer/` | -| Repo root | Changelog, README, package metadata | `/` | +| Plugin | Claude Code plugin (commands, hooks, scripts, templates) | `code-et-implementer/` | +| Repo root | Changelog, README, marketplace manifest | `/` | --- -## Plugin — Commands (v4.0) +## Plugin — Commands (v5.0) | Command | Skill name | File | Description | |---------|------------|------|-------------| -| `/code:start` | `code:start` | `code-et-implementer/commands/start.md` | Scaffold a new pure-Rust full-stack project; runs `cargo update` post-scaffold | -| `/code:install-ci` | `code:install-ci` | `code-et-implementer/commands/install-ci.md` | Retrofit the CI audit gate onto an existing Rust repo | +| `/code:start` | `code:start` | `code-et-implementer/commands/start.md` | Scaffold a new Bun + Hono + Drizzle TypeScript project; deep-modules shape | +| `/code:install-ci` | `code:install-ci` | `code-et-implementer/commands/install-ci.md` | Drop the audit GitHub workflow into an existing TS repo | | `/code:fix` | `code:fix` | `code-et-implementer/commands/fix.md` | Single-bug intake → Task Brief; user implements directly | -| `/code:plan` | `code:plan` | `code-et-implementer/commands/plan.md` | One extended turn: refined brief → PRD on disk → vertical-slice tasks (3 checkpoints) | +| `/code:plan` | `code:plan` | `code-et-implementer/commands/plan.md` | Refined brief → PRD on disk → vertical-slice tasks (3 checkpoints) | | `/code:ship` | `code:ship` | `code-et-implementer/commands/ship.md` | Parallel worktree agents + post-merge audit + 1-pass auto-retry on CRITICAL/HIGH | -| `/code:review` | `code:review` | `code-et-implementer/commands/review.md` | Pre-merge gate — full audit + diff review (delegates to engineering plugin) | +| `/code:review` | `code:review` | `code-et-implementer/commands/review.md` | Pre-merge gate — local audit + diff review (delegates to engineering plugin) | + +## Plugin — Doctrine + +| File | Description | +|------|-------------| +| `code-et-implementer/docs/architecture.md` | Deep modules, dependency categories, seam discipline. Vocabulary from Ousterhout (deep modules) and Feathers (seams). | +| `code-et-implementer/docs/anti-slop.md` | 4 elements (shallow modules, duplication, defensive over-programming, drift), 5 categories, 8 hard rules. | +| `code-et-implementer/docs/testing.md` | Interface-as-test-surface; module-interface, HTTP-seam, e2e patterns with `bun test`. Mirror-test ban. | ## Plugin — Hooks | File | Description | |------|-------------| -| `code-et-implementer/hooks/hooks.json` | Hook bindings: `PermissionRequest`, `SubagentStop`, `SessionStart`, `TaskCreated`, `TaskCompleted`, `PreCompact` | +| `code-et-implementer/hooks/hooks.json` | One hook: `PermissionRequest` auto-approves Read/Grep/Glob/LSP. v4's TaskCreate regex, PRD-resume, SubagentStop audit, etc. all dropped — trust-the-model. | ## Plugin — Scripts | Script | File | Description | |--------|------|-------------| | Auto-approve readonly | `code-et-implementer/scripts/auto-approve-readonly.sh` | Auto-approves Read/Grep/Glob/LSP for agents | -| Pre-compact PRD | `code-et-implementer/scripts/pre-compact-prd.sh` | Injects open-stories summary before compaction | -| Resolve PRD | `code-et-implementer/scripts/resolve-prd.sh` | Branch → `plans/YYYY-MM-DD-<slug>.md` lookup | -| Run tests | `code-et-implementer/scripts/run-tests.sh` | Runs test + lint with timeout and cmux notifications | -| Session start PRD | `code-et-implementer/scripts/session-start-prd.sh` | Injects 3-line PRD pointer at session start | -| Task complete | `code-et-implementer/scripts/task-complete.sh` | Desktop notification + agent attribution on task done | -| Task-created tag check | `code-et-implementer/scripts/task-created-tag-check.sh` | Enforces `user_story` tag on feature-lane tasks | -| Verify gate | `code-et-implementer/scripts/verify-gate.sh` | SubagentStop verification gate | -## Plugin — Tests +## Plugin — Templates | Path | Description | |------|-------------| -| `code-et-implementer/tests/` | Bats test suite for hook scripts | +| `code-et-implementer/templates/typescript/` | The full TS scaffold copied by `/code:start`. `package.json`, `tsconfig.json`, `biome.json`, `drizzle.config.ts`, an example `greetings` module + HTTP route + tests, and a composition-root `main.ts`. | +| `code-et-implementer/templates/shared/.github/workflows/code-et-audit.yml` | CI workflow — Bun + Biome + tsc + bun audit + bun test. | +| `code-et-implementer/templates/shared/CLAUDE.md.template` | Per-project CLAUDE.md scaffolded into each new project. | +| `code-et-implementer/templates/shared/UPDATING.md` | Maintainer checklist for keeping the template aligned with upstream TS ecosystem. | ## Plugin — Config | File | Description | |------|-------------| -| `code-et-implementer/.claude-plugin/plugin.json` | Plugin manifest (name, version, description) — **must stay in sync with CHANGELOG version** | -| `code-et-implementer/.claude-plugin/settings.json` | Plugin settings | -| `code-et-implementer/CLAUDE.md` | Plugin-level Claude instructions | +| `code-et-implementer/.claude-plugin/plugin.json` | Plugin manifest (name, version, description) — **must stay in sync with CHANGELOG version**. | +| `code-et-implementer/.claude-plugin/settings.json` | Plugin settings (plans dir, permissions, spinner tips). | +| `code-et-implementer/CLAUDE.md` | Plugin-level Claude instructions. | ## Repo Root | File | Description | |------|-------------| -| `README.md` | Plugin documentation, workflow diagrams, install instructions (≤210 lines) | -| `CHANGELOG.md` | Version history — leading entry must match `plugin.json` version | -| `.claude-plugin/marketplace.json` | Marketplace manifest (points at `code-et-implementer/`) | -| `.claude/settings.json` | Project-level Claude Code settings | -| `.claude/settings.local.json` | Local-only Claude Code settings | -| `plans/` | PRDs and plan artifacts (`YYYY-MM-DD-<slug>.md`) | +| `README.md` | Plugin documentation, workflow diagrams, install instructions. | +| `CHANGELOG.md` | Version history — leading entry must match `plugin.json` version. | +| `.claude-plugin/marketplace.json` | Marketplace manifest (points at `code-et-implementer/`). | +| `plans/` | PRDs and plan artifacts (`YYYY-MM-DD-<slug>.md`). | --- ## Hot Paths -Files that run on every primary user action vs files that run once per session/install. -Used to gauge blast radius of a change. - | Path type | Files | |-----------|-------| | Per-readonly-permission-request (Read/Grep/Glob/LSP) | `scripts/auto-approve-readonly.sh` | -| Per-task-creation | `scripts/task-created-tag-check.sh` | -| Per-task-completion | `scripts/task-complete.sh` | -| Per-subagent-stop | `scripts/verify-gate.sh` → `scripts/run-tests.sh` | -| Per-session-start | `scripts/session-start-prd.sh` | -| Per-compact (rare) | `scripts/pre-compact-prd.sh` | | Per-`/code:*`-invocation | `commands/<name>.md` | | Once on install | `.claude-plugin/plugin.json`, `.claude-plugin/settings.json` | -A regression in `auto-approve-readonly.sh` blocks every read permission prompt; a regression in `verify-gate.sh` blocks every subagent finish. Treat changes to either as wide-blast-radius — run the bats suite under `tests/` before commit. +A regression in `auto-approve-readonly.sh` blocks every read permission prompt — treat changes there as wide-blast-radius. ## Landmines @@ -94,20 +87,21 @@ A regression in `auto-approve-readonly.sh` blocks every read permission prompt; | Never push directly to `main` | Branch + PR only — see `code-et-implementer/CLAUDE.md` | | Never force push | Rebase locally, push normally | | Never bump only `CHANGELOG.md` without `plugin.json` | The 3.7.0 release shipped with manifest stuck on 3.6.1 — installs reported the old version | -| Never call `/ultraplan` via `Skill("ultraplan", …)` | It's a built-in Claude Code command, not a callable skill (3.6.1 fix) | +| Never call `/ultraplan` via `Skill("ultraplan", …)` | It's a built-in Claude Code command, not a callable skill | | Never `git worktree add` from inside `/code:ship` | Use `Agent(isolation: "worktree")` so the subagent forks cleanly | | Never have a subagent merge its own branch | The subagent has no view of the parent feature branch — orchestrator merges | -| Never serialize independent `/code:ship` tasks | Dispatch them in a single message with multiple `Agent` calls | +| Never serialize independent `/code:ship` tasks | Dispatch in a single message with multiple `Agent` calls | +| Never extract a shallow helper "for testability" | Apply the deletion test first — would removal concentrate complexity, or just move it? | ## Module Invariants | Module | Invariant | |--------|-----------| -| `commands/*.md` | Frontmatter must declare `effort`; commands default to `xhigh` (4.7 default); `/code:install-ci` and `/code:review` are the documented exceptions (`high`) | +| `commands/*.md` | Frontmatter must declare `effort`; defaults to `xhigh` for plan/ship/start, `high` for fix/review/install-ci. | | `commands/ship.md` | Each task → one `Agent(isolation: "worktree")` call; orchestrator owns merge + worktree cleanup, subagent does not. Post-merge audit auto-retries once on CRITICAL/HIGH. | | `commands/plan.md` | Every task's `metadata.rationale` is mandatory; subagents start cold and need the *why*. PRD lands on disk before task decomposition (Phase 2 → Phase 3 checkpoint). | -| `hooks/hooks.json` | All script paths use `${CLAUDE_PLUGIN_ROOT}` — never hard-code `code-et-implementer/scripts/...`. Add hooks only when (a) no CLAUDE.md instruction can replace them and (b) the per-event token cost is justified by the value. | -| `FILE-REFERENCE.md` | Refresh only after a PR merges to main with structural changes (`*/page.tsx`, `*/route.ts`, `*/layout.tsx`, new top-level apps/packages). Do not edit on a feature branch — it tracks merged state. | -| `scripts/*.sh` | Must exit 0 on the no-op path; non-zero exit blocks the tool/event they hook into | -| `.claude-plugin/plugin.json` | `version` must match the leading `CHANGELOG.md` entry — bump together or not at all | -| Feature lane tasks | Must carry `user_story: US-N \| AC-N.M \| chore:<reason>`; enforced by `task-created-tag-check.sh` | +| `docs/architecture.md` | Vocabulary is non-negotiable: module / interface / seam / adapter / depth / leverage / locality. Don't drift into "component" / "service" / "API" / "boundary". | +| `hooks/hooks.json` | All script paths use `${CLAUDE_PLUGIN_ROOT}`. Add hooks only when (a) no doctrine/skill instruction can replace them and (b) the per-event token cost is justified by the value. | +| `templates/typescript/` | The `greetings` module is the canary: any template change must keep `bun run audit` green after `/code:start` runs. | +| `.claude-plugin/plugin.json` | `version` must match the leading `CHANGELOG.md` entry. | +| Feature lane tasks | Must carry `user_story: US-N \| AC-N.M \| chore:<reason>` (one tag, not alternation). | diff --git a/README.md b/README.md index 6b14620..19258f6 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,21 @@ # code-et -Pure-Rust Clean Architecture workflow for Claude Code. **One stack, six commands, no slop.** +Lightweight TypeScript workflow for Claude Code, built on **deep modules**. **One stack, six commands, no slop.** -Stack: `axum + sqlx + Dioxus 0.7+ + tokio`. One frontend codebase renders to web, desktop, and mobile. Database: SQLite for local, Postgres on GCP Cloud SQL for prod. Always-latest semver-compatible deps via post-scaffold `cargo update`. +Stack: `Bun + Hono + Drizzle + Biome`. Database: SQLite out of the box (Bun's built-in). Postgres and a web frontend (Vite + React, Solid, etc.) are deliberately manual add-ons rather than flags — they're short additions, and putting them behind flags would hide decisions the user should make explicitly. Architecture vocabulary — *module / interface / seam / adapter / depth / leverage / locality* — is standard software-engineering terminology drawn from Ousterhout's *A Philosophy of Software Design* (deep modules) and Feathers' *Working Effectively with Legacy Code* (seams). -> v4.0.0 is a deliberate rewrite. The plugin is biased toward starting fresh: `/code:start` scaffolds the four-crate workspace; the rest of the commands keep you in flow. +> **v5.0.0** is a deliberate rewrite. v4.x was Rust + Clean Architecture; v5 is TypeScript + deep modules. The command surface (`/code:start … /code:review`) is unchanged. ## The six commands | Command | What it does | |---|---| -| `/code:start <name>` | Scaffold a new pure-Rust full-stack project — 4 crates (`domain`, `application`, `infrastructure`, `interface`), 4 apps (`server`, `web`, `desktop`, `mobile`), CI gate, latest deps. | -| `/code:install-ci` | Drop the audit GitHub workflow + layer-deps validator into an existing Rust repo. | -| `/code:fix "<bug>"` | Single-bug intake → Task Brief (with per-file `Layer`). You implement directly. | +| `/code:start <name>` | Scaffold a Bun + Hono + Drizzle project. Deep-modules shape: `src/modules/<name>/index.ts` is the interface. SQLite by default — Postgres and a web frontend are manual add-ons, deliberately not flags. | +| `/code:install-ci` | Drop the audit GitHub workflow into an existing TS repo. | +| `/code:fix "<bug>"` | Single-bug intake → Task Brief (with per-file `Module`). You implement directly. | | `/code:plan "<idea>"` | One extended turn: refined brief → PRD on disk → vertical-slice tasks. Three checkpoints — interrupt at any. | | `/code:ship` | Execute pending tasks in parallel worktree-isolated agents → audit → 1 auto-retry on CRITICAL/HIGH. | -| `/code:review` | Pre-merge gate: full local audit + diff review. Delegates to engineering plugin's `code-review` skill. | +| `/code:review` | Pre-merge gate: local audit + diff review. Delegates to engineering plugin's `code-review` skill if installed. | That's it. `/commit-push-pr` (from the `commit-commands` plugin) ships the PR. @@ -23,13 +23,13 @@ That's it. `/commit-push-pr` (from the `commit-commands` plugin) ships the PR. ``` NEW PROJECT (one-time) - /code:start myapp [--db sqlite|postgres] [--targets web,desktop,mobile,server] + /code:start myapp ↓ - cd myapp && cp .env.example .env && just db-migrate && just run-server + cd myapp && cp .env.example .env && bun run db:migrate && bun run dev DAILY - Bug? /code:fix "..." → user implements 1-3 files → /commit-push-pr + Bug? /code:fix "..." → user implements 1–3 files → /commit-push-pr Feature? /code:plan "..." → /code:ship → /code:review → /commit-push-pr ↓ audit auto-runs @@ -40,72 +40,59 @@ DAILY Two lanes. No mid-points. `/code:fix` does **not** chain into `/code:plan` or `/code:ship` — bugs that span vertical slices are features in disguise; write a PRD. -## Architecture (enforced) +## Architecture — deep modules -The four-crate workspace **is** the architecture. Imports point inward; the `Cargo.toml` deps enforce it. +There is **no fixed layer taxonomy**. Modules grow around interfaces. The deletion test — *would removing this module make complexity vanish, or reappear across N callers?* — decides whether something earns its place. ``` -crates/ - domain/ Entities, value objects, errors. Pure logic. NO workspace deps. - application/ Use cases + ports (traits). Orchestrates domain. - infrastructure/ Adapters: sqlx repos, HTTP clients, secrets. Implements ports. - interface/ axum handlers + Dioxus components. Composition lives in apps/. - -apps/ - server/ axum + dioxus-fullstack SSR (always present) - web/ dioxus-web (WASM) - desktop/ dioxus-desktop - mobile/ dioxus-mobile (best-effort) +src/ + modules/ + <module-name>/ + index.ts The interface. Public exports + types only. + <impl>.ts The implementation. + <name>.test.ts Tests cross the same seam callers do. + db/ Drizzle schema + migrations. + http/ + app.ts Hono app — wires modules to routes. + routes/<r>.ts One file per resource. + config.ts Zod-validated env loading. + main.ts Composition root — the only place adapters are wired in. ``` -Each `apps/<name>/main.rs` is the **composition root** — the only place that instantiates concrete `infrastructure` types and wires them into `interface` ports. - Doctrine lives in [`code-et-implementer/docs/`](code-et-implementer/docs/): -- [`architecture.md`](code-et-implementer/docs/architecture.md) — Clean Architecture, dependency rule, secrets, security checklist. -- [`anti-slop.md`](code-et-implementer/docs/anti-slop.md) — 4 elements (dead code, duplication, complexity, drift) + 5 categories + 6 hard rules. -- [`testing.md`](code-et-implementer/docs/testing.md) — per-layer test matrix, mirror-test ban, contract tests at boundaries. +- [`architecture.md`](code-et-implementer/docs/architecture.md) — deep modules, dependency categories, seam discipline. +- [`anti-slop.md`](code-et-implementer/docs/anti-slop.md) — 4 elements, 5 categories, 8 hard rules. +- [`testing.md`](code-et-implementer/docs/testing.md) — interface-as-test-surface, deep-module test patterns, mirror-test ban. ## CI gate `.github/workflows/code-et-audit.yml` runs on every PR + push to main: -1. `cargo fmt --check` -2. `cargo clippy --workspace --all-targets --all-features -- -D warnings` -3. `scripts/layer-deps-validator.sh` (defence-in-depth on the layer rule) -4. `cargo machete` (unused deps) -5. `cargo audit` (security advisories) -6. `cargo deny check` (license + bans + sources) -7. `cargo nextest run --workspace --all-features` +1. `biome check .` — lint + format +2. `tsc --noEmit` — type safety +3. `bun audit` — dependency advisories +4. `bun test` — unit + integration + http-seam tests -Local mirror: `just audit`. `/code:ship` runs the same pipeline post-merge with a 1-pass auto-fix retry on CRITICAL/HIGH findings. **A green audit is the merge gate** — no manual override. +Local mirror: `bun run audit`. `/code:ship` runs the same pipeline with a 1-pass auto-fix retry on CRITICAL/HIGH findings. **A green audit is the merge gate** — no manual override. -> **GitHub Actions:** the workflow uses public actions (`actions/checkout`, `dtolnay/rust-toolchain`, `Swatinem/rust-cache`, `taiki-e/install-action`, `bnjbvr/cargo-machete`, `rustsec/audit-check`, `EmbarkStudios/cargo-deny-action`). GitHub fetches them automatically on first run — nothing to install. `secrets.GITHUB_TOKEN` is auto-provided. Just push the repo and the gate runs. +> **GitHub Actions:** uses `actions/checkout`, `oven-sh/setup-bun`, `actions/cache`. All public; GitHub fetches them automatically. `secrets.GITHUB_TOKEN` is auto-provided. ## Install ``` -# Required companions -/plugin marketplace add knowledge-work-plugins -/plugin install engineering@knowledge-work-plugins -/plugin install rust-analyzer-lsp@claude-plugins-official +# Required — replace <owner> with the org/repo hosting your code-et fork +/plugin marketplace add <owner>/code-et +/plugin install code@code-et -# Recommended +# Recommended companions +/plugin install engineering@knowledge-work-plugins # code-review, tech-debt, testing-strategy, system-design /plugin install commit-commands@claude-plugins-official -/plugin install code-review@claude-plugins-official /plugin install claude-md-management@claude-plugins-official - -# code-et -/plugin marketplace add Emerging-Tech-Visma/code-et -/plugin install code@code-et ``` -The `engineering` plugin provides `code-review`, `tech-debt`, `testing-strategy`, `system-design` skills that code-et delegates to. `rust-analyzer-lsp` powers symbol-level precision in `/code:fix` and `/code:plan`. - ### Local development -Test plugin changes without the install/update/restart cycle: - ```bash claude --plugin-dir /path/to/code-et/code-et-implementer ``` @@ -115,53 +102,20 @@ Type `/code:` to confirm all six commands appear. ## Prerequisites - **Claude Code** — `npm install -g @anthropic-ai/claude-code` -- **Rust toolchain** — `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh` (Rust 2024 / 1.85+) -- **GitHub CLI (`gh`)** — for PRs and issues ([install](https://cli.github.com/)) -- **Audit tools**: - ``` - cargo install --locked cargo-machete cargo-audit cargo-deny cargo-nextest sqlx-cli dioxus-cli - ``` - Or pass `--install-tools` to `/code:start` and the bootstrap will run this for you. - -### LSP (recommended) +- **Bun** — `curl -fsSL https://bun.sh/install | bash` (Bun 1.1+) +- **GitHub CLI (`gh`)** — for PRs ([install](https://cli.github.com/)) -Add to `~/.claude/settings.json`: - -```json -{ - "env": { "ENABLE_LSP_TOOL": "1" } -} -``` - -Install `rust-analyzer`: - -``` -rustup component add rust-analyzer -# or: brew install rust-analyzer -``` - -Verify with: *"use LSP to find the definition of `<symbol>`"* — if it returns a `file:line`, all three layers (env var, plugin, binary) are wired. +The audit gate uses Bun's built-ins for everything (test, audit, package install). Biome and TypeScript ship as dev-dependencies in the scaffolded `package.json`. ## Always-latest dependencies -`/code:start` runs `cargo update` post-scaffold so every dep is at its latest semver-compatible patch. Caret pins (`dioxus = "0.7"`, `axum = "0.8"`, `sqlx = "0.8"`, `tokio = "1"`, `tower = "0.5"`, etc.) deliver the latest minor/patch automatically. - -**Major bumps** (e.g., `dioxus 0.7 → 0.8` once it ships) need a manual `cargo upgrade` (cargo-edit) and a smoke test: - -```bash -cargo install cargo-edit -cargo upgrade --workspace -just audit # smoke -``` - -`cargo audit` (CI step 5) catches yanked or vulnerable pins on every PR. `cargo machete` (step 4) catches accumulating unused deps. +The template's `package.json` pins to caret (`^4.6.0`) so patches and compatible minors flow through `bun install`. Major bumps need a manual `bun update` plus a `bun run audit` smoke. The CI gate's `bun audit` step catches yanked or vulnerable pins on every PR. ## Settings ```json { "env": { - "ENABLE_LSP_TOOL": "1", "CLAUDE_CODE_TASK_LIST_ID": "<your-project-tasks>" } } @@ -174,45 +128,31 @@ just audit # smoke | Plugin | What it gives you | |---|---| | `engineering` (knowledge-work-plugins) | `code-review`, `tech-debt`, `testing-strategy`, `system-design` skills — delegated to from code-et's CLAUDE.md | -| `rust-analyzer-lsp` (official) | Symbol-level precision for `/code:fix` and `/code:plan` | | `commit-commands` (official) | `/commit`, `/commit-push-pr`, `/clean_gone` | | `code-review` (official) | Multi-agent PR review | | `claude-md-management` (official) | `/revise-claude-md`, `/claude-md-improver` | -## Deploy & upload — always via scripts - -`/code:start` ships `scripts/deploy.sh` and `scripts/upload.sh` in every scaffolded project, plus `just deploy <env>` and `just upload <kind> <env>` targets in the justfile. **All deploys and uploads go through these scripts** — never raw `cargo`, `docker`, `gcloud`, or `gsutil` typed into a shell. +## How it stays simple -The scripts are starting points: pre-flight (clean tree + audit gate) → build container → run migrations → roll out → smoke check. Host-specific lines (`gcloud run deploy …`, `gsutil rsync …`, `flyctl deploy …`) are marked `# TODO:` blocks — fill in once for your project. From then on every deploy is one command: +1. **One stack.** Bun + Hono + Drizzle. No flags for "TS or Rust", no legacy branches. +2. **Six commands.** Most days you use three (`/code:fix`, `/code:plan`, `/code:ship`). +3. **One philosophy.** Deep modules with shared vocabulary. The deletion test is the decision rule. +4. **Doctrine, not docs.** Three short markdown files (`architecture`, `anti-slop`, `testing`) loaded on demand. +5. **CI is the gate.** Local hooks give fast feedback; a green CI is the merge requirement. +6. **Vertical slices, not layers as tasks.** Each task touches every seam it needs to touch — `/code:plan` rejects horizontal tasks at decomposition time. -``` -just deploy staging -just deploy prod -just upload web prod -``` +## Migrating from v4.x (Rust) -The bundled CLAUDE.md template encodes the rule: *if you find yourself typing the underlying command directly, stop and add the missing step to the script instead.* +v5 is a different stack. The command surface is preserved, but projects scaffolded with v4 (`/code:start` → 4-crate Rust workspace) are not migrated automatically. Options: -## How it stays simple +- **Keep using v4 for existing Rust projects.** Pin to `5.0.0`-prior in your marketplace; v4's commands continue to work. +- **Start a fresh TS project with v5.** Run `/code:start <name>` in a new directory. -1. **One stack.** axum + sqlx + Dioxus + tokio. No flags for "TS or Rust", no legacy branches. -2. **Six commands.** Most days you use three (`/code:fix`, `/code:plan`, `/code:ship`). -3. **Doctrine, not docs.** Three short markdown files (`architecture`, `anti-slop`, `testing`) loaded on demand. -4. **CI is the gate.** Local hooks give fast feedback; a green CI is the merge requirement. No process can override it. -5. **Vertical slices, not layers as tasks.** Each task touches every layer it needs to touch — `/code:plan` rejects horizontal tasks at decomposition time. +v4's final state is commit `c5bad00` on `main`. Pin or branch from there if you need to keep iterating on the Rust workflow. -## Migrating from v3.x +## Influences -| v3.x command | v4.x replacement | -|---|---| -| `/code:bootstrap` | `/code:start` | -| `/code:go` | `/code:fix` | -| `/code:grill` + `/code:prd` + `/code:plan-issue` | `/code:plan` (one extended turn, three checkpoints) | -| `/code:implement` | `/code:ship` | -| `/code:audit` | `just audit` (or runs automatically inside `/code:ship`) | -| `/code:install-ci` | `/code:install-ci` (unchanged) | - -The doctrine files (`docs/architecture.md`, `docs/anti-slop.md`, `docs/testing.md`) and the CI gate template are unchanged — same architecture, fewer commands. +The architecture vocabulary (module, interface, seam, adapter, depth, leverage, locality) is standard software-engineering terminology — **deep modules** are from Ousterhout's *A Philosophy of Software Design*; **seams** are from Feathers' *Working Effectively with Legacy Code*; the **deletion test** is a common refactoring heuristic. The vertical-slice + TDD shape of `/code:plan` and `/code:ship` follows Kent Beck's "tracer bullet" / TDD practice. ## License diff --git a/code-et-implementer/.claude-plugin/plugin.json b/code-et-implementer/.claude-plugin/plugin.json index 1ad5b7a..fe940d4 100644 --- a/code-et-implementer/.claude-plugin/plugin.json +++ b/code-et-implementer/.claude-plugin/plugin.json @@ -1,19 +1,20 @@ { "name": "code", - "version": "4.3.1", - "description": "Pure-Rust Clean Architecture workflow. Six commands: start, fix, plan, ship, review, install-ci. Always-latest deps, CI audit gate, anti-slop enforced.", + "version": "5.0.0", + "description": "Lightweight TypeScript workflow built on deep modules. Six commands: start, fix, plan, ship, review, install-ci. Stack: Bun + Hono + Drizzle + Biome. Deep-modules vocabulary from Ousterhout and Feathers.", "author": { - "name": "Kennet Kusk" + "name": "code-et" }, "license": "MIT", "keywords": [ "coding", "workflow", - "rust", - "clean-architecture", - "dioxus", - "axum", - "sqlx", + "typescript", + "deep-modules", + "bun", + "hono", + "drizzle", + "biome", "anti-slop", "task-driven", "agents", diff --git a/code-et-implementer/.claude-plugin/settings.json b/code-et-implementer/.claude-plugin/settings.json index 4ecc54e..fc6cf09 100644 --- a/code-et-implementer/.claude-plugin/settings.json +++ b/code-et-implementer/.claude-plugin/settings.json @@ -1,7 +1,7 @@ { "plansDirectory": "plans", "permissions": { - "allow": ["Bash(git:*)", "Bash(gh:*)", "Bash", "Edit", "Write"] + "allow": ["Bash(git:*)", "Bash(gh:*)", "Bash(bun:*)", "Bash(bunx:*)", "Bash", "Edit", "Write"] }, "spinnerTipsOverride": [ "Press ctrl+t to view task progress", @@ -10,6 +10,7 @@ "Use commit-commands plugin for PRs (/commit-push-pr)", "Stuck? Run /code:ship again to resume", "/code:plan writes the PRD to disk before decomposing tasks", - "/code:ship auto-retries 1 fix-pass on CRITICAL/HIGH audit findings" + "/code:ship auto-retries 1 fix-pass on CRITICAL/HIGH audit findings", + "Apply the deletion test before extracting a new module" ] } diff --git a/code-et-implementer/CLAUDE.md b/code-et-implementer/CLAUDE.md index 0183bbc..80c7a1f 100644 --- a/code-et-implementer/CLAUDE.md +++ b/code-et-implementer/CLAUDE.md @@ -1,6 +1,6 @@ -# code-et v4.x +# code-et v5.x -Pure-Rust Clean Architecture workflow. Stack: `axum + sqlx + Dioxus 0.7+ + tokio`. One frontend codebase renders to web, desktop, and mobile. +Lightweight TypeScript workflow built on **deep modules**. Stack: `Bun + Hono + Drizzle + Biome`. Architecture vocabulary — *module / interface / seam / adapter / depth / leverage / locality* — is standard software-engineering terminology drawn from Ousterhout's *A Philosophy of Software Design* and Feathers' *Working Effectively with Legacy Code*. ## Git Rules @@ -18,15 +18,32 @@ Pure-Rust Clean Architecture workflow. Stack: `axum + sqlx + Dioxus 0.7+ + tokio | Feature, end-to-end | `/code:plan` (idea → PRD → tasks) → `/code:ship` (parallel agents + audit) | | Pre-merge gate | `/code:review` (audit + diff review) | -For commits and PRs use `commit-commands` plugin (`/commit`, `/commit-push-pr`). +For commits and PRs use the `commit-commands` plugin (`/commit`, `/commit-push-pr`). ## Workflow — two lanes -**Bug lane.** `/code:fix` produces a Task Brief and stops. Most bugs are 1-3 file edits — no orchestration needed. If the work spans multiple coherent vertical slices (UI ↔ logic ↔ API ↔ DB), it's a feature in disguise — write a PRD instead. +**Bug lane.** `/code:fix` produces a Task Brief and stops. Most bugs are 1–3 file edits — no orchestration needed. If the work spans multiple coherent vertical slices (HTTP seam + module + DB for a real feature), it's a feature in disguise — write a PRD instead. -**Feature lane.** `/code:plan` (one extended turn: refined brief → PRD on disk → vertical-slice tasks) → `/code:ship` (parallel worktree agents + post-merge audit + 1 auto-retry on CRITICAL/HIGH) → `/code:review` (pre-merge gate) → `/commit-push-pr`. +**Feature lane.** `/code:plan` (refined brief → PRD on disk → vertical-slice tasks) → `/code:ship` (parallel worktree agents + audit + 1 auto-retry on CRITICAL/HIGH) → `/code:review` (pre-merge gate) → `/commit-push-pr`. -Each task is a **vertical slice**: UI ↔ logic ↔ API ↔ DB, end-to-end testable. When a slice supersedes existing code, deletion of the old code is part of the same commit — no parallel utilities, no `// TODO: remove old X`. +Each task is a **vertical slice**: HTTP route → module → DB (or whatever path the slice actually traverses), end-to-end testable. When a slice supersedes existing code, deletion of the old code is part of the same commit — no parallel utilities, no `// TODO: remove old X`. + +## Vocabulary — use these terms exactly + +- **Module** — anything with an interface and an implementation. Function, file, folder, package. +- **Interface** — everything a caller must know: types, invariants, error modes, ordering. Not just the type signature. +- **Implementation** — the body of code inside. +- **Depth** — leverage at the interface; a lot of behaviour behind a small interface. +- **Seam** — where an interface lives. +- **Adapter** — a concrete thing satisfying an interface at a seam. + +**Three principles:** + +1. **Deletion test.** Imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across callers, the module earned its keep. +2. **Interface is the test surface.** Tests cross the same seam callers do. +3. **One adapter = hypothetical seam. Two adapters = real seam.** + +Don't drift into "component", "service", "API", or "boundary". ## Task Metadata Convention @@ -34,68 +51,69 @@ Tasks created with `TaskCreate` carry: ``` metadata: { - verification: "cargo nextest run && cargo clippy --all-targets -- -D warnings", + verification: "bun test && bun run typecheck", files: [ - {"path": "crates/<layer>/src/path/to/file.rs", "symbol": "Type::method", "line": 42, "op": "modify"}, - {"path": "crates/<layer>/src/new.rs", "symbol": "NewType", "op": "add"}, - {"path": "crates/<layer>/src/legacy.rs", "symbol": "old_fn", "line": 89, "op": "delete"} + {"path": "src/modules/<m>/index.ts", "symbol": "Orders.place", "line": 42, "op": "modify"}, + {"path": "src/http/routes/orders.ts", "symbol": "placeOrder", "op": "add"}, + {"path": "src/modules/<m>/legacy.ts", "symbol": "oldPlace", "line": 89, "op": "delete"} ], expected_outcome: "what success looks like", - rationale: "why this task exists — the constraint or decision driving it", + rationale: "why this slice exists — the constraint driving it", user_story: "US-N" | "AC-N.M" | "chore:<reason>", - layer: "domain" | "application" | "infrastructure" | "interface" | "chore" + module: "orders" } ``` -`files[]` entries: `path` + `op` always required; `symbol` required for `modify|replace|delete`; `line` is an LSP-resolved hint (drift-tolerant — `symbol` is the contract). Full schema and validation rules in `commands/plan.md` §"TaskCreate metadata". +`files[]` entries: `path` + `op` always required; `symbol` required for `modify|replace|delete`; `line` is a drift-tolerant hint — `symbol` is the contract. Full schema in `commands/plan.md` §"TaskCreate metadata". -`rationale` is mandatory. Subagents in `/code:ship` start cold — they need the *why*, not just the *what*, to make judgment calls. +`rationale` is mandatory — implementer subagents in `/code:ship` start cold. -`layer` is mandatory. Each *file* declares its layer; vertical slices may span layers. +`module` is free-form lowercase (`orders`, `payments`, `auth`, `chore`) — no enforced taxonomy. Matches the folder name in `src/modules/`. ## Model Assignments -Different roles in the workflow run on different models. The `Agent` tool's `model` param accepts `opus` | `sonnet` | `haiku` — each resolves to the latest of that family. - | Role | Model | Where | |---|---|---| | Orchestrator (`/code:plan`, `/code:ship`) | `opus` (4.7) | Inherited; multi-step coordination + judgment. | | Per-task implementer | `sonnet` (4.6) | `/code:ship` — routine vertical-slice coding from a complete brief. | -| Per-task reviewer | `opus` (4.7) | `/code:ship` — diff review via engineering plugin's `code-review` skill (falls back to inline 5-area checklist if the plugin isn't installed). Bugs the reviewer misses fail silently; the 8-pt SWE-bench gap matters here. | -| Per-task review fix-pass | `opus` (4.7) | `/code:ship` — applies reviewer findings; same model as reviewer for consistent judgment across find/fix. | -| Post-merge audit fix-pass | `opus` (4.7) | `/code:ship` — judgment on layer slips, dep advisories, test failures. | +| Per-task reviewer | `opus` (4.7) | `/code:ship` — diff review via engineering plugin's `code-review` (falls back to inline 5-area checklist). Bugs the reviewer misses fail silently. | +| Per-task review fix-pass | `opus` (4.7) | `/code:ship` — applies reviewer findings; same model for find/fix consistency. | +| Audit fix-pass | `opus` (4.7) | `/code:ship` — judgment on type errors, dep advisories, test failures. | | Explore (breadth searches) | `haiku` (4.5) | `/code:plan`, `/code:fix` — cheap parallel discovery. | -The principle: heavy lifting (planning, judgment, review) on Opus; routine coding from a complete brief on Sonnet; breadth gathering on Haiku. +Heavy lifting (planning, judgment, review) on Opus; routine coding from a complete brief on Sonnet; breadth gathering on Haiku. ## Code Standards -- Rust 2024 edition; `cargo clippy --all-targets -- -D warnings`; `cargo fmt --check`. -- Max 600 lines per file. -- Compose at app boundaries (`apps/<name>/main.rs`); never instantiate `infrastructure` inside `interface` — pass via constructor or DI trait. -- All SQL through `sqlx::query!` / `query_as!` (compile-time-checked). Raw `sqlx::query` is forbidden in production code. -- Forward-only migrations under `migrations/`. Each rollback is its own forward migration. +- TypeScript strict (`strict: true`, `noUncheckedIndexedAccess: true`). +- `biome check .` clean, `tsc --noEmit` clean. +- Max ~400 lines per file (soft guidance — split when adding behaviour, not by line count alone). +- Compose at `src/main.ts` (the composition root); no module instantiates another module's concrete implementation. +- All HTTP input parsed with Zod at the route seam. Modules trust their callers within the process boundary. +- All DB access through Drizzle — no raw SQL string-concatenation. +- Forward-only migrations under `src/db/migrations/`. Each rollback is its own forward migration. -## Clean Architecture — controlling rules +## Deep Modules — controlling rules -Apply Clean Architecture per [`docs/architecture.md`](docs/architecture.md). Each new or modified file declares its layer (`domain` | `application` | `infrastructure` | `interface` | `chore`) in `metadata.layer`. Imports point inward; the workspace `Cargo.toml` deps already enforce this — violating imports fail at `cargo build`. +Apply the doctrine in [`docs/architecture.md`](docs/architecture.md). Each new or modified file belongs to a named module (`metadata.module` in tasks). Modules grow organically; no fixed taxonomy. -UI: Dioxus 0.7+ for web, desktop, and mobile from one component tree. DB: Postgres on GCP Cloud SQL for prod, SQLite for local. `sqlx` with `query!` (compile-time-checked); never raw SQL. Forward-only migrations. +UI: optional Vite + React frontend in `web/`. Drop it if API-only. +DB: SQLite for local + dev, Postgres for prod. Same Drizzle schema; the migrator emits dialect-aware SQL. -Delegation map for human-judgment passes (engineering plugin: `knowledge-work-plugins/engineering`): +**Delegation map for human-judgment passes** (engineering plugin: `knowledge-work-plugins/engineering`): - security / code review → `code-review` skill - testing strategy → `testing-strategy` skill - tech-debt triage → `tech-debt` skill -- ADR / system design → `system-design` skill or `/architecture` -- LSP precision → `rust-analyzer-lsp` companion plugin +- ADR / system design → `system-design` skill +- architecture refactors → engineering plugin's `improve-codebase-architecture` skill (if installed) -Anti-slop hard rules: see [`docs/anti-slop.md`](docs/anti-slop.md). Rule of Three. No mirror tests. No defensive validation at trusted boundaries. +**Anti-slop hard rules:** see [`docs/anti-slop.md`](docs/anti-slop.md). Deletion test before extraction. Rule of Three. No mirror tests. No defensive validation at trusted seams. ## Brevity Drop filler ("just", "simply", "really"), hedging ("perhaps", "maybe"), pleasantries ("Sure!", "Happy to help"). Fragments over sentences when meaning is clear. Pattern: `[thing] [action] [reason]. [next].` -Task subjects: `<verb> <object>` ≤50 chars. ✗ "I will implement the auth middleware". ✓ "add auth middleware in interface/http/middleware.rs". +Task subjects: `<verb> <object>` ≤50 chars. ✗ "I will implement the auth middleware". ✓ "add auth middleware in src/http/routes/auth.ts". Never compress: code, file paths, URLs, error messages, security warnings. @@ -103,19 +121,11 @@ Never compress: code, file paths, URLs, error messages, security warnings. Token waste = worse plans + worse code. -1. **Trim attachments.** Quote back only the slice you act on. Ignore siblings the harness attached. Duplicate blocks count once. +1. **Trim attachments.** Quote back only the slice you act on. Duplicate blocks count once. 2. **Read in slices.** Files >200 lines: Grep first, then `Read(offset, limit)` for a window. Re-reading the same file twice = first read should have been a slice. -3. **Delegate breadth.** 3+ independent areas, or fix in an unknown file → `Agent(subagent_type: "Explore")`. Parallel queries → one message, multiple Agent calls. Don't delegate AND search. Specify thoroughness: `quick` | `medium` | `very thorough`. +3. **Delegate breadth.** 3+ independent areas → `Agent(subagent_type: "Explore", model: "haiku")`. Parallel queries → one message, multiple Agent calls. 4. **Stop at sufficient.** `file:line` + rationale per task is enough. 5 sharp tasks > 15 vague ones. -## FILE-REFERENCE.md Lifecycle - -`FILE-REFERENCE.md` is updated **only after a PR merges to main** that touched structural files: `crates/*/Cargo.toml`, `apps/*/Cargo.toml`, root `Cargo.toml`, `migrations/*`, or new top-level apps/crates. Workflow: - -1. All changes start on a branch (`feature/`, `fix/`, `chore/`) and ship via PR. -2. After merging the PR to main, if the diff touched structural files, run `/code:fix update` to refresh `FILE-REFERENCE.md`. Commit the refresh on a follow-up branch + PR. -3. On a feature branch, do **not** edit `FILE-REFERENCE.md` — it tracks merged-to-main reality, not in-flight work. - ## Always-latest dependencies -`/code:start` runs `cargo update` post-scaffold so every dep is at its latest semver-compatible patch. Major bumps (e.g., dioxus 0.7→0.8 once it ships) need a manual `cargo upgrade` (cargo-edit) and a `just audit` smoke. The CI gate's `cargo audit` step catches yanked or vulnerable pins on every PR. +The template's `package.json` pins to caret (`^4.6.0`) so patches and compatible minors flow through `bun install`. Major bumps need a manual `bun update` plus a `bun run audit` smoke. The CI gate's `bun audit` step catches yanked or vulnerable pins on every PR. diff --git a/code-et-implementer/hooks/hooks.json b/code-et-implementer/hooks/hooks.json index e6a4cf8..14f5507 100644 --- a/code-et-implementer/hooks/hooks.json +++ b/code-et-implementer/hooks/hooks.json @@ -11,66 +11,6 @@ } ] } - ], - "SubagentStop": [ - { - "matcher": "", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/verify-gate.sh", - "timeout": 30000 - } - ] - } - ], - "SessionStart": [ - { - "matcher": "", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/session-start-prd.sh", - "timeout": 3000 - } - ] - } - ], - "PreToolUse": [ - { - "matcher": "TaskCreate", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/task-created-tag-check.sh", - "timeout": 3000 - } - ] - } - ], - "TaskCompleted": [ - { - "matcher": "", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/task-complete.sh", - "timeout": 5000 - } - ] - } - ], - "PreCompact": [ - { - "matcher": "", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/pre-compact-prd.sh", - "timeout": 3000 - } - ] - } ] } } diff --git a/code-et-implementer/scripts/audit-report.sh b/code-et-implementer/scripts/audit-report.sh deleted file mode 100755 index f583a5a..0000000 --- a/code-et-implementer/scripts/audit-report.sh +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env bash -# audit-report.sh — write a markdown audit report from findings on stdin. -# -# Findings format (one per line, pipe-delimited): -# <severity>|<stage>|<path>|<line>|<message> -# -# severity ∈ { CRITICAL, HIGH, MEDIUM, LOW }. -# -# Usage: audit-report.sh <report-path> [--review-file <diff-path>] -# stdin: zero or more finding lines. -# Writes <report-path> with findings grouped by severity (CRITICAL > HIGH > -# MEDIUM > LOW); within a group, original input order is preserved. -# -# When any non-LOW finding is present, the report is prepended with a single -# summary line of the form (US-12 / AC-12.1): -# [<SEVERITY>] <stage>: <message-head> — fix: <hint> -# LOW-only and empty inputs do NOT trigger a summary prefix. -# -# When --review-file is supplied (US-5), appends a "## Review" section under -# the static findings. The file's contents (a captured `git diff`) are queued -# for consumption by the engineering plugin's code-review skill — this writer -# does not invoke the skill itself. -# -# Empty input → a "no findings" report; the caller still gets a written file -# (AC-7.1: every audit run writes a report). - -set -euo pipefail - -if [ "$#" -lt 1 ]; then - echo "usage: audit-report.sh <report-path> [--review-file <diff-path>]" >&2 - exit 2 -fi - -REPORT_PATH="$1" -shift -REVIEW_FILE="" -while [ "$#" -gt 0 ]; do - case "$1" in - --review-file) - shift - REVIEW_FILE="${1:-}" - [ -z "$REVIEW_FILE" ] && { echo "audit-report.sh: --review-file requires a path" >&2; exit 2; } - shift - ;; - *) - echo "audit-report.sh: unknown arg: $1" >&2 - exit 2 - ;; - esac -done - -mkdir -p "$(dirname "$REPORT_PATH")" - -INPUT="$(cat)" - -# Group lines by severity, preserving input order within each group. -filter_severity() { - local sev="$1" - printf '%s\n' "$INPUT" | awk -F'|' -v sev="$sev" '$1 == sev { print }' -} - -emit_group() { - local sev="$1" header="$2" lines - lines="$(filter_severity "$sev")" - if [ -z "$lines" ]; then return; fi - printf '## %s\n\n' "$header" - while IFS='|' read -r severity stage path line message; do - [ -z "$severity" ] && continue - # `printf --` ends option parsing so format strings starting with `-` are - # accepted on bash 5.3+ (which treats a leading `-` as a flag otherwise). - printf -- '- **%s** (%s) — %s — `%s:%s`\n' "$severity" "$stage" "$message" "$path" "$line" - done <<< "$lines" - printf '\n' -} - -# Stage-specific remediation hint. Closed set per the v3.9.0 gate; unknown -# stages fall back to a generic pointer. -remediation_for() { - case "$1" in - "fmt") echo "run \`cargo fmt --all\`" ;; - "clippy (deny warnings)") echo "address the warning or allow it explicitly" ;; - "layer-deps validator") echo "remove the disallowed cross-layer import" ;; - "cargo-machete (unused deps)") echo "drop the unused dependency from Cargo.toml" ;; - "cargo-audit (advisories)") echo "upgrade the affected crate or pin an advisory exception" ;; - "cargo-deny (license + bans + sources + advisories)") echo "review deny.toml and update the offending dependency" ;; - "tests") echo "fix the failing test(s) shown above" ;; - *) echo "see report for details" ;; - esac -} - -# Compose the one-line summary from the highest-severity finding present. -# Echoes the line on stdout; emits nothing when only LOW (or no) findings. -summary_line() { - local sev stage path line message hint head - for sev in CRITICAL HIGH MEDIUM; do - IFS='|' read -r _severity stage path line message \ - < <(filter_severity "$sev" | head -n 1) || true - if [ -n "${stage:-}" ]; then - hint="$(remediation_for "$stage")" - # Trim the message to its first 80 chars so the summary stays compact. - head="$message" - if [ "${#head}" -gt 80 ]; then - head="${head:0:77}..." - fi - printf '[%s] %s: %s — fix: %s\n' "$sev" "$stage" "$head" "$hint" - return 0 - fi - done - return 0 -} - -SUMMARY="$(summary_line)" - -emit_review() { - [ -z "$REVIEW_FILE" ] && return - printf '## Review\n\n' - printf '_Diff queued for the engineering plugin'\''s code-review skill._\n\n' - if [ -s "$REVIEW_FILE" ]; then - printf '```diff\n' - cat "$REVIEW_FILE" - printf '\n```\n\n' - else - printf '_No diff captured — branch matches its merge base._\n\n' - fi -} - -{ - if [ -n "$SUMMARY" ]; then - printf '%s\n\n' "$SUMMARY" - fi - - printf '# code-et audit report\n\n' - printf '_Generated: %s UTC_\n\n' "$(date -u '+%Y-%m-%d %H:%M:%S')" - - if [ -z "$INPUT" ]; then - printf 'All stages passed — no findings.\n\n' - else - emit_group "CRITICAL" "CRITICAL" - emit_group "HIGH" "HIGH" - emit_group "MEDIUM" "MEDIUM" - emit_group "LOW" "LOW" - fi - - emit_review -} > "$REPORT_PATH" - -# Echo the summary line (if any) on stdout so callers can surface it without -# re-reading the report file. -if [ -n "$SUMMARY" ]; then - printf '%s\n' "$SUMMARY" -fi diff --git a/code-et-implementer/scripts/audit-stages.sh b/code-et-implementer/scripts/audit-stages.sh deleted file mode 100755 index 82f3869..0000000 --- a/code-et-implementer/scripts/audit-stages.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env bash -# audit-stages.sh — yaml parser for the audit pipeline (used by audit.sh and /code:ship). -# Emits one `name|command` line per gate stage on stdout, in declared order. -# Reads the workflow yaml at $1 (default: shared template path). -# The yaml is the single source of truth — no hardcoded stage list. -# -# Mapping for `uses:` steps (GitHub Action → local cargo subcommand): -# bnjbvr/cargo-machete → cargo machete -# rustsec/audit-check → cargo audit -# EmbarkStudios/cargo-deny-action → cargo deny check -# -# Setup-only `uses:` steps are skipped (checkout, rust-toolchain, rust-cache, -# install-action). They install tooling, not run audit stages. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DEFAULT_YAML="$SCRIPT_DIR/../templates/shared/.github/workflows/code-et-audit.yml" -YAML_PATH="${1:-$DEFAULT_YAML}" - -if [ ! -f "$YAML_PATH" ]; then - echo "audit-stages.sh: yaml not found: $YAML_PATH" >&2 - exit 2 -fi - -# Walk the yaml line by line. The workflow is hand-written and stable, so a -# small awk pass is enough — we don't need a real yaml lib. -awk ' - function flush( cmd) { - if (name == "") return - if (run != "") { - cmd = run - } else if (uses != "") { - # Strip @<ref> suffix. - action = uses - sub(/@.*/, "", action) - if (action == "actions/checkout") { reset(); return } - if (action == "dtolnay/rust-toolchain") { reset(); return } - if (action == "Swatinem/rust-cache") { reset(); return } - if (action == "taiki-e/install-action") { reset(); return } - if (action == "bnjbvr/cargo-machete") { cmd = "cargo machete" } - else if (action == "rustsec/audit-check") { cmd = "cargo audit" } - else if (action == "EmbarkStudios/cargo-deny-action") { cmd = "cargo deny check" } - else { - printf "audit-stages.sh: unknown uses action: %s\n", action > "/dev/stderr" - exit 3 - } - } else { - reset(); return - } - printf "%s|%s\n", name, cmd - reset() - } - function reset() { name=""; run=""; uses="" } - - BEGIN { in_steps = 0; reset() } - - # Detect entering the steps: block. Match exactly two-space indent under - # jobs.audit, then four-space "steps:". - /^ steps:[[:space:]]*$/ { in_steps = 1; next } - - # Leaving steps: a top-level key (no leading space) or a sibling at indent <=4 - in_steps == 1 && /^[a-zA-Z]/ { in_steps = 0 } - - in_steps == 1 { - # A new step starts with " - " (six spaces, dash, space) at the steps - # list indent. - if ($0 ~ /^ - /) { - flush() - # The dash-line itself may carry "uses:" or "name:" inline. - line = $0 - sub(/^ - /, "", line) - if (line ~ /^uses:[[:space:]]/) { - sub(/^uses:[[:space:]]+/, "", line) - uses = line - } else if (line ~ /^name:[[:space:]]/) { - sub(/^name:[[:space:]]+/, "", line) - name = line - } - next - } - # Continuation keys for the current step: indent eight spaces. - if ($0 ~ /^ name:[[:space:]]/) { - line = $0 - sub(/^ name:[[:space:]]+/, "", line) - name = line - next - } - if ($0 ~ /^ uses:[[:space:]]/) { - line = $0 - sub(/^ uses:[[:space:]]+/, "", line) - uses = line - next - } - if ($0 ~ /^ run:[[:space:]]/) { - line = $0 - sub(/^ run:[[:space:]]+/, "", line) - run = line - next - } - } - - END { flush() } -' "$YAML_PATH" diff --git a/code-et-implementer/scripts/audit.sh b/code-et-implementer/scripts/audit.sh deleted file mode 100755 index eefb08e..0000000 --- a/code-et-implementer/scripts/audit.sh +++ /dev/null @@ -1,325 +0,0 @@ -#!/usr/bin/env bash -# audit.sh — local mirror of the v3.9.0 CI audit gate. -# -# Usage: audit.sh [--fast | --stage <n>] [--review] [target-dir] -# --fast runs only stages 1-2 (fmt + clippy) -# --stage <n> runs only the n-th stage (1-indexed) from the parsed yaml -# --review chains the engineering plugin's code-review skill against -# `git diff <merge-base>..HEAD`; missing plugin → exit non-zero -# target-dir defaults to the current working directory. -# -# Exits 0 with "not a Rust workspace, skipping" on stderr if no Cargo.toml -# is found at target or its git root. Otherwise parses the workflow yaml -# and runs every stage in order; missing tools (cargo-machete/audit/deny, -# cargo-nextest, layer-deps script) emit a WARNING and are recorded as -# LOW-severity findings. Always writes <target>/.claude/audit-<UTC>.md. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -STAGES_SCRIPT="$SCRIPT_DIR/audit-stages.sh" -REPORT_SCRIPT="$SCRIPT_DIR/audit-report.sh" - -MODE="all" -STAGE_NUM="" -REVIEW=0 -POSITIONAL=() - -print_usage() { - local stages - stages="$(bash "$STAGES_SCRIPT")" || return 1 - echo "Usage: audit.sh [--fast | --stage <n>] [--review] [target-dir]" >&2 - echo "" >&2 - echo "Valid stages:" >&2 - local i=0 - while IFS='|' read -r name _cmd; do - [ -z "$name" ] && continue - i=$((i + 1)) - echo " $i) $name" >&2 - done <<< "$stages" -} - -while [ $# -gt 0 ]; do - case "$1" in - --fast) - if [ "$MODE" = "stage" ]; then - echo "audit.sh: --fast and --stage are mutually exclusive" >&2 - print_usage - exit 2 - fi - MODE="fast" - shift - ;; - --stage) - if [ "$MODE" = "fast" ]; then - echo "audit.sh: --fast and --stage are mutually exclusive" >&2 - print_usage - exit 2 - fi - if [ $# -lt 2 ] || [ -z "${2:-}" ]; then - echo "audit.sh: --stage requires a numeric argument" >&2 - print_usage - exit 2 - fi - MODE="stage" - STAGE_NUM="$2" - shift 2 - ;; - --review) - REVIEW=1 - shift - ;; - --) - shift - while [ $# -gt 0 ]; do POSITIONAL+=("$1"); shift; done - ;; - -*) - echo "audit.sh: unknown flag: $1" >&2 - print_usage - exit 2 - ;; - *) - POSITIONAL+=("$1") - shift - ;; - esac -done - -TARGET_RAW="${POSITIONAL[0]:-$PWD}" -TARGET="$(cd "$TARGET_RAW" 2>/dev/null && pwd)" || { - echo "audit.sh: target dir not found: $TARGET_RAW" >&2 - exit 2 -} - -STAGES="$(bash "$STAGES_SCRIPT")" -STAGE_COUNT="$(printf '%s\n' "$STAGES" | grep -c '|' || true)" - -# --stage <n> validation runs before the workspace guard so bad input always -# exits non-zero, regardless of whether the target is a Rust workspace. -if [ "$MODE" = "stage" ]; then - if ! [[ "$STAGE_NUM" =~ ^[1-9][0-9]*$ ]] || [ "$STAGE_NUM" -gt "$STAGE_COUNT" ]; then - echo "audit.sh: invalid stage '$STAGE_NUM' (valid: 1..$STAGE_COUNT)" >&2 - print_usage - exit 2 - fi -fi - -# Target dir checked first (fixture is a Rust workspace nested inside a -# non-Rust outer repo); git root fallback only if target itself has no Cargo.toml. -find_workspace_root() { - local dir="$1" - if [ -f "$dir/Cargo.toml" ]; then - echo "$dir" - return 0 - fi - local git_root - if git_root="$(git -C "$dir" rev-parse --show-toplevel 2>/dev/null)"; then - if [ -f "$git_root/Cargo.toml" ]; then - echo "$git_root" - return 0 - fi - fi - return 1 -} - -if ! WORKSPACE="$(find_workspace_root "$TARGET")"; then - echo "audit.sh: not a Rust workspace, skipping" >&2 - exit 0 -fi - -severity_for() { - case "$1" in - "fmt") echo "MEDIUM" ;; - "clippy (deny warnings)") echo "HIGH" ;; - "layer-deps validator") echo "HIGH" ;; - "cargo-machete (unused deps)") echo "MEDIUM" ;; - "cargo-audit (advisories)") echo "CRITICAL" ;; - "cargo-deny (license + bans + sources + advisories)") echo "CRITICAL" ;; - "tests") echo "HIGH" ;; - *) echo "MEDIUM" ;; - esac -} - -stage_runnable() { - local cmd="$1" first sub path - first="$(echo "$cmd" | awk '{print $1}')" - case "$first" in - cargo) - sub="$(echo "$cmd" | awk '{print $2}')" - # `cargo fmt`/`cargo clippy` ship with the toolchain via rustup — they - # are dispatched by `cargo` itself, not by separate `cargo-fmt` binaries. - if [ "$sub" = "fmt" ] || [ "$sub" = "clippy" ]; then - command -v cargo >/dev/null - return $? - fi - command -v "cargo-$sub" >/dev/null - ;; - bash) - path="$(echo "$cmd" | awk '{print $2}')" - [ -f "$WORKSPACE/$path" ] - ;; - *) - command -v "$first" >/dev/null - ;; - esac -} - -# Describe the missing artifact when a stage is not runnable. -# Emits "<artifact>|<install_hint>" — the artifact is the canonical binary or -# script name (used as the finding's `path` slot), and the hint is a short -# remediation string with no pipe characters. -missing_artifact_for() { - local cmd="$1" - local first sub path - first="$(echo "$cmd" | awk '{print $1}')" - case "$first" in - cargo) - sub="$(echo "$cmd" | awk '{print $2}')" - printf 'cargo-%s|tool not installed; skipping. Install with: cargo install cargo-%s\n' "$sub" "$sub" - ;; - bash) - path="$(echo "$cmd" | awk '{print $2}')" - printf '%s|script not present in workspace; skipping. Add %s to your workspace.\n' "$path" "$path" - ;; - *) - printf '%s|tool not installed; skipping. Install %s and re-run.\n' "$first" "$first" - ;; - esac -} - -# Heuristic: extract a path:line citation from stage stderr/stdout. Falls back -# to <workspace-relative-cargo>:1 so every finding has a citation per AC-7.3. -extract_citation() { - local log="$1" - local hit - # rust diagnostic format: " --> path/to/file.rs:LINE:COL" - hit="$(grep -oE '[^[:space:]]+\.rs:[0-9]+:[0-9]+' "$log" 2>/dev/null | head -n1 || true)" - if [ -n "$hit" ]; then - # Drop the column; keep path:line. - echo "$hit" | awk -F: '{print $1 ":" $2}' - return - fi - # generic "path:line:" prefix - hit="$(grep -oE '[^[:space:]]+:[0-9]+:' "$log" 2>/dev/null | head -n1 || true)" - if [ -n "$hit" ]; then - echo "${hit%:}" - return - fi - echo "Cargo.toml:1" -} - -# Detect engineering plugin's code-review skill. Returns 0 and prints the -# SKILL.md path on stdout; 1 if absent. -detect_engineering_plugin() { - local cand - if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then - cand="$CLAUDE_PLUGIN_ROOT/../engineering/skills/code-review/SKILL.md" - if [ -f "$cand" ]; then echo "$cand"; return 0; fi - fi - if [ -n "${HOME:-}" ]; then - for cand in "$HOME"/.claude/plugins/cache/*/engineering/skills/code-review/SKILL.md; do - [ -f "$cand" ] && { echo "$cand"; return 0; } - done - fi - cand="$WORKSPACE/.claude/plugins/engineering/skills/code-review/SKILL.md" - if [ -f "$cand" ]; then echo "$cand"; return 0; fi - return 1 -} - -# Capture diff against merge-base, falling through origin/main → main → HEAD~1 -# so a branch with no upstream still gets something reviewable. Writes to $1. -capture_review_diff() { - local out="$1" base="" head="" - head="$(git -C "$WORKSPACE" rev-parse HEAD 2>/dev/null || true)" - base="$(git -C "$WORKSPACE" merge-base origin/main HEAD 2>/dev/null || true)" - if [ -z "$base" ] || [ "$base" = "$head" ]; then - base="$(git -C "$WORKSPACE" merge-base main HEAD 2>/dev/null || true)" - fi - if [ -z "$base" ] || [ "$base" = "$head" ]; then - base="$(git -C "$WORKSPACE" rev-parse HEAD~1 2>/dev/null || true)" - fi - if [ -n "$base" ] && [ "$base" != "$head" ]; then - git -C "$WORKSPACE" diff "$base"..HEAD > "$out" 2>/dev/null || : > "$out" - else - : > "$out" - fi -} - -REPORT_DIR="$WORKSPACE/.claude" -TIMESTAMP="$(date -u +%Y%m%d-%H%M%S)" -REPORT_PATH="$REPORT_DIR/audit-$TIMESTAMP.md" -mkdir -p "$REPORT_DIR" - -FINDINGS_TMP="$(mktemp -t code-et-audit.XXXXXX)" -REVIEW_TMP="$(mktemp -t code-et-audit-review.XXXXXX)" -trap 'rm -f "$FINDINGS_TMP" "$REVIEW_TMP"' EXIT - -OVERALL_FAIL=0 - -case "$MODE" in - fast) STAGES="$(printf '%s\n' "$STAGES" | sed -n '1,2p')" ;; - stage) STAGES="$(printf '%s\n' "$STAGES" | sed -n "${STAGE_NUM}p")" ;; - all) ;; -esac - -while IFS='|' read -r name cmd; do - [ -z "$name" ] && continue - severity="$(severity_for "$name")" - - if ! stage_runnable "$cmd"; then - artifact_hint="$(missing_artifact_for "$cmd")" - artifact="${artifact_hint%%|*}" - hint="${artifact_hint#*|}" - echo "audit: WARNING: $name — $hint" >&2 - # Record as LOW finding; exit code stays 0 unless another stage fails. - msg="${hint//|/\\|}" - printf '%s|%s|%s|%s|%s\n' \ - "LOW" "$name" "$artifact" "1" "$msg" \ - >> "$FINDINGS_TMP" - continue - fi - - echo "audit: $name — running" >&2 - stage_log="$(mktemp -t code-et-audit-stage.XXXXXX)" - set +e - ( cd "$WORKSPACE" && eval "$cmd" ) > "$stage_log" 2>&1 - rc=$? - set -e - cat "$stage_log" >&2 - if [ "$rc" -ne 0 ]; then - OVERALL_FAIL=1 - citation="$(extract_citation "$stage_log")" - msg="stage '$name' failed (exit $rc): $cmd" - # Strip pipes from the message so the delimited format survives. - msg="${msg//|/\\|}" - printf '%s|%s|%s|%s|%s\n' \ - "$severity" "$name" "${citation%:*}" "${citation##*:}" "$msg" \ - >> "$FINDINGS_TMP" - fi - rm -f "$stage_log" -done <<< "$STAGES" - -REPORT_ARGS=("$REPORT_PATH") - -# Review wiring only attaches when static stages pass — a failing audit is -# already actionable without prose review noise. -if [ "$REVIEW" -eq 1 ] && [ "$OVERALL_FAIL" -eq 0 ]; then - if ! SKILL_PATH="$(detect_engineering_plugin)"; then - echo "audit: engineering plugin not installed; run /plugin install engineering to enable --review" >&2 - exit 1 - fi - echo "audit: --review enabled — capturing diff for $SKILL_PATH" >&2 - capture_review_diff "$REVIEW_TMP" - REPORT_ARGS+=(--review-file "$REVIEW_TMP") -fi - -SUMMARY_LINE="$(bash "$REPORT_SCRIPT" "${REPORT_ARGS[@]}" < "$FINDINGS_TMP")" -echo "audit: report written to $REPORT_PATH" >&2 - -# AC-12.2: on failure, surface the writer's summary line on stderr so the user -# sees the highest-severity finding without opening the report. -if [ "$OVERALL_FAIL" -ne 0 ] && [ -n "$SUMMARY_LINE" ]; then - echo "$SUMMARY_LINE" >&2 -fi - -exit "$OVERALL_FAIL" diff --git a/code-et-implementer/scripts/pre-compact-prd.sh b/code-et-implementer/scripts/pre-compact-prd.sh deleted file mode 100755 index 2ada441..0000000 --- a/code-et-implementer/scripts/pre-compact-prd.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# PreCompact hook: inject PRD open-stories summary before Claude compacts. -set -euo pipefail - -here="$(cd "$(dirname "$0")" && pwd)" -prd="$("$here/resolve-prd.sh" 2>/dev/null || true)" - -if [ -z "$prd" ]; then - echo '{}' - exit 0 -fi - -open_count="$(grep -cE '^\- \[ \] US-' "$prd" 2>/dev/null || true)" -open_count="${open_count:-0}" - -if [ "$open_count" -gt 20 ]; then - body="Active PRD: $prd -${open_count} open stories. Read the PRD for full list." -else - lines="$(grep -E '^\- \[ \] US-' "$prd" 2>/dev/null || true)" - body="Active PRD: $prd -Open stories: -${lines}" -fi - -body_json="$(printf '%s' "$body" | jq -Rs .)" -printf '{"context": %s}\n' "$body_json" diff --git a/code-et-implementer/scripts/resolve-prd.sh b/code-et-implementer/scripts/resolve-prd.sh deleted file mode 100755 index 9631225..0000000 --- a/code-et-implementer/scripts/resolve-prd.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -# Resolve active PRD for current branch. -# Usage: resolve-prd.sh [branch-name] -# Prints absolute path to plans/YYYY-MM-DD-<slug>.md (most recent) on success. -# Exits 1 with empty stdout when no match. - -set -euo pipefail - -branch="${1:-$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')}" -[ -z "$branch" ] && exit 1 - -# Strip standard prefixes -slug="${branch#feature/}" -slug="${slug#fix/}" -slug="${slug#chore/}" - -repo_root="$(git rev-parse --show-toplevel 2>/dev/null || echo '.')" -plans_dir="$repo_root/plans" -[ -d "$plans_dir" ] || exit 1 - -# Match YYYY-MM-DD-<slug>.md, newest first -match="$(ls -1 "$plans_dir"/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-"$slug".md 2>/dev/null | sort -r | head -n1 || true)" - -[ -z "$match" ] && exit 1 -echo "$match" diff --git a/code-et-implementer/scripts/run-tests.sh b/code-et-implementer/scripts/run-tests.sh deleted file mode 100755 index f4e8c5f..0000000 --- a/code-et-implementer/scripts/run-tests.sh +++ /dev/null @@ -1,132 +0,0 @@ -#!/bin/bash -# Shared verification gate — detects and runs project test command -# Usage: run-tests.sh <caller-label> <failure-exit-code> -# Args: -# $1 = caller label ("Agent" or "Teammate") for log messages -# $2 = exit code on test failure (1 for SubagentStop, 2 for TaskCompleted) - -CALLER="${1:-Agent}" -FAIL_EXIT="${2:-1}" - -# Read hook input from stdin (JSON with last_assistant_message) -HOOK_INPUT="" -if [ ! -t 0 ]; then - HOOK_INPUT=$(cat) -fi - -# Parse agent identity and last_assistant_message (jq preferred, grep fallback) -AGENT_ID="" -AGENT_TYPE="" -LAST_MSG="" -if [ -n "$HOOK_INPUT" ]; then - if command -v jq &> /dev/null; then - LAST_MSG=$(echo "$HOOK_INPUT" | jq -r '.last_assistant_message // empty' 2>/dev/null) - AGENT_ID=$(echo "$HOOK_INPUT" | jq -r '.agent_id // empty' 2>/dev/null) - AGENT_TYPE=$(echo "$HOOK_INPUT" | jq -r '.agent_type // empty' 2>/dev/null) - else - LAST_MSG=$(echo "$HOOK_INPUT" | grep -o '"last_assistant_message":"[^"]*"' | head -1 | sed 's/"last_assistant_message":"//;s/"$//') - fi -fi - -# Build agent label for log messages -AGENT_LABEL="$CALLER" -[ -n "$AGENT_ID" ] && AGENT_LABEL="$CALLER[${AGENT_ID}${AGENT_TYPE:+/$AGENT_TYPE}]" - -# Check agent's final status -if [ -n "$LAST_MSG" ]; then - if echo "$LAST_MSG" | grep -qi "BLOCKED:"; then - CLAIM=$(echo "$LAST_MSG" | head -c 200) - echo "{\"info\": \"$AGENT_LABEL reported BLOCKED — skipping verification\", \"claim\": \"$CLAIM\"}" - exit 0 - fi - if ! echo "$LAST_MSG" | grep -qi "COMPLETE"; then - echo "{\"warning\": \"$AGENT_LABEL exited without COMPLETE or BLOCKED — running verification anyway\"}" - fi -fi - -# Detect quality gates (tests + lint + typecheck) -detect_quality_gates() { - local gates="" - - if [ -f "package.json" ]; then - local runner="npm" - command -v bun &> /dev/null && runner="bun" - - _add_gate() { - if grep -q "\"$1\"" package.json; then - local cmd="$runner ${2:-run $1}" - gates="${gates:+$gates && }$cmd" - fi - } - - _add_gate "test" "$runner test" - _add_gate "lint" - _add_gate "typecheck" - - if [ -n "$gates" ]; then - echo "$gates" - return - fi - fi - - if [ -f "Makefile" ] && grep -q "^test:" Makefile; then - echo "make test" - return - fi - - if [ -f "pyproject.toml" ]; then - if command -v uv &> /dev/null; then - echo "uv run pytest" - else - echo "pytest" - fi - return - fi - - if [ -f "Cargo.toml" ]; then - echo "cargo test" - return - fi - - echo "" -} - -TEST_CMD=$(detect_quality_gates) - -if [ -z "$TEST_CMD" ]; then - echo "{\"info\": \"No test command detected — verification skipped\"}" - exit 0 -fi - -echo "{\"verification\": \"Running: $TEST_CMD\"}" - -# Run tests with 120s timeout -if command -v timeout &> /dev/null; then - timeout 120 bash -c "$TEST_CMD" -else - # macOS fallback: use perl for timeout - perl -e 'alarm 120; exec "bash", "-c", $ARGV[0]' "$TEST_CMD" -fi - -EXIT_CODE=$? - -# cmux notification helper -_cmux_notify() { - if command -v cmux &>/dev/null && [ -n "$CMUX_SOCKET_PATH" ]; then - cmux notify --title "$1" --subtitle "$2" --body "$3" 2>/dev/null || true - fi -} - -if [ $EXIT_CODE -eq 124 ]; then - echo "{\"error\": \"Tests timed out after 120 seconds\"}" - _cmux_notify "$AGENT_LABEL" "Timeout" "Tests timed out after 120 seconds" - exit $FAIL_EXIT -elif [ $EXIT_CODE -ne 0 ]; then - echo "{\"error\": \"Tests failed (exit $EXIT_CODE)\"}" - _cmux_notify "$AGENT_LABEL" "Tests Failed" "Verification failed (exit $EXIT_CODE)" - exit $FAIL_EXIT -fi - -echo '{"verification": "Tests passed"}' -_cmux_notify "$AGENT_LABEL" "Tests Passed" "Verification gate passed" -exit 0 diff --git a/code-et-implementer/scripts/session-start-prd.sh b/code-et-implementer/scripts/session-start-prd.sh deleted file mode 100755 index 36c1314..0000000 --- a/code-et-implementer/scripts/session-start-prd.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash -# SessionStart hook: inject 3-line PRD pointer when a PRD matches current branch. -set -euo pipefail - -here="$(cd "$(dirname "$0")" && pwd)" -prd="$("$here/resolve-prd.sh" 2>/dev/null || true)" - -if [ -z "$prd" ]; then - echo '{}' - exit 0 -fi - -open_stories="$(grep -oE '^\- \[ \] US-[0-9]+' "$prd" 2>/dev/null | sed -E 's/^- \[ \] //' | paste -sd, - | sed 's/,/, /g' || true)" -[ -z "$open_stories" ] && open_stories="(none)" - -payload="Active PRD: $prd -Open: $open_stories -Read the PRD before planning or implementing." - -body_json="$(printf '%s' "$payload" | jq -Rs .)" -printf '{"context": %s}\n' "$body_json" diff --git a/code-et-implementer/scripts/task-complete.sh b/code-et-implementer/scripts/task-complete.sh deleted file mode 100755 index c02dd2b..0000000 --- a/code-et-implementer/scripts/task-complete.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -# TaskCompleted hook — cmux notification with agent attribution - -HOOK_INPUT="" -if [ ! -t 0 ]; then - HOOK_INPUT=$(cat) -fi - -TASK_ID="" -AGENT_ID="" -AGENT_TYPE="" -if [ -n "$HOOK_INPUT" ] && command -v jq &>/dev/null; then - TASK_ID=$(echo "$HOOK_INPUT" | jq -r '.task_id // empty' 2>/dev/null) - AGENT_ID=$(echo "$HOOK_INPUT" | jq -r '.agent_id // empty' 2>/dev/null) - AGENT_TYPE=$(echo "$HOOK_INPUT" | jq -r '.agent_type // empty' 2>/dev/null) -fi - -LABEL="Task ${TASK_ID:-unknown}" -[ -n "$AGENT_ID" ] && LABEL="$LABEL (agent: $AGENT_ID)" - -# cmux notification -if command -v cmux &>/dev/null && [ -n "$CMUX_SOCKET_PATH" ]; then - cmux notify --title "Task Completed" --subtitle "${AGENT_TYPE:-agent}" --body "$LABEL" 2>/dev/null || true -fi - -echo "{\"info\": \"$LABEL completed\"}" -exit 0 diff --git a/code-et-implementer/scripts/task-created-tag-check.sh b/code-et-implementer/scripts/task-created-tag-check.sh deleted file mode 100755 index dd4f746..0000000 --- a/code-et-implementer/scripts/task-created-tag-check.sh +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env bash -# PreToolUse(TaskCreate) hook: enforce user_story tag (and layer on Rust) -# on branches with an active PRD. Runs *before* the task is created so we -# reject the call instead of orphaning a malformed task. -# -# Pre-v4.2.3 this ran on the `TaskCreated` lifecycle event, which delivers -# a flat post-hoc payload (task_id, task_subject, task_description) with -# no `metadata` field — so well-formed calls were uniformly rejected. The -# `tool_input.metadata` payload only exists on the tool-call envelope -# (PermissionRequest / PreToolUse), not the lifecycle event. -# -# Exit 0 = allow, exit 2 = block (per Claude Code hook contract). - -set -euo pipefail - -here="$(cd "$(dirname "$0")" && pwd)" -payload="$(cat)" - -prd="$("$here/resolve-prd.sh" 2>/dev/null || true)" - -# Metadata location is harness-dependent. We try, in order: -# 1. tool_input.metadata (object or JSON-encoded string) -# 2. tool_input (fields may be flattened at top level of tool_input) -# 3. .metadata (no tool_input wrapper) -# 4. whole payload as string → parse → recurse -# 5. deep search for user_story / layer keys anywhere in the tree -# Whatever shape arrives, well-formed metadata gets extracted. -extract() { - # $1 = jq expr that produces the candidate metadata node - printf '%s' "$payload" | jq -c " - def norm(\$x): if (\$x|type)==\"string\" then (\$x|fromjson? // {}) elif (\$x|type)==\"object\" then \$x else {} end; - norm($1) - " 2>/dev/null -} - -read_field() { - # $1 = field name; tries each candidate metadata location in turn. - local field="$1" val="" - for expr in \ - '.tool_input.metadata' \ - '.tool_input' \ - '.metadata' \ - '(. | if type=="string" then (fromjson? // {}) else {} end)' \ - '(.tool_input | if type=="string" then (fromjson? // {}) else {} end)' - do - val="$(extract "$expr" | jq -r --arg f "$field" '.[$f] // empty' 2>/dev/null || echo '')" - [ -n "$val" ] && [ "$val" != "null" ] && { printf '%s' "$val"; return; } - done - # Last resort: deep search anywhere in the JSON tree. - val="$(printf '%s' "$payload" | jq -r --arg f "$field" ' - [.. | objects | select(has($f)) | .[$f]] - | map(select(type=="string" and length > 0)) - | first // empty - ' 2>/dev/null || echo '')" - [ "$val" = "null" ] && val="" - printf '%s' "$val" -} - -tag="$(read_field user_story)" -layer="$(read_field layer)" - -if [ -z "$prd" ]; then - # Bug lane — anything goes - exit 0 -fi - -tag_ok=0 -if [[ "$tag" =~ ^US-[0-9]+$ ]] \ - || [[ "$tag" =~ ^AC-[0-9]+\.[0-9]+$ ]] \ - || [[ "$tag" =~ ^chore:.+ ]]; then - tag_ok=1 -fi - -# layer is required only when the project is Rust (Cargo.toml at repo root). -layer_required=0 -if git rev-parse --show-toplevel &>/dev/null; then - root="$(git rev-parse --show-toplevel)" - [ -f "$root/Cargo.toml" ] && layer_required=1 -fi - -layer_ok=1 -if [ "$layer_required" -eq 1 ]; then - case "$layer" in - domain|application|infrastructure|interface|chore) layer_ok=1 ;; - *) layer_ok=0 ;; - esac -fi - -if [ "$tag_ok" -eq 1 ] && [ "$layer_ok" -eq 1 ]; then - exit 0 -fi - -# Diagnostic: dump the raw payload + what we extracted, so the failure mode is -# legible without needing to wrap the hook. Newest dump wins; previous is kept -# as .prev.json for one cycle. -debug_dir="${TMPDIR:-/tmp}/code-et-task-hook" -mkdir -p "$debug_dir" 2>/dev/null || true -if [ -f "$debug_dir/last-rejected.json" ]; then - mv -f "$debug_dir/last-rejected.json" "$debug_dir/last-rejected.prev.json" 2>/dev/null || true -fi -payload_json="$(printf '%s' "$payload" | jq -c . 2>/dev/null || printf '%s' "$payload" | jq -Rs .)" -{ - printf '{"ts":"%s","extracted":{"user_story":%s,"layer":%s},"payload":%s}\n' \ - "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ - "$(printf '%s' "$tag" | jq -Rs .)" \ - "$(printf '%s' "$layer" | jq -Rs .)" \ - "$payload_json" -} > "$debug_dir/last-rejected.json" 2>/dev/null || true - -cat >&2 <<EOF -Task rejected: required metadata is missing or invalid. - -Active PRD: $prd - -metadata.user_story — required: "US-<N>" | "AC-<N>.<M>" | "chore:<reason>" -EOF -if [ "$layer_required" -eq 1 ]; then - cat >&2 <<EOF -metadata.layer — required on Rust projects: "domain" | "application" | "infrastructure" | "interface" | "chore" - See code-et-implementer/docs/architecture.md §"Layer model". -EOF -fi -cat >&2 <<EOF - -Raw payload + extraction trace: $debug_dir/last-rejected.json -EOF -exit 2 diff --git a/code-et-implementer/scripts/verify-gate.sh b/code-et-implementer/scripts/verify-gate.sh deleted file mode 100755 index 9b8ea02..0000000 --- a/code-et-implementer/scripts/verify-gate.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# SubagentStop hook — verification gate after background agent -DIR="$(cd "$(dirname "$0")" && pwd)" -"$DIR/run-tests.sh" "Agent" 1 || exit $? - -# US-3: Run audit --fast on Rust workspaces. Detect repo root via git, fall -# back to cwd. Skip silently when no Cargo.toml — keeps the hook safe to wire -# into shared SubagentStop on non-Rust repos. -ROOT="" -if command -v git >/dev/null 2>&1; then - ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)" -fi -[ -z "$ROOT" ] && ROOT="$PWD" -if [ -f "$ROOT/Cargo.toml" ]; then - "$DIR/audit.sh" --fast "$ROOT" </dev/null -fi diff --git a/code-et-implementer/tests/README.md b/code-et-implementer/tests/README.md deleted file mode 100644 index 404385b..0000000 --- a/code-et-implementer/tests/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Hook script tests - -Run all: `./run-tests.sh` -Run one: `bats resolve-prd.bats` - -Requires: `bats-core` (`brew install bats-core` or `npm i -g bats`). - -Fixtures live under `fixtures/`. Each test creates a temp repo via -`mktemp -d` and exports `CLAUDE_PLUGIN_ROOT` so scripts resolve correctly. diff --git a/code-et-implementer/tests/audit-review-with-plugin.sh b/code-et-implementer/tests/audit-review-with-plugin.sh deleted file mode 100755 index 57593cb..0000000 --- a/code-et-implementer/tests/audit-review-with-plugin.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env bash -# audit-review-with-plugin.sh — assert audit.sh --review writes a Review -# section into the report when the engineering plugin is detectable -# (AC-5.1, AC-5.2). -# -# Strategy: stub plugin presence by creating a fake SKILL.md under a sandboxed -# HOME, build a minimal Rust workspace in a tempdir (Cargo.toml only), seed it -# as a git repo with two commits so a diff exists, then invoke audit --review -# and assert exit 0 and the report contains "## Review". - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -AUDIT="$SCRIPT_DIR/../scripts/audit.sh" - -TMPROOT="$(mktemp -d -t code-et-audit-rev.XXXXXX)" -trap 'rm -rf "$TMPROOT"' EXIT - -# Stub plugin presence under a sandboxed HOME. -FAKE_HOME="$TMPROOT/home" -SKILL_DIR="$FAKE_HOME/.claude/plugins/cache/knowledge-work-plugins/engineering/skills/code-review" -mkdir -p "$SKILL_DIR" -cat > "$SKILL_DIR/SKILL.md" <<'EOF' ---- -name: code-review ---- -stub -EOF - -# Minimal Rust workspace + git history so the merge-base diff is non-empty. -WS="$TMPROOT/ws" -mkdir -p "$WS/src" -cat > "$WS/Cargo.toml" <<'EOF' -[package] -name = "stub" -version = "0.1.0" -edition = "2021" -EOF -echo 'fn main() {}' > "$WS/src/main.rs" - -git -C "$WS" init -q -b main -git -C "$WS" -c user.email=test@test -c user.name=test add -A -git -C "$WS" -c user.email=test@test -c user.name=test commit -q -m "init" -echo '// edit' >> "$WS/src/main.rs" -git -C "$WS" -c user.email=test@test -c user.name=test commit -q -am "edit" - -# Sandbox PATH to a minimal toolset so cargo/clippy are absent and every -# stage skips cleanly. We need git, awk, grep, mktemp, date, sed, cat, head, -# bash, and core POSIX tools — symlink them in. -SANDBOX_BIN="$TMPROOT/bin" -mkdir -p "$SANDBOX_BIN" -for t in bash sh awk grep sed cat head tail mktemp date dirname basename ls cd rm cp mv mkdir tr cut wc sort uniq printf id git env which command find chmod test pwd; do - if cmd_path="$(command -v "$t" 2>/dev/null)"; then - ln -sf "$cmd_path" "$SANDBOX_BIN/$t" - fi -done - -STDERR_LOG="$TMPROOT/stderr.log" -set +e -HOME="$FAKE_HOME" CLAUDE_PLUGIN_ROOT="" PATH="$SANDBOX_BIN" \ - bash "$AUDIT" --review "$WS" 2> "$STDERR_LOG" -rc=$? -set -e - -if [ "$rc" -ne 0 ]; then - echo "FAIL: audit --review exited $rc; expected 0" >&2 - cat "$STDERR_LOG" >&2 - exit 1 -fi - -REPORT="$(ls "$WS"/.claude/audit-*.md 2>/dev/null | head -n1 || true)" -if [ -z "$REPORT" ]; then - echo "FAIL: no report file written" >&2 - cat "$STDERR_LOG" >&2 - exit 1 -fi - -if ! grep -q '^## Review' "$REPORT"; then - echo "FAIL: report missing '## Review' section" >&2 - cat "$REPORT" >&2 - exit 1 -fi - -if ! grep -q "code-review skill" "$REPORT"; then - echo "FAIL: review section missing skill-consumption note" >&2 - cat "$REPORT" >&2 - exit 1 -fi - -if ! grep -q '```diff' "$REPORT"; then - echo "FAIL: review section missing fenced diff block" >&2 - cat "$REPORT" >&2 - exit 1 -fi - -echo "PASS: audit-review-with-plugin" diff --git a/code-et-implementer/tests/audit-review-without-plugin.sh b/code-et-implementer/tests/audit-review-without-plugin.sh deleted file mode 100755 index 0a293c8..0000000 --- a/code-et-implementer/tests/audit-review-without-plugin.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env bash -# audit-review-without-plugin.sh — assert audit.sh --review exits non-zero -# with the install hint when the engineering plugin is not detectable -# (AC-5.3). -# -# Strategy: sandbox HOME to an empty tempdir so no plugin SKILL.md is reachable, -# point CLAUDE_PLUGIN_ROOT at a stub with no engineering sibling, build a -# minimal Rust workspace with a git history, and invoke audit --review. -# Assert non-zero exit and the hint string on stderr. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -AUDIT="$SCRIPT_DIR/../scripts/audit.sh" - -TMPROOT="$(mktemp -d -t code-et-audit-norev.XXXXXX)" -trap 'rm -rf "$TMPROOT"' EXIT - -# Sandboxed HOME with no plugins. CLAUDE_PLUGIN_ROOT points at a stub dir -# whose ../engineering/skills/code-review/SKILL.md does NOT exist. -FAKE_HOME="$TMPROOT/home" -mkdir -p "$FAKE_HOME" -STUB_PLUGIN_ROOT="$TMPROOT/code-et-implementer" -mkdir -p "$STUB_PLUGIN_ROOT" - -# Minimal Rust workspace + git history. -WS="$TMPROOT/ws" -mkdir -p "$WS/src" -cat > "$WS/Cargo.toml" <<'EOF' -[package] -name = "stub" -version = "0.1.0" -edition = "2021" -EOF -echo 'fn main() {}' > "$WS/src/main.rs" - -git -C "$WS" init -q -b main -git -C "$WS" -c user.email=test@test -c user.name=test add -A -git -C "$WS" -c user.email=test@test -c user.name=test commit -q -m "init" - -# Sandbox PATH so cargo/clippy don't surface real failures unrelated to the -# plugin-detection branch we're testing. -SANDBOX_BIN="$TMPROOT/bin" -mkdir -p "$SANDBOX_BIN" -for t in bash sh awk grep sed cat head tail mktemp date dirname basename ls cd rm cp mv mkdir tr cut wc sort uniq printf id git env which command find chmod test pwd; do - if cmd_path="$(command -v "$t" 2>/dev/null)"; then - ln -sf "$cmd_path" "$SANDBOX_BIN/$t" - fi -done - -STDERR_LOG="$TMPROOT/stderr.log" -set +e -HOME="$FAKE_HOME" CLAUDE_PLUGIN_ROOT="$STUB_PLUGIN_ROOT" PATH="$SANDBOX_BIN" \ - bash "$AUDIT" --review "$WS" 2> "$STDERR_LOG" -rc=$? -set -e - -if [ "$rc" -eq 0 ]; then - echo "FAIL: audit --review exited 0; expected non-zero (no plugin)" >&2 - cat "$STDERR_LOG" >&2 - exit 1 -fi - -if ! grep -q "engineering plugin not installed" "$STDERR_LOG"; then - echo "FAIL: missing install-hint string on stderr" >&2 - cat "$STDERR_LOG" >&2 - exit 1 -fi - -if ! grep -q "/plugin install engineering" "$STDERR_LOG"; then - echo "FAIL: hint missing install command" >&2 - cat "$STDERR_LOG" >&2 - exit 1 -fi - -echo "PASS: audit-review-without-plugin" diff --git a/code-et-implementer/tests/audit-skips-missing-tools.sh b/code-et-implementer/tests/audit-skips-missing-tools.sh deleted file mode 100755 index 9f13b80..0000000 --- a/code-et-implementer/tests/audit-skips-missing-tools.sh +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env bash -# audit-skips-missing-tools.sh — assert audit.sh degrades gracefully when -# optional toolchain binaries (and the layer-deps validator script) are -# missing on a fresh dev machine (US-9, AC-9.1, AC-9.2). -# -# Strategy: build a minimal Rust workspace fixture, then invoke audit.sh -# with a sanitised PATH that contains only a stub `cargo` shim and the bare -# Unix utilities. The stub cargo handles `fmt` and `clippy` (noops) but no -# `cargo-machete` / `cargo-audit` / `cargo-deny` / `cargo-nextest` exist on -# PATH, and the workspace ships no `scripts/layer-deps-validator.sh`. -# -# Expected outcome: -# - exit code 0 (no genuine stage failure) -# - five LOW findings: machete, audit, deny, layer-deps, nextest -# - one "WARNING" line per skipped stage on stderr -# - report contains a `## LOW` section listing each skip - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -AUDIT="$SCRIPT_DIR/../scripts/audit.sh" - -TMPDIR_TEST="$(mktemp -d -t code-et-audit-missing.XXXXXX)" -trap 'rm -rf "$TMPDIR_TEST"' EXIT - -WORKSPACE="$TMPDIR_TEST/workspace" -STUB_BIN="$TMPDIR_TEST/bin" -mkdir -p "$WORKSPACE" "$STUB_BIN" - -# Minimal empty Rust workspace — enough for find_workspace_root to accept it. -cat > "$WORKSPACE/Cargo.toml" <<'EOF' -[workspace] -members = [] -resolver = "2" -EOF - -# Stub `cargo` — handles fmt/clippy as noops, refuses unknown subcommands the -# way real cargo does so audit.sh's stage_runnable probe (which checks for -# `cargo-<sub>` on PATH) still rejects them. -cat > "$STUB_BIN/cargo" <<'EOF' -#!/usr/bin/env bash -case "${1:-}" in - fmt|clippy) exit 0 ;; - *) - echo "error: no such subcommand: \`$1\`" >&2 - exit 101 - ;; -esac -EOF -chmod +x "$STUB_BIN/cargo" - -STDERR_LOG="$TMPDIR_TEST/stderr.log" - -set +e -PATH="$STUB_BIN:/usr/bin:/bin" bash "$AUDIT" "$WORKSPACE" 2> "$STDERR_LOG" -rc=$? -set -e - -if [ "$rc" -ne 0 ]; then - echo "FAIL: audit.sh exited $rc with all optional tools missing; expected 0" >&2 - cat "$STDERR_LOG" >&2 - exit 1 -fi - -# Locate the freshly-written report. There should be exactly one. -REPORT="$(ls "$WORKSPACE/.claude/audit-"*.md 2>/dev/null | head -n1 || true)" -if [ -z "$REPORT" ]; then - echo "FAIL: no audit report written" >&2 - cat "$STDERR_LOG" >&2 - exit 1 -fi - -# AC-9.1: a WARNING line on stderr for each missing tool/script. -expected_warnings=( - "cargo-machete (unused deps)" - "cargo-audit (advisories)" - "cargo-deny (license + bans + sources + advisories)" - "layer-deps validator" - "tests" -) -for stage in "${expected_warnings[@]}"; do - if ! grep -qF "WARNING: $stage" "$STDERR_LOG"; then - echo "FAIL: missing WARNING line for stage '$stage' on stderr" >&2 - cat "$STDERR_LOG" >&2 - exit 1 - fi -done - -# AC-9.2: each skip is recorded in the report under the LOW group. -if ! grep -q '^## LOW' "$REPORT"; then - echo "FAIL: report has no LOW severity section" >&2 - cat "$REPORT" >&2 - exit 1 -fi - -expected_paths=( - "cargo-machete" - "cargo-audit" - "cargo-deny" - "scripts/layer-deps-validator.sh" - "cargo-nextest" -) -for path in "${expected_paths[@]}"; do - if ! grep -qF "\`$path:1\`" "$REPORT"; then - echo "FAIL: report missing LOW finding with path '$path:1'" >&2 - cat "$REPORT" >&2 - exit 1 - fi -done - -# Sanity: every LOW finding line carries the install/remediation hint stem. -low_lines="$(awk '/^## LOW/{flag=1; next} /^## /{flag=0} flag && /^- /' "$REPORT")" -low_count="$(printf '%s\n' "$low_lines" | grep -c '^- ' || true)" -if [ "$low_count" -ne 5 ]; then - echo "FAIL: expected 5 LOW findings, got $low_count" >&2 - cat "$REPORT" >&2 - exit 1 -fi - -# Bracket: no CRITICAL/HIGH/MEDIUM sections — only skips, no real failures. -for sev in CRITICAL HIGH MEDIUM; do - if grep -q "^## $sev" "$REPORT"; then - echo "FAIL: report has unexpected $sev section" >&2 - cat "$REPORT" >&2 - exit 1 - fi -done - -echo "PASS: audit-skips-missing-tools ($low_count LOW findings, exit 0)" diff --git a/code-et-implementer/tests/audit-skips-non-rust.sh b/code-et-implementer/tests/audit-skips-non-rust.sh deleted file mode 100755 index dae59d1..0000000 --- a/code-et-implementer/tests/audit-skips-non-rust.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -# audit-skips-non-rust.sh — assert audit.sh exits 0 with no report when the -# target dir is not a Rust workspace (AC-6.1). - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -AUDIT="$SCRIPT_DIR/../scripts/audit.sh" - -TMPDIR_TEST="$(mktemp -d -t code-et-audit-test.XXXXXX)" -trap 'rm -rf "$TMPDIR_TEST"' EXIT - -# A bare temp dir with no Cargo.toml and no enclosing git tree. -STDERR_LOG="$(mktemp -t code-et-audit-stderr.XXXXXX)" -set +e -bash "$AUDIT" "$TMPDIR_TEST" 2> "$STDERR_LOG" -rc=$? -set -e - -if [ "$rc" -ne 0 ]; then - echo "FAIL: audit.sh exited $rc on non-Rust dir; expected 0" >&2 - cat "$STDERR_LOG" >&2 - exit 1 -fi - -if ! grep -q "not a Rust workspace, skipping" "$STDERR_LOG"; then - echo "FAIL: missing skip message on stderr" >&2 - cat "$STDERR_LOG" >&2 - exit 1 -fi - -if ls "$TMPDIR_TEST/.claude/audit-"*.md >/dev/null 2>&1; then - echo "FAIL: report file written despite skip" >&2 - exit 1 -fi - -rm -f "$STDERR_LOG" -echo "PASS: audit-skips-non-rust" diff --git a/code-et-implementer/tests/audit-stage-list-matches-yaml.sh b/code-et-implementer/tests/audit-stage-list-matches-yaml.sh deleted file mode 100755 index 2a0e32b..0000000 --- a/code-et-implementer/tests/audit-stage-list-matches-yaml.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env bash -# audit-stage-list-matches-yaml.sh — assert audit-stages.sh's parsed list -# matches the workflow yaml step-for-step (AC-8.2). -# -# We re-parse the yaml here using a deliberately different extraction (grep on -# `name:` and `run:` / `uses:` lines) so the comparison has signal, not a -# tautology — a typo in audit-stages.sh's mapping would still be caught. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -STAGES="$SCRIPT_DIR/../scripts/audit-stages.sh" -YAML="$SCRIPT_DIR/../templates/shared/.github/workflows/code-et-audit.yml" - -# Setup-only steps to ignore. Match by `uses:` action prefix (no @ref). -SETUP_USES_RE='^(actions/checkout|dtolnay/rust-toolchain|Swatinem/rust-cache|taiki-e/install-action)' - -# Action-to-command map for `uses:` gate steps. Mirrored in audit-stages.sh — -# the test's job is to fail loudly if the two ever diverge. -map_uses_to_cmd() { - case "$1" in - bnjbvr/cargo-machete) echo "cargo machete" ;; - rustsec/audit-check) echo "cargo audit" ;; - EmbarkStudios/cargo-deny-action) echo "cargo deny check" ;; - *) echo "UNKNOWN" ;; - esac -} - -# Walk the yaml. For each step, capture `name:` and `run:` / `uses:`. -# Emit `name|command` for non-setup steps. Use Python for indent-aware parsing -# only if available; otherwise a portable awk pass. -expected="$(awk ' - function flush( cmd, action) { - if (name == "") return - if (run != "") { - cmd = run - } else if (uses != "") { - action = uses - sub(/@.*/, "", action) - if (action ~ /^actions\/checkout$/ || - action ~ /^dtolnay\/rust-toolchain$/ || - action ~ /^Swatinem\/rust-cache$/ || - action ~ /^taiki-e\/install-action$/) { - reset(); return - } - if (action == "bnjbvr/cargo-machete") cmd = "cargo machete" - else if (action == "rustsec/audit-check") cmd = "cargo audit" - else if (action == "EmbarkStudios/cargo-deny-action") cmd = "cargo deny check" - else { printf "test: unmapped uses %s\n", action > "/dev/stderr"; exit 5 } - } else { reset(); return } - printf "%s|%s\n", name, cmd - reset() - } - function reset() { name=""; run=""; uses="" } - BEGIN { in_steps = 0; reset() } - /^ steps:[[:space:]]*$/ { in_steps = 1; next } - in_steps == 1 && /^[a-zA-Z]/ { in_steps = 0 } - in_steps == 1 && /^ - / { - flush() - line = $0; sub(/^ - /, "", line) - if (line ~ /^uses:[[:space:]]/) { sub(/^uses:[[:space:]]+/, "", line); uses = line } - else if (line ~ /^name:[[:space:]]/) { sub(/^name:[[:space:]]+/, "", line); name = line } - next - } - in_steps == 1 && /^ name:[[:space:]]/ { line = $0; sub(/^ name:[[:space:]]+/, "", line); name = line; next } - in_steps == 1 && /^ uses:[[:space:]]/ { line = $0; sub(/^ uses:[[:space:]]+/, "", line); uses = line; next } - in_steps == 1 && /^ run:[[:space:]]/ { line = $0; sub(/^ run:[[:space:]]+/, "", line); run = line; next } - END { flush() } -' "$YAML")" - -actual="$(bash "$STAGES" "$YAML")" - -if [ "$expected" != "$actual" ]; then - echo "FAIL: audit-stages.sh output diverges from yaml" >&2 - diff <(echo "$expected") <(echo "$actual") >&2 || true - exit 1 -fi - -# Sanity: exactly seven gate stages. -count="$(echo "$actual" | grep -c '|')" -if [ "$count" -ne 7 ]; then - echo "FAIL: expected 7 stages, got $count" >&2 - echo "$actual" >&2 - exit 1 -fi - -# Suppress unused-var warning for SETUP_USES_RE (kept for documentation). -: "$SETUP_USES_RE" - -echo "PASS: audit-stage-list-matches-yaml ($count stages)" diff --git a/code-et-implementer/tests/audit-summary-line-on-failure.sh b/code-et-implementer/tests/audit-summary-line-on-failure.sh deleted file mode 100755 index 1e59842..0000000 --- a/code-et-implementer/tests/audit-summary-line-on-failure.sh +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env bash -# audit-summary-line-on-failure.sh — assert the highest-severity finding is -# surfaced as a one-line summary at the top of the report and echoed to stderr -# on failure (US-12 / AC-12.1, AC-12.2). -# -# Two phases: -# 1. Writer-only: pipe synthetic findings into audit-report.sh and inspect -# the produced file. No cargo / rust toolchain needed. -# 2. Runner: drive audit.sh against a tiny tmp Cargo workspace whose source -# has a deterministic `cargo fmt --check` violation. Asserts the same -# summary line surfaces on stderr and matches the report's first line. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -WRITER="$SCRIPT_DIR/../scripts/audit-report.sh" -AUDIT="$SCRIPT_DIR/../scripts/audit.sh" - -# AC-12.1 pattern: literal `[<SEV>]` prefix where SEV is non-LOW, then space, -# then arbitrary content, then ` — fix:` (em-dash, U+2014). -SUMMARY_RE='^\[(CRITICAL|HIGH|MEDIUM)\] .* — fix:' - -TMPROOT="$(mktemp -d -t code-et-audit-summary.XXXXXX)" -trap 'rm -rf "$TMPROOT"' EXIT - -# ---------------------------------------------------------------------------- -# Phase 1: writer — synthetic findings. -# ---------------------------------------------------------------------------- - -# Case A: HIGH finding present → first line matches the summary pattern. -report_a="$TMPROOT/case-a.md" -printf 'MEDIUM|fmt|src/foo.rs|1|formatting drift\nHIGH|clippy (deny warnings)|src/bar.rs|42|unused variable `x`\n' \ - | bash "$WRITER" "$report_a" - -first_a="$(head -n 1 "$report_a")" -if ! [[ "$first_a" =~ $SUMMARY_RE ]]; then - echo "FAIL (case A): first line did not match summary regex" >&2 - echo " got: $first_a" >&2 - exit 1 -fi -# Should pick HIGH (not MEDIUM): the prefix tag must be HIGH. -if [[ "$first_a" != \[HIGH\]* ]]; then - echo "FAIL (case A): expected [HIGH] prefix, got: $first_a" >&2 - exit 1 -fi - -# Case B: LOW-only findings → no summary prefix; report starts with heading. -report_b="$TMPROOT/case-b.md" -printf 'LOW|cargo-machete (unused deps)|Cargo.toml|1|tool not installed\n' \ - | bash "$WRITER" "$report_b" - -first_b="$(head -n 1 "$report_b")" -if [[ "$first_b" =~ $SUMMARY_RE ]]; then - echo "FAIL (case B): LOW-only run produced a summary prefix" >&2 - echo " got: $first_b" >&2 - exit 1 -fi -if [[ "$first_b" != "# code-et audit report" ]]; then - echo "FAIL (case B): expected '# code-et audit report' as first line, got: $first_b" >&2 - exit 1 -fi - -# Case C: no findings → no summary prefix; report starts with heading. -report_c="$TMPROOT/case-c.md" -: | bash "$WRITER" "$report_c" -first_c="$(head -n 1 "$report_c")" -if [[ "$first_c" =~ $SUMMARY_RE ]]; then - echo "FAIL (case C): empty input produced a summary prefix" >&2 - echo " got: $first_c" >&2 - exit 1 -fi -if [[ "$first_c" != "# code-et audit report" ]]; then - echo "FAIL (case C): expected '# code-et audit report' as first line, got: $first_c" >&2 - exit 1 -fi - -# Case D: CRITICAL beats HIGH beats MEDIUM in the summary tag. -report_d="$TMPROOT/case-d.md" -printf 'MEDIUM|fmt|src/a.rs|1|m\nHIGH|tests|src/b.rs|2|h\nCRITICAL|cargo-audit (advisories)|Cargo.lock|1|RUSTSEC-XXXX\n' \ - | bash "$WRITER" "$report_d" -first_d="$(head -n 1 "$report_d")" -if [[ "$first_d" != \[CRITICAL\]* ]]; then - echo "FAIL (case D): expected [CRITICAL] prefix, got: $first_d" >&2 - exit 1 -fi - -# ---------------------------------------------------------------------------- -# Phase 2: runner — drive audit.sh against a tmp workspace with a deterministic -# fmt violation. Skip if `cargo` is not on PATH (keeps the writer phase usable -# on stripped CI images). -# ---------------------------------------------------------------------------- - -if ! command -v cargo >/dev/null 2>&1; then - echo "PASS: audit-summary-line-on-failure (writer phase only — cargo missing)" - exit 0 -fi - -WS="$TMPROOT/ws" -mkdir -p "$WS/src" -cat > "$WS/Cargo.toml" <<'EOF' -[package] -name = "audit-summary-fixture" -version = "0.0.1" -edition = "2021" - -[[bin]] -name = "audit-summary-fixture" -path = "src/main.rs" -EOF -# Single line, no trailing newline guarantees `cargo fmt --check` will flag it. -printf 'fn main(){let x=1;let _=x;}' > "$WS/src/main.rs" - -stderr_log="$TMPROOT/audit.stderr" -set +e -bash "$AUDIT" "$WS" 2> "$stderr_log" >/dev/null -rc=$? -set -e - -if [ "$rc" -eq 0 ]; then - echo "FAIL (runner): audit.sh exited 0 on a workspace with fmt violation" >&2 - cat "$stderr_log" >&2 - exit 1 -fi - -# Locate the report file the runner wrote. -report="$(ls "$WS/.claude/audit-"*.md 2>/dev/null | head -n1 || true)" -if [ -z "$report" ] || [ ! -f "$report" ]; then - echo "FAIL (runner): no audit report written under $WS/.claude/" >&2 - cat "$stderr_log" >&2 - exit 1 -fi - -first_line="$(head -n 1 "$report")" -if ! [[ "$first_line" =~ $SUMMARY_RE ]]; then - echo "FAIL (runner): report first line missing summary prefix" >&2 - echo " got: $first_line" >&2 - exit 1 -fi - -# AC-12.2: the same summary line must appear on the runner's stderr. -if ! grep -Fq -- "$first_line" "$stderr_log"; then - echo "FAIL (runner): summary line not echoed to stderr" >&2 - echo " expected: $first_line" >&2 - echo " --- stderr ---" >&2 - cat "$stderr_log" >&2 - exit 1 -fi - -echo "PASS: audit-summary-line-on-failure" diff --git a/code-et-implementer/tests/doctrine-links.bats b/code-et-implementer/tests/doctrine-links.bats deleted file mode 100644 index 2c2c802..0000000 --- a/code-et-implementer/tests/doctrine-links.bats +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env bats - -# Verify the doctrine files exist with expected structure. Smoke test — -# guards against accidental deletion or stripped frontmatter. - -DOCS="${BATS_TEST_DIRNAME}/../docs" - -@test "architecture.md exists" { - [ -f "$DOCS/architecture.md" ] -} - -@test "architecture.md has frontmatter" { - head -1 "$DOCS/architecture.md" | grep -q '^---$' - grep -q '^name: architecture' "$DOCS/architecture.md" -} - -@test "architecture.md contains Uncle Bob excerpt" { - grep -q "The Dependency Rule" "$DOCS/architecture.md" - grep -q "source code dependencies" "$DOCS/architecture.md" -} - -@test "architecture.md describes the four layers" { - grep -q "domain" "$DOCS/architecture.md" - grep -q "application" "$DOCS/architecture.md" - grep -q "infrastructure" "$DOCS/architecture.md" - grep -q "interface" "$DOCS/architecture.md" -} - -@test "architecture.md has Dioxus targets matrix" { - grep -q "Dioxus" "$DOCS/architecture.md" - grep -q "web" "$DOCS/architecture.md" - grep -q "desktop" "$DOCS/architecture.md" - grep -q "mobile" "$DOCS/architecture.md" -} - -@test "architecture.md has database section with GCP and SQLite" { - grep -q "Cloud SQL" "$DOCS/architecture.md" - grep -q "SQLite" "$DOCS/architecture.md" - grep -q "sqlx" "$DOCS/architecture.md" -} - -@test "architecture.md has secrets baseline" { - grep -q "Secret Manager" "$DOCS/architecture.md" - grep -qi "secrecy" "$DOCS/architecture.md" -} - -@test "architecture.md has Rust security checklist" { - grep -qi "security checklist" "$DOCS/architecture.md" - grep -q "unsafe" "$DOCS/architecture.md" - grep -q "cargo audit" "$DOCS/architecture.md" -} - -@test "anti-slop.md exists with frontmatter" { - [ -f "$DOCS/anti-slop.md" ] - head -1 "$DOCS/anti-slop.md" | grep -q '^---$' - grep -q '^name: anti-slop' "$DOCS/anti-slop.md" -} - -@test "anti-slop.md has the 4 elements" { - grep -q "dead code" "$DOCS/anti-slop.md" - grep -q "[Dd]uplication" "$DOCS/anti-slop.md" - grep -q "[Cc]omplexity" "$DOCS/anti-slop.md" - grep -q "[Aa]rchitecture drift" "$DOCS/anti-slop.md" -} - -@test "anti-slop.md has 5 slop categories" { - grep -q "Superficial Competence" "$DOCS/anti-slop.md" - grep -q "Unnecessary Complexity" "$DOCS/anti-slop.md" - grep -q "Defensive Over-Programming" "$DOCS/anti-slop.md" - grep -q "Mirror Tests" "$DOCS/anti-slop.md" - grep -q "Inconsistent Styling" "$DOCS/anti-slop.md" -} - -@test "anti-slop.md has the 4-stage CI loop" { - grep -q "Static validation" "$DOCS/anti-slop.md" - grep -q "Architectural check" "$DOCS/anti-slop.md" - grep -q "Dependency audit" "$DOCS/anti-slop.md" - grep -qi "complexity.*duplication\|complexity & duplication" "$DOCS/anti-slop.md" -} - -@test "anti-slop.md has Rule of Three" { - grep -q "Rule of Three" "$DOCS/anti-slop.md" -} - -@test "testing.md exists with frontmatter" { - [ -f "$DOCS/testing.md" ] - head -1 "$DOCS/testing.md" | grep -q '^---$' - grep -q '^name: testing' "$DOCS/testing.md" -} - -@test "testing.md has per-layer matrix" { - grep -q "domain" "$DOCS/testing.md" - grep -q "application" "$DOCS/testing.md" - grep -q "infrastructure" "$DOCS/testing.md" - grep -q "interface" "$DOCS/testing.md" -} - -@test "testing.md bans mirror tests" { - grep -qi "mirror.*test" "$DOCS/testing.md" - grep -qi "ban\|banned" "$DOCS/testing.md" -} - -@test "testing.md uses cargo-nextest" { - grep -q "cargo.nextest\|cargo-nextest" "$DOCS/testing.md" -} - -@test "testing.md cross-links to engineering testing-strategy skill" { - grep -q "testing-strategy" "$DOCS/testing.md" -} - -@test "CLAUDE.md has Clean Architecture (Rust) controlling rules section" { - grep -q "Clean Architecture (Rust)" "${BATS_TEST_DIRNAME}/../CLAUDE.md" - grep -q "controlling rules" "${BATS_TEST_DIRNAME}/../CLAUDE.md" -} - -@test "CLAUDE.md no longer contains stale bun test example" { - ! grep -q "bun test && bun run lint" "${BATS_TEST_DIRNAME}/../CLAUDE.md" -} - -@test "CLAUDE.md uses Rust verification example" { - grep -q "cargo nextest run" "${BATS_TEST_DIRNAME}/../CLAUDE.md" - grep -q "cargo clippy" "${BATS_TEST_DIRNAME}/../CLAUDE.md" -} - -@test "CLAUDE.md names the engineering plugin and rust-analyzer-lsp" { - grep -qi "engineering" "${BATS_TEST_DIRNAME}/../CLAUDE.md" - grep -q "rust-analyzer-lsp" "${BATS_TEST_DIRNAME}/../CLAUDE.md" -} diff --git a/code-et-implementer/tests/fixtures/plans/.keep b/code-et-implementer/tests/fixtures/plans/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/code-et-implementer/tests/implement-chain-halts-on-audit-failure.sh b/code-et-implementer/tests/implement-chain-halts-on-audit-failure.sh deleted file mode 100755 index b341391..0000000 --- a/code-et-implementer/tests/implement-chain-halts-on-audit-failure.sh +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env bash -# implement-chain-halts-on-audit-failure.sh — assert /code:implement chains -# `--fast` audit after simplify and that a fast audit failure halts the loop -# with the audit report path surfaced (US-2 / AC-2.1, AC-2.2). -# -# We can't drive the full Claude orchestrator from a bash test, so the chain -# assertion is split into two empirical phases: -# -# Phase A — wiring (AC-2.1): grep implement.md to confirm the line -# containing `Skill("simplify")` is followed by an `audit` invocation -# in `--fast` mode. This mirrors the verification in the task manifest. -# -# Phase B — regression fixture (AC-2.2): build a tiny Rust workspace whose -# `cargo clippy ... -D warnings` fails (unused variable). Drive -# `audit.sh --fast` directly, assert non-zero exit, that a report file -# was written under `<workspace>/.claude/`, and that the runner echoes -# `audit: report written to <path>` on stderr — the surfacing mechanism -# a Skill chain inherits via the harness. Skip phase B if `cargo` is -# missing so the structural check still runs on stripped CI images. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -IMPLEMENT_MD="$SCRIPT_DIR/../commands/implement.md" -AUDIT="$SCRIPT_DIR/../scripts/audit.sh" - -# ---------------------------------------------------------------------------- -# Phase A — structural wiring (AC-2.1). -# ---------------------------------------------------------------------------- - -if [ ! -f "$IMPLEMENT_MD" ]; then - echo "FAIL (phase A): implement.md not found at $IMPLEMENT_MD" >&2 - exit 1 -fi - -# The line carrying Skill("simplify") must be followed (within `grep -A1`) by -# an `audit … --fast` reference. Same predicate the task manifest uses. -if ! grep -A1 'Skill("simplify")' "$IMPLEMENT_MD" | grep -q 'audit.*--fast'; then - echo "FAIL (phase A): implement.md does not chain audit --fast after Skill(\"simplify\")" >&2 - echo " expected: a line matching 'audit.*--fast' immediately after the simplify call" >&2 - echo " --- relevant slice ---" >&2 - grep -n -A2 'Skill("simplify")' "$IMPLEMENT_MD" >&2 || true - exit 1 -fi - -# ---------------------------------------------------------------------------- -# Phase B — regression fixture (AC-2.2). -# Skip cleanly if cargo is not on PATH; phase A still proves the wiring. -# ---------------------------------------------------------------------------- - -if ! command -v cargo >/dev/null 2>&1; then - echo "PASS: implement-chain-halts-on-audit-failure (phase A only — cargo missing)" - exit 0 -fi - -TMPROOT="$(mktemp -d -t code-et-implement-chain.XXXXXX)" -trap 'rm -rf "$TMPROOT"' EXIT - -WS="$TMPROOT/ws" -mkdir -p "$WS/src" - -# Standalone Cargo package — `audit.sh` accepts a target dir and runs the -# stages with that dir as the working directory. -cat > "$WS/Cargo.toml" <<'EOF' -[package] -name = "implement-chain-fixture" -version = "0.0.1" -edition = "2021" - -[[bin]] -name = "implement-chain-fixture" -path = "src/main.rs" -EOF - -# Source: well-formatted (so `cargo fmt --check` passes) but carries an -# unused variable — `cargo clippy ... -D warnings` promotes the rustc -# `unused_variables` lint to an error and stage 2 fails. That is the -# "known clippy regression" the chain must catch. -cat > "$WS/src/main.rs" <<'EOF' -fn main() { - let x = 1; -} -EOF - -stderr_log="$TMPROOT/audit.stderr" -stdout_log="$TMPROOT/audit.stdout" - -set +e -bash "$AUDIT" --fast "$WS" > "$stdout_log" 2> "$stderr_log" -rc=$? -set -e - -if [ "$rc" -eq 0 ]; then - echo "FAIL (phase B): audit.sh --fast exited 0 on a workspace with a clippy regression" >&2 - echo " --- stderr ---" >&2 - cat "$stderr_log" >&2 - exit 1 -fi - -# Locate the report file the runner wrote. -report="$(ls "$WS/.claude/audit-"*.md 2>/dev/null | head -n1 || true)" -if [ -z "$report" ] || [ ! -f "$report" ]; then - echo "FAIL (phase B): no audit report written under $WS/.claude/" >&2 - cat "$stderr_log" >&2 - exit 1 -fi - -if [ ! -s "$report" ]; then - echo "FAIL (phase B): audit report is empty: $report" >&2 - exit 1 -fi - -# AC-2.2 surfacing: the runner echoes the report path on stderr. That is the -# string the implement chain (or any caller) sees and can forward to chat. -if ! grep -Fq "audit: report written to $report" "$stderr_log"; then - echo "FAIL (phase B): report path not surfaced on stderr" >&2 - echo " expected: audit: report written to $report" >&2 - echo " --- stderr ---" >&2 - cat "$stderr_log" >&2 - exit 1 -fi - -# Sanity: `--fast` must run only stages 1-2. Stage 3+ names should never -# appear in the runner's stderr trace. If they do, the chain hook would be -# slower than the PRD allows. -for forbidden in "layer-deps validator" "cargo-machete" "cargo-audit" "cargo-deny" "tests"; do - if grep -Fq "$forbidden — running" "$stderr_log"; then - echo "FAIL (phase B): --fast invoked a stage outside 1-2: '$forbidden'" >&2 - cat "$stderr_log" >&2 - exit 1 - fi -done - -echo "PASS: implement-chain-halts-on-audit-failure" diff --git a/code-et-implementer/tests/layer-deps-validator.bats b/code-et-implementer/tests/layer-deps-validator.bats deleted file mode 100644 index d6dd5fd..0000000 --- a/code-et-implementer/tests/layer-deps-validator.bats +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env bats - -# layer-deps-validator.sh — defence-in-depth layer-direction check. - -setup() { - REPO="$(mktemp -d)" - cd "$REPO" - git init -q - git config user.email "t@t" - git config user.name "t" - git commit --allow-empty -q -m init - cp "${BATS_TEST_DIRNAME}/../templates/shared/scripts/layer-deps-validator.sh" "$REPO/validator.sh" - chmod +x "$REPO/validator.sh" -} - -teardown() { rm -rf "$REPO"; } - -# Helper: write a minimal Cargo.toml under crates/<layer>/ with a dependency block. -# Args: layer, deps... -make_crate() { - local layer="$1"; shift - mkdir -p "crates/$layer" - { - echo "[package]" - echo "name = \"$layer\"" - echo "version = \"0.1.0\"" - echo "edition = \"2024\"" - echo - echo "[dependencies]" - for dep in "$@"; do - if [ -d "crates/$dep" ]; then - echo "$dep = { path = \"../$dep\" }" - else - echo "$dep = \"1\"" - fi - done - } > "crates/$layer/Cargo.toml" -} - -@test "clean: domain has no workspace deps" { - make_crate domain serde - make_crate application domain async-trait - make_crate infrastructure application domain sqlx - make_crate interface application domain dioxus - run bash validator.sh - [ "$status" -eq 0 ] - echo "$output" | grep -q "clean" -} - -@test "violation: domain depends on infrastructure" { - make_crate infrastructure - make_crate domain infrastructure serde - make_crate application domain - make_crate interface application domain - run bash validator.sh - [ "$status" -eq 1 ] - echo "$output" | grep -q "layer violation" -} - -@test "violation: application depends on infrastructure" { - make_crate domain serde - make_crate infrastructure - make_crate application domain infrastructure async-trait - make_crate interface application domain - run bash validator.sh - [ "$status" -eq 1 ] - echo "$output" | grep -q "layer violation" -} - -@test "violation: interface depends on infrastructure" { - make_crate domain serde - make_crate application domain - make_crate infrastructure application domain - make_crate interface application domain infrastructure dioxus - run bash validator.sh - [ "$status" -eq 1 ] - echo "$output" | grep -q "layer violation" -} - -@test "no-op: project without crates/ directory" { - # Simulating a non-clean-architecture Rust project (e.g. a single-crate cargo new) - echo '[workspace]' > Cargo.toml - run bash validator.sh - [ "$status" -eq 0 ] - echo "$output" | grep -q "clean" -} - -@test "third-party deps don't count as layer violations" { - make_crate domain serde uuid time thiserror - run bash validator.sh - [ "$status" -eq 0 ] -} diff --git a/code-et-implementer/tests/layer-tag-enforcement.bats b/code-et-implementer/tests/layer-tag-enforcement.bats deleted file mode 100644 index 59ce2ea..0000000 --- a/code-et-implementer/tests/layer-tag-enforcement.bats +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env bats - -# task-created-tag-check.sh extension: metadata.layer is required on Rust -# projects (Cargo.toml at repo root). Non-Rust projects unchanged. - -setup() { - REPO="$(mktemp -d)" - cd "$REPO" - git init -q - git config user.email "t@t" - git config user.name "t" - git commit --allow-empty -q -m init - git checkout -q -b main 2>/dev/null || true - mkdir -p plans - export SCRIPT="${BATS_TEST_DIRNAME}/../scripts/task-created-tag-check.sh" -} - -teardown() { rm -rf "$REPO"; } - -# --- Rust project (Cargo.toml at root) --- - -@test "rust project: allows valid user_story + valid layer" { - echo '[workspace]' > Cargo.toml - touch plans/2026-05-06-feature.md - git checkout -q -b feature/feature - run bash -c 'echo "{\"tool_input\":{\"metadata\":{\"user_story\":\"US-1\",\"layer\":\"domain\"}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] -} - -@test "rust project: blocks missing layer" { - echo '[workspace]' > Cargo.toml - touch plans/2026-05-06-feature.md - git checkout -q -b feature/feature - run bash -c 'echo "{\"tool_input\":{\"metadata\":{\"user_story\":\"US-1\"}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 2 ] -} - -@test "rust project: blocks invalid layer" { - echo '[workspace]' > Cargo.toml - touch plans/2026-05-06-feature.md - git checkout -q -b feature/feature - run bash -c 'echo "{\"tool_input\":{\"metadata\":{\"user_story\":\"US-1\",\"layer\":\"god\"}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 2 ] -} - -@test "rust project: blocks missing user_story even with valid layer" { - echo '[workspace]' > Cargo.toml - touch plans/2026-05-06-feature.md - git checkout -q -b feature/feature - run bash -c 'echo "{\"tool_input\":{\"metadata\":{\"layer\":\"domain\"}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 2 ] -} - -@test "rust project: chore layer accepted" { - echo '[workspace]' > Cargo.toml - touch plans/2026-05-06-feature.md - git checkout -q -b feature/feature - run bash -c 'echo "{\"tool_input\":{\"metadata\":{\"user_story\":\"chore:bump deps\",\"layer\":\"chore\"}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] -} - -@test "rust project: all four production layers accepted" { - echo '[workspace]' > Cargo.toml - touch plans/2026-05-06-feature.md - git checkout -q -b feature/feature - for layer in domain application infrastructure interface; do - run bash -c 'echo "{\"tool_input\":{\"metadata\":{\"user_story\":\"US-1\",\"layer\":\"'"$layer"'\"}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] - done -} - -# --- Non-Rust project (no Cargo.toml) --- - -@test "non-rust project: layer is optional" { - touch plans/2026-05-06-feature.md - git checkout -q -b feature/feature - run bash -c 'echo "{\"tool_input\":{\"metadata\":{\"user_story\":\"US-1\"}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] -} - -@test "non-rust project: bug lane still permits anything" { - git checkout -q -b fix/login-crash - run bash -c 'echo "{\"tool_input\":{\"metadata\":{}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] -} diff --git a/code-et-implementer/tests/pre-compact-prd.bats b/code-et-implementer/tests/pre-compact-prd.bats deleted file mode 100644 index c68b98f..0000000 --- a/code-et-implementer/tests/pre-compact-prd.bats +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bats - -setup() { - REPO="$(mktemp -d)" - cd "$REPO" - git init -q - git config user.email "t@t" - git config user.name "t" - git commit --allow-empty -q -m init - git checkout -q -b main 2>/dev/null || true - mkdir -p plans - export SCRIPT="${BATS_TEST_DIRNAME}/../scripts/pre-compact-prd.sh" -} - -teardown() { rm -rf "$REPO"; } - -@test "no PRD: emits empty JSON" { - git checkout -q -b feature/nothing - run "$SCRIPT" - [ "$status" -eq 0 ] - [ "$output" = "{}" ] -} - -@test "PRD with few open stories includes all" { - { - echo "# Plan" - for i in 1 2 3; do echo "- [ ] US-$i: thing $i"; done - echo "- [x] US-99: done" - } > plans/2026-04-20-x.md - git checkout -q -b feature/x - run "$SCRIPT" - [ "$status" -eq 0 ] - [[ "$output" == *"US-1"* ]] - [[ "$output" == *"US-2"* ]] - [[ "$output" == *"US-3"* ]] - [[ "$output" != *"US-99"* ]] -} - -@test "PRD with many open stories emits summary" { - { - echo "# Plan" - for i in $(seq 1 25); do echo "- [ ] US-$i: thing $i"; done - } > plans/2026-04-20-big.md - git checkout -q -b feature/big - run "$SCRIPT" - [ "$status" -eq 0 ] - [[ "$output" == *"25 open stories"* ]] - [[ "$output" == *"plans/2026-04-20-big.md"* ]] -} diff --git a/code-et-implementer/tests/resolve-prd.bats b/code-et-implementer/tests/resolve-prd.bats deleted file mode 100644 index 7d51c53..0000000 --- a/code-et-implementer/tests/resolve-prd.bats +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bats - -setup() { - REPO="$(mktemp -d)" - cd "$REPO" - git init -q - git config user.email "t@t" - git config user.name "t" - git commit --allow-empty -q -m init - git checkout -q -b main 2>/dev/null || true - mkdir -p plans - export SCRIPT="${BATS_TEST_DIRNAME}/../scripts/resolve-prd.sh" -} - -teardown() { rm -rf "$REPO"; } - -@test "returns matching PRD for feature/<slug>" { - touch "plans/2026-04-20-dark-mode.md" - git checkout -q -b feature/dark-mode - run "$SCRIPT" - [ "$status" -eq 0 ] - [[ "$output" == *"plans/2026-04-20-dark-mode.md" ]] -} - -@test "strips fix/ prefix" { - touch "plans/2026-04-20-login-bug.md" - git checkout -q -b fix/login-bug - run "$SCRIPT" - [ "$status" -eq 0 ] - [[ "$output" == *"plans/2026-04-20-login-bug.md" ]] -} - -@test "strips chore/ prefix" { - touch "plans/2026-04-20-deps-bump.md" - git checkout -q -b chore/deps-bump - run "$SCRIPT" - [ "$status" -eq 0 ] - [[ "$output" == *"plans/2026-04-20-deps-bump.md" ]] -} - -@test "picks most recent when multiple dates exist" { - touch "plans/2026-01-01-dark-mode.md" - touch "plans/2026-04-20-dark-mode.md" - git checkout -q -b feature/dark-mode - run "$SCRIPT" - [ "$status" -eq 0 ] - [[ "$output" == *"2026-04-20-dark-mode.md" ]] -} - -@test "exits 1 with empty output when no PRD matches" { - git checkout -q -b feature/unknown - run "$SCRIPT" - [ "$status" -eq 1 ] - [ -z "$output" ] -} - -@test "accepts branch name as arg 1" { - touch "plans/2026-04-20-dark-mode.md" - run "$SCRIPT" "feature/dark-mode" - [ "$status" -eq 0 ] - [[ "$output" == *"2026-04-20-dark-mode.md" ]] -} - -@test "ignores non-dated plan files (legacy)" { - touch "plans/cheeky-wibbling-puddle.md" - git checkout -q -b feature/cheeky-wibbling-puddle - run "$SCRIPT" - [ "$status" -eq 1 ] -} diff --git a/code-et-implementer/tests/run-tests.sh b/code-et-implementer/tests/run-tests.sh deleted file mode 100755 index 0197e6c..0000000 --- a/code-et-implementer/tests/run-tests.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -cd "$(dirname "$0")" -bats --tap *.bats diff --git a/code-et-implementer/tests/session-start-prd.bats b/code-et-implementer/tests/session-start-prd.bats deleted file mode 100644 index 0c02497..0000000 --- a/code-et-implementer/tests/session-start-prd.bats +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bats - -setup() { - REPO="$(mktemp -d)" - cd "$REPO" - git init -q - git config user.email "t@t" - git config user.name "t" - git commit --allow-empty -q -m init - git checkout -q -b main 2>/dev/null || true - mkdir -p plans - export SCRIPT="${BATS_TEST_DIRNAME}/../scripts/session-start-prd.sh" -} - -teardown() { rm -rf "$REPO"; } - -@test "no PRD: emits empty JSON, exits 0" { - git checkout -q -b feature/no-prd - run "$SCRIPT" - [ "$status" -eq 0 ] - [[ "$output" == "{}" ]] -} - -@test "PRD found: emits context JSON with path and open stories" { - cat > plans/2026-04-20-dark-mode.md <<'EOF' -# Dark Mode - -- [ ] US-1: toggle component -- [x] US-2: persist preference -- [ ] US-3: system theme detection -EOF - git checkout -q -b feature/dark-mode - run "$SCRIPT" - [ "$status" -eq 0 ] - [[ "$output" == *"Active PRD:"* ]] - [[ "$output" == *"plans/2026-04-20-dark-mode.md"* ]] - [[ "$output" == *"US-1"* ]] - [[ "$output" == *"US-3"* ]] -} - -@test "PRD with no open stories: still emits context with (none)" { - cat > plans/2026-04-20-done.md <<'EOF' -# Done - -- [x] US-1: thing -EOF - git checkout -q -b feature/done - run "$SCRIPT" - [ "$status" -eq 0 ] - [[ "$output" == *"Active PRD:"* ]] - [[ "$output" == *"(none)"* ]] -} diff --git a/code-et-implementer/tests/task-created-tag-check.bats b/code-et-implementer/tests/task-created-tag-check.bats deleted file mode 100644 index ecd48aa..0000000 --- a/code-et-implementer/tests/task-created-tag-check.bats +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env bats - -setup() { - REPO="$(mktemp -d)" - cd "$REPO" - git init -q - git config user.email "t@t" - git config user.name "t" - git commit --allow-empty -q -m init - git checkout -q -b main 2>/dev/null || true - mkdir -p plans - export SCRIPT="${BATS_TEST_DIRNAME}/../scripts/task-created-tag-check.sh" -} - -teardown() { rm -rf "$REPO"; } - -@test "allows US-N tag when PRD exists" { - touch plans/2026-04-20-dark-mode.md - git checkout -q -b feature/dark-mode - run bash -c 'echo "{\"tool_input\":{\"metadata\":{\"user_story\":\"US-3\"}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] -} - -@test "allows AC-N.M tag when PRD exists" { - touch plans/2026-04-20-dark-mode.md - git checkout -q -b feature/dark-mode - run bash -c 'echo "{\"tool_input\":{\"metadata\":{\"user_story\":\"AC-3.2\"}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] -} - -@test "allows chore:<reason> tag when PRD exists" { - touch plans/2026-04-20-dark-mode.md - git checkout -q -b feature/dark-mode - run bash -c 'echo "{\"tool_input\":{\"metadata\":{\"user_story\":\"chore:bump tailwind\"}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] -} - -@test "blocks missing tag when PRD exists" { - touch plans/2026-04-20-dark-mode.md - git checkout -q -b feature/dark-mode - run bash -c 'echo "{\"tool_input\":{\"metadata\":{}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 2 ] -} - -@test "blocks invalid tag when PRD exists" { - touch plans/2026-04-20-dark-mode.md - git checkout -q -b feature/dark-mode - run bash -c 'echo "{\"tool_input\":{\"metadata\":{\"user_story\":\"random\"}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 2 ] -} - -@test "allows any task when no PRD (bug lane)" { - git checkout -q -b fix/login-crash - run bash -c 'echo "{\"tool_input\":{\"metadata\":{}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] -} - -@test "allows user_story:none when no PRD" { - git checkout -q -b fix/login-crash - run bash -c 'echo "{\"tool_input\":{\"metadata\":{\"user_story\":\"none\"}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] -} - -@test "tolerates stringified metadata when PRD exists" { - touch plans/2026-04-20-dark-mode.md - git checkout -q -b feature/dark-mode - run bash -c 'echo "{\"tool_input\":{\"metadata\":\"{\\\"user_story\\\":\\\"US-3\\\"}\"}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] -} - -@test "tolerates flattened metadata fields on tool_input" { - touch plans/2026-04-20-dark-mode.md - git checkout -q -b feature/dark-mode - run bash -c 'echo "{\"tool_input\":{\"user_story\":\"US-3\",\"layer\":\"interface\"}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] -} - -@test "tolerates metadata at payload root (no tool_input wrapper)" { - touch plans/2026-04-20-dark-mode.md - git checkout -q -b feature/dark-mode - run bash -c 'echo "{\"metadata\":{\"user_story\":\"AC-1.2\"}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] -} - -@test "deep-searches for user_story in nested envelopes" { - touch plans/2026-04-20-dark-mode.md - git checkout -q -b feature/dark-mode - run bash -c 'echo "{\"params\":{\"input\":{\"task\":{\"metadata\":{\"user_story\":\"US-7\"}}}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] -} - -@test "accepts real PreToolUse(TaskCreate) envelope with metadata" { - touch plans/2026-04-20-dark-mode.md - git checkout -q -b feature/dark-mode - run bash -c 'echo "{\"session_id\":\"s1\",\"hook_event_name\":\"PreToolUse\",\"tool_name\":\"TaskCreate\",\"tool_input\":{\"subject\":\"T1\",\"description\":\"d\",\"metadata\":{\"user_story\":\"US-1\",\"layer\":\"interface\"}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 0 ] -} - -@test "rejects real PreToolUse(TaskCreate) envelope without metadata" { - touch plans/2026-04-20-dark-mode.md - git checkout -q -b feature/dark-mode - run bash -c 'echo "{\"session_id\":\"s1\",\"hook_event_name\":\"PreToolUse\",\"tool_name\":\"TaskCreate\",\"tool_input\":{\"subject\":\"T1\",\"description\":\"d\"}}" | "$0"' "$SCRIPT" - [ "$status" -eq 2 ] -} - -@test "rejection writes diagnostic dump to debug_dir" { - touch plans/2026-04-20-dark-mode.md - git checkout -q -b feature/dark-mode - export TMPDIR="$REPO/tmp" - mkdir -p "$TMPDIR" - run bash -c 'echo "{\"tool_input\":{\"metadata\":{\"user_story\":\"bogus\"}}}" | "$0"' "$SCRIPT" - [ "$status" -eq 2 ] - [ -f "$TMPDIR/code-et-task-hook/last-rejected.json" ] - run jq -r '.extracted.user_story' "$TMPDIR/code-et-task-hook/last-rejected.json" - [ "$output" = "bogus" ] -} diff --git a/code-et-implementer/tests/verify-gate-runs-audit-on-rust.sh b/code-et-implementer/tests/verify-gate-runs-audit-on-rust.sh deleted file mode 100755 index 51faaf1..0000000 --- a/code-et-implementer/tests/verify-gate-runs-audit-on-rust.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env bash -# verify-gate-runs-audit-on-rust.sh — assert verify-gate.sh invokes -# audit.sh --fast when a Cargo.toml is present at the repo root (AC-3.1). -# -# Strategy: stage a copy of verify-gate.sh in a fresh scripts dir alongside -# stub run-tests.sh and audit.sh shims that drop sentinel files when called. -# Run a Rust workspace fixture (just a Cargo.toml) through the gate; assert -# both stubs were called and the gate exits 0. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -GATE_SRC="$SCRIPT_DIR/../scripts/verify-gate.sh" - -TMPDIR_TEST="$(mktemp -d -t code-et-gate-rust.XXXXXX)" -trap 'rm -rf "$TMPDIR_TEST"' EXIT - -WORKSPACE="$TMPDIR_TEST/workspace" -SCRIPTS="$TMPDIR_TEST/scripts" -mkdir -p "$WORKSPACE" "$SCRIPTS" - -# Minimal Rust workspace — Cargo.toml at root is the only signal verify-gate -# uses to gate the audit step. -cat > "$WORKSPACE/Cargo.toml" <<'EOF' -[workspace] -members = [] -resolver = "2" -EOF - -SENTINEL_TESTS="$TMPDIR_TEST/run-tests-was-called" -SENTINEL_AUDIT="$TMPDIR_TEST/audit-was-called" - -# Stub run-tests.sh — drops sentinel, exits 0. -cat > "$SCRIPTS/run-tests.sh" <<EOF -#!/usr/bin/env bash -touch "$SENTINEL_TESTS" -exit 0 -EOF -chmod +x "$SCRIPTS/run-tests.sh" - -# Stub audit.sh — drops sentinel with the args it received, exits 0. -cat > "$SCRIPTS/audit.sh" <<EOF -#!/usr/bin/env bash -echo "\$*" > "$SENTINEL_AUDIT" -exit 0 -EOF -chmod +x "$SCRIPTS/audit.sh" - -cp "$GATE_SRC" "$SCRIPTS/verify-gate.sh" -chmod +x "$SCRIPTS/verify-gate.sh" - -# Run from inside the workspace so the git/pwd fallback resolves there. -# Outside any git tree -> falls back to PWD; PWD has Cargo.toml -> audit runs. -set +e -(cd "$WORKSPACE" && bash "$SCRIPTS/verify-gate.sh" </dev/null) -rc=$? -set -e - -if [ "$rc" -ne 0 ]; then - echo "FAIL: verify-gate.sh exited $rc on Rust workspace; expected 0" >&2 - exit 1 -fi - -if [ ! -f "$SENTINEL_TESTS" ]; then - echo "FAIL: run-tests.sh stub was not invoked" >&2 - exit 1 -fi - -if [ ! -f "$SENTINEL_AUDIT" ]; then - echo "FAIL: audit.sh stub was not invoked on Rust workspace" >&2 - exit 1 -fi - -# Sanity: the audit was called with --fast and the workspace path. -if ! grep -q -- "--fast" "$SENTINEL_AUDIT"; then - echo "FAIL: audit invocation missing --fast flag" >&2 - cat "$SENTINEL_AUDIT" >&2 - exit 1 -fi -if ! grep -qF "$WORKSPACE" "$SENTINEL_AUDIT"; then - echo "FAIL: audit invocation missing workspace path" >&2 - cat "$SENTINEL_AUDIT" >&2 - exit 1 -fi - -echo "PASS: verify-gate-runs-audit-on-rust" diff --git a/code-et-implementer/tests/verify-gate-skips-audit-on-non-rust.sh b/code-et-implementer/tests/verify-gate-skips-audit-on-non-rust.sh deleted file mode 100755 index ad23d44..0000000 --- a/code-et-implementer/tests/verify-gate-skips-audit-on-non-rust.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env bash -# verify-gate-skips-audit-on-non-rust.sh — assert verify-gate.sh skips the -# audit step when no Cargo.toml is present at the repo root (AC-3.2). -# -# Same shim strategy as the rust variant: a sentinel-touching stub for -# audit.sh that must NOT be created when the gate runs on a bare temp dir. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -GATE_SRC="$SCRIPT_DIR/../scripts/verify-gate.sh" - -TMPDIR_TEST="$(mktemp -d -t code-et-gate-norust.XXXXXX)" -trap 'rm -rf "$TMPDIR_TEST"' EXIT - -WORKSPACE="$TMPDIR_TEST/workspace" -SCRIPTS="$TMPDIR_TEST/scripts" -mkdir -p "$WORKSPACE" "$SCRIPTS" - -# Bare workspace — no Cargo.toml, no git tree. - -SENTINEL_TESTS="$TMPDIR_TEST/run-tests-was-called" -SENTINEL_AUDIT="$TMPDIR_TEST/audit-was-called" - -cat > "$SCRIPTS/run-tests.sh" <<EOF -#!/usr/bin/env bash -touch "$SENTINEL_TESTS" -exit 0 -EOF -chmod +x "$SCRIPTS/run-tests.sh" - -# Audit shim — if invoked on a non-Rust tree, this is a regression. The -# sentinel must not be created. -cat > "$SCRIPTS/audit.sh" <<EOF -#!/usr/bin/env bash -echo "\$*" > "$SENTINEL_AUDIT" -exit 0 -EOF -chmod +x "$SCRIPTS/audit.sh" - -cp "$GATE_SRC" "$SCRIPTS/verify-gate.sh" -chmod +x "$SCRIPTS/verify-gate.sh" - -STDERR_LOG="$TMPDIR_TEST/stderr.log" - -set +e -(cd "$WORKSPACE" && bash "$SCRIPTS/verify-gate.sh" </dev/null) 2> "$STDERR_LOG" -rc=$? -set -e - -if [ "$rc" -ne 0 ]; then - echo "FAIL: verify-gate.sh exited $rc on non-Rust dir; expected 0" >&2 - cat "$STDERR_LOG" >&2 - exit 1 -fi - -if [ ! -f "$SENTINEL_TESTS" ]; then - echo "FAIL: run-tests.sh stub was not invoked" >&2 - exit 1 -fi - -if [ -f "$SENTINEL_AUDIT" ]; then - echo "FAIL: audit.sh was invoked on non-Rust dir; expected silent skip" >&2 - cat "$SENTINEL_AUDIT" >&2 - exit 1 -fi - -# Silent skip: stderr should not mention the audit or the workspace skip. -if grep -qi "audit\|not a Rust workspace" "$STDERR_LOG"; then - echo "FAIL: skip path emitted stderr; expected silent" >&2 - cat "$STDERR_LOG" >&2 - exit 1 -fi - -echo "PASS: verify-gate-skips-audit-on-non-rust" From 57803e5a6492607bddbe6ac2834e83dbe259ae6d Mon Sep 17 00:00:00 2001 From: Kennet Dahl Kusk <kennet.dahl.kusk@visma.com> Date: Sun, 17 May 2026 22:07:50 +0200 Subject: [PATCH 4/5] v5.0.1: make the TS lint stack explicit; drop any lingering Rust-lint framing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The framework already used Biome (lint + format in one binary) for the TS template, but the docs and the install-ci command treated the lint stage as an implicit slice of `audit`. After dropping the Rust toolchain (clippy / rustfmt / cargo-deny / etc.) in v5.0.0, name the TS lint stack explicitly: - biome.json — add useConst, useTemplate, noImplicitAnyLet, noUnusedFunctionParameters, noUselessLoneBlockStatements, noUselessTypeConstraint. Stays inside `recommended` + the existing custom rules; smoke-tested clean against the template. - docs/anti-slop.md — new "Lint stack — Biome only" subsection states the policy: one binary, no ESLint, no Prettier, no Rust toolchain. 4-stage table refreshed: stage 1 is now "Lint + format" (not generic "Static validation"); stage 3 explicitly shows --audit-level=high. - workflow steps renamed: `lint (biome)` / `typecheck (tsc)` / `dependency audit (bun audit)` / `test (bun test)` so CI failures identify the tool at a glance. - commands/install-ci.md — add standalone `lint`, `lint:fix`, `typecheck` scripts to package.json (in addition to the chained `audit`). Note that Biome replaces ESLint + Prettier so users migrating from those tools see the trade. Smoke-tested against /tmp/code-et-smoke3: `bun run lint`, `bun run audit` both green; 4/4 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --- code-et-implementer/commands/install-ci.md | 11 ++++++--- code-et-implementer/docs/anti-slop.md | 23 +++++++++++++++++-- .../.github/workflows/code-et-audit.yml | 8 +++---- .../templates/typescript/biome.json | 19 +++++++++++---- 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/code-et-implementer/commands/install-ci.md b/code-et-implementer/commands/install-ci.md index b8ae09e..1c72ddb 100644 --- a/code-et-implementer/commands/install-ci.md +++ b/code-et-implementer/commands/install-ci.md @@ -35,13 +35,16 @@ The audit runs `biome check`, `tsc --noEmit`, `bun audit`, `bun test`. See [`doc Bash('mkdir -p .github/workflows && cp "${CLAUDE_PLUGIN_ROOT}/templates/shared/.github/workflows/code-et-audit.yml" .github/workflows/') ``` -4. **Add an `audit` npm script** if `package.json` doesn't have one. Read `package.json`, add to `scripts`: +4. **Add `lint`, `lint:fix`, `typecheck`, and `audit` scripts** to `package.json` if any are missing. The four scripts are independent enough that users want to run them in isolation while debugging; `audit` chains all four for the merge gate. ```json + "lint": "biome check .", + "lint:fix": "biome check --write .", + "typecheck": "tsc --noEmit", "audit": "biome check . && tsc --noEmit && bun audit --audit-level=high && bun test" ``` - Skip if the project already defines `audit` differently — print a note instead. + If a project already defines any of these differently, leave that entry alone and print a note. Do not overwrite — the user may have wired their own tooling. 5. **Recommend doctrine adoption.** Print: @@ -50,12 +53,14 @@ The audit runs `biome check`, `tsc --noEmit`, `bun audit`, `bun test`. See [`doc See ${CLAUDE_PLUGIN_ROOT}/docs/architecture.md for the vocabulary. ``` -6. **Recommend dev-dep installs** if missing: +6. **Recommend dev-dep installs** if missing. **Biome is the lint stack** — one binary covers lint + format + import-sort. ``` bun add -d @biomejs/biome typescript ``` + If the project currently uses ESLint or Prettier, mention that Biome replaces both and ask before removing them. + ## Output `"Installed CI audit gate. Push the changes; the next PR will run the audit job."` diff --git a/code-et-implementer/docs/anti-slop.md b/code-et-implementer/docs/anti-slop.md index 6d78e24..ff611f0 100644 --- a/code-et-implementer/docs/anti-slop.md +++ b/code-et-implementer/docs/anti-slop.md @@ -52,15 +52,34 @@ When CI is green but something still feels wrong, look for these. The engineerin | **Mirror Tests** | Tests replay the implementation. `expect(add(2, 3)).toBe(2 + 3)`. The test asserts what the implementation will compute, not what callers expect. | Tests assert observable behaviour through the public interface. A test that passes for two different correct implementations is a real test. | | **Inconsistent Styling** | Mixed quote styles, mixed `===` / `==`, naming drift (`userId` here, `user_id` there), comments restating the obvious. | `biome check --apply`. Project-wide naming enforced in review. | +## Lint stack — Biome only + +code-et uses **[Biome](https://biomejs.dev)** for both linting *and* formatting. One binary, one config (`biome.json`), one mental model. There is no ESLint, no Prettier, no separate import sorter, and (this is the v4-to-v5 change) no Rust toolchain anywhere. + +What the bundled `biome.json` turns on beyond Biome's `recommended` set: + +- `style.useImportType` (error) — `import type` for type-only imports; keeps the runtime bundle honest. +- `style.useConst` / `style.useTemplate` (error) — defaults that prevent drift. +- `suspicious.noExplicitAny` (warn), `suspicious.noImplicitAnyLet` (error), `suspicious.noConsole` (warn — allow `error/warn/info`). +- `correctness.noUnusedImports` / `noUnusedVariables` (error), `noUnusedFunctionParameters` (warn) — dead-code detection at the file scope. +- `complexity.noUselessConstructor` / `noUselessLoneBlockStatements` / `noUselessTypeConstraint` (error) — ratchet against shallow-extraction slop. + +Local commands: + +- `bun run lint` — `biome check .` (read-only, fails on findings) +- `bun run lint:fix` — `biome check --write .` (safe auto-fixes applied in place) + +The lint stage is the **first** step of the CI audit; if Biome is unhappy, the rest of the pipeline doesn't run. + ## The 4-stage verification loop — what CI runs The `.github/workflows/code-et-audit.yml` shipped by `/code:start` and `/code:install-ci` runs these stages on every PR. The same pipeline runs locally via `bun run audit` and as the tail step of `/code:ship`. | Stage | What | Tool | |---|---|---| -| 1 — Static validation | Format, lint, dead exports | `biome check .` | +| 1 — Lint + format | Style, dead exports, complexity ratchets | `biome check .` | | 2 — Type safety | TypeScript checks across the workspace | `tsc --noEmit` | -| 3 — Dependency audit | Vulnerable + unused deps | `bun audit`. Optional: `npx knip` for unused exports/files. | +| 3 — Dependency audit | Vulnerable deps (high+critical block) | `bun audit --audit-level=high` | | 4 — Tests | Unit + integration | `bun test` | A finding is **CRITICAL** if it falls into one of: diff --git a/code-et-implementer/templates/shared/.github/workflows/code-et-audit.yml b/code-et-implementer/templates/shared/.github/workflows/code-et-audit.yml index fcb2b44..7e74ecf 100644 --- a/code-et-implementer/templates/shared/.github/workflows/code-et-audit.yml +++ b/code-et-implementer/templates/shared/.github/workflows/code-et-audit.yml @@ -35,15 +35,15 @@ jobs: - name: generate drizzle migrations run: bun run db:generate || echo "no schema changes" - - name: lint + - name: lint (biome) run: bunx biome check . - - name: typecheck + - name: typecheck (tsc) run: bunx tsc --noEmit - - name: dependency audit + - name: dependency audit (bun audit) run: bun audit --audit-level=high continue-on-error: false - - name: test + - name: test (bun test) run: bun test diff --git a/code-et-implementer/templates/typescript/biome.json b/code-et-implementer/templates/typescript/biome.json index 3c991ee..56b69ff 100644 --- a/code-et-implementer/templates/typescript/biome.json +++ b/code-et-implementer/templates/typescript/biome.json @@ -15,17 +15,28 @@ "enabled": true, "rules": { "recommended": true, - "style": { "noNonNullAssertion": "warn", "useImportType": "error" }, + "style": { + "noNonNullAssertion": "warn", + "useImportType": "error", + "useConst": "error", + "useTemplate": "error" + }, "suspicious": { "noExplicitAny": "warn", - "noConsole": { "level": "warn", "options": { "allow": ["error", "warn", "info"] } } + "noConsole": { "level": "warn", "options": { "allow": ["error", "warn", "info"] } }, + "noImplicitAnyLet": "error" }, "correctness": { "useExhaustiveDependencies": "warn", "noUnusedImports": "error", - "noUnusedVariables": "error" + "noUnusedVariables": "error", + "noUnusedFunctionParameters": "warn" }, - "complexity": { "noUselessConstructor": "error" } + "complexity": { + "noUselessConstructor": "error", + "noUselessLoneBlockStatements": "error", + "noUselessTypeConstraint": "error" + } } } } From 4cc7228dee137227ec69c0ced46e14f5c3771659 Mon Sep 17 00:00:00 2001 From: Kennet Dahl Kusk <kennet.dahl.kusk@visma.com> Date: Sun, 17 May 2026 22:18:53 +0200 Subject: [PATCH 5/5] README: name Biome as the lint stack; surface lint/typecheck commands The CI gate section listed four pipeline stages but didn't make the lint-stack policy explicit ("Biome only, no ESLint, no Prettier") or mention the standalone `bun run lint` / `lint:fix` / `typecheck` commands. Section retitled to "Lint + audit gate" so the lint policy is visible before the gate's stage list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --- README.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 19258f6..6f7045e 100644 --- a/README.md +++ b/README.md @@ -65,16 +65,25 @@ Doctrine lives in [`code-et-implementer/docs/`](code-et-implementer/docs/): - [`anti-slop.md`](code-et-implementer/docs/anti-slop.md) — 4 elements, 5 categories, 8 hard rules. - [`testing.md`](code-et-implementer/docs/testing.md) — interface-as-test-surface, deep-module test patterns, mirror-test ban. -## CI gate +## Lint + audit gate + +**Biome is the lint stack** — one binary covers lint + format + import-sort. No ESLint, no Prettier, no separate import sorter. The bundled `biome.json` enables Biome's `recommended` set plus targeted rules against shallow-extraction slop (`useConst`, `useTemplate`, `noImplicitAnyLet`, `noUnusedFunctionParameters`, `noUselessLoneBlockStatements`, `noUselessTypeConstraint`). + +Local commands: + +- `bun run lint` — `biome check .` (read-only; exits non-zero on findings) +- `bun run lint:fix` — `biome check --write .` (safe auto-fixes in place) +- `bun run typecheck` — `tsc --noEmit` +- `bun run audit` — runs all four stages below in series `.github/workflows/code-et-audit.yml` runs on every PR + push to main: -1. `biome check .` — lint + format -2. `tsc --noEmit` — type safety -3. `bun audit` — dependency advisories -4. `bun test` — unit + integration + http-seam tests +1. **lint (biome)** — `biome check .` +2. **typecheck (tsc)** — `tsc --noEmit` +3. **dependency audit (bun audit)** — `bun audit --audit-level=high` (high + critical block; dev-only moderate advisories don't) +4. **test (bun test)** — `bun test` -Local mirror: `bun run audit`. `/code:ship` runs the same pipeline with a 1-pass auto-fix retry on CRITICAL/HIGH findings. **A green audit is the merge gate** — no manual override. +`/code:ship` runs the same pipeline with a 1-pass auto-fix retry on CRITICAL/HIGH findings. **A green audit is the merge gate** — no manual override. > **GitHub Actions:** uses `actions/checkout`, `oven-sh/setup-bun`, `actions/cache`. All public; GitHub fetches them automatically. `secrets.GITHUB_TOKEN` is auto-provided.