@payconductor/houndsman 0.1.1 → 0.1.4

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 (44) hide show
  1. package/README.md +32 -14
  2. package/package.json +1 -1
  3. package/src/cli/index.ts +22 -11
  4. package/src/core/assets.ts +3 -0
  5. package/src/core/brief.ts +2 -3
  6. package/src/core/commands/findings.ts +232 -4
  7. package/src/core/commands/review.ts +2 -0
  8. package/src/core/commands/scaffold.ts +13 -44
  9. package/src/core/diff-source.ts +4 -4
  10. package/src/core/findings-state.ts +20 -2
  11. package/src/core/fix-batch.ts +236 -0
  12. package/src/core/git-revision.ts +105 -0
  13. package/src/core/jev/index.ts +1 -0
  14. package/src/core/jev/jev-detector.ts +2 -2
  15. package/src/core/jev/jev-verify.ts +178 -0
  16. package/src/core/orchestrator.ts +202 -15
  17. package/src/core/paths.ts +6 -0
  18. package/src/core/registry-check.ts +53 -0
  19. package/src/core/report-builder.ts +5 -0
  20. package/src/core/run-status.ts +123 -0
  21. package/src/core/verify.ts +175 -0
  22. package/src/core/watchdog-registry.ts +1 -2
  23. package/src/index.ts +1 -1
  24. package/src/server/artifacts.ts +1 -0
  25. package/src/server/client/RunList.tsx +22 -13
  26. package/src/server/client/constants.ts +32 -1
  27. package/src/server/client/types.ts +4 -0
  28. package/src/server/index.ts +12 -1
  29. package/src/skill/AUDIT-PROTOCOL.md +7 -2
  30. package/src/skill/README.md +32 -6
  31. package/src/skill/SETUP.md +11 -2
  32. package/src/skill/SKILL.md +48 -0
  33. package/src/skill/fix.md +206 -0
  34. package/src/skill/{houndsman.md → hunt.md} +42 -68
  35. package/src/skill/{houndsman-update.md → train.md} +38 -17
  36. package/src/skill/verify.md +70 -0
  37. package/src/tests/findings-commands.test.ts +252 -2
  38. package/src/tests/findings-state.test.ts +52 -1
  39. package/src/tests/fix-batch.test.ts +257 -0
  40. package/src/tests/review-scripts.test.ts +36 -0
  41. package/src/tests/run-status.test.ts +147 -0
  42. package/src/tests/scaffold.test.ts +59 -0
  43. package/src/tests/server.test.ts +30 -0
  44. package/src/tests/verify.test.ts +170 -0
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Verificação de findings (biblioteca) — "as violações foram resolvidas?".
3
+ *
4
+ * Checagem barata, **sem re-revisar a PR**:
5
+ * - Tier 0 (local, grátis): reancora a âncora de conteúdo contra o working tree.
6
+ * - Tier 1 (Jev, opcional): confirma/refuta os casos ambíguos (âncora sumida ou
7
+ * inexistente) via LLM, sobre um diff focado do arquivo (`jev-verify.ts`).
8
+ *
9
+ * `verifyLocal` é puro (recebe o leitor de arquivos); o comando renderiza e, com
10
+ * `--apply`, persiste `resolved` → `execState: done`.
11
+ */
12
+
13
+ import { reanchor } from "./finding-anchor";
14
+ import {
15
+ applyFindingUpdate,
16
+ type FileReader,
17
+ type FindingState,
18
+ type FindingsFilter,
19
+ type FindingsStateFile,
20
+ filterFindings,
21
+ } from "./findings-state";
22
+ import type { Severity } from "./types";
23
+
24
+ /** Resultado da verificação de um finding. */
25
+ export type VerifyStatus = "resolved" | "persisting" | "uncertain" | "file-missing";
26
+
27
+ /** Mecanismo que decidiu o status (reancoragem local ou LLM). */
28
+ export type VerifyBasis = "anchor" | "jev";
29
+
30
+ /** Valores canônicos de status (ordem de exibição). */
31
+ export const VERIFY_STATUSES: readonly VerifyStatus[] = [
32
+ "resolved",
33
+ "persisting",
34
+ "uncertain",
35
+ "file-missing",
36
+ ];
37
+
38
+ /** Um finding verificado. */
39
+ export type VerifyEntry = {
40
+ id: string;
41
+ file: string;
42
+ rule: string;
43
+ severity: Severity;
44
+ message: string;
45
+ line: number | null;
46
+ status: VerifyStatus;
47
+ basis: VerifyBasis;
48
+ /** Confiança do Jev (0–1) quando `basis: "jev"`. */
49
+ confidence?: number;
50
+ detail?: string;
51
+ /** Ambíguo no tier local — candidato à confirmação do Jev. */
52
+ ambiguous: boolean;
53
+ };
54
+
55
+ /** Fila de verificação: presentes, excluindo `ignore` (descartados pelo usuário). */
56
+ export function selectVerifiable(
57
+ state: FindingsStateFile,
58
+ filter: FindingsFilter = {},
59
+ ): FindingState[] {
60
+ return filterFindings(state, filter).filter(
61
+ (finding) => finding.decision !== "ignore",
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Tier 0 — checagem local por conteúdo. Marca `ambiguous: true` quando a âncora
67
+ * sumiu (corrigida ou trecho reescrito) ou não existe; nesses casos o Jev pode
68
+ * confirmar. Âncora presente (`ok`/`moved`) ⇒ `persisting`, definitivo.
69
+ */
70
+ export function verifyLocal(
71
+ findings: readonly FindingState[],
72
+ readFile: FileReader,
73
+ ): VerifyEntry[] {
74
+ return findings.map((finding) => {
75
+ const base = {
76
+ id: finding.id,
77
+ file: finding.file,
78
+ rule: finding.rule,
79
+ severity: finding.severity,
80
+ message: finding.message,
81
+ line: finding.line,
82
+ };
83
+ const content = readFile(finding.file);
84
+ if (content === null) {
85
+ return {
86
+ ...base,
87
+ status: "file-missing" as const,
88
+ basis: "anchor" as const,
89
+ ambiguous: false,
90
+ detail: "arquivo ausente no working tree",
91
+ };
92
+ }
93
+ if (!finding.anchor) {
94
+ return {
95
+ ...base,
96
+ status: "uncertain" as const,
97
+ basis: "anchor" as const,
98
+ ambiguous: true,
99
+ detail: "finding sem âncora de conteúdo",
100
+ };
101
+ }
102
+ const { status } = reanchor(content, finding.anchor, finding.line);
103
+ if (status === "stale") {
104
+ return {
105
+ ...base,
106
+ status: "resolved" as const,
107
+ basis: "anchor" as const,
108
+ ambiguous: true,
109
+ detail: "âncora ausente (trecho corrigido?)",
110
+ };
111
+ }
112
+ return {
113
+ ...base,
114
+ status: "persisting" as const,
115
+ basis: "anchor" as const,
116
+ ambiguous: false,
117
+ detail: status === "moved" ? "âncora deslocada" : undefined,
118
+ };
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Tier 1 — aplica a confirmação do Jev aos casos ambíguos.
124
+ * `confidence[id] >= threshold` ⇒ a violação persiste; abaixo ⇒ resolvida.
125
+ * Ids sem resposta mantêm o provisório local (fail-open). Devolve o nº aplicado.
126
+ */
127
+ export function applyJev(
128
+ entries: VerifyEntry[],
129
+ confidence: Map<string, number>,
130
+ threshold: number,
131
+ ): number {
132
+ let applied = 0;
133
+ for (const entry of entries) {
134
+ if (!entry.ambiguous) continue;
135
+ const value = confidence.get(entry.id);
136
+ if (value === undefined) continue;
137
+ entry.basis = "jev";
138
+ entry.confidence = value;
139
+ entry.status = value >= threshold ? "persisting" : "resolved";
140
+ applied++;
141
+ }
142
+ return applied;
143
+ }
144
+
145
+ export type VerifySummary = Record<VerifyStatus, number>;
146
+
147
+ /** Contagem por status. */
148
+ export function summarizeVerify(entries: readonly VerifyEntry[]): VerifySummary {
149
+ const summary = Object.fromEntries(
150
+ VERIFY_STATUSES.map((status) => [status, 0]),
151
+ ) as VerifySummary;
152
+ for (const entry of entries) summary[entry.status]++;
153
+ return summary;
154
+ }
155
+
156
+ /**
157
+ * Persiste os `resolved` como `done` (acao `verify`). Devolve os ids alterados.
158
+ * Muta o estado em memória (o caller persiste).
159
+ */
160
+ export function applyResolutions(
161
+ state: FindingsStateFile,
162
+ entries: readonly VerifyEntry[],
163
+ now: string = new Date().toISOString(),
164
+ ): string[] {
165
+ const changed: string[] = [];
166
+ for (const entry of entries) {
167
+ if (entry.status !== "resolved") continue;
168
+ const finding = state.findings[entry.id];
169
+ if (!finding || finding.execState === "done") continue;
170
+ applyFindingUpdate(finding, { execState: "done", by: "verify" }, now);
171
+ changed.push(finding.id);
172
+ }
173
+ if (changed.length > 0) state.updatedAt = now;
174
+ return changed;
175
+ }
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Acesso tipado aos watchdogs de um `HoundsmanConfig` (biblioteca).
3
3
  *
4
- * Substitui o antigo barrel `registry/`. O motor não conhece ids concretos —
5
- * tudo chega via `config`.
4
+ * O motor não conhece ids concretos — tudo chega via `config`.
6
5
  */
7
6
 
8
7
  import type { HoundsmanConfig } from "./config";
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  export { assetPath, OUTPUT_FORMAT_FILE } from "./core/assets";
9
9
  export { defaultRenderBrief, PROCEDURE, renderBrief } from "./core/brief";
10
10
  export type { FindingsCommandResult } from "./core/commands/findings";
11
- export { runFindingsCommand } from "./core/commands/findings";
11
+ export { runFindingsCommand, runVerifyCommand } from "./core/commands/findings";
12
12
  export type {
13
13
  HoundsmanBudgets,
14
14
  HoundsmanConcurrency,
@@ -11,6 +11,7 @@ import { isAbsolute, join, relative, resolve } from "node:path";
11
11
  const RUN_MARKERS = [
12
12
  "manifest.json",
13
13
  "state.json",
14
+ "run.json",
14
15
  "classification.json",
15
16
  "review.md",
16
17
  ] as const;
@@ -2,6 +2,7 @@
2
2
  * Sidebar: logo do houndsman + lista de runs (branch e nº de findings).
3
3
  */
4
4
 
5
+ import { describeRunStatus } from "./constants";
5
6
  import type { RunSummary } from "./types";
6
7
 
7
8
  export function RunList({
@@ -24,19 +25,27 @@ export function RunList({
24
25
  </div>
25
26
  <nav class="runs">
26
27
  {runs.length === 0 && <p class="empty">Nenhuma run encontrada.</p>}
27
- {runs.map((run) => (
28
- <button
29
- type="button"
30
- title={`Abrir a run da branch ${run.branch}`}
31
- class={`run${run.slug === slug ? " active" : ""}`}
32
- onClick={() => onSelect(run.slug)}
33
- >
34
- <span class="run-branch">{run.branch}</span>
35
- <span class="run-meta">
36
- {run.summary.present}/{run.summary.total} presentes
37
- </span>
38
- </button>
39
- ))}
28
+ {runs.map((run) => {
29
+ const status = describeRunStatus(run);
30
+ return (
31
+ <button
32
+ type="button"
33
+ title={`Abrir a run da branch ${run.branch}`}
34
+ class={`run${run.slug === slug ? " active" : ""}`}
35
+ onClick={() => onSelect(run.slug)}
36
+ >
37
+ <span class="run-branch">{run.branch}</span>
38
+ <span class="run-meta">
39
+ {run.summary.present}/{run.summary.total} presentes
40
+ </span>
41
+ {status && (
42
+ <span class={`run-status status-${status.kind}`}>
43
+ {status.label}
44
+ </span>
45
+ )}
46
+ </button>
47
+ );
48
+ })}
40
49
  </nav>
41
50
  </aside>
42
51
  );
@@ -2,7 +2,7 @@
2
2
  * Constantes de apresentação do cliente (labels, ordem, emojis).
3
3
  */
4
4
 
5
- import type { Decision, ExecState, Severity } from "./types";
5
+ import type { Decision, ExecState, RunStatus, RunSummary, Severity } from "./types";
6
6
 
7
7
  export const SEVERITY_EMOJI: Record<Severity, string> = {
8
8
  blocking: "❌",
@@ -30,6 +30,37 @@ export const EXEC_LABELS: Record<ExecState, string> = {
30
30
  stale: "exec: stale",
31
31
  };
32
32
 
33
+ export const RUN_STATUS_LABELS: Record<RunStatus, string> = {
34
+ reviewing: "Revisando",
35
+ review_done: "Revisão pronta",
36
+ fixing: "Corrigindo",
37
+ fix_done: "Correção pronta",
38
+ };
39
+
40
+ /** Limiar de inatividade para exibir timeout (display-only; não é status). */
41
+ export const STATUS_TIMEOUT_MS = 10 * 60 * 1000;
42
+
43
+ export type RunStatusView = { kind: string; label: string; timeout: boolean };
44
+
45
+ /** View do status de uma run — label; vira timeout quando ativo e sem ping recente. */
46
+ export function describeRunStatus(
47
+ run: RunSummary,
48
+ now: number = Date.now(),
49
+ ): RunStatusView | null {
50
+ const status = run.status;
51
+ if (!status) return null;
52
+ const active = status === "reviewing" || status === "fixing";
53
+ const updated = run.updatedAt ? Date.parse(run.updatedAt) : Number.NaN;
54
+ if (active && Number.isFinite(updated) && now - updated > STATUS_TIMEOUT_MS) {
55
+ return {
56
+ kind: "timeout",
57
+ label: status === "reviewing" ? "Timeout na revisão" : "Timeout na correção",
58
+ timeout: true,
59
+ };
60
+ }
61
+ return { kind: status, label: RUN_STATUS_LABELS[status], timeout: false };
62
+ }
63
+
33
64
  /** Artefatos que podem ser lidos na aba "Artefatos". */
34
65
  export const ARTIFACTS = [
35
66
  "manifest.json",
@@ -9,6 +9,9 @@ export type Severity = "blocking" | "attention" | "suggestion" | "nit";
9
9
  export type Decision = "pending" | "execute" | "ignore";
10
10
  export type ExecState = "pending" | "in_progress" | "done" | "stale";
11
11
 
12
+ /** Estados de execução de uma run (espelham `core/run-status.ts`). */
13
+ export type RunStatus = "reviewing" | "review_done" | "fixing" | "fix_done";
14
+
12
15
  export type Finding = {
13
16
  id: string;
14
17
  rule: string;
@@ -38,6 +41,7 @@ export type RunSummary = {
38
41
  slug: string;
39
42
  branch: string;
40
43
  updatedAt?: string;
44
+ status?: RunStatus | null;
41
45
  summary: Summary;
42
46
  };
43
47
 
@@ -27,6 +27,7 @@ import {
27
27
  syncState,
28
28
  } from "../core/findings-state";
29
29
  import { STATE_FILE } from "../core/paths";
30
+ import { pingFindingsStatus, readRunStatus } from "../core/run-status";
30
31
  import type { Severity } from "../core/types";
31
32
  import { listRunSlugs, readRunFile, readRunJson, runDirForSlug } from "./artifacts";
32
33
  import { buildClientBundle } from "./client-build";
@@ -95,10 +96,12 @@ function listRuns(artifactDir: string): unknown[] {
95
96
  dir,
96
97
  "manifest.json",
97
98
  );
99
+ const status = readRunStatus(dir);
98
100
  return {
99
101
  slug,
100
102
  branch: state.branch || manifest?.branch || slug,
101
- updatedAt: state.updatedAt,
103
+ updatedAt: status?.updatedAt ?? state.updatedAt,
104
+ status: status?.status ?? null,
102
105
  summary: summarize(state),
103
106
  };
104
107
  });
@@ -114,6 +117,7 @@ function runDetail(dir: string, slug: string): unknown {
114
117
  return {
115
118
  slug,
116
119
  branch: state.branch || slug,
120
+ status: readRunStatus(dir),
117
121
  manifest: readRunJson(dir, "manifest.json"),
118
122
  summary: summarize(state),
119
123
  watchdogs: Object.keys(classification?.watchdogs ?? {}),
@@ -153,6 +157,7 @@ async function updateFinding(
153
157
  applyFindingUpdate(finding, body);
154
158
  reorder(state);
155
159
  store.write(state);
160
+ pingFindingsStatus(dir, state);
156
161
  return json({ finding });
157
162
  }
158
163
 
@@ -161,6 +166,7 @@ function syncRun(dir: string, slug: string): Response {
161
166
  const state = store.read() ?? buildForRun(dir, slug) ?? emptyState(slug);
162
167
  const result: SyncResult = syncState(state, fileReader(process.cwd()));
163
168
  store.write(state);
169
+ pingFindingsStatus(dir, state);
164
170
  return json({ result, summary: summarize(state) });
165
171
  }
166
172
 
@@ -301,6 +307,11 @@ code, pre { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospac
301
307
  .run.active { border-color:var(--accent); background:#22304d; }
302
308
  .run-branch { font-weight:600; font-size:13px; }
303
309
  .run-meta { color:var(--muted); font-size:11px; }
310
+ .run-status { font-size:10px; text-transform:uppercase; letter-spacing:.04em; color:var(--muted); }
311
+ .status-reviewing, .status-fixing { color:#e3a008; }
312
+ .status-review_done { color:#4c8dff; }
313
+ .status-fix_done { color:#3fb950; }
314
+ .status-timeout { color:#e5484d; }
304
315
  .main { flex:1; padding:16px 20px; max-width:980px; }
305
316
  .topbar { display:flex; justify-content:space-between; align-items:center; margin-bottom:12px; }
306
317
  .tabs { display:flex; gap:6px; flex-wrap:wrap; }
@@ -1,7 +1,7 @@
1
1
  # Protocolo de Auditoria dos Watchdogs — houndsman
2
2
 
3
- Referência do Modo B do skill `houndsman-update`. Objetivo: garantir que as regras dos
4
- watchdogs validam **apenas padrões que o projeto aplica hoje**. Regras "utópicas" (não
3
+ Referência do Modo B da capability `train` (skill `/houndsman`). Objetivo: garantir que as
4
+ regras dos watchdogs validam **apenas padrões que o projeto aplica hoje**. Regras "utópicas" (não
5
5
  aplicadas em nenhuma parte do código) devem ser **removidas**, não apenas rebaixadas.
6
6
 
7
7
  ---
@@ -11,6 +11,10 @@ aplicadas em nenhuma parte do código) devem ser **removidas**, não apenas reba
11
11
  1. `.houndsman/watchdogs/<id>.ts` — `WATCHDOG_DEFINITIONS[<id>]` é a fonte única de verdade:
12
12
  `description`, `scope`, `rules` (severidade + texto), `classify`, `contextFiles`,
13
13
  `reviewFocus`, `exclude`.
14
+
15
+ > **Onde cada coisa vive:** a **regra** vive só em `rules`; `reviewFocus` = paths a priorizar;
16
+ > `contextFiles` = docs a consultar; `exclude` = paths a ignorar; `scope`/`description` = prosa.
17
+ > Texto de regra em `reviewFocus`/`contextFiles`/`exclude` é **duplicação** a corrigir.
14
18
  2. `src/core/brief.ts` (no motor) — `PROCEDURE` (passos de revisão compartilhados).
15
19
  3. Ground truth (padrões reais do projeto):
16
20
  - Documentos de convenção do projeto (ex.: `AGENTS.md`, `CLAUDE.md`, `.github/copilot-instructions.md`).
@@ -41,6 +45,7 @@ classifique sem evidência (caminho de arquivo, linha, ou "0 ocorrências via gr
41
45
  | `OUTDATED` | Já foi aplicado, mas o código mudou | Remover ou reescrever |
42
46
  | `MISCALIBRATED` | Regra válida, mas severidade errada | Ajustar severidade |
43
47
  | `DEAD-PATH` | `scope`/`contextFiles`/`classify` apontam para caminho/arquivo inexistente | Corrigir ou remover |
48
+ | `REDUNDANT` | Regra duplicada no mesmo watchdog ou repetida em `reviewFocus`/`contextFiles`/`exclude` | Remover a cópia (a regra fica só em `rules`) |
44
49
 
45
50
  ## Formato do relatório
46
51
 
@@ -2,6 +2,18 @@
2
2
 
3
3
  Revisão automatizada de Pull Requests — CLI independente + camada de configuração do projeto.
4
4
 
5
+ ## Capabilities
6
+
7
+ Um único skill (`/houndsman`), quatro capabilities (detalhadas em `hunt.md`, `train.md`,
8
+ `fix.md`, `verify.md`):
9
+
10
+ | Capability | O que faz |
11
+ |---|---|
12
+ | `hunt` | Revisão de PR (Passos 0–3; incremental quando possível). |
13
+ | `train` | Manutenção das regras dos watchdogs. |
14
+ | `fix` | Correção dos findings marcados como `execute`. |
15
+ | `verify` | Confere se as violações foram resolvidas, sem re-revisar a PR. |
16
+
5
17
  ## Terminologia
6
18
 
7
19
  - **houndsman** — o orquestrador/treinador (CLI + motor).
@@ -35,15 +47,17 @@ Revisão automatizada de Pull Requests — CLI independente + camada de configur
35
47
  | `houndsman` | Passo 1 — preparar (gera os briefs) |
36
48
  | `houndsman --finalize` | Passo 3 — gerar o relatório |
37
49
  | `houndsman watchdog <id>` | Contexto de um watchdog (`--json`, `--rules`) |
50
+ | `houndsman findings <sub>` | Triagem/correção dos findings (`list`/`set`/`done`/`sync`/`batch`/`verify`/...) |
51
+ | `houndsman server` | UI local de triagem (Bun.serve + Preact) |
38
52
  | `houndsman setup` | Cria a configuração inicial do projeto |
39
53
  | `houndsman update` | Sincroniza os arquivos auto-gerados (`.houndsman/core`) |
40
54
  | `houndsman doctor` | Valida a configuração, o ambiente e a credencial Jev (anti-drift) |
41
- | `houndsman test` | Roda a suíte de testes da lib |
55
+ | `houndsman test` | **Manutenção da lib** — roda a suíte de testes do pacote (não é uso do consumidor) |
42
56
 
43
57
  ### Flags
44
58
 
45
59
  `--config <path>` · `--base <branch>` (default `main`) · `--head <ref>` (default `HEAD`) ·
46
- `--branch <nome>` · `--dir <dir>` · `--clean` · `--no-jev` · `--fail-on-blocking` (no `--finalize`).
60
+ `--branch <nome>` · `--dir <dir>` · `--clean` · `--full` · `--no-jev` · `--fail-on-blocking` (no `--finalize`).
47
61
 
48
62
  ## Artefatos
49
63
 
@@ -60,6 +74,9 @@ Tudo em `.houndsman/.runs/<branch-slug>/` (default do `artifactDir`, `.houndsman
60
74
  | `findings/<watchdog>.json` | Saída dos sub-agentes (schema `OUTPUT-FORMAT.md`) |
61
75
  | `review.md` | Relatório final (achados agrupados por arquivo) |
62
76
  | `review.<watchdog>.md` | Relatório por domínio (só quando há achados) |
77
+ | `state.json` | Estado de triagem dos findings (decisão/execução + fingerprint + commit revisado) |
78
+ | `run.json` | Status do run (`reviewing`/`review_done`/`fixing`/`fix_done`) + timestamps |
79
+ | `verify.md` | Relatório do `findings verify` (resolvidos/persistem/incertos) |
63
80
 
64
81
  ## Estrutura
65
82
 
@@ -83,6 +100,12 @@ src/
83
100
  │ ├── diff-source.ts # diff único (patch + --numstat) p/ churn/orçamento
84
101
  │ ├── doc-discoverer.ts # descoberta de docs
85
102
  │ ├── report-builder.ts # dedupe/render do relatório
103
+ │ ├── finding-anchor.ts # âncora/fingerprint de conteúdo + reancoragem
104
+ │ ├── findings-state.ts # estado de triagem (state.json) + store atômico
105
+ │ ├── git-revision.ts # refs/ancestralidade + arquivo por commit + diff por intervalo
106
+ │ ├── verify.ts # verificação de findings (tier local + Jev)
107
+ │ ├── run-status.ts # run.json (reviewing/review_done/fixing/fix_done)
108
+ │ ├── fix-batch.ts # agrupamento/paginação do lote (findings batch)
86
109
  │ ├── registry-check.ts # validações anti-drift (config-driven)
87
110
  │ ├── commands/ # context · review · watchdog · setup · update · test
88
111
  │ └── jev/ # subsistema Jev (Passo 1.5)
@@ -105,8 +128,9 @@ houndsman.config.ts # HoundsmanConfig (dados; descoberto em runtime)
105
128
  └── doc-mappings.ts # USUÁRIO
106
129
  ```
107
130
 
108
- > Os `SKILL.md` em `.agents/skills/houndsman/` e `.claude/skills/houndsman/` são
109
- > ponteiros para `.houndsman/core/skill/houndsman.md`.
131
+ > O `SKILL.md` do skill (`.agents/skills/houndsman/SKILL.md` e
132
+ > `.claude/skills/houndsman/SKILL.md`) é gerado a partir de `src/skill/SKILL.md`
133
+ > pelo `houndsman setup`/`update` (com `{{projectName}}` resolvido).
110
134
 
111
135
  ## Manutenção
112
136
 
@@ -114,11 +138,13 @@ houndsman.config.ts # HoundsmanConfig (dados; descoberto em runtime)
114
138
  > prove que o padrão existe no código (regra negativa: prove que o anti-padrão **não** existe).
115
139
  > Regras devem ser **gerais/reutilizáveis** no domínio e declarar a **polaridade** no texto
116
140
  > (`Promover:` = deve acontecer; `Evitar:`/`Não`/`Nunca` = não deve acontecer).
141
+ > Cada regra vive **só** em `rules` — não a repita em `reviewFocus`/`contextFiles`/`scope`/
142
+ > `description`/`exclude` (isso só duplicaria o brief).
117
143
 
118
144
  1. Novas regras/watchdogs → editar `.houndsman/watchdogs/<id>.ts` (regras + escopo +
119
145
  `classify`) ou `doc-mappings.ts`/`exclusions.ts`. Um watchdog novo exige só o módulo em
120
146
  `.houndsman/watchdogs/<id>.ts` + entrada em `.houndsman/watchdogs/index.ts`.
121
147
  2. Rodar `bun run houndsman doctor` — valida a config (definições, classificação, specs Jev).
122
148
 
123
- > Para qualquer alteração de regra (pontual ou auditoria completa), use o skill
124
- > **`houndsman-update`**: `.houndsman/core/skill/houndsman-update.md`.
149
+ > Para qualquer alteração de regra (pontual ou auditoria completa), use a capability
150
+ > **`train`**: `/houndsman train` → `.houndsman/core/skill/train.md`.
@@ -39,7 +39,7 @@ A camada separa o que é **auto-gerado** do que é **do usuário**:
39
39
  | `.houndsman/core/types.ts` | **auto** | Snapshot dos tipos + helpers (tipagem sem install) |
40
40
  | `.houndsman/core/skill/*.md` | **auto** | Documentação do skill |
41
41
  | `.houndsman/.gitignore` | **auto** | Ignora `core/` e `.runs/` (gerados/temporários) |
42
- | `.agents/skills/houndsman/SKILL.md` etc. | **auto** | Ponteiros de skill dos harnesses |
42
+ | `.agents/skills/houndsman/SKILL.md` etc. | **auto** | `SKILL.md` do skill `/houndsman` (capabilities `hunt`/`train`/`fix`) por harness |
43
43
 
44
44
  Além disso, adiciona o script `"houndsman": "houndsman"` ao `package.json` — assim
45
45
  `bun run houndsman [...]` funciona na raiz do projeto. Os artefatos ficam em
@@ -60,10 +60,19 @@ bun run houndsman update # reescreve .houndsman/core/** e .houndsman/.gitig
60
60
 
61
61
  ```bash
62
62
  bun run houndsman doctor # valida config + ambiente + credencial Jev
63
- bun run houndsman test # suíte da lib (project-agnóstica)
64
63
  bun run houndsman --base main # prepara briefs do diff atual
65
64
  ```
66
65
 
66
+ ## Capabilities do skill
67
+
68
+ Depois do setup, invoque o skill `/houndsman` no seu harness:
69
+
70
+ | Capability | Para quê |
71
+ |---|---|
72
+ | `/houndsman hunt` | Revisar a PR (default: `/houndsman`). |
73
+ | `/houndsman train` | Adicionar/ajustar/remover regras dos watchdogs. |
74
+ | `/houndsman fix` | Corrigir os findings marcados como `execute`. |
75
+
67
76
  ## Contrato do motor (`HoundsmanConfig`)
68
77
 
69
78
  Definido em `src/core/config.ts` (pacote) e espelhado em `.houndsman/core/types.ts` (gerado):
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: houndsman
3
+ description: 'Revisa Pull Requests com watchdogs especializados por domínio de negócio e mantém essas regras alinhadas ao projeto. Use para revisar/code review de PR, atualizar as regras de revisão dos watchdogs e corrigir/verificar os findings da revisão. Capabilities: `hunt` (revisar a PR), `train` (atualizar regras), `fix` (corrigir achados), `verify` (violações resolvidas?).'
4
+ argument-hint: 'Capability: hunt | train | fix | verify (default: hunt)'
5
+ ---
6
+
7
+ # houndsman — {{projectName}}
8
+
9
+ O **houndsman** revisa Pull Requests com **watchdogs** — agentes especialistas por domínio de
10
+ negócio. Cada watchdog audita a parte do diff que é sua e grava **findings** (achados); o motor
11
+ consolida tudo num relatório. O motor é o CLI **`houndsman`**; as regras do projeto vivem em
12
+ `houndsman.config.ts` + `.houndsman/`.
13
+
14
+ **Terminologia:** **houndsman** = orquestrador/treinador (CLI + motor) · **watchdog** = agente
15
+ especialista por domínio · **finding** = um achado da revisão · **Jev** = pré-análise opcional.
16
+
17
+ ## Capabilities
18
+
19
+ | Capability | Objetivo | Documento |
20
+ |---|---|---|
21
+ | **`hunt`** | Revisar uma Pull Request e produzir o relatório de achados. | `.houndsman/core/skill/hunt.md` |
22
+ | **`train`** | Manter as regras dos watchdogs alinhadas ao que o projeto aplica. | `.houndsman/core/skill/train.md` |
23
+ | **`fix`** | Corrigir os achados de uma revisão já finalizada. | `.houndsman/core/skill/fix.md` |
24
+ | **`verify`** | Conferir se as violações foram resolvidas, sem re-revisar a PR. | `.houndsman/core/skill/verify.md` |
25
+
26
+ ## Invocação
27
+
28
+ ```
29
+ /houndsman → hunt (default)
30
+ /houndsman hunt → revisar a Pull Request
31
+ /houndsman train → atualizar as regras dos watchdogs
32
+ /houndsman fix → corrigir os findings
33
+ /houndsman verify → conferir se as violações foram resolvidas
34
+ ```
35
+
36
+ Um argumento extra é o alvo da capability (ex.: `/houndsman hunt feature/minha-branch`).
37
+ Escolha a capability pelo que o usuário quer, leia o documento correspondente em
38
+ `.houndsman/core/skill/<capability>.md` e siga-o.
39
+
40
+ > **Depois de revisar (`hunt`), valide na UI:** `bun run houndsman server --open` — revise os
41
+ > achados e marque os que devem ser executados (`execute`) antes de rodar `fix`.
42
+
43
+ ## Referências
44
+
45
+ - `.houndsman/core/skill/OUTPUT-FORMAT.md` — schema do JSON de saída dos watchdogs.
46
+ - `.houndsman/core/skill/AUDIT-PROTOCOL.md` — taxonomia da auditoria de regras (Modo B do `train`).
47
+ - `.houndsman/core/skill/SETUP.md` · `.houndsman/core/skill/README.md` — configuração e visão geral.
48
+ - `.houndsman/core/skill/example-review.md` — exemplo de relatório.