@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.
- package/README.md +32 -14
- package/package.json +1 -1
- package/src/cli/index.ts +22 -11
- package/src/core/assets.ts +3 -0
- package/src/core/brief.ts +2 -3
- package/src/core/commands/findings.ts +232 -4
- package/src/core/commands/review.ts +2 -0
- package/src/core/commands/scaffold.ts +13 -44
- package/src/core/diff-source.ts +4 -4
- package/src/core/findings-state.ts +20 -2
- package/src/core/fix-batch.ts +236 -0
- package/src/core/git-revision.ts +105 -0
- package/src/core/jev/index.ts +1 -0
- package/src/core/jev/jev-detector.ts +2 -2
- package/src/core/jev/jev-verify.ts +178 -0
- package/src/core/orchestrator.ts +202 -15
- package/src/core/paths.ts +6 -0
- package/src/core/registry-check.ts +53 -0
- package/src/core/report-builder.ts +5 -0
- package/src/core/run-status.ts +123 -0
- package/src/core/verify.ts +175 -0
- package/src/core/watchdog-registry.ts +1 -2
- package/src/index.ts +1 -1
- package/src/server/artifacts.ts +1 -0
- package/src/server/client/RunList.tsx +22 -13
- package/src/server/client/constants.ts +32 -1
- package/src/server/client/types.ts +4 -0
- package/src/server/index.ts +12 -1
- package/src/skill/AUDIT-PROTOCOL.md +7 -2
- package/src/skill/README.md +32 -6
- package/src/skill/SETUP.md +11 -2
- package/src/skill/SKILL.md +48 -0
- package/src/skill/fix.md +206 -0
- package/src/skill/{houndsman.md → hunt.md} +42 -68
- package/src/skill/{houndsman-update.md → train.md} +38 -17
- package/src/skill/verify.md +70 -0
- package/src/tests/findings-commands.test.ts +252 -2
- package/src/tests/findings-state.test.ts +52 -1
- package/src/tests/fix-batch.test.ts +257 -0
- package/src/tests/review-scripts.test.ts +36 -0
- package/src/tests/run-status.test.ts +147 -0
- package/src/tests/scaffold.test.ts +59 -0
- package/src/tests/server.test.ts +30 -0
- 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
|
-
*
|
|
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,
|
package/src/server/artifacts.ts
CHANGED
|
@@ -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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
package/src/server/index.ts
CHANGED
|
@@ -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
|
|
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
|
|
package/src/skill/README.md
CHANGED
|
@@ -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` |
|
|
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
|
-
>
|
|
109
|
-
>
|
|
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
|
|
124
|
-
> **`
|
|
149
|
+
> Para qualquer alteração de regra (pontual ou auditoria completa), use a capability
|
|
150
|
+
> **`train`**: `/houndsman train` → `.houndsman/core/skill/train.md`.
|
package/src/skill/SETUP.md
CHANGED
|
@@ -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** |
|
|
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.
|