@payconductor/houndsman 0.2.0 → 0.4.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.
- package/README.md +18 -6
- package/package.json +1 -1
- package/src/cli/arg-parser.ts +8 -0
- package/src/cli/commands/command.ts +23 -0
- package/src/cli/commands/context-command.ts +40 -0
- package/src/cli/commands/doctor-command.ts +60 -0
- package/src/cli/commands/findings-command.ts +38 -0
- package/src/cli/commands/help-command.ts +13 -0
- package/src/cli/commands/registry.ts +34 -0
- package/src/cli/commands/review-command.ts +21 -0
- package/src/cli/commands/server-command.ts +37 -0
- package/src/cli/commands/setup-command.ts +34 -0
- package/src/cli/commands/watchdog-command.ts +34 -0
- package/src/cli/help.ts +47 -0
- package/src/cli/index.ts +60 -268
- package/src/core/arg-parser.ts +48 -0
- package/src/core/commands/context.ts +29 -13
- package/src/core/commands/findings.ts +185 -260
- package/src/core/commands/review.ts +21 -39
- package/src/core/commands/scaffold.ts +189 -80
- package/src/core/commands/setup.ts +16 -11
- package/src/core/commands/update.ts +7 -7
- package/src/core/commands/watchdog.ts +8 -80
- package/src/core/config.ts +24 -25
- package/src/core/defaults.ts +20 -0
- package/src/core/diff-source.ts +1 -13
- package/src/core/doc-discoverer.ts +25 -15
- package/src/core/domain/budget.ts +42 -0
- package/src/core/domain/classification.ts +94 -0
- package/src/core/domain/diff.ts +53 -0
- package/src/core/domain/watchdog.ts +96 -0
- package/src/core/engine/carryover.ts +67 -0
- package/src/core/engine/houndsman-engine.ts +65 -0
- package/src/core/engine/review-scope.ts +95 -0
- package/src/core/engine/review-session.ts +79 -0
- package/src/core/file-classifier.ts +11 -67
- package/src/core/findings-state.ts +193 -100
- package/src/core/git-revision.ts +14 -40
- package/src/core/git-runner.ts +46 -0
- package/src/core/github-review.ts +4 -33
- package/src/core/jev/jev-candidates.ts +49 -35
- package/src/core/orchestrator.ts +247 -421
- package/src/core/paths.ts +26 -11
- package/src/core/pr-context.ts +39 -51
- package/src/core/project-config.ts +56 -0
- package/src/core/renderers/batch-presenter.ts +40 -0
- package/src/core/renderers/brief-renderer.ts +23 -0
- package/src/core/renderers/findings-presenter.ts +74 -0
- package/src/core/renderers/manifest-writer.ts +123 -0
- package/src/core/renderers/report-renderer.ts +66 -0
- package/src/core/renderers/verify-presenter.ts +50 -0
- package/src/core/renderers/watchdog-presenter.ts +100 -0
- package/src/core/report-builder.ts +12 -7
- package/src/core/run-layout.ts +8 -0
- package/src/core/run-status.ts +29 -59
- package/src/core/services/clock.ts +12 -0
- package/src/core/services/file-classifier.ts +34 -0
- package/src/core/services/fix-batch-builder.ts +13 -0
- package/src/core/services/gh-client.ts +54 -0
- package/src/core/services/git-repository.ts +139 -0
- package/src/core/services/project-files.ts +91 -0
- package/src/core/services/verifier.ts +58 -0
- package/src/core/storage/factory.ts +24 -0
- package/src/core/storage/file/file-storage.ts +269 -0
- package/src/core/storage/paths.ts +64 -0
- package/src/core/storage/repo-identity.ts +70 -0
- package/src/core/storage/sqlite/mappers.ts +160 -0
- package/src/core/storage/sqlite/schema.ts +108 -0
- package/src/core/storage/sqlite/sqlite-storage.ts +427 -0
- package/src/core/storage/types.ts +143 -0
- package/src/core/storage/work-area.ts +191 -0
- package/src/core/verify.ts +2 -1
- package/src/core/watchdog-registry.ts +58 -16
- package/src/index.ts +57 -6
- package/src/server/artifacts.ts +8 -61
- package/src/server/client/ArtifactsPanel.tsx +137 -23
- package/src/server/client/FindingCard.tsx +37 -6
- package/src/server/client/MarkdownText.tsx +101 -0
- package/src/server/client/SubmitModal.tsx +9 -4
- package/src/server/client/code-highlight.ts +422 -0
- package/src/server/client/constants.ts +130 -24
- package/src/server/client/json-highlight.ts +55 -0
- package/src/server/client/markdown.ts +153 -0
- package/src/server/client/types.ts +23 -0
- package/src/server/index.ts +155 -245
- package/src/server/run-repository.ts +299 -0
- package/src/skill/OUTPUT-FORMAT.md +6 -4
- package/src/skill/README.md +22 -14
- package/src/skill/SETUP.md +4 -4
- package/src/skill/fix.md +5 -5
- package/src/skill/hunt.md +27 -18
- package/src/skill/train.md +1 -1
- package/src/skill/verify.md +5 -5
- package/src/tests/__snapshots__/baseline.test.ts.snap +825 -0
- package/src/tests/baseline.test.ts +555 -493
- package/src/tests/cli.test.ts +40 -19
- package/src/tests/code-highlight.test.ts +123 -0
- package/src/tests/domain.test.ts +328 -0
- package/src/tests/findings-commands.test.ts +153 -131
- package/src/tests/findings-state.test.ts +6 -3
- package/src/tests/fixtures/fake-project.ts +0 -1
- package/src/tests/infrastructure.test.ts +205 -0
- package/src/tests/markdown.test.ts +89 -0
- package/src/tests/review-scripts.test.ts +8 -5
- package/src/tests/run-status.test.ts +33 -23
- package/src/tests/server.test.ts +108 -75
- package/src/tests/storage.test.ts +386 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repositório das runs (servidor) — leitura/escrita via storage.
|
|
3
|
+
*
|
|
4
|
+
* Escopa tudo ao repositório do cwd: a UI mostra só as runs do projeto atual.
|
|
5
|
+
* O slug continua sendo o branch-slug (estável nas URLs).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
import {
|
|
10
|
+
applyFindingUpdate,
|
|
11
|
+
emptyState,
|
|
12
|
+
type FindingDecision,
|
|
13
|
+
type FindingExecState,
|
|
14
|
+
type FindingState,
|
|
15
|
+
type FindingsFilter,
|
|
16
|
+
type FindingsStateFile,
|
|
17
|
+
type FindingsSummary,
|
|
18
|
+
type FindingUpdate,
|
|
19
|
+
fileReader,
|
|
20
|
+
filterFindings,
|
|
21
|
+
reorder,
|
|
22
|
+
resolveFindingId,
|
|
23
|
+
type SyncResult,
|
|
24
|
+
summarize,
|
|
25
|
+
syncState,
|
|
26
|
+
} from "../core/findings-state";
|
|
27
|
+
import {
|
|
28
|
+
buildSubmitPreview,
|
|
29
|
+
type GhRunner,
|
|
30
|
+
resolvePrTarget,
|
|
31
|
+
type SubmitSelection,
|
|
32
|
+
submitFindings,
|
|
33
|
+
} from "../core/github-review";
|
|
34
|
+
import { pingFindingsStatus } from "../core/run-status";
|
|
35
|
+
import { systemClock } from "../core/services/clock";
|
|
36
|
+
import type { RepoIdentity, ReviewRecord, StorageProvider } from "../core/storage/types";
|
|
37
|
+
import type { Severity } from "../core/types";
|
|
38
|
+
import { isSlug } from "./artifacts";
|
|
39
|
+
|
|
40
|
+
/** Item da lista de execuções (sidebar) — já ordenado do mais recente ao antigo. */
|
|
41
|
+
export type RunListItem = {
|
|
42
|
+
slug: string;
|
|
43
|
+
branch: string;
|
|
44
|
+
updatedAt?: string;
|
|
45
|
+
status: ReviewRecord["status"];
|
|
46
|
+
summary: FindingsSummary;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Detalhe de uma run (cabeçalho da UI). */
|
|
50
|
+
export type RunDetail = {
|
|
51
|
+
slug: string;
|
|
52
|
+
branch: string;
|
|
53
|
+
status: ReviewRecord | null;
|
|
54
|
+
manifest: unknown;
|
|
55
|
+
summary: FindingsSummary;
|
|
56
|
+
watchdogs: string[];
|
|
57
|
+
unclassified: string[];
|
|
58
|
+
deleted: string[];
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Resultado do envio dos findings à PR. */
|
|
62
|
+
export type SubmitResult =
|
|
63
|
+
| { available: false; reason: string }
|
|
64
|
+
| { available: true; target: unknown; outcome: unknown };
|
|
65
|
+
|
|
66
|
+
/** Metadados de um artefato (sem conteúdo) para a lista da UI. */
|
|
67
|
+
export type ArtifactListItem = {
|
|
68
|
+
name: string;
|
|
69
|
+
kind: string;
|
|
70
|
+
contentType: string;
|
|
71
|
+
updatedAt: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** Acesso às runs de um repositório. */
|
|
75
|
+
export class RunRepository {
|
|
76
|
+
private readonly storage: StorageProvider;
|
|
77
|
+
private readonly repo: RepoIdentity;
|
|
78
|
+
|
|
79
|
+
constructor(storage: StorageProvider, repo: RepoIdentity) {
|
|
80
|
+
this.storage = storage;
|
|
81
|
+
this.repo = repo;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** `true` se o slug aponta para uma run do repositório atual. */
|
|
85
|
+
exists(slug: string): boolean {
|
|
86
|
+
return this.findBySlug(slug) !== null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Lista as runs com resumo (mais recentes primeiro). */
|
|
90
|
+
list(): RunListItem[] {
|
|
91
|
+
return this.storage.listReviews(this.repo.key).map((review) => {
|
|
92
|
+
const state = this.storage.readFindings(review.id);
|
|
93
|
+
return {
|
|
94
|
+
slug: review.slug,
|
|
95
|
+
branch: review.branch,
|
|
96
|
+
updatedAt: review.updatedAt,
|
|
97
|
+
status: review.status,
|
|
98
|
+
summary: summarize(state ?? emptyState(review.branch)),
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Detalhe da run (null se o slug for inválido). */
|
|
104
|
+
detail(slug: string): RunDetail | null {
|
|
105
|
+
const review = this.findBySlug(slug);
|
|
106
|
+
if (!review) return null;
|
|
107
|
+
const state = this.loadState(review);
|
|
108
|
+
const classification = this.readArtifactJson<{
|
|
109
|
+
watchdogs?: Record<string, string[]>;
|
|
110
|
+
unclassified?: string[];
|
|
111
|
+
deleted?: string[];
|
|
112
|
+
}>(review, "classification.json");
|
|
113
|
+
return {
|
|
114
|
+
slug: review.slug,
|
|
115
|
+
branch: review.branch,
|
|
116
|
+
status: review,
|
|
117
|
+
manifest: this.readArtifactJson(review, "manifest.json"),
|
|
118
|
+
summary: summarize(state),
|
|
119
|
+
watchdogs: Object.keys(classification?.watchdogs ?? {}),
|
|
120
|
+
unclassified: classification?.unclassified ?? [],
|
|
121
|
+
deleted: classification?.deleted ?? [],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Aplica `VACUUM`/`optimize` no backend (chamado no start do server). */
|
|
126
|
+
maintain(): void {
|
|
127
|
+
this.storage.maintain();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Fecha o provider (shutdown do server). */
|
|
131
|
+
dispose(): void {
|
|
132
|
+
this.storage.close();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// -----------------------------------------------------------------------
|
|
136
|
+
// Internos
|
|
137
|
+
// -----------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
private findBySlug(slug: string): ReviewRecord | null {
|
|
140
|
+
if (!isSlug(slug)) return null;
|
|
141
|
+
return (
|
|
142
|
+
this.storage
|
|
143
|
+
.listReviews(this.repo.key)
|
|
144
|
+
.find((review) => review.slug === slug) ?? null
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private loadState(review: ReviewRecord): FindingsStateFile {
|
|
149
|
+
return this.storage.readFindings(review.id) ?? emptyState(review.branch);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private readArtifactJson<T>(review: ReviewRecord, name: string): T | null {
|
|
153
|
+
const artifact = this.storage.readArtifact(review.id, name);
|
|
154
|
+
if (!artifact) return null;
|
|
155
|
+
try {
|
|
156
|
+
return JSON.parse(artifact.content) as T;
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Findings filtrados + resumo (null se o slug for inválido). */
|
|
163
|
+
findings(
|
|
164
|
+
slug: string,
|
|
165
|
+
params: URLSearchParams,
|
|
166
|
+
): {
|
|
167
|
+
branch: string;
|
|
168
|
+
findings: (FindingState & { absFile: string })[];
|
|
169
|
+
summary: FindingsSummary;
|
|
170
|
+
} | null {
|
|
171
|
+
const review = this.findBySlug(slug);
|
|
172
|
+
if (!review) return null;
|
|
173
|
+
const state = this.loadState(review);
|
|
174
|
+
const filter: FindingsFilter = {
|
|
175
|
+
decision: (params.get("decision") as FindingDecision) || undefined,
|
|
176
|
+
execState: (params.get("exec") as FindingExecState) || undefined,
|
|
177
|
+
severity: (params.get("severity") as Severity) || undefined,
|
|
178
|
+
watchdog: params.get("watchdog") || undefined,
|
|
179
|
+
file: params.get("file") || undefined,
|
|
180
|
+
includeAbsent: params.get("all") === "1",
|
|
181
|
+
};
|
|
182
|
+
return {
|
|
183
|
+
branch: review.branch,
|
|
184
|
+
findings: filterFindings(state, filter).map((finding) =>
|
|
185
|
+
toClientFinding(finding, review),
|
|
186
|
+
),
|
|
187
|
+
summary: summarize(state),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Um finding (null se ausente/inválido). Aceita prefixo único do id. */
|
|
192
|
+
finding(slug: string, id: string): (FindingState & { absFile: string }) | null {
|
|
193
|
+
const review = this.findBySlug(slug);
|
|
194
|
+
if (!review) return null;
|
|
195
|
+
const state = this.loadState(review);
|
|
196
|
+
const resolution = resolveFindingId(state, id);
|
|
197
|
+
if (resolution.status !== "found") return null;
|
|
198
|
+
return toClientFinding(state.findings[resolution.id], review);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Aplica uma atualização a um finding (null se ausente/inválido). */
|
|
202
|
+
update(
|
|
203
|
+
slug: string,
|
|
204
|
+
id: string,
|
|
205
|
+
body: FindingUpdate,
|
|
206
|
+
): (FindingState & { absFile: string }) | null {
|
|
207
|
+
const review = this.findBySlug(slug);
|
|
208
|
+
if (!review) return null;
|
|
209
|
+
const state = this.loadState(review);
|
|
210
|
+
const resolution = resolveFindingId(state, id);
|
|
211
|
+
if (resolution.status !== "found") return null;
|
|
212
|
+
const finding = state.findings[resolution.id];
|
|
213
|
+
applyFindingUpdate(finding, body);
|
|
214
|
+
reorder(state);
|
|
215
|
+
state.updatedAt = systemClock();
|
|
216
|
+
this.storage.writeFindings(review.id, state);
|
|
217
|
+
pingFindingsStatus(this.storage, review.id, state);
|
|
218
|
+
return toClientFinding(finding, review);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Reancora os findings contra os arquivos atuais. */
|
|
222
|
+
sync(slug: string): {
|
|
223
|
+
result: SyncResult;
|
|
224
|
+
summary: FindingsSummary;
|
|
225
|
+
} | null {
|
|
226
|
+
const review = this.findBySlug(slug);
|
|
227
|
+
if (!review) return null;
|
|
228
|
+
const state = this.loadState(review);
|
|
229
|
+
const result = syncState(state, fileReader(process.cwd()));
|
|
230
|
+
this.storage.writeFindings(review.id, state);
|
|
231
|
+
pingFindingsStatus(this.storage, review.id, state);
|
|
232
|
+
return { result, summary: summarize(state) };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Prévia do envio da revisão (candidatos + disponibilidade da PR). */
|
|
236
|
+
submitPreview(slug: string, runner: GhRunner): unknown | null {
|
|
237
|
+
const review = this.findBySlug(slug);
|
|
238
|
+
if (!review) return null;
|
|
239
|
+
const state = this.loadState(review);
|
|
240
|
+
const target = resolvePrTarget(runner, review.branch);
|
|
241
|
+
return buildSubmitPreview(state, target);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Envia os findings selecionados à PR (fail-open). */
|
|
245
|
+
submit(
|
|
246
|
+
slug: string,
|
|
247
|
+
selection: SubmitSelection,
|
|
248
|
+
runner: GhRunner,
|
|
249
|
+
): SubmitResult | null {
|
|
250
|
+
const review = this.findBySlug(slug);
|
|
251
|
+
if (!review) return null;
|
|
252
|
+
const state = this.loadState(review);
|
|
253
|
+
const target = resolvePrTarget(runner, review.branch);
|
|
254
|
+
if (!target.ok) return { available: false, reason: target.reason };
|
|
255
|
+
|
|
256
|
+
const outcome = submitFindings({
|
|
257
|
+
state,
|
|
258
|
+
target: target.target,
|
|
259
|
+
runner,
|
|
260
|
+
selection,
|
|
261
|
+
});
|
|
262
|
+
this.storage.writeFindings(review.id, state);
|
|
263
|
+
pingFindingsStatus(this.storage, review.id, state);
|
|
264
|
+
return { available: true, target: target.target, outcome };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Lista os artefatos da run (sem conteúdo). */
|
|
268
|
+
artifacts(slug: string): ArtifactListItem[] | null {
|
|
269
|
+
const review = this.findBySlug(slug);
|
|
270
|
+
if (!review) return null;
|
|
271
|
+
return this.storage.listArtifacts(review.id).map((artifact) => ({
|
|
272
|
+
name: artifact.name,
|
|
273
|
+
kind: artifact.kind,
|
|
274
|
+
contentType: artifact.contentType,
|
|
275
|
+
updatedAt: artifact.updatedAt,
|
|
276
|
+
}));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Conteúdo de um artefato (`json`/`md`) da run (null se não servível). */
|
|
280
|
+
artifact(slug: string, name: string): { content: string; type: string } | null {
|
|
281
|
+
if (!/\.(json|md)$/i.test(name)) return null;
|
|
282
|
+
const review = this.findBySlug(slug);
|
|
283
|
+
if (!review) return null;
|
|
284
|
+
const artifact = this.storage.readArtifact(review.id, name);
|
|
285
|
+
if (!artifact) return null;
|
|
286
|
+
return { content: artifact.content, type: artifact.contentType };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Finding + caminho absoluto. O cliente monta o link `vscode://file/...` e não
|
|
292
|
+
* conhece o cwd do servidor — o caminho relativo do relatório não basta.
|
|
293
|
+
*/
|
|
294
|
+
function toClientFinding(
|
|
295
|
+
finding: FindingState,
|
|
296
|
+
review: ReviewRecord,
|
|
297
|
+
): FindingState & { absFile: string } {
|
|
298
|
+
return { ...finding, absFile: resolve(review.repoRoot, finding.file) };
|
|
299
|
+
}
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
# Formato de Saída dos Watchdogs
|
|
2
2
|
|
|
3
3
|
Todo watchdog DEVE gravar **um arquivo JSON** com seus achados. O `report-builder.ts`
|
|
4
|
-
consolida os JSONs
|
|
4
|
+
consolida os JSONs no artefato `review.md` do storage global.
|
|
5
5
|
|
|
6
6
|
> **Não** escrever markdown livre no chat. O único output consumido pelo pipeline é o JSON.
|
|
7
7
|
|
|
8
8
|
## Caminho do arquivo
|
|
9
9
|
|
|
10
|
-
O caminho exato é informado no brief do watchdog (gerado por `houndsman`)
|
|
10
|
+
O caminho exato é informado no brief do watchdog (gerado por `houndsman`). Ele aponta para o
|
|
11
|
+
staging global da review:
|
|
11
12
|
|
|
12
13
|
```
|
|
13
|
-
|
|
14
|
+
<HOUNDSMAN_HOME>/work/<reviewId>/findings/<watchdogId>.json
|
|
14
15
|
```
|
|
15
16
|
|
|
16
|
-
- `<
|
|
17
|
+
- `<reviewId>`: UUID da review (estável entre prepare/finalize).
|
|
17
18
|
- `<watchdogId>`: id do watchdog (ex.: `backend-architecture`).
|
|
19
|
+
- Use exatamente o `outputPath` do brief — não invente caminhos.
|
|
18
20
|
|
|
19
21
|
## Schema
|
|
20
22
|
|
package/src/skill/README.md
CHANGED
|
@@ -57,26 +57,33 @@ Um único skill (`/houndsman`), quatro capabilities (detalhadas em `hunt.md`, `t
|
|
|
57
57
|
### Flags
|
|
58
58
|
|
|
59
59
|
`--config <path>` · `--base <branch>` (default `main`) · `--head <ref>` (default `HEAD`) ·
|
|
60
|
-
`--branch <nome>` · `--
|
|
60
|
+
`--branch <nome>` · `--clean` · `--full` · `--no-jev` · `--fail-on-blocking` (no `--finalize`).
|
|
61
61
|
|
|
62
62
|
## Artefatos
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
Runs, findings e artefatos vivem no **storage global** (SQLite em `<HOUNDSMAN_HOME>/houndsman.db`):
|
|
65
65
|
|
|
66
|
-
|
|
|
66
|
+
| Item | Conteúdo |
|
|
67
67
|
|---|---|
|
|
68
|
+
| review | Repo (`origin` normalizado), branch, slug, base/head, commit revisado, status e timestamps |
|
|
69
|
+
| findings | Decisão/execução + fingerprint + âncora + histórico + vínculo com o GitHub |
|
|
68
70
|
| `classification.json` | Watchdogs ativados + status (A/M/D/R) + churn |
|
|
69
71
|
| `candidates.json` | Candidatos da pré-análise Jev |
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
+
| `manifest.json` | Resumo (watchdogs, arquivos, deletados, contexto, orçamento, churn) |
|
|
73
|
+
| `briefs/<watchdog>.md` | Brief enviado a cada sub-agente (auditoria) |
|
|
74
|
+
| `review.md` / `review.<watchdog>.md` | Relatório consolidado e por domínio |
|
|
75
|
+
| `verify.md` | Relatório do `findings verify` |
|
|
76
|
+
| `context.json` / `context-brief.md` / `context-summary.json` | Contexto da PR |
|
|
77
|
+
|
|
78
|
+
O **staging dos subagentes** fica em `<HOUNDSMAN_HOME>/work/<reviewId>/` e é limpo após o
|
|
79
|
+
`--finalize`. Briefs e contexto também são persistidos como artefatos (acima); apenas os
|
|
80
|
+
findings brutos existem só no staging:
|
|
81
|
+
|
|
82
|
+
| Arquivo | Conteúdo |
|
|
83
|
+
|---|---|
|
|
72
84
|
| `briefs/<watchdog>.md` | Instruções prontas para cada sub-agente (arquivos por risco) |
|
|
73
|
-
| `manifest.json` | Resumo (watchdogs, arquivos, deletados, status, contexto, orçamento, churn) |
|
|
74
85
|
| `findings/<watchdog>.json` | Saída dos sub-agentes (schema `OUTPUT-FORMAT.md`) |
|
|
75
|
-
| `
|
|
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) |
|
|
86
|
+
| `context.json` / `context-brief.md` / `context-summary.json` | Contexto da PR |
|
|
80
87
|
|
|
81
88
|
## Estrutura
|
|
82
89
|
|
|
@@ -101,10 +108,11 @@ src/
|
|
|
101
108
|
│ ├── doc-discoverer.ts # descoberta de docs
|
|
102
109
|
│ ├── report-builder.ts # dedupe/render do relatório
|
|
103
110
|
│ ├── finding-anchor.ts # âncora/fingerprint de conteúdo + reancoragem
|
|
104
|
-
│ ├── findings-state.ts # estado de triagem
|
|
111
|
+
│ ├── findings-state.ts # estado de triagem + funções puras
|
|
105
112
|
│ ├── git-revision.ts # refs/ancestralidade + arquivo por commit + diff por intervalo
|
|
106
113
|
│ ├── verify.ts # verificação de findings (tier local + Jev)
|
|
107
|
-
│ ├── run-status.ts #
|
|
114
|
+
│ ├── run-status.ts # status da review (reviewing/review_done/fixing/fix_done)
|
|
115
|
+
│ ├── storage/ # StorageProvider (SQLite default + arquivos) + staging
|
|
108
116
|
│ ├── fix-batch.ts # agrupamento/paginação do lote (findings batch)
|
|
109
117
|
│ ├── registry-check.ts # validações anti-drift (config-driven)
|
|
110
118
|
│ ├── commands/ # context · review · watchdog · setup · update · test
|
|
@@ -118,7 +126,7 @@ src/
|
|
|
118
126
|
```
|
|
119
127
|
houndsman.config.ts # HoundsmanConfig (dados; descoberto em runtime)
|
|
120
128
|
.houndsman/
|
|
121
|
-
├── .gitignore # AUTO — ignora core/
|
|
129
|
+
├── .gitignore # AUTO — ignora core/ (nada de execução fica no projeto)
|
|
122
130
|
├── core/ # AUTO — sincronizado por `houndsman update`
|
|
123
131
|
│ ├── types.ts # snapshot dos tipos + helpers (autocontido)
|
|
124
132
|
│ └── skill/*.md # documentação do skill
|
package/src/skill/SETUP.md
CHANGED
|
@@ -38,12 +38,12 @@ A camada separa o que é **auto-gerado** do que é **do usuário**:
|
|
|
38
38
|
| `.houndsman/doc-mappings.ts` | usuário | path → documentação de contexto |
|
|
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
|
-
| `.houndsman/.gitignore` | **auto** | Ignora `core/`
|
|
41
|
+
| `.houndsman/.gitignore` | **auto** | Ignora `core/` (gerado) |
|
|
42
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
|
-
`bun run houndsman [...]` funciona na raiz do projeto.
|
|
46
|
-
|
|
45
|
+
`bun run houndsman [...]` funciona na raiz do projeto. Runs, findings e artefatos ficam no
|
|
46
|
+
storage global (`<HOUNDSMAN_HOME>/houndsman.db`) — fora do projeto.
|
|
47
47
|
|
|
48
48
|
Flags: `--name <projeto>` · `--harness agents,claude|both|none` · `--force` (sobrescreve só os
|
|
49
49
|
arquivos do usuário). É **idempotente** — arquivos do usuário existentes são mantidos.
|
|
@@ -80,7 +80,7 @@ Definido em `src/core/config.ts` (pacote) e espelhado em `.houndsman/core/types.
|
|
|
80
80
|
| Campo | Descrição |
|
|
81
81
|
|---|---|
|
|
82
82
|
| `projectName` | Nome legível (prompts/docs). |
|
|
83
|
-
| `
|
|
83
|
+
| `storage` | **Opcional** — `{ provider: "sqlite" \| "file", path? }`. Default: SQLite em `<HOUNDSMAN_HOME>/houndsman.db`. |
|
|
84
84
|
| `watchdogs` | Definições dos watchdogs (incluem `classify`); os **ids são derivados** das chaves. |
|
|
85
85
|
| `exclusions` | Arquivos que nunca entram na revisão. |
|
|
86
86
|
| `docMappings` | Path → documentação de contexto. |
|
package/src/skill/fix.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
> (`.agents/skills/houndsman/SKILL.md` e `.claude/skills/houndsman/SKILL.md`).
|
|
5
5
|
|
|
6
6
|
Corrige os achados de uma revisão **já finalizada** (`hunt`): lê a triagem, aplica as
|
|
7
|
-
correções marcadas e mantém o estado de triagem em dia — **sem editar o
|
|
7
|
+
correções marcadas e mantém o estado de triagem em dia — **sem editar o banco à mão**.
|
|
8
8
|
|
|
9
9
|
Invocação: `/houndsman fix`. Não revisa nem altera regras; consome o que o `hunt` produziu.
|
|
10
10
|
|
|
@@ -15,8 +15,8 @@ Invocação: `/houndsman fix`. Não revisa nem altera regras; consome o que o `h
|
|
|
15
15
|
`findings set <id> --decision execute`.
|
|
16
16
|
- "Corrija os findings", "aplica as correções", "resolve os bloqueantes".
|
|
17
17
|
|
|
18
|
-
> **Pré-requisito:** existe
|
|
19
|
-
>
|
|
18
|
+
> **Pré-requisito:** existe uma review com findings no storage global (gerada no
|
|
19
|
+
> `--finalize`). Se não existir, a revisão não foi finalizada — rode o `hunt` antes.
|
|
20
20
|
> A triagem (`execute`) é feita na UI (`bun run houndsman server --open`) ou via `findings set`,
|
|
21
21
|
> e é o que alimenta a fila de correção.
|
|
22
22
|
|
|
@@ -153,7 +153,7 @@ Subagente serve para **isolar contexto**, não para paralelizar. Regras:
|
|
|
153
153
|
colidem.
|
|
154
154
|
- O subagente recebe **só** o grupo atual (findings + arquivo) e devolve um resumo compacto
|
|
155
155
|
(ids corrigidos / não corrigidos + motivo). Assim o agente principal não carrega o arquivo.
|
|
156
|
-
- A escrita
|
|
156
|
+
- A escrita da triagem fica **centralizada no agente principal**: o subagente apenas
|
|
157
157
|
lê/edita; `sync` / `done` / `set` são do orquestrador. Nada de `findings` dentro do subagente.
|
|
158
158
|
- Grupos pequenos (1–2 findings) o próprio agente corrige inline.
|
|
159
159
|
|
|
@@ -196,7 +196,7 @@ Arquivo: <file>
|
|
|
196
196
|
|
|
197
197
|
- ❌ Carregar a fila inteira de uma vez (`findings list` sem filtro) — use `findings batch`.
|
|
198
198
|
- ❌ Corrigir vários grupos em paralelo (subagentes simultâneos sobre arquivos que se relacionam).
|
|
199
|
-
- ❌ Editar
|
|
199
|
+
- ❌ Editar o banco/storage à mão — use os comandos `findings`.
|
|
200
200
|
- ❌ Corrigir findings `pending` ou `ignore` sem o usuário marcar `execute`.
|
|
201
201
|
- ❌ Chavear por `(file, line)` — a confiança é o **fingerprint** de conteúdo.
|
|
202
202
|
- ❌ Correção cosmética para "sumir" com o finding sem atender a `rule`.
|
package/src/skill/hunt.md
CHANGED
|
@@ -21,7 +21,7 @@ mudanças antes de push.
|
|
|
21
21
|
| 0. Contexto | `houndsman context` | subagente | `context.json` + `context-brief.md` → `context-summary.json` |
|
|
22
22
|
| 1. Preparar | `houndsman` | skill | `classification.json` + `candidates.json` + `briefs/` + `manifest.json` |
|
|
23
23
|
| 2. Revisar | *(subagentes)* | skill (via `runSubagent`) | `findings/<watchdog>.json` (um por watchdog) |
|
|
24
|
-
| 3. Finalizar | `houndsman --finalize` | skill | `review.md` +
|
|
24
|
+
| 3. Finalizar | `houndsman --finalize` | skill | `review.md` + triagem/status no storage |
|
|
25
25
|
|
|
26
26
|
> Por que comandos separados? Entre o passo 1 e o 3 acontece trabalho de LLM (os subagentes).
|
|
27
27
|
> Depois do passo 3, a correção dos achados é a capability **`fix`** — ver `fix.md`.
|
|
@@ -70,14 +70,19 @@ leia o `context-brief.md` indicado na saída; siga as instruções e grave
|
|
|
70
70
|
houndsman --base main
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
Grava os artefatos no **storage global** (SQLite em `<HOUNDSMAN_HOME>/houndsman.db`) e o
|
|
74
|
+
staging dos subagentes em `<HOUNDSMAN_HOME>/work/<reviewId>/`:
|
|
74
75
|
|
|
75
76
|
| Artefato | Conteúdo |
|
|
76
77
|
|---|---|
|
|
77
|
-
| `classification.json` | Watchdogs ativados por arquivo + status (A/M/D/R) + churn |
|
|
78
|
-
| `candidates.json` | Indícios da pré-análise Jev (regra + arquivo + confiança) |
|
|
79
|
-
| `briefs/<watchdog>.md` | Instruções prontas para cada subagente (
|
|
80
|
-
| `manifest.json` | Resumo: watchdogs, arquivos, deletados, status, contexto, Jev, orçamento |
|
|
78
|
+
| `classification.json` | Watchdogs ativados por arquivo + status (A/M/D/R) + churn (storage) |
|
|
79
|
+
| `candidates.json` | Indícios da pré-análise Jev (regra + arquivo + confiança) (storage) |
|
|
80
|
+
| `briefs/<watchdog>.md` | Instruções prontas para cada subagente (staging + storage, para auditoria) |
|
|
81
|
+
| `manifest.json` | Resumo: watchdogs, arquivos, deletados, status, contexto, Jev, orçamento (storage) |
|
|
82
|
+
|
|
83
|
+
> O prepare imprime o caminho absoluto do diretório de briefs e cada brief traz o
|
|
84
|
+
> `outputPath` do JSON que o subagente deve gravar. Briefs e contexto também ficam
|
|
85
|
+
> persistidos no storage para auditoria; o staging é limpo após o `--finalize`.
|
|
81
86
|
|
|
82
87
|
**Excluídos automaticamente:** lock files, `dist/`, `build/`, `.env*`, imagens, source
|
|
83
88
|
maps (padrões do projeto em `.houndsman/exclusions.ts`).
|
|
@@ -111,7 +116,7 @@ as regras atômicas dos watchdogs. Respostas acima do limiar viram **candidatos*
|
|
|
111
116
|
|
|
112
117
|
### Review incremental (da 2ª revisão em diante)
|
|
113
118
|
|
|
114
|
-
Cada `--finalize` grava o **commit revisado** (`
|
|
119
|
+
Cada `--finalize` grava o **commit revisado** (`reviewedSha` da review no storage). No prepare
|
|
115
120
|
seguinte, se o `HEAD` descende desse commit (sem rebase), o motor revisa **só o delta** — os
|
|
116
121
|
arquivos alterados desde a última revisão — e **carrega** os findings dos arquivos inalterados
|
|
117
122
|
(carry-over), sem re-revisá-los. O `manifest.json` traz o bloco `incremental` (`from`,
|
|
@@ -131,11 +136,11 @@ Um subagente por brief. Despache em **ondas** (ex.: até 8 subagentes por bloco
|
|
|
131
136
|
calls) — evita estourar limites de taxa quando há muitos watchdogs. O prompt é o próprio brief:
|
|
132
137
|
|
|
133
138
|
```
|
|
134
|
-
Leia e execute o brief:
|
|
139
|
+
Leia e execute o brief: <caminho absoluto impresso pelo prepare>/briefs/<watchdog-id>.md
|
|
135
140
|
Você é o watchdog <watchdog-id> do {{projectName}}.
|
|
136
141
|
```
|
|
137
142
|
|
|
138
|
-
Cada subagente grava `
|
|
143
|
+
Cada subagente grava o `outputPath` indicado no brief (schema `.houndsman/core/skill/OUTPUT-FORMAT.md`).
|
|
139
144
|
Sem achados → `"findings": []` (arquivo obrigatório). Arquivos na seção **Fora do orçamento**
|
|
140
145
|
não são lidos: registre-os em `skipped`.
|
|
141
146
|
|
|
@@ -150,17 +155,17 @@ não são lidos: registre-os em `skipped`.
|
|
|
150
155
|
houndsman --finalize
|
|
151
156
|
```
|
|
152
157
|
|
|
153
|
-
Falha com mensagem clara se faltar algum `findings/<watchdog>.json
|
|
158
|
+
Falha com mensagem clara se faltar algum `findings/<watchdog>.json` no staging. Quando todos existem:
|
|
154
159
|
1. Valida o schema de cada JSON
|
|
155
160
|
2. Deduplica achados por `(file, line, rule)` mesclando watchdogs sobrepostos
|
|
156
161
|
3. Agrupa por severidade: ❌ blocking > ⚠️ attention > 💡 suggestion > 🔎 nit
|
|
157
|
-
4. Grava
|
|
162
|
+
4. Grava o artefato `review.md` no storage + tabela de cobertura
|
|
158
163
|
5. Anexa os candidatos Jev não confirmados em `🔎 Candidatos não verificados` (não bloqueiam)
|
|
159
164
|
6. Agrupa os achados **por arquivo** dentro de cada severidade (navegabilidade em PRs grandes)
|
|
160
165
|
7. Grava um `review.<watchdog>.md` por watchdog com achados e indexa no `review.md`
|
|
161
166
|
(**Relatórios por domínio**) — mantém o relatório consolidado enxuto
|
|
162
|
-
8.
|
|
163
|
-
|
|
167
|
+
8. Persiste a **triagem** no storage — um `id` curto e um `fingerprint` de conteúdo por
|
|
168
|
+
finding, preservados entre execuções
|
|
164
169
|
|
|
165
170
|
Após finalizar, corrija os achados com a capability **`fix`** (`fix.md`): a UI ou os
|
|
166
171
|
comandos `findings` levam do achado à correção.
|
|
@@ -212,15 +217,15 @@ houndsman context (gh) ──► context.json ──► subagente ─
|
|
|
212
217
|
↓
|
|
213
218
|
git diff --name-status (base...head)
|
|
214
219
|
↓
|
|
215
|
-
[1] houndsman ──► classification
|
|
220
|
+
[1] houndsman ──► storage (classification/candidates/manifest) + briefs/ no staging
|
|
216
221
|
↓
|
|
217
|
-
[2] N subagentes em paralelo ──► findings/<watchdog>.json
|
|
222
|
+
[2] N subagentes em paralelo ──► findings/<watchdog>.json no staging
|
|
218
223
|
↓
|
|
219
|
-
[3] houndsman --finalize ──► review.md +
|
|
224
|
+
[3] houndsman --finalize ──► review.md + triagem no storage (review_done)
|
|
220
225
|
↓
|
|
221
226
|
[4] server (UI) ──► validar a revisão + marcar decision: execute
|
|
222
227
|
↓
|
|
223
|
-
[5] fix ──► correções +
|
|
228
|
+
[5] fix ──► correções + triagem/status no storage (fix_done)
|
|
224
229
|
```
|
|
225
230
|
|
|
226
231
|
---
|
|
@@ -244,9 +249,13 @@ Se a branch não for informada, detecta com `git branch --show-current`. Base do
|
|
|
244
249
|
| `houndsman --finalize` | Passo 3 — gerar o relatório |
|
|
245
250
|
| `houndsman watchdog <id>` | Contexto de um watchdog (`--json`, `--rules`) |
|
|
246
251
|
|
|
247
|
-
Flags do review: `--base` · `--head` · `--branch` · `--
|
|
252
|
+
Flags do review: `--base` · `--head` · `--branch` · `--clean` · `--full` (força
|
|
248
253
|
completo) · `--no-jev` · `--fail-on-blocking` (com `--finalize`).
|
|
249
254
|
|
|
255
|
+
> Persistência: SQLite global em `<HOUNDSMAN_HOME>/houndsman.db`. O staging dos subagentes
|
|
256
|
+
> (`briefs/`, `findings/`, contexto) vive em `<HOUNDSMAN_HOME>/work/<reviewId>/` e é limpo
|
|
257
|
+
> depois do `--finalize`.
|
|
258
|
+
|
|
250
259
|
---
|
|
251
260
|
|
|
252
261
|
## Anti-padrões
|
package/src/skill/train.md
CHANGED
|
@@ -118,7 +118,7 @@ Cada informação tem o seu campo — não espelhe a regra em vários lugares:
|
|
|
118
118
|
|---|---|
|
|
119
119
|
| `.houndsman/watchdogs/<id>.ts` | **Fonte de verdade**: `defineWatchdog` com `rules` (severidade + texto), `scope`, `description`, `reviewFocus`, `exclude`, `breakingChange`, `contextFiles`, `classify` |
|
|
120
120
|
| `.houndsman/watchdogs/index.ts` | `WATCHDOG_DEFINITIONS` — as **chaves definem os ids** (`WatchdogId`); `watchdogIds` é derivado |
|
|
121
|
-
| `houndsman.config.ts` | `houndsmanConfig` do projeto — `projectName`, `
|
|
121
|
+
| `houndsman.config.ts` | `houndsmanConfig` do projeto — `projectName`, `storage`, `watchdogs`, `exclusions`, `docMappings` (descoberto em runtime) |
|
|
122
122
|
| `.houndsman/exclusions.ts` | `EXCLUSION_PATTERNS` + `isExcluded` |
|
|
123
123
|
| `.houndsman/doc-mappings.ts` | `DOC_MAPPINGS` (path → docs) |
|
|
124
124
|
| `.houndsman/core/types.ts` | Snapshot gerado dos tipos + helpers (autocontido) |
|
package/src/skill/verify.md
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
> (`.agents/skills/houndsman/SKILL.md` e `.claude/skills/houndsman/SKILL.md`).
|
|
5
5
|
|
|
6
6
|
Confere se os findings de uma revisão **já finalizada** foram resolvidos — **sem re-revisar a
|
|
7
|
-
PR**. Invocação: `/houndsman verify`. Consome
|
|
8
|
-
regras.
|
|
7
|
+
PR**. Invocação: `/houndsman verify`. Consome a triagem persistida pelo `hunt`; não revisa nem
|
|
8
|
+
altera regras.
|
|
9
9
|
|
|
10
10
|
**Quando usar:**
|
|
11
11
|
|
|
12
12
|
- Depois de corrigir achados (`fix`), para confirmar que as violações sumiram.
|
|
13
13
|
- As violações foram resolvidas? · confere se ainda está lá · valida o fix.
|
|
14
14
|
|
|
15
|
-
> **Pré-requisito:** existe
|
|
15
|
+
> **Pré-requisito:** existe uma review com findings persistidos (gerada no `--finalize`).
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
@@ -38,10 +38,10 @@ completa.
|
|
|
38
38
|
```bash
|
|
39
39
|
bun run houndsman findings verify # relatório (não altera o estado)
|
|
40
40
|
bun run houndsman findings verify --json # saída estruturada
|
|
41
|
-
bun run houndsman findings verify --apply # marca os resolvidos como done (ping no
|
|
41
|
+
bun run houndsman findings verify --apply # marca os resolvidos como done (ping no status)
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
- Sem `--apply`: **só relatório** (grava `verify.md`
|
|
44
|
+
- Sem `--apply`: **só relatório** (grava o artefato `verify.md` no storage). Nada muda na triagem.
|
|
45
45
|
- Com `--apply`: os findings com status **`resolved`** viram `execState: done` (histórico
|
|
46
46
|
`by: "verify"`). `persisting` e `uncertain` **não** são tocados.
|
|
47
47
|
|