📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-08-24 09:30:03 +08:00
parent 14e7209e78
commit 5d88510274
153 changed files with 3428 additions and 1203 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
{
"name": "brooks-lint",
"description": "AI code reviews grounded in twelve classic engineering books — decay risk diagnostics with book citations, severity labels, and six analysis modes (PR review, architecture audit, tech debt, test quality, health dashboard, full-sweep auto-fix)",
"version": "1.4.3",
"version": "1.5.0",
"source": "./",
"author": {
"name": "hyhmrright",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "brooks-lint",
"description": "AI code reviews grounded in twelve classic engineering books — decay risk diagnostics with book citations, severity labels, and six analysis modes (PR review, architecture audit, tech debt, test quality, health dashboard, full-sweep auto-fix)",
"version": "1.4.3",
"version": "1.5.0",
"author": {
"name": "hyhmrright",
"email": "hyhmrright@gmail.com"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "brooks-lint",
"version": "1.4.3",
"version": "1.5.0",
"description": "AI code reviews grounded in twelve classic engineering books — decay risk diagnostics with book citations, severity labels, and six analysis modes (PR review, architecture audit, tech debt, test quality, health dashboard, full-sweep auto-fix)",
"author": {
"name": "hyhmrright",
+10
View File
@@ -24,6 +24,9 @@ inputs:
model:
description: "Claude model to use"
default: "claude-sonnet-4-6"
api-base-url:
description: "Anthropic-compatible /v1/messages endpoint to call instead of api.anthropic.com (empty = Anthropic)"
default: ""
outputs:
score:
@@ -71,10 +74,17 @@ runs:
shell: bash
env:
ANTHROPIC_API_KEY: ${{ inputs.anthropic-api-key }}
API_BASE_URL: ${{ inputs.api-base-url }}
SARIF_FILE: ${{ inputs.sarif-file }}
MODE: ${{ inputs.mode }}
MODEL: ${{ inputs.model }}
run: |
# Exported only when the input is set: naming ANTHROPIC_BASE_URL in the
# env block above would blank out one inherited from the job env. The
# SDK reads the variable itself, so nothing downstream needs a flag.
if [ -n "$API_BASE_URL" ]; then
export ANTHROPIC_BASE_URL="$API_BASE_URL"
fi
sarif_arg=()
if [ -n "$SARIF_FILE" ]; then
sarif_arg=(--sarif-out "$SARIF_FILE")
+40
View File
@@ -0,0 +1,40 @@
name: Star history
# GitHub restricted the stargazers API to a repo's own admins and collaborators,
# so no third-party service can chart this repo any more. GITHUB_TOKEN can read
# it, so we regenerate the committed SVG here instead.
on:
schedule:
- cron: "17 3 * * 1" # Mondays, 03:17 UTC
workflow_dispatch:
concurrency:
group: star-history
cancel-in-progress: true
jobs:
refresh:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Node.js
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with:
node-version: 22
- name: Regenerate the chart
run: node scripts/gen-star-history.mjs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Commit when the chart moved
run: |
if git diff --quiet -- assets/star-history.svg assets/star-history.json; then
echo "No change."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -m "chore: refresh the star history chart" -- assets/star-history.svg assets/star-history.json
git push
+22
View File
@@ -2,6 +2,28 @@
All notable changes to brooks-lint are documented here.
## [1.5.0] - 2026-08-14
### Added
- **DeepSeek Harness (`dsh`) support** — DeepSeek AI's open-source agent harness
loads standard Agent Skills through `packages/skill/skill-filesystem`, so all
six modes run with no conversion. `./scripts/install.sh dsh` installs into
`$DSH_HOME/skills` (default `~/.dsh/skills`); `--project` targets
`./.dsh/skills`. dsh also scans `~/.agents/skills`, so the existing
vendor-neutral `install.sh agents` install already covered it. New guide at
[`docs/dsh-setup.md`](docs/dsh-setup.md), with the platform added to the README
install table in all six languages and to `docs/getting-started.md`.
Three details of dsh's loader matter and are documented in the guide: discovery
is one level deep (`<root>/<name>/SKILL.md`), which the installer's flat layout
already satisfies so `../_shared/` resolves; skill names must be kebab-case,
which `brooks-*` already is; and dsh recognises a whitespace-bounded `/name`
token anywhere in a message, so `/brooks-review` and the other five work from
its `/` menu or typed inline. dsh also reads `AGENTS.md``$DSH_HOME/AGENTS.md`
plus every file from the project root down to the working directory — so the
repo's Iron Law and Health Score rules load the same way they do elsewhere.
## [1.4.3] - 2026-08-04
### Fixed
+2
View File
@@ -36,6 +36,8 @@ Guidance for Claude Code when modifying this repository. For repo layout, instal
- `` field `commands` is not accepted `` — `"commands": []` is what stops the CLI from migrating `commands/*.md` into duplicate `source-command-brooks-*` skills (issue #22). Confirmed by installing both ways under an isolated `CODEX_HOME`: drop the field and Codex regenerates `.codex-plugin/migrated-command-skills/`. The CLI's `RawPluginManifest` accepts `commands`; only the ingestion schema rejects it.
- `` skill `_shared` is missing `SKILL.md` `` — the validator only skips dot-prefixed dirs, and `_shared/` is a shared-framework dir by design (see above). Renaming it to `.shared/` would also require changing `install.sh`'s `cp -R "$SRC"/*`, whose glob does not match dot-dirs.
- **GitHub Action cache:** `.github/actions/brooks-lint/action.yml` uses `actions/cache@v4` with built-in cache-hit guard — do NOT add a manual directory check.
- **Adding a platform is derived, not hardcoded:** create `docs/<name>-setup.md`, add the platform to `install.sh`'s `PLATFORMS` **and** both `global_dir()` / `project_dir()` case tables, then link the new guide from every `README*.md` and `docs/getting-started.md`. `npm run validate` derives both sides (`scripts/platforms.mjs`) and fails on any gap — a guide missing from one translation, or a `PLATFORMS` entry with no directory mapping. Do not add a hand-maintained platform list anywhere.
- **Star history is data-first, never hand-drawn:** `assets/star-history.json` (raw `starred_at` timestamps, no usernames) is the source of truth; `assets/star-history.svg` is a pure function of it. Never hand-edit the SVG — `npm run validate` re-renders and fails on any mismatch. `node scripts/gen-star-history.mjs` refetches (needs `GITHUB_TOKEN` or `gh auth`, since GitHub restricted the stargazers API to admins/collaborators on 2026-06-30); `--render-only` redraws offline from the committed data. The render must stay deterministic — anchoring the time axis to the clock would make the weekly workflow commit noise on every run.
- **Custom risks:** Teams add project-specific risk codes via `custom-risks-guide.md` in their project root. Template lives at `skills/_shared/custom-risks-guide.md`.
## How the Skills Work
+26 -12
View File
@@ -27,7 +27,7 @@
</p>
<p align="center">
<img src="https://img.shields.io/badge/version-1.4.3-blue.svg" alt="Version">
<img src="https://img.shields.io/badge/version-1.5.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">
@@ -78,7 +78,7 @@ Luego solo pide ("revisa este PR", "audita la arquitectura"), o ejecuta uno de l
([qué hace cada uno](#comandos-de-barra)).
Cada hallazgo se devuelve como **Síntoma → Origen → Consecuencia → Remedio** con una cita de libro y
una puntuación de salud de 0 a 100. Las opciones completas de instalación (8 plataformas más) y la
una puntuación de salud de 0 a 100. Las opciones completas de instalación (9 plataformas más) y la
configuración de CI/CD están [más abajo](#instalación).
## Los doce libros
@@ -273,7 +273,7 @@ Install the brooks-lint skill from hyhmrright/brooks-lint # pídelo dentro
O usa el instalador de abajo: `./scripts/install.sh gemini` / `./scripts/install.sh codex`.
### Cualquier otra plataforma — OpenCode · Cursor · Windsurf · Antigravity · pi · Copilot · Kiro · Factory Droid
### Cualquier otra plataforma — OpenCode · Cursor · Windsurf · Antigravity · pi · Copilot · Kiro · Factory Droid · DeepSeek Harness
brooks-lint se distribuye como [Agent Skills](https://agentskills.io) estándar. **Cualquier agente que cargue Agent
Skills ejecuta los seis modos sin conversión alguna** — un solo comando los instala:
@@ -281,7 +281,7 @@ Skills ejecuta los seis modos sin conversión alguna** — un solo comando los i
```bash
# elige tu plataforma; --project instala en el repositorio actual en lugar de en tu configuración global
curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts/install.sh | bash -s -- <platform>
# <platform> = opencode · cursor · windsurf · antigravity · pi · kiro · copilot · droid · gemini · codex · agents
# <platform> = opencode · cursor · windsurf · antigravity · pi · kiro · copilot · droid · dsh · gemini · codex · agents
```
El instalador copia los skills **de forma plana** en la carpeta correcta, de modo que el framework compartido
@@ -298,11 +298,12 @@ El instalador copia los skills **de forma plana** en la carpeta correcta, de mod
| GitHub Copilot | `.github/skills` (`--project`) | `.claude/skills`, `AGENTS.md` | [configuración](docs/copilot-setup.md) |
| Kiro (AWS) | `~/.kiro/skills` | `AGENTS.md` | [configuración](docs/kiro-setup.md) |
| Factory Droid | `~/.factory/skills` | `AGENTS.md` | [configuración](docs/factory-droid-setup.md) |
| DeepSeek Harness (`dsh`) | `~/.dsh/skills` | `~/.agents/skills`, `AGENTS.md` | [configuración](docs/dsh-setup.md) |
Kiro y Factory Droid también registran `/brooks-review` automáticamente. ¿Nuevo en los skills, o usas un
agente que no aparece aquí? Consulta **[docs/getting-started.md](docs/getting-started.md)**.
Kiro, Factory Droid y DeepSeek Harness también registran `/brooks-review` automáticamente. ¿Nuevo en los
skills, o usas un agente que no aparece aquí? Consulta **[docs/getting-started.md](docs/getting-started.md)**.
> **🧪 Estado de verificación.** Claude Code, Gemini CLI y Codex CLI están verificados por el mantenedor. Las ocho
> **🧪 Estado de verificación.** Claude Code, Gemini CLI y Codex CLI están verificados por el mantenedor. Las nueve
> plataformas anteriores están documentadas a partir de la especificación oficial de skills de cada herramienta y verificadas a nivel
> de diseño de archivos (el instalador está probado), pero el mantenedor aún no las ha ejecutado de extremo a extremo en cada plataforma. ¿Probaste
> alguna — funciona **o** está rota? [Abre un issue](https://github.com/hyhmrright/brooks-lint/issues/new) con
@@ -322,10 +323,11 @@ agente que no aparece aquí? Consulta **[docs/getting-started.md](docs/getting-s
**Sintaxis por plataforma.** Claude Code también acepta la forma con espacio de nombres
`/brooks-lint:brooks-review` — las formas cortas las instala el hook session-start al iniciar la primera
sesión. Codex CLI usa `$brooks-review`. Gemini CLI usa la tabla tal cual. OpenCode, Cursor, Antigravity y pi
invocan los Agent Skills desde la `description` de cada skill, así que basta con pedirlo ("revisa este PR",
"¿dónde está nuestra peor deuda técnica?"); para invocarlos explícitamente usa la sintaxis propia de cada
plataforma (pi registra cada skill como `/skill:brooks-review`). En todas las plataformas los skills también
sesión. Codex CLI usa `$brooks-review`. Gemini CLI usa la tabla tal cual. OpenCode, Cursor, Antigravity, pi y
DeepSeek Harness invocan los Agent Skills desde la `description` de cada skill, así que basta con pedirlo
("revisa este PR", "¿dónde está nuestra peor deuda técnica?"); para invocarlos explícitamente usa la sintaxis
propia de cada plataforma (pi registra cada skill como `/skill:brooks-review`; dsh usa la tabla tal cual, desde
su menú `/` o escrito a mano). En todas las plataformas los skills también
se activan solos cuando hablas de calidad de código, arquitectura o salud de las pruebas.
> Las revisiones de PR incluyen automáticamente una comprobación rápida de pruebas (Step 7, ligera; se omite
@@ -448,6 +450,18 @@ La action publica la revisión como un comentario del PR y, opcionalmente, hace
`fail-on-regression` lee `.brooks-lint-history.json`, así que confirma ese archivo para imponer "sin nuevas regresiones". Definir `sarif-file` hace que los hallazgos aparezcan en línea en la pestaña **Files changed** del PR y requiere el permiso `security-events: write` en el job.
**Endpoint de API personalizado.** `api-base-url` apunta la action a cualquier endpoint `/v1/messages` compatible con Anthropic —un proxy autoalojado, una pasarela de LLM, un espejo regional— en lugar de `api.anthropic.com`. Pasa la clave de ese endpoint como `anthropic-api-key` y el id de modelo que espera como `model`:
```yaml
with:
mode: review
api-base-url: https://your-gateway.example.com
anthropic-api-key: ${{ secrets.GATEWAY_API_KEY }}
model: gateway-model-id
```
brooks-lint envía tu diff al host que indiques aquí, así que apúntalo solo a uno en el que confíes con tu código fuente. Ejecutar `scripts/ci-review.mjs` por tu cuenta no necesita ninguna opción: el SDK de Anthropic lee `ANTHROPIC_BASE_URL` directamente.
**Coste:** ~$0,050,15 por ejecución de PR, según el tamaño del diff y el modelo. Se recomienda ejecutar solo en eventos `pull_request`.
## Hoja de ruta
@@ -486,7 +500,7 @@ síntesis de sus ideas, aplicada a la evaluación moderna de la calidad del cód
## Historial de estrellas
[![Star History Chart](https://api.star-history.com/svg?repos=hyhmrright/brooks-lint&type=Date)](https://star-history.com/#hyhmrright/brooks-lint&Date)
[![Star History](assets/star-history.svg)](https://github.com/hyhmrright/brooks-lint/stargazers)
---
+25 -11
View File
@@ -27,7 +27,7 @@
</p>
<p align="center">
<img src="https://img.shields.io/badge/version-1.4.3-blue.svg" alt="Version">
<img src="https://img.shields.io/badge/version-1.5.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">
@@ -78,7 +78,7 @@ curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts
`/brooks-health``/brooks-sweep`[それぞれの機能](#スラッシュコマンド))。
すべての指摘は **症状 → 根源 → 結果 → 対策** の形式で、書籍の出典と 0〜100 の健全性スコアとともに
返されます。完全なインストール方法(さらに 8 つのプラットフォーム)と CI/CD のセットアップは
返されます。完全なインストール方法(さらに 9 つのプラットフォーム)と CI/CD のセットアップは
[以下](#インストール)を参照してください。
## 十二冊の書籍
@@ -273,7 +273,7 @@ Install the brooks-lint skill from hyhmrright/brooks-lint # Codex セッ
または下記のインストーラーを使用:`./scripts/install.sh gemini` / `./scripts/install.sh codex`
### その他すべてのプラットフォーム — OpenCode · Cursor · Windsurf · Antigravity · pi · Copilot · Kiro · Factory Droid
### その他すべてのプラットフォーム — OpenCode · Cursor · Windsurf · Antigravity · pi · Copilot · Kiro · Factory Droid · DeepSeek Harness
brooks-lint は標準的な [Agent Skills](https://agentskills.io) として配布されています。**Agent
Skills を読み込むエージェントなら、どれも変換なしで六つすべてのモードを実行できます**——1 つのコマンドでインストールできます:
@@ -281,7 +281,7 @@ Skills を読み込むエージェントなら、どれも変換なしで六つ
```bash
# プラットフォームを選択;--project はグローバル設定ではなく現在のリポジトリにインストール
curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts/install.sh | bash -s -- <platform>
# <platform> = opencode · cursor · windsurf · antigravity · pi · kiro · copilot · droid · gemini · codex · agents
# <platform> = opencode · cursor · windsurf · antigravity · pi · kiro · copilot · droid · dsh · gemini · codex · agents
```
インストーラーはスキルをあなたのプラットフォームに適したフォルダへ**フラット**にコピーするため、共有フレームワーク
@@ -299,11 +299,12 @@ curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts
| GitHub Copilot | `.github/skills``--project` | `.claude/skills``AGENTS.md` | [設定](docs/copilot-setup.md) |
| KiroAWS | `~/.kiro/skills` | `AGENTS.md` | [設定](docs/kiro-setup.md) |
| Factory Droid | `~/.factory/skills` | `AGENTS.md` | [設定](docs/factory-droid-setup.md) |
| DeepSeek Harness`dsh` | `~/.dsh/skills` | `~/.agents/skills``AGENTS.md` | [設定](docs/dsh-setup.md) |
KiroFactory Droid は `/brooks-review` も自動登録します。スキルが初めて、または上記にないエージェントを
お使いですか? **[docs/getting-started.md](docs/getting-started.md)** を参照してください。
KiroFactory Droid、DeepSeek Harness`/brooks-review` も自動登録します。スキルが初めて、または
上記にないエージェントをお使いですか? **[docs/getting-started.md](docs/getting-started.md)** を参照してください。
> **🧪 検証状況。** Claude Code、Gemini CLI、Codex CLI はメンテナーによって検証済みです。上記のつの
> **🧪 検証状況。** Claude Code、Gemini CLI、Codex CLI はメンテナーによって検証済みです。上記のつの
> プラットフォームは各ツールの公式スキル仕様から文書化され、ファイルレイアウトのレベルで検証されています
> (インストーラーはテスト済み)が、メンテナーがすべてのプラットフォームでエンドツーエンドに実行したわけ
> ではまだありません。どれかを試した——動いた **または** 壊れた? プラットフォーム、バージョン、見たこと
@@ -323,10 +324,11 @@ Kiro と Factory Droid は `/brooks-review` も自動登録します。スキル
**プラットフォーム別の構文。** Claude Code は名前空間付きの完全形 `/brooks-lint:brooks-review` も受け付けます
——短縮形は session-start フックが最初のセッション開始時に自動インストールします。Codex CLI は
`$brooks-review`。Gemini CLI は上の表のとおり。OpenCode、Cursor、Antigravity、pi は各スキルの
`description` から Agent Skills を呼び出すので、話しかけるだけで十分です(「この PR をレビューして」
`$brooks-review`。Gemini CLI は上の表のとおり。OpenCode、Cursor、Antigravity、pi、DeepSeek Harness
各スキルの `description` から Agent Skills を呼び出すので、話しかけるだけで十分です(「この PR をレビューして」
「最悪の技術的負債はどこ?」)。明示的に呼び出す場合は各プラットフォームの構文を使います(pi は各スキルを
`/skill:brooks-review` として登録)。どのプラットフォームでも、コード品質・アーキテクチャ・テストの健全性に
`/skill:brooks-review` として登録。dsh は上の表のとおりで、`/` メニューから選ぶか直接入力)。どの
プラットフォームでも、コード品質・アーキテクチャ・テストの健全性に
ついて話すと、スキルは自動的にトリガーされます。
> PR レビューには軽量な Step 7 クイックテストチェックが自動的に含まれます(ドキュメントのみの diff では
@@ -448,6 +450,18 @@ jobs:
`fail-on-regression``.brooks-lint-history.json` を読み取るため、そのファイルをコミットすれば「新たな回帰なし」を強制できます。`sarif-file` を設定すると、指摘が PR の **Files changed** タブにインラインで表示されるようになり、ジョブに `security-events: write` 権限が必要になります。
**カスタム API エンドポイント。** `api-base-url` を指定すると、action は `api.anthropic.com` ではなく Anthropic 互換の `/v1/messages` エンドポイント(自前のプロキシ、LLM ゲートウェイ、リージョンミラーなど)を呼び出します。そのエンドポイントのキーを `anthropic-api-key` に、期待されるモデル id を `model` に渡してください:
```yaml
with:
mode: review
api-base-url: https://your-gateway.example.com
anthropic-api-key: ${{ secrets.GATEWAY_API_KEY }}
model: gateway-model-id
```
brooks-lint はここで指定したホストに diff を送信するため、ソースコードを預けられる相手だけを指定してください。`scripts/ci-review.mjs` を自分で実行する場合はフラグは不要です — Anthropic SDK が `ANTHROPIC_BASE_URL` を直接読み取ります。
**コスト:** PR 実行ごとにおよそ $0.05〜0.15、diff のサイズとモデルによります。`pull_request` イベントのみで実行することを推奨します。
## ロードマップ
@@ -486,7 +500,7 @@ MIT License — 詳細は [LICENSE](LICENSE) を参照してください。
## スター履歴
[![Star History Chart](https://api.star-history.com/svg?repos=hyhmrright/brooks-lint&type=Date)](https://star-history.com/#hyhmrright/brooks-lint&Date)
[![Star History](assets/star-history.svg)](https://github.com/hyhmrright/brooks-lint/stargazers)
---
+25 -11
View File
@@ -27,7 +27,7 @@
</p>
<p align="center">
<img src="https://img.shields.io/badge/version-1.4.3-blue.svg" alt="Version">
<img src="https://img.shields.io/badge/version-1.5.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">
@@ -78,7 +78,7 @@ curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts
`/brooks-sweep`[각각 하는 일](#슬래시-명령).
모든 진단은 도서 출처와 0–100 건강 점수와 함께 **증상 → 근원 → 결과 → 처방** 형태로 돌아옵니다. 전체
설치 옵션(추가 8개 플랫폼)과 CI/CD 설정은 [아래](#설치)를 참고하세요.
설치 옵션(추가 9개 플랫폼)과 CI/CD 설정은 [아래](#설치)를 참고하세요.
## 열두 권의 책
@@ -272,7 +272,7 @@ Install the brooks-lint skill from hyhmrright/brooks-lint # Codex 세션
또는 아래 설치기를 사용하세요: `./scripts/install.sh gemini` / `./scripts/install.sh codex`.
### 그 밖의 모든 플랫폼 — OpenCode · Cursor · Windsurf · Antigravity · pi · Copilot · Kiro · Factory Droid
### 그 밖의 모든 플랫폼 — OpenCode · Cursor · Windsurf · Antigravity · pi · Copilot · Kiro · Factory Droid · DeepSeek Harness
brooks-lint는 표준 [Agent Skills](https://agentskills.io) 형태로 배포됩니다. **Agent
Skills를 로드하는 모든 에이전트는 변환 없이 여섯 가지 모드를 모두 실행합니다** — 한 줄의 명령으로 설치됩니다:
@@ -280,7 +280,7 @@ Skills를 로드하는 모든 에이전트는 변환 없이 여섯 가지 모드
```bash
# 플랫폼을 고르세요; --project는 전역 설정 대신 현재 저장소에 설치합니다
curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts/install.sh | bash -s -- <platform>
# <platform> = opencode · cursor · windsurf · antigravity · pi · kiro · copilot · droid · gemini · codex · agents
# <platform> = opencode · cursor · windsurf · antigravity · pi · kiro · copilot · droid · dsh · gemini · codex · agents
```
설치기는 스킬을 당신의 플랫폼에 맞는 폴더로 **평평하게** 복사하므로, 공유 프레임워크(`../_shared/`)가
@@ -297,12 +297,13 @@ curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts
| GitHub Copilot | `.github/skills``--project` | `.claude/skills`, `AGENTS.md` | [설정](docs/copilot-setup.md) |
| Kiro (AWS) | `~/.kiro/skills` | `AGENTS.md` | [설정](docs/kiro-setup.md) |
| Factory Droid | `~/.factory/skills` | `AGENTS.md` | [설정](docs/factory-droid-setup.md) |
| DeepSeek Harness (`dsh`) | `~/.dsh/skills` | `~/.agents/skills`, `AGENTS.md` | [설정](docs/dsh-setup.md) |
Kiro Factory Droid는 `/brooks-review`도 자동 등록합니다. 스킬이 처음이거나 위 목록에 없는 에이전트를
쓰시나요? **[docs/getting-started.md](docs/getting-started.md)**를 참고하세요.
Kiro, Factory Droid, DeepSeek Harness`/brooks-review`도 자동 등록합니다. 스킬이 처음이거나 위
목록에 없는 에이전트를 쓰시나요? **[docs/getting-started.md](docs/getting-started.md)**를 참고하세요.
> **🧪 검증 상태.** Claude Code, Gemini CLI, Codex CLI는 메인테이너가 검증했습니다. 위
> 여덟 개 플랫폼은 각 도구의 공식 스킬 명세를 토대로 문서화되었고 파일 레이아웃
> 아홉 개 플랫폼은 각 도구의 공식 스킬 명세를 토대로 문서화되었고 파일 레이아웃
> 수준에서 검증되었으나(설치기는 테스트되었음), 메인테이너가 모든 플랫폼에서 end-to-end로 직접 실행해 보지는
> 못했습니다. 어떤 것을 시도해 보셨나요 — 잘 되든 **안 되든**? 플랫폼, 버전, 본 결과를 담아
> [이슈를 열어 주세요](https://github.com/hyhmrright/brooks-lint/issues/new).
@@ -322,9 +323,10 @@ Kiro와 Factory Droid는 `/brooks-review`도 자동 등록합니다. 스킬이
**플랫폼별 문법.** Claude Code는 네임스페이스가 붙은 전체 형식 `/brooks-lint:brooks-review`도 받습니다
— 짧은 형식은 session-start 훅이 첫 세션 시작 시 자동 설치합니다. Codex CLI는 `$brooks-review`를 씁니다.
Gemini CLI는 위 표 그대로입니다. OpenCode, Cursor, Antigravity, pi는 각 스킬의 `description`에서 Agent
Skills를 호출하므로 그냥 요청하면 됩니다("이 PR을 리뷰해줘", "우리 최악의 기술 부채는 어디야?"). 명시적
호출이 필요하면 각 플랫폼의 문법을 쓰세요(pi는 각 스킬을 `/skill:brooks-review`로 등록). 모든
Gemini CLI는 위 표 그대로입니다. OpenCode, Cursor, Antigravity, pi, DeepSeek Harness는 각 스킬의
`description`에서 Agent Skills를 호출하므로 그냥 요청하면 됩니다("이 PR을 리뷰해줘", "우리 최악의 기술
부채는 어디야?"). 명시적 호출이 필요하면 각 플랫폼의 문법을 쓰세요(pi는 각 스킬을
`/skill:brooks-review`로 등록; dsh는 위 표 그대로이며 `/` 메뉴에서 고르거나 직접 입력). 모든
플랫폼에서 코드 품질, 아키텍처, 테스트 건강을 이야기하면 스킬이 자동으로 트리거됩니다.
> PR 리뷰에는 가벼운 Step 7 빠른 테스트 점검이 자동으로 포함됩니다(문서만 바뀐 diff에서는 건너뜀).
@@ -446,6 +448,18 @@ jobs:
`fail-on-regression``.brooks-lint-history.json`을 읽으므로, "새로운 회귀 없음"을 강제하려면 그 파일을 커밋하세요. `sarif-file`을 설정하면 진단이 PR의 **Files changed** 탭에 인라인으로 나타나며, job에 `security-events: write` 권한이 필요합니다.
**사용자 지정 API 엔드포인트.** `api-base-url`을 지정하면 action이 `api.anthropic.com` 대신 Anthropic 호환 `/v1/messages` 엔드포인트(자체 호스팅 프록시, LLM 게이트웨이, 리전 미러)를 호출합니다. 해당 엔드포인트의 키를 `anthropic-api-key`로, 기대하는 모델 id를 `model`로 전달하세요:
```yaml
with:
mode: review
api-base-url: https://your-gateway.example.com
anthropic-api-key: ${{ secrets.GATEWAY_API_KEY }}
model: gateway-model-id
```
brooks-lint는 여기에 지정한 호스트로 diff를 전송하므로, 소스 코드를 맡겨도 되는 곳만 지정하세요. `scripts/ci-review.mjs`를 직접 실행할 때는 플래그가 필요 없습니다 — Anthropic SDK가 `ANTHROPIC_BASE_URL`을 직접 읽습니다.
**비용:** PR 실행당 약 $0.050.15로, diff 크기와 모델에 따라 다릅니다. `pull_request` 이벤트에서만 실행할 것을 권장합니다.
## 로드맵
@@ -484,7 +498,7 @@ MIT License — 자세한 내용은 [LICENSE](LICENSE)를 참고하세요.
## Star 히스토리
[![Star History Chart](https://api.star-history.com/svg?repos=hyhmrright/brooks-lint&type=Date)](https://star-history.com/#hyhmrright/brooks-lint&Date)
[![Star History](assets/star-history.svg)](https://github.com/hyhmrright/brooks-lint/stargazers)
---
+25 -11
View File
@@ -27,7 +27,7 @@
</p>
<p align="center">
<img src="https://img.shields.io/badge/version-1.4.3-blue.svg" alt="Version">
<img src="https://img.shields.io/badge/version-1.5.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">
@@ -78,7 +78,7 @@ Then just ask ("review this PR", "audit the architecture"), or run one of the si
([what each one does](#slash-commands)).
Every finding comes back as **Symptom → Source → Consequence → Remedy** with a book citation and a
0100 Health Score. Full install options (8 more platforms) and CI/CD setup are [below](#installation).
0100 Health Score. Full install options (9 more platforms) and CI/CD setup are [below](#installation).
## The Twelve Books
@@ -272,7 +272,7 @@ Install the brooks-lint skill from hyhmrright/brooks-lint # ask inside a C
Or use the installer below: `./scripts/install.sh gemini` / `./scripts/install.sh codex`.
### Every other platform — OpenCode · Cursor · Windsurf · Antigravity · pi · Copilot · Kiro · Factory Droid
### Every other platform — OpenCode · Cursor · Windsurf · Antigravity · pi · Copilot · Kiro · Factory Droid · DeepSeek Harness
brooks-lint ships as standard [Agent Skills](https://agentskills.io). **Any agent that loads Agent
Skills runs all six modes with no conversion** — one command installs them:
@@ -280,7 +280,7 @@ Skills runs all six modes with no conversion** — one command installs them:
```bash
# pick your platform; --project installs into the current repo instead of your global config
curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts/install.sh | bash -s -- <platform>
# <platform> = opencode · cursor · windsurf · antigravity · pi · kiro · copilot · droid · gemini · codex · agents
# <platform> = opencode · cursor · windsurf · antigravity · pi · kiro · copilot · droid · dsh · gemini · codex · agents
```
The installer copies the skills **flat** into the right folder, so the shared framework
@@ -297,12 +297,13 @@ The installer copies the skills **flat** into the right folder, so the shared fr
| GitHub Copilot | `.github/skills` (`--project`) | `.claude/skills`, `AGENTS.md` | [setup](docs/copilot-setup.md) |
| Kiro (AWS) | `~/.kiro/skills` | `AGENTS.md` | [setup](docs/kiro-setup.md) |
| Factory Droid | `~/.factory/skills` | `AGENTS.md` | [setup](docs/factory-droid-setup.md) |
| DeepSeek Harness (`dsh`) | `~/.dsh/skills` | `~/.agents/skills`, `AGENTS.md` | [setup](docs/dsh-setup.md) |
Kiro and Factory Droid also auto-register `/brooks-review`. New to skills, or using an agent not
listed? See **[docs/getting-started.md](docs/getting-started.md)**.
Kiro, Factory Droid, and DeepSeek Harness also auto-register `/brooks-review`. New to skills, or
using an agent not listed? See **[docs/getting-started.md](docs/getting-started.md)**.
> **🧪 Verification status.** Claude Code, Gemini CLI, and Codex CLI are maintainer-verified. The
> eight platforms above are documented from each tool's official skill spec and verified at the
> nine platforms above are documented from each tool's official skill spec and verified at the
> file-layout level (the installer is tested), but not yet run end-to-end by the maintainer on every
> platform. Tried one — working **or** broken?
> [Open an issue](https://github.com/hyhmrright/brooks-lint/issues/new) with the platform, version,
@@ -323,9 +324,10 @@ listed? See **[docs/getting-started.md](docs/getting-started.md)**.
**Syntax by platform.** Claude Code also accepts the namespaced form
`/brooks-lint:brooks-review` — short forms are auto-installed on first session start by the
session-start hook. Codex CLI uses `$brooks-review`. Gemini CLI uses the table as written.
OpenCode, Cursor, Antigravity, and pi invoke Agent Skills from each skill's `description`, so
just ask ("review this PR", "where's our worst tech debt?"); for explicit invocation use the
platform's own syntax (pi registers each skill as `/skill:brooks-review`). On every platform the
OpenCode, Cursor, Antigravity, pi, and DeepSeek Harness invoke Agent Skills from each skill's
`description`, so just ask ("review this PR", "where's our worst tech debt?"); for explicit
invocation use the platform's own syntax (pi registers each skill as `/skill:brooks-review`; dsh
takes the table as written, from its `/` menu or typed inline). On every platform the
skills also trigger automatically when you discuss code quality, architecture, or test health.
> PR reviews include a lightweight Step 7 Quick Test Check automatically (skipped for docs-only
@@ -448,6 +450,18 @@ The action posts the review as a PR comment and optionally fails the check if th
`fail-on-regression` reads `.brooks-lint-history.json`, so commit that file to enforce "no new regressions". Setting `sarif-file` makes findings appear inline on the PR's **Files changed** tab and requires `security-events: write` permission on the job.
**Custom API endpoint.** `api-base-url` points the action at any Anthropic-compatible `/v1/messages` endpoint — a self-hosted proxy, an LLM gateway, a regional mirror — instead of `api.anthropic.com`. Pass that endpoint's key as `anthropic-api-key` and the model id it expects as `model`:
```yaml
with:
mode: review
api-base-url: https://your-gateway.example.com
anthropic-api-key: ${{ secrets.GATEWAY_API_KEY }}
model: gateway-model-id
```
brooks-lint sends your diff to whatever host you name here, so only point it at one you trust with your source. Running `scripts/ci-review.mjs` yourself needs no flag at all — the Anthropic SDK reads `ANTHROPIC_BASE_URL` directly.
**Cost:** ~$0.050.15 per PR run depending on diff size and model. Recommend running on `pull_request` events only.
## Roadmap
@@ -486,7 +500,7 @@ their ideas, applied to modern code quality assessment.
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=hyhmrright/brooks-lint&type=Date)](https://star-history.com/#hyhmrright/brooks-lint&Date)
[![Star History](assets/star-history.svg)](https://github.com/hyhmrright/brooks-lint/stargazers)
---
+25 -11
View File
@@ -27,7 +27,7 @@
</p>
<p align="center">
<img src="https://img.shields.io/badge/version-1.4.3-blue.svg" alt="Version">
<img src="https://img.shields.io/badge/version-1.5.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">
@@ -76,7 +76,7 @@ curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts
装好后直接开口("审查这个 PR""审计架构"),或运行六个命令之一——`/brooks-review``/brooks-audit`
`/brooks-debt``/brooks-test``/brooks-health``/brooks-sweep`[各自的作用](#斜杠命令))。
每条诊断都以 **症状 → 根源 → 后果 → 对策** 返回,附书目出处和 0–100 健康分。完整安装方式(另外 8
每条诊断都以 **症状 → 根源 → 后果 → 对策** 返回,附书目出处和 0–100 健康分。完整安装方式(另外 9
平台)和 CI/CD 配置见[下文](#安装)。
## 十二本书
@@ -270,7 +270,7 @@ Install the brooks-lint skill from hyhmrright/brooks-lint # 在 Codex 会
或使用下面的安装器:`./scripts/install.sh gemini` / `./scripts/install.sh codex`
### 其它所有平台——OpenCode · Cursor · Windsurf · Antigravity · pi · Copilot · Kiro · Factory Droid
### 其它所有平台——OpenCode · Cursor · Windsurf · Antigravity · pi · Copilot · Kiro · Factory Droid · DeepSeek Harness
brooks-lint 以标准 [Agent Skills](https://agentskills.io) 形式分发。**任何加载 Agent Skills 的 agent
都能无需任何转换运行全部六种模式**——一条命令即可安装:
@@ -278,7 +278,7 @@ brooks-lint 以标准 [Agent Skills](https://agentskills.io) 形式分发。**
```bash
# 选择你的平台;加 --project 装进当前仓库而非全局配置
curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts/install.sh | bash -s -- <平台>
# <平台> = opencode · cursor · windsurf · antigravity · pi · kiro · copilot · droid · gemini · codex · agents
# <平台> = opencode · cursor · windsurf · antigravity · pi · kiro · copilot · droid · dsh · gemini · codex · agents
```
安装器会把技能**扁平**拷进该平台对应的文件夹,让共享框架(`../_shared/`)始终正确解析——你不可能装错布局。
@@ -294,11 +294,12 @@ curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts
| GitHub Copilot | `.github/skills``--project` | `.claude/skills``AGENTS.md` | [配置](docs/copilot-setup.md) |
| KiroAWS | `~/.kiro/skills` | `AGENTS.md` | [配置](docs/kiro-setup.md) |
| Factory Droid | `~/.factory/skills` | `AGENTS.md` | [配置](docs/factory-droid-setup.md) |
| DeepSeek Harness`dsh` | `~/.dsh/skills` | `~/.agents/skills``AGENTS.md` | [配置](docs/dsh-setup.md) |
KiroFactory Droid 还会自动注册 `/brooks-review`。不熟悉 skills、或用的是上面没列出的 agent
**[docs/getting-started.md](docs/getting-started.md)**。
KiroFactory Droid 与 DeepSeek Harness 还会自动注册 `/brooks-review`。不熟悉 skills、或用的是上面
没列出的 agent**[docs/getting-started.md](docs/getting-started.md)**。
> **🧪 验证状态。** Claude Code、Gemini CLI、Codex CLI 已由维护者验证。上面个平台依据各工具官方技能规范编写,
> **🧪 验证状态。** Claude Code、Gemini CLI、Codex CLI 已由维护者验证。上面个平台依据各工具官方技能规范编写,
> 并已在文件布局层面验证(安装器经过测试),但维护者尚未在每个平台端到端实跑。在某平台试过了——无论成功**还是**失败?
> 请[提一个 issue](https://github.com/hyhmrright/brooks-lint/issues/new),附上平台、版本和你看到的结果。
> 用的是其它兼容 Agent Skills 的 agent?它几乎肯定以同样方式工作——告诉我们,我们会补上。
@@ -316,9 +317,10 @@ Kiro 与 Factory Droid 还会自动注册 `/brooks-review`。不熟悉 skills、
**各平台语法。** Claude Code 也接受带命名空间的完整形式 `/brooks-lint:brooks-review`——短命令由
session-start 钩子在首次会话启动时自动安装。Codex CLI 用 `$brooks-review`。Gemini CLI 直接用上表。
OpenCode、Cursor、Antigravity、pi 依据每个技能的 `description` 自动调用 Agent Skills,直接提问即可
"审查这个 PR"、"我们最糟的技术债在哪");需要显式调用时用各平台自己的语法pi 把每个技能注册为
`/skill:brooks-review`)。在所有平台上,当你讨论代码质量、架构或测试健康时,这些技能也会自动触发。
OpenCode、Cursor、Antigravity、pi、DeepSeek Harness 依据每个技能的 `description` 自动调用 Agent
Skills,直接提问即可"审查这个 PR"、"我们最糟的技术债在哪");需要显式调用时用各平台自己的语法
pi 把每个技能注册为 `/skill:brooks-review`dsh 直接用上表,可从 `/` 菜单选或手打)。在所有平台上,
当你讨论代码质量、架构或测试健康时,这些技能也会自动触发。
> PR 审查会自动包含一个轻量的第 7 步快速测试检查(对纯文档 diff 会跳过)。需要完整的测试审查请用
> `/brooks-test`;需要某个维度的深度诊断时,请用该维度的专项技能,而不是 `/brooks-health`。
@@ -437,6 +439,18 @@ jobs:
`fail-on-regression` 读取 `.brooks-lint-history.json`,因此提交该文件即可强制"无新增回归"。设置 `sarif-file` 会让诊断直接显示在 PR 的 **Files changed** 标签页,并需要 job 具备 `security-events: write` 权限。
**自定义 API 端点。** `api-base-url` 让 action 改为调用任意 Anthropic 兼容的 `/v1/messages` 端点 —— 自建代理、LLM 网关或区域镜像 —— 而不是 `api.anthropic.com`。把该端点的密钥作为 `anthropic-api-key` 传入,把它期望的模型 id 作为 `model` 传入:
```yaml
with:
mode: review
api-base-url: https://your-gateway.example.com
anthropic-api-key: ${{ secrets.GATEWAY_API_KEY }}
model: gateway-model-id
```
brooks-lint 会把你的 diff 发送到这里填写的主机,因此只应指向你信任其接触源码的一方。自己运行 `scripts/ci-review.mjs` 完全不需要参数 —— Anthropic SDK 会直接读取 `ANTHROPIC_BASE_URL`
**成本:** 每次 PR 运行约 $0.05–0.15,取决于 diff 大小和模型。建议仅在 `pull_request` 事件上运行。
## 路线图
@@ -472,7 +486,7 @@ MIT License——详见 [LICENSE](LICENSE)。
## Star 历史
[![Star History Chart](https://api.star-history.com/svg?repos=hyhmrright/brooks-lint&type=Date)](https://star-history.com/#hyhmrright/brooks-lint&Date)
[![Star History](assets/star-history.svg)](https://github.com/hyhmrright/brooks-lint/stargazers)
---
+25 -11
View File
@@ -27,7 +27,7 @@
</p>
<p align="center">
<img src="https://img.shields.io/badge/version-1.4.3-blue.svg" alt="Version">
<img src="https://img.shields.io/badge/version-1.5.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">
@@ -76,7 +76,7 @@ curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts
裝好後直接開口(「審查這個 PR」「稽核架構」),或執行六個命令之一——`/brooks-review``/brooks-audit`
`/brooks-debt``/brooks-test``/brooks-health``/brooks-sweep`[各自的作用](#斜線命令))。
每條診斷都以 **症狀 → 根源 → 後果 → 對策** 回傳,附書目出處和 0–100 健康分。完整安裝方式(另外 8
每條診斷都以 **症狀 → 根源 → 後果 → 對策** 回傳,附書目出處和 0–100 健康分。完整安裝方式(另外 9
平台)和 CI/CD 設定見[下文](#安裝)。
## 十二本書
@@ -270,7 +270,7 @@ Install the brooks-lint skill from hyhmrright/brooks-lint # 在 Codex 工
或使用下面的安裝器:`./scripts/install.sh gemini` / `./scripts/install.sh codex`
### 其他所有平台——OpenCode · Cursor · Windsurf · Antigravity · pi · Copilot · Kiro · Factory Droid
### 其他所有平台——OpenCode · Cursor · Windsurf · Antigravity · pi · Copilot · Kiro · Factory Droid · DeepSeek Harness
brooks-lint 以標準 [Agent Skills](https://agentskills.io) 形式散布。**任何載入 Agent Skills 的 agent
都能無需任何轉換執行全部六種模式**——一條命令即可安裝:
@@ -278,7 +278,7 @@ brooks-lint 以標準 [Agent Skills](https://agentskills.io) 形式散布。**
```bash
# 選擇你的平台;加 --project 裝進當前儲存庫而非全域設定
curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts/install.sh | bash -s -- <平台>
# <平台> = opencode · cursor · windsurf · antigravity · pi · kiro · copilot · droid · gemini · codex · agents
# <平台> = opencode · cursor · windsurf · antigravity · pi · kiro · copilot · droid · dsh · gemini · codex · agents
```
安裝器會把技能**扁平**複製進該平台對應的資料夾,讓共享框架(`../_shared/`)始終正確解析——你不可能裝錯佈局。
@@ -294,11 +294,12 @@ curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts
| GitHub Copilot | `.github/skills``--project` | `.claude/skills``AGENTS.md` | [設定](docs/copilot-setup.md) |
| KiroAWS | `~/.kiro/skills` | `AGENTS.md` | [設定](docs/kiro-setup.md) |
| Factory Droid | `~/.factory/skills` | `AGENTS.md` | [設定](docs/factory-droid-setup.md) |
| DeepSeek Harness`dsh` | `~/.dsh/skills` | `~/.agents/skills``AGENTS.md` | [設定](docs/dsh-setup.md) |
KiroFactory Droid 還會自動註冊 `/brooks-review`。不熟悉 skills、或用的是上面沒列出的 agent
**[docs/getting-started.md](docs/getting-started.md)**。
KiroFactory Droid 與 DeepSeek Harness 還會自動註冊 `/brooks-review`。不熟悉 skills、或用的是上面
沒列出的 agent**[docs/getting-started.md](docs/getting-started.md)**。
> **🧪 驗證狀態。** Claude Code、Gemini CLI、Codex CLI 已由維護者驗證。上面個平台依據各工具官方技能規範撰寫,
> **🧪 驗證狀態。** Claude Code、Gemini CLI、Codex CLI 已由維護者驗證。上面個平台依據各工具官方技能規範撰寫,
> 並已在檔案佈局層面驗證(安裝器經過測試),但維護者尚未在每個平台端到端實跑。在某平台試過了——無論成功**還是**失敗?
> 請[提一個 issue](https://github.com/hyhmrright/brooks-lint/issues/new),附上平台、版本和你看到的結果。
> 用的是其他相容 Agent Skills 的 agent?它幾乎肯定以同樣方式運作——告訴我們,我們會補上。
@@ -316,9 +317,10 @@ Kiro 與 Factory Droid 還會自動註冊 `/brooks-review`。不熟悉 skills、
**各平台語法。** Claude Code 也接受帶命名空間的完整形式 `/brooks-lint:brooks-review`——短命令由
session-start 鉤子在首次工作階段啟動時自動安裝。Codex CLI 用 `$brooks-review`。Gemini CLI 直接用上表。
OpenCode、Cursor、Antigravity、pi 依據每個技能的 `description` 自動呼叫 Agent Skills,直接提問即可
(「審查這個 PR」、「我們最糟的技術債在哪」);需要顯式呼叫時用各平台自己的語法pi 把每個技能註冊為
`/skill:brooks-review`)。在所有平台上,當你討論程式碼品質、架構或測試健康時,這些技能也會自動觸發。
OpenCode、Cursor、Antigravity、pi、DeepSeek Harness 依據每個技能的 `description` 自動呼叫 Agent
Skills,直接提問即可(「審查這個 PR」、「我們最糟的技術債在哪」);需要顯式呼叫時用各平台自己的語法
pi 把每個技能註冊為 `/skill:brooks-review`dsh 直接用上表,可從 `/` 選單挑或手打)。在所有平台上,
當你討論程式碼品質、架構或測試健康時,這些技能也會自動觸發。
> PR 審查會自動包含一個輕量的第 7 步快速測試檢查(對純文件 diff 會跳過)。需要完整的測試稽核請用
> `/brooks-test`;需要某個維度的深度診斷時,請用該維度的專項技能,而不是 `/brooks-health`
@@ -437,6 +439,18 @@ jobs:
`fail-on-regression` 讀取 `.brooks-lint-history.json`,因此提交該檔案即可強制「無新增回歸」。設定 `sarif-file` 會讓診斷直接顯示在 PR 的 **Files changed** 分頁,並需要 job 具備 `security-events: write` 權限。
**自訂 API 端點。** `api-base-url` 讓 action 改為呼叫任意 Anthropic 相容的 `/v1/messages` 端點 —— 自建代理、LLM 閘道或區域鏡像 —— 而不是 `api.anthropic.com`。把該端點的金鑰作為 `anthropic-api-key` 傳入,把它預期的模型 id 作為 `model` 傳入:
```yaml
with:
mode: review
api-base-url: https://your-gateway.example.com
anthropic-api-key: ${{ secrets.GATEWAY_API_KEY }}
model: gateway-model-id
```
brooks-lint 會把你的 diff 傳送到這裡填寫的主機,因此只應指向你信任其接觸原始碼的一方。自己執行 `scripts/ci-review.mjs` 完全不需要參數 —— Anthropic SDK 會直接讀取 `ANTHROPIC_BASE_URL`
**成本:** 每次 PR 執行約 $0.05–0.15,取決於 diff 大小和模型。建議僅在 `pull_request` 事件上執行。
## 路線圖
@@ -472,7 +486,7 @@ MIT License——詳見 [LICENSE](LICENSE)。
## Star 歷史
[![Star History Chart](https://api.star-history.com/svg?repos=hyhmrright/brooks-lint&type=Date)](https://star-history.com/#hyhmrright/brooks-lint&Date)
[![Star History](assets/star-history.svg)](https://github.com/hyhmrright/brooks-lint/stargazers)
---
+2 -2
View File
@@ -1,8 +1,8 @@
# Source
- Repo: https://github.com/hyhmrright/brooks-lint
- Ref: 814174cd5b340bc0d8b0161b6d8288980428a44d
- Ref: e9acf5c6100d2d786ed58f0fb7b9566a6218926a
- Remove-Paths:
- Snapshot: 2026-08-12
- Snapshot: 2026-08-24
- Sync-Mode: copy_skill_dirs
- Notes: vendored into playbook branch thirdparty/skill
File diff suppressed because it is too large Load Diff
+45
View File
@@ -0,0 +1,45 @@
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="400" viewBox="0 0 800 400" role="img" aria-label="Star history for hyhmrright/brooks-lint: 1393 stars">
<style>
.bg { fill: #ffffff; }
.title { fill: #111827; font: 600 16px -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; }
.sub { fill: #6b7280; font: 400 12px -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; }
.tick { fill: #6b7280; font: 400 11px -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; }
.grid { stroke: #e5e7eb; stroke-width: 1; }
.axis { stroke: #d1d5db; stroke-width: 1; }
.total { fill: #3b82f6; font: 600 13px -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; }
@media (prefers-color-scheme: dark) {
.bg { fill: #0d1117; }
.title { fill: #e6edf3; }
.sub, .tick { fill: #8b949e; }
.grid { stroke: #21262d; }
.axis { stroke: #30363d; }
}
</style>
<defs>
<linearGradient id="fade" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.28"/>
<stop offset="100%" stop-color="#3b82f6" stop-opacity="0.02"/>
</linearGradient>
</defs>
<rect width="800" height="400" class="bg"/>
<text x="64" y="26" class="title">Star History</text>
<text x="776" y="26" class="sub" text-anchor="end">hyhmrright/brooks-lint</text>
<g>
<line x1="64" y1="352.0" x2="776" y2="352.0" class="grid"/><text x="54" y="356.0" class="tick" text-anchor="end">0</text>
<line x1="64" y1="249.3" x2="776" y2="249.3" class="grid"/><text x="54" y="253.3" class="tick" text-anchor="end">500</text>
<line x1="64" y1="146.7" x2="776" y2="146.7" class="grid"/><text x="54" y="150.7" class="tick" text-anchor="end">1,000</text>
<line x1="64" y1="44.0" x2="776" y2="44.0" class="grid"/><text x="54" y="48.0" class="tick" text-anchor="end">1,500</text>
</g>
<line x1="64" y1="352" x2="776" y2="352" class="axis"/>
<path d="M64.0 351.8L82.8 350.8L92.5 349.7L99.2 348.7L116.3 347.7L125.3 346.7L156.4 345.6L168.8 344.6L178.1 343.6L186.6 342.6L194.8 341.5L198.1 340.5L205.9 339.5L212.1 338.4L215.9 337.4L218.4 336.4L222.5 335.4L227.0 334.3L229.0 333.3L239.8 332.3L246.3 331.3L257.3 330.2L259.1 329.2L268.1 328.2L279.8 327.2L290.7 326.1L296.3 325.1L303.7 324.1L312.2 323.0L318.6 322.0L322.8 321.0L330.8 320.0L344.8 318.9L349.9 317.9L355.5 316.9L360.2 315.9L372.6 314.8L386.8 313.8L404.4 312.8L413.5 311.8L413.6 310.7L413.7 309.7L413.8 308.7L413.8 307.6L413.8 306.6L413.8 305.6L413.9 304.6L413.9 303.5L413.9 302.5L413.9 301.5L414.0 300.5L414.0 299.4L414.0 298.4L414.1 297.4L414.1 296.4L414.1 295.3L414.2 294.3L414.2 293.3L414.2 292.2L414.3 291.2L414.3 290.2L414.4 289.2L414.4 288.1L414.5 287.1L414.5 286.1L414.5 285.1L414.6 284.0L414.6 283.0L414.6 282.0L414.7 281.0L414.7 279.9L414.7 278.9L414.8 277.9L414.8 276.8L414.8 275.8L414.8 274.8L414.9 273.8L414.9 272.7L414.9 271.7L414.9 270.7L414.9 269.7L414.9 268.6L415.0 267.6L415.0 266.6L415.0 265.6L415.0 264.5L415.1 263.5L415.1 262.5L415.1 261.4L415.2 260.4L415.2 259.4L415.3 258.4L415.3 257.3L415.3 256.3L415.3 255.3L415.4 254.3L415.4 253.2L415.4 252.2L415.4 251.2L415.5 250.2L415.5 249.1L415.5 248.1L415.6 247.1L415.6 246.0L415.6 245.0L415.6 244.0L415.7 243.0L415.7 241.9L415.7 240.9L415.7 239.9L415.7 238.9L415.8 237.8L415.8 236.8L415.8 235.8L415.8 234.8L415.8 233.7L415.9 232.7L415.9 231.7L415.9 230.6L416.0 229.6L416.0 228.6L416.1 227.6L416.2 226.5L416.2 225.5L416.3 224.5L416.4 223.5L416.6 222.4L416.8 221.4L416.9 220.4L417.0 219.4L417.1 218.3L417.1 217.3L417.2 216.3L417.3 215.2L417.4 214.2L417.4 213.2L417.5 212.2L417.5 211.1L417.6 210.1L417.7 209.1L417.7 208.1L417.8 207.0L417.9 206.0L417.9 205.0L418.0 204.0L418.1 202.9L418.2 201.9L418.3 200.9L418.4 199.8L418.6 198.8L418.7 197.8L418.8 196.8L418.9 195.7L419.0 194.7L419.1 193.7L419.2 192.7L419.4 191.6L419.5 190.6L419.5 189.6L419.6 188.6L419.7 187.5L419.8 186.5L420.0 185.5L420.1 184.4L420.2 183.4L420.3 182.4L420.4 181.4L420.5 180.3L420.6 179.3L420.8 178.3L421.2 177.3L421.9 176.2L422.6 175.2L423.4 174.2L423.6 173.2L424.1 172.1L425.9 171.1L427.4 170.1L428.8 169.0L430.5 168.0L434.1 167.0L437.1 166.0L438.3 164.9L439.4 163.9L442.1 162.9L442.2 161.9L442.3 160.8L442.4 159.8L442.6 158.8L442.7 157.8L442.8 156.7L443.0 155.7L443.2 154.7L443.6 153.6L443.7 152.6L443.8 151.6L444.1 150.6L444.3 149.5L444.5 148.5L444.6 147.5L444.7 146.5L444.8 145.4L444.9 144.4L444.9 143.4L445.3 142.4L445.9 141.3L446.5 140.3L446.7 139.3L447.2 138.2L447.7 137.2L448.4 136.2L452.1 135.2L452.7 134.1L453.3 133.1L454.4 132.1L457.6 131.1L460.0 130.0L462.9 129.0L463.8 128.0L468.1 127.0L473.2 125.9L478.9 124.9L482.0 123.9L485.9 122.8L492.4 121.8L493.5 120.8L501.3 119.8L505.4 118.7L508.4 117.7L517.8 116.7L520.3 115.7L523.7 114.6L527.5 113.6L531.8 112.6L535.5 111.6L542.7 110.5L551.8 109.5L559.4 108.5L561.3 107.4L565.7 106.4L575.1 105.4L578.5 104.4L587.9 103.3L589.5 102.3L593.6 101.3L604.0 100.3L620.8 99.2L631.3 98.2L634.9 97.2L656.5 96.2L658.1 95.1L662.9 94.1L671.4 93.1L672.3 92.0L675.8 91.0L687.6 90.0L698.1 89.0L717.6 87.9L725.3 86.9L732.2 85.9L747.1 84.9L747.6 83.8L748.3 82.8L749.0 81.8L749.5 80.8L750.2 79.7L750.8 78.7L751.1 77.7L751.8 76.6L752.6 75.6L754.4 74.6L755.3 73.6L756.8 72.5L760.1 71.5L762.4 70.5L765.0 69.5L769.5 68.4L770.7 67.4L774.8 66.4L776.0 66.0L776.0 352.0L64.0 352.0Z" fill="url(#fade)"/>
<path d="M64.0 351.8L82.8 350.8L92.5 349.7L99.2 348.7L116.3 347.7L125.3 346.7L156.4 345.6L168.8 344.6L178.1 343.6L186.6 342.6L194.8 341.5L198.1 340.5L205.9 339.5L212.1 338.4L215.9 337.4L218.4 336.4L222.5 335.4L227.0 334.3L229.0 333.3L239.8 332.3L246.3 331.3L257.3 330.2L259.1 329.2L268.1 328.2L279.8 327.2L290.7 326.1L296.3 325.1L303.7 324.1L312.2 323.0L318.6 322.0L322.8 321.0L330.8 320.0L344.8 318.9L349.9 317.9L355.5 316.9L360.2 315.9L372.6 314.8L386.8 313.8L404.4 312.8L413.5 311.8L413.6 310.7L413.7 309.7L413.8 308.7L413.8 307.6L413.8 306.6L413.8 305.6L413.9 304.6L413.9 303.5L413.9 302.5L413.9 301.5L414.0 300.5L414.0 299.4L414.0 298.4L414.1 297.4L414.1 296.4L414.1 295.3L414.2 294.3L414.2 293.3L414.2 292.2L414.3 291.2L414.3 290.2L414.4 289.2L414.4 288.1L414.5 287.1L414.5 286.1L414.5 285.1L414.6 284.0L414.6 283.0L414.6 282.0L414.7 281.0L414.7 279.9L414.7 278.9L414.8 277.9L414.8 276.8L414.8 275.8L414.8 274.8L414.9 273.8L414.9 272.7L414.9 271.7L414.9 270.7L414.9 269.7L414.9 268.6L415.0 267.6L415.0 266.6L415.0 265.6L415.0 264.5L415.1 263.5L415.1 262.5L415.1 261.4L415.2 260.4L415.2 259.4L415.3 258.4L415.3 257.3L415.3 256.3L415.3 255.3L415.4 254.3L415.4 253.2L415.4 252.2L415.4 251.2L415.5 250.2L415.5 249.1L415.5 248.1L415.6 247.1L415.6 246.0L415.6 245.0L415.6 244.0L415.7 243.0L415.7 241.9L415.7 240.9L415.7 239.9L415.7 238.9L415.8 237.8L415.8 236.8L415.8 235.8L415.8 234.8L415.8 233.7L415.9 232.7L415.9 231.7L415.9 230.6L416.0 229.6L416.0 228.6L416.1 227.6L416.2 226.5L416.2 225.5L416.3 224.5L416.4 223.5L416.6 222.4L416.8 221.4L416.9 220.4L417.0 219.4L417.1 218.3L417.1 217.3L417.2 216.3L417.3 215.2L417.4 214.2L417.4 213.2L417.5 212.2L417.5 211.1L417.6 210.1L417.7 209.1L417.7 208.1L417.8 207.0L417.9 206.0L417.9 205.0L418.0 204.0L418.1 202.9L418.2 201.9L418.3 200.9L418.4 199.8L418.6 198.8L418.7 197.8L418.8 196.8L418.9 195.7L419.0 194.7L419.1 193.7L419.2 192.7L419.4 191.6L419.5 190.6L419.5 189.6L419.6 188.6L419.7 187.5L419.8 186.5L420.0 185.5L420.1 184.4L420.2 183.4L420.3 182.4L420.4 181.4L420.5 180.3L420.6 179.3L420.8 178.3L421.2 177.3L421.9 176.2L422.6 175.2L423.4 174.2L423.6 173.2L424.1 172.1L425.9 171.1L427.4 170.1L428.8 169.0L430.5 168.0L434.1 167.0L437.1 166.0L438.3 164.9L439.4 163.9L442.1 162.9L442.2 161.9L442.3 160.8L442.4 159.8L442.6 158.8L442.7 157.8L442.8 156.7L443.0 155.7L443.2 154.7L443.6 153.6L443.7 152.6L443.8 151.6L444.1 150.6L444.3 149.5L444.5 148.5L444.6 147.5L444.7 146.5L444.8 145.4L444.9 144.4L444.9 143.4L445.3 142.4L445.9 141.3L446.5 140.3L446.7 139.3L447.2 138.2L447.7 137.2L448.4 136.2L452.1 135.2L452.7 134.1L453.3 133.1L454.4 132.1L457.6 131.1L460.0 130.0L462.9 129.0L463.8 128.0L468.1 127.0L473.2 125.9L478.9 124.9L482.0 123.9L485.9 122.8L492.4 121.8L493.5 120.8L501.3 119.8L505.4 118.7L508.4 117.7L517.8 116.7L520.3 115.7L523.7 114.6L527.5 113.6L531.8 112.6L535.5 111.6L542.7 110.5L551.8 109.5L559.4 108.5L561.3 107.4L565.7 106.4L575.1 105.4L578.5 104.4L587.9 103.3L589.5 102.3L593.6 101.3L604.0 100.3L620.8 99.2L631.3 98.2L634.9 97.2L656.5 96.2L658.1 95.1L662.9 94.1L671.4 93.1L672.3 92.0L675.8 91.0L687.6 90.0L698.1 89.0L717.6 87.9L725.3 86.9L732.2 85.9L747.1 84.9L747.6 83.8L748.3 82.8L749.0 81.8L749.5 80.8L750.2 79.7L750.8 78.7L751.1 77.7L751.8 76.6L752.6 75.6L754.4 74.6L755.3 73.6L756.8 72.5L760.1 71.5L762.4 70.5L765.0 69.5L769.5 68.4L770.7 67.4L774.8 66.4L776.0 66.0" fill="none" stroke="#3b82f6" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/>
<circle cx="776.0" cy="66.0" r="4" fill="#3b82f6"/>
<text x="764.0" y="70.0" class="total" text-anchor="end">1,393</text>
<g>
<text x="85.7" y="374" class="tick" text-anchor="middle">Apr 2026</text>
<text x="232.0" y="374" class="tick" text-anchor="middle">May</text>
<text x="383.1" y="374" class="tick" text-anchor="middle">Jun</text>
<text x="529.4" y="374" class="tick" text-anchor="middle">Jul</text>
<text x="680.6" y="374" class="tick" text-anchor="middle">Aug</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.1 KiB

+60
View File
@@ -0,0 +1,60 @@
# DeepSeek Harness (dsh) Setup
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`) is DeepSeek AI's
open-source agent harness — an "everything is a plugin" architecture with a Web UI, started with
`npx @deepseek-ai/dsh web`. It natively loads [Agent Skills](https://agentskills.io) and reads
`AGENTS.md`, so all six brooks-lint modes run with no conversion.
## Install
```bash
# simplest — one command (global)
curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts/install.sh | bash -s -- dsh
# from a clone
./scripts/install.sh dsh # global: ~/.dsh/skills (or $DSH_HOME/skills)
./scripts/install.sh dsh --project # this repo: ./.dsh/skills
```
Prefer a manual copy? Clone the repo and `cp -r skills/* ~/.dsh/skills/` — the contents, not the
`skills/` folder itself, so `_shared/` lands as a sibling of the `brooks-*` folders.
dsh scans these skill roots, highest priority first, so an existing vendor-neutral install is picked
up automatically:
| Root | Notes |
|---|---|
| `<projectRoot>/.dsh/skills` | what `--project` writes |
| `<projectRoot>/.agents/skills` | shared with Cursor, Copilot, pi |
| `$DSH_HOME/skills` (default `~/.dsh/skills`) | what the global install writes |
| `$DSH_AGENTS_HOME/skills` (default `~/.agents/skills`) | `./scripts/install.sh agents` also covers dsh |
The project root is the nearest ancestor containing `.git`; without one, dsh uses the current
directory. When the same skill name appears in two roots, the higher one wins.
## Invoke
Just ask — dsh routes to a skill from its `description`:
- "review this PR" → `brooks-review`
- "audit the architecture" → `brooks-audit`
- "where's our worst tech debt?" → `brooks-debt`
For explicit invocation, type `/` in the Web UI prompt and pick the skill, or type the token by hand:
`/brooks-review`, `/brooks-audit`, `/brooks-debt`, `/brooks-test`, `/brooks-health`, `/brooks-sweep`.
dsh recognises a whitespace-bounded `/name` anywhere in a message and injects that skill's body
deterministically. The repo's `AGENTS.md` carries the Iron Law (Symptom → Source → Consequence →
Remedy) and the Health Score rules; dsh also loads `$DSH_HOME/AGENTS.md` plus every `AGENTS.md` from
the project root down to your working directory.
## Notes
- **Flat layout** is mandatory (the installer guarantees it): discovery is one level deep
(`<root>/<name>/SKILL.md`), never recursive, and the skills read `../_shared/`, which only resolves
when `_shared/` sits beside the `brooks-*` folders. `_shared/` itself has no `SKILL.md`, so dsh
ignores it as a skill and the modes read it as ordinary files.
- dsh is in developer preview and warns of compatibility-breaking changes; the skill discovery
contract above reflects `packages/skill/skill-filesystem` as of August 2026.
- 🧪 Documented per the official [skills subsystem](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/skills.md)
docs; community end-to-end verification welcome —
[open an issue](https://github.com/hyhmrright/brooks-lint/issues/new).
+6 -5
View File
@@ -33,10 +33,10 @@ layout wrong:
curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts/install.sh | bash -s -- <platform>
```
`<platform>` ∈ `opencode · cursor · windsurf · antigravity · pi · kiro · copilot · droid · gemini ·
codex · claude · agents`. Add `--project` to install into the current repo instead of your global
config. `agents` targets the vendor-neutral `~/.agents/skills` folder that Cursor, Copilot, pi, Gemini,
and Codex all read.
`<platform>` ∈ `opencode · cursor · windsurf · antigravity · pi · kiro · copilot · droid · dsh ·
gemini · codex · claude · agents`. Add `--project` to install into the current repo instead of your
global config. `agents` targets the vendor-neutral `~/.agents/skills` folder that Cursor, Copilot, pi,
Gemini, Codex, and DeepSeek Harness all read.
## Per-platform guides
@@ -50,6 +50,7 @@ and Codex all read.
| GitHub Copilot | [copilot-setup.md](copilot-setup.md) | `.github/skills`, `.claude/skills`, `~/.copilot/skills` | ✅ |
| Kiro | [kiro-setup.md](kiro-setup.md) | `.kiro/skills`, `~/.kiro/skills` | ✅ |
| Factory Droid | [factory-droid-setup.md](factory-droid-setup.md) | `~/.factory/skills`, `.factory/skills`, `.agent/skills` | ✅ |
| DeepSeek Harness | [dsh-setup.md](dsh-setup.md) | `.dsh/skills`, `.agents/skills`, `~/.dsh/skills`, `~/.agents/skills` | ✅ |
For Claude Code, Gemini CLI, and Codex CLI, see the [README install section](../README.md#installation).
`./scripts/install.sh gemini` and `./scripts/install.sh codex` also work and use the flat layout these
@@ -68,7 +69,7 @@ If your agent accepts a skills folder or an instruction file, brooks-lint works:
## Verification status
The marketplace-installed platforms (Claude Code, Gemini CLI, Codex CLI) are maintainer-verified. The
eight Agent-Skills platforms above are documented from each tool's official skill spec and verified at
nine Agent-Skills platforms above are documented from each tool's official skill spec and verified at
the file-layout level (the installer is tested), but not yet end-to-end run by the maintainer on every
platform. **Tried one? Tell us** — [open an issue](https://github.com/hyhmrright/brooks-lint/issues/new)
with the platform, version, and what you saw, working or broken.
@@ -28,3 +28,4 @@ jobs:
# fail-on: critical # fail on any Critical finding (none | warning | critical)
# fail-on-regression: true # fail if the Health Score dropped vs the last run
# sarif-file: brooks-lint.sarif # also upload findings to GitHub Code Scanning
# api-base-url: https://your-gateway.example.com # any Anthropic-compatible /v1/messages endpoint
+1 -1
View File
@@ -28,7 +28,7 @@
"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.4.3",
"softwareVersion": "1.5.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",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "brooks-lint",
"version": "1.4.3",
"version": "1.5.0",
"description": "AI code reviews grounded in twelve classic engineering books — decay risk diagnostics with book citations, severity labels, and six analysis modes (PR review, architecture audit, tech debt, test quality, health dashboard, full-sweep auto-fix)",
"author": "hyhmrright",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "brooks-lint",
"version": "1.4.3",
"version": "1.5.0",
"type": "module",
"scripts": {
"bump": "node scripts/bump-version.mjs",
+3 -1
View File
@@ -101,7 +101,9 @@ try {
process.exit(1);
}
const report = message.content[0]?.text ?? "";
// The first block is not always the text one — an endpoint that returns a
// thinking block first would otherwise yield undefined.
const report = message.content.find((block) => block.type === "text")?.text ?? "";
const scoreMatch = report.match(/Health\s+Score[:\s]+(\d+)/i);
const score = scoreMatch ? parseInt(scoreMatch[1], 10) : null;
+241
View File
@@ -0,0 +1,241 @@
// Generates the README star-history chart from first-party GitHub data.
//
// GitHub restricted the public stargazers API to a repository's own admins and
// collaborators (announced 2026-06-30), which broke every third-party chart
// service — api.star-history.com now serves an error card instead of a chart.
// We own this repo, so we read the star data ourselves and commit the result,
// keeping the README free of any third-party image host.
//
// The committed dataset, not the drawing, is the source of truth: the SVG is a
// pure function of assets/star-history.json. That keeps the chart re-renderable
// with no credentials if the endpoint tightens further, keeps the weekly diff
// readable (dates, not shifted path coordinates), and lets `npm run validate`
// prove the two files agree.
//
// Run: node scripts/gen-star-history.mjs → refetch, rewrite both files
// node scripts/gen-star-history.mjs --render-only → redraw the SVG offline
import { execFileSync } from "node:child_process";
import { readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
// README-only assets, so they live in assets/ alone — unlike the banners, the
// GitHub Pages site under docs/ does not render them.
const OUT = join(ROOT, "assets", "star-history.svg");
const DATA = join(ROOT, "assets", "star-history.json");
const REPO = process.env.GITHUB_REPOSITORY ?? "hyhmrright/brooks-lint";
const W = 800, H = 400;
const PAD = { top: 44, right: 24, bottom: 48, left: 64 };
const PLOT_W = W - PAD.left - PAD.right;
const PLOT_H = H - PAD.top - PAD.bottom;
const ACCENT = "#3b82f6"; // matches the logo palette used by gen-banner.mjs
const FONT = `-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif`;
const esc = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
// The stargazers endpoint is no longer public: it needs credentials that can
// read this repo. CI supplies GITHUB_TOKEN; locally we borrow the gh CLI's.
function resolveToken() {
if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;
try {
return execFileSync("gh", ["auth", "token"], { encoding: "utf8" }).trim();
} catch {
throw new Error(
"No GITHUB_TOKEN set and `gh auth token` failed. Since GitHub restricted " +
"the stargazers API, this script needs credentials for a repo admin or collaborator.",
);
}
}
// Returns the raw starred_at strings, oldest first — exactly what we commit.
async function fetchStarTimestamps(token) {
const stamps = [];
for (let page = 1; ; page++) {
const url = `https://api.github.com/repos/${REPO}/stargazers?per_page=100&page=${page}`;
const res = await fetch(url, {
headers: {
// The star+json media type is what adds starred_at to each entry.
Accept: "application/vnd.github.star+json",
Authorization: `Bearer ${token}`,
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "brooks-lint-gen-star-history",
},
});
// Past 400 pages GitHub answers 422 rather than paginating, so a repo above
// 40,000 stars can no longer be read in full — the committed dataset is what
// preserves the history when that day comes.
if (!res.ok) {
throw new Error(`GitHub API ${res.status} on page ${page}: ${(await res.text()).slice(0, 200)}`);
}
const batch = await res.json();
for (const entry of batch) {
// A missing starred_at means the star+json media type stopped being
// honoured. Fail loudly rather than plot NaN coordinates.
if (Number.isNaN(Date.parse(entry.starred_at))) {
throw new Error(`Stargazer without a usable starred_at on page ${page}.`);
}
stamps.push(entry.starred_at);
}
if (batch.length < 100) return stamps.sort((a, b) => Date.parse(a) - Date.parse(b));
}
}
export function readStamps() {
return JSON.parse(readFileSync(DATA, "utf8")).starredAt;
}
function writeStamps(stamps) {
// Deliberately no generated-at field: the file has to stay byte-identical when
// no star was added, or the workflow's "commit only when it moved" guard would
// fire every single run. Git already records when it last changed.
writeFileSync(DATA, `${JSON.stringify({ repo: REPO, starredAt: stamps }, null, 2)}\n`);
}
// Round the axis maximum up to a 1/2/5 × 10ⁿ step so tick labels stay readable.
function niceStep(max, targetTicks) {
const raw = max / targetTicks;
const mag = 10 ** Math.floor(Math.log10(raw));
for (const m of [1, 2, 5]) if (raw <= m * mag) return m * mag;
return 10 * mag;
}
// One tick per month, thinned out so labels never collide on a long history.
function monthTicks(from, to) {
const all = [];
const cursor = new Date(from);
cursor.setUTCDate(1);
cursor.setUTCHours(0, 0, 0, 0);
if (cursor.getTime() < from) cursor.setUTCMonth(cursor.getUTCMonth() + 1);
while (cursor.getTime() <= to) {
all.push(cursor.getTime());
cursor.setUTCMonth(cursor.getUTCMonth() + 1);
}
const stride = Math.ceil(all.length / 8) || 1;
return all.filter((_, i) => i % stride === 0);
}
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
function monthLabel(ms, showYear) {
const d = new Date(ms);
const month = MONTHS[d.getUTCMonth()];
return showYear ? `${month} ${d.getUTCFullYear()}` : month;
}
// 1,392 points would bloat the committed SVG for no visible gain. Keep every
// nth sample plus the exact final point, so the headline count stays truthful.
function downsample(points, limit) {
if (points.length <= limit) return points;
const stride = Math.ceil(points.length / limit);
const kept = points.filter((_, i) => i % stride === 0);
if (kept.at(-1) !== points.at(-1)) kept.push(points.at(-1));
return kept;
}
export function render(stamps) {
const times = stamps.map((iso) => Date.parse(iso));
const total = times.length;
const t0 = times[0];
// The axis ends at the newest star rather than at "now". Anchoring it to the
// clock would shift every x coordinate on every run, so the workflow could
// never tell a real change from a redraw and would commit noise weekly.
const t1 = times.at(-1);
// Stars are whole numbers, so never let a tiny repo produce fractional ticks.
const step = Math.max(1, niceStep(total, 5));
const yMax = Math.ceil(total / step) * step;
const x = (ms) => PAD.left + ((ms - t0) / (t1 - t0)) * PLOT_W;
const y = (n) => PAD.top + PLOT_H - (n / yMax) * PLOT_H;
const points = downsample(
times.map((ms, i) => [ms, i + 1]),
300,
);
const line = points.map(([ms, n], i) => `${i === 0 ? "M" : "L"}${x(ms).toFixed(1)} ${y(n).toFixed(1)}`).join("");
const area = `${line}L${x(t1).toFixed(1)} ${y(0).toFixed(1)}L${x(t0).toFixed(1)} ${y(0).toFixed(1)}Z`;
const yTicks = [];
for (let n = 0; n <= yMax; n += step) yTicks.push(n);
const xTicks = monthTicks(t0, t1);
const spansYears = new Date(t0).getUTCFullYear() !== new Date(t1).getUTCFullYear();
const gridLines = yTicks
.map(
(n) =>
`<line x1="${PAD.left}" y1="${y(n).toFixed(1)}" x2="${PAD.left + PLOT_W}" y2="${y(n).toFixed(1)}" class="grid"/>` +
`<text x="${PAD.left - 10}" y="${(y(n) + 4).toFixed(1)}" class="tick" text-anchor="end">${n.toLocaleString("en-US")}</text>`,
)
.join("\n ");
const xLabels = xTicks
.map(
(ms, i) =>
`<text x="${x(ms).toFixed(1)}" y="${PAD.top + PLOT_H + 22}" class="tick" text-anchor="middle">` +
`${monthLabel(ms, spansYears || i === 0)}</text>`,
)
.join("\n ");
// Anchoring the axis to the newest star puts the final point exactly on the
// right edge, so the callout always hangs back inside the plot.
const lastX = PAD.left + PLOT_W;
const lastY = y(total);
return `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" role="img" aria-label="Star history for ${esc(REPO)}: ${total} stars">
<style>
.bg { fill: #ffffff; }
.title { fill: #111827; font: 600 16px ${FONT}; }
.sub { fill: #6b7280; font: 400 12px ${FONT}; }
.tick { fill: #6b7280; font: 400 11px ${FONT}; }
.grid { stroke: #e5e7eb; stroke-width: 1; }
.axis { stroke: #d1d5db; stroke-width: 1; }
.total { fill: ${ACCENT}; font: 600 13px ${FONT}; }
@media (prefers-color-scheme: dark) {
.bg { fill: #0d1117; }
.title { fill: #e6edf3; }
.sub, .tick { fill: #8b949e; }
.grid { stroke: #21262d; }
.axis { stroke: #30363d; }
}
</style>
<defs>
<linearGradient id="fade" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="${ACCENT}" stop-opacity="0.28"/>
<stop offset="100%" stop-color="${ACCENT}" stop-opacity="0.02"/>
</linearGradient>
</defs>
<rect width="${W}" height="${H}" class="bg"/>
<text x="${PAD.left}" y="26" class="title">Star History</text>
<text x="${W - PAD.right}" y="26" class="sub" text-anchor="end">${esc(REPO)}</text>
<g>
${gridLines}
</g>
<line x1="${PAD.left}" y1="${PAD.top + PLOT_H}" x2="${PAD.left + PLOT_W}" y2="${PAD.top + PLOT_H}" class="axis"/>
<path d="${area}" fill="url(#fade)"/>
<path d="${line}" fill="none" stroke="${ACCENT}" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/>
<circle cx="${lastX.toFixed(1)}" cy="${lastY.toFixed(1)}" r="4" fill="${ACCENT}"/>
<text x="${(lastX - 12).toFixed(1)}" y="${(lastY + 4).toFixed(1)}" class="total" text-anchor="end">${total.toLocaleString("en-US")}</text>
<g>
${xLabels}
</g>
</svg>
`;
}
async function main() {
const renderOnly = process.argv.includes("--render-only");
const stamps = renderOnly ? readStamps() : await fetchStarTimestamps(resolveToken());
// Two points are the minimum a time axis can span; one would divide by zero
// and silently write a chart full of NaN coordinates.
if (stamps.length < 2) throw new Error(`Only ${stamps.length} stargazer(s) for ${REPO} — refusing to write a chart.`);
if (!renderOnly) writeStamps(stamps);
writeFileSync(OUT, render(stamps));
console.log(`Wrote ${OUT}${stamps.length} stars through ${stamps.at(-1).slice(0, 10)}`);
}
// Importable so `npm run validate` can re-render from the committed data and
// prove the SVG is in sync; only a direct run touches the network or disk.
if (process.argv[1] === fileURLToPath(import.meta.url)) await main();
+7 -3
View File
@@ -13,8 +13,9 @@
# ./scripts/install.sh <platform> [--project]
# curl -fsSL https://raw.githubusercontent.com/hyhmrright/brooks-lint/main/scripts/install.sh | bash -s -- <platform>
#
# Platforms: opencode cursor windsurf antigravity pi kiro copilot droid gemini codex claude agents
# agents = the vendor-neutral ~/.agents/skills folder (read by Cursor, Copilot, pi, Gemini, Codex)
# Platforms: opencode cursor windsurf antigravity pi kiro copilot droid dsh gemini codex claude agents
# agents = the vendor-neutral ~/.agents/skills folder (read by Cursor, Copilot, pi, Gemini,
# Codex, and DeepSeek Harness)
#
# Flags:
# --project install into the current repo (./.<platform>/skills) instead of the global folder
@@ -24,7 +25,7 @@
set -euo pipefail
REPO_URL="https://github.com/hyhmrright/brooks-lint.git"
PLATFORMS="opencode cursor windsurf antigravity pi kiro copilot droid gemini codex claude agents"
PLATFORMS="opencode cursor windsurf antigravity pi kiro copilot droid dsh gemini codex claude agents"
err() { printf '\033[31merror:\033[0m %s\n' "$*" >&2; }
info() { printf '\033[36m\033[0m %s\n' "$*"; }
@@ -59,6 +60,8 @@ global_dir() {
kiro) printf '%s' "$HOME/.kiro/skills" ;;
copilot) printf '%s' "$HOME/.copilot/skills" ;;
droid) printf '%s' "$HOME/.factory/skills" ;;
# DeepSeek Harness resolves its config root from $DSH_HOME, falling back to ~/.dsh.
dsh) printf '%s' "${DSH_HOME:-$HOME/.dsh}/skills" ;;
gemini) printf '%s' "$HOME/.gemini/skills" ;;
codex) printf '%s' "$HOME/.codex/skills" ;;
claude) printf '%s' "$HOME/.claude/skills" ;;
@@ -77,6 +80,7 @@ project_dir() {
kiro) printf '%s' "$PWD/.kiro/skills" ;;
copilot) printf '%s' "$PWD/.github/skills" ;;
droid) printf '%s' "$PWD/.factory/skills" ;;
dsh) printf '%s' "$PWD/.dsh/skills" ;;
gemini) printf '%s' "$PWD/.gemini/skills" ;;
codex) printf '%s' "$PWD/.codex/skills" ;;
claude) printf '%s' "$PWD/.claude/skills" ;;
+64
View File
@@ -0,0 +1,64 @@
/**
* Platform-inventory helpers shared by validate-repo.mjs and its tests.
*
* Nothing here hardcodes a platform or a translation. The platforms carrying an
* install-table row are discovered from the docs/<name>-setup.md files on disk,
* the documents that must show that table are discovered from README*.md, and
* the installer's own list is parsed out of scripts/install.sh so a new
* platform, or a seventh language, is covered on arrival. Keeping a separate
* hand-maintained list is what let the localized README badges go stale before
* (see version-refs.mjs).
*/
import { readdirSync } from "node:fs";
import path from "node:path";
/**
* Every document that carries the per-platform install table: all README
* translations, plus the getting-started guide, which is the one non-README
* page with a platform table of its own.
*/
export function platformDocs(root) {
const readmes = readdirSync(root)
.filter((file) => file.startsWith("README") && file.endsWith(".md"))
.sort();
return [...readmes, path.join("docs", "getting-started.md")];
}
/** Every per-platform setup guide, as bare filenames. */
export function setupGuides(root) {
return readdirSync(path.join(root, "docs"))
.filter((file) => file.endsWith("-setup.md"))
.sort();
}
/**
* Setup-guide filenames linked from one document, normalized so a README's
* `docs/kiro-setup.md` and getting-started's sibling `kiro-setup.md` compare
* equal. Returns a sorted, de-duplicated array.
*/
export function linkedSetupGuides(text) {
const links = text.matchAll(/\((?:docs\/)?([a-z0-9-]+-setup\.md)\)/g);
return [...new Set([...links].map((match) => match[1]))].sort();
}
/**
* The installer's platform list plus the platforms each directory-mapping
* function actually handles. A platform in PLATFORMS with no case arm fails
* `install.sh <platform>` with "unknown platform"; a case arm missing from
* PLATFORMS is invisible in --list and the help text.
*/
export function parseInstallerPlatforms(text) {
const declared = text.match(/^PLATFORMS="([^"]*)"/m)?.[1].trim();
return {
declared: declared ? declared.split(/\s+/) : [],
global: caseArms(text, "global_dir"),
project: caseArms(text, "project_dir"),
};
}
/** Platform names matched by the `case` arms of one shell function. */
function caseArms(text, fnName) {
const body = text.match(new RegExp(`^${fnName}\\(\\) \\{$([\\s\\S]*?)^\\}$`, "m"))?.[1] ?? "";
return [...body.matchAll(/^\s+([a-z][a-z0-9-]*)\)/gm)].map((match) => match[1]);
}
+1 -1
View File
@@ -81,7 +81,7 @@ for (const scenario of scenarios) {
system: systemPrompt,
messages: [{ role: "user", content: userMessage }],
});
aiText = message.content[0]?.text ?? "";
aiText = message.content.find((block) => block.type === "text")?.text ?? "";
verdict = classify(scenario, aiText);
} catch (err) {
error = err.message;
+48
View File
@@ -15,6 +15,8 @@ import {
} from "./frontmatter.mjs";
import { GUIDE_BY_MODE, VALID_MODES } from "./assemble-prompt.mjs";
import { versionRefs } from "./version-refs.mjs";
import { platformDocs, setupGuides, linkedSetupGuides, parseInstallerPlatforms } from "./platforms.mjs";
import { render as renderStarHistory, readStamps as readStarStamps } from "./gen-star-history.mjs";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(__dirname, "..");
@@ -325,6 +327,49 @@ function checkAgentsDocs() {
}
}
// Every docs/<name>-setup.md must be linked from all six READMEs and the
// getting-started table. A platform added to one language and forgotten in the
// others was previously caught only by hand.
function checkPlatformDocs() {
const guides = setupGuides(root);
check(guides.length > 0, "docs/ should contain at least one <platform>-setup.md guide");
for (const file of platformDocs(root)) {
const linked = linkedSetupGuides(readText(file));
for (const guide of guides) {
check(linked.includes(guide), `${file} is missing an install-table link to docs/${guide}`);
}
for (const link of linked) {
check(guides.includes(link), `${file} links to docs/${link}, which does not exist`);
}
}
}
function checkInstallerPlatforms() {
const { declared, global: globalArms, project } = parseInstallerPlatforms(readText("scripts/install.sh"));
check(declared.length > 0, "scripts/install.sh should declare a PLATFORMS list");
for (const [arms, fn] of [[globalArms, "global_dir"], [project, "project_dir"]]) {
for (const platform of declared) {
check(arms.includes(platform), `scripts/install.sh ${fn}() has no path for PLATFORMS entry '${platform}'`);
}
for (const platform of arms) {
check(declared.includes(platform), `scripts/install.sh ${fn}() maps '${platform}', which PLATFORMS omits`);
}
}
}
// assets/star-history.svg is a pure function of assets/star-history.json, so a
// mismatch means the chart was hand-edited or the data moved without a redraw.
// Re-rendering here needs no credentials, which is the point of committing the
// data rather than the drawing alone.
function checkStarHistory() {
check(
renderStarHistory(readStarStamps()) === readText("assets/star-history.svg"),
"assets/star-history.svg does not match assets/star-history.json — rerun `node scripts/gen-star-history.mjs --render-only`",
);
}
function checkSecurity() {
const security = readText("SECURITY.md");
check(!security.includes("<!--"), "SECURITY.md still contains placeholder content");
@@ -373,7 +418,10 @@ checkStepAlignment();
checkEvalSuite();
checkContributing();
checkAgentsDocs();
checkPlatformDocs();
checkInstallerPlatforms();
checkSecurity();
checkStarHistory();
checkHookOutput();
// ── Report ─────────────────────────────────────────────────────────────────
@@ -31,6 +31,7 @@ import { reportToSarif } from "./sarif.mjs";
import { severityBreached, isRegression } from "./ci-gate.mjs";
import { summarize } from "./benchmark.mjs";
import { versionRefs } from "./version-refs.mjs";
import { linkedSetupGuides, parseInstallerPlatforms } from "./platforms.mjs";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -905,6 +906,70 @@ test("patterns match the real badge and JSON-LD shapes", () => {
});
});
console.log("\nlinkedSetupGuides");
test("collects guides from README-style and sibling-style links alike", () => {
const text = "| Kiro | [setup](docs/kiro-setup.md) |\n| pi | [pi-setup.md](pi-setup.md) |";
assert.deepEqual(linkedSetupGuides(text), ["kiro-setup.md", "pi-setup.md"]);
});
test("de-duplicates a guide linked more than once", () => {
const text = "[a](docs/dsh-setup.md) … [b](dsh-setup.md)";
assert.deepEqual(linkedSetupGuides(text), ["dsh-setup.md"]);
});
test("returns an empty array when no guide is linked", () => {
assert.deepEqual(linkedSetupGuides("no links here"), []);
});
console.log("\nparseInstallerPlatforms");
const INSTALLER_FIXTURE = [
'PLATFORMS="kiro dsh"',
"",
"global_dir() {",
" case $1 in",
" kiro) printf '%s' \"$HOME/.kiro/skills\" ;;",
" # DeepSeek Harness resolves its config root from $DSH_HOME.",
" dsh) printf '%s' \"${DSH_HOME:-$HOME/.dsh}/skills\" ;;",
" *) return 1 ;;",
" esac",
"}",
"",
"project_dir() {",
" case $1 in",
" kiro) printf '%s' \"$PWD/.kiro/skills\" ;;",
" *) return 1 ;;",
" esac",
"}",
].join("\n");
test("reads the declared list and both directory mappings", () => {
const parsed = parseInstallerPlatforms(INSTALLER_FIXTURE);
assert.deepEqual(parsed.declared, ["kiro", "dsh"]);
assert.deepEqual(parsed.global, ["kiro", "dsh"]);
});
test("omits a platform whose case arm is missing, so the validator can catch it", () => {
// project_dir() in the fixture handles kiro but not dsh — running
// `install.sh dsh --project` would die with "unknown platform".
assert.deepEqual(parseInstallerPlatforms(INSTALLER_FIXTURE).project, ["kiro"]);
});
test("skips comments and the catch-all arm", () => {
const { global: arms } = parseInstallerPlatforms(INSTALLER_FIXTURE);
assert.ok(!arms.includes("*"));
assert.equal(arms.length, 2);
});
test("parses the real installer, proving the patterns still match", () => {
const installer = readFileSync(path.join(__dirname, "install.sh"), "utf8");
const { declared, global: globalArms, project } = parseInstallerPlatforms(installer);
assert.ok(declared.length >= 12, `expected the full platform list, got ${declared.length}`);
assert.deepEqual(new Set(globalArms), new Set(declared));
assert.deepEqual(new Set(project), new Set(declared));
});
// ── Integration: validate-repo.mjs passes against current repo ─────────────
console.log("\nvalidate-repo integration");