@payconductor/houndsman 0.3.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/commands/context-command.ts +11 -9
- package/src/cli/commands/doctor-command.ts +5 -0
- package/src/cli/commands/findings-command.ts +13 -3
- package/src/cli/commands/review-command.ts +1 -2
- package/src/cli/help.ts +7 -3
- package/src/cli/index.ts +2 -2
- package/src/core/commands/context.ts +29 -13
- package/src/core/commands/findings.ts +155 -120
- package/src/core/commands/review.ts +7 -8
- package/src/core/commands/scaffold.ts +15 -16
- package/src/core/config.ts +16 -7
- package/src/core/engine/houndsman-engine.ts +15 -1
- package/src/core/engine/review-session.ts +37 -9
- package/src/core/findings-state.ts +32 -71
- package/src/core/jev/jev-candidates.ts +49 -35
- package/src/core/orchestrator.ts +175 -128
- package/src/core/paths.ts +23 -17
- package/src/core/pr-context.ts +19 -5
- package/src/core/project-config.ts +4 -5
- package/src/core/renderers/manifest-writer.ts +29 -18
- package/src/core/run-layout.ts +4 -104
- package/src/core/run-status.ts +28 -53
- package/src/core/services/git-repository.ts +16 -0
- package/src/core/services/project-files.ts +13 -1
- 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/index.ts +27 -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 +95 -15
- package/src/server/run-repository.ts +144 -136
- 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 +44 -33
- package/src/tests/baseline.test.ts +76 -34
- package/src/tests/cli.test.ts +40 -19
- package/src/tests/code-highlight.test.ts +123 -0
- package/src/tests/domain.test.ts +1 -1
- 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 +33 -38
- 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
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
|
|
|
@@ -4,7 +4,7 @@ exports[`baseline — brief defaultRenderBrief (contexto, churn, overflow, candi
|
|
|
4
4
|
"# Brief — Alpha (\`alpha\`)
|
|
5
5
|
|
|
6
6
|
**Branch:** feature/x
|
|
7
|
-
**Output obrigatório:**
|
|
7
|
+
**Output obrigatório:** \`/workspace/work/review-x/findings/alpha.json\`
|
|
8
8
|
**Schema do output:** \`.houndsman/core/skill/OUTPUT-FORMAT.md\`
|
|
9
9
|
|
|
10
10
|
## Objetivo
|
|
@@ -382,10 +382,16 @@ export type HoundsmanConcurrency = {
|
|
|
382
382
|
jev?: number;
|
|
383
383
|
};
|
|
384
384
|
|
|
385
|
+
/** Backend de persistência (reviews, findings e artefatos). */
|
|
386
|
+
export type HoundsmanStorage = {
|
|
387
|
+
provider?: "sqlite" | "file";
|
|
388
|
+
path?: string;
|
|
389
|
+
};
|
|
390
|
+
|
|
385
391
|
/** Contrato de configuração do motor. */
|
|
386
392
|
export type HoundsmanConfig<TId extends string = string> = {
|
|
387
393
|
projectName: string;
|
|
388
|
-
|
|
394
|
+
storage?: HoundsmanStorage;
|
|
389
395
|
watchdogIds?: readonly TId[];
|
|
390
396
|
watchdogs: Record<TId, WatchdogDefinition>;
|
|
391
397
|
exclusions: readonly RegExp[];
|
|
@@ -412,10 +418,9 @@ export function defineHoundsmanConfig<TId extends string>(
|
|
|
412
418
|
"/**
|
|
413
419
|
* Mapeamento de padrões nos paths → documentação relevante.
|
|
414
420
|
* Consumido pelo motor via \`HoundsmanConfig.docMappings\`.
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
];
|
|
421
|
+
*/\`core/\` é regenerado por \`houndsman update\`.
|
|
422
|
+
# Runs, findings e artefatos vivem no storage global (fora do projeto).
|
|
423
|
+
core
|
|
419
424
|
"
|
|
420
425
|
,
|
|
421
426
|
".houndsman/exclusions.ts":
|
|
@@ -486,9 +491,9 @@ import type { HoundsmanConfig } from "./.houndsman/core/types";
|
|
|
486
491
|
/**
|
|
487
492
|
* Configuração do houndsman para acme.
|
|
488
493
|
*
|
|
489
|
-
*
|
|
490
|
-
* output em .houndsman/core/skill/OUTPUT-FORMAT.md. Ajuste
|
|
491
|
-
* docs conforme o projeto.
|
|
494
|
+
* Runs, findings e artefatos vivem no storage global (SQLite por default) —
|
|
495
|
+
* fora do projeto. O schema de output fica em .houndsman/core/skill/OUTPUT-FORMAT.md. Ajuste
|
|
496
|
+
* watchdogs, exclusões e mapeamentos de docs conforme o projeto.
|
|
492
497
|
*/
|
|
493
498
|
export const houndsmanConfig = {
|
|
494
499
|
projectName: "acme",
|
|
@@ -511,15 +516,15 @@ exports[`baseline — camada gerada setup gera os mesmos arquivos e conteúdos 3
|
|
|
511
516
|
},
|
|
512
517
|
{
|
|
513
518
|
"file": ".houndsman/core/skill/OUTPUT-FORMAT.md",
|
|
514
|
-
"sha256": "
|
|
519
|
+
"sha256": "06aedccd5688aaa8",
|
|
515
520
|
},
|
|
516
521
|
{
|
|
517
522
|
"file": ".houndsman/core/skill/README.md",
|
|
518
|
-
"sha256": "
|
|
523
|
+
"sha256": "0c1514669d044033",
|
|
519
524
|
},
|
|
520
525
|
{
|
|
521
526
|
"file": ".houndsman/core/skill/SETUP.md",
|
|
522
|
-
"sha256": "
|
|
527
|
+
"sha256": "7ce23b97d9609ac2",
|
|
523
528
|
},
|
|
524
529
|
{
|
|
525
530
|
"file": ".houndsman/core/skill/example-review.md",
|
|
@@ -527,31 +532,31 @@ exports[`baseline — camada gerada setup gera os mesmos arquivos e conteúdos 3
|
|
|
527
532
|
},
|
|
528
533
|
{
|
|
529
534
|
"file": ".houndsman/core/skill/fix.md",
|
|
530
|
-
"sha256": "
|
|
535
|
+
"sha256": "9f10b2d846dc7db5",
|
|
531
536
|
},
|
|
532
537
|
{
|
|
533
538
|
"file": ".houndsman/core/skill/hunt.md",
|
|
534
|
-
"sha256": "
|
|
539
|
+
"sha256": "c47a24c6c36c1c03",
|
|
535
540
|
},
|
|
536
541
|
{
|
|
537
542
|
"file": ".houndsman/core/skill/train.md",
|
|
538
|
-
"sha256": "
|
|
543
|
+
"sha256": "d774215c68d15159",
|
|
539
544
|
},
|
|
540
545
|
{
|
|
541
546
|
"file": ".houndsman/core/skill/verify.md",
|
|
542
|
-
"sha256": "
|
|
547
|
+
"sha256": "7ae6e1ca8bf0a868",
|
|
543
548
|
},
|
|
544
549
|
]
|
|
545
550
|
`;
|
|
546
551
|
|
|
547
552
|
exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, review e state 1`] = `
|
|
548
553
|
"Discovery: 1 watchdog(s) ativado(s), 3 arquivo(s), 1 deletado(s).
|
|
549
|
-
Briefs:
|
|
554
|
+
Briefs: <cwd>/.home/work/<uuid>/briefs/
|
|
550
555
|
Deletado (breaking change): src/alpha/old.ts
|
|
551
556
|
Jev: pulado (desativado (--no-jev)).
|
|
552
557
|
Sem contexto da PR (rode \`bun run houndsman context\` antes, se houver PR).
|
|
553
558
|
|
|
554
|
-
Próximo passo: despachar os sub-agentes (um por brief em
|
|
559
|
+
Próximo passo: despachar os sub-agentes (um por brief em <cwd>/.home/work/<uuid>/briefs/).
|
|
555
560
|
Quando os findings estiverem gravados: bun run houndsman --finalize"
|
|
556
561
|
`;
|
|
557
562
|
|
|
@@ -631,8 +636,8 @@ exports[`baseline — artefatos de prepare/finalize gera classification, brief,
|
|
|
631
636
|
"src/alpha/b.ts"
|
|
632
637
|
],
|
|
633
638
|
"overflow": 0,
|
|
634
|
-
"brief": "
|
|
635
|
-
"findings": "
|
|
639
|
+
"brief": "<cwd>\\\\.home\\\\work\\\\<uuid>\\\\briefs\\\\alpha.md",
|
|
640
|
+
"findings": "<cwd>\\\\.home\\\\work\\\\<uuid>\\\\findings\\\\alpha.json",
|
|
636
641
|
"status": "pending"
|
|
637
642
|
}
|
|
638
643
|
],
|
|
@@ -657,7 +662,7 @@ exports[`baseline — artefatos de prepare/finalize gera classification, brief,
|
|
|
657
662
|
"# Brief — Alpha (\`alpha\`)
|
|
658
663
|
|
|
659
664
|
**Branch:** main
|
|
660
|
-
**Output obrigatório:**
|
|
665
|
+
**Output obrigatório:** \`<cwd>\\.home\\work\\<uuid>\\findings\\alpha.json\`
|
|
661
666
|
**Schema do output:** \`.houndsman/core/skill/OUTPUT-FORMAT.md\`
|
|
662
667
|
|
|
663
668
|
## Objetivo
|
|
@@ -697,8 +702,8 @@ src/alpha/**
|
|
|
697
702
|
`;
|
|
698
703
|
|
|
699
704
|
exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, review e state 2`] = `
|
|
700
|
-
"Triagem:
|
|
701
|
-
Relatório:
|
|
705
|
+
"Triagem: sqlite:<cwd>\\houndsman.db#<uuid> — 1 finding(s).
|
|
706
|
+
Relatório: sqlite:<cwd>\\houndsman.db#<uuid> — 1 bloqueante(s), 0 atenção."
|
|
702
707
|
`;
|
|
703
708
|
|
|
704
709
|
exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, review e state: review.md 1`] = `
|
|
@@ -764,10 +769,6 @@ exports[`baseline — artefatos de prepare/finalize gera classification, brief,
|
|
|
764
769
|
"order": [
|
|
765
770
|
"<id>"
|
|
766
771
|
],
|
|
767
|
-
"reviewed": {
|
|
768
|
-
"headSha": "<sha>",
|
|
769
|
-
"at": "<timestamp>"
|
|
770
|
-
},
|
|
771
772
|
"findings": {
|
|
772
773
|
"<id>": {
|
|
773
774
|
"id": "<id>",
|
|
@@ -796,19 +797,29 @@ exports[`baseline — artefatos de prepare/finalize gera classification, brief,
|
|
|
796
797
|
"updatedAt": "<timestamp>",
|
|
797
798
|
"history": []
|
|
798
799
|
}
|
|
800
|
+
},
|
|
801
|
+
"reviewed": {
|
|
802
|
+
"headSha": "<sha>",
|
|
803
|
+
"at": "<timestamp>"
|
|
799
804
|
}
|
|
800
|
-
}
|
|
801
|
-
"
|
|
805
|
+
}"
|
|
802
806
|
`;
|
|
803
807
|
|
|
804
808
|
exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, review e state: run.json 1`] = `
|
|
805
809
|
"{
|
|
806
|
-
"
|
|
807
|
-
"
|
|
810
|
+
"id": "<uuid>",
|
|
811
|
+
"repoKey": "path:<cwd>",
|
|
812
|
+
"repoRoot": "<cwd>",
|
|
808
813
|
"branch": "main",
|
|
814
|
+
"slug": "main",
|
|
815
|
+
"base": "<sha>",
|
|
816
|
+
"head": "HEAD",
|
|
817
|
+
"headSha": "<sha>",
|
|
818
|
+
"reviewedSha": "<sha>",
|
|
819
|
+
"reviewedAt": "<timestamp>",
|
|
809
820
|
"status": "review_done",
|
|
810
821
|
"startedAt": "<timestamp>",
|
|
822
|
+
"createdAt": "<timestamp>",
|
|
811
823
|
"updatedAt": "<timestamp>"
|
|
812
|
-
}
|
|
813
|
-
"
|
|
824
|
+
}"
|
|
814
825
|
`;
|
|
@@ -26,13 +26,16 @@ import {
|
|
|
26
26
|
writeUserLayer,
|
|
27
27
|
} from "../core/commands/scaffold";
|
|
28
28
|
import { defineHoundsmanConfig } from "../core/config";
|
|
29
|
-
import { defaultOptions, finalize, prepare } from "../core/orchestrator";
|
|
29
|
+
import { defaultOptions, finalize, prepare, reviewContext } from "../core/orchestrator";
|
|
30
|
+
import { displayPath } from "../core/paths";
|
|
30
31
|
import {
|
|
31
32
|
type MergedFinding,
|
|
32
33
|
renderDomainReport,
|
|
33
34
|
renderReport,
|
|
34
35
|
type WatchdogReport,
|
|
35
36
|
} from "../core/report-builder";
|
|
37
|
+
import { resolveRepoIdentity } from "../core/storage/repo-identity";
|
|
38
|
+
import { SQLiteStorageProvider } from "../core/storage/sqlite/sqlite-storage";
|
|
36
39
|
import { defineWatchdog } from "../core/types";
|
|
37
40
|
import * as facade from "../index";
|
|
38
41
|
|
|
@@ -56,7 +59,6 @@ const baselineWatchdog = defineWatchdog({
|
|
|
56
59
|
|
|
57
60
|
const baselineConfig = defineHoundsmanConfig({
|
|
58
61
|
projectName: "Baseline",
|
|
59
|
-
artifactDir: ".houndsman/.runs",
|
|
60
62
|
watchdogs: { alpha: baselineWatchdog },
|
|
61
63
|
exclusions: [/^dist\//],
|
|
62
64
|
docMappings: [[/^src\//, ["README.md"]]],
|
|
@@ -75,6 +77,10 @@ function normalizeArtifacts(text: string): string {
|
|
|
75
77
|
'"$1": "<timestamp>"',
|
|
76
78
|
)
|
|
77
79
|
.replace(/"\d{4}-\d{2}-\d{2}T[^"]*"/g, '"<timestamp>"')
|
|
80
|
+
.replace(
|
|
81
|
+
/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g,
|
|
82
|
+
"<uuid>",
|
|
83
|
+
)
|
|
78
84
|
.replace(/\b[0-9a-f]{40}\b/g, "<sha>")
|
|
79
85
|
.replace(/\b[0-9a-f]{16}\b/g, "<fingerprint>")
|
|
80
86
|
.replace(/\b[0-9a-f]{10}\b/g, "<id>")
|
|
@@ -128,7 +134,7 @@ describe("baseline — brief", () => {
|
|
|
128
134
|
overflow: ["src/alpha/c.ts"],
|
|
129
135
|
deleted: ["src/alpha/old.ts"],
|
|
130
136
|
branch: "feature/x",
|
|
131
|
-
outputPath: "
|
|
137
|
+
outputPath: "/workspace/work/review-x/findings/alpha.json",
|
|
132
138
|
outputFormatPath: ".houndsman/core/skill/OUTPUT-FORMAT.md",
|
|
133
139
|
docs: ["AGENTS.md", "README.md"],
|
|
134
140
|
context: {
|
|
@@ -317,6 +323,8 @@ describe("baseline — artefatos de prepare/finalize", () => {
|
|
|
317
323
|
test("gera classification, brief, manifest, review e state", async () => {
|
|
318
324
|
const cwd = mkdtempSync(join(tmpdir(), "houndsman-baseline-run-"));
|
|
319
325
|
const originalCwd = process.cwd();
|
|
326
|
+
const originalHome = process.env.HOUNDSMAN_HOME;
|
|
327
|
+
let storage: SQLiteStorageProvider | null = null;
|
|
320
328
|
|
|
321
329
|
try {
|
|
322
330
|
git(cwd, ["init", "-b", "main"]);
|
|
@@ -343,30 +351,58 @@ describe("baseline — artefatos de prepare/finalize", () => {
|
|
|
343
351
|
git(cwd, ["commit", "-m", "change"]);
|
|
344
352
|
|
|
345
353
|
const opts = {
|
|
346
|
-
...defaultOptions(
|
|
354
|
+
...defaultOptions(),
|
|
347
355
|
base,
|
|
348
356
|
head: "HEAD",
|
|
349
357
|
noJev: true,
|
|
350
358
|
};
|
|
351
359
|
|
|
360
|
+
process.env.HOUNDSMAN_HOME = join(cwd, ".home");
|
|
352
361
|
process.chdir(cwd);
|
|
353
362
|
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
363
|
+
const config = {
|
|
364
|
+
...baselineConfig,
|
|
365
|
+
storage: {
|
|
366
|
+
provider: "sqlite" as const,
|
|
367
|
+
path: join(cwd, "houndsman.db"),
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
storage = new SQLiteStorageProvider(join(cwd, "houndsman.db"));
|
|
371
|
+
const repo = resolveRepoIdentity();
|
|
372
|
+
const review = storage.getOrCreateReview({
|
|
373
|
+
repo,
|
|
374
|
+
branch: "main",
|
|
375
|
+
slug: "main",
|
|
376
|
+
});
|
|
377
|
+
const ctx = reviewContext(storage, review);
|
|
378
|
+
const homeDisplay = displayPath(cwd);
|
|
379
|
+
const escapedCwd = cwd.replace(/\\/g, "\\\\");
|
|
380
|
+
const normalize = (text: string): string =>
|
|
381
|
+
normalizeArtifacts(
|
|
382
|
+
text
|
|
383
|
+
.replaceAll(escapedCwd, "<cwd>")
|
|
384
|
+
.replaceAll(cwd, "<cwd>")
|
|
385
|
+
.replaceAll(cwd.replace(/\\/g, "/"), "<cwd>")
|
|
386
|
+
.replaceAll(cwd.replace(/\\/g, "/").toLowerCase(), "<cwd>")
|
|
387
|
+
.replaceAll(homeDisplay, "<cwd>"),
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const prepared = await prepare(opts, "main", config, ctx);
|
|
391
|
+
expect(normalize(prepared.messages.join("\n"))).toMatchSnapshot();
|
|
392
|
+
|
|
393
|
+
for (const name of ["classification.json", "manifest.json"]) {
|
|
394
|
+
const artifact = storage.readArtifact(review.id, name);
|
|
395
|
+
expect(normalize(artifact?.content ?? "")).toMatchSnapshot(name);
|
|
366
396
|
}
|
|
397
|
+
const workBrief = readFileSync(ctx.work.briefFile("alpha"), "utf8");
|
|
398
|
+
const briefArtifact = storage.readArtifact(review.id, "briefs/alpha.md");
|
|
399
|
+
expect(briefArtifact?.content).toBe(workBrief);
|
|
400
|
+
expect(normalize(briefArtifact?.content ?? "")).toMatchSnapshot(
|
|
401
|
+
"briefs/alpha.md",
|
|
402
|
+
);
|
|
367
403
|
|
|
368
404
|
writeFileSync(
|
|
369
|
-
|
|
405
|
+
ctx.work.findingFile("alpha"),
|
|
370
406
|
`${JSON.stringify(
|
|
371
407
|
{
|
|
372
408
|
watchdogId: "alpha",
|
|
@@ -389,21 +425,24 @@ describe("baseline — artefatos de prepare/finalize", () => {
|
|
|
389
425
|
)}\n`,
|
|
390
426
|
);
|
|
391
427
|
|
|
392
|
-
const finalized = finalize(opts, "main",
|
|
393
|
-
expect(
|
|
394
|
-
|
|
395
|
-
for (const
|
|
396
|
-
|
|
397
|
-
"
|
|
398
|
-
"state.json",
|
|
399
|
-
"run.json",
|
|
400
|
-
]) {
|
|
401
|
-
expect(
|
|
402
|
-
normalizeArtifacts(readFileSync(join(runDir, file), "utf8")),
|
|
403
|
-
).toMatchSnapshot(file);
|
|
428
|
+
const finalized = finalize(opts, "main", config, ctx);
|
|
429
|
+
expect(normalize(finalized.messages.join("\n"))).toMatchSnapshot();
|
|
430
|
+
|
|
431
|
+
for (const name of ["review.md", "review.alpha.md"]) {
|
|
432
|
+
const artifact = storage.readArtifact(review.id, name);
|
|
433
|
+
expect(normalize(artifact?.content ?? "")).toMatchSnapshot(name);
|
|
404
434
|
}
|
|
435
|
+
expect(
|
|
436
|
+
normalize(JSON.stringify(storage.readFindings(review.id), null, 2)),
|
|
437
|
+
).toMatchSnapshot("state.json");
|
|
438
|
+
expect(
|
|
439
|
+
normalize(JSON.stringify(storage.getReview(review.id), null, 2)),
|
|
440
|
+
).toMatchSnapshot("run.json");
|
|
405
441
|
} finally {
|
|
442
|
+
storage?.close();
|
|
406
443
|
process.chdir(originalCwd);
|
|
444
|
+
if (originalHome === undefined) delete process.env.HOUNDSMAN_HOME;
|
|
445
|
+
else process.env.HOUNDSMAN_HOME = originalHome;
|
|
407
446
|
rmSync(cwd, { recursive: true, force: true });
|
|
408
447
|
}
|
|
409
448
|
}, 30_000);
|
|
@@ -433,12 +472,19 @@ const REQUIRED_EXPORTS = [
|
|
|
433
472
|
"GitRepository",
|
|
434
473
|
"ProjectConfig",
|
|
435
474
|
"ProjectFiles",
|
|
436
|
-
"RunLayout",
|
|
437
475
|
"Watchdog",
|
|
438
476
|
"WatchdogRegistry",
|
|
439
477
|
"systemClock",
|
|
440
478
|
"HoundsmanServer",
|
|
441
479
|
"HoundsmanEngine",
|
|
480
|
+
"ReviewWorkArea",
|
|
481
|
+
"SQLiteStorageProvider",
|
|
482
|
+
"FileStorageProvider",
|
|
483
|
+
"createStorage",
|
|
484
|
+
"createMemoryStorage",
|
|
485
|
+
"resolveRepoIdentity",
|
|
486
|
+
"buildRepoIdentity",
|
|
487
|
+
"normalizeRemote",
|
|
442
488
|
"ReviewSession",
|
|
443
489
|
"ReviewScopeResolver",
|
|
444
490
|
"buildCarryover",
|
|
@@ -447,7 +493,6 @@ const REQUIRED_EXPORTS = [
|
|
|
447
493
|
"ArgParser",
|
|
448
494
|
"ProjectScaffolder",
|
|
449
495
|
"FixBatchBuilder",
|
|
450
|
-
"Verifier",
|
|
451
496
|
"BriefRenderer",
|
|
452
497
|
"ReportRenderer",
|
|
453
498
|
"ManifestWriter",
|
|
@@ -455,7 +500,6 @@ const REQUIRED_EXPORTS = [
|
|
|
455
500
|
"assetPath",
|
|
456
501
|
"buildAnchor",
|
|
457
502
|
"buildStateFromFindings",
|
|
458
|
-
"buildStateFromRunDir",
|
|
459
503
|
"checkRegistry",
|
|
460
504
|
"churnOf",
|
|
461
505
|
"classifyEntries",
|
|
@@ -492,14 +536,12 @@ const REQUIRED_EXPORTS = [
|
|
|
492
536
|
"resolveConfigPath",
|
|
493
537
|
"resolveJevConcurrency",
|
|
494
538
|
"resolveMaxFilesPerBrief",
|
|
495
|
-
"runDir",
|
|
496
539
|
"runFindingsCommand",
|
|
497
540
|
"runVerifyCommand",
|
|
498
541
|
"selectByBudget",
|
|
499
542
|
"severityRank",
|
|
500
543
|
"sortByRisk",
|
|
501
544
|
"startServer",
|
|
502
|
-
"stateFile",
|
|
503
545
|
"summarize",
|
|
504
546
|
"syncState",
|
|
505
547
|
] as const;
|
package/src/tests/cli.test.ts
CHANGED
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { describe, expect, test } from "bun:test";
|
|
10
|
-
import {
|
|
10
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { tmpdir } from "node:os";
|
|
12
12
|
import { join } from "node:path";
|
|
13
|
+
import { buildStateFromFindings, fileReader } from "../core/findings-state";
|
|
14
|
+
import { dedupeFindings, parseWatchdogReport } from "../core/report-builder";
|
|
15
|
+
import { buildRepoIdentity } from "../core/storage/repo-identity";
|
|
16
|
+
import { SQLiteStorageProvider } from "../core/storage/sqlite/sqlite-storage";
|
|
13
17
|
|
|
14
18
|
/** Caminho do executável do CLI (TS, rodado pelo Bun). */
|
|
15
19
|
const CLI = join(import.meta.dir, "..", "cli", "index.ts");
|
|
@@ -17,6 +21,7 @@ const CLI = join(import.meta.dir, "..", "cli", "index.ts");
|
|
|
17
21
|
/** Config mínima, como objeto literal (o `loadConfig` deriva `watchdogIds`). */
|
|
18
22
|
const CONFIG = `export default {
|
|
19
23
|
projectName: "Smoke",
|
|
24
|
+
storage: { provider: "sqlite", path: "./houndsman.db" },
|
|
20
25
|
watchdogs: {
|
|
21
26
|
alpha: {
|
|
22
27
|
id: "alpha",
|
|
@@ -33,22 +38,18 @@ const CONFIG = `export default {
|
|
|
33
38
|
};
|
|
34
39
|
`;
|
|
35
40
|
|
|
36
|
-
type Project = { cwd: string;
|
|
41
|
+
type Project = { cwd: string; cleanup: () => void };
|
|
37
42
|
|
|
38
|
-
/** Projeto temporário: config + uma run
|
|
43
|
+
/** Projeto temporário: config + banco com uma run e um finding. */
|
|
39
44
|
function makeProject(): Project {
|
|
40
45
|
const cwd = mkdtempSync(join(tmpdir(), "houndsman-cli-"));
|
|
41
|
-
const artifacts = join(cwd, ".houndsman", ".runs");
|
|
42
|
-
const runDir = join(artifacts, "main");
|
|
43
|
-
mkdirSync(join(runDir, "findings"), { recursive: true });
|
|
44
46
|
writeFileSync(join(cwd, "houndsman.config.ts"), CONFIG);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
JSON.stringify({
|
|
47
|
+
|
|
48
|
+
const storage = new SQLiteStorageProvider(join(cwd, "houndsman.db"));
|
|
49
|
+
const repo = buildRepoIdentity(null, cwd);
|
|
50
|
+
const review = storage.getOrCreateReview({ repo, branch: "main", slug: "main" });
|
|
51
|
+
const report = parseWatchdogReport(
|
|
52
|
+
{
|
|
52
53
|
watchdogId: "alpha",
|
|
53
54
|
reviewedFiles: ["src/a.ts"],
|
|
54
55
|
skipped: [],
|
|
@@ -62,11 +63,22 @@ function makeProject(): Project {
|
|
|
62
63
|
action: "tipar",
|
|
63
64
|
},
|
|
64
65
|
],
|
|
65
|
-
}
|
|
66
|
+
},
|
|
67
|
+
"alpha.json",
|
|
66
68
|
);
|
|
69
|
+
if (report) {
|
|
70
|
+
storage.writeFindings(
|
|
71
|
+
review.id,
|
|
72
|
+
buildStateFromFindings(dedupeFindings([report]), {
|
|
73
|
+
branch: "main",
|
|
74
|
+
readFile: fileReader(cwd),
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
storage.close();
|
|
79
|
+
|
|
67
80
|
return {
|
|
68
81
|
cwd,
|
|
69
|
-
artifacts,
|
|
70
82
|
cleanup: () => rmSync(cwd, { recursive: true, force: true }),
|
|
71
83
|
};
|
|
72
84
|
}
|
|
@@ -112,8 +124,6 @@ describe("cli smoke — imports dinâmicos", () => {
|
|
|
112
124
|
"findings",
|
|
113
125
|
"summary",
|
|
114
126
|
"--json",
|
|
115
|
-
"--dir",
|
|
116
|
-
project.artifacts,
|
|
117
127
|
"--branch",
|
|
118
128
|
"main",
|
|
119
129
|
]);
|
|
@@ -132,8 +142,6 @@ describe("cli smoke — imports dinâmicos", () => {
|
|
|
132
142
|
"submit",
|
|
133
143
|
"--dry-run",
|
|
134
144
|
"--json",
|
|
135
|
-
"--dir",
|
|
136
|
-
project.artifacts,
|
|
137
145
|
"--branch",
|
|
138
146
|
"main",
|
|
139
147
|
]);
|
|
@@ -160,6 +168,19 @@ describe("cli smoke — imports dinâmicos", () => {
|
|
|
160
168
|
try {
|
|
161
169
|
const result = runCli(project.cwd, ["context", "--branch", "main"]);
|
|
162
170
|
expect(result.code).toBe(0);
|
|
171
|
+
|
|
172
|
+
// Auditoria: o brief do contexto fica persistido mesmo sem PR.
|
|
173
|
+
const storage = new SQLiteStorageProvider(join(project.cwd, "houndsman.db"));
|
|
174
|
+
try {
|
|
175
|
+
const repo = buildRepoIdentity(null, project.cwd);
|
|
176
|
+
const review = storage.findReview(repo.key, "main");
|
|
177
|
+
expect(review).not.toBeNull();
|
|
178
|
+
expect(
|
|
179
|
+
storage.readArtifact(review?.id ?? "", "context-brief.md")?.kind,
|
|
180
|
+
).toBe("context-brief");
|
|
181
|
+
} finally {
|
|
182
|
+
storage.close();
|
|
183
|
+
}
|
|
163
184
|
} finally {
|
|
164
185
|
project.cleanup();
|
|
165
186
|
}
|