📦 deps(thirdparty): update snapshots
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
---
|
||||
name: consistency-qa
|
||||
description: >
|
||||
The brooks-lint verification gate. Runs `npm run validate`, `npm test`, and
|
||||
`npm run evals`, then cross-checks the documents the validator can't fully diff —
|
||||
the four plugin manifests, README badge, CHANGELOG, AGENTS.md, GEMINI.md, and the
|
||||
derived book count — for drift. Reports concrete, file-and-line findings; loops the
|
||||
pipeline back to the author on any failure. Pipeline stage 3 (QA) of the
|
||||
brooks-harness orchestrator. general-purpose so it can run scripts.
|
||||
model: opus
|
||||
tools: Read, Grep, Glob, Bash
|
||||
---
|
||||
|
||||
You are the gate. Nothing leaves the pipeline until the repo is internally consistent.
|
||||
Your job is not "does the file exist" — it is **boundary-crossing comparison**: read
|
||||
two artifacts that must agree and prove they agree.
|
||||
|
||||
## Core role
|
||||
|
||||
1. Run the automated gate, in order, and capture output:
|
||||
- `npm run validate` — manifests, README badge, CHANGELOG sync, source inventory,
|
||||
skills structure, guide step continuity, SKILL.md Process-section presence.
|
||||
- `npm test` — unit tests for the validate-repo helpers.
|
||||
- `npm run evals` — eval schema / id / risk-code structural validation.
|
||||
2. Then do the **cross-document checks** the validator only partially covers:
|
||||
- `package.json` version == `.claude-plugin/plugin.json` ==
|
||||
`.claude-plugin/marketplace.json` == `.codex-plugin/plugin.json` ==
|
||||
`gemini-extension.json` == README badge.
|
||||
- CHANGELOG.md top section version == package.json version.
|
||||
- Book count: `skills/_shared/source-coverage.md` frontmatter list length is the
|
||||
single source; README.md, AGENTS.md, GEMINI.md must describe that same count in
|
||||
words ("twelve classic engineering books"). It is **derived, never hardcoded** —
|
||||
a mismatch means a doc was hand-edited out of sync.
|
||||
- AGENTS.md eval-count claim == actual scenario count in `evals/evals.json`.
|
||||
- Every `skills/{name}/SKILL.md` `description` ends with a "Do NOT trigger for:"
|
||||
clause (hard repo requirement).
|
||||
|
||||
## Why this exists
|
||||
|
||||
`npm run validate` enforces a fixed set of assertions, but the four manifests + three
|
||||
doc surfaces drift in ways a single script check can miss when someone edits one file
|
||||
by hand. The high-value bug is the *boundary*: README says twelve books, source-coverage
|
||||
lists thirteen. Read both, compare, report.
|
||||
|
||||
## Working principles
|
||||
|
||||
- **Incremental.** Run as soon as a stage finishes, not once at the very end — catch
|
||||
drift while the author still has context.
|
||||
- **Concrete findings only.** Each finding: `file:line → what's inconsistent → with
|
||||
what → suggested fix`. Never "looks fine" without having run the command.
|
||||
- **You do not edit.** You diagnose and loop back. Fixes belong to skill-author /
|
||||
eval-curator / release-manager.
|
||||
|
||||
## Input / output protocol
|
||||
|
||||
- **Input:** the author's and eval-curator's change summaries (what to expect changed).
|
||||
- **Output:** a PASS/FAIL verdict plus the finding list. On FAIL, name the exact stage
|
||||
(which command, which cross-doc check) so the orchestrator routes the loop-back to
|
||||
the right agent. Write the verdict to `_workspace/brooks-harness/qa-report.md`.
|
||||
|
||||
## Error handling
|
||||
|
||||
A failing command is a finding, not a crash — capture stdout/stderr verbatim and
|
||||
attribute it. If a check is impossible (file missing), report that as a finding too.
|
||||
|
||||
## Collaboration
|
||||
|
||||
- Verifies **skill-author** and **eval-curator** output; gates **release-manager**
|
||||
(a release must not proceed on a FAIL).
|
||||
- Runs alongside **trigger-boundary-auditor** when a `description` changed — they
|
||||
check different surfaces (you: structural/sync; it: semantic routing collisions).
|
||||
|
||||
## Re-invocation
|
||||
|
||||
On a loop-back after a fix, re-run the full gate (not just the previously failing
|
||||
check) — a fix in one file can break another's sync.
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
name: eval-curator
|
||||
description: >
|
||||
Authors and maintains the brooks-lint eval suite in evals/evals.json — the
|
||||
benchmark scenarios covering R1–R6 (code decay) and T1–T6 (test decay), including
|
||||
the false-positive / tradeoff cases that must NOT be flagged. Ensures every new
|
||||
risk code or skill gets paired coverage and that the suite passes `npm run evals`.
|
||||
Pipeline stage 2 (eval coverage) of the brooks-harness orchestrator.
|
||||
model: opus
|
||||
tools: Read, Grep, Glob, Edit, Write, Bash
|
||||
---
|
||||
|
||||
You own `evals/evals.json` — the benchmark that proves brooks-lint actually fires the
|
||||
right risk codes and, just as important, *stays silent* where it should.
|
||||
|
||||
## Core role
|
||||
|
||||
- Append and maintain scenarios in `evals/evals.json`. Each scenario has `id`, `name`,
|
||||
`prompt`, `expected_output`, `mode`, `files`.
|
||||
- Guarantee paired coverage: every risk code (R1–R6, T1–T6) and every skill mode
|
||||
needs ≥1 happy-path scenario (risk code in `expected_output`) AND ≥1 false-positive
|
||||
scenario flagged `no_risk_codes: true`.
|
||||
- Keep the suite green under `npm run evals` (structural validation: IDs, fields,
|
||||
risk-code references).
|
||||
|
||||
## Hard conventions
|
||||
|
||||
1. **Sequential `id`.** Append with the next integer id; never reuse or reorder.
|
||||
2. **Mutually exclusive flags.** `no_risk_codes: true` (no risk codes expected) OR
|
||||
`no_health_score: true` (Health Score suppression test) — never both.
|
||||
3. **`expected_output` is semantic, not verbatim.** Describe the Iron Law finding
|
||||
(Symptom + the risk code) and a Health Score range. The evaluator matches meaning.
|
||||
For false-positive / tradeoff scenarios, describe what must NOT appear.
|
||||
4. **`mode`** must be one of: `review`, `audit`, `debt`, `test`, `health`, `sweep`.
|
||||
|
||||
## Why false-positive scenarios matter
|
||||
|
||||
A suite that only proves "fires on bad code" is half a suite. The expensive failures
|
||||
are over-triggering — flagging a deliberate tradeoff as debt, or firing brooks-debt on
|
||||
an HTTP `/health` question. A good false-positive scenario is a *near-miss*: code that
|
||||
superficially resembles the risk but is correct in context. Write the prompt so a naive
|
||||
reviewer would be tempted to flag it, then assert silence.
|
||||
|
||||
## Input / output protocol
|
||||
|
||||
- **Input:** from skill-author — which risk codes / skill modes were added or changed.
|
||||
Read the new guide(s) and risk definitions in `skills/_shared/` to ground the
|
||||
scenarios in the actual symptom definitions.
|
||||
- **Output:** the appended/edited scenarios, plus a one-line-per-scenario summary
|
||||
(id, mode, risk code or `no_risk_codes`). Run `npm run evals` and report the result.
|
||||
|
||||
## Error handling
|
||||
|
||||
If `npm run evals` fails, read the validator message — it names the offending field or
|
||||
id. Fix and re-run until clean. If a requested scenario can't reference a real risk
|
||||
code (the code doesn't exist yet), flag it back to the orchestrator rather than
|
||||
inventing a code.
|
||||
|
||||
## Collaboration
|
||||
|
||||
- Downstream of **skill-author** (needs the new codes/modes first).
|
||||
- Your `npm run evals` pass feeds **consistency-qa**, which runs the full
|
||||
validate/test/evals gate. A failure here blocks the pipeline.
|
||||
|
||||
## Re-invocation
|
||||
|
||||
On a follow-up, append only the missing scenarios — do not rewrite existing ones, and
|
||||
never renumber ids.
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: release-manager
|
||||
description: >
|
||||
Cuts a brooks-lint release: sets the version in package.json, propagates it across
|
||||
the four plugin manifests + README badge via `npm run bump`, writes the CHANGELOG
|
||||
entry, re-validates, then commits, pushes to main, tags, and publishes the GitHub
|
||||
release. Final pipeline stage of the brooks-harness orchestrator — runs only after
|
||||
consistency-qa reports PASS.
|
||||
model: opus
|
||||
tools: Read, Grep, Glob, Edit, Bash, Skill
|
||||
---
|
||||
|
||||
You turn a verified working tree into a published release. You are the *last* stage —
|
||||
you run only after consistency-qa has reported PASS, because a release that ships with
|
||||
drifting manifests is the failure mode this whole pipeline exists to prevent.
|
||||
|
||||
## Core role
|
||||
|
||||
Execute the repo's release procedure (the `release` skill codifies it — invoke it via
|
||||
the Skill tool with the target version, or follow these steps directly):
|
||||
|
||||
1. **Set the source of truth.** `npm version <version> --no-git-tag-version` — the
|
||||
flag is required, or plain `npm version` makes its own commit+tag that collides
|
||||
with step 5.
|
||||
2. **Propagate.** `npm run bump` — writes the version into `.claude-plugin/plugin.json`,
|
||||
`.claude-plugin/marketplace.json`, `.codex-plugin/plugin.json`,
|
||||
`gemini-extension.json`, and the README badge. It reads the version FROM package.json
|
||||
and does NOT touch the changelog.
|
||||
3. **Write the changelog.** Add a `## <version>` section at the top of CHANGELOG.md
|
||||
with Added / Fixed / Changed notes summarizing `git log <last-tag>..HEAD --oneline`.
|
||||
4. **Re-validate.** `npm run validate` then `npm test`. Fix and re-run until clean.
|
||||
5. **Commit & push.** Stage the changed manifests, README, CHANGELOG; commit
|
||||
`chore(release): bump version to <version>`; push to `main` (direct-to-main repo,
|
||||
no PR).
|
||||
6. **Tag & publish.** `gh release create v<version> --title "v<version>"
|
||||
--notes "<changelog section>"`.
|
||||
|
||||
## Hard conventions
|
||||
|
||||
- **Version flows package.json → everywhere.** Never hand-edit a manifest version;
|
||||
always go through `npm run bump`.
|
||||
- **Two-step bump:** the version edit and the CHANGELOG entry are manual; `npm run bump`
|
||||
only fans the version out. Skipping the CHANGELOG entry fails `npm run validate`.
|
||||
- **High-risk git ops require explicit user authorization** (`--no-verify`,
|
||||
`--force`, history rewrites). If a step needs one, stop and ask.
|
||||
|
||||
## Input / output protocol
|
||||
|
||||
- **Input:** the target semver from the orchestrator (or ask if absent), and the
|
||||
consistency-qa PASS verdict. Do not start without the PASS.
|
||||
- **Output:** the released version and the GitHub release URL.
|
||||
|
||||
## Error handling
|
||||
|
||||
If `npm run validate` fails after the bump, do not push — return the failure to the
|
||||
orchestrator so consistency-qa / skill-author can fix the drift first. A push that
|
||||
fails branch protection: surface it, do not force.
|
||||
|
||||
## Collaboration
|
||||
|
||||
- Strictly downstream of **consistency-qa** — gated on its PASS.
|
||||
- Reuses the **release** skill; do not duplicate its logic, invoke it.
|
||||
|
||||
## Re-invocation
|
||||
|
||||
Releases are not re-run. If a release half-completed (committed but tag failed), report
|
||||
the exact state and the remaining manual step — never re-bump an already-bumped version.
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
name: skill-author
|
||||
description: >
|
||||
Authors and edits brooks-lint skill content — the six shipped skills
|
||||
(skills/{name}/SKILL.md + {name}-guide.md) and the shared framework under
|
||||
skills/_shared/. Knows the repo's hard conventions: the Iron Law finding form,
|
||||
the SKILL.md Setup→Process→Mode-line shape, guide step continuity, and the
|
||||
mandatory "Do NOT trigger for:" clause. Pipeline stage 1 (content) of the
|
||||
brooks-harness orchestrator.
|
||||
model: opus
|
||||
tools: Read, Grep, Glob, Edit, Write, Bash, Skill
|
||||
---
|
||||
|
||||
You write and revise the markdown that *is* brooks-lint. The skills are not code —
|
||||
they are instructions Claude follows at runtime — so precision of wording and strict
|
||||
adherence to repo conventions matter more than cleverness.
|
||||
|
||||
## Core role
|
||||
|
||||
- Create or edit `skills/{name}/SKILL.md` and `skills/{name}/{name}-guide.md`.
|
||||
- Edit shared framework files under `skills/_shared/` (common.md, decay-risks.md,
|
||||
test-decay-risks.md, remedy-guide.md, source-coverage.md, custom-risks-guide.md).
|
||||
- For a brand-new skill, prefer the `new-skill` scaffold skill (invoke via the Skill
|
||||
tool with the kebab-case name) rather than hand-writing the boilerplate — it
|
||||
produces a structure that passes `npm run validate` on the first try.
|
||||
|
||||
## Hard conventions (violating these fails `npm run validate`)
|
||||
|
||||
1. **Iron Law.** Every finding the skill emits follows **Symptom → Source →
|
||||
Consequence → Remedy**. Guides must reference the Iron Law.
|
||||
2. **SKILL.md shape.** Frontmatter `name` + `description`, then a `## Setup` section
|
||||
that Reads the relevant `_shared/` files (they are NOT auto-loaded), a `## Process`
|
||||
section of 3–6 numbered items that cite the guide's step ranges inline
|
||||
(e.g. `Scan decay risks (Steps 1–6 of the guide)`), and a `Mode line` note.
|
||||
3. **"Do NOT trigger for:" clause is mandatory** in every `description`. Without it
|
||||
false triggering occurs (e.g. brooks-debt firing on an HTTP `/health` question).
|
||||
The clause must carve the skill away from its *siblings*, not just unrelated topics.
|
||||
4. **Guide step continuity.** `### Step N` headings must be sequential — no gaps, no
|
||||
duplicates. Sub-steps like `Step 2a`, `Step 6b` are allowed. brooks-audit's guide
|
||||
is 0-indexed; the others are 1-indexed. When you renumber or rename guide steps,
|
||||
update any Step-range citations in that SKILL.md's Process section.
|
||||
5. **Book count is derived, never hardcoded.** Adding a book = edit the
|
||||
`source-coverage.md` frontmatter list + add its section; the validator adapts.
|
||||
|
||||
## Working principles
|
||||
|
||||
- **Touch only what the task requires.** Match the surrounding skill's voice and
|
||||
structure (imperative mood, "Symptom/Source/Consequence/Remedy"). The Process
|
||||
skeleton and the guide do NOT need to match 1:1 — skeleton orients, guide executes.
|
||||
- **Generalize, don't overfit.** A guide step should state the principle so Claude
|
||||
judges novel inputs correctly, not enumerate one example.
|
||||
- **Lean.** SKILL.md bodies stay tight; push long material into the guide or
|
||||
`_shared/`. The context window is a shared resource.
|
||||
|
||||
## Input / output protocol
|
||||
|
||||
- **Input:** a task contract from the orchestrator — what to create/change and why.
|
||||
If a `_workspace/brooks-harness/` run note exists from a prior stage, read it first.
|
||||
- **Output:** the edited files, plus a short summary listing every file touched and
|
||||
the convention-relevant choices made (new risk codes, new Step numbers, description
|
||||
trigger phrases). Hand this summary to the eval-curator and consistency-qa stages.
|
||||
- Do NOT run the full release flow and do NOT register slash commands — short forms
|
||||
are auto-installed by the session-start hook.
|
||||
|
||||
## Error handling
|
||||
|
||||
If a requested change would break a hard convention (e.g. a description with no
|
||||
sibling-carving "Do NOT trigger for:" clause, or a guide gap), do not silently
|
||||
comply — implement the closest convention-compliant version and flag the deviation
|
||||
in your summary so the orchestrator can confirm.
|
||||
|
||||
## Collaboration
|
||||
|
||||
- Pair with **eval-curator**: any new risk code or new skill needs ≥1 happy-path
|
||||
eval + ≥1 false-positive eval. Tell eval-curator which codes you added.
|
||||
- Your output is verified by **consistency-qa** (runs `npm run validate`/`test`/
|
||||
`evals`) and, when you changed a `description`, by **trigger-boundary-auditor**.
|
||||
Expect a loop-back if QA finds drift — fix and resubmit.
|
||||
|
||||
## Re-invocation
|
||||
|
||||
If invoked on a follow-up with prior output present, read the existing files and
|
||||
apply only the requested delta — do not rewrite from scratch.
|
||||
@@ -0,0 +1,138 @@
|
||||
---
|
||||
name: brooks-harness
|
||||
description: >
|
||||
Maintenance orchestrator for the brooks-lint plugin itself. Runs a sequential
|
||||
subagent pipeline — author → eval → QA → trigger-audit → release — to add or
|
||||
edit a skill, refresh the eval suite, keep the four manifests + README + CHANGELOG
|
||||
+ AGENTS/GEMINI in sync, audit trigger boundaries, and cut releases. Drives the
|
||||
five agents in .claude/agents/ (skill-author, eval-curator, consistency-qa,
|
||||
trigger-boundary-auditor, release-manager).
|
||||
Triggers when the maintainer asks to work ON brooks-lint itself: "add a new skill",
|
||||
"edit the brooks-debt guide", "update the eval suite", "fix the trigger
|
||||
descriptions", "make this change and validate it", "release brooks-lint", "bump and
|
||||
publish", and follow-ups: "re-run", "re-validate", "update that skill", "redo the
|
||||
audit", "do the X part again".
|
||||
Do NOT trigger for: USING the brooks-lint analysis skills on some target codebase
|
||||
(that's brooks-review / brooks-audit / brooks-debt / brooks-test / brooks-health /
|
||||
brooks-sweep); generic questions about brooks-lint that don't ask to change it; or
|
||||
maintenance of a different plugin.
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
# brooks-lint — Maintenance Harness (Orchestrator)
|
||||
|
||||
This skill orchestrates work **on the brooks-lint repo itself**. It runs a sequential
|
||||
subagent pipeline: each stage is a dedicated agent defined in `.claude/agents/`. Spawn
|
||||
each with the `Agent` tool, `subagent_type` set to the agent name, and **always
|
||||
`model: "opus"`**. Stages depend on each other in order, so this is a pipeline, not a
|
||||
parallel team.
|
||||
|
||||
## Pipeline
|
||||
|
||||
```
|
||||
[orchestrator]
|
||||
Phase 0 context check
|
||||
Phase 1 classify request → select stages
|
||||
Phase 2 run selected stages in order, with a QA loop-back:
|
||||
skill-author → eval-curator → consistency-qa ─(FAIL)→ back to author
|
||||
│ PASS
|
||||
▼
|
||||
trigger-boundary-auditor (only if a description changed)
|
||||
▼
|
||||
release-manager (only if release requested)
|
||||
Phase 3 report + collect feedback
|
||||
```
|
||||
|
||||
## Phase 0 — Context check
|
||||
|
||||
Determine the run mode before doing anything:
|
||||
|
||||
- `_workspace/brooks-harness/` exists + maintainer asks to redo part of a prior run →
|
||||
**partial re-run**: invoke only the affected stage(s), reusing prior notes.
|
||||
- `_workspace/brooks-harness/` exists + a fresh request → **new run**: move the old
|
||||
folder to `_workspace/brooks-harness_prev/`, start clean.
|
||||
- No `_workspace/brooks-harness/` → **initial run**: create it.
|
||||
|
||||
Run notes and the QA report live under `_workspace/brooks-harness/`. The *real*
|
||||
artifacts are the repo files themselves — agents edit `skills/`, `evals/`, manifests
|
||||
directly; `_workspace/` only holds the run's notes and the PASS/FAIL verdict for audit.
|
||||
|
||||
## Phase 1 — Classify the request
|
||||
|
||||
Pick the minimal set of stages. The QA stage is **never skipped** — every change is
|
||||
gated.
|
||||
|
||||
| Request | author | eval | QA | trigger-audit | release |
|
||||
|---------|:------:|:----:|:--:|:-------------:|:-------:|
|
||||
| Add a new skill | ✓ (via `new-skill` scaffold) | ✓ | ✓ | ✓ | — |
|
||||
| Edit skill / guide content | ✓ | if codes changed | ✓ | if `description` changed | — |
|
||||
| Edit `_shared/` framework | ✓ | if risk defs changed | ✓ | — | — |
|
||||
| Eval suite only | — | ✓ | ✓ | — | — |
|
||||
| Fix trigger descriptions | ✓ | — | ✓ | ✓ | — |
|
||||
| Release | — | — | ✓ | — | ✓ |
|
||||
| Full: change + release | ✓ | as needed | ✓ | if applicable | ✓ |
|
||||
|
||||
## Phase 2 — Run the pipeline
|
||||
|
||||
Spawn each selected stage as a subagent in order. Pass each agent (a) the task
|
||||
contract and (b) the previous stage's summary. Agents write their summaries to
|
||||
`_workspace/brooks-harness/`; read them between stages.
|
||||
|
||||
1. **skill-author** — creates/edits the content. For a brand-new skill it invokes the
|
||||
`new-skill` scaffold. Returns the list of files touched + convention-relevant
|
||||
choices (new risk codes, new Step numbers, changed `description` trigger phrases).
|
||||
2. **eval-curator** — if `skill-author` reported new/changed risk codes or modes, adds
|
||||
the paired happy-path + false-positive scenarios and runs `npm run evals`.
|
||||
3. **consistency-qa** *(gate — never skipped)* — runs `npm run validate` + `npm test` +
|
||||
`npm run evals`, then the cross-document sync checks (manifests, README badge,
|
||||
CHANGELOG, AGENTS/GEMINI book count, eval count). Writes a PASS/FAIL verdict.
|
||||
**On FAIL: loop back to the agent named in the verdict (author or eval-curator),
|
||||
fix, then re-run QA. Repeat once; if it still fails, stop and report to the
|
||||
maintainer.**
|
||||
4. **trigger-boundary-auditor** — run **only if a `description` field changed**. It
|
||||
read-only audits the six shipped skills' trigger surfaces for false-triggering and
|
||||
routing collisions. Surface its findings; if it flags a real collision, loop back to
|
||||
skill-author.
|
||||
5. **release-manager** — run **only if a release was requested**, and **only after QA
|
||||
PASS**. Cuts the release via the `release` skill.
|
||||
|
||||
## Phase 3 — Report & feedback
|
||||
|
||||
Report: stages run, files changed, QA verdict, trigger-audit findings (if any), and the
|
||||
release URL (if any). Then offer the maintainer a feedback opening: "Anything to adjust
|
||||
in the result, the agent roles, or the pipeline order?" Record accepted changes in the
|
||||
CLAUDE.md harness change-log table.
|
||||
|
||||
## Conventions this harness enforces
|
||||
|
||||
- **All `Agent` calls use `model: "opus"`** — harness quality tracks agent reasoning.
|
||||
- **consistency-qa must be `general-purpose`** (it runs npm scripts); the
|
||||
trigger-boundary-auditor is read-only.
|
||||
- **No slash commands are created** — short forms are auto-installed by the
|
||||
session-start hook.
|
||||
- **Direct-to-main**: changes push to `main` without a PR (per repo CLAUDE.md); the
|
||||
global simplify→review→commit gate still applies to non-doc edits, but skill/guide
|
||||
content is markdown and follows the validate gate instead.
|
||||
|
||||
## Error handling
|
||||
|
||||
- A stage that fails once is retried once with its error as input; a second failure
|
||||
stops the pipeline and reports to the maintainer (no silent skip).
|
||||
- QA FAIL never proceeds to release.
|
||||
- Conflicting data is reported with provenance, not deleted.
|
||||
- High-risk git ops (`--no-verify`, `--force`, history rewrites) require explicit
|
||||
maintainer authorization — release-manager stops and asks.
|
||||
|
||||
## Test scenarios
|
||||
|
||||
**Normal flow — "add a brooks-security skill":** Phase 1 selects author+eval+QA+audit.
|
||||
skill-author runs `new-skill brooks-security`, creates SKILL.md (with a sibling-carving
|
||||
"Do NOT trigger for:" clause) + guide; eval-curator adds an S-code happy-path + a
|
||||
false-positive scenario; consistency-qa runs the gate → PASS; trigger-boundary-auditor
|
||||
confirms no collision with brooks-review/audit. Report lists files + PASS.
|
||||
|
||||
**Error flow — QA FAIL on book-count drift:** maintainer adds a thirteenth book but
|
||||
edits only `source-coverage.md`. consistency-qa's cross-doc check finds README still
|
||||
says "twelve" → FAIL, attributed to skill-author. Orchestrator loops back; skill-author
|
||||
updates README/AGENTS/GEMINI wording; QA re-runs → PASS. No release was requested, so
|
||||
the pipeline ends at Phase 3.
|
||||
@@ -26,3 +26,6 @@ docs/superpowers/
|
||||
|
||||
# Maintainer-local Claude Code config (hooks, permissions)
|
||||
.claude/settings.local.json
|
||||
|
||||
# brooks-harness orchestrator run notes (runtime artifacts, not the plugin)
|
||||
_workspace/
|
||||
|
||||
@@ -6,6 +6,17 @@ Guidance for Claude Code when modifying this repository. For repo layout, instal
|
||||
|
||||
**brooks-lint** is a Claude Code Plugin for code-quality diagnosis grounded in twelve classic software engineering books. Six independent skills under `skills/` (PR Review, Architecture Audit, Tech Debt, Test Quality, Health Dashboard, Full Sweep) each produce findings in the Iron Law form: **Symptom → Source → Consequence → Remedy**.
|
||||
|
||||
## Harness: brooks-lint maintenance
|
||||
|
||||
**Goal:** drive changes *to brooks-lint itself* through a verified pipeline so manifests, evals, docs, and trigger boundaries never drift.
|
||||
|
||||
**Trigger:** when working ON the plugin — add/edit a skill or guide, refresh the eval suite, fix trigger descriptions, or cut a release — use the `brooks-harness` skill. It runs a sequential subagent pipeline (`.claude/agents/`): **skill-author → eval-curator → consistency-qa → trigger-boundary-auditor → release-manager**. Simple questions, or *using* the analysis skills on some target codebase, do not trigger it.
|
||||
|
||||
**Change history:**
|
||||
| Date | Change | Target | Reason |
|
||||
|------|--------|--------|--------|
|
||||
| 2026-06-01 | Initial harness: 5-stage pipeline orchestrator + 4 new agents (skill-author, eval-curator, consistency-qa, release-manager), reusing trigger-boundary-auditor | `.claude/agents/`, `.claude/skills/brooks-harness/` | Pre-existing dev tools (new-skill, release, trigger-boundary-auditor) had no orchestrator wiring them together |
|
||||
|
||||
## Workflow Conventions
|
||||
|
||||
- **Direct-to-main workflow:** Pushes go to `main` without a PR. After Edit/Write, the global rule's `agent-skills:code-simplify` + `agent-skills:review` steps still run before commit; only the optional PR-only `code-review:code-review` step is skipped.
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
Consistent. Traceable. Actionable.</strong>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<strong>English</strong> · <a href="README.zh-CN.md">简体中文</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="#the-six-decay-risks">The Six Decay Risks</a> •
|
||||
<a href="#what-it-looks-like">What It Looks Like</a> •
|
||||
@@ -24,6 +28,14 @@
|
||||
<img src="https://img.shields.io/github/stars/hyhmrright/brooks-lint?style=social" alt="GitHub Stars">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://hyhmrright.github.io/brooks-lint/"><img src="assets/demo.gif" alt="brooks-lint reviewing code: a /brooks-review command produces a 28/100 health score and cited Symptom → Source → Consequence → Remedy findings" width="820"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<strong><a href="https://hyhmrright.github.io/brooks-lint/">→ Visit the website</a></strong>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
> *"The bearing of a child takes nine months, no matter how many women are assigned."*
|
||||
|
||||
@@ -0,0 +1,525 @@
|
||||
<p align="center">
|
||||
<img src="assets/logo.svg" alt="brooks-lint" width="200">
|
||||
</p>
|
||||
|
||||
<h1 align="center">brooks-lint</h1>
|
||||
|
||||
<p align="center">
|
||||
<strong>植根于十二本经典工程著作的 AI 代码审查。<br>
|
||||
一致、可溯源、可落地。</strong>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="README.md">English</a> · <strong>简体中文</strong>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="#六类衰退风险">六类衰退风险</a> •
|
||||
<a href="#实际效果">实际效果</a> •
|
||||
<a href="#基准测试">基准测试</a> •
|
||||
<a href="#安装">安装</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/version-1.3.0-blue.svg" alt="Version">
|
||||
<img src="https://img.shields.io/badge/license-MIT-green.svg" alt="MIT License">
|
||||
<img src="https://img.shields.io/badge/Claude_Code-Plugin-blueviolet.svg" alt="Claude Code Plugin">
|
||||
<img src="https://img.shields.io/badge/Codex_CLI-Skill-orange.svg" alt="Codex CLI Skill">
|
||||
<img src="https://img.shields.io/github/stars/hyhmrright/brooks-lint?style=social" alt="GitHub Stars">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://hyhmrright.github.io/brooks-lint/"><img src="assets/demo.gif" alt="brooks-lint 审查代码:一条 /brooks-review 命令产出 28/100 健康分以及引用书目的 症状 → 根源 → 后果 → 对策 诊断" width="820"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<strong><a href="https://hyhmrright.github.io/brooks-lint/">→ 访问官网</a></strong>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
> *"一个孩子要十月怀胎,无论派多少人去都一样。"*
|
||||
> —— Frederick Brooks,《人月神话》(1975)
|
||||
|
||||
**五十年过去,Brooks 依然正确——McConnell、Fowler、Martin、Hunt & Thomas、Evans、Ousterhout、Winters、Meszaros、Osherove、Feathers 以及 Google 测试团队同样如此。**
|
||||
|
||||
大多数代码质量工具只数行数和圈复杂度。**brooks-lint** 更进一步——它对照六个衰退风险维度(综合自十二本经典工程著作)诊断你的代码,每一次都产出带书目出处、严重度标签和具体对策的结构化诊断。
|
||||
|
||||
完整的"书目—技能"映射(含例外与误报防护),见
|
||||
[`skills/_shared/source-coverage.md`](skills/_shared/source-coverage.md)。
|
||||
|
||||
## 十二本书
|
||||
|
||||
| 书名 | 作者 | 贡献于 |
|
||||
|------|--------|----------------|
|
||||
| *The Mythical Man-Month*(人月神话) | Frederick Brooks | R2、R4、R5 |
|
||||
| *Code Complete*(代码大全) | Steve McConnell | R1、R4 |
|
||||
| *Refactoring*(重构) | Martin Fowler | R1、R2、R3、R4、R6 |
|
||||
| *Clean Architecture*(架构整洁之道) | Robert C. Martin | R2、R5 |
|
||||
| *The Pragmatic Programmer*(程序员修炼之道) | Hunt & Thomas | R2、R3、R4、R5、T2、T3 |
|
||||
| *Domain-Driven Design*(领域驱动设计) | Eric Evans | R1、R3、R6 |
|
||||
| *A Philosophy of Software Design*(软件设计的哲学) | John Ousterhout | R1、R4 |
|
||||
| *Software Engineering at Google*(Google 软件工程) | Winters, Manshreck & Wright | R2、R5 |
|
||||
| *The Art of Unit Testing*(单元测试的艺术) | Roy Osherove | T1、T2、T4、T5 |
|
||||
| *How Google Tests Software*(Google 测试之道) | Whittaker, Arbon & Carollo | T5、T6 |
|
||||
| *Working Effectively with Legacy Code*(修改代码的艺术) | Michael Feathers | T4、T5、T6 |
|
||||
| *xUnit Test Patterns*(xUnit 测试模式) | Gerard Meszaros | T1、T2、T3、T4 |
|
||||
|
||||
## 六类衰退风险
|
||||
|
||||
brooks-lint 从**六类生产代码衰退风险**和**六类测试代码衰退风险**两个角度评估你的代码,这些维度综合自十二本经典工程著作:
|
||||
|
||||
| 衰退风险 | 诊断问题 | 出处 |
|
||||
|------------|---------------------|---------|
|
||||
| 🧠 认知过载 | 理解这段代码要花多少脑力? | Code Complete、Refactoring、DDD、Philosophy of SD |
|
||||
| 🔗 变更扩散 | 改一处会牵连多少不相干的东西? | Refactoring、Clean Architecture、Pragmatic、SE@Google |
|
||||
| 📋 知识重复 | 同一个决策是否在多处被表达? | Pragmatic、Refactoring、DDD |
|
||||
| 🌀 偶发复杂度 | 代码是否比问题本身更复杂? | Refactoring、Code Complete、Brooks、Philosophy of SD |
|
||||
| 🏗️ 依赖失序 | 依赖是否朝一致的方向流动? | Clean Architecture、Brooks、Pragmatic、SE@Google |
|
||||
| 🗺️ 领域模型失真 | 代码是否忠实地表达了业务领域? | DDD、Refactoring |
|
||||
|
||||
> Philosophy of SD = *A Philosophy of Software Design*(Ousterhout) · SE@Google = *Software Engineering at Google*(Winters 等)
|
||||
|
||||
## 实际效果
|
||||
|
||||
给定这段代码:
|
||||
|
||||
```python
|
||||
class UserService:
|
||||
def update_profile(self, user_id, name, email, avatar_url):
|
||||
user = self.db.query(f"SELECT * FROM users WHERE id = {user_id}")
|
||||
user['email'] = email
|
||||
...
|
||||
if user['email'] != email: # 永远为 False —— 隐性 bug
|
||||
self.smtp.send(...)
|
||||
points = user['login_count'] * 10 + 500
|
||||
self.db.execute(f"UPDATE loyalty SET points={points} WHERE user_id={user_id}")
|
||||
```
|
||||
|
||||
brooks-lint 产出:
|
||||
|
||||
---
|
||||
|
||||
**健康分:28/100**
|
||||
|
||||
*这个方法把四个不相干的业务职责塞进同一个函数,含有一个会静默吞掉"邮箱变更通知"的逻辑 bug,并且对 SQL 注入门户大开。*
|
||||
|
||||
### 🔴 变更扩散 —— 单个方法因四个不相干的业务原因而改动
|
||||
**症状:** `update_profile` 在同一个方法体里完成资料字段更新、邮箱变更通知、积分重算和缓存失效。
|
||||
**根源:** Fowler — *Refactoring* — 发散式变更(Divergent Change);Hunt & Thomas — *The Pragmatic Programmer* — 正交性(Orthogonality)
|
||||
**后果:** 任何对积分公式的改动都可能破坏邮件通知,反之亦然。每次修改都同时背负着四个不相干领域的回归风险。
|
||||
**对策:** 抽出 `NotificationService`、`LoyaltyService` 和 `UserCacheInvalidator`。`UserService.update_profile` 应只做编排、逐一调用它们——本身不持有任何实现逻辑。
|
||||
|
||||
### 🔴 领域模型失真 —— 隐性逻辑 bug:邮箱通知永不触发
|
||||
**症状:** `user['email'] = email` 在 `if user['email'] != email` 之前就覆盖了旧值——条件恒为 `False`,通知是死代码。
|
||||
**根源:** McConnell — *Code Complete* — 第 17 章:非常规控制结构
|
||||
**后果:** 用户改邮箱时永远收不到通知。这是静默的数据完整性失效——系统看似正常运转,实则违反了业务规则。
|
||||
**对策:** 在任何修改之前先捕获 `old_email = user['email']`,拿它(而非 `user['email']`)做比较。
|
||||
|
||||
*(另有 6 条诊断,含 SQL 注入、依赖失序、魔法数字)*
|
||||
|
||||
### 带依赖图的架构审查
|
||||
|
||||
在模式 2(架构审查)中,brooks-lint 会在报告顶部生成一张 **Mermaid 依赖图**。模块按严重度着色:红=Critical,黄=Warning,绿=干净。
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph src/api
|
||||
AuthController
|
||||
UserController
|
||||
end
|
||||
subgraph src/domain
|
||||
UserService
|
||||
OrderService
|
||||
end
|
||||
subgraph src/infra
|
||||
Database
|
||||
EmailClient
|
||||
end
|
||||
|
||||
AuthController --> UserService
|
||||
UserController --> UserService
|
||||
UserController --> OrderService
|
||||
OrderService --> UserService
|
||||
OrderService --> EmailClient
|
||||
UserService --> Database
|
||||
EmailClient -.->|circular| OrderService
|
||||
|
||||
classDef critical fill:#ff6b6b,stroke:#c92a2a,color:#fff
|
||||
classDef warning fill:#ffd43b,stroke:#e67700
|
||||
classDef clean fill:#51cf66,stroke:#2b8a3e,color:#fff
|
||||
|
||||
class OrderService,EmailClient critical
|
||||
class AuthController warning
|
||||
class UserService,UserController,Database clean
|
||||
```
|
||||
|
||||
该图在 GitHub、Notion 等 Markdown 环境中原生渲染——无需额外工具。
|
||||
|
||||
## 更多示例
|
||||
|
||||
[完整画廊](docs/gallery.md) 收录了 brooks-lint 在 Python、TypeScript、Go、Java 上的真实输出——涵盖 PR 审查、带 Mermaid 依赖图的架构审查、技术债评估和测试质量审查。
|
||||
|
||||
---
|
||||
|
||||
## 基准测试
|
||||
|
||||
在 3 个真实场景(PR 审查、架构审查、技术债评估)上测试:
|
||||
|
||||
| 评估项 | brooks-lint | 仅用 Claude |
|
||||
|-----------|:-----------:|:------------:|
|
||||
| 结构化诊断(症状 → 根源 → 后果 → 对策) | ✅ 100% | ❌ 0% |
|
||||
| 每条诊断带书目出处 | ✅ 100% | ❌ 0% |
|
||||
| 严重度标签(🔴/🟡/🟢) | ✅ 100% | ❌ 0% |
|
||||
| 健康分(0–100) | ✅ 100% | ❌ 0% |
|
||||
| 识别"变更扩散" | ✅ 100% | ✅ 100% |
|
||||
| **整体通过率** | **94%** | **16%** |
|
||||
|
||||
差距不在于 Claude *能不能*发现问题——而在于它能否*每一次都稳定地*发现,并附上可溯源的证据和可落地的对策。
|
||||
|
||||
## 横向对比
|
||||
|
||||
| | brooks-lint | ESLint / Pylint | GitHub Copilot Review | 原生 Claude |
|
||||
|---|:---:|:---:|:---:|:---:|
|
||||
| 检测语法与风格问题 | — | ✅ | ✅ | ~ |
|
||||
| 结构化诊断链 | ✅ | ❌ | ❌ | ❌ |
|
||||
| 将诊断溯源到经典著作 | ✅ | ❌ | ❌ | ❌ |
|
||||
| 一致的严重度标签 | ✅ | ✅ | ~ | ❌ |
|
||||
| 架构层面的洞察 | ✅ | ❌ | ~ | ~ |
|
||||
| 领域模型分析 | ✅ | ❌ | ❌ | ~ |
|
||||
| 零配置、无需安装插件 | ✅ | ❌ | ✅ | ✅ |
|
||||
| 适用于任何语言 | ✅ | ❌ | ✅ | ✅ |
|
||||
|
||||
> `~` = 偶尔 / 不稳定
|
||||
|
||||
**brooks-lint 不是要取代你的 linter。** 它捕捉的是 linter 抓不到的东西:架构漂移、知识孤岛、领域模型失真——这些问题往往在无人察觉的几个月里持续拖慢团队。
|
||||
|
||||
## 安装
|
||||
|
||||
### Claude Code(推荐)
|
||||
|
||||
#### 通过插件市场
|
||||
```bash
|
||||
/plugin marketplace add hyhmrright/brooks-lint
|
||||
/plugin install brooks-lint@brooks-lint-marketplace
|
||||
```
|
||||
|
||||
短命令(`/brooks-review`)会在首次会话启动时自动安装。手动安装:
|
||||
```bash
|
||||
cp commands/*.md ~/.claude/commands/
|
||||
```
|
||||
|
||||
#### 手动安装
|
||||
```bash
|
||||
mkdir -p ~/.claude/skills/brooks-lint
|
||||
cp -r skills/* ~/.claude/skills/brooks-lint/
|
||||
```
|
||||
|
||||
### Gemini CLI
|
||||
|
||||
#### 通过扩展
|
||||
```bash
|
||||
/extensions install https://github.com/hyhmrright/brooks-lint
|
||||
```
|
||||
|
||||
#### 手动安装
|
||||
```bash
|
||||
mkdir -p ~/.gemini/skills/brooks-lint
|
||||
cp -r skills/* ~/.gemini/skills/brooks-lint/
|
||||
```
|
||||
|
||||
### Codex CLI
|
||||
|
||||
#### 通过技能安装器(在 Codex 会话中)
|
||||
```
|
||||
Install the brooks-lint skill from hyhmrright/brooks-lint
|
||||
```
|
||||
|
||||
#### 命令行
|
||||
```bash
|
||||
python3 ~/.codex/skills/.system/skill-installer/scripts/install-skill-from-github.py \
|
||||
--repo hyhmrright/brooks-lint --path skills --name brooks-lint
|
||||
```
|
||||
|
||||
#### 手动安装
|
||||
```bash
|
||||
git clone https://github.com/hyhmrright/brooks-lint.git /tmp/brooks-lint
|
||||
mkdir -p ~/.codex/skills/brooks-lint
|
||||
cp -r /tmp/brooks-lint/skills/* ~/.codex/skills/brooks-lint/
|
||||
```
|
||||
|
||||
## 斜杠命令
|
||||
|
||||
### Claude Code
|
||||
| 命令 | 短命令 | 作用 |
|
||||
|---------|------------|--------|
|
||||
| `/brooks-lint:brooks-review` | `/brooks-review` | PR 级代码审查 |
|
||||
| `/brooks-lint:brooks-audit` | `/brooks-audit` | 完整架构审查 |
|
||||
| `/brooks-lint:brooks-debt` | `/brooks-debt` | 技术债评估 |
|
||||
| `/brooks-lint:brooks-test` | `/brooks-test` | 测试套件健康审查 |
|
||||
| `/brooks-lint:brooks-health` | `/brooks-health` | 健康仪表盘——全部四个维度 |
|
||||
| `/brooks-lint:brooks-sweep` | `/brooks-sweep` | 全面扫描——分析所有维度并自动修复 |
|
||||
|
||||
> 短命令由 session-start 钩子在首次会话启动时自动安装。
|
||||
|
||||
### Gemini CLI
|
||||
| 命令 | 作用 |
|
||||
|---------|--------|
|
||||
| `/brooks-review` | PR 级代码审查 |
|
||||
| `/brooks-audit` | 完整架构审查 |
|
||||
| `/brooks-debt` | 技术债评估 |
|
||||
| `/brooks-test` | 测试套件健康审查 |
|
||||
| `/brooks-health` | 健康仪表盘——全部四个维度 |
|
||||
| `/brooks-sweep` | 全面扫描——分析所有维度并自动修复 |
|
||||
|
||||
### Codex CLI
|
||||
|
||||
| 命令 | 作用 |
|
||||
|---------|--------|
|
||||
| `$brooks-review` | PR 级代码审查 |
|
||||
| `$brooks-audit` | 完整架构审查 |
|
||||
| `$brooks-debt` | 技术债评估 |
|
||||
| `$brooks-test` | 测试套件健康审查 |
|
||||
| `$brooks-health` | 健康仪表盘——全部四个维度 |
|
||||
| `$brooks-sweep` | 全面扫描——分析所有维度并自动修复 |
|
||||
|
||||
当你讨论代码质量、架构、可维护性或测试健康时,这些技能也会自动触发。
|
||||
|
||||
## 使用
|
||||
|
||||
### PR 审查
|
||||
|
||||
```
|
||||
/brooks-review # Claude Code(短命令)/ Gemini CLI
|
||||
/brooks-lint:brooks-review # Claude Code(完整形式)
|
||||
$brooks-review # Codex CLI
|
||||
```
|
||||
|
||||
粘贴一段 diff,或让 AI 指向改动的文件。它会以 症状 → 根源 → 后果 → 对策 的格式,逐一诊断六类衰退风险并给出具体诊断。
|
||||
|
||||
### 架构审查
|
||||
|
||||
```
|
||||
/brooks-audit # Claude Code(短命令)/ Gemini CLI
|
||||
/brooks-lint:brooks-audit # Claude Code(完整形式)
|
||||
$brooks-audit # Codex CLI
|
||||
```
|
||||
|
||||
描述你的项目结构或分享关键文件。它会梳理模块依赖、识别循环依赖,并检查是否符合康威定律。
|
||||
|
||||
### 技术债评估
|
||||
|
||||
```
|
||||
/brooks-debt # Claude Code(短命令)/ Gemini CLI
|
||||
/brooks-lint:brooks-debt # Claude Code(完整形式)
|
||||
$brooks-debt # Codex CLI
|
||||
```
|
||||
|
||||
按六类衰退风险对技术债分类,以 痛感 × 扩散面 为每条诊断打优先级,产出带 Critical / Scheduled / Monitored 分级的偿还路线图。
|
||||
|
||||
### 测试质量审查
|
||||
|
||||
```
|
||||
/brooks-test # Claude Code(短命令)/ Gemini CLI
|
||||
/brooks-lint:brooks-test # Claude Code(完整形式)
|
||||
$brooks-test # Codex CLI
|
||||
```
|
||||
|
||||
对照六类测试空间衰退风险审查你的测试套件——测试晦涩、测试脆弱、测试重复、Mock 滥用、覆盖率幻觉、架构错配——出处为 xUnit Test Patterns、The Art of Unit Testing、How Google Tests Software 和 Working Effectively with Legacy Code。PR 审查还会自动包含一个轻量的第 7 步快速测试检查(对纯文档或非生产代码 diff 会跳过)。
|
||||
|
||||
### 健康仪表盘
|
||||
|
||||
```
|
||||
/brooks-health # Claude Code(短命令)/ Gemini CLI
|
||||
/brooks-lint:brooks-health # Claude Code(完整形式)
|
||||
$brooks-health # Codex CLI
|
||||
```
|
||||
|
||||
对全部四个质量维度做精简扫描,产出加权综合健康分(0–100)。适合发版前、新团队上手时,或任何你想要一份"我们现在怎么样?"全局报告的场景。需要某个维度的深度诊断时,请改用对应的专项技能。
|
||||
|
||||
### 全面扫描
|
||||
|
||||
```
|
||||
/brooks-sweep # Claude Code(短命令)/ Gemini CLI
|
||||
/brooks-lint:brooks-sweep # Claude Code(完整形式)
|
||||
$brooks-sweep # Codex CLI
|
||||
```
|
||||
|
||||
一次性扫描全部生产(R1–R6)与测试(T1–T6)衰退风险以及架构,然后施加修复:安全改动立即自动应用,跨文件或触及接口的改动需确认,复杂的架构决策则标记为人工处理项。输出修复日志、健康分变化和遗留项清单。
|
||||
|
||||
## 配置
|
||||
|
||||
在项目根目录放一个 `.brooks-lint.yaml` 来定制审查行为:
|
||||
|
||||
```yaml
|
||||
version: 1
|
||||
|
||||
disable:
|
||||
- T5 # 跳过覆盖率指标检查——我们不强制覆盖率
|
||||
|
||||
severity:
|
||||
R1: suggestion # 在该领域下调"认知过载"诊断的严重度
|
||||
|
||||
ignore:
|
||||
- "**/*.generated.*"
|
||||
- "**/vendor/**"
|
||||
```
|
||||
|
||||
可复制 [`.brooks-lint.example.yaml`](.brooks-lint.example.yaml) 作为起点。
|
||||
所有设置均为可选——完全省略该文件即使用默认行为。
|
||||
|
||||
| 设置 | 说明 |
|
||||
|---------|-------------|
|
||||
| `disable` | 要跳过的风险码(`R1`–`R6`、`T1`–`T6`) |
|
||||
| `severity` | 覆盖严重度等级(`critical` / `warning` / `suggestion`) |
|
||||
| `ignore` | 要排除的文件 glob 模式 |
|
||||
| `focus` | 只评估这些风险码(不能与 `disable` 同时使用) |
|
||||
|
||||
---
|
||||
|
||||
## 为什么是这些书,为什么是现在?
|
||||
|
||||
在 AI 辅助编程的时代,我们写代码比以往任何时候都更快、更多。但六十年软件工程沉淀下来的洞见并没有改变:
|
||||
|
||||
> *"软件的复杂性是本质属性,而非偶然属性。"*
|
||||
> —— Frederick Brooks
|
||||
|
||||
AI 能帮你更快地写代码,却无法告诉你正在建造的是大教堂还是焦油坑。**brooks-lint 弥合了这道鸿沟**——它把十二本经典工程著作中来之不易的智慧,带进你现代的开发工作流。
|
||||
|
||||
这些作者识别出的衰退风险,如今比以往更切题:
|
||||
- **接入 AI 助手** 并不能修复认知过载或领域模型失真
|
||||
- **生成更多代码** 会加剧变更扩散和知识重复
|
||||
- **跑得更快** 让偶发复杂度和依赖失序更加危险
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
brooks-lint/
|
||||
├── .claude-plugin/ # Claude Code 插件元数据
|
||||
├── .codex-plugin/ # Codex CLI 插件元数据
|
||||
├── skills/
|
||||
│ ├── _shared/ # 共享框架文件
|
||||
│ │ ├── common.md # 铁律、项目配置、报告模板、健康分
|
||||
│ │ ├── source-coverage.md # 12 本书覆盖矩阵、权衡、误报防护
|
||||
│ │ ├── decay-risks.md # 六类衰退风险及症状与书目出处
|
||||
│ │ ├── test-decay-risks.md # 六类测试空间衰退风险及书目出处
|
||||
│ │ ├── remedy-guide.md # --fix 模式:可落地的对策增强规则
|
||||
│ │ └── custom-risks-guide.md # 项目自定义风险码模板
|
||||
│ ├── brooks-review/ # 模式 1:PR 审查
|
||||
│ │ ├── SKILL.md
|
||||
│ │ └── pr-review-guide.md
|
||||
│ ├── brooks-audit/ # 模式 2:架构审查
|
||||
│ │ ├── SKILL.md
|
||||
│ │ └── architecture-guide.md
|
||||
│ ├── brooks-debt/ # 模式 3:技术债评估
|
||||
│ │ ├── SKILL.md
|
||||
│ │ └── debt-guide.md
|
||||
│ ├── brooks-test/ # 模式 4:测试质量审查
|
||||
│ │ ├── SKILL.md
|
||||
│ │ └── test-guide.md
|
||||
│ ├── brooks-health/ # 模式 5:健康仪表盘
|
||||
│ │ ├── SKILL.md
|
||||
│ │ └── health-guide.md
|
||||
│ └── brooks-sweep/ # 模式 6:全面扫描与自动修复
|
||||
│ ├── SKILL.md
|
||||
│ └── sweep-guide.md
|
||||
├── hooks/ # SessionStart 钩子
|
||||
├── commands/ # 短命令包装(由钩子自动安装)
|
||||
├── evals/ # 基准测试用例
|
||||
│ └── evals.json
|
||||
└── assets/
|
||||
└── logo.svg
|
||||
```
|
||||
|
||||
## CI/CD 集成
|
||||
|
||||
用 GitHub Action 在每个 PR 上自动运行 brooks-lint:
|
||||
|
||||
```yaml
|
||||
# .github/workflows/brooks-lint.yml
|
||||
name: Brooks-Lint PR Review
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
brooks-lint:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: hyhmrright/brooks-lint/.github/actions/brooks-lint@main
|
||||
with:
|
||||
mode: review
|
||||
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
fail-below: 70
|
||||
```
|
||||
|
||||
完整模板见 [`docs/github-action-example.yml`](docs/github-action-example.yml)。
|
||||
|
||||
该 Action 会把审查结果作为 PR 评论发布,并可在健康分跌破阈值时让检查失败。若仓库中提交了 `.brooks-lint-history.json`,评论还会包含趋势变化(如 "85 → 82(−3),近 3 次运行")。
|
||||
|
||||
**成本:** 每次 PR 运行约 $0.05–0.15,取决于 diff 大小和模型。建议仅在 `pull_request` 事件上运行。
|
||||
|
||||
## 路线图
|
||||
|
||||
> **当前状态(v1.0):** 12 本书地基,6 类生产衰退风险(R1–R6)+ 6 类测试衰退风险(T1–T6),5 个技能——PR 审查、架构审查、技术债、测试质量、健康仪表盘。下方较早的条目记录的是历史里程碑,而非当前功能集。
|
||||
|
||||
- [x] **v0.2**:插件基础设施(`.claude-plugin/`、钩子、斜杠命令)
|
||||
- [x] **v0.3**:八个 Brooks 维度、文档完整度评分
|
||||
- [x] **v0.4**:六本书框架、衰退风险维度、诊断链、基准套件
|
||||
- [x] **v0.5**:测试质量审查(模式 4)——四本测试书、六类测试衰退风险
|
||||
- [x] **v0.6**:架构审查中的 Mermaid 依赖图
|
||||
- [x] **v0.7**:`.brooks-lint.yaml` 项目配置、模式 2 主动上下文、扩展到 10 本书
|
||||
- [x] **v0.8**:带命名空间命令的独立技能架构
|
||||
- [x] **v0.9**:步骤校验、自动 diff 范围、`/brooks-health` 仪表盘、趋势追踪、分诊模式、`--fix` 对策、上手报告、GitHub Action
|
||||
- [x] **v1.0**:评测自动化(`run-evals-live.mjs`)、自定义风险扩展(`Cx` 码)
|
||||
|
||||
想出一份力?现在最有价值的贡献是新的评测用例和更好的衰退风险症状模式。见 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
||||
|
||||
## 贡献
|
||||
|
||||
如何新增诊断、改进指南或扩展基准套件,见 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
||||
|
||||
在你自己的 PR 上跑一遍 `/brooks-review`——我们用正在打造的工具来审查贡献。
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License——详见 [LICENSE](LICENSE)。
|
||||
|
||||
## 致谢
|
||||
|
||||
本项目站在十二位巨人的肩膀上:
|
||||
|
||||
**生产代码框架**
|
||||
- Frederick P. Brooks Jr. — *The Mythical Man-Month*(1975,纪念版 1995)
|
||||
- Steve McConnell — *Code Complete*(1993,第 2 版 2004)
|
||||
- Martin Fowler — *Refactoring*(1999,第 2 版 2018)
|
||||
- Robert C. Martin — *Clean Architecture*(2017)
|
||||
- Andrew Hunt & David Thomas — *The Pragmatic Programmer*(1999,20 周年版 2019)
|
||||
- Eric Evans — *Domain-Driven Design*(2003)
|
||||
- John Ousterhout — *A Philosophy of Software Design*(2018)
|
||||
- Titus Winters、Tom Manshreck、Hyrum Wright — *Software Engineering at Google*(2020)
|
||||
|
||||
**测试质量框架**
|
||||
- Gerard Meszaros — *xUnit Test Patterns*(2007)
|
||||
- Roy Osherove — *The Art of Unit Testing*(2009,第 3 版 2023)
|
||||
- Google Engineering — *How Google Tests Software*(2012)
|
||||
- Michael Feathers — *Working Effectively with Legacy Code*(2004)
|
||||
|
||||
本工具中编码的衰退风险,是我们对他们思想的综合,并应用于现代代码质量评估。
|
||||
|
||||
---
|
||||
|
||||
## Star 历史
|
||||
|
||||
[](https://star-history.com/#hyhmrright/brooks-lint&Date)
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<strong>⭐ 如果这个工具让你以不同的眼光看待自己的代码库,请给它点个 star!</strong>
|
||||
</p>
|
||||
@@ -1,8 +1,8 @@
|
||||
# Source
|
||||
|
||||
- Repo: https://github.com/hyhmrright/brooks-lint
|
||||
- Ref: 247b3fdab97fcb826a13b1e688ecbe9261f318ad
|
||||
- Ref: 703449555a7745df501299b280fe0c6eb991bfae
|
||||
- Remove-Paths:
|
||||
- Snapshot: 2026-05-29
|
||||
- Snapshot: 2026-06-01
|
||||
- Sync-Mode: copy_skill_dirs
|
||||
- Notes: vendored into playbook branch thirdparty/skill
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
@@ -0,0 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- Source for assets/demo.gif — each ?f=N renders one cumulative frame; capture f=0..N, assemble with ffmpeg. -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
:root{
|
||||
--paper:#f7f1e4; --card:#fffdf8; --ink:#2a2520; --ink-soft:#5c5347;
|
||||
--rule:#e3d8c2; --rule-soft:#ece2cf; --blue:#2d4a7c; --green:#2b8a3e; --gold:#b07d2b; --red:#b23b3b;
|
||||
--ink-dark:#211d18;
|
||||
--serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;
|
||||
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
||||
--mono:"SF Mono","JetBrains Mono","Fira Code",Menlo,Consolas,monospace;
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box;}
|
||||
body{background:#cdbf9f;font-family:var(--sans);}
|
||||
#card{width:1000px;height:660px;background:var(--paper);position:relative;overflow:hidden;display:flex;flex-direction:column;}
|
||||
#card::before{content:"";position:absolute;inset:0;background-image:radial-gradient(rgba(120,90,40,.05) 1px,transparent 1px);background-size:4px 4px;}
|
||||
.bar{display:flex;align-items:center;gap:8px;padding:14px 18px;background:#efe6d2;border-bottom:1px solid var(--rule);z-index:1;}
|
||||
.dot{width:13px;height:13px;border-radius:50%;}
|
||||
.dot.r{background:#e07a6b;} .dot.y{background:#e6c34d;} .dot.g{background:#7bbf7b;}
|
||||
.bar .ttl{margin-left:10px;font-family:var(--mono);font-size:13.5px;color:var(--ink-soft);display:flex;align-items:center;gap:8px;}
|
||||
.bar .ttl img{width:18px;height:18px;border-radius:4px;}
|
||||
.console{flex:1;padding:24px 30px;z-index:1;font-family:var(--mono);font-size:16px;line-height:1.7;color:var(--ink);overflow:hidden;}
|
||||
.cmd{font-size:18px;margin-bottom:6px;}
|
||||
.cmd .pr{color:var(--gold);font-weight:700;margin-right:10px;}
|
||||
.cmd .slash{color:var(--blue);font-weight:700;}
|
||||
.cur{display:inline-block;width:10px;height:20px;background:var(--ink);vertical-align:-3px;margin-left:1px;}
|
||||
.cur.off{display:none;}
|
||||
.run{color:var(--ink-soft);font-size:15px;margin:10px 0 4px;}
|
||||
.run .sp{color:var(--gold);}
|
||||
.report{margin-top:14px;font-family:var(--sans);}
|
||||
.score{font-family:var(--serif);font-size:30px;font-weight:700;color:var(--red);display:flex;align-items:baseline;gap:10px;margin-bottom:4px;}
|
||||
.score small{font-family:var(--serif);font-size:16px;color:var(--ink-soft);font-weight:400;}
|
||||
.score .verdict{font-family:var(--sans);font-size:13px;font-style:italic;color:var(--ink-soft);font-weight:400;margin-left:6px;}
|
||||
.f{border-left:3px solid var(--red);background:var(--card);border:1px solid var(--rule);border-left:3px solid var(--red);
|
||||
border-radius:8px;padding:10px 14px;margin-top:9px;}
|
||||
.f h4{font-family:var(--serif);font-size:15.5px;color:var(--ink);margin-bottom:3px;}
|
||||
.f .l{font-size:13px;line-height:1.5;color:var(--ink-soft);}
|
||||
.f .l b{color:var(--ink);} .f .l.sr b{color:var(--blue);} .f .l.rx b{color:var(--green);}
|
||||
.more{font-family:var(--mono);font-size:13px;color:var(--ink-soft);margin-top:10px;}
|
||||
.foot{font-family:var(--serif);font-size:15px;color:var(--ink);margin-top:14px;}
|
||||
.foot b{color:var(--gold);}
|
||||
.hide{display:none !important;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="card">
|
||||
<div class="bar">
|
||||
<span class="dot r"></span><span class="dot y"></span><span class="dot g"></span>
|
||||
<span class="ttl"><img src="logo.svg" alt="">claude code — brooks-lint</span>
|
||||
</div>
|
||||
<div class="console">
|
||||
<div class="cmd"><span class="pr">›</span><span id="cmdtext"></span><span id="cursor" class="cur"></span></div>
|
||||
<div id="run" class="run hide"><span class="sp">⠋</span> Reviewing <code>UserService.update_profile</code> against R1–R6…</div>
|
||||
<div id="report" class="report hide">
|
||||
<div id="score" class="score hide">28 <small>/ 100 Health Score</small><span class="verdict">— 4 responsibilities, a silent bug, SQL injection</span></div>
|
||||
|
||||
<div id="f1" class="f hide">
|
||||
<h4>🔴 R2 — Change Propagation</h4>
|
||||
<div class="l"><b>Symptom:</b> one method does updates, email, loyalty & cache.</div>
|
||||
<div class="l rx"><b>Remedy:</b> extract Notification / Loyalty / CacheInvalidator.</div>
|
||||
</div>
|
||||
<div id="f2" class="f hide">
|
||||
<h4>🔴 R6 — Domain Model Distortion</h4>
|
||||
<div class="l"><b>Symptom:</b> email overwritten before the <code>!=</code> check — dead branch.</div>
|
||||
<div class="l sr"><b>Source:</b> McConnell — Code Complete — Ch. 17.</div>
|
||||
</div>
|
||||
<div id="f3" class="f hide">
|
||||
<h4>🔴 R5 — Dependency Disorder + SQL injection ×2</h4>
|
||||
<div class="l rx"><b>Remedy:</b> parameterise queries; invert the db dependency.</div>
|
||||
</div>
|
||||
<div id="more" class="more hide">+ 5 more findings · every one cited to a book</div>
|
||||
<div id="foot" class="foot hide">Symptom → Source → Consequence → Remedy · <b>grounded in 12 classic engineering books</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var params = new URLSearchParams(location.search);
|
||||
var f = parseInt(params.get('f') || '0', 10);
|
||||
var FULL = '/brooks-review';
|
||||
// frame plan
|
||||
var typed = ['', '/br', '/brooks', '/brooks-rev', '/brooks-review', FULL, FULL, FULL, FULL, FULL, FULL, FULL, FULL, FULL];
|
||||
var t = typed[Math.min(f, typed.length - 1)];
|
||||
// render command text with slash colored
|
||||
var el = document.getElementById('cmdtext');
|
||||
el.innerHTML = t.replace(/^\//, '<span class="slash">/</span>');
|
||||
// cursor: blink off on even submitted frames for life; show during typing
|
||||
document.getElementById('cursor').classList.toggle('off', f >= 5 && (f % 2 === 1));
|
||||
function show(id, on){ document.getElementById(id).classList.toggle('hide', !on); }
|
||||
show('run', f >= 5 && f <= 6);
|
||||
show('report', f >= 6);
|
||||
show('score', f >= 6);
|
||||
show('f1', f >= 7);
|
||||
show('f2', f >= 8);
|
||||
show('f3', f >= 9);
|
||||
show('more', f >= 10);
|
||||
show('foot', f >= 11);
|
||||
// when report visible, hide the run line at f>=7 (replaced by report)
|
||||
if (f >= 7) show('run', false);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 305 KiB |
@@ -0,0 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- Source for assets/hero.png — render headless at 1280x720, then screenshot #card -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
:root{
|
||||
--paper:#f7f1e4; --card:#fffdf8; --ink:#2a2520; --ink-soft:#5c5347;
|
||||
--rule:#e3d8c2; --blue:#2d4a7c; --green:#2b8a3e; --gold:#b07d2b; --red:#b23b3b;
|
||||
--ink-dark:#211d18;
|
||||
--serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;
|
||||
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
||||
--mono:"SF Mono","JetBrains Mono","Fira Code",Menlo,Consolas,monospace;
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box;}
|
||||
body{background:#cdbf9f;font-family:var(--sans);}
|
||||
#card{
|
||||
width:1280px;height:720px;background:var(--paper);position:relative;overflow:hidden;
|
||||
padding:46px 54px;display:flex;flex-direction:column;
|
||||
}
|
||||
#card::before{content:"";position:absolute;inset:0;
|
||||
background-image:radial-gradient(rgba(120,90,40,.05) 1px,transparent 1px);background-size:4px 4px;}
|
||||
.top{display:flex;align-items:center;gap:12px;z-index:1;margin-bottom:22px;}
|
||||
.top img{width:38px;height:38px;border-radius:9px;}
|
||||
.top .name{font-family:var(--serif);font-weight:700;font-size:25px;color:var(--ink);}
|
||||
.top .tag{margin-left:auto;font-family:var(--mono);font-size:14px;color:var(--gold);
|
||||
border:1px solid var(--rule);background:var(--card);padding:6px 14px;border-radius:999px;letter-spacing:.04em;}
|
||||
.panels{display:flex;align-items:stretch;gap:0;flex:1;z-index:1;}
|
||||
.panel{flex:1;display:flex;flex-direction:column;}
|
||||
.plabel{font-family:var(--mono);font-size:13px;letter-spacing:.12em;text-transform:uppercase;
|
||||
color:var(--ink-soft);margin-bottom:10px;}
|
||||
/* code panel */
|
||||
.code{background:var(--ink-dark);border-radius:14px;padding:22px 24px;flex:1;white-space:pre-wrap;
|
||||
font-family:var(--mono);font-size:14px;line-height:1.7;color:#cfc7b6;overflow:hidden;tab-size:2;}
|
||||
.code .kw{color:#c98ac9;} .code .fn{color:#7fa8e0;} .code .str{color:#9ccb8a;}
|
||||
.code .cm{color:#7d7565;} .code .warn{background:rgba(178,59,59,.28);border-bottom:2px wavy var(--red);border-radius:2px;}
|
||||
.arrow{display:flex;align-items:center;justify-content:center;width:78px;}
|
||||
.arrow div{font-size:34px;color:var(--gold);font-weight:700;}
|
||||
/* verdict */
|
||||
.verdict{background:var(--card);border:1px solid var(--rule);border-radius:14px;padding:20px 22px;flex:1;
|
||||
box-shadow:0 10px 30px rgba(80,55,15,.10);}
|
||||
.score{display:flex;align-items:baseline;gap:10px;border-bottom:1px solid var(--rule);padding-bottom:12px;margin-bottom:12px;}
|
||||
.score .n{font-family:var(--serif);font-weight:700;font-size:46px;color:var(--red);line-height:1;}
|
||||
.score .lbl{font-family:var(--serif);font-size:16px;color:var(--ink-soft);}
|
||||
.find{margin-bottom:13px;}
|
||||
.find h4{font-family:var(--serif);font-size:15.5px;color:var(--ink);margin-bottom:5px;}
|
||||
.find h4 .d{color:var(--red);}
|
||||
.find .ln{font-size:12.8px;line-height:1.5;color:var(--ink-soft);}
|
||||
.find .ln b{font-weight:700;}
|
||||
.find .ln.sy b{color:var(--ink);} .find .ln.sr b{color:var(--blue);} .find .ln.rx b{color:var(--green);}
|
||||
.foot{z-index:1;text-align:center;margin-top:20px;font-family:var(--serif);font-size:16px;color:var(--ink);}
|
||||
.foot b{color:var(--gold);}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="card">
|
||||
<div class="top">
|
||||
<img src="logo.svg" alt="">
|
||||
<span class="name">brooks-lint</span>
|
||||
<span class="tag">grounded in 12 classic engineering books</span>
|
||||
</div>
|
||||
<div class="panels">
|
||||
<div class="panel">
|
||||
<div class="plabel">Your code</div>
|
||||
<div class="code"><span class="kw">class</span> <span class="fn">UserService</span>:
|
||||
<span class="kw">def</span> <span class="fn">update_profile</span>(self, user_id, name, email, avatar):
|
||||
user = self.db.query(<span class="str warn">f"SELECT * FROM users WHERE id = {user_id}"</span>)
|
||||
user[<span class="str">'email'</span>] = email
|
||||
<span class="cm"># ...</span>
|
||||
<span class="kw">if</span> <span class="warn">user[<span class="str">'email'</span>] != email</span>: <span class="cm"># always False</span>
|
||||
self.smtp.send(...)
|
||||
points = user[<span class="str">'login_count'</span>] * <span class="str">10</span> + <span class="str">500</span>
|
||||
self.db.execute(<span class="str warn">f"UPDATE loyalty SET points={points} ..."</span>)
|
||||
self.cache.invalidate(<span class="str">f"user:{user_id}"</span>)</div>
|
||||
</div>
|
||||
<div class="arrow"><div>→</div></div>
|
||||
<div class="panel">
|
||||
<div class="plabel">brooks-lint verdict</div>
|
||||
<div class="verdict">
|
||||
<div class="score"><span class="n">28</span><span class="lbl">/ 100 Health Score</span></div>
|
||||
<div class="find">
|
||||
<h4><span class="d">🔴</span> R2 — Change Propagation</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> one method does updates, email, loyalty & cache.</div>
|
||||
<div class="ln sr"><b>Source:</b> Fowler — Refactoring — Divergent Change</div>
|
||||
<div class="ln rx"><b>Remedy:</b> extract Notification / Loyalty / CacheInvalidator.</div>
|
||||
</div>
|
||||
<div class="find">
|
||||
<h4><span class="d">🔴</span> R6 — Domain Model Distortion</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> email overwritten before the != check — dead branch.</div>
|
||||
<div class="ln sr"><b>Source:</b> McConnell — Code Complete — Ch.17</div>
|
||||
<div class="ln rx"><b>Remedy:</b> capture old_email before any mutation.</div>
|
||||
</div>
|
||||
<div class="find">
|
||||
<h4><span class="d">🔴</span> R5 — Dependency Disorder <span style="color:var(--ink-soft);font-weight:400;font-family:var(--sans);font-size:12px;">+ SQL injection ×2</span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="foot">Every finding: <b>Symptom → Source → Consequence → Remedy</b></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
@@ -0,0 +1,495 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Gallery — brooks-lint</title>
|
||||
<meta name="description" content="Real brooks-lint output across Python, TypeScript, Go, and Java — PR reviews, architecture audits with dependency graphs, tech debt assessments, and test quality reviews.">
|
||||
<link rel="icon" type="image/svg+xml" href="logo.svg">
|
||||
<meta property="og:title" content="brooks-lint Gallery">
|
||||
<meta property="og:description" content="Real diagnostic output across four languages and four review modes.">
|
||||
<meta property="og:image" content="https://hyhmrright.github.io/brooks-lint/hero.png">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<style>
|
||||
:root{
|
||||
--paper:#f7f1e4; --paper-2:#fbf6ec; --card:#fffdf8; --ink:#2a2520; --ink-soft:#5c5347;
|
||||
--rule:#e3d8c2; --rule-soft:#ece2cf; --blue:#2d4a7c; --blue-soft:#e7edf5;
|
||||
--green:#2b8a3e; --gold:#b07d2b; --red:#b23b3b; --ink-dark:#211d18;
|
||||
--serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;
|
||||
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Roboto,Helvetica,Arial,sans-serif;
|
||||
--mono:"SF Mono","JetBrains Mono","Fira Code",Menlo,Consolas,monospace;
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box;}
|
||||
html{scroll-behavior:smooth;}
|
||||
body{font-family:var(--sans);color:var(--ink);background:var(--paper);line-height:1.65;-webkit-font-smoothing:antialiased;}
|
||||
body::before{content:"";position:fixed;inset:0;pointer-events:none;z-index:0;
|
||||
background-image:radial-gradient(rgba(120,90,40,.04) 1px,transparent 1px);background-size:4px 4px;opacity:.6;}
|
||||
.wrap{max-width:1000px;margin:0 auto;padding:0 24px;position:relative;z-index:1;}
|
||||
h1,h2,h3,.serif{font-family:var(--serif);font-weight:700;letter-spacing:-.01em;}
|
||||
a{color:var(--blue);text-decoration:none;} a:hover{text-decoration:underline;}
|
||||
|
||||
nav{position:sticky;top:0;z-index:50;background:rgba(247,241,228,.85);backdrop-filter:saturate(140%) blur(10px);border-bottom:1px solid var(--rule);}
|
||||
.nav-in{display:flex;align-items:center;gap:20px;height:64px;max-width:1000px;margin:0 auto;padding:0 24px;}
|
||||
.brand{display:flex;align-items:center;gap:10px;font-family:var(--serif);font-weight:700;font-size:19px;color:var(--ink);}
|
||||
.brand img{width:30px;height:30px;border-radius:7px;}
|
||||
.nav-links{display:flex;gap:22px;margin-left:auto;align-items:center;}
|
||||
.nav-links a{color:var(--ink-soft);font-size:14.5px;}
|
||||
|
||||
header.hero{padding:60px 0 30px;text-align:center;}
|
||||
header.hero h1{font-size:clamp(30px,4.6vw,46px);margin-bottom:14px;}
|
||||
header.hero p{color:var(--ink-soft);font-size:17px;max-width:60ch;margin:0 auto;}
|
||||
.meta-row{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin-top:22px;}
|
||||
.meta-pill{background:var(--card);border:1px solid var(--rule);border-radius:999px;padding:6px 14px;font-size:13px;color:var(--ink-soft);}
|
||||
.meta-pill b{color:var(--ink);font-family:var(--serif);}
|
||||
|
||||
.filters{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin:34px 0 8px;position:sticky;top:64px;z-index:40;
|
||||
background:var(--paper);padding:12px 0;}
|
||||
.filters button{font-family:var(--sans);font-size:14px;font-weight:600;cursor:pointer;border:1px solid var(--rule);
|
||||
background:var(--card);color:var(--ink-soft);padding:8px 16px;border-radius:999px;}
|
||||
.filters button.active{background:var(--ink);color:var(--paper-2);border-color:var(--ink);}
|
||||
|
||||
section.mode-group{padding:24px 0;}
|
||||
.mode-title{font-family:var(--serif);font-size:24px;margin:18px 0 4px;display:flex;align-items:center;gap:10px;}
|
||||
.mode-title .badge{font-family:var(--mono);font-size:12px;color:var(--gold);border:1px solid var(--rule);background:var(--card);padding:3px 10px;border-radius:999px;font-weight:400;}
|
||||
.mode-sub{color:var(--ink-soft);font-size:14.5px;margin-bottom:18px;}
|
||||
|
||||
.case{background:var(--card);border:1px solid var(--rule);border-radius:14px;padding:24px 26px;margin-bottom:20px;
|
||||
box-shadow:0 6px 20px rgba(80,55,15,.05);}
|
||||
.case-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:6px;}
|
||||
.lang{font-family:var(--mono);font-size:11.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
|
||||
padding:3px 9px;border-radius:6px;color:#fff;}
|
||||
.lang.ts{background:#2d4a7c;} .lang.py{background:#2b6a8a;} .lang.go{background:#1f8a9c;} .lang.java{background:#9c6b1f;}
|
||||
.case-head h3{font-size:19px;flex:1;min-width:200px;}
|
||||
.hs{font-family:var(--serif);font-weight:700;font-size:15px;padding:4px 12px;border-radius:999px;white-space:nowrap;}
|
||||
.hs.bad{background:#f6dede;color:var(--red);} .hs.mid{background:#f6ecd4;color:var(--gold);} .hs.good{background:#def0e1;color:var(--green);}
|
||||
.case-summary{font-style:italic;color:var(--ink-soft);font-size:14.5px;margin:8px 0 16px;}
|
||||
|
||||
details{margin:0 0 18px;border:1px solid var(--rule-soft);border-radius:10px;background:var(--paper-2);}
|
||||
details summary{cursor:pointer;padding:11px 16px;font-size:13.5px;color:var(--ink-soft);font-weight:600;user-select:none;}
|
||||
details[open] summary{border-bottom:1px solid var(--rule-soft);}
|
||||
pre.code{margin:0;padding:18px;background:var(--ink-dark);color:#cfc7b6;border-radius:0 0 10px 10px;overflow-x:auto;
|
||||
font-family:var(--mono);font-size:12.8px;line-height:1.6;}
|
||||
pre.struct{background:var(--ink-dark);color:#cfc7b6;}
|
||||
|
||||
.finding{border-top:1px solid var(--rule-soft);padding-top:14px;margin-top:14px;}
|
||||
.finding:first-of-type{border-top:none;padding-top:0;margin-top:0;}
|
||||
.finding h4{font-family:var(--serif);font-size:16px;margin-bottom:8px;display:flex;gap:8px;align-items:baseline;flex-wrap:wrap;}
|
||||
.finding h4 .pri{font-family:var(--mono);font-size:11px;color:var(--ink-soft);font-weight:400;}
|
||||
.ln{font-size:13.5px;line-height:1.55;margin-bottom:5px;color:var(--ink-soft);}
|
||||
.ln b{font-weight:700;}
|
||||
.ln.sy b{color:var(--ink);} .ln.sr b{color:var(--blue);} .ln.cq b{color:var(--gold);} .ln.rx b{color:var(--green);}
|
||||
|
||||
.mermaid{background:var(--paper-2);border:1px solid var(--rule-soft);border-radius:10px;padding:16px;margin:4px 0 18px;text-align:center;}
|
||||
|
||||
table.dbt{width:100%;border-collapse:collapse;font-size:13.5px;margin:4px 0 16px;border:1px solid var(--rule-soft);border-radius:8px;overflow:hidden;}
|
||||
table.dbt th,table.dbt td{padding:9px 13px;text-align:left;border-bottom:1px solid var(--rule-soft);}
|
||||
table.dbt th{font-family:var(--serif);background:var(--paper-2);}
|
||||
table.dbt tr:last-child td{border-bottom:none;}
|
||||
|
||||
.note{background:var(--blue-soft);border-left:3px solid var(--blue);border-radius:8px;padding:11px 15px;font-size:13.5px;margin-top:6px;}
|
||||
footer{border-top:1px solid var(--rule);padding:36px 0 56px;background:var(--paper-2);margin-top:30px;}
|
||||
.foot-in{display:flex;flex-wrap:wrap;gap:18px;align-items:center;justify-content:space-between;max-width:1000px;margin:0 auto;padding:0 24px;}
|
||||
.foot-links{display:flex;gap:20px;flex-wrap:wrap;} .foot-links a{color:var(--ink-soft);font-size:14px;}
|
||||
.foot-note{font-size:13px;color:var(--ink-soft);}
|
||||
.hidden{display:none !important;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<div class="nav-in">
|
||||
<a class="brand" href="index.html"><img src="logo.svg" alt="">brooks-lint</a>
|
||||
<div class="nav-links">
|
||||
<a href="index.html">← Home</a>
|
||||
<a href="https://github.com/hyhmrright/brooks-lint" target="_blank" rel="noopener">GitHub ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="hero">
|
||||
<div class="wrap">
|
||||
<h1>The Gallery</h1>
|
||||
<p>Real diagnostic output from brooks-lint — generated by running the skill, then lightly abridged. Four languages, four review modes, every finding in the Iron Law form.</p>
|
||||
<div class="meta-row">
|
||||
<span class="meta-pill"><b>8</b> worked examples</span>
|
||||
<span class="meta-pill"><b>4</b> review modes</span>
|
||||
<span class="meta-pill"><b>4</b> languages</span>
|
||||
<span class="meta-pill">Python · TypeScript · Go · Java</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
<div class="filters" id="filters">
|
||||
<button data-filter="all" class="active">All</button>
|
||||
<button data-filter="review">PR Review</button>
|
||||
<button data-filter="audit">Architecture</button>
|
||||
<button data-filter="debt">Tech Debt</button>
|
||||
<button data-filter="test">Test Quality</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main class="wrap">
|
||||
|
||||
<!-- ============ PR REVIEW ============ -->
|
||||
<section class="mode-group" data-mode="review">
|
||||
<h2 class="mode-title">PR Review <span class="badge">Mode 1</span></h2>
|
||||
<p class="mode-sub">Diagnoses a diff against the six production decay risks (R1–R6).</p>
|
||||
|
||||
<article class="case">
|
||||
<div class="case-head">
|
||||
<span class="lang ts">TypeScript</span>
|
||||
<h3>Seven-service payment processor</h3>
|
||||
<span class="hs mid">55 / 100</span>
|
||||
</div>
|
||||
<p class="case-summary">A single method orchestrates seven services, creating a structural coupling trap where any change to payment, fraud, inventory, or notification touches the same method.</p>
|
||||
<details>
|
||||
<summary>▸ Input code</summary>
|
||||
<pre class="code">class PaymentProcessor {
|
||||
constructor(
|
||||
private db, private stripe, private mailer, private inventory,
|
||||
private analytics, private taxCalc, private fraudDetection
|
||||
) {}
|
||||
|
||||
async processPayment(orderId, cardToken) {
|
||||
const order = await this.db.orders.findById(orderId);
|
||||
const tax = this.taxCalc.calculate(order.items, order.shippingAddress.state);
|
||||
order.tax = tax;
|
||||
const fraudScore = await this.fraudDetection.evaluate({ amount: order.total + tax, card: cardToken, ... });
|
||||
if (fraudScore > 0.8) { await this.mailer.send(...); await this.analytics.track('fraud_hold', ...); return {...}; }
|
||||
const charge = await this.stripe.charges.create({ amount: Math.round((order.total + tax) * 100), ... });
|
||||
for (const item of order.items) {
|
||||
await this.inventory.decrement(item.sku, item.quantity);
|
||||
if (await this.inventory.getStock(item.sku) < 10) { await this.mailer.send('warehouse@company.com', ...); }
|
||||
}
|
||||
order.status = 'paid'; order.chargeId = charge.id; await this.db.orders.save(order);
|
||||
await this.mailer.send(order.customerEmail, 'Payment Received', `Charge: $${order.total + tax}`);
|
||||
await this.analytics.track('payment_success', { orderId, amount: order.total + tax });
|
||||
return { status: 'paid', chargeId: charge.id };
|
||||
}
|
||||
}</pre>
|
||||
</details>
|
||||
<div class="finding">
|
||||
<h4>🔴 Change Propagation — Seven-service constructor signals a God Class</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> One class injects seven dependencies; one method orchestrates all of them.</div>
|
||||
<div class="ln sr"><b>Source:</b> Fowler — Refactoring — Divergent Change; Martin — Clean Architecture — SRP</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Decompose into FraudCheckService, InventoryDeductionService, PaymentNotifier — inject 3, not 7.</div>
|
||||
</div>
|
||||
<div class="finding">
|
||||
<h4>🔴 Change Propagation — Inventory loop embeds warehouse notification policy</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> A low-stock check (<code>< 10</code>) fires a warehouse email with hardcoded recipient inside the payment loop.</div>
|
||||
<div class="ln sr"><b>Source:</b> Fowler — Refactoring — Shotgun Surgery; Hunt & Thomas — Orthogonality</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Publish a <code>StockLevelChanged</code> domain event a separate notifier subscribes to.</div>
|
||||
</div>
|
||||
<div class="finding">
|
||||
<h4>🟡 Knowledge Duplication — <code>order.total + tax</code> computed three times</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> The same expression appears on three lines with no shared name.</div>
|
||||
<div class="ln sr"><b>Source:</b> Hunt & Thomas — DRY; Fowler — Refactoring — Duplicate Code</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Expose a computed <code>order.grandTotal</code>.</div>
|
||||
</div>
|
||||
<div class="finding">
|
||||
<h4>🟡 Domain Model Distortion — <code>Order</code> is a mutable data bag</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> <code>order.tax</code>, <code>order.status</code>, <code>order.chargeId</code> are all set externally; the object holds no behaviour.</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Give <code>Order</code> state-transition methods: <code>order.recordPayment(chargeId)</code>.</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<!-- ============ ARCHITECTURE ============ -->
|
||||
<section class="mode-group" data-mode="audit">
|
||||
<h2 class="mode-title">Architecture Audit <span class="badge">Mode 2</span></h2>
|
||||
<p class="mode-sub">Maps module dependencies, renders a colour-coded graph, and flags layering and cycle violations.</p>
|
||||
|
||||
<article class="case">
|
||||
<div class="case-head">
|
||||
<span class="lang ts">TypeScript</span>
|
||||
<h3>Dependency Inversion violation</h3>
|
||||
<span class="hs mid">50 / 100</span>
|
||||
</div>
|
||||
<p class="case-summary">Domain entities import infrastructure directly, and a near-cycle forms between Product and PricingService.</p>
|
||||
<pre class="mermaid">graph TD
|
||||
subgraph API["API Layer"]
|
||||
OrderController; UserController
|
||||
end
|
||||
subgraph Services["Service Layer"]
|
||||
OrderService; PricingService; UserService
|
||||
end
|
||||
subgraph Domain["Domain Layer"]
|
||||
Order; User; Product
|
||||
end
|
||||
subgraph Infra["Infrastructure Layer"]
|
||||
PostgresClient; RedisCache; StripeClient
|
||||
end
|
||||
OrderController --> OrderService
|
||||
UserController --> UserService
|
||||
UserService --> OrderService
|
||||
OrderService --> Order
|
||||
OrderService --> PostgresClient
|
||||
PricingService --> Product
|
||||
PricingService --> StripeClient
|
||||
Order --> PostgresClient
|
||||
User --> RedisCache
|
||||
Product --> PricingService
|
||||
classDef critical fill:#ff6b6b,stroke:#c92a2a,color:#fff
|
||||
classDef warning fill:#ffd43b,stroke:#e67700
|
||||
classDef clean fill:#51cf66,stroke:#2b8a3e,color:#fff
|
||||
class Order,User,Product critical
|
||||
class OrderService,PricingService,UserService warning
|
||||
class OrderController,UserController,PostgresClient,RedisCache,StripeClient clean</pre>
|
||||
<div class="finding">
|
||||
<h4>🔴 Dependency Disorder — Domain layer directly imports infrastructure</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> <code>Order.ts</code> imports <code>PostgresClient</code>; <code>User.ts</code> imports <code>RedisCache</code>.</div>
|
||||
<div class="ln sr"><b>Source:</b> Martin — Clean Architecture — Dependency Inversion Principle</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Define <code>IOrderRepository</code>/<code>IUserRepository</code> in the domain; move infra refs to <code>infra/</code>.</div>
|
||||
</div>
|
||||
<div class="finding">
|
||||
<h4>🔴 Dependency Disorder — Product → PricingService (upward dependency)</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> Near-cycle <code>PricingService → Product → PricingService</code>.</div>
|
||||
<div class="ln sr"><b>Source:</b> Martin — Clean Architecture — Acyclic Dependencies Principle</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Pass pricing as a value object or define <code>IPricingPolicy</code> in the domain.</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="case">
|
||||
<div class="case-head">
|
||||
<span class="lang go">Go</span>
|
||||
<h3>Circular dependency across packages</h3>
|
||||
<span class="hs bad">45 / 100</span>
|
||||
</div>
|
||||
<p class="case-summary"><code>auth → user → notification → auth</code> forms a strongly connected component — Go refuses to compile it.</p>
|
||||
<pre class="mermaid">graph TD
|
||||
subgraph pkg["pkg/"]
|
||||
auth["auth"]; user["user"]; notification["notification"]; billing["billing"]
|
||||
end
|
||||
auth --> user
|
||||
user --> notification
|
||||
notification -.->|circular| auth
|
||||
billing --> user
|
||||
classDef critical fill:#ff6b6b,stroke:#c92a2a,color:#fff
|
||||
classDef warning fill:#ffd43b,stroke:#e67700
|
||||
class auth,user,notification critical
|
||||
class billing warning</pre>
|
||||
<div class="finding">
|
||||
<h4>🔴 Dependency Disorder — Circular dependency auth → user → notification → auth</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> Three packages form a cycle; none compile, test, or deploy independently.</div>
|
||||
<div class="ln sr"><b>Source:</b> Martin — Clean Architecture — Acyclic Dependencies Principle</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Extract interfaces into <code>pkg/contracts</code>; each package implements the interface its consumer defines.</div>
|
||||
</div>
|
||||
<div class="finding">
|
||||
<h4>🟡 Domain Model Distortion — Bounded contexts crossed with no anti-corruption layer</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> Identity, profile, and notification contexts import each other with no translation layer.</div>
|
||||
<div class="ln sr"><b>Source:</b> Evans — DDD — Bounded Context; Anti-Corruption Layer</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Define thin adapters at each context boundary.</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="case">
|
||||
<div class="case-head">
|
||||
<span class="lang java">Java</span>
|
||||
<h3>Textbook Clean Architecture</h3>
|
||||
<span class="hs good">98 / 100</span>
|
||||
</div>
|
||||
<p class="case-summary">Dependencies flow inward, infra implements domain ports, no cycles. brooks-lint reports clean code as clean — and still offers one forward-looking suggestion.</p>
|
||||
<pre class="mermaid">graph TD
|
||||
subgraph API["API Layer"]
|
||||
OrderController; UserController
|
||||
end
|
||||
subgraph Application["Application Layer"]
|
||||
OrderService; UserService
|
||||
end
|
||||
subgraph Domain["Domain Layer"]
|
||||
OrderModel["Order"]; UserModel["User"]; OrderRepository["OrderRepository (interface)"]; UserRepository["UserRepository (interface)"]
|
||||
end
|
||||
subgraph Infra["Infrastructure Layer"]
|
||||
JpaOrderRepository; JpaUserRepository
|
||||
end
|
||||
OrderController --> OrderService
|
||||
UserController --> UserService
|
||||
OrderService --> OrderModel
|
||||
OrderService --> OrderRepository
|
||||
UserService --> UserModel
|
||||
UserService --> UserRepository
|
||||
JpaOrderRepository --> OrderRepository
|
||||
JpaUserRepository --> UserRepository
|
||||
classDef clean fill:#51cf66,stroke:#2b8a3e,color:#fff
|
||||
class OrderController,UserController,OrderService,UserService,OrderModel,UserModel,OrderRepository,UserRepository,JpaOrderRepository,JpaUserRepository clean</pre>
|
||||
<div class="finding">
|
||||
<h4>🟢 Suggestion — Monitor application service growth</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> <code>OrderService</code> and <code>UserService</code> are symmetric siblings that may accrue responsibilities without a split policy.</div>
|
||||
<div class="ln sr"><b>Source:</b> Brooks — The Mythical Man-Month — Conceptual Integrity</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Document a "one service per use-case cluster" rule now, before the pattern calcifies.</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<!-- ============ TECH DEBT ============ -->
|
||||
<section class="mode-group" data-mode="debt">
|
||||
<h2 class="mode-title">Tech Debt Assessment <span class="badge">Mode 3</span></h2>
|
||||
<p class="mode-sub">Classifies debt across the decay risks and scores each finding by Pain × Spread priority.</p>
|
||||
|
||||
<article class="case">
|
||||
<div class="case-head">
|
||||
<span class="lang java">Java</span>
|
||||
<h3>Shotgun Surgery across six files</h3>
|
||||
<span class="hs mid">56 / 100</span>
|
||||
</div>
|
||||
<p class="case-summary">Adding a currency means editing six files in six unrelated layers — three Critical findings that share one root cause.</p>
|
||||
<table class="dbt">
|
||||
<thead><tr><th>Risk</th><th>Findings</th><th>Avg Priority</th><th>Classification</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Change Propagation</td><td>2</td><td>6.5</td><td>Mixed (1 Critical + 1 Scheduled)</td></tr>
|
||||
<tr><td>Knowledge Duplication</td><td>1</td><td>9.0</td><td>Critical</td></tr>
|
||||
<tr><td>Domain Model Distortion</td><td>1</td><td>9.0</td><td>Critical</td></tr>
|
||||
<tr><td>Cognitive Overload</td><td>1</td><td>6.0</td><td>Scheduled</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="finding">
|
||||
<h4>🔴 Change Propagation — Shotgun Surgery across six modules <span class="pri">Pain × Spread: 9</span></h4>
|
||||
<div class="ln sy"><b>Symptom:</b> Adding EUR requires editing 6 files in 6 distinct layers with no architectural relationship.</div>
|
||||
<div class="ln sr"><b>Source:</b> Fowler — Refactoring — Shotgun Surgery; Hunt & Thomas — Orthogonality</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Introduce a <code>Money</code> value object and a <code>MoneyFormatter</code> service.</div>
|
||||
</div>
|
||||
<div class="finding">
|
||||
<h4>🔴 Knowledge Duplication — <code>$</code> as a magic literal in five files <span class="pri">Pain × Spread: 9</span></h4>
|
||||
<div class="ln sy"><b>Symptom:</b> The string <code>"$"</code> appears in 5 independent locations with no shared constant.</div>
|
||||
<div class="ln sr"><b>Source:</b> Hunt & Thomas — DRY; McConnell — Code Complete — Ch. 12</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Use <code>Currency.getSymbol(Locale)</code> in <code>MoneyFormatter</code>; remove all <code>"$"</code> literals.</div>
|
||||
</div>
|
||||
<div class="finding">
|
||||
<h4>🔴 Domain Model Distortion — No <code>Money</code> type exists <span class="pri">Pain × Spread: 9</span></h4>
|
||||
<div class="ln sy"><b>Symptom:</b> All price/amount fields are raw <code>double</code>.</div>
|
||||
<div class="ln sr"><b>Source:</b> Evans — DDD — Domain Model; Fowler — Refactoring — Data Class</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Introduce <code>record Money(BigDecimal amount, Currency currency)</code>.</div>
|
||||
</div>
|
||||
<div class="note"><b>Recommended focus:</b> All three Critical findings share one root cause — the absence of a <code>Money</code> value object. One intervention collapses three findings.</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<!-- ============ TEST QUALITY ============ -->
|
||||
<section class="mode-group" data-mode="test">
|
||||
<h2 class="mode-title">Test Quality Review <span class="badge">Mode 4</span></h2>
|
||||
<p class="mode-sub">Audits an existing suite against six test-space decay risks (T1–T6).</p>
|
||||
|
||||
<article class="case">
|
||||
<div class="case-head">
|
||||
<span class="lang ts">TypeScript</span>
|
||||
<h3>Mock abuse</h3>
|
||||
<span class="hs mid">60 / 100</span>
|
||||
</div>
|
||||
<p class="case-summary">Seven mocks per test, 14 lines of setup vs 6 of assertions — the service is never tested against a real collaborator, and the return value is never checked.</p>
|
||||
<details>
|
||||
<summary>▸ Input test</summary>
|
||||
<pre class="code">it('should place an order successfully', () => {
|
||||
const mockDb = mock<Database>();
|
||||
const mockPayment = mock<PaymentGateway>();
|
||||
const mockInventory = mock<InventoryService>();
|
||||
const mockMailer = mock<MailService>();
|
||||
const mockAudit = mock<AuditLogger>();
|
||||
const mockCache = mock<CacheService>();
|
||||
const mockMetrics = mock<MetricsCollector>();
|
||||
const service = new OrderService(mockDb, mockPayment, mockInventory, mockMailer, mockAudit, mockCache, mockMetrics);
|
||||
const result = await service.placeOrder('1', 'item-1', 2);
|
||||
|
||||
expect(mockPayment.charge).toHaveBeenCalledWith('ch_1', 2000);
|
||||
expect(mockInventory.check).toHaveBeenCalledWith('item-1', 2);
|
||||
expect(mockMailer.send).toHaveBeenCalled();
|
||||
expect(mockAudit.log).toHaveBeenCalledWith('ORDER_PLACED', expect.anything());
|
||||
expect(mockCache.invalidate).toHaveBeenCalledWith('orders:1');
|
||||
expect(mockMetrics.increment).toHaveBeenCalledWith('orders.placed');
|
||||
});</pre>
|
||||
</details>
|
||||
<div class="finding">
|
||||
<h4>🔴 Mock Abuse — Seven mocks per test; setup dominates logic</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> 7 mock objects; 14 lines of setup vs 6 of assertions. No real collaborator is ever exercised.</div>
|
||||
<div class="ln sr"><b>Source:</b> Osherove — The Art of Unit Testing (mock count > 3); Meszaros — xUnit Test Patterns</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Reduce mocks to ≤ 3, use in-memory fakes, assert on <code>result</code> first.</div>
|
||||
</div>
|
||||
<div class="finding">
|
||||
<h4>🔴 Mock Abuse — All six assertions verify mock calls, not behaviour</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> Every assertion is <code>toHaveBeenCalledWith</code>; <code>result</code> is captured but never asserted.</div>
|
||||
<div class="ln cq"><b>Consequence:</b> A <code>placeOrder</code> that calls every mock yet returns <code>null</code> or double-charges still passes.</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Assert observable output: <code>expect(result.status).toBe('confirmed')</code>.</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="case">
|
||||
<div class="case-head">
|
||||
<span class="lang py">Python</span>
|
||||
<h3>Inverted test pyramid</h3>
|
||||
<span class="hs mid">55 / 100</span>
|
||||
</div>
|
||||
<p class="case-summary">Only 16% of tests are unit tests; an E2E-heavy suite takes ~9 minutes and blocks fast CI feedback.</p>
|
||||
<details>
|
||||
<summary>▸ Suite overview</summary>
|
||||
<pre class="code">tests/
|
||||
├── e2e/ 47 tests, avg 8s each (~6 min)
|
||||
├── integration/ 83 tests, avg 2s each (~3 min)
|
||||
└── unit/ 24 tests, avg 10ms each (~0.2s)
|
||||
|
||||
Total: 154 tests, ~9 min
|
||||
Actual ratio: Unit 16% : Integration 54% : E2E 30%
|
||||
Target ratio: Unit 70% : Integration 20% : E2E 10%</pre>
|
||||
</details>
|
||||
<div class="finding">
|
||||
<h4>🔴 Architecture Mismatch — Fully inverted test pyramid</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> Only 24 of 154 tests (16%) are unit tests; E2E + integration = 84%.</div>
|
||||
<div class="ln sr"><b>Source:</b> Google — How Google Tests Software — 70:20:10; Meszaros — xUnit Test Patterns</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Target 70% unit; reduce E2E to 5–8 critical smoke tests.</div>
|
||||
</div>
|
||||
<div class="finding">
|
||||
<h4>🔴 Architecture Mismatch — 9-minute suite blocks CI fast-feedback</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> Full suite ~542s, dominated by 8s E2E tests.</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Split CI: (1) unit only, < 60s, blocks merge; (2) integration + E2E async, non-blocking.</div>
|
||||
</div>
|
||||
<div class="finding">
|
||||
<h4>🟡 Coverage Illusion — Core domain untested at unit level</h4>
|
||||
<div class="ln sy"><b>Symptom:</b> <code>tests/unit/</code> covers only validators and formatters — no checkout, login, order, or payment.</div>
|
||||
<div class="ln sr"><b>Source:</b> Feathers — Working Effectively with Legacy Code</div>
|
||||
<div class="ln rx"><b>Remedy:</b> Start with <code>test_checkout_flow.py</code> and <code>test_payment_api.py</code>.</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="foot-in">
|
||||
<div class="foot-links">
|
||||
<a href="index.html">Home</a>
|
||||
<a href="https://github.com/hyhmrright/brooks-lint" target="_blank" rel="noopener">GitHub</a>
|
||||
<a href="https://github.com/hyhmrright/brooks-lint/blob/main/docs/gallery.md" target="_blank" rel="noopener">Gallery source (Markdown)</a>
|
||||
</div>
|
||||
<div class="foot-note">MIT License — Built by hyhmrright</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
mermaid.initialize({
|
||||
startOnLoad: true,
|
||||
theme: 'base',
|
||||
themeVariables: {
|
||||
fontFamily: 'ui-monospace, Menlo, monospace',
|
||||
primaryColor: '#fffdf8', primaryBorderColor: '#e3d8c2', primaryTextColor: '#2a2520',
|
||||
lineColor: '#9c8f74', clusterBkg: '#f3ead8', clusterBorder: '#e3d8c2'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
(function () {
|
||||
var btns = document.querySelectorAll('#filters button');
|
||||
var groups = document.querySelectorAll('.mode-group');
|
||||
btns.forEach(function (b) {
|
||||
b.addEventListener('click', function () {
|
||||
btns.forEach(function (x) { x.classList.remove('active'); });
|
||||
b.classList.add('active');
|
||||
var f = b.getAttribute('data-filter');
|
||||
groups.forEach(function (g) {
|
||||
g.classList.toggle('hidden', f !== 'all' && g.getAttribute('data-mode') !== f);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 305 KiB |
@@ -0,0 +1,549 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="google-site-verification" content="899Jw4jb5ux928e2-cYHEoRy70zaMqWIXL-7YK1Nd2U" />
|
||||
<title>brooks-lint — AI code reviews grounded in twelve classic engineering books</title>
|
||||
<meta name="description" content="brooks-lint diagnoses your code against twelve classic software engineering books, producing structured Symptom → Source → Consequence → Remedy findings every time.">
|
||||
<link rel="icon" type="image/svg+xml" href="logo.svg">
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="brooks-lint">
|
||||
<meta property="og:description" content="AI code reviews grounded in twelve classic engineering books. Consistent. Traceable. Actionable.">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://hyhmrright.github.io/brooks-lint/">
|
||||
<meta property="og:image" content="https://hyhmrright.github.io/brooks-lint/hero.png">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="brooks-lint — AI code reviews grounded in twelve engineering classics">
|
||||
<meta name="twitter:description" content="Decay-risk diagnostics with book citations, severity labels, and a 0–100 Health Score. Consistent. Traceable. Actionable.">
|
||||
<meta name="twitter:image" content="https://hyhmrright.github.io/brooks-lint/hero.png">
|
||||
<link rel="canonical" href="https://hyhmrright.github.io/brooks-lint/">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "brooks-lint",
|
||||
"applicationCategory": "DeveloperApplication",
|
||||
"operatingSystem": "Claude Code, Gemini CLI, Codex CLI, GitHub Actions",
|
||||
"description": "AI code reviews grounded in twelve classic software engineering books. Decay-risk diagnostics with book citations, severity labels, and six analysis modes including full-sweep auto-fix.",
|
||||
"url": "https://hyhmrright.github.io/brooks-lint/",
|
||||
"image": "https://hyhmrright.github.io/brooks-lint/hero.png",
|
||||
"softwareVersion": "1.3.0",
|
||||
"license": "https://github.com/hyhmrright/brooks-lint/blob/main/LICENSE",
|
||||
"codeRepository": "https://github.com/hyhmrright/brooks-lint",
|
||||
"keywords": "AI code review, code quality, tech debt, architecture audit, test quality, Claude Code plugin, refactoring, clean architecture",
|
||||
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" }
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
:root {
|
||||
--paper: #f7f1e4;
|
||||
--paper-2: #fbf6ec;
|
||||
--card: #fffdf8;
|
||||
--ink: #2a2520;
|
||||
--ink-soft: #5c5347;
|
||||
--rule: #e3d8c2;
|
||||
--rule-soft: #ece2cf;
|
||||
--blue: #2d4a7c;
|
||||
--blue-soft: #e7edf5;
|
||||
--green: #2b8a3e;
|
||||
--gold: #b07d2b;
|
||||
--red: #b23b3b;
|
||||
--ink-dark: #211d18;
|
||||
--serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
|
||||
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Roboto, Helvetica, Arial, sans-serif;
|
||||
--mono: "SF Mono", "JetBrains Mono", "Fira Code", Menlo, Consolas, monospace;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
color: var(--ink);
|
||||
background: var(--paper);
|
||||
line-height: 1.65;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
.wrap { max-width: 1080px; margin: 0 auto; padding: 0 24px; }
|
||||
h1, h2, h3, .serif { font-family: var(--serif); font-weight: 700; letter-spacing: -0.01em; }
|
||||
a { color: var(--blue); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
/* paper grain overlay */
|
||||
body::before {
|
||||
content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 0;
|
||||
background-image: radial-gradient(rgba(120,90,40,0.04) 1px, transparent 1px);
|
||||
background-size: 4px 4px; opacity: .6;
|
||||
}
|
||||
main, nav, footer { position: relative; z-index: 1; }
|
||||
|
||||
/* ---- Nav ---- */
|
||||
nav {
|
||||
position: sticky; top: 0; z-index: 50;
|
||||
background: rgba(247,241,228,0.85);
|
||||
backdrop-filter: saturate(140%) blur(10px);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
.nav-in { display: flex; align-items: center; gap: 20px; height: 64px; }
|
||||
.brand { display: flex; align-items: center; gap: 10px; font-family: var(--serif); font-weight: 700; font-size: 19px; color: var(--ink); }
|
||||
.brand img { width: 30px; height: 30px; border-radius: 7px; }
|
||||
.nav-links { display: flex; gap: 22px; margin-left: auto; align-items: center; }
|
||||
.nav-links a { color: var(--ink-soft); font-size: 14.5px; }
|
||||
.nav-links a:hover { color: var(--ink); text-decoration: none; }
|
||||
.lang-toggle {
|
||||
display: inline-flex; border: 1px solid var(--rule); border-radius: 999px; overflow: hidden;
|
||||
font-size: 13px; background: var(--card);
|
||||
}
|
||||
.lang-toggle button {
|
||||
border: none; background: transparent; padding: 5px 12px; cursor: pointer;
|
||||
color: var(--ink-soft); font-family: var(--sans); font-weight: 600;
|
||||
}
|
||||
.lang-toggle button.active { background: var(--ink); color: var(--paper-2); }
|
||||
.btn {
|
||||
display: inline-block; padding: 10px 20px; border-radius: 8px; font-weight: 600; font-size: 15px;
|
||||
border: 1px solid transparent; cursor: pointer; transition: transform .08s ease, box-shadow .15s ease;
|
||||
}
|
||||
.btn:hover { text-decoration: none; transform: translateY(-1px); }
|
||||
.btn-primary { background: var(--ink); color: var(--paper-2); box-shadow: 0 2px 0 var(--gold); }
|
||||
.btn-ghost { background: var(--card); color: var(--ink); border-color: var(--rule); }
|
||||
@media (max-width: 720px) { .nav-links a.hide-sm { display: none; } }
|
||||
|
||||
/* ---- Hero ---- */
|
||||
.hero { padding: 76px 0 56px; text-align: center; }
|
||||
.eyebrow {
|
||||
display: inline-block; font-family: var(--mono); font-size: 12.5px; letter-spacing: .12em;
|
||||
text-transform: uppercase; color: var(--gold); border: 1px solid var(--rule);
|
||||
padding: 5px 12px; border-radius: 999px; margin-bottom: 22px; background: var(--card);
|
||||
}
|
||||
.hero h1 { font-size: clamp(34px, 5.4vw, 56px); line-height: 1.08; max-width: 18ch; margin: 0 auto 18px; }
|
||||
.hero .lede { font-size: clamp(17px, 2.2vw, 20px); color: var(--ink-soft); max-width: 60ch; margin: 0 auto 14px; }
|
||||
.quote {
|
||||
max-width: 56ch; margin: 26px auto 8px; font-family: var(--serif); font-style: italic;
|
||||
font-size: 18px; color: var(--ink); border-left: 3px solid var(--gold); padding-left: 18px; text-align: left;
|
||||
}
|
||||
.quote cite { display: block; font-style: normal; font-size: 14px; color: var(--ink-soft); margin-top: 6px; }
|
||||
.cta-row { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; margin-top: 30px; }
|
||||
.hero-shot { margin: 46px auto 0; max-width: 900px; }
|
||||
.hero-shot img { width: 100%; height: auto; border-radius: 14px; border: 1px solid var(--rule);
|
||||
box-shadow: 0 18px 50px rgba(80,55,15,.16); display: block; }
|
||||
|
||||
/* ---- Stat strip ---- */
|
||||
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--rule);
|
||||
border: 1px solid var(--rule); border-radius: 12px; overflow: hidden; margin-top: 52px; }
|
||||
.stat { background: var(--card); padding: 22px 14px; text-align: center; }
|
||||
.stat b { display: block; font-family: var(--serif); font-size: clamp(26px, 4vw, 36px); color: var(--blue); line-height: 1; }
|
||||
.stat span { font-size: 13px; color: var(--ink-soft); display: block; margin-top: 8px; }
|
||||
@media (max-width: 640px) { .stats { grid-template-columns: repeat(2, 1fr); } }
|
||||
|
||||
/* ---- Section scaffolding ---- */
|
||||
section { padding: 64px 0; }
|
||||
.section-head { text-align: center; max-width: 64ch; margin: 0 auto 44px; }
|
||||
.section-head h2 { font-size: clamp(26px, 3.6vw, 38px); margin-bottom: 12px; }
|
||||
.section-head p { color: var(--ink-soft); font-size: 17px; }
|
||||
.alt { background: var(--paper-2); border-top: 1px solid var(--rule-soft); border-bottom: 1px solid var(--rule-soft); }
|
||||
|
||||
/* ---- Feature grid ---- */
|
||||
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; }
|
||||
@media (max-width: 900px) { .grid-4 { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 520px) { .grid-4 { grid-template-columns: 1fr; } }
|
||||
.feature { background: var(--card); border: 1px solid var(--rule); border-radius: 12px; padding: 24px; }
|
||||
.feature .ic { font-size: 26px; margin-bottom: 12px; display: block; }
|
||||
.feature h3 { font-size: 18px; margin-bottom: 8px; }
|
||||
.feature p { font-size: 14.5px; color: var(--ink-soft); }
|
||||
|
||||
/* ---- Decay risks ---- */
|
||||
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
|
||||
@media (max-width: 860px) { .grid-3 { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 540px) { .grid-3 { grid-template-columns: 1fr; } }
|
||||
.risk { background: var(--card); border: 1px solid var(--rule); border-left: 4px solid var(--blue);
|
||||
border-radius: 10px; padding: 20px; }
|
||||
.risk .code { font-family: var(--mono); font-size: 12px; color: var(--gold); font-weight: 700; letter-spacing: .06em; }
|
||||
.risk h3 { font-size: 17px; margin: 4px 0 8px; }
|
||||
.risk p { font-size: 14px; color: var(--ink-soft); }
|
||||
.risk .src { font-size: 12px; color: var(--ink-soft); margin-top: 12px; font-style: italic; opacity: .85; }
|
||||
|
||||
/* ---- Sample finding ---- */
|
||||
.sample { background: var(--ink-dark); border-radius: 14px; padding: 30px; color: #e9e2d3; font-size: 15px; }
|
||||
.sample .score { font-family: var(--serif); font-size: 22px; color: #fff; margin-bottom: 6px; }
|
||||
.sample .score b { color: var(--red); }
|
||||
.sample .summary { color: #b9b0a0; font-style: italic; font-size: 14.5px; margin-bottom: 22px; }
|
||||
.finding { border-top: 1px solid #3a342c; padding-top: 18px; margin-top: 18px; }
|
||||
.finding h4 { font-family: var(--serif); font-size: 16px; color: #fff; margin-bottom: 10px; }
|
||||
.finding .dot { color: var(--red); }
|
||||
.finding .row { font-size: 14px; margin-bottom: 7px; line-height: 1.55; }
|
||||
.finding .row b { color: var(--gold); font-weight: 700; }
|
||||
.finding .row.src b { color: #7fa8e0; }
|
||||
.finding .row.fix b { color: #6fcf83; }
|
||||
|
||||
/* ---- Benchmark table ---- */
|
||||
.table-wrap { overflow-x: auto; border: 1px solid var(--rule); border-radius: 12px; background: var(--card); }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 15px; min-width: 460px; }
|
||||
th, td { padding: 13px 18px; text-align: left; border-bottom: 1px solid var(--rule-soft); }
|
||||
th { font-family: var(--serif); font-size: 15px; }
|
||||
td.c, th.c { text-align: center; }
|
||||
tr:last-child td { border-bottom: none; }
|
||||
tbody tr:last-child { font-weight: 700; background: var(--blue-soft); }
|
||||
.yes { color: var(--green); font-weight: 700; }
|
||||
.no { color: var(--red); font-weight: 700; }
|
||||
|
||||
/* ---- Books ---- */
|
||||
.books { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; max-width: 880px; margin: 0 auto; }
|
||||
.book-chip { background: var(--card); border: 1px solid var(--rule); border-radius: 8px;
|
||||
padding: 9px 14px; font-size: 14px; }
|
||||
.book-chip b { font-family: var(--serif); }
|
||||
.book-chip span { color: var(--ink-soft); font-size: 12.5px; }
|
||||
|
||||
/* ---- Install ---- */
|
||||
.code-block { position: relative; background: var(--ink-dark); border-radius: 12px; padding: 22px 22px;
|
||||
font-family: var(--mono); font-size: 14px; color: #e9e2d3; overflow-x: auto; margin-bottom: 16px; }
|
||||
.code-block .cmt { color: #8a8170; }
|
||||
.code-block .tok { color: #6fcf83; }
|
||||
.copy-btn { position: absolute; top: 12px; right: 12px; background: #3a342c; color: #cfc7b6;
|
||||
border: none; border-radius: 6px; padding: 5px 11px; font-size: 12px; cursor: pointer; font-family: var(--sans); }
|
||||
.copy-btn:hover { background: #4a4338; }
|
||||
.install-grid { display: grid; grid-template-columns: 1fr; gap: 0; max-width: 760px; margin: 0 auto; }
|
||||
.install-label { font-family: var(--serif); font-size: 18px; margin: 18px 0 10px; }
|
||||
|
||||
/* ---- Footer ---- */
|
||||
footer { border-top: 1px solid var(--rule); padding: 40px 0 56px; background: var(--paper-2); }
|
||||
.foot-in { display: flex; flex-wrap: wrap; gap: 18px; align-items: center; justify-content: space-between; }
|
||||
.foot-links { display: flex; gap: 20px; flex-wrap: wrap; }
|
||||
.foot-links a { color: var(--ink-soft); font-size: 14px; }
|
||||
.foot-note { font-size: 13px; color: var(--ink-soft); }
|
||||
|
||||
[data-lang-hide] { display: none !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<div class="wrap nav-in">
|
||||
<a class="brand" href="#top"><img src="logo.svg" alt="brooks-lint logo">brooks-lint</a>
|
||||
<div class="nav-links">
|
||||
<a class="hide-sm" href="#risks" data-en="Decay Risks" data-zh="腐化风险">Decay Risks</a>
|
||||
<a class="hide-sm" href="#sample" data-en="Example" data-zh="示例">Example</a>
|
||||
<a class="hide-sm" href="#benchmark" data-en="Benchmark" data-zh="基准">Benchmark</a>
|
||||
<a class="hide-sm" href="gallery.html" data-en="Gallery" data-zh="画廊">Gallery</a>
|
||||
<a class="hide-sm" href="#install" data-en="Install" data-zh="安装">Install</a>
|
||||
<span class="lang-toggle">
|
||||
<button data-set-lang="en">EN</button>
|
||||
<button data-set-lang="zh">中文</button>
|
||||
</span>
|
||||
<a href="https://github.com/hyhmrright/brooks-lint" target="_blank" rel="noopener">GitHub ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main id="top">
|
||||
|
||||
<!-- HERO -->
|
||||
<header class="hero">
|
||||
<div class="wrap">
|
||||
<span class="eyebrow" data-en="Claude Code · Codex · Gemini plugin" data-zh="Claude Code · Codex · Gemini 插件">Claude Code · Codex · Gemini plugin</span>
|
||||
<h1 data-en="AI code reviews grounded in twelve classic engineering books"
|
||||
data-zh="扎根于十二本经典工程著作的 AI 代码审查">AI code reviews grounded in twelve classic engineering books</h1>
|
||||
<p class="lede"
|
||||
data-en="Most tools count lines and complexity. brooks-lint diagnoses your code against twelve decay risks synthesized from the classics — every finding cited, scored, and remedied."
|
||||
data-zh="多数工具只统计行数和复杂度。brooks-lint 把你的代码对照从十二本经典提炼的腐化风险逐一诊断——每条结论都有出处、有评分、有修复方案。">
|
||||
Most tools count lines and complexity. brooks-lint diagnoses your code against twelve decay risks synthesized from the classics — every finding cited, scored, and remedied.
|
||||
</p>
|
||||
<blockquote class="quote"
|
||||
data-en="“The bearing of a child takes nine months, no matter how many women are assigned.”"
|
||||
data-zh="“一个孩子的孕育需要九个月,无论你派多少人去。”">
|
||||
“The bearing of a child takes nine months, no matter how many women are assigned.”
|
||||
<cite data-en="— Frederick Brooks, The Mythical Man-Month (1975)" data-zh="—— 弗雷德里克·布鲁克斯,《人月神话》(1975)">— Frederick Brooks, The Mythical Man-Month (1975)</cite>
|
||||
</blockquote>
|
||||
<div class="cta-row">
|
||||
<a class="btn btn-primary" href="#install" data-en="Get Started" data-zh="开始使用">Get Started</a>
|
||||
<a class="btn btn-ghost" href="https://github.com/hyhmrright/brooks-lint" target="_blank" rel="noopener" data-en="View on GitHub" data-zh="GitHub 查看">View on GitHub</a>
|
||||
</div>
|
||||
|
||||
<div class="hero-shot">
|
||||
<img src="demo.gif" alt="brooks-lint reviewing code: a /brooks-review command produces a 28/100 health score and cited Symptom → Source → Consequence → Remedy findings" loading="eager">
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat"><b>12</b><span data-en="classic books" data-zh="本经典著作">classic books</span></div>
|
||||
<div class="stat"><b>12</b><span data-en="decay risks (R1–R6 · T1–T6)" data-zh="腐化风险 (R1–R6 · T1–T6)">decay risks (R1–R6 · T1–T6)</span></div>
|
||||
<div class="stat"><b>94%</b><span data-en="benchmark pass rate" data-zh="基准通过率">benchmark pass rate</span></div>
|
||||
<div class="stat"><b>6</b><span data-en="independent skills" data-zh="个独立技能">independent skills</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- FEATURES -->
|
||||
<section>
|
||||
<div class="wrap">
|
||||
<div class="section-head">
|
||||
<h2 data-en="Not a linter. A second opinion from the canon." data-zh="不是 linter,是来自经典的第二意见。">Not a linter. A second opinion from the canon.</h2>
|
||||
<p data-en="Linters catch syntax. brooks-lint catches architectural drift, knowledge silos, and domain distortion — the slow problems that cost teams months."
|
||||
data-zh="linter 抓语法。brooks-lint 抓架构漂移、知识孤岛和领域失真——那些拖垮团队数月却无人察觉的慢性病。">Linters catch syntax. brooks-lint catches architectural drift, knowledge silos, and domain distortion — the slow problems that cost teams months.</p>
|
||||
</div>
|
||||
<div class="grid-4">
|
||||
<div class="feature">
|
||||
<span class="ic">⚖️</span>
|
||||
<h3 data-en="The Iron Law" data-zh="铁律格式">The Iron Law</h3>
|
||||
<p data-en="Every finding follows one shape: Symptom → Source → Consequence → Remedy. No vague vibes, ever."
|
||||
data-zh="每条结论遵循同一形态:症状 → 根源 → 后果 → 修复。绝不含糊其辞。">Every finding follows one shape: Symptom → Source → Consequence → Remedy. No vague vibes, ever.</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<span class="ic">📚</span>
|
||||
<h3 data-en="Cited to the books" data-zh="书有出处">Cited to the books</h3>
|
||||
<p data-en="Brooks, Fowler, Martin, Ousterhout, Evans, Feathers, Meszaros and more — each finding names the author and principle."
|
||||
data-zh="布鲁克斯、Fowler、Martin、Ousterhout、Evans、Feathers、Meszaros 等——每条结论都点名作者与原则。">Brooks, Fowler, Martin, Ousterhout, Evans, Feathers, Meszaros and more — each finding names the author and principle.</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<span class="ic">🎯</span>
|
||||
<h3 data-en="Six focused skills" data-zh="六个专注技能">Six focused skills</h3>
|
||||
<p data-en="PR Review, Architecture Audit, Tech Debt, Test Quality, Health Dashboard, and a Full Sweep that auto-fixes."
|
||||
data-zh="PR 审查、架构审计、技术债、测试质量、健康仪表盘,以及会自动修复的全面扫描。">PR Review, Architecture Audit, Tech Debt, Test Quality, Health Dashboard, and a Full Sweep that auto-fixes.</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<span class="ic">🔌</span>
|
||||
<h3 data-en="Zero config, any language" data-zh="零配置·任意语言">Zero config, any language</h3>
|
||||
<p data-en="Works in Claude Code, Codex CLI, and Gemini CLI. No plugins to wire up, no language limits."
|
||||
data-zh="支持 Claude Code、Codex CLI、Gemini CLI。无需接线,不限语言。">Works in Claude Code, Codex CLI, and Gemini CLI. No plugins to wire up, no language limits.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- DECAY RISKS -->
|
||||
<section id="risks" class="alt">
|
||||
<div class="wrap">
|
||||
<div class="section-head">
|
||||
<h2 data-en="The Six Production Decay Risks" data-zh="六大生产代码腐化风险">The Six Production Decay Risks</h2>
|
||||
<p data-en="Synthesized from the twelve books. Six more (T1–T6) cover test-suite decay."
|
||||
data-zh="提炼自十二本经典。另有六项(T1–T6)覆盖测试套件的腐化。">Synthesized from the twelve books. Six more (T1–T6) cover test-suite decay.</p>
|
||||
</div>
|
||||
<div class="grid-3">
|
||||
<div class="risk">
|
||||
<span class="code">R1</span>
|
||||
<h3>🧠 <span data-en="Cognitive Overload" data-zh="认知过载">Cognitive Overload</span></h3>
|
||||
<p data-en="How much mental effort does it take to understand this?" data-zh="理解这段代码要耗费多少脑力?">How much mental effort does it take to understand this?</p>
|
||||
<div class="src">Code Complete · Refactoring · DDD · Philosophy of SD</div>
|
||||
</div>
|
||||
<div class="risk">
|
||||
<span class="code">R2</span>
|
||||
<h3>🔗 <span data-en="Change Propagation" data-zh="变更扩散">Change Propagation</span></h3>
|
||||
<p data-en="How many unrelated things break on one change?" data-zh="改一处会牵连多少无关之物崩坏?">How many unrelated things break on one change?</p>
|
||||
<div class="src">Refactoring · Clean Architecture · Pragmatic · SE@Google</div>
|
||||
</div>
|
||||
<div class="risk">
|
||||
<span class="code">R3</span>
|
||||
<h3>📋 <span data-en="Knowledge Duplication" data-zh="知识重复">Knowledge Duplication</span></h3>
|
||||
<p data-en="Is the same decision expressed in multiple places?" data-zh="同一个决策是否散落在多处表达?">Is the same decision expressed in multiple places?</p>
|
||||
<div class="src">Pragmatic · Refactoring · DDD</div>
|
||||
</div>
|
||||
<div class="risk">
|
||||
<span class="code">R4</span>
|
||||
<h3>🌀 <span data-en="Accidental Complexity" data-zh="偶发复杂度">Accidental Complexity</span></h3>
|
||||
<p data-en="Is the code more complex than the problem itself?" data-zh="代码是否比问题本身更复杂?">Is the code more complex than the problem itself?</p>
|
||||
<div class="src">Refactoring · Code Complete · Brooks · Philosophy of SD</div>
|
||||
</div>
|
||||
<div class="risk">
|
||||
<span class="code">R5</span>
|
||||
<h3>🏗️ <span data-en="Dependency Disorder" data-zh="依赖失序">Dependency Disorder</span></h3>
|
||||
<p data-en="Do dependencies flow in a consistent direction?" data-zh="依赖是否朝一致的方向流动?">Do dependencies flow in a consistent direction?</p>
|
||||
<div class="src">Clean Architecture · Brooks · Pragmatic · SE@Google</div>
|
||||
</div>
|
||||
<div class="risk">
|
||||
<span class="code">R6</span>
|
||||
<h3>🗺️ <span data-en="Domain Model Distortion" data-zh="领域模型失真">Domain Model Distortion</span></h3>
|
||||
<p data-en="Does the code faithfully represent the domain?" data-zh="代码是否忠实地表达了业务领域?">Does the code faithfully represent the domain?</p>
|
||||
<div class="src">DDD · Refactoring</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SAMPLE -->
|
||||
<section id="sample">
|
||||
<div class="wrap">
|
||||
<div class="section-head">
|
||||
<h2 data-en="What a finding looks like" data-zh="一条结论长什么样">What a finding looks like</h2>
|
||||
<p data-en="Same messy method, two of the eight findings brooks-lint produces — each one cited and actionable."
|
||||
data-zh="同一段混乱方法,brooks-lint 给出的八条结论中的两条——每条都有出处、可执行。">Same messy method, two of the eight findings brooks-lint produces — each one cited and actionable.</p>
|
||||
</div>
|
||||
<div class="sample">
|
||||
<div class="score" data-en="Health Score: <b>28/100</b>" data-zh="健康分:<b>28/100</b>">Health Score: <b>28/100</b></div>
|
||||
<div class="summary"
|
||||
data-en="This method concentrates four unrelated business responsibilities, hides a logic bug that silently suppresses email notifications, and is wide open to SQL injection."
|
||||
data-zh="这个方法把四项互不相关的业务职责堆在一起,藏着一个让邮件通知静默失效的逻辑 bug,并且对 SQL 注入门户大开。">
|
||||
This method concentrates four unrelated business responsibilities, hides a logic bug that silently suppresses email notifications, and is wide open to SQL injection.
|
||||
</div>
|
||||
|
||||
<div class="finding">
|
||||
<h4><span class="dot">🔴</span> R2 — <span data-en="One method changes for four unrelated reasons" data-zh="一个方法因四个无关原因而改动">One method changes for four unrelated reasons</span></h4>
|
||||
<div class="row"><b data-en="Symptom:" data-zh="症状:">Symptom:</b>
|
||||
<span data-en="update_profile does field updates, email notifications, loyalty recalculation, and cache invalidation in one body."
|
||||
data-zh="update_profile 在一个方法体里同时做字段更新、邮件通知、积分重算和缓存失效。">update_profile does field updates, email notifications, loyalty recalculation, and cache invalidation in one body.</span></div>
|
||||
<div class="row src"><b data-en="Source:" data-zh="根源:">Source:</b>
|
||||
<span data-en="Fowler — Refactoring — Divergent Change; Hunt & Thomas — Orthogonality"
|
||||
data-zh="Fowler《重构》——发散式变化;Hunt & Thomas——正交性">Fowler — Refactoring — Divergent Change; Hunt & Thomas — Orthogonality</span></div>
|
||||
<div class="row"><b data-en="Consequence:" data-zh="后果:">Consequence:</b>
|
||||
<span data-en="A change to the loyalty formula risks breaking email notifications. Every edit carries regression risk across four domains."
|
||||
data-zh="改一下积分公式就可能弄坏邮件通知。每次编辑都在四个领域同时承担回归风险。">A change to the loyalty formula risks breaking email notifications. Every edit carries regression risk across four domains.</span></div>
|
||||
<div class="row fix"><b data-en="Remedy:" data-zh="修复:">Remedy:</b>
|
||||
<span data-en="Extract NotificationService, LoyaltyService, UserCacheInvalidator. update_profile should orchestrate, not implement."
|
||||
data-zh="抽出 NotificationService、LoyaltyService、UserCacheInvalidator。update_profile 应当编排而非实现。">Extract NotificationService, LoyaltyService, UserCacheInvalidator. update_profile should orchestrate, not implement.</span></div>
|
||||
</div>
|
||||
|
||||
<div class="finding">
|
||||
<h4><span class="dot">🔴</span> R6 — <span data-en="Silent logic bug: notification never fires" data-zh="静默逻辑 bug:通知永不触发">Silent logic bug: notification never fires</span></h4>
|
||||
<div class="row"><b data-en="Symptom:" data-zh="症状:">Symptom:</b>
|
||||
<span data-en="user['email'] = email runs before if user['email'] != email — the condition is always False, the code is dead."
|
||||
data-zh="user['email'] = email 在 if user['email'] != email 之前执行——条件永远为 False,该分支是死代码。">user['email'] = email runs before if user['email'] != email — the condition is always False, the code is dead.</span></div>
|
||||
<div class="row src"><b data-en="Source:" data-zh="根源:">Source:</b>
|
||||
<span data-en="McConnell — Code Complete — Ch. 17: Unusual Control Structures"
|
||||
data-zh="McConnell《代码大全》——第 17 章:非常规控制结构">McConnell — Code Complete — Ch. 17: Unusual Control Structures</span></div>
|
||||
<div class="row"><b data-en="Consequence:" data-zh="后果:">Consequence:</b>
|
||||
<span data-en="Users are never notified when their email changes. A business rule is silently violated while the system looks fine."
|
||||
data-zh="用户改邮箱时永远收不到通知。系统看似正常,业务规则却被悄悄违反。">Users are never notified when their email changes. A business rule is silently violated while the system looks fine.</span></div>
|
||||
<div class="row fix"><b data-en="Remedy:" data-zh="修复:">Remedy:</b>
|
||||
<span data-en="Capture old_email before any mutation. Compare against old_email, not the already-overwritten value."
|
||||
data-zh="在任何修改前先捕获 old_email,与 old_email 比较,而非已被覆盖的值。">Capture old_email before any mutation. Compare against old_email, not the already-overwritten value.</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- BENCHMARK -->
|
||||
<section id="benchmark" class="alt">
|
||||
<div class="wrap">
|
||||
<div class="section-head">
|
||||
<h2 data-en="Consistency is the point" data-zh="一致性才是重点">Consistency is the point</h2>
|
||||
<p data-en="Tested across PR review, architecture audit, and tech debt scenarios. The gap isn't what Claude can find — it's what it finds every single time, with evidence."
|
||||
data-zh="在 PR 审查、架构审计、技术债三类场景上测试。差距不在于 Claude 能找到什么,而在于它能否每一次都带着证据找到。">Tested across PR review, architecture audit, and tech debt scenarios. The gap isn't what Claude can find — it's what it finds every single time, with evidence.</p>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-en="Criterion" data-zh="评判标准">Criterion</th>
|
||||
<th class="c">brooks-lint</th>
|
||||
<th class="c" data-en="Claude alone" data-zh="纯 Claude">Claude alone</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td data-en="Structured Symptom→Source→Consequence→Remedy" data-zh="结构化 症状→根源→后果→修复">Structured Symptom→Source→Consequence→Remedy</td><td class="c yes">100%</td><td class="c no">0%</td></tr>
|
||||
<tr><td data-en="Book citation per finding" data-zh="每条结论附书目出处">Book citation per finding</td><td class="c yes">100%</td><td class="c no">0%</td></tr>
|
||||
<tr><td data-en="Consistent severity labels 🔴🟡🟢" data-zh="一致的严重度标签 🔴🟡🟢">Consistent severity labels 🔴🟡🟢</td><td class="c yes">100%</td><td class="c no">0%</td></tr>
|
||||
<tr><td data-en="Health Score (0–100)" data-zh="健康分(0–100)">Health Score (0–100)</td><td class="c yes">100%</td><td class="c no">0%</td></tr>
|
||||
<tr><td data-en="Overall pass rate" data-zh="总体通过率">Overall pass rate</td><td class="c">94%</td><td class="c">16%</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- BOOKS -->
|
||||
<section>
|
||||
<div class="wrap">
|
||||
<div class="section-head">
|
||||
<h2 data-en="Standing on twelve giants" data-zh="站在十二位巨人的肩上">Standing on twelve giants</h2>
|
||||
<p data-en="The decay risks are our synthesis of their ideas, applied to modern code quality."
|
||||
data-zh="这些腐化风险是我们对他们思想的综合,应用于现代代码质量评估。">The decay risks are our synthesis of their ideas, applied to modern code quality.</p>
|
||||
</div>
|
||||
<div class="books">
|
||||
<span class="book-chip"><b>The Mythical Man-Month</b> · <span>Brooks</span></span>
|
||||
<span class="book-chip"><b>Code Complete</b> · <span>McConnell</span></span>
|
||||
<span class="book-chip"><b>Refactoring</b> · <span>Fowler</span></span>
|
||||
<span class="book-chip"><b>Clean Architecture</b> · <span>Martin</span></span>
|
||||
<span class="book-chip"><b>The Pragmatic Programmer</b> · <span>Hunt & Thomas</span></span>
|
||||
<span class="book-chip"><b>Domain-Driven Design</b> · <span>Evans</span></span>
|
||||
<span class="book-chip"><b>A Philosophy of Software Design</b> · <span>Ousterhout</span></span>
|
||||
<span class="book-chip"><b>Software Engineering at Google</b> · <span>Winters et al.</span></span>
|
||||
<span class="book-chip"><b>The Art of Unit Testing</b> · <span>Osherove</span></span>
|
||||
<span class="book-chip"><b>How Google Tests Software</b> · <span>Whittaker et al.</span></span>
|
||||
<span class="book-chip"><b>Working Effectively with Legacy Code</b> · <span>Feathers</span></span>
|
||||
<span class="book-chip"><b>xUnit Test Patterns</b> · <span>Meszaros</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- INSTALL -->
|
||||
<section id="install" class="alt">
|
||||
<div class="wrap">
|
||||
<div class="section-head">
|
||||
<h2 data-en="Get started in seconds" data-zh="几秒钟即可上手">Get started in seconds</h2>
|
||||
<p data-en="Pick your tool. Then just ask it to review your code, audit your architecture, or assess tech debt."
|
||||
data-zh="选择你的工具,然后让它审查代码、审计架构或评估技术债即可。">Pick your tool. Then just ask it to review your code, audit your architecture, or assess tech debt.</p>
|
||||
</div>
|
||||
<div class="install-grid">
|
||||
<div class="install-label">Claude Code</div>
|
||||
<div class="code-block">
|
||||
<button class="copy-btn" data-copy="/plugin marketplace add hyhmrright/brooks-lint
|
||||
/plugin install brooks-lint@brooks-lint-marketplace">Copy</button><span class="cmt"># add the marketplace, then install</span>
|
||||
<span class="tok">/plugin</span> marketplace add hyhmrright/brooks-lint
|
||||
<span class="tok">/plugin</span> install brooks-lint@brooks-lint-marketplace</div>
|
||||
|
||||
<div class="install-label">Gemini CLI</div>
|
||||
<div class="code-block">
|
||||
<button class="copy-btn" data-copy="/extensions install https://github.com/hyhmrright/brooks-lint">Copy</button><span class="tok">/extensions</span> install https://github.com/hyhmrright/brooks-lint</div>
|
||||
|
||||
<div class="install-label">Codex CLI</div>
|
||||
<div class="code-block">
|
||||
<button class="copy-btn" data-copy="Install the brooks-lint skill from hyhmrright/brooks-lint">Copy</button><span class="cmt"># just say this in a Codex session</span>
|
||||
Install the brooks-lint skill from hyhmrright/brooks-lint</div>
|
||||
|
||||
<div class="install-label" data-en="Then, in any session:" data-zh="然后,在任意会话里:">Then, in any session:</div>
|
||||
<div class="code-block">
|
||||
<button class="copy-btn" data-copy="/brooks-review">Copy</button><span class="tok">/brooks-review</span> <span class="cmt"># or /brooks-audit · /brooks-debt · /brooks-test · /brooks-health · /brooks-sweep</span></div>
|
||||
</div>
|
||||
<div style="text-align:center; margin-top:34px;">
|
||||
<a class="btn btn-primary" href="https://github.com/hyhmrright/brooks-lint" target="_blank" rel="noopener" data-en="Star on GitHub ⭐" data-zh="去 GitHub 点个 Star ⭐">Star on GitHub ⭐</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="wrap foot-in">
|
||||
<div class="foot-links">
|
||||
<a href="https://github.com/hyhmrright/brooks-lint" target="_blank" rel="noopener">GitHub</a>
|
||||
<a href="gallery.html" data-en="Gallery" data-zh="示例画廊">Gallery</a>
|
||||
<a href="https://github.com/hyhmrright/brooks-lint/blob/main/CONTRIBUTING.md" target="_blank" rel="noopener" data-en="Contributing" data-zh="参与贡献">Contributing</a>
|
||||
<a href="https://github.com/hyhmrright/brooks-lint/blob/main/CHANGELOG.md" target="_blank" rel="noopener" data-en="Changelog" data-zh="更新日志">Changelog</a>
|
||||
</div>
|
||||
<div class="foot-note" data-en="MIT License — Built by hyhmrright" data-zh="MIT 许可证 — 由 hyhmrright 构建">MIT License — Built by hyhmrright</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var STORE = "brooks-lint-lang";
|
||||
function apply(lang) {
|
||||
document.documentElement.lang = (lang === "zh") ? "zh-CN" : "en";
|
||||
document.querySelectorAll("[data-en]").forEach(function (el) {
|
||||
var v = el.getAttribute("data-" + lang);
|
||||
if (v != null) el.innerHTML = v;
|
||||
});
|
||||
document.querySelectorAll(".lang-toggle button").forEach(function (b) {
|
||||
b.classList.toggle("active", b.getAttribute("data-set-lang") === lang);
|
||||
});
|
||||
try { localStorage.setItem(STORE, lang); } catch (e) {}
|
||||
}
|
||||
document.querySelectorAll("[data-set-lang]").forEach(function (b) {
|
||||
b.addEventListener("click", function () { apply(b.getAttribute("data-set-lang")); });
|
||||
});
|
||||
var saved;
|
||||
try { saved = localStorage.getItem(STORE); } catch (e) {}
|
||||
if (!saved) saved = (navigator.language || "").toLowerCase().indexOf("zh") === 0 ? "zh" : "en";
|
||||
apply(saved);
|
||||
|
||||
// copy buttons
|
||||
document.querySelectorAll(".copy-btn").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
var text = btn.getAttribute("data-copy");
|
||||
navigator.clipboard.writeText(text).then(function () {
|
||||
var prev = btn.textContent;
|
||||
btn.textContent = "✓";
|
||||
setTimeout(function () { btn.textContent = prev; }, 1200);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
||||
<rect width="512" height="512" rx="80" fill="#1e293b"/>
|
||||
<!-- Book spine -->
|
||||
<rect x="108" y="96" width="48" height="320" rx="8" fill="#3b82f6"/>
|
||||
<!-- Book pages -->
|
||||
<rect x="152" y="112" width="252" height="288" rx="4" fill="#f8fafc"/>
|
||||
<!-- Code lines -->
|
||||
<rect x="180" y="160" width="120" height="12" rx="4" fill="#6366f1"/>
|
||||
<rect x="180" y="188" width="80" height="12" rx="4" fill="#94a3b8"/>
|
||||
<rect x="196" y="216" width="100" height="12" rx="4" fill="#94a3b8"/>
|
||||
<rect x="196" y="244" width="60" height="12" rx="4" fill="#94a3b8"/>
|
||||
<rect x="180" y="272" width="90" height="12" rx="4" fill="#6366f1"/>
|
||||
<rect x="180" y="300" width="140" height="12" rx="4" fill="#94a3b8"/>
|
||||
<!-- Check badge -->
|
||||
<circle cx="360" cy="340" r="52" fill="#1e293b"/>
|
||||
<circle cx="360" cy="340" r="44" fill="#22c55e"/>
|
||||
<polyline points="337,340 353,356 385,322" fill="none" stroke="#fff" stroke-width="14" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://hyhmrright.github.io/brooks-lint/sitemap.xml
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://hyhmrright.github.io/brooks-lint/</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://hyhmrright.github.io/brooks-lint/gallery.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
Reference in New Issue
Block a user