@payconductor/houndsman 0.4.0 → 0.5.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 +12 -38
- package/package.json +1 -1
- package/src/cli/commands/status-command.ts +46 -0
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +3 -0
- package/src/core/commands/findings.ts +61 -5
- package/src/core/commands/status.ts +63 -0
- package/src/core/engine/review-scope.ts +26 -8
- package/src/core/findings-state.ts +21 -4
- package/src/core/github-review.ts +620 -57
- package/src/core/orchestrator.ts +98 -8
- package/src/core/paths.ts +3 -0
- package/src/core/renderers/resume-renderer.ts +151 -0
- package/src/core/report-builder.ts +7 -4
- package/src/core/run-status.ts +39 -5
- package/src/core/storage/file/file-storage.ts +2 -0
- package/src/core/storage/sqlite/mappers.ts +2 -0
- package/src/core/storage/sqlite/schema.ts +9 -1
- package/src/core/storage/sqlite/sqlite-storage.ts +5 -1
- package/src/core/storage/types.ts +9 -0
- package/src/server/client/App.tsx +26 -9
- package/src/server/client/ArtifactsPanel.tsx +12 -5
- package/src/server/client/AutoRefresh.tsx +1 -1
- package/src/server/client/Filters.tsx +7 -2
- package/src/server/client/FindingCard.tsx +14 -10
- package/src/server/client/FindingsList.tsx +2 -2
- package/src/server/client/MarkdownText.tsx +92 -12
- package/src/server/client/ResumePanel.tsx +368 -0
- package/src/server/client/RunList.tsx +3 -3
- package/src/server/client/SubmitModal.tsx +49 -6
- package/src/server/client/SummaryBar.tsx +1 -1
- package/src/server/client/icons.tsx +17 -0
- package/src/server/client/{constants.ts → utils/constants.ts} +34 -5
- package/src/server/client/utils/markdown.ts +319 -0
- package/src/server/client/{types.ts → utils/types.ts} +58 -0
- package/src/server/index.ts +63 -5
- package/src/server/run-repository.ts +9 -0
- package/src/skill/example-review.md +22 -14
- package/src/skill/hunt.md +31 -17
- package/src/skill/verify.md +2 -1
- package/src/tests/__snapshots__/baseline.test.ts.snap +32 -30
- package/src/tests/baseline.test.ts +3 -0
- package/src/tests/code-highlight.test.ts +1 -1
- package/src/tests/findings-state.test.ts +50 -2
- package/src/tests/github-review.test.ts +409 -60
- package/src/tests/markdown.test.ts +61 -1
- package/src/tests/review-resume.test.ts +101 -0
- package/src/tests/review-scope.test.ts +302 -0
- package/src/tests/review-scripts.test.ts +73 -0
- package/src/tests/run-status-view.test.ts +94 -0
- package/src/tests/run-status.test.ts +53 -0
- package/src/tests/server.test.ts +76 -21
- package/src/tests/storage.test.ts +63 -0
- package/src/server/client/markdown.ts +0 -153
- /package/src/server/client/{api.ts → utils/api.ts} +0 -0
- /package/src/server/client/{code-highlight.ts → utils/code-highlight.ts} +0 -0
- /package/src/server/client/{format.ts → utils/format.ts} +0 -0
- /package/src/server/client/{json-highlight.ts → utils/json-highlight.ts} +0 -0
package/src/server/index.ts
CHANGED
|
@@ -315,7 +315,7 @@ code, pre, .mono { font-family:var(--font-mono); tab-size:4; }
|
|
|
315
315
|
.btn:disabled { opacity:.55; cursor:not-allowed; }
|
|
316
316
|
.icon.spin { animation:spin .8s linear infinite; }
|
|
317
317
|
.content { width:100%; max-width:1180px; margin:0 auto; padding:22px 24px 64px; }
|
|
318
|
-
.page-header { display:flex; align-items:flex-start; justify-content:space-between; gap:18px; }
|
|
318
|
+
.page-header { display:flex; align-items:flex-start; justify-content:space-between; gap:18px; margin-bottom:16px; }
|
|
319
319
|
.page-header-text { min-width:0; }
|
|
320
320
|
.page-header-actions { display:flex; align-items:center; gap:10px; flex:none; }
|
|
321
321
|
.btn.submit-review { color:#1b1305; background:linear-gradient(180deg, var(--accent-strong), var(--accent)); border-color:transparent; box-shadow:0 4px 14px rgba(240,165,44,.22); }
|
|
@@ -412,12 +412,25 @@ code, pre, .mono { font-family:var(--font-mono); tab-size:4; }
|
|
|
412
412
|
.action-text p:last-child { margin-bottom:0; }
|
|
413
413
|
.md-list { margin:0 0 6px; padding-left:20px; font-size:13px; line-height:1.65; color:#f0ece6; }
|
|
414
414
|
.md-list li { margin:0 0 2px; }
|
|
415
|
-
.md-quote { margin:0 0 6px; padding:2px 0 2px 10px; border-left:2px solid var(--accent-line); color:var(--muted); font-size:13px; line-height:1.65; }
|
|
415
|
+
.md-quote { margin:0 0 6px; padding:2px 0 2px 10px; border-left:2px solid var(--accent-line); color:var(--muted); font-size:13px; line-height:1.65; white-space:pre-wrap; }
|
|
416
416
|
.action-text code, .md-code { font-family:var(--font-mono); font-size:11.5px; padding:1px 5px; color:#f0ece6; background:rgba(255,255,255,.06); border:1px solid var(--border); border-radius:5px; }
|
|
417
|
-
.
|
|
418
|
-
.action-text
|
|
419
|
-
.action-text
|
|
417
|
+
code.md-path { color:#8fc7ff; background:rgba(90,164,240,.1); border-color:rgba(90,164,240,.24); }
|
|
418
|
+
.action-text a, .md-p a, .md-list a, .md-quote a, .md-table a, .md-h a { color:var(--accent-strong); text-decoration:underline; text-underline-offset:2px; }
|
|
419
|
+
.action-text strong, .md-p strong, .md-list strong, .md-quote strong, .md-table strong, .md-h strong { color:#fff; font-weight:600; }
|
|
420
|
+
.action-text em, .md-p em, .md-list em, .md-quote em, .md-table em, .md-h em { color:#e9e4dc; }
|
|
420
421
|
.md-code-block { margin:6px 0; padding:10px 12px; max-height:320px; overflow:auto; white-space:pre; font-size:11.5px; line-height:1.6; color:#cdc7bf; background:#0b0b0e; border-radius:10px; }
|
|
422
|
+
.md-h { margin:16px 0 8px; font-size:14px; font-weight:700; line-height:1.35; letter-spacing:-.01em; color:#eee9e2; }
|
|
423
|
+
.md-h:first-child { margin-top:0; }
|
|
424
|
+
.md-h1 { font-size:19px; }
|
|
425
|
+
.md-h2 { font-size:16px; }
|
|
426
|
+
.md-h3 { font-size:14px; }
|
|
427
|
+
.md-h4, .md-h5, .md-h6 { font-size:13px; color:#e4dfd7; font-weight:600; }
|
|
428
|
+
.md-hr { margin:14px 0; border:0; border-top:1px solid var(--border); }
|
|
429
|
+
.md-table-wrap { margin:8px 0 12px; padding:4px; overflow-x:auto; border:1px solid rgba(255,255,255,.07); border-radius:10px; background:rgba(255,255,255,.012); }
|
|
430
|
+
.md-table { width:100%; border-collapse:separate; border-spacing:3px; font-size:12px; line-height:1.5; }
|
|
431
|
+
.md-table th, .md-table td { padding:6px 9px; text-align:left; vertical-align:top; border:1px solid rgba(255,255,255,.035); border-radius:6px; }
|
|
432
|
+
.md-table th { color:var(--muted); font-weight:600; background:rgba(255,255,255,.03); }
|
|
433
|
+
.md-table td { color:#d8d3cc; }
|
|
421
434
|
.decision-tags-block { display:flex; align-items:center; gap:8px; }
|
|
422
435
|
.evidence { margin:0; padding: 16px; white-space:break-spaces; font-size:12px; line-height:1.6; color:#cdc7bf;
|
|
423
436
|
background:#0b0b0e; border:1px solid var(--border); border-radius:var(--radius); }
|
|
@@ -476,6 +489,10 @@ code, pre, .mono { font-family:var(--font-mono); tab-size:4; }
|
|
|
476
489
|
border:1px solid var(--accent-line); border-radius:999px; }
|
|
477
490
|
.artifact-view { margin:0; padding:14px 16px; max-height:70vh; overflow:auto; font-size:12px; line-height:1.6; color:#cdc7bf;
|
|
478
491
|
background:#0b0b0e; border:1px solid var(--border); border-radius:var(--radius); }
|
|
492
|
+
.artifact-markdown { white-space:normal; }
|
|
493
|
+
.artifact-markdown .md-p, .artifact-markdown .md-list, .artifact-markdown .md-quote { font-size:12.5px; }
|
|
494
|
+
.artifact-markdown .md-p, .artifact-markdown .md-list { color:#d8d3cc; }
|
|
495
|
+
.artifact-markdown .md-code-block { border:1px solid var(--border); }
|
|
479
496
|
.json-view { white-space:pre; }
|
|
480
497
|
.json-key { color:var(--accent-strong); }
|
|
481
498
|
.json-string { color:#9ddc8a; }
|
|
@@ -486,6 +503,47 @@ code, pre, .mono { font-family:var(--font-mono); tab-size:4; }
|
|
|
486
503
|
.sent-tag { display:inline-flex; align-items:center; gap:5px; padding:2px 8px; font-size:10px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
|
|
487
504
|
color:var(--success); background:rgba(69,192,122,.12); border:1px solid rgba(69,192,122,.32); border-radius:999px; text-decoration:none; }
|
|
488
505
|
.sent-tag:hover { color:#fff; }
|
|
506
|
+
.sent-tag.gh { color:#b9c7d6; background:rgba(139,164,191,.12); border-color:rgba(139,164,191,.35); }
|
|
507
|
+
.sent-tag.gh:hover { color:#fff; background:rgba(139,164,191,.2); }
|
|
508
|
+
.resume { display:flex; flex-direction:column; }
|
|
509
|
+
.resume-hero { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; padding:18px 20px;
|
|
510
|
+
background:linear-gradient(135deg, rgba(240,165,44,.08), rgba(255,255,255,.015) 55%);
|
|
511
|
+
border:1px solid var(--border); border-radius:var(--radius-lg); }
|
|
512
|
+
.resume-kicker { display:block; font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--accent); }
|
|
513
|
+
.resume-title { margin:5px 0 7px; font-size:22px; font-weight:700; letter-spacing:-.02em; word-break:break-word; }
|
|
514
|
+
.resume-meta { display:flex; flex-wrap:wrap; align-items:center; gap:12px; margin:0; font-size:12px; color:var(--muted); }
|
|
515
|
+
.resume-pr { display:inline-flex; align-items:center; gap:5px; color:var(--accent-strong); text-decoration:none; }
|
|
516
|
+
.resume-pr:hover { text-decoration:underline; }
|
|
517
|
+
.resume-hero-side { display:flex; flex:none; flex-direction:column; align-items:flex-end; gap:10px; }
|
|
518
|
+
.resume-status { display:inline-flex; align-items:center; padding:5px 12px; font-size:11px; font-weight:700; letter-spacing:.04em; border-radius:999px; }
|
|
519
|
+
.resume-status.blocking { color:var(--blocking); background:rgba(242,89,94,.12); border:1px solid rgba(242,89,94,.35); }
|
|
520
|
+
.resume-status.attention { color:var(--attention); background:rgba(237,161,58,.12); border:1px solid rgba(237,161,58,.35); }
|
|
521
|
+
.resume-status.clean { color:var(--success); background:rgba(69,192,122,.12); border:1px solid rgba(69,192,122,.35); }
|
|
522
|
+
.resume-kpis { margin:14px 0 18px; }
|
|
523
|
+
.resume-kpis .kpi-value { font-size:24px; }
|
|
524
|
+
.churn-add { color:var(--success); }
|
|
525
|
+
.churn-del { color:var(--blocking); }
|
|
526
|
+
.churn-sep { margin:0 3px; color:var(--muted-2); font-weight:400; }
|
|
527
|
+
.kpi-blocking .kpi-value { color:var(--blocking); }
|
|
528
|
+
.kpi-attention .kpi-value { color:var(--attention); }
|
|
529
|
+
.kpi-total .kpi-value { color:var(--fg); }
|
|
530
|
+
.resume-grid { display:grid; grid-template-columns:minmax(0,1.35fr) minmax(0,1fr); gap:16px; align-items:start; }
|
|
531
|
+
.resume-card { min-width:0; padding:16px 18px; background:var(--card); border:1px solid var(--border); border-radius:var(--radius-lg); }
|
|
532
|
+
.resume-card-head { display:flex; align-items:center; justify-content:space-between; gap:10px; }
|
|
533
|
+
.resume-card-title { margin:0 0 10px; font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--muted); }
|
|
534
|
+
.resume-card-count { display:inline-flex; align-items:center; justify-content:center; min-width:22px; height:22px; margin-bottom:10px; padding:0 7px;
|
|
535
|
+
font-family:var(--font-mono); font-size:11px; color:var(--accent); background:var(--accent-soft); border:1px solid var(--accent-line); border-radius:999px; }
|
|
536
|
+
.resume-narrative .md-p:last-child { margin-bottom:0; }
|
|
537
|
+
.resume-domains { display:flex; flex-direction:column; gap:8px; margin:0; padding:0; list-style:none; }
|
|
538
|
+
.resume-domain { display:flex; flex-direction:column; gap:7px; padding:10px 12px; background:var(--surface-2);
|
|
539
|
+
border:1px solid var(--border); border-radius:10px; }
|
|
540
|
+
.resume-domain-head { display:flex; align-items:baseline; justify-content:space-between; gap:10px; }
|
|
541
|
+
.resume-domain-name { font-size:12.5px; font-weight:600; color:var(--fg); }
|
|
542
|
+
.resume-domain-files { flex:none; font-family:var(--font-mono); font-size:10.5px; color:var(--muted-2); }
|
|
543
|
+
.resume-domain-sev { display:flex; flex-wrap:wrap; gap:6px; }
|
|
544
|
+
.resume-domain-sev .sev-badge { padding:2px 7px; font-size:9.5px; }
|
|
545
|
+
.resume-domain-ok { font-size:11.5px; color:var(--success); }
|
|
546
|
+
@media (max-width:900px) { .resume-grid { grid-template-columns:1fr; } .resume-hero { flex-direction:column; } .resume-hero-side { flex-direction:row; align-items:flex-start; } }
|
|
489
547
|
.modal-layer { position:fixed; inset:0; z-index:50; display:grid; place-items:center; padding:24px; }
|
|
490
548
|
.modal-backdrop { position:absolute; inset:0; width:100%; padding:0; background:rgba(5,5,7,.66); border:0; cursor:default; backdrop-filter:blur(5px); -webkit-backdrop-filter:blur(5px); }
|
|
491
549
|
.modal { position:relative; width:min(640px, 100%); max-height:min(78vh, 780px); display:flex; flex-direction:column; overflow:hidden;
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
resolvePrTarget,
|
|
31
31
|
type SubmitSelection,
|
|
32
32
|
submitFindings,
|
|
33
|
+
syncResolvedThreads,
|
|
33
34
|
} from "../core/github-review";
|
|
34
35
|
import { pingFindingsStatus } from "../core/run-status";
|
|
35
36
|
import { systemClock } from "../core/services/clock";
|
|
@@ -42,6 +43,8 @@ export type RunListItem = {
|
|
|
42
43
|
slug: string;
|
|
43
44
|
branch: string;
|
|
44
45
|
updatedAt?: string;
|
|
46
|
+
startedAt?: string;
|
|
47
|
+
heartbeatAt?: string;
|
|
45
48
|
status: ReviewRecord["status"];
|
|
46
49
|
summary: FindingsSummary;
|
|
47
50
|
};
|
|
@@ -94,6 +97,8 @@ export class RunRepository {
|
|
|
94
97
|
slug: review.slug,
|
|
95
98
|
branch: review.branch,
|
|
96
99
|
updatedAt: review.updatedAt,
|
|
100
|
+
startedAt: review.startedAt,
|
|
101
|
+
heartbeatAt: review.heartbeatAt,
|
|
97
102
|
status: review.status,
|
|
98
103
|
summary: summarize(state ?? emptyState(review.branch)),
|
|
99
104
|
};
|
|
@@ -211,6 +216,10 @@ export class RunRepository {
|
|
|
211
216
|
if (resolution.status !== "found") return null;
|
|
212
217
|
const finding = state.findings[resolution.id];
|
|
213
218
|
applyFindingUpdate(finding, body);
|
|
219
|
+
// "Done" na UI é resolução local: resolve a conversa no PR (fail-open).
|
|
220
|
+
if (body.execState === "done") {
|
|
221
|
+
syncResolvedThreads({ state, ids: [finding.id] });
|
|
222
|
+
}
|
|
214
223
|
reorder(state);
|
|
215
224
|
state.updatedAt = systemClock();
|
|
216
225
|
this.storage.writeFindings(review.id, state);
|
|
@@ -14,6 +14,28 @@
|
|
|
14
14
|
- Novo endpoint interno para reprocessar eventos manualmente.
|
|
15
15
|
- Registro de tentativas no handler de envio.
|
|
16
16
|
|
|
17
|
+
## Relatórios por domínio
|
|
18
|
+
|
|
19
|
+
| Watchdog | Achados | Revisados | Relatório |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| Arquitetura Backend | 2 | 3/3 | [`review.backend-architecture.md`](review.backend-architecture.md) |
|
|
22
|
+
| Pagamentos & Integrações | 1 | 4/4 | [`review.payments-integrations.md`](review.payments-integrations.md) |
|
|
23
|
+
| Acquirer Integrations | 2 | 3/4 | [`review.acquirer-integrations.md`](review.acquirer-integrations.md) |
|
|
24
|
+
|
|
25
|
+
## Cobertura
|
|
26
|
+
|
|
27
|
+
| Watchdog | Arquivos revisados | Findings |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| API & Contratos | 2 | 0 |
|
|
30
|
+
| Arquitetura Backend | 3 | 2 |
|
|
31
|
+
| Pagamentos & Integrações | 4 | 1 |
|
|
32
|
+
| Acquirer Integrations | 3/4 (faltam 1) | 2 |
|
|
33
|
+
|
|
34
|
+
## Conclusão
|
|
35
|
+
|
|
36
|
+
**Status:** ❌ Mudanças Necessárias
|
|
37
|
+
**Resumo:** 2 violações bloqueantes, 1 ponto de atenção, 1 sugestão e 1 nit.
|
|
38
|
+
|
|
17
39
|
## ❌ blocking
|
|
18
40
|
|
|
19
41
|
- **Webhook handler sem verifySignature** — `server/src/services/integrations/acquirer/webhook/handle-charge-event.ts:15` · _Acquirer Integrations, Pagamentos & Integrações_
|
|
@@ -42,17 +64,3 @@
|
|
|
42
64
|
- **Ordem de membros** — `server/src/services/webhook/webhook-retry-service.ts:12` · _Arquitetura Backend_
|
|
43
65
|
Campo privado declarado antes do construtor público.
|
|
44
66
|
**Ação:** Reordenar membros (públicos primeiro).
|
|
45
|
-
|
|
46
|
-
## Cobertura
|
|
47
|
-
|
|
48
|
-
| Watchdog | Arquivos revisados | Findings |
|
|
49
|
-
|---|---|---|
|
|
50
|
-
| API & Contratos | 2 | 0 |
|
|
51
|
-
| Arquitetura Backend | 3 | 1 |
|
|
52
|
-
| Pagamentos & Integrações | 4 | 2 |
|
|
53
|
-
| Acquirer Integrations | 3/4 (faltam 1) | 2 |
|
|
54
|
-
|
|
55
|
-
## Conclusão
|
|
56
|
-
|
|
57
|
-
**Status:** ❌ Mudanças Necessárias
|
|
58
|
-
**Resumo:** 2 violações bloqueantes, 1 ponto de atenção, 1 sugestão.
|
package/src/skill/hunt.md
CHANGED
|
@@ -122,11 +122,14 @@ arquivos alterados desde a última revisão — e **carrega** os findings dos ar
|
|
|
122
122
|
(carry-over), sem re-revisá-los. O `manifest.json` traz o bloco `incremental` (`from`,
|
|
123
123
|
`changedFiles`, `carried`) e o `review.md` abre com um aviso.
|
|
124
124
|
|
|
125
|
-
- Sem mudanças desde a última revisão → **no-op
|
|
125
|
+
- Sem mudanças desde a última revisão → **no-op**: o prepare não gera briefs nem reescreve
|
|
126
|
+
artefatos (classification, manifest, review.md), e o `--finalize` também vira no-op — pare o
|
|
127
|
+
fluxo aí (nenhum subagente é despachado).
|
|
126
128
|
- Rebase/force-push (histórico reescrito) → cai automaticamente no **review completo**.
|
|
127
129
|
- Forçar completo: `houndsman --full` (ou `--clean`, que também descarta a triagem anterior).
|
|
128
130
|
|
|
129
131
|
> Custo: o LLM só é gasto nos arquivos que mudaram desde a última revisão — não na PR inteira.
|
|
132
|
+
> Re-rodar `/houndsman` no mesmo commit **não** re-revisa a PR; use `--full` para isso.
|
|
130
133
|
|
|
131
134
|
---
|
|
132
135
|
|
|
@@ -135,6 +138,10 @@ arquivos alterados desde a última revisão — e **carrega** os findings dos ar
|
|
|
135
138
|
Um subagente por brief. Despache em **ondas** (ex.: até 8 subagentes por bloco de tool
|
|
136
139
|
calls) — evita estourar limites de taxa quando há muitos watchdogs. O prompt é o próprio brief:
|
|
137
140
|
|
|
141
|
+
> **Heartbeat:** antes de despachar cada onda, rode `houndsman status ping`. A UI usa esse
|
|
142
|
+
> sinal (e não `updatedAt`) para saber que a execução está viva; sem ping por 10 min ela
|
|
143
|
+
> mostra "Timeout na revisão".
|
|
144
|
+
|
|
138
145
|
```
|
|
139
146
|
Leia e execute o brief: <caminho absoluto impresso pelo prepare>/briefs/<watchdog-id>.md
|
|
140
147
|
Você é o watchdog <watchdog-id> do {{projectName}}.
|
|
@@ -158,15 +165,18 @@ houndsman --finalize
|
|
|
158
165
|
Falha com mensagem clara se faltar algum `findings/<watchdog>.json` no staging. Quando todos existem:
|
|
159
166
|
1. Valida o schema de cada JSON
|
|
160
167
|
2. Deduplica achados por `(file, line, rule)` mesclando watchdogs sobrepostos
|
|
161
|
-
3.
|
|
162
|
-
4. Grava o artefato `review.md` no storage + tabela de cobertura
|
|
163
|
-
5. Anexa os candidatos Jev não confirmados em `🔎 Candidatos não verificados` (não bloqueiam)
|
|
164
|
-
6. Agrupa os achados **por arquivo** dentro de cada severidade (navegabilidade em PRs grandes)
|
|
165
|
-
7. Grava um `review.<watchdog>.md` por watchdog com achados e indexa no `review.md`
|
|
168
|
+
3. Grava um `review.<watchdog>.md` por watchdog com achados e indexa no `review.md`
|
|
166
169
|
(**Relatórios por domínio**) — mantém o relatório consolidado enxuto
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
4. Grava o `review.md` abrindo pelo **sumário**: contexto, relatórios por domínio, cobertura e
|
|
171
|
+
conclusão — antes dos achados
|
|
172
|
+
5. Agrupa os achados por severidade (❌ blocking > ⚠️ attention > 💡 suggestion > 🔎 nit) e
|
|
173
|
+
**por arquivo** dentro de cada severidade (navegabilidade em PRs grandes)
|
|
174
|
+
6. Anexa os candidatos Jev não confirmados em `🔎 Candidatos não verificados` ao final
|
|
175
|
+
(não bloqueiam)
|
|
176
|
+
7. Persiste a **triagem** no storage — um `id` curto e um `fingerprint` de conteúdo por
|
|
177
|
+
finding, preservados entre execuções9. Marca como **`done`** os findings que sumiram do relatório (corrigidos manualmente entre
|
|
178
|
+
revisões) e **resolve as conversas correspondentes no GitHub** — só as ainda abertas, e
|
|
179
|
+
falha-aberto (sem `gh`/PR o relatório segue igual)
|
|
170
180
|
Após finalizar, corrija os achados com a capability **`fix`** (`fix.md`): a UI ou os
|
|
171
181
|
comandos `findings` levam do achado à correção.
|
|
172
182
|
|
|
@@ -188,14 +198,6 @@ comandos `findings` levam do achado à correção.
|
|
|
188
198
|
**Resumo das alterações:**
|
|
189
199
|
- ...
|
|
190
200
|
|
|
191
|
-
## ❌ blocking
|
|
192
|
-
### `src/routes/order-route.ts`
|
|
193
|
-
- **<regra>** — `src/routes/order-route.ts:5` · _<watchdogs>_
|
|
194
|
-
<mensagem>
|
|
195
|
-
**Ação:** <ação>
|
|
196
|
-
|
|
197
|
-
## 🔎 Candidatos não verificados (Jev) ← se houver
|
|
198
|
-
|
|
199
201
|
## Relatórios por domínio
|
|
200
202
|
| Watchdog | Achados | Revisados | Relatório |
|
|
201
203
|
|---|---|---|---|
|
|
@@ -206,8 +208,19 @@ comandos `findings` levam do achado à correção.
|
|
|
206
208
|
|
|
207
209
|
## Conclusão
|
|
208
210
|
**Status:** ✅ Aprovado / ⚠️ Aprovado com Ressalvas / ❌ Mudanças Necessárias
|
|
211
|
+
|
|
212
|
+
## ❌ blocking
|
|
213
|
+
### `src/routes/order-route.ts`
|
|
214
|
+
- **<regra>** — `src/routes/order-route.ts:5` · _<watchdogs>_
|
|
215
|
+
<mensagem>
|
|
216
|
+
**Ação:** <ação>
|
|
217
|
+
|
|
218
|
+
## 🔎 Candidatos não verificados (Jev) ← se houver, sempre ao final
|
|
209
219
|
```
|
|
210
220
|
|
|
221
|
+
> O **sumário** (relatórios por domínio, cobertura e conclusão) vem **antes** dos achados;
|
|
222
|
+
> os pontos específicos ficam no fim.
|
|
223
|
+
|
|
211
224
|
---
|
|
212
225
|
|
|
213
226
|
## Fluxo Completo
|
|
@@ -246,6 +259,7 @@ Se a branch não for informada, detecta com `git branch --show-current`. Base do
|
|
|
246
259
|
|---|---|
|
|
247
260
|
| `houndsman context` | Passo 0 — contexto da PR via `gh` |
|
|
248
261
|
| `houndsman` | Passo 1 — preparar (briefs + manifesto; incremental quando possível) |
|
|
262
|
+
| `houndsman status ping` | Passo 2 — heartbeat da execução (antes de cada onda) |
|
|
249
263
|
| `houndsman --finalize` | Passo 3 — gerar o relatório |
|
|
250
264
|
| `houndsman watchdog <id>` | Contexto de um watchdog (`--json`, `--rules`) |
|
|
251
265
|
|
package/src/skill/verify.md
CHANGED
|
@@ -43,7 +43,8 @@ bun run houndsman findings verify --apply # marca os resolvidos como done (pi
|
|
|
43
43
|
|
|
44
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
|
-
`by: "verify"`)
|
|
46
|
+
`by: "verify"`) e a **thread correspondente é resolvida no GitHub** (quando há comentário e a
|
|
47
|
+
conversa ainda está aberta; fail-open). `persisting` e `uncertain` **não** são tocados.
|
|
47
48
|
|
|
48
49
|
Filtros (como no `findings list`): `--severity`, `--watchdog`, `--file`, `--decision`. `--all`
|
|
49
50
|
inclui ausentes. `--no-jev` desliga o tier LLM. `--fail-on-persisting` sai com código 1 se algum
|
|
@@ -88,23 +88,6 @@ exports[`baseline — relatório renderReport consolidado 1`] = `
|
|
|
88
88
|
- Faz A
|
|
89
89
|
- Remove B
|
|
90
90
|
|
|
91
|
-
## ❌ blocking
|
|
92
|
-
|
|
93
|
-
### \`src/alpha/a.ts\`
|
|
94
|
-
|
|
95
|
-
- **Nunca usar \`any\`.** — \`src/alpha/a.ts:1\` · _Alpha_ · \`id-alpha\`
|
|
96
|
-
any detectado
|
|
97
|
-
**Ação:** tipar
|
|
98
|
-
**Evidência:** \`export const a: any = 1;\`
|
|
99
|
-
|
|
100
|
-
## ⚠️ attention
|
|
101
|
-
|
|
102
|
-
### \`src/alpha/b.ts\`
|
|
103
|
-
|
|
104
|
-
- **Promover: validar a entrada.** — \`src/alpha/b.ts:2\` · _Alpha, Beta_ · \`id-beta\`
|
|
105
|
-
sem validação
|
|
106
|
-
**Ação:** adicionar schema
|
|
107
|
-
|
|
108
91
|
## Relatórios por domínio
|
|
109
92
|
|
|
110
93
|
| Watchdog | Achados | Revisados | Relatório |
|
|
@@ -121,7 +104,25 @@ exports[`baseline — relatório renderReport consolidado 1`] = `
|
|
|
121
104
|
## Conclusão
|
|
122
105
|
|
|
123
106
|
**Status:** ❌ Mudanças Necessárias
|
|
124
|
-
**Resumo:** 1 violações bloqueantes, 1 pontos de atenção, 0 sugestões e 0 nits.
|
|
107
|
+
**Resumo:** 1 violações bloqueantes, 1 pontos de atenção, 0 sugestões e 0 nits.
|
|
108
|
+
|
|
109
|
+
## ❌ blocking
|
|
110
|
+
|
|
111
|
+
### \`src/alpha/a.ts\`
|
|
112
|
+
|
|
113
|
+
- **Nunca usar \`any\`.** — \`src/alpha/a.ts:1\` · _Alpha_ · \`id-alpha\`
|
|
114
|
+
any detectado
|
|
115
|
+
**Ação:** tipar
|
|
116
|
+
**Evidência:** \`export const a: any = 1;\`
|
|
117
|
+
|
|
118
|
+
## ⚠️ attention
|
|
119
|
+
|
|
120
|
+
### \`src/alpha/b.ts\`
|
|
121
|
+
|
|
122
|
+
- **Promover: validar a entrada.** — \`src/alpha/b.ts:2\` · _Alpha, Beta_ · \`id-beta\`
|
|
123
|
+
sem validação
|
|
124
|
+
**Ação:** adicionar schema
|
|
125
|
+
"
|
|
125
126
|
`;
|
|
126
127
|
|
|
127
128
|
exports[`baseline — relatório renderDomainReport isolado 1`] = `
|
|
@@ -528,7 +529,7 @@ exports[`baseline — camada gerada setup gera os mesmos arquivos e conteúdos 3
|
|
|
528
529
|
},
|
|
529
530
|
{
|
|
530
531
|
"file": ".houndsman/core/skill/example-review.md",
|
|
531
|
-
"sha256": "
|
|
532
|
+
"sha256": "d9e75054cc9811dc",
|
|
532
533
|
},
|
|
533
534
|
{
|
|
534
535
|
"file": ".houndsman/core/skill/fix.md",
|
|
@@ -536,7 +537,7 @@ exports[`baseline — camada gerada setup gera os mesmos arquivos e conteúdos 3
|
|
|
536
537
|
},
|
|
537
538
|
{
|
|
538
539
|
"file": ".houndsman/core/skill/hunt.md",
|
|
539
|
-
"sha256": "
|
|
540
|
+
"sha256": "c978287603f4e088",
|
|
540
541
|
},
|
|
541
542
|
{
|
|
542
543
|
"file": ".houndsman/core/skill/train.md",
|
|
@@ -544,7 +545,7 @@ exports[`baseline — camada gerada setup gera os mesmos arquivos e conteúdos 3
|
|
|
544
545
|
},
|
|
545
546
|
{
|
|
546
547
|
"file": ".houndsman/core/skill/verify.md",
|
|
547
|
-
"sha256": "
|
|
548
|
+
"sha256": "29bfdeb7def2298e",
|
|
548
549
|
},
|
|
549
550
|
]
|
|
550
551
|
`;
|
|
@@ -713,15 +714,6 @@ exports[`baseline — artefatos de prepare/finalize gera classification, brief,
|
|
|
713
714
|
**Watchdogs ativos:** alpha
|
|
714
715
|
**Achados:** 1 bloqueantes · 0 atenção · 0 sugestões · 0 nits
|
|
715
716
|
|
|
716
|
-
## ❌ blocking
|
|
717
|
-
|
|
718
|
-
### \`src/alpha/a.ts\`
|
|
719
|
-
|
|
720
|
-
- **Nunca usar \`any\`.** — \`src/alpha/a.ts:1\` · _Alpha_ · \`<id>\`
|
|
721
|
-
any detectado
|
|
722
|
-
**Ação:** tipar
|
|
723
|
-
**Evidência:** \`export const a: any = 1;\`
|
|
724
|
-
|
|
725
717
|
## Relatórios por domínio
|
|
726
718
|
|
|
727
719
|
| Watchdog | Achados | Revisados | Relatório |
|
|
@@ -738,6 +730,16 @@ exports[`baseline — artefatos de prepare/finalize gera classification, brief,
|
|
|
738
730
|
|
|
739
731
|
**Status:** ❌ Mudanças Necessárias
|
|
740
732
|
**Resumo:** 1 violações bloqueantes, 0 pontos de atenção, 0 sugestões e 0 nits.
|
|
733
|
+
|
|
734
|
+
## ❌ blocking
|
|
735
|
+
|
|
736
|
+
### \`src/alpha/a.ts\`
|
|
737
|
+
|
|
738
|
+
- **Nunca usar \`any\`.** — \`src/alpha/a.ts:1\` · _Alpha_ · \`<id>\`
|
|
739
|
+
any detectado
|
|
740
|
+
**Ação:** tipar
|
|
741
|
+
**Evidência:** \`export const a: any = 1;\`
|
|
742
|
+
|
|
741
743
|
"
|
|
742
744
|
`;
|
|
743
745
|
|
|
@@ -432,6 +432,9 @@ describe("baseline — artefatos de prepare/finalize", () => {
|
|
|
432
432
|
const artifact = storage.readArtifact(review.id, name);
|
|
433
433
|
expect(normalize(artifact?.content ?? "")).toMatchSnapshot(name);
|
|
434
434
|
}
|
|
435
|
+
const resume = storage.readArtifact(review.id, "review-resume.md");
|
|
436
|
+
expect(resume?.kind).toBe("resume");
|
|
437
|
+
expect((resume?.content ?? "").length).toBeLessThanOrEqual(2500);
|
|
435
438
|
expect(
|
|
436
439
|
normalize(JSON.stringify(storage.readFindings(review.id), null, 2)),
|
|
437
440
|
).toMatchSnapshot("state.json");
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
highlightCode,
|
|
11
11
|
languageFromFile,
|
|
12
12
|
languageFromName,
|
|
13
|
-
} from "../server/client/code-highlight";
|
|
13
|
+
} from "../server/client/utils/code-highlight";
|
|
14
14
|
|
|
15
15
|
/** O texto concatenado dos tokens deve ser idêntico ao input. */
|
|
16
16
|
function joined(tokens: { text: string }[]): string {
|
|
@@ -112,7 +112,7 @@ describe("buildStateFromFindings", () => {
|
|
|
112
112
|
}
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
-
test("finding que sumiu
|
|
115
|
+
test("finding que sumiu vira done (corrigido entre revisões)", () => {
|
|
116
116
|
const project = makeProject({ "src/f.ts": DUPLICATED });
|
|
117
117
|
try {
|
|
118
118
|
const reader = fileReader(project.cwd);
|
|
@@ -121,7 +121,7 @@ describe("buildStateFromFindings", () => {
|
|
|
121
121
|
{ branch: "main", readFile: reader },
|
|
122
122
|
);
|
|
123
123
|
const id = Object.keys(first.findings)[0];
|
|
124
|
-
|
|
124
|
+
expect(first.findings[id].decision).toBe("pending");
|
|
125
125
|
|
|
126
126
|
const second = buildStateFromFindings([], {
|
|
127
127
|
branch: "main",
|
|
@@ -135,6 +135,54 @@ describe("buildStateFromFindings", () => {
|
|
|
135
135
|
}
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
+
test("finding ausente e marcado como 'ignore' preserva o estado", () => {
|
|
139
|
+
const project = makeProject({ "src/f.ts": DUPLICATED });
|
|
140
|
+
try {
|
|
141
|
+
const reader = fileReader(project.cwd);
|
|
142
|
+
const first = buildStateFromFindings(
|
|
143
|
+
[merged({ file: "src/f.ts", rule: "r", line: 2 })],
|
|
144
|
+
{ branch: "main", readFile: reader },
|
|
145
|
+
);
|
|
146
|
+
const id = Object.keys(first.findings)[0];
|
|
147
|
+
applyFindingUpdate(first.findings[id], { decision: "ignore" });
|
|
148
|
+
|
|
149
|
+
const second = buildStateFromFindings([], {
|
|
150
|
+
branch: "main",
|
|
151
|
+
readFile: reader,
|
|
152
|
+
previous: first,
|
|
153
|
+
});
|
|
154
|
+
expect(second.findings[id].present).toBe(false);
|
|
155
|
+
expect(second.findings[id].execState).toBe("pending");
|
|
156
|
+
expect(second.findings[id].decision).toBe("ignore");
|
|
157
|
+
} finally {
|
|
158
|
+
project.cleanup();
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("arquivo fora da revisão (orçamento/skipped) não vira done", () => {
|
|
163
|
+
const project = makeProject({ "src/f.ts": DUPLICATED });
|
|
164
|
+
try {
|
|
165
|
+
const reader = fileReader(project.cwd);
|
|
166
|
+
const first = buildStateFromFindings(
|
|
167
|
+
[merged({ file: "src/f.ts", rule: "r", line: 2 })],
|
|
168
|
+
{ branch: "main", readFile: reader },
|
|
169
|
+
);
|
|
170
|
+
const id = Object.keys(first.findings)[0];
|
|
171
|
+
|
|
172
|
+
const second = buildStateFromFindings([], {
|
|
173
|
+
branch: "main",
|
|
174
|
+
readFile: reader,
|
|
175
|
+
previous: first,
|
|
176
|
+
// Nenhum arquivo foi revisado neste ciclo: ausência não prova resolução.
|
|
177
|
+
reviewedPaths: new Set(["src/outro.ts"]),
|
|
178
|
+
});
|
|
179
|
+
expect(second.findings[id].present).toBe(false);
|
|
180
|
+
expect(second.findings[id].execState).toBe("pending");
|
|
181
|
+
} finally {
|
|
182
|
+
project.cleanup();
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
138
186
|
test("ocorrências idênticas (mesmo contexto) viram um grupo com count", () => {
|
|
139
187
|
const content = [
|
|
140
188
|
"const a = 1;",
|