@payconductor/houndsman 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -3
  3. package/assets/houndsman.png +0 -0
  4. package/package.json +63 -5
  5. package/src/cli/index.ts +210 -0
  6. package/src/core/assets.ts +20 -0
  7. package/src/core/brief.ts +162 -0
  8. package/src/core/commands/context.ts +39 -0
  9. package/src/core/commands/review.ts +50 -0
  10. package/src/core/commands/scaffold.ts +373 -0
  11. package/src/core/commands/setup.ts +75 -0
  12. package/src/core/commands/test.ts +20 -0
  13. package/src/core/commands/update.ts +30 -0
  14. package/src/core/commands/watchdog.ts +105 -0
  15. package/src/core/config-loader.ts +87 -0
  16. package/src/core/config.ts +59 -0
  17. package/src/core/doc-discoverer.ts +33 -0
  18. package/src/core/file-classifier.ts +247 -0
  19. package/src/core/jev/index.ts +12 -0
  20. package/src/core/jev/jev-candidates.ts +105 -0
  21. package/src/core/jev/jev-client.ts +99 -0
  22. package/src/core/jev/jev-detector.ts +203 -0
  23. package/src/core/jev/jev-questions.ts +92 -0
  24. package/src/core/jev/jev-types.ts +91 -0
  25. package/src/core/orchestrator.ts +444 -0
  26. package/src/core/paths.ts +30 -0
  27. package/src/core/pr-context.ts +271 -0
  28. package/src/core/registry-check.ts +145 -0
  29. package/src/core/report-builder.ts +357 -0
  30. package/src/core/types.ts +137 -0
  31. package/src/core/watchdog-registry.ts +43 -0
  32. package/src/index.ts +56 -0
  33. package/src/skill/AUDIT-PROTOCOL.md +96 -0
  34. package/src/skill/OUTPUT-FORMAT.md +70 -0
  35. package/src/skill/README.md +116 -0
  36. package/src/skill/SETUP.md +99 -0
  37. package/src/skill/example-review.md +58 -0
  38. package/src/skill/houndsman-update.md +251 -0
  39. package/src/skill/houndsman.md +239 -0
  40. package/src/tests/fixtures/fake-project.ts +64 -0
  41. package/src/tests/review-scripts.test.ts +851 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Houndsman contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,195 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # houndsman
2
+
3
+ Orquestrador de revisão de Pull Requests. Classifica o diff, despacha **watchdogs**
4
+ especializados por domínio (um subagente por watchdog) e consolida os achados em um
5
+ relatório único.
6
+
7
+ > Review de PR no piloto automático: alguém aprova no "LGTM" sem ler, um `catch {}` engole
8
+ > o erro e ninguém nota. Sexta às 18h a produção cobra a conta — e aí todo mundo lembra
9
+ > daquela linha.
10
+
11
+ ![houndsman](assets/houndsman.png)
12
+
13
+ **Terminologia:** **houndsman** = orquestrador/treinador (CLI + motor) · **watchdog** = cada
14
+ agente especialista por domínio que audita o diff · **review/revisão** = o processo ·
15
+ **Jev** = pré-análise opcional.
16
+
17
+ ---
18
+
19
+ ## Como iniciar
20
+
21
+ Você monta o **seu time de cães de caça** (os watchdogs), cada um farejando um domínio
22
+ diferente das PRs. O motor só coordena a caçada — as regras são problema seu.
23
+
24
+ ### 1. Pré-requisitos
25
+
26
+ - **Bun** — runtime obrigatório.
27
+ - **git** — o diff usa `git diff <base>...<head>`.
28
+ - Opcionais, ambos **falha-aberto**: **`gh`** (contexto da PR) e **`TYPESAFE_API_KEY`**
29
+ (pré-análise Jev). Sem eles, a revisão roda igual.
30
+
31
+ ### 2. Instale o CLI (global)
32
+
33
+ ```bash
34
+ bun add -g @payconductor/houndsman
35
+ ```
36
+
37
+ ### 3. Configure o projeto (uma vez)
38
+
39
+ Na raiz do projeto:
40
+
41
+ ```bash
42
+ bun run houndsman setup
43
+ ```
44
+
45
+ Cria a camada `.houndsman/`, que é **autocontida** (não instala nada no seu projeto):
46
+
47
+ | Caminho | Dono | O que é |
48
+ |---|---|---|
49
+ | `houndsman.config.ts` | você | Configuração do projeto (um objeto de dados) |
50
+ | `.houndsman/watchdogs/` | você | Suas regras — começa com **1 watchdog de exemplo** |
51
+ | `.houndsman/exclusions.ts` | você | Arquivos que nunca entram na revisão |
52
+ | `.houndsman/doc-mappings.ts` | você | path → documentação de contexto |
53
+ | `.houndsman/core/**` | auto | Tipos + docs do skill (sincronizados por `houndsman update`) |
54
+ | `.agents/` · `.claude/` | auto | Ponteiros de skill dos harnesses |
55
+ | `package.json` | — | Adiciona o script `"houndsman"` |
56
+
57
+ > **Não existe "modo completo".** O `setup` já cria a camada inicial com um watchdog de
58
+ > exemplo — troque o exemplo pelas regras reais do seu projeto (ou use `/houndsman-update`).
59
+
60
+ ### 4. Escreva suas regras
61
+
62
+ Edite `.houndsman/watchdogs/` — veja [Escrevendo boas regras](#escrevendo-boas-regras).
63
+
64
+ ### 5. Valide
65
+
66
+ ```bash
67
+ bun run houndsman doctor
68
+ ```
69
+
70
+ ### 6. Rode a primeira revisão
71
+
72
+ No seu harness (Claude Code / agents), invoque o skill:
73
+
74
+ ```
75
+ /houndsman
76
+ ```
77
+
78
+ O skill orquestra tudo e grava o relatório em `.houndsman/.runs/<branch-slug>/review.md`.
79
+ Não precisa rodar nenhum script à mão.
80
+
81
+ ---
82
+
83
+ ## O fluxo de trabalho
84
+
85
+ ```
86
+ /houndsman
87
+ ├─ Passo 0 contexto da PR (gh, opcional)
88
+ ├─ Passo 1 classifica o diff → briefs por watchdog
89
+ ├─ Passo 2 N subagentes revisam em paralelo
90
+ └─ Passo 3 consolida em review.md
91
+ ```
92
+
93
+ - Revisão normal: só `/houndsman` (a branch é detectada; base default `main`).
94
+ - Revisar outra branch: `/houndsman feature/minha-branch`.
95
+ - Manual (sem skill): `houndsman context` → `houndsman --base main` → *(subagentes)* →
96
+ `houndsman --finalize`.
97
+ - CI: `houndsman --finalize --fail-on-blocking` (exit 1 se houver bloqueantes).
98
+
99
+ ---
100
+
101
+ ## Mantendo a configuração
102
+
103
+ - **Alterar regras** → use o skill **`/houndsman-update`**. Ele prova no código que o padrão
104
+ existe (evidência) antes de editar e roda as validações.
105
+ - **Atualizou a lib?** Sincronize os arquivos auto-gerados sem tocar nas suas regras:
106
+
107
+ ```bash
108
+ bun run houndsman update
109
+ bun run houndsman doctor
110
+ ```
111
+
112
+ O `update` reescreve **só** `.houndsman/core/**` e `.houndsman/.gitignore`. Seus arquivos
113
+ (`watchdogs/`, `exclusions.ts`, `doc-mappings.ts`, `houndsman.config.ts`) nunca são tocados.
114
+ - **Recomeçar a camada do zero** → `houndsman setup --force` (sobrescreve só os arquivos do
115
+ usuário; sem `--force` o `setup` é idempotente).
116
+
117
+ ---
118
+
119
+ ## Avançado
120
+
121
+ ### Comandos do dia a dia
122
+
123
+ | Comando | Função |
124
+ |---|---|
125
+ | `houndsman setup` | Cria a configuração inicial do projeto |
126
+ | `houndsman update` | Sincroniza os arquivos auto-gerados (`.houndsman/core`) |
127
+ | `houndsman doctor` | Valida a configuração e o ambiente (anti-drift) |
128
+ | `houndsman context` | Passo 0 — contexto da PR via `gh` |
129
+ | `houndsman --base main` | Passo 1 — prepara os briefs |
130
+ | `houndsman --finalize` | Passo 3 — gera o relatório |
131
+ | `houndsman watchdog <id>` | Contexto de um watchdog (`--rules`, `--json`) |
132
+
133
+ Flags: `--base <branch>` (default `main`) · `--head <ref>` (default `HEAD`) ·
134
+ `--branch <nome>` · `--dir <dir>` · `--config <path>` · `--clean` · `--no-jev` ·
135
+ `--fail-on-blocking` (com `--finalize`).
136
+
137
+ ### Escrevendo boas regras
138
+
139
+ O texto da regra é renderizado **literalmente** no brief do subagente — ambiguidade vira
140
+ ruído na revisão. Toda regra começa com um **prefixo de polaridade**:
141
+
142
+ - `Promover:` → algo que **DEVE** acontecer (o padrão precisa existir no código).
143
+ - `Evitar:` → algo que **NÃO DEVE** acontecer. `Não` e `Nunca` no início são equivalentes.
144
+
145
+ Boas práticas:
146
+
147
+ 1. **Só regra que o projeto aplica hoje** — prove com evidência (`arquivo:linha` para
148
+ afirmativas; `0 ocorrências via grep` do anti-padrão para negativas). Regra afirmativa sem
149
+ aplicação real gera falso positivo.
150
+ 2. **Geral e reutilizável** — descreva um padrão do domínio, não uma regra de negócio
151
+ amarrada a uma única classe/use-case (isso é papel de teste automatizado).
152
+ 3. **Condição inequívoca e critério concreto** — duas IAs chegam à mesma conclusão; use
153
+ limite/lista explícita ou símbolo real do código.
154
+ 4. **Sem jargão vago nem qualificador vazio** — nada de "o antigo", "isso", "apenas por
155
+ compatibilidade".
156
+ 5. **Declare o `classify`** do watchdog — regex de path (`filename`) ou de conteúdo
157
+ (`content`) para o motor achar os arquivos do domínio.
158
+ 6. **Prefira editar via `/houndsman-update`** — ele aplica a rubrica completa e roda as
159
+ verificações (`doctor`, `test`).
160
+
161
+ > `houndsman doctor` emite um aviso (não bloqueante) para regra sem prefixo de polaridade.
162
+
163
+ Exemplo:
164
+
165
+ ```ts
166
+ { severity: "blocking", text: "Evitar: handler de webhook processa o payload antes de validar a assinatura do provider." }
167
+ { severity: "attention", text: "Promover: rotas validam a entrada com o schema do domínio." }
168
+ ```
169
+
170
+ ### Estrutura gerada por `setup`
171
+
172
+ ```
173
+ houndsman.config.ts
174
+ .houndsman/
175
+ ├── .gitignore # AUTO
176
+ ├── core/ # AUTO — sincronizado por `update`
177
+ │ ├── types.ts
178
+ │ └── skill/*.md
179
+ ├── watchdogs/ # você — 1 módulo .ts por watchdog
180
+ │ └── index.ts
181
+ ├── exclusions.ts # você
182
+ └── doc-mappings.ts # você
183
+ ```
184
+
185
+ Os artefatos de cada review ficam em `.houndsman/.runs/<branch-slug>/` (auto-ignorados).
186
+
187
+ ---
188
+
189
+ ## Documentação
190
+
191
+ - **`docs/CONTRIBUTING.md`** — arquitetura, fluxos internos, princípios e desenvolvimento da
192
+ lib (para quem vai contribuir).
193
+ - `src/skill/SETUP.md` — referência de configuração passo a passo.
194
+ - `src/skill/houndsman.md` — orquestração do skill `/houndsman`.
195
+ - `src/skill/houndsman-update.md` — manutenção das regras dos watchdogs.
Binary file
package/package.json CHANGED
@@ -1,6 +1,64 @@
1
1
  {
2
- "name": "@payconductor/houndsman",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@payconductor/houndsman",
3
+ "version": "0.1.0",
4
+ "description": "Orquestrador de revisão de Pull Requests: classifica o diff, despacha watchdogs especializados por domínio e consolida o relatório.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "houndsman": "./src/cli/index.ts"
9
+ },
10
+ "exports": {
11
+ ".": "./src/index.ts"
12
+ },
13
+ "files": [
14
+ "src",
15
+ "assets",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/payconductor-ai/houndsman.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/payconductor-ai/houndsman/issues"
25
+ },
26
+ "homepage": "https://github.com/payconductor-ai/houndsman#readme",
27
+ "author": "PayConductor",
28
+ "keywords": [
29
+ "pull-request",
30
+ "code-review",
31
+ "cli",
32
+ "bun",
33
+ "ai-agents",
34
+ "orchestrator"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "engines": {
40
+ "bun": ">=1.1.0"
41
+ },
42
+ "scripts": {
43
+ "houndsman": "bun run ./src/cli/index.ts",
44
+ "build": "bun build ./src/cli/index.ts --target bun --outdir dist",
45
+ "test": "bun test ./src/tests",
46
+ "typecheck": "tsc -b",
47
+ "lint": "biome check .",
48
+ "format": "biome format --write .",
49
+ "ci": "biome ci . && tsc -b",
50
+ "ci:check": "biome check",
51
+ "ci:check:fix": "biome check --fix",
52
+ "ci:fix": "bun run ci:check:fix && bun run ci:format:fix",
53
+ "ci:format": "biome format",
54
+ "ci:format:fix": "biome format --fix"
55
+ },
56
+ "dependencies": {
57
+ "axios": "^1.12.2"
58
+ },
59
+ "devDependencies": {
60
+ "@biomejs/biome": "2.2.4",
61
+ "bun-types": "^1.3.14",
62
+ "typescript": "^7.0.2"
63
+ }
64
+ }
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Entrypoint único do `houndsman` — `houndsman [comando]`.
5
+ *
6
+ * Carrega a configuração do projeto (`HoundsmanConfig`) em runtime (a partir do
7
+ * cwd) e orquestra funções/classes de `core/`. Não importa nenhum caminho do
8
+ * projeto consumidor estaticamente.
9
+ *
10
+ * Comandos:
11
+ * (default) Passo 1 — preparar (classificação + briefs + manifesto)
12
+ * --finalize Passo 3 — consolidar os findings em review.md
13
+ * context Passo 0 — coletar o contexto da PR via `gh`
14
+ * setup Cria a configuração inicial do projeto (config + skills)
15
+ * update Sincroniza os arquivos auto-gerados (.houndsman/core)
16
+ * doctor Valida a configuração e o ambiente (anti-drift)
17
+ * test Roda a suíte de testes da lib
18
+ * watchdog <id> Contexto de um watchdog (`--rules`, `--json`)
19
+ *
20
+ * Flags: `--base <b>` · `--head <ref>` · `--branch <b>` · `--dir <d>` ·
21
+ * `--config <path>` · `--clean` · `--no-jev` · `--fail-on-blocking`.
22
+ */
23
+
24
+ import { runContextCommand } from "../core/commands/context";
25
+ import { parseReviewArgs } from "../core/commands/review";
26
+ import { runSetupCommand } from "../core/commands/setup";
27
+ import { runLibTests } from "../core/commands/test";
28
+ import { runUpdateCommand } from "../core/commands/update";
29
+ import { renderWatchdogContext } from "../core/commands/watchdog";
30
+ import { type HoundsmanConfig, resolveArtifactDir } from "../core/config";
31
+ import { loadConfig } from "../core/config-loader";
32
+ import { finalize, prepare, resolveBranch } from "../core/orchestrator";
33
+ import { relFromRoot } from "../core/paths";
34
+ import { checkRegistry } from "../core/registry-check";
35
+
36
+ const HELP = `Uso: houndsman [comando] [flags]
37
+
38
+ Comandos:
39
+ (nenhum) Prepara a revisão (classification + briefs + manifesto)
40
+ --finalize Consolida os findings em review.md
41
+ context Coleta o contexto da PR via gh (Passo 0)
42
+ setup Cria a configuração inicial do projeto (config + skills)
43
+ update Sincroniza os arquivos auto-gerados (.houndsman/core)
44
+ doctor Valida a configuração e o ambiente (anti-drift)
45
+ test Roda a suíte de testes da lib
46
+ watchdog <id> Contexto de um watchdog (--rules, --json)
47
+
48
+ Flags:
49
+ --config <path> Caminho do houndsman.config.* (default: detecta no cwd)
50
+ --base <branch> Base do diff (default: main)
51
+ --head <ref> Head do diff (default: HEAD)
52
+ --branch <nome> Branch alvo (default: detecta do git)
53
+ --dir <dir> Diretório dos artefatos
54
+ --clean Descarta findings anteriores
55
+ --no-jev Desativa a pré-análise Jev
56
+ --fail-on-blocking Exit 1 se houver bloqueantes (com --finalize)`;
57
+
58
+ function readFlag(args: string[], flag: string): string | undefined {
59
+ const index = args.indexOf(flag);
60
+ return index >= 0 ? args[index + 1] : undefined;
61
+ }
62
+
63
+ /** Passo 0 — contexto da PR via `gh`. */
64
+ function runContext(config: HoundsmanConfig, args: string[]): void {
65
+ const { paths, context } = runContextCommand(config, {
66
+ branch: readFlag(args, "--branch"),
67
+ dir: readFlag(args, "--dir"),
68
+ });
69
+
70
+ if (context.available) {
71
+ const src = context.source;
72
+ const label = [src?.number ? `#${src.number}` : "", src?.title ?? ""]
73
+ .filter(Boolean)
74
+ .join(" ");
75
+ console.log(
76
+ `[houndsman] PR ${label} (${src?.head} → ${src?.base}), ${context.stats?.files ?? 0} arquivo(s).`,
77
+ );
78
+ console.log(`[houndsman] contexto: ${relFromRoot(paths.context)}`);
79
+ console.log(`[houndsman] brief: ${relFromRoot(paths.brief)}`);
80
+ console.log(
81
+ `[houndsman] próximo passo: subagente resume em ${relFromRoot(paths.summary)}`,
82
+ );
83
+ } else {
84
+ console.log(
85
+ `[houndsman] contexto indisponível (${context.reason}): ${context.detail}`,
86
+ );
87
+ console.log(`[houndsman] marcador: ${relFromRoot(paths.context)}`);
88
+ }
89
+ }
90
+
91
+ /** Valida a configuração do projeto e o ambiente (anti-drift). */
92
+ function runDoctor(config: HoundsmanConfig): void {
93
+ console.log("→ Validando configuração do projeto...");
94
+ const { problems, warnings, jevQuestions } = checkRegistry(config);
95
+
96
+ if (problems.length > 0) {
97
+ console.error("\n❌ Problemas na configuração de revisão:\n");
98
+ for (const problem of problems) console.error(` - ${problem}`);
99
+ process.exit(1);
100
+ }
101
+ console.log(
102
+ ` ✅ ${config.watchdogIds.length} watchdogs, ${jevQuestions} pergunta(s) Jev.`,
103
+ );
104
+
105
+ if (warnings.length > 0) {
106
+ console.log(`\n⚠️ ${warnings.length} aviso(s) (não bloqueantes):`);
107
+ for (const warning of warnings) console.log(` - ${warning}`);
108
+ }
109
+
110
+ console.log("\n→ Ambiente:");
111
+ console.log(` ${Bun.which("git") ? "✅" : "❌"} git`);
112
+ console.log(` ${Bun.which("gh") ? "✅" : "⚠️ "} gh (opcional — contexto da PR)`);
113
+ console.log(
114
+ ` ${process.env.TYPESAFE_API_KEY ? "✅" : "⚠️ "} TYPESAFE_API_KEY (opcional — pré-análise Jev)`,
115
+ );
116
+ }
117
+
118
+ /** Contexto de um watchdog. */
119
+ function runWatchdog(config: HoundsmanConfig, args: string[]): void {
120
+ const id = args.find((arg) => !arg.startsWith("-"));
121
+
122
+ if (!id || renderWatchdogContext(config, id) === null) {
123
+ console.error(
124
+ id
125
+ ? `Watchdog '${id}' não existe.`
126
+ : "Informe o id do watchdog: houndsman watchdog <id>",
127
+ );
128
+ console.error(`Ids válidos: ${config.watchdogIds.join(", ")}`);
129
+ process.exit(1);
130
+ }
131
+
132
+ console.log(
133
+ renderWatchdogContext(config, id, {
134
+ withRules: args.includes("--rules"),
135
+ json: args.includes("--json"),
136
+ }),
137
+ );
138
+ }
139
+
140
+ /** Passo 1/3 — preparar ou finalizar. */
141
+ async function runReview(config: HoundsmanConfig, args: string[]): Promise<void> {
142
+ const opts = parseReviewArgs(args, resolveArtifactDir(config));
143
+ const branch = resolveBranch(opts);
144
+
145
+ if (opts.finalize) {
146
+ const result = finalize(opts, branch, config);
147
+ if (opts.failOnBlocking && result.blockers > 0) process.exit(1);
148
+ } else {
149
+ await prepare(opts, branch, config);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Executa um subcomando. `--config` sobrepõe a descoberta automática.
155
+ * Comandos que não precisam de projeto (`test`, `setup`) são tratados antes do load.
156
+ */
157
+ export async function main(): Promise<void> {
158
+ const argv = process.argv.slice(2);
159
+ const first = argv[0];
160
+ const command = first && !first.startsWith("-") ? first : undefined;
161
+ const rest = command ? argv.slice(1) : argv;
162
+
163
+ if (command === "test") {
164
+ process.exit(runLibTests());
165
+ }
166
+ if (command === "setup") {
167
+ await runSetupCommand({ cwd: process.cwd(), args: rest });
168
+ return;
169
+ }
170
+ if (command === "update") {
171
+ await runUpdateCommand({ cwd: process.cwd(), args: rest });
172
+ return;
173
+ }
174
+
175
+ if (command === "help") {
176
+ console.info(HELP);
177
+ return;
178
+ }
179
+
180
+ const { config } = await loadConfig({
181
+ configPath: readFlag(argv, "--config"),
182
+ });
183
+
184
+ switch (command) {
185
+ case "context":
186
+ runContext(config, rest);
187
+ return;
188
+ case "doctor":
189
+ case "check":
190
+ runDoctor(config);
191
+ return;
192
+ case "watchdog":
193
+ runWatchdog(config, rest);
194
+ return;
195
+ case undefined:
196
+ await runReview(config, rest);
197
+ return;
198
+ default:
199
+ console.error(`Subcomando desconhecido: '${command}'.\n`);
200
+ console.error(HELP);
201
+ process.exit(1);
202
+ }
203
+ }
204
+
205
+ if (import.meta.main) {
206
+ await main().catch((error) => {
207
+ console.error(error instanceof Error ? error.message : String(error));
208
+ process.exit(1);
209
+ });
210
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Resolução dos assets embarcados no pacote (`src/skill/`) — biblioteca.
3
+ *
4
+ * Usados para **gerar** a camada de projeto (`houndsman setup`/`update`), não
5
+ * para resolver caminhos em runtime. Derivados da localização do módulo — não
6
+ * do cwd.
7
+ */
8
+
9
+ import { join } from "node:path";
10
+
11
+ /** Diretório de assets markdown embarcados no pacote. */
12
+ export const SKILL_DIR = join(import.meta.dir, "..", "skill");
13
+
14
+ /** Nome do arquivo de schema de output dos watchdogs. */
15
+ export const OUTPUT_FORMAT_FILE = "OUTPUT-FORMAT.md";
16
+
17
+ /** Caminho absoluto de um asset embarcado em `src/skill/`. */
18
+ export function assetPath(fileName: string): string {
19
+ return join(SKILL_DIR, fileName);
20
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Renderização do brief de um watchdog (biblioteca) — `defaultRenderBrief`.
3
+ *
4
+ * O procedimento (`PROCEDURE`) é compartilhado por todos os watchdogs; o que era
5
+ * protocolo específico vive nas regras do watchdog (`rules`).
6
+ */
7
+
8
+ import {
9
+ type Rule,
10
+ SEVERITY_EMOJI,
11
+ severityRank,
12
+ type WatchdogDefinition,
13
+ type WatchdogRenderArgs,
14
+ type WatchdogRenderCandidate,
15
+ type WatchdogRenderContext,
16
+ } from "./types";
17
+
18
+ /**
19
+ * Procedimento de revisão — compartilhado por todos os watchdogs.
20
+ * Substitui o antigo protocolo genérico de 5 passos + o ponteiro `PROTOCOL_GLOB`.
21
+ */
22
+ export const PROCEDURE: readonly string[] = [
23
+ "1. **FILTER** — descarte o que não for do seu domínio (ver *Prioridade de leitura* e *Não carregar*).",
24
+ "2. **READ HEAD** — leia os arquivos seletivamente (primeiras ~30 linhas). NUNCA o diff completo.",
25
+ "3. **GREP** — use as regras críticas/anti-padrões como heurística para localizar violações.",
26
+ "4. **DEEP-READ** — aprofunde apenas os arquivos com suspeita de violação.",
27
+ '5. **CONCLUDE** — grave o JSON no caminho de output, com `reviewedFiles` preenchido. Sem achados → `"findings": []` (arquivo obrigatório). Não escreva markdown livre.',
28
+ ];
29
+
30
+ /** Bloco de contexto da PR, injetado no brief de cada watchdog. */
31
+ export function renderBriefContext(context: WatchdogRenderContext): string[] {
32
+ const lines = ["## Contexto da PR", ""];
33
+ if (context.objective) lines.push(`**Objetivo:** ${context.objective}`);
34
+ if (context.motivation) lines.push(`**Motivação:** ${context.motivation}`);
35
+ const changeSummary = context.changeSummary ?? [];
36
+ if (changeSummary.length > 0) {
37
+ lines.push("", "**Resumo das alterações:**", "");
38
+ for (const item of changeSummary) lines.push(`- ${item}`);
39
+ }
40
+ lines.push("");
41
+ return lines;
42
+ }
43
+
44
+ /** Seção de candidatos pré-analisados (Jev) — o watchdog confirma ou refuta. */
45
+ export function renderBriefCandidates(
46
+ candidates: readonly WatchdogRenderCandidate[],
47
+ ): string[] {
48
+ const lines = [
49
+ "## Candidatos pré-analisados (Jev) — confirme ou refute",
50
+ "",
51
+ "> Indícios automáticos com confiança calibrada. **NÃO** são achados:",
52
+ "> confirme (virando finding, com a `rule` abaixo) ou ignore se for falso positivo.",
53
+ "",
54
+ ];
55
+ for (const candidate of candidates) {
56
+ lines.push(
57
+ `- [ ] \`${candidate.file}\` · **${candidate.severity}** · confiança ${candidate.confidence.toFixed(2)}`,
58
+ ` Regra: ${candidate.rule}`,
59
+ );
60
+ }
61
+ lines.push("");
62
+ return lines;
63
+ }
64
+
65
+ /** Linha de lista em code-span: `- \`valor\``. */
66
+ function codeItem(value: string): string {
67
+ return `- \`${value}\``;
68
+ }
69
+
70
+ /** Linha de regra com emoji de severidade. */
71
+ function ruleItem(rule: Rule): string {
72
+ return `- ${SEVERITY_EMOJI[rule.severity]} **${rule.severity}** — ${rule.text}`;
73
+ }
74
+
75
+ /** Seção opcional (`## <título>` + itens) — vazia se não houver itens. */
76
+ function optionalList(title: string, items: readonly string[] | undefined): string[] {
77
+ if (!items || items.length === 0) return [];
78
+ return [`## ${title}`, "", ...items.map((item) => `- ${item}`), ""];
79
+ }
80
+
81
+ /** Lista de arquivos do domínio com status. */
82
+ function fileItems(args: WatchdogRenderArgs): string[] {
83
+ return args.files.map((file) => {
84
+ const status = args.statuses[file]?.status ?? "modified";
85
+ return `- \`${file}\` (${status})`;
86
+ });
87
+ }
88
+
89
+ /** Seção de deletados (breaking change) — só p/ watchdogs com `breakingChange`. */
90
+ function deletedSection(args: WatchdogRenderArgs, def: WatchdogDefinition): string[] {
91
+ if (!def.breakingChange || args.deleted.length === 0) return [];
92
+ return [
93
+ "## Deletados — checar breaking change",
94
+ "",
95
+ ...args.deleted.map(codeItem),
96
+ "",
97
+ ];
98
+ }
99
+
100
+ /**
101
+ * Render padrão do brief de um watchdog (ordem estável).
102
+ * `def` é a definição do próprio watchdog (evita ciclo com o registry).
103
+ */
104
+ export function defaultRenderBrief(
105
+ args: WatchdogRenderArgs,
106
+ def: WatchdogDefinition,
107
+ ): string[] {
108
+ const lines: string[] = [
109
+ `# Brief — ${def.name} (\`${def.id}\`)`,
110
+ "",
111
+ `**Branch:** ${args.branch}`,
112
+ `**Output obrigatório:** \`${args.outputPath}\``,
113
+ `**Schema do output:** \`${args.outputFormatPath}\``,
114
+ "",
115
+ ];
116
+
117
+ if (def.description) lines.push("## Objetivo", "", def.description, "");
118
+ lines.push("## Escopo", "", def.scope, "");
119
+
120
+ if (args.context) lines.push(...renderBriefContext(args.context));
121
+
122
+ lines.push(...optionalList("Prioridade de leitura", def.reviewFocus));
123
+ lines.push(
124
+ "## Regras (por severidade)",
125
+ "",
126
+ ...[...def.rules]
127
+ .sort((a, b) => severityRank(a.severity) - severityRank(b.severity))
128
+ .map(ruleItem),
129
+ "",
130
+ );
131
+ lines.push(
132
+ "## Docs de contexto (carregar sob demanda)",
133
+ "",
134
+ ...args.docs.map(codeItem),
135
+ "",
136
+ );
137
+ lines.push(
138
+ `## Arquivos do seu domínio (${args.files.length})`,
139
+ "",
140
+ ...fileItems(args),
141
+ "",
142
+ );
143
+
144
+ if (args.candidates && args.candidates.length > 0) {
145
+ lines.push(...renderBriefCandidates(args.candidates));
146
+ }
147
+
148
+ lines.push(...deletedSection(args, def));
149
+ lines.push(...optionalList("Não carregar", def.exclude));
150
+ lines.push("## Procedimento", "", ...PROCEDURE);
151
+
152
+ return lines;
153
+ }
154
+
155
+ /**
156
+ * Renderiza o brief via hook custom (`def.renderBrief`) ou o padrão.
157
+ * Passa a própria definição para que o hook possa reutilizar o render padrão.
158
+ */
159
+ export function renderBrief(def: WatchdogDefinition, args: WatchdogRenderArgs): string {
160
+ const render = def.renderBrief ?? defaultRenderBrief;
161
+ return render(args, def).join("\n");
162
+ }