@payconductor/houndsman 0.12.0 → 0.14.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 +23 -2
- package/package.json +1 -1
- package/src/cli/commands/compare-command.ts +112 -0
- package/src/cli/commands/context-command.ts +1 -0
- package/src/cli/commands/doctor-command.ts +22 -0
- package/src/cli/commands/findings-command.ts +11 -1
- package/src/cli/commands/hunt-command.ts +479 -0
- package/src/cli/commands/review-command.ts +58 -2
- package/src/cli/commands/status-command.ts +3 -2
- package/src/cli/help.ts +20 -3
- package/src/cli/index.ts +20 -3
- package/src/cli/progress.ts +50 -0
- package/src/core/ai/ai-client.ts +38 -2
- package/src/core/ai/ai-sdk-model-provider.ts +39 -0
- package/src/core/ai/features/brief-enricher.ts +1 -1
- package/src/core/ai/features/business-analyzer.ts +61 -0
- package/src/core/ai/features/business-summarizer.ts +44 -0
- package/src/core/ai/index.ts +14 -1
- package/src/core/ai/prompts/brief.ts +33 -7
- package/src/core/ai/prompts/business.ts +76 -0
- package/src/core/ai/provider-capabilities.ts +12 -0
- package/src/core/ai/schemas.ts +70 -2
- package/src/core/ai/types.ts +2 -0
- package/src/core/ai/usage-recorder.ts +53 -1
- package/src/core/brief.ts +246 -12
- package/src/core/builtins.ts +108 -0
- package/src/core/business-summary.ts +583 -0
- package/src/core/commands/compare.ts +233 -0
- package/src/core/commands/context.ts +6 -2
- package/src/core/commands/findings.ts +38 -30
- package/src/core/commands/review.ts +6 -0
- package/src/core/commands/scaffold.ts +38 -0
- package/src/core/commands/status.ts +9 -4
- package/src/core/compare.ts +243 -0
- package/src/core/concurrency.ts +30 -0
- package/src/core/config.ts +57 -1
- package/src/core/defaults.ts +63 -2
- package/src/core/domain/budget.ts +62 -4
- package/src/core/domain/classification.ts +12 -2
- package/src/core/engine/carryover.ts +4 -0
- package/src/core/engine/finalize-pipeline.ts +347 -0
- package/src/core/engine/prepare-pipeline.ts +1294 -0
- package/src/core/engine/review-context.ts +51 -0
- package/src/core/engine/review-options.ts +47 -0
- package/src/core/engine/review-scope.ts +106 -26
- package/src/core/engine/review-session.ts +18 -16
- package/src/core/engine/review-workspace.ts +14 -0
- package/src/core/engine/run-artifacts.ts +196 -0
- package/src/core/engine/run-format.ts +4 -0
- package/src/core/env.ts +14 -0
- package/src/core/findings-state.ts +13 -1
- package/src/core/github-review.ts +5 -2
- package/src/core/hunt/hunt-runner.ts +418 -0
- package/src/core/hunt/index.ts +61 -0
- package/src/core/hunt/maki-install.ts +331 -0
- package/src/core/hunt/maki-intent-prompt.ts +50 -0
- package/src/core/hunt/maki-model.ts +101 -0
- package/src/core/hunt/maki-prompt.ts +83 -0
- package/src/core/hunt/maki-release.ts +104 -0
- package/src/core/hunt/maki-runner.ts +276 -0
- package/src/core/hunt/pool.ts +8 -0
- package/src/core/logging/index.ts +7 -0
- package/src/core/logging/logger.ts +19 -0
- package/src/core/logging/progress.ts +366 -0
- package/src/core/orchestrator.ts +33 -1180
- package/src/core/paths.ts +6 -3
- package/src/core/pr-context.ts +2 -4
- package/src/core/prepare-report.ts +61 -0
- package/src/core/project-config.ts +46 -1
- package/src/core/propagation.ts +127 -0
- package/src/core/registry-check.ts +60 -21
- package/src/core/renderers/manifest-writer.ts +13 -0
- package/src/core/renderers/resume-renderer.ts +151 -11
- package/src/core/report-builder.ts +108 -278
- package/src/core/storage/file/file-storage.ts +7 -3
- package/src/core/storage/sqlite/mappers.ts +10 -0
- package/src/core/storage/sqlite/schema.ts +104 -23
- package/src/core/storage/sqlite/sqlite-storage.ts +22 -9
- package/src/core/storage/types.ts +17 -4
- package/src/core/storage/work-area.ts +59 -1
- package/src/core/types.ts +31 -0
- package/src/core/watchdog-registry.ts +21 -3
- package/src/index.ts +78 -4
- package/src/server/client/ArtifactsPanel.tsx +4 -2
- package/src/server/client/FindingCard.tsx +21 -1
- package/src/server/client/RunsSection.tsx +5 -0
- package/src/server/client/utils/constants.ts +21 -11
- package/src/server/client/utils/markdown.ts +1 -1
- package/src/server/client/utils/types.ts +14 -2
- package/src/server/run-repository.ts +35 -14
- package/src/server/styles.css +2 -0
- package/src/skill/OUTPUT-FORMAT.md +36 -10
- package/src/skill/README.md +30 -14
- package/src/skill/SETUP.md +22 -1
- package/src/skill/SKILL.md +17 -4
- package/src/skill/example-resume.md +43 -0
- package/src/skill/hunt.md +181 -58
- package/src/tests/__snapshots__/baseline.test.ts.snap +206 -178
- package/src/tests/ai-core.test.ts +84 -1
- package/src/tests/ai-features.test.ts +95 -4
- package/src/tests/baseline.test.ts +29 -121
- package/src/tests/brief-scope.test.ts +250 -0
- package/src/tests/business-summary.test.ts +258 -0
- package/src/tests/cli.test.ts +1 -0
- package/src/tests/compare.test.ts +233 -0
- package/src/tests/fixtures/fake-model-provider.ts +2 -1
- package/src/tests/hunt-cli.test.ts +185 -0
- package/src/tests/hunt-install.test.ts +153 -0
- package/src/tests/hunt-maki.test.ts +525 -0
- package/src/tests/markdown.test.ts +3 -3
- package/src/tests/progress.test.ts +145 -0
- package/src/tests/propagation.test.ts +119 -0
- package/src/tests/review-resume.test.ts +60 -1
- package/src/tests/review-scope.test.ts +90 -19
- package/src/tests/review-scripts.test.ts +106 -218
- package/src/tests/server.test.ts +7 -4
- package/src/tests/storage.test.ts +194 -9
- package/src/core/renderers/report-renderer.ts +0 -66
- package/src/skill/example-review.md +0 -66
|
@@ -157,7 +157,7 @@ export function FindingCard({
|
|
|
157
157
|
</span>
|
|
158
158
|
)}
|
|
159
159
|
{!finding.present && (
|
|
160
|
-
<span class="tag" title="Não aparece
|
|
160
|
+
<span class="tag" title="Não aparece na última revisão">
|
|
161
161
|
ausente
|
|
162
162
|
</span>
|
|
163
163
|
)}
|
|
@@ -206,6 +206,26 @@ export function FindingCard({
|
|
|
206
206
|
<div class="card-body">
|
|
207
207
|
<div class="meta-row">{finding.rule}</div>
|
|
208
208
|
|
|
209
|
+
{finding.scenario && (
|
|
210
|
+
<div class="action-block">
|
|
211
|
+
<Sparkles size={16} class="action-icon" />
|
|
212
|
+
<div class="action-text">
|
|
213
|
+
<span class="action-label">Cenário:</span>
|
|
214
|
+
<MarkdownText text={finding.scenario} />
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
|
|
219
|
+
{finding.impact && (
|
|
220
|
+
<div class="action-block">
|
|
221
|
+
<Sparkles size={16} class="action-icon" />
|
|
222
|
+
<div class="action-text">
|
|
223
|
+
<span class="action-label">Impacto:</span>
|
|
224
|
+
<MarkdownText text={finding.impact} />
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
|
|
209
229
|
<div class="action-block">
|
|
210
230
|
<Sparkles size={16} class="action-icon" />
|
|
211
231
|
<div class="action-text">
|
|
@@ -30,6 +30,11 @@ function RunItem({
|
|
|
30
30
|
<span class="run-top">
|
|
31
31
|
<BranchIcon size={14} class="run-icon" />
|
|
32
32
|
<span class="run-branch">{run.branch}</span>
|
|
33
|
+
{run.variant ? (
|
|
34
|
+
<span class="run-variant" title="Variante A/B">
|
|
35
|
+
{run.variant}
|
|
36
|
+
</span>
|
|
37
|
+
) : null}
|
|
33
38
|
</span>
|
|
34
39
|
<span class="run-meta">
|
|
35
40
|
<span class="run-when">
|
|
@@ -83,13 +83,13 @@ export function describeRunStatus(
|
|
|
83
83
|
|
|
84
84
|
/** Ordem canônica dos tipos de artefato na lista da UI. */
|
|
85
85
|
const KIND_ORDER: ArtifactKind[] = [
|
|
86
|
-
"
|
|
86
|
+
"compare",
|
|
87
87
|
"resume",
|
|
88
88
|
"technical",
|
|
89
|
-
"domain-review",
|
|
90
89
|
"verify",
|
|
91
90
|
"brief",
|
|
92
91
|
"manifest",
|
|
92
|
+
"prepare",
|
|
93
93
|
"classification",
|
|
94
94
|
"candidates",
|
|
95
95
|
"ast",
|
|
@@ -100,6 +100,8 @@ const KIND_ORDER: ArtifactKind[] = [
|
|
|
100
100
|
"context",
|
|
101
101
|
"context-brief",
|
|
102
102
|
"context-summary",
|
|
103
|
+
"business",
|
|
104
|
+
"business-brief",
|
|
103
105
|
"finding-report",
|
|
104
106
|
"other",
|
|
105
107
|
];
|
|
@@ -107,12 +109,12 @@ const KIND_ORDER: ArtifactKind[] = [
|
|
|
107
109
|
/** Grupo visual de cada tipo (relatórios, briefs, núcleo, contexto…). */
|
|
108
110
|
const GROUP_BY_KIND: Record<ArtifactKind, { key: string; label: string; order: number }> =
|
|
109
111
|
{
|
|
110
|
-
|
|
112
|
+
compare: { key: "report", label: "Relatórios", order: 0 },
|
|
111
113
|
resume: { key: "report", label: "Relatórios", order: 0 },
|
|
112
114
|
technical: { key: "core", label: "Núcleo da revisão", order: 3 },
|
|
113
|
-
"domain-review": { key: "report", label: "Relatórios", order: 0 },
|
|
114
115
|
verify: { key: "verify", label: "Verificação", order: 1 },
|
|
115
116
|
brief: { key: "brief", label: "Briefs (auditoria)", order: 2 },
|
|
117
|
+
prepare: { key: "core", label: "Núcleo da revisão", order: 3 },
|
|
116
118
|
manifest: { key: "core", label: "Núcleo da revisão", order: 3 },
|
|
117
119
|
classification: { key: "core", label: "Núcleo da revisão", order: 3 },
|
|
118
120
|
candidates: { key: "core", label: "Núcleo da revisão", order: 3 },
|
|
@@ -124,17 +126,19 @@ const GROUP_BY_KIND: Record<ArtifactKind, { key: string; label: string; order: n
|
|
|
124
126
|
context: { key: "context", label: "Contexto da PR", order: 5 },
|
|
125
127
|
"context-brief": { key: "context", label: "Contexto da PR", order: 5 },
|
|
126
128
|
"context-summary": { key: "context", label: "Contexto da PR", order: 5 },
|
|
129
|
+
business: { key: "business", label: "Intenção da PR", order: 5 },
|
|
130
|
+
"business-brief": { key: "business", label: "Intenção da PR", order: 5 },
|
|
127
131
|
"finding-report": { key: "other", label: "Outros", order: 6 },
|
|
128
132
|
other: { key: "other", label: "Outros", order: 6 },
|
|
129
133
|
};
|
|
130
134
|
|
|
131
135
|
/** Rótulo curto do tipo (badge do item). */
|
|
132
136
|
export const ARTIFACT_KIND_LABELS: Record<ArtifactKind, string> = {
|
|
133
|
-
|
|
137
|
+
compare: "comparação",
|
|
134
138
|
resume: "resumo",
|
|
135
139
|
technical: "pontos técnicos",
|
|
136
|
-
"domain-review": "domínio",
|
|
137
140
|
verify: "verify",
|
|
141
|
+
prepare: "prepare",
|
|
138
142
|
brief: "brief",
|
|
139
143
|
manifest: "manifesto",
|
|
140
144
|
classification: "classificação",
|
|
@@ -147,6 +151,8 @@ export const ARTIFACT_KIND_LABELS: Record<ArtifactKind, string> = {
|
|
|
147
151
|
context: "contexto",
|
|
148
152
|
"context-brief": "brief ctx",
|
|
149
153
|
"context-summary": "resumo ctx",
|
|
154
|
+
business: "intenção",
|
|
155
|
+
"business-brief": "brief intenção",
|
|
150
156
|
"finding-report": "findings",
|
|
151
157
|
other: "outro",
|
|
152
158
|
};
|
|
@@ -161,16 +167,16 @@ export function displayArtifactName(meta: ArtifactMeta): string {
|
|
|
161
167
|
/** Descrição curta do artefato (cabeçalho do painel). */
|
|
162
168
|
export function describeArtifact(meta: ArtifactMeta): string {
|
|
163
169
|
switch (meta.kind) {
|
|
164
|
-
case "
|
|
165
|
-
return "
|
|
170
|
+
case "compare":
|
|
171
|
+
return "Comparação A/B entre duas runs: achados novos/sumidos, cobertura, tempo e tokens.";
|
|
166
172
|
case "resume":
|
|
167
|
-
return "Resumo final
|
|
173
|
+
return "Resumo final: negócio, pontos técnicos, cobertura por domínio e conclusão.";
|
|
168
174
|
case "technical":
|
|
169
175
|
return "Destaques estruturais da mudança (módulos, contratos, dependências) usados no resumo final.";
|
|
170
|
-
case "domain-review":
|
|
171
|
-
return "Relatório isolado do domínio: achados daquele watchdog.";
|
|
172
176
|
case "verify":
|
|
173
177
|
return "Resultado do `findings verify` (resolvidos, persistentes e incertos).";
|
|
178
|
+
case "prepare":
|
|
179
|
+
return "Duração por etapa do prepare e uso de IA (diagnóstico de performance).";
|
|
174
180
|
case "brief":
|
|
175
181
|
return "Instruções enviadas ao subagente (auditoria do prompt).";
|
|
176
182
|
case "manifest":
|
|
@@ -189,6 +195,10 @@ export function describeArtifact(meta: ArtifactMeta): string {
|
|
|
189
195
|
return "Brief do subagente que resume o contexto da PR.";
|
|
190
196
|
case "context-summary":
|
|
191
197
|
return "Resumo da PR (objetivo, motivação e alterações) gerado no Passo 0.";
|
|
198
|
+
case "business":
|
|
199
|
+
return "Modelo de intenção da PR: o que ela deve entregar (fluxos, invariantes, contratos e estados).";
|
|
200
|
+
case "business-brief":
|
|
201
|
+
return "Brief do passo de intenção (subagente na skill / pré-passe maki).";
|
|
192
202
|
default:
|
|
193
203
|
return meta.name;
|
|
194
204
|
}
|
|
@@ -240,7 +240,7 @@ export function sectionBlocks(text: string, title: string): MarkdownBlock[] {
|
|
|
240
240
|
const FILE_EXTENSION =
|
|
241
241
|
/\.(?:[cm]?[jt]sx?|json|md|ya?ml|toml|css|scss|html?|py|go|rs|java|kt|cs|php|rb|sh|sql|txt|lock)$/i;
|
|
242
242
|
|
|
243
|
-
/** Caminho/arquivo dentro de um texto corrido (`src/foo.ts`, `
|
|
243
|
+
/** Caminho/arquivo dentro de um texto corrido (`src/foo.ts`, `manifest.json`). */
|
|
244
244
|
const PATH_TOKEN =
|
|
245
245
|
/(?:[\w.-]+[\\/])*[\w-]+\.(?:[cm]?[jt]sx?|json|md|ya?ml|toml|css|scss|html?|py|go|rs|java|kt|cs|php|rb|sh|sql|txt|lock)\b/g;
|
|
246
246
|
|
|
@@ -32,6 +32,14 @@ export type Finding = {
|
|
|
32
32
|
message: string;
|
|
33
33
|
action: string;
|
|
34
34
|
evidence?: string;
|
|
35
|
+
/** Gatilho concreto do gap (fora de regra). */
|
|
36
|
+
scenario?: string;
|
|
37
|
+
/** Consequência observável do gap. */
|
|
38
|
+
impact?: string;
|
|
39
|
+
/** Confiança do revisor (0–1). */
|
|
40
|
+
confidence?: number;
|
|
41
|
+
/** Origem do achado: regra, lógica ou domínio. */
|
|
42
|
+
basis?: "rule" | "logic" | "domain";
|
|
35
43
|
count: number;
|
|
36
44
|
present: boolean;
|
|
37
45
|
decision: Decision;
|
|
@@ -59,17 +67,19 @@ export type ArtifactKind =
|
|
|
59
67
|
| "context"
|
|
60
68
|
| "context-brief"
|
|
61
69
|
| "context-summary"
|
|
70
|
+
| "business"
|
|
71
|
+
| "business-brief"
|
|
62
72
|
| "finding-report"
|
|
63
|
-
| "review"
|
|
64
73
|
| "resume"
|
|
65
74
|
| "technical"
|
|
66
|
-
| "domain-review"
|
|
67
75
|
| "verify"
|
|
68
76
|
| "ast"
|
|
69
77
|
| "ast-impact"
|
|
70
78
|
| "ai"
|
|
71
79
|
| "ai-usage"
|
|
72
80
|
| "ai-brief"
|
|
81
|
+
| "prepare"
|
|
82
|
+
| "compare"
|
|
73
83
|
| "other";
|
|
74
84
|
|
|
75
85
|
/** Metadados de artefato retornados pela API (sem conteúdo). */
|
|
@@ -83,6 +93,8 @@ export type ArtifactMeta = {
|
|
|
83
93
|
export type RunSummary = {
|
|
84
94
|
slug: string;
|
|
85
95
|
branch: string;
|
|
96
|
+
/** Variante A/B (ausente = run principal da branch). */
|
|
97
|
+
variant?: string;
|
|
86
98
|
/** Worktree onde a run foi executada (ausente = clone principal). */
|
|
87
99
|
worktreePath?: string;
|
|
88
100
|
prNumber?: number;
|
|
@@ -48,6 +48,8 @@ import { fetchPrComments, type PrCommentsResponse } from "./pr-comments";
|
|
|
48
48
|
export type RunListItem = {
|
|
49
49
|
slug: string;
|
|
50
50
|
branch: string;
|
|
51
|
+
/** Variante A/B (ausente = run principal da branch). */
|
|
52
|
+
variant?: string;
|
|
51
53
|
/** Worktree onde a run foi executada (ausente = clone principal). */
|
|
52
54
|
worktreePath?: string;
|
|
53
55
|
/** PR revisada (quando iniciada via `--pr`). */
|
|
@@ -63,6 +65,8 @@ export type RunListItem = {
|
|
|
63
65
|
export type RunDetail = {
|
|
64
66
|
slug: string;
|
|
65
67
|
branch: string;
|
|
68
|
+
/** Variante A/B (ausente = run principal da branch). */
|
|
69
|
+
variant?: string;
|
|
66
70
|
/** Worktree onde a run foi executada (ausente = clone principal). */
|
|
67
71
|
worktreePath?: string;
|
|
68
72
|
/** PR revisada (quando iniciada via `--pr`). */
|
|
@@ -129,6 +133,7 @@ export class RunRepository {
|
|
|
129
133
|
return this.storage.listReviews(this.repo.key).map((review) => {
|
|
130
134
|
const state = this.storage.readFindings(review.id);
|
|
131
135
|
return {
|
|
136
|
+
variant: review.variant,
|
|
132
137
|
slug: review.slug,
|
|
133
138
|
branch: review.branch,
|
|
134
139
|
worktreePath: review.worktreePath,
|
|
@@ -144,11 +149,11 @@ export class RunRepository {
|
|
|
144
149
|
|
|
145
150
|
/** Mapa branch → slug das runs (usado para casar PRs com execuções). */
|
|
146
151
|
branchIndex(): Map<string, string> {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
const index = new Map<string, string>();
|
|
153
|
+
for (const review of this.reviewsForBranchLinks()) {
|
|
154
|
+
if (!index.has(review.branch)) index.set(review.branch, review.slug);
|
|
155
|
+
}
|
|
156
|
+
return index;
|
|
152
157
|
}
|
|
153
158
|
|
|
154
159
|
/** Mapa branch → run + worktree (sidebar de PRs). */
|
|
@@ -156,15 +161,30 @@ export class RunRepository {
|
|
|
156
161
|
string,
|
|
157
162
|
{ slug: string; worktreePath?: string; prNumber?: number }
|
|
158
163
|
> {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
const index = new Map<
|
|
165
|
+
string,
|
|
166
|
+
{ slug: string; worktreePath?: string; prNumber?: number }
|
|
167
|
+
>();
|
|
168
|
+
for (const review of this.reviewsForBranchLinks()) {
|
|
169
|
+
if (index.has(review.branch)) continue;
|
|
170
|
+
index.set(review.branch, {
|
|
171
|
+
slug: review.slug,
|
|
172
|
+
worktreePath: review.worktreePath,
|
|
173
|
+
prNumber: review.prNumber,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return index;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Runs ordenadas para links de PR: a principal da branch primeiro (uma
|
|
181
|
+
* variante A/B não deve "roubar" o atalho da PR), depois as mais recentes.
|
|
182
|
+
*/
|
|
183
|
+
private reviewsForBranchLinks(): ReviewRecord[] {
|
|
184
|
+
return [...this.storage.listReviews(this.repo.key)].sort(
|
|
185
|
+
(a, b) =>
|
|
186
|
+
Number(Boolean(a.variant)) - Number(Boolean(b.variant)) ||
|
|
187
|
+
b.updatedAt.localeCompare(a.updatedAt),
|
|
168
188
|
);
|
|
169
189
|
}
|
|
170
190
|
|
|
@@ -180,6 +200,7 @@ export class RunRepository {
|
|
|
180
200
|
}>(review, "classification.json");
|
|
181
201
|
return {
|
|
182
202
|
slug: review.slug,
|
|
203
|
+
variant: review.variant,
|
|
183
204
|
branch: review.branch,
|
|
184
205
|
worktreePath: review.worktreePath,
|
|
185
206
|
prNumber: review.prNumber,
|
package/src/server/styles.css
CHANGED
|
@@ -77,6 +77,8 @@ body.resizing-sidebar { cursor:col-resize; user-select:none; }
|
|
|
77
77
|
.run-icon { flex:none; margin-top:2px; color:var(--muted-2); }
|
|
78
78
|
.run.active .run-icon { color:var(--accent); }
|
|
79
79
|
.run-branch { font-size:12.5px; font-weight:600; line-height:1.35; word-break:break-word; }
|
|
80
|
+
.run-variant { flex:none; align-self:flex-start; padding:1px 6px; border-radius:999px; font-family:var(--font-mono);
|
|
81
|
+
font-size:9.5px; color:var(--accent); background:var(--accent-soft); border:1px solid var(--accent-line); }
|
|
80
82
|
.run-meta { display:flex; align-items:center; gap:10px; font-family:var(--font-mono); font-size:10.5px; color:var(--muted); }
|
|
81
83
|
.run-when { display:inline-flex; align-items:center; gap:6px; }
|
|
82
84
|
.run-count { margin-left:auto; color:var(--muted-2); }
|
|
@@ -1,7 +1,8 @@
|
|
|
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
|
-
|
|
4
|
+
valida e deduplica os JSONs; o `--finalize` grava a triagem no storage e o
|
|
5
|
+
`review-resume.md` com a cobertura por domínio.
|
|
5
6
|
|
|
6
7
|
> **Não** escrever markdown livre no chat. O único output consumido pelo pipeline é o JSON.
|
|
7
8
|
|
|
@@ -33,7 +34,11 @@ staging global da review:
|
|
|
33
34
|
"line": 5,
|
|
34
35
|
"message": "Arquivo de rota importa `db` e executa query inline.",
|
|
35
36
|
"action": "Mover a query para o service correspondente.",
|
|
36
|
-
"evidence": "import { db } from '../db'"
|
|
37
|
+
"evidence": "import { db } from '../db'",
|
|
38
|
+
"scenario": "request autenticada em /orders executa a query na rota",
|
|
39
|
+
"impact": "acoplamento e bypass das regras do service",
|
|
40
|
+
"confidence": 0.9,
|
|
41
|
+
"basis": "logic"
|
|
37
42
|
}
|
|
38
43
|
]
|
|
39
44
|
}
|
|
@@ -44,7 +49,7 @@ staging global da review:
|
|
|
44
49
|
| Campo | Tipo | Obrigatório | Descrição |
|
|
45
50
|
|---|---|---|---|
|
|
46
51
|
| `watchdogId` | string | ✅ | Id do watchdog (deve casar com uma chave de `.houndsman/watchdogs/index.ts`) |
|
|
47
|
-
| `reviewedFiles` | string[] | ✅ | Arquivos efetivamente
|
|
52
|
+
| `reviewedFiles` | string[] | ✅ | Arquivos **do seu domínio** efetivamente revisados (subconjunto de `## Arquivos do seu domínio`; não inclua chamadores/importadores nem arquivos de outros domínios) |
|
|
48
53
|
| `skipped` | string[] | ✅ | Arquivos do domínio que NÃO foram lidos + motivo |
|
|
49
54
|
| `findings` | object[] | ✅ | Achados (vazio se nada encontrado) |
|
|
50
55
|
| `findings[].rule` | string | ✅ | Regra violada (conforme o brief do watchdog) |
|
|
@@ -54,6 +59,10 @@ staging global da review:
|
|
|
54
59
|
| `findings[].message` | string | ✅ | O que está errado (curto, específico) |
|
|
55
60
|
| `findings[].action` | string | ✅ | O que fazer |
|
|
56
61
|
| `findings[].evidence` | string | — | Trecho que comprova o achado |
|
|
62
|
+
| `findings[].scenario` | string | — | Gatilho concreto que expõe o gap (ex.: "renovação de assinatura Pix sem split") |
|
|
63
|
+
| `findings[].impact` | string | — | Consequência observável (ex.: "cobrança recriada sem o split configurado") |
|
|
64
|
+
| `findings[].confidence` | number | — | Confiança do revisor (0–1) |
|
|
65
|
+
| `findings[].basis` | `"rule" \| "logic" \| "domain"` | — | Origem do achado; inferida pelo prefixo `Lógica:`/`Domínio:` |
|
|
57
66
|
|
|
58
67
|
## Severidade
|
|
59
68
|
|
|
@@ -68,7 +77,14 @@ staging global da review:
|
|
|
68
77
|
|
|
69
78
|
- Sem achados → `"findings": []` (arquivo **obrigatório**).
|
|
70
79
|
- Um achado por regra violada; não agrupar regras diferentes.
|
|
80
|
+
- **Teto de 20 achados por watchdog.** Se houver mais, mantenha os de maior severidade e
|
|
81
|
+
agrupe ocorrências idênticas (mesmo trecho + mesma regra) em um único item. O JSON precisa
|
|
82
|
+
caber inteiro — relatório cortado perde achados.
|
|
83
|
+
- `reviewedFiles ∪ skipped` deve cobrir **todo** o escopo do brief. O que não foi revisado
|
|
84
|
+
entra em `skipped` com o motivo (`budget`, `truncated`, `fora do fluxo`).
|
|
71
85
|
- `file:line` sempre que possível; `evidence` quando o achado não é óbvio.
|
|
86
|
+
- **Trecho truncado** (`… [trecho truncado]`): sem abrir o arquivo para ver o restante, não
|
|
87
|
+
afirme violação sobre ele — reduza a `confidence` ou não reporte.
|
|
72
88
|
- **Escopo do diff:** a `line` deve cair dentro das faixas listadas em
|
|
73
89
|
*Escopo do diff (símbolos alterados)* no brief. Código fora delas é contexto — não é
|
|
74
90
|
achado desta revisão. Se a alteração quebra código não alterado, ancore o finding na
|
|
@@ -76,14 +92,24 @@ staging global da review:
|
|
|
76
92
|
- Achado **sem linha** só quando for inerente à alteração (vira comentário de arquivo).
|
|
77
93
|
- Não inventar achados — se não houver violação, o array fica vazio.
|
|
78
94
|
|
|
79
|
-
## Findings emergentes (
|
|
95
|
+
## Findings emergentes (obrigatórios no `general`)
|
|
80
96
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
reservados:
|
|
97
|
+
O watchdog `general` (id reservado, sempre ativo) **deve** reportar gaps de lógica e domínio
|
|
98
|
+
que não correspondem a uma regra explícita, usando os prefixos reservados:
|
|
84
99
|
|
|
85
100
|
- `Lógica: <descrição>` — nome/contrato vs comportamento, fluxos, estados, bordas.
|
|
86
|
-
- `Domínio: <descrição>` — transições, idempotência, invariantes, arredondamento, timezone
|
|
101
|
+
- `Domínio: <descrição>` — transições, idempotência, invariantes, arredondamento, timezone,
|
|
102
|
+
paridade de capacidade, propagação de credencial.
|
|
87
103
|
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
Demonstre o gap em três partes:
|
|
105
|
+
|
|
106
|
+
1. `scenario` — o gatilho concreto (ex.: "`AsaasSplit` recebe `RenewAutomaticPix`").
|
|
107
|
+
2. `message` — o comportamento observado e o invariante/regra violado.
|
|
108
|
+
3. `evidence` — o trecho que comprova.
|
|
109
|
+
|
|
110
|
+
**Severidade:** fora de regra, `blocking` só é aceito com `scenario` **e** `evidence`. Sem os
|
|
111
|
+
dois, o motor rebaixa para `attention` (e registra um aviso). Regras explícitas mantêm a
|
|
112
|
+
severidade declarada pelo watchdog.
|
|
113
|
+
|
|
114
|
+
Os demais watchdogs podem usar os mesmos prefixos apenas quando declararem
|
|
115
|
+
`allowEmergentFindings: true`.
|
package/src/skill/README.md
CHANGED
|
@@ -14,6 +14,16 @@ Um único skill (`/houndsman`), quatro capabilities (detalhadas em `hunt.md`, `t
|
|
|
14
14
|
| `fix` | Correção dos findings marcados como `execute`. |
|
|
15
15
|
| `verify` | Confere se as violações foram resolvidas, sem re-revisar a PR. |
|
|
16
16
|
|
|
17
|
+
## Dois caminhos para o `hunt`
|
|
18
|
+
|
|
19
|
+
| Caminho | Quem usa | Requisitos |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| Skill `/houndsman hunt` | agente externo (Copilot/Claude/etc.) | nenhum |
|
|
22
|
+
| CLI `houndsman hunt` | operador/CI, sem agente | `houndsman hunt install` |
|
|
23
|
+
|
|
24
|
+
Os dois usam o mesmo `prepare`/`finalize`, o mesmo staging e o mesmo schema de findings.
|
|
25
|
+
O CLI só substitui o despacho manual dos subagentes por processos maki headless.
|
|
26
|
+
|
|
17
27
|
## Terminologia
|
|
18
28
|
|
|
19
29
|
- **houndsman** — o orquestrador/treinador (CLI + motor).
|
|
@@ -35,24 +45,29 @@ Um único skill (`/houndsman`), quatro capabilities (detalhadas em `hunt.md`, `t
|
|
|
35
45
|
|
|
36
46
|
| Passo | Comando | Quem roda | Resultado |
|
|
37
47
|
|---|---|---|---|
|
|
38
|
-
| 0. Contexto | `
|
|
39
|
-
| 1. Preparar | `bun run houndsman` | skill | `classification.json` + `candidates.json` + `briefs/` + `manifest.json` |
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
>
|
|
45
|
-
>
|
|
46
|
-
>
|
|
48
|
+
| 0. Contexto | `houndsman --pr` (via `gh`) | prepare | `context.json` + `context-brief.md` → `context-summary.json` (IA; sem IA, subagente) |
|
|
49
|
+
| 1. Preparar | `bun run houndsman` | skill | `classification.json` + `candidates.json` + `business-summary.json` + `briefs/` + `manifest.json` |
|
|
50
|
+
| 1.5. Intenção | *(subagente)* | skill, só sem IA | `business-summary.json` enriquecido (só quando não veio da IA) |
|
|
51
|
+
| 2. Revisar | *(subagentes)* | skill | `findings/<watchdog>.json` (inclui o `general`, sempre ativo) |
|
|
52
|
+
| 3. Finalizar | `bun run houndsman --finalize` | skill | `review-resume.md` + triagem no storage |
|
|
53
|
+
|
|
54
|
+
> **Alvo = PR** (ex.: “revise a PR 456”): use `houndsman --pr 456` e
|
|
55
|
+
> `houndsman --pr 456 --finalize`. O prepare coleta o contexto via `gh`, cria um **worktree**
|
|
56
|
+
> detached em `<repo>/.houndsman/worktrees/<repo>/<branch>` e resume o contexto com IA;
|
|
57
|
+
> os briefs trazem `**Worktree:**` e os subagentes devem ler essa árvore (nunca o clone
|
|
58
|
+
> principal). Despache os briefs em paralelo (mesmo bloco de tool calls).
|
|
47
59
|
|
|
48
60
|
## Comandos
|
|
49
61
|
|
|
50
62
|
| Comando | Função |
|
|
51
63
|
|---|---|
|
|
52
|
-
| `houndsman context` |
|
|
64
|
+
| `houndsman context` | Contexto da PR via `gh` (`--pr <n>`/`--worktree <path>`) — o prepare já coleta com `--pr` |
|
|
53
65
|
| `houndsman` | Passo 1 — preparar (gera os briefs) |
|
|
54
66
|
| `houndsman --pr <n>` | Passo 1 em worktree dedicado (revisões paralelas) |
|
|
55
|
-
| `houndsman --finalize` | Passo 3 —
|
|
67
|
+
| `houndsman --finalize` | Passo 3 — gravar a triagem e o resumo final (`--pr <n>` reusa o worktree) |
|
|
68
|
+
| `houndsman hunt [--pr <n>]` | Harness local opcional (maki): prepare → watchdogs → finalize |
|
|
69
|
+
| `houndsman compare` | Compara duas runs/variantes (`--list`, `--a`, `--b` ou dois slugs) |
|
|
70
|
+
| `houndsman hunt install` | Baixa o maki fixado para `<HOUNDSMAN_HOME>/tools/maki` |
|
|
56
71
|
| `houndsman worktree <ação>` | `list`/`add`/`remove`/`prune`/`path` dos worktrees |
|
|
57
72
|
| `houndsman watchdog <id>` | Contexto de um watchdog (`--json`, `--rules`) |
|
|
58
73
|
| `houndsman findings <sub>` | Triagem/correção dos findings (`list`/`set`/`done`/`sync`/`batch`/`verify`/...) |
|
|
@@ -78,9 +93,10 @@ Runs, findings e artefatos vivem no **storage global** (SQLite em `<HOUNDSMAN_HO
|
|
|
78
93
|
| findings | Decisão/execução + fingerprint + âncora + histórico + vínculo com o GitHub |
|
|
79
94
|
| `classification.json` | Watchdogs ativados + status (A/M/D/R) + churn |
|
|
80
95
|
| `candidates.json` | Candidatos da pré-análise Jev |
|
|
81
|
-
| `
|
|
96
|
+
| `business-summary.json` / `business-brief.md` | Modelo de intenção da PR e o brief do passo |
|
|
97
|
+
| `manifest.json` | Resumo (watchdogs, arquivos, deletados, contexto, intenção, orçamento, churn) |
|
|
82
98
|
| `briefs/<watchdog>.md` | Brief enviado a cada sub-agente (auditoria) |
|
|
83
|
-
| `review.md`
|
|
99
|
+
| `review-resume.md` | Resumo final: negócio, pontos técnicos, cobertura por domínio e conclusão |
|
|
84
100
|
| `verify.md` | Relatório do `findings verify` |
|
|
85
101
|
| `context.json` / `context-brief.md` / `context-summary.json` | Contexto da PR |
|
|
86
102
|
|
|
@@ -109,7 +125,7 @@ src/
|
|
|
109
125
|
│ ├── paths.ts # layout fixo da camada (.houndsman/) + relFromRoot
|
|
110
126
|
│ ├── types.ts # Severity, Rule, WatchdogDefinition, defineWatchdog
|
|
111
127
|
│ ├── watchdog-registry.ts # getWatchdog/findWatchdog/getWatchdogContext(config)
|
|
112
|
-
│ ├── orchestrator.ts # preparar/finalizar
|
|
128
|
+
│ ├── orchestrator.ts # fachada: preparar/finalizar (pipelines em engine/)
|
|
113
129
|
│ ├── brief.ts # defaultRenderBrief + PROCEDURE
|
|
114
130
|
│ ├── pr-context.ts # contexto da PR via gh
|
|
115
131
|
│ ├── file-classifier.ts # classificação (via `classify` dos watchdogs) + churn
|
package/src/skill/SETUP.md
CHANGED
|
@@ -76,6 +76,25 @@ Depois do setup, invoque o skill `/houndsman` no seu harness:
|
|
|
76
76
|
| `/houndsman train` | Adicionar/ajustar/remover regras dos watchdogs. |
|
|
77
77
|
| `/houndsman fix` | Corrigir os findings marcados como `execute`. |
|
|
78
78
|
|
|
79
|
+
## Harness local (opcional) — `houndsman hunt`
|
|
80
|
+
|
|
81
|
+
Quem não quer rodar a skill dentro de um agente externo pode executar a revisão 100% pelo
|
|
82
|
+
CLI. O maki é instalado sob demanda, fora do projeto, e reaproveita a credencial já existente:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
houndsman hunt install # baixa o maki fixado (sha256) em <HOUNDSMAN_HOME>/tools/maki
|
|
86
|
+
houndsman hunt doctor # binário, modelo e credencial (opcional; não falha sem maki)
|
|
87
|
+
houndsman hunt --pr 456 # prepare → maki (um processo por brief) → finalize
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- A skill `/houndsman hunt` continua sendo o caminho canônico e não depende do maki.
|
|
91
|
+
- `HOUNDSMAN_MAKI_PATH` aponta um binário já instalado; `HOUNDSMAN_MAKI_VERSION` troca a
|
|
92
|
+
versão baixada.
|
|
93
|
+
- Config opcional em `houndsman.config.ts`: `hunt: { model?, concurrency?, timeoutMs?,
|
|
94
|
+
retries?, binaryPath?, enabled? }`.
|
|
95
|
+
- Se algum watchdog falhar, o comando **não finaliza** (preserva o staging); use
|
|
96
|
+
`--allow-partial` para finalizar com findings vazios registrados em `skipped`.
|
|
97
|
+
|
|
79
98
|
## Contrato do motor (`HoundsmanConfig`)
|
|
80
99
|
|
|
81
100
|
Definido em `src/core/config.ts` (pacote) e espelhado em `.houndsman/core/types.ts` (gerado):
|
|
@@ -87,10 +106,12 @@ Definido em `src/core/config.ts` (pacote) e espelhado em `.houndsman/core/types.
|
|
|
87
106
|
| `watchdogs` | Definições dos watchdogs (incluem `classify`); os **ids são derivados** das chaves. |
|
|
88
107
|
| `exclusions` | Arquivos que nunca entram na revisão. |
|
|
89
108
|
| `docMappings` | Path → documentação de contexto. |
|
|
109
|
+
| `priorities` | **Opcional** — prioridade por caminho `[{ pattern: RegExp, bonus?: number, maxRiskFromChurn?: number }]`. Risco = `min(churn, maxRiskFromChurn) + bonus`: `bonus` promove caminho crítico e o teto impede que superchurn de caminho de baixa criticidade escale. A primeira regra que casar vence. Ordena o orçamento, o rateio do diff e o recorte do `general`. Sem config: churn puro. |
|
|
90
110
|
| `alwaysIncludeDocs` | Docs sempre presentes nos briefs (default `["AGENTS.md"]`). |
|
|
91
111
|
| `budgets` | **Opcional** — teto de arquivos por brief (`maxFilesPerBrief`, default `40`); o excedente vira "fora do orçamento" (registrar em `skipped`). |
|
|
92
112
|
| `concurrency` | **Opcional** — chamadas Jev simultâneas (`jev`, default `5`). |
|
|
93
113
|
| `ast` | **Opcional** — AST determinístico para TS/JS (`enabled`, `maxFileBytes`, `maxImpactFiles`, `languages`, `aliases`). Default: ligado; falhas são fail-open. |
|
|
114
|
+
| `hunt` | **Opcional** — harness local (`model`, `concurrency`, `timeoutMs`, `retries`, `binaryPath`, `enabled`). Só afeta `houndsman hunt`; a skill não depende. |
|
|
94
115
|
|
|
95
116
|
> O motor **não contém** ids, paths ou regras do projeto — tudo chega por `HoundsmanConfig`,
|
|
96
117
|
> carregado em runtime a partir de `houndsman.config.ts` (ou `--config <path>`).
|
|
@@ -103,7 +124,7 @@ houndsman/
|
|
|
103
124
|
└── src/
|
|
104
125
|
├── index.ts # API pública (defineWatchdog, defineHoundsmanConfig, tipos)
|
|
105
126
|
├── cli/index.ts # CLI único — bin `houndsman`
|
|
106
|
-
├── core/ # bibliotecas genéricas (config, orchestrator, brief, jev/, …)
|
|
127
|
+
├── core/ # bibliotecas genéricas (config, orchestrator, brief, jev/, hunt/, …)
|
|
107
128
|
├── skill/*.md # assets markdown (embarcados no pacote)
|
|
108
129
|
└── tests/ # suíte da lib (project-agnóstica, "project fake")
|
|
109
130
|
```
|
package/src/skill/SKILL.md
CHANGED
|
@@ -14,6 +14,11 @@ consolida tudo num relatório. O motor é o CLI **`houndsman`**; as regras do pr
|
|
|
14
14
|
**Terminologia:** **houndsman** = orquestrador/treinador (CLI + motor) · **watchdog** = agente
|
|
15
15
|
especialista por domínio · **finding** = um achado da revisão · **Jev** = pré-análise opcional.
|
|
16
16
|
|
|
17
|
+
> **Duas etapas são obrigatórias em toda revisão:** o **modelo de intenção**
|
|
18
|
+
> (`business-summary.json`) e o revisor de fluxo **`general`** (id reservado, sempre ativo).
|
|
19
|
+
> O modelo vem da IA no prepare quando há chave; sem chave, um subagente (Passo 1.5) ou o
|
|
20
|
+
> pré-passe maki o produz. Nenhum dos dois é configurável pelo usuário.
|
|
21
|
+
|
|
17
22
|
## Capabilities
|
|
18
23
|
|
|
19
24
|
| Capability | Objetivo | Documento |
|
|
@@ -38,6 +43,10 @@ especialista por domínio · **finding** = um achado da revisão · **Jev** = pr
|
|
|
38
43
|
Um argumento extra é o alvo da capability. Escolha a capability pelo que o usuário quer,
|
|
39
44
|
leia o documento correspondente em `.houndsman/core/skill/<capability>.md` e siga-o.
|
|
40
45
|
|
|
46
|
+
> **Harness local (opcional):** para rodar sem agente externo, o operador pode instalar o
|
|
47
|
+
> maki (`houndsman hunt install`) e usar `houndsman hunt --pr 456`. Isso não muda esta
|
|
48
|
+
> skill: se você é o agente, siga o fluxo normal e não rode `houndsman hunt`.
|
|
49
|
+
|
|
41
50
|
### Alvo da revisão (PR × branch)
|
|
42
51
|
|
|
43
52
|
Interprete o alvo em linguagem natural **antes** de rodar o CLI:
|
|
@@ -56,12 +65,16 @@ Interprete o alvo em linguagem natural **antes** de rodar o CLI:
|
|
|
56
65
|
Sequência para uma PR (ex.: 456):
|
|
57
66
|
|
|
58
67
|
```bash
|
|
59
|
-
houndsman
|
|
60
|
-
|
|
61
|
-
# despache os briefs (cada um traz **Worktree:** <path>)
|
|
68
|
+
houndsman --pr 456 # cria/reusa o worktree, coleta o contexto e prepara
|
|
69
|
+
# despache os briefs em paralelo (cada um traz **Worktree:** <path>)
|
|
62
70
|
houndsman --pr 456 --finalize
|
|
63
71
|
```
|
|
64
72
|
|
|
73
|
+
- O contexto da PR é coletado pelo próprio prepare (via `gh`); com IA ligada ele também é
|
|
74
|
+
resumido ali — não gaste um subagente nisso. Desative com `--no-context`.
|
|
75
|
+
- Para comparar duas execuções, use `--variant <label>` em cada uma e depois
|
|
76
|
+
`houndsman compare --branch <head> --a <a> --b <b>` (ou `compare --list`).
|
|
77
|
+
|
|
65
78
|
- O worktree vive em `<repo>/.houndsman/worktrees/<repo>/<branch>` (detached no commit da PR)
|
|
66
79
|
e é reutilizado nas próximas execuções da mesma branch. Ele fica **dentro do workspace**
|
|
67
80
|
(e o staging também) para o agente ler/executar sem prompts de permissão.
|
|
@@ -79,4 +92,4 @@ houndsman --pr 456 --finalize
|
|
|
79
92
|
- `.houndsman/core/skill/OUTPUT-FORMAT.md` — schema do JSON de saída dos watchdogs.
|
|
80
93
|
- `.houndsman/core/skill/AUDIT-PROTOCOL.md` — taxonomia da auditoria de regras (Modo B do `train`).
|
|
81
94
|
- `.houndsman/core/skill/SETUP.md` · `.houndsman/core/skill/README.md` — configuração e visão geral.
|
|
82
|
-
- `.houndsman/core/skill/example-
|
|
95
|
+
- `.houndsman/core/skill/example-resume.md` — exemplo do resumo final.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Resumo da revisão — feature/add-webhook-retry
|
|
2
|
+
|
|
3
|
+
**Data:** 2026-04-20 · **Status:** ❌ Mudanças Necessárias
|
|
4
|
+
|
|
5
|
+
## Resumo de negócio
|
|
6
|
+
|
|
7
|
+
Tornar o envio de webhooks resiliente a falhas temporárias do destinatário, sem perda de
|
|
8
|
+
eventos e sem reenvio manual.
|
|
9
|
+
|
|
10
|
+
**Invariantes:**
|
|
11
|
+
- Todo evento entregue tem no máximo uma tentativa `in_flight` por destinatário.
|
|
12
|
+
- O retry nunca duplica o `eventId` já aceito pelo integrador.
|
|
13
|
+
|
|
14
|
+
**Fluxos (2):**
|
|
15
|
+
- **Retry de webhook** — disparo: resposta não-2xx/timeout — esperado: reagendamento com
|
|
16
|
+
backoff exponencial e registro da tentativa.
|
|
17
|
+
|
|
18
|
+
## Pontos técnicos
|
|
19
|
+
|
|
20
|
+
- **Contratos** — `WebhookRetryPolicy`, `RetryAttempt` (`src/webhooks/types.ts`).
|
|
21
|
+
- **Implementações** — `WebhookDispatcher` (`src/webhooks/dispatcher.ts`).
|
|
22
|
+
- **Novas dependências** — `p-retry`.
|
|
23
|
+
|
|
24
|
+
## Cobertura por domínio
|
|
25
|
+
|
|
26
|
+
| Domínio | Achados | Revisados | Faltam |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| Arquitetura Backend | 2 | 3/3 | — |
|
|
29
|
+
| Pagamentos & Integrações | 1 | 4/4 | — |
|
|
30
|
+
| Acquirer Integrations | 2 | 3/4 | 1 |
|
|
31
|
+
|
|
32
|
+
## Candidatos não verificados (1)
|
|
33
|
+
|
|
34
|
+
> Indícios da pré-análise que nenhum watchdog confirmou. Não bloqueiam.
|
|
35
|
+
|
|
36
|
+
- `src/webhooks/queue.ts` · attention · Fila sem limite de tentativas.
|
|
37
|
+
|
|
38
|
+
## Conclusão
|
|
39
|
+
|
|
40
|
+
**Resumo:** 2 bloqueantes · 1 atenção · 1 sugestões · 1 nits.
|
|
41
|
+
|
|
42
|
+
> Achados individuais (com `id` de triagem) vivem na UI (`Findings`) e em
|
|
43
|
+
> `houndsman findings list --json` — o resumo não os duplica.
|