@payconductor/houndsman 0.4.0 → 0.6.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 +35 -41
- package/assets/grammars/tree-sitter-javascript.wasm +0 -0
- package/assets/grammars/tree-sitter-tsx.wasm +0 -0
- package/assets/grammars/tree-sitter-typescript.wasm +0 -0
- package/assets/grammars/web-tree-sitter.wasm +0 -0
- package/package.json +11 -3
- package/src/cli/commands/command.ts +5 -0
- package/src/cli/commands/doctor-command.ts +116 -24
- package/src/cli/commands/hunt-command.ts +25 -0
- package/src/cli/commands/status-command.ts +46 -0
- package/src/cli/help.ts +18 -1
- package/src/cli/index.ts +13 -6
- package/src/core/ai/ai-client.ts +221 -0
- package/src/core/ai/ai-sdk-model-provider.ts +101 -0
- package/src/core/ai/errors.ts +36 -0
- package/src/core/ai/features/brief-enricher.ts +62 -0
- package/src/core/ai/features/classifier.ts +50 -0
- package/src/core/ai/features/context-summarizer.ts +31 -0
- package/src/core/ai/features/pre-analyzer.ts +51 -0
- package/src/core/ai/features/verifier.ts +45 -0
- package/src/core/ai/index.ts +56 -0
- package/src/core/ai/model-factory.ts +137 -0
- package/src/core/ai/prompts/base.ts +36 -0
- package/src/core/ai/prompts/brief.ts +87 -0
- package/src/core/ai/prompts/classification.ts +36 -0
- package/src/core/ai/prompts/context.ts +29 -0
- package/src/core/ai/prompts/pre-analysis.ts +49 -0
- package/src/core/ai/prompts/verify.ts +40 -0
- package/src/core/ai/schemas.ts +110 -0
- package/src/core/ai/types.ts +76 -0
- package/src/core/ai/usage-budget.ts +75 -0
- package/src/core/ai/usage-recorder.ts +79 -0
- package/src/core/assets.ts +16 -0
- package/src/core/ast/diff.ts +220 -0
- package/src/core/ast/impact.ts +127 -0
- package/src/core/ast/parser.ts +167 -0
- package/src/core/ast/service.ts +233 -0
- package/src/core/ast/symbols.ts +294 -0
- package/src/core/ast/types.ts +125 -0
- package/src/core/brief.ts +138 -0
- package/src/core/commands/findings.ts +120 -6
- package/src/core/commands/review.ts +2 -0
- package/src/core/commands/scaffold.ts +159 -10
- package/src/core/commands/status.ts +63 -0
- package/src/core/commands/update.ts +5 -3
- package/src/core/config.ts +85 -0
- package/src/core/defaults.ts +27 -0
- package/src/core/domain/watchdog.ts +43 -5
- package/src/core/engine/houndsman-engine.ts +16 -1
- package/src/core/engine/review-scope.ts +77 -13
- package/src/core/engine/review-session.ts +27 -2
- package/src/core/env.ts +101 -0
- package/src/core/file-classifier.ts +25 -4
- package/src/core/findings-state.ts +21 -4
- package/src/core/github-review.ts +620 -57
- package/src/core/jev/jev-client.ts +8 -7
- package/src/core/jev/jev-verify.ts +2 -1
- package/src/core/logging/index.ts +17 -0
- package/src/core/logging/logger.ts +187 -0
- package/src/core/orchestrator.ts +454 -16
- package/src/core/paths.ts +3 -0
- package/src/core/project-config.ts +123 -1
- package/src/core/registry-check.ts +148 -6
- package/src/core/renderers/manifest-writer.ts +33 -0
- package/src/core/renderers/resume-renderer.ts +151 -0
- package/src/core/report-builder.ts +7 -4
- package/src/core/rule-text.ts +97 -0
- package/src/core/run-status.ts +39 -5
- package/src/core/services/file-classifier.ts +10 -3
- package/src/core/services/verifier.ts +14 -3
- package/src/core/storage/file/file-storage.ts +2 -0
- package/src/core/storage/paths.ts +6 -5
- 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 +15 -0
- package/src/core/storage/work-area.ts +12 -0
- package/src/core/types.ts +37 -1
- package/src/core/verify.ts +17 -5
- package/src/harness/README.md +15 -0
- package/src/harness/agent/agent-context.ts +33 -0
- package/src/harness/agent/agent-errors.ts +30 -0
- package/src/harness/agent/agent-factory.ts +45 -0
- package/src/harness/agent/agent-reporter.ts +34 -0
- package/src/harness/agent/agent-result.ts +16 -0
- package/src/harness/agent/agent-runner.ts +32 -0
- package/src/harness/agent/agent-scheduler.ts +37 -0
- package/src/harness/agent/review-agent.ts +391 -0
- package/src/harness/config/harness-config.ts +84 -0
- package/src/harness/hunt/hunt-harness.ts +83 -0
- package/src/harness/hunt/hunt-options.ts +75 -0
- package/src/harness/hunt/hunt-result.ts +17 -0
- package/src/harness/hunt/hunt-session.ts +372 -0
- package/src/harness/hunt/review-planner.ts +98 -0
- package/src/harness/index.ts +36 -0
- package/src/harness/investigation/agent-state.ts +134 -0
- package/src/harness/investigation/evidence-verifier.ts +129 -0
- package/src/harness/investigation/evidence.ts +110 -0
- package/src/harness/investigation/finding-critic.ts +96 -0
- package/src/harness/investigation/investigation-planner.ts +118 -0
- package/src/harness/investigation/investigation-task.ts +10 -0
- package/src/harness/investigation/review-journal.ts +87 -0
- package/src/harness/investigation/rule-analyzer.ts +178 -0
- package/src/harness/investigation/rule-signals.ts +110 -0
- package/src/harness/prompts/agent-schemas.ts +57 -0
- package/src/harness/prompts/reviewer-prompt-builder.ts +101 -0
- package/src/harness/prompts/states/conclude-prompt.ts +16 -0
- package/src/harness/prompts/states/investigate-prompt.ts +37 -0
- package/src/harness/prompts/states/orient-prompt.ts +15 -0
- package/src/harness/prompts/states/verify-prompt.ts +22 -0
- package/src/harness/tools/agent-tool.ts +102 -0
- package/src/harness/tools/builtin/changed-symbols-tool.ts +48 -0
- package/src/harness/tools/builtin/doc-context-tool.ts +74 -0
- package/src/harness/tools/builtin/file-outline-tool.ts +66 -0
- package/src/harness/tools/builtin/find-references-tool.ts +66 -0
- package/src/harness/tools/builtin/find-symbol-tool.ts +91 -0
- package/src/harness/tools/builtin/helpers.ts +115 -0
- package/src/harness/tools/builtin/impact-tool.ts +48 -0
- package/src/harness/tools/builtin/imports-exports-tool.ts +53 -0
- package/src/harness/tools/builtin/index.ts +59 -0
- package/src/harness/tools/builtin/list-files-tool.ts +85 -0
- package/src/harness/tools/builtin/pr-context-tool.ts +41 -0
- package/src/harness/tools/builtin/read-file-range-tool.ts +73 -0
- package/src/harness/tools/builtin/rule-signals-tool.ts +36 -0
- package/src/harness/tools/builtin/search-code-tool.ts +65 -0
- package/src/harness/tools/builtin/symbol-context-tool.ts +106 -0
- package/src/harness/tools/builtin/tests-for-symbol-tool.ts +61 -0
- package/src/harness/tools/path-guard.ts +90 -0
- package/src/harness/tools/result-limiter.ts +49 -0
- package/src/harness/tools/secret-redactor.ts +54 -0
- package/src/harness/tools/tool-cache.ts +50 -0
- package/src/harness/tools/tool-call-budget.ts +60 -0
- package/src/harness/tools/tool-context.ts +5 -0
- package/src/harness/tools/tool-middleware.ts +105 -0
- package/src/harness/tools/tool-policy.ts +51 -0
- package/src/harness/tools/tool-registry.ts +112 -0
- package/src/harness/tools/tool-result.ts +15 -0
- package/src/harness/tools/toolbox.ts +36 -0
- package/src/harness/validation/finding-validator.ts +119 -0
- package/src/index.ts +46 -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} +61 -10
- package/src/server/client/utils/markdown.ts +319 -0
- package/src/server/client/{types.ts → utils/types.ts} +64 -0
- package/src/server/index.ts +66 -7
- package/src/server/run-repository.ts +9 -0
- package/src/skill/OUTPUT-FORMAT.md +12 -0
- package/src/skill/SETUP.md +6 -2
- package/src/skill/example-review.md +22 -14
- package/src/skill/hunt.md +45 -19
- package/src/skill/verify.md +3 -2
- package/src/tests/__snapshots__/baseline.test.ts.snap +126 -42
- package/src/tests/ai-core.test.ts +120 -0
- package/src/tests/ai-features.test.ts +183 -0
- package/src/tests/ast.test.ts +335 -0
- package/src/tests/baseline.test.ts +3 -0
- package/src/tests/code-highlight.test.ts +1 -1
- package/src/tests/env.test.ts +69 -0
- package/src/tests/fallback.test.ts +172 -0
- package/src/tests/findings-state.test.ts +50 -2
- package/src/tests/fixtures/fake-agent-tools.ts +69 -0
- package/src/tests/fixtures/fake-investigation.ts +64 -0
- package/src/tests/fixtures/fake-model-provider.ts +48 -0
- package/src/tests/github-review.test.ts +409 -60
- package/src/tests/harness-agent.test.ts +122 -0
- package/src/tests/harness-investigation.test.ts +206 -0
- package/src/tests/harness-tools.test.ts +148 -0
- package/src/tests/hunt-command.test.ts +212 -0
- package/src/tests/logger.test.ts +83 -0
- 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/rule-analyzer.test.ts +61 -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/tests/tool-contracts.test.ts +82 -0
- package/src/tests/verify.test.ts +1 -1
- 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/README.md
CHANGED
|
@@ -26,8 +26,13 @@ diferente das PRs. O motor só coordena a caçada — as regras são problema se
|
|
|
26
26
|
|
|
27
27
|
- **Bun** — runtime obrigatório.
|
|
28
28
|
- **git** — o diff usa `git diff <base>...<head>`.
|
|
29
|
-
-
|
|
30
|
-
|
|
29
|
+
- **gh** (Opcional) — Obtém contexto da PR e dispara comentários para o GitHub
|
|
30
|
+
- **`HOUNDSMAN_TYPE_SAFE_API_KEY`** (opcional) — pré-análise Jev.
|
|
31
|
+
- **`HOUNDSMAN_DEEP_SEEK_API_KEY`** (opcional) — IA própria do `hunt` nativo e das features de IA.
|
|
32
|
+
- **`HOUNDSMAN_OPENAI_API_KEY`** / **`HOUNDSMAN_ANTHROPIC_API_KEY`** (opcional) — apenas se
|
|
33
|
+
`ai.provider` for `openai`/`anthropic`.
|
|
34
|
+
|
|
35
|
+
> Coloque as chaves em um `.env` na raiz do projeto; o Bun carrega automaticamente.
|
|
31
36
|
|
|
32
37
|
### 2. Instale o CLI (global)
|
|
33
38
|
|
|
@@ -48,13 +53,14 @@ Cria a camada `.houndsman/`, que é **autocontida** (não instala nada no seu pr
|
|
|
48
53
|
| Caminho | Dono | O que é |
|
|
49
54
|
|---|---|---|
|
|
50
55
|
| `houndsman.config.ts` | você | Configuração do projeto (um objeto de dados) |
|
|
51
|
-
| `.houndsman/watchdogs/` | você | Suas regras — começa com
|
|
56
|
+
| `.houndsman/watchdogs/` | você | Suas regras — começa com um watchdog `default` (fallback) e um `example` |
|
|
52
57
|
| `.houndsman/exclusions.ts` | você | Arquivos que nunca entram na revisão |
|
|
53
58
|
| `.houndsman/doc-mappings.ts` | você | path → documentação de contexto |
|
|
54
59
|
| `.houndsman/core/**` | auto | Tipos + docs do skill (sincronizados por `houndsman update`) |
|
|
55
60
|
|
|
56
|
-
> O `setup` já cria a camada inicial com um watchdog
|
|
57
|
-
> regras reais do seu projeto (ou use
|
|
61
|
+
> O `setup` já cria a camada inicial com um watchdog `default` (fallback, cobre arquivos sem
|
|
62
|
+
> domínio) e um `example` — troque o exemplo pelas regras reais do seu projeto (ou use
|
|
63
|
+
> `/houndsman train`).
|
|
58
64
|
|
|
59
65
|
### 4. Escreva suas regras
|
|
60
66
|
|
|
@@ -78,6 +84,20 @@ O skill orquestra tudo e persiste o relatório no storage global (SQLite em
|
|
|
78
84
|
`<HOUNDSMAN_HOME>/houndsman.db`); a UI lê os artefatos direto do storage. Não precisa rodar
|
|
79
85
|
nenhum script à mão.
|
|
80
86
|
|
|
87
|
+
### 7. Revisão nativa (opcional) — `houndsman hunt`
|
|
88
|
+
|
|
89
|
+
Além do skill, o houndsman tem um **harness próprio**: o `hunt` roda o agente de revisão
|
|
90
|
+
dentro do CLI, com tools read-only, investigação por evidência e a mesma saída `review.md`.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
bun run houndsman hunt --dry-run # inspeciona watchdogs/arquivos, sem IA
|
|
94
|
+
HOUNDSMAN_DEEP_SEEK_API_KEY=... bun run houndsman hunt
|
|
95
|
+
bun run houndsman hunt --resume # preserva findings válidos do staging
|
|
96
|
+
bun run houndsman hunt --fail-on-blocking
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Detalhes (catálogo de tools, máquina de estados, custos e segurança): [`docs/HARNESS.md`](docs/HARNESS.md).
|
|
100
|
+
|
|
81
101
|
---
|
|
82
102
|
|
|
83
103
|
## O fluxo de trabalho
|
|
@@ -95,62 +115,38 @@ nenhum script à mão.
|
|
|
95
115
|
|
|
96
116
|
O skill tem **4 capabilities**, todas em `/houndsman`:
|
|
97
117
|
|
|
98
|
-
- **`hunt`** — revisa a PR
|
|
118
|
+
- **`hunt`** — revisa a PR.
|
|
99
119
|
- **`train`** — mantém/atualiza as regras dos watchdogs.
|
|
100
|
-
- **`fix`** — corrige os findings marcados
|
|
101
|
-
- **`verify`** — confere se as violações foram resolvidas
|
|
120
|
+
- **`fix`** — corrige os findings marcados.
|
|
121
|
+
- **`verify`** — confere se as violações foram resolvidas, sem re-revisar.
|
|
102
122
|
|
|
103
|
-
- Revisão normal: `/houndsman` ou `/houndsman hunt` (a branch é detectada; base default `main`).
|
|
104
123
|
- Revisar outra branch: `/houndsman hunt feature/minha-branch`.
|
|
105
|
-
- Validar
|
|
106
|
-
- Manual (sem skill): `houndsman context` → `houndsman --base main` → *(subagentes)* →
|
|
107
|
-
`houndsman --finalize`.
|
|
108
|
-
- CI: `houndsman --finalize --fail-on-blocking` (exit 1 se houver bloqueantes).
|
|
124
|
+
- Validar na UI: `houndsman server --open` — marque os findings a executar (`execute`).
|
|
109
125
|
|
|
110
126
|
---
|
|
111
127
|
|
|
112
|
-
##
|
|
128
|
+
## Validando os findings (opcional)
|
|
113
129
|
|
|
114
|
-
|
|
115
|
-
no storage global. Você decide o que fazer com cada um — pela
|
|
116
|
-
**UI local** ou pelo **agente** (auto-fix) — sem abrir o JSON à mão:
|
|
130
|
+
Você decide o que fazer com cada finding pela **UI local**, informando se quer `Executar` ou `Ignorar`
|
|
117
131
|
|
|
118
132
|
```bash
|
|
119
133
|
bun run houndsman server --open # UI: triar, comentar, marcar feito, ler artefatos
|
|
120
134
|
```
|
|
121
135
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
**Auto-fix pelo agente** — as linhas mudam depois do fix, e o motor reancora por conteúdo:
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
bun run houndsman findings batch --json # grupo atual (agrupamento automático) + outros
|
|
129
|
-
# ... o agente corrige apenas esse grupo ...
|
|
130
|
-
bun run houndsman findings sync # reancora por conteúdo
|
|
131
|
-
bun run houndsman findings done <id>
|
|
132
|
-
bun run houndsman findings verify # confere o que resolveu (barato, sem re-revisar)
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
> A partir da 2ª revisão o `hunt` é **incremental**: revisa só os arquivos que mudaram desde
|
|
136
|
-
> o último `--finalize` e **carrega** os findings dos arquivos inalterados. `--full` força
|
|
137
|
-
> o review completo.
|
|
138
|
-
|
|
139
|
-
> A identidade do finding é o **conteúdo** (fingerprint), não a linha: itens deslocados
|
|
140
|
-
> pelo fix não se perdem. O estado mora no storage global (`<HOUNDSMAN_HOME>/houndsman.db`).
|
|
136
|
+
> A partir da 2ª revisão o `hunt` é **incremental**: revisa apenas os arquivos que mudaram desde
|
|
137
|
+
> a última revisão. `--full` força o review completo.
|
|
141
138
|
|
|
142
139
|
---
|
|
143
140
|
|
|
144
141
|
## Mantendo a configuração
|
|
145
142
|
|
|
146
143
|
- **Persistência** → por default, SQLite global em `<HOUNDSMAN_HOME>/houndsman.db`
|
|
147
|
-
(`HOUNDSMAN_HOME` sobrescreve o diretório de dados).
|
|
148
|
-
troque só o provider — a interface `StorageProvider` é a mesma:
|
|
144
|
+
(`HOUNDSMAN_HOME` sobrescreve o diretório de dados). Caso queira storage base em arquivos, na config, troque provider para `file`:
|
|
149
145
|
|
|
150
146
|
```ts
|
|
151
147
|
export const houndsmanConfig = {
|
|
152
148
|
// ...
|
|
153
|
-
storage: { provider: "file", path: "./houndsman
|
|
149
|
+
storage: { provider: "file", path: "./houndsman/runs" },
|
|
154
150
|
};
|
|
155
151
|
```
|
|
156
152
|
|
|
@@ -165,8 +161,6 @@ bun run houndsman findings verify # confere o que resolveu (barato, se
|
|
|
165
161
|
|
|
166
162
|
O `update` reescreve **só** `.houndsman/core/**` e `.houndsman/.gitignore`. Seus arquivos
|
|
167
163
|
(`watchdogs/`, `exclusions.ts`, `doc-mappings.ts`, `houndsman.config.ts`) nunca são tocados.
|
|
168
|
-
- **Recomeçar a camada do zero** → `houndsman setup --force` (sobrescreve só os arquivos do
|
|
169
|
-
usuário; sem `--force` o `setup` é idempotente).
|
|
170
164
|
|
|
171
165
|
---
|
|
172
166
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payconductor/houndsman",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Orquestrador de revisão de Pull Requests: classifica o diff, despacha watchdogs especializados por domínio e consolida o relatório.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,14 +51,22 @@
|
|
|
51
51
|
"ci:check:fix": "biome check --fix",
|
|
52
52
|
"ci:fix": "bun run ci:check:fix && bun run ci:format:fix",
|
|
53
53
|
"ci:format": "biome format",
|
|
54
|
-
"ci:format:fix": "biome format --fix"
|
|
54
|
+
"ci:format:fix": "biome format --fix",
|
|
55
|
+
"grammars:sync": "bun run ./scripts/sync-grammars.ts"
|
|
55
56
|
},
|
|
56
57
|
"dependencies": {
|
|
58
|
+
"@ai-sdk/deepseek": "3.0.67",
|
|
59
|
+
"ai": "7.0.143",
|
|
57
60
|
"axios": "^1.12.2",
|
|
58
|
-
"
|
|
61
|
+
"chalk": "^6.0.1",
|
|
62
|
+
"ora": "^9.4.1",
|
|
63
|
+
"preact": "^11.0.1",
|
|
64
|
+
"web-tree-sitter": "0.27.1",
|
|
65
|
+
"zod": "4.6.7"
|
|
59
66
|
},
|
|
60
67
|
"devDependencies": {
|
|
61
68
|
"@biomejs/biome": "2.2.4",
|
|
69
|
+
"@vscode/tree-sitter-wasm": "0.3.1",
|
|
62
70
|
"bun-types": "^1.3.14",
|
|
63
71
|
"typescript": "^7.0.2"
|
|
64
72
|
}
|
|
@@ -6,13 +6,18 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { HoundsmanEngine } from "../../core/engine/houndsman-engine";
|
|
9
|
+
import type { LoggerLike } from "../../core/logging";
|
|
9
10
|
|
|
10
11
|
/** Contexto de execução de um comando. */
|
|
11
12
|
export type CliContext = {
|
|
12
13
|
cwd: string;
|
|
13
14
|
/** Carrega a config do projeto e devolve o engine (lazy). */
|
|
14
15
|
engine: () => Promise<HoundsmanEngine>;
|
|
16
|
+
/** Logger único do CLI (cores/spinner/progresso). */
|
|
17
|
+
logger: LoggerLike;
|
|
18
|
+
/** @deprecated use `logger.info`. */
|
|
15
19
|
out: (message: string) => void;
|
|
20
|
+
/** @deprecated use `logger.error`. */
|
|
16
21
|
err: (message: string) => void;
|
|
17
22
|
};
|
|
18
23
|
|
|
@@ -1,60 +1,152 @@
|
|
|
1
|
-
/** Comando `doctor` (alias `check`) — valida config, ambiente e
|
|
1
|
+
/** Comando `doctor` (alias `check`) — valida config, ambiente e credenciais. */
|
|
2
2
|
|
|
3
|
+
import type { AiClient } from "../../core/ai/ai-client";
|
|
4
|
+
import { ArgParser } from "../../core/arg-parser";
|
|
5
|
+
import type { AiProviderKind } from "../../core/config";
|
|
6
|
+
import { ENV_NAMES, env } from "../../core/env";
|
|
3
7
|
import { checkRegistry } from "../../core/registry-check";
|
|
4
8
|
import { storageLocation } from "../../core/storage/paths";
|
|
5
9
|
import type { CliContext, Command } from "./command";
|
|
6
10
|
|
|
11
|
+
/** Chave de ambiente esperada por provider. */
|
|
12
|
+
function keyFor(provider: AiProviderKind): string | undefined {
|
|
13
|
+
switch (provider) {
|
|
14
|
+
case "deepseek":
|
|
15
|
+
return env.deepSeekApiKey;
|
|
16
|
+
case "openai":
|
|
17
|
+
return env.openAiApiKey;
|
|
18
|
+
case "anthropic":
|
|
19
|
+
return env.anthropicApiKey;
|
|
20
|
+
default:
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Nome da variável de ambiente por provider. */
|
|
26
|
+
function envNameFor(provider: AiProviderKind): string {
|
|
27
|
+
switch (provider) {
|
|
28
|
+
case "openai":
|
|
29
|
+
return ENV_NAMES.openAiApiKey;
|
|
30
|
+
case "anthropic":
|
|
31
|
+
return ENV_NAMES.anthropicApiKey;
|
|
32
|
+
default:
|
|
33
|
+
return ENV_NAMES.deepSeekApiKey;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
7
37
|
export class DoctorCommand implements Command {
|
|
8
38
|
readonly name = "doctor";
|
|
9
39
|
|
|
10
|
-
async run(
|
|
40
|
+
async run(args: string[], context: CliContext): Promise<number> {
|
|
41
|
+
const parser = ArgParser.from(args);
|
|
42
|
+
const noPing = parser.has("--no-ping");
|
|
11
43
|
const engine = await context.engine();
|
|
12
|
-
|
|
44
|
+
const { logger } = context;
|
|
45
|
+
|
|
46
|
+
logger.step("Validando configuração do projeto...");
|
|
13
47
|
const { problems, warnings, jevQuestions } = checkRegistry(engine.config);
|
|
14
48
|
|
|
15
49
|
if (problems.length > 0) {
|
|
16
|
-
|
|
17
|
-
for (const problem of problems)
|
|
50
|
+
logger.error("Problemas na configuração de revisão:");
|
|
51
|
+
for (const problem of problems) logger.error(` - ${problem}`);
|
|
18
52
|
return 1;
|
|
19
53
|
}
|
|
20
|
-
|
|
21
|
-
|
|
54
|
+
logger.success(
|
|
55
|
+
`${engine.config.watchdogIds.length} watchdogs, ${jevQuestions} pergunta(s) Jev.`,
|
|
22
56
|
);
|
|
23
57
|
|
|
24
58
|
if (warnings.length > 0) {
|
|
25
|
-
|
|
26
|
-
for (const warning of warnings)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
59
|
+
logger.warn(`${warnings.length} aviso(s) (não bloqueantes):`);
|
|
60
|
+
for (const warning of warnings) logger.bullet(warning);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
logger.step("Ambiente:");
|
|
64
|
+
logger.bullet(`${Bun.which("git") ? "✅" : "❌"} git`);
|
|
65
|
+
logger.bullet(`${Bun.which("gh") ? "✅" : "⚠️ "} gh (opcional — contexto da PR)`);
|
|
66
|
+
const ast = await engine.ast.check();
|
|
67
|
+
logger.bullet(`${ast.ok ? "✅" : "⚠️ "} AST: ${ast.detail}`);
|
|
68
|
+
|
|
69
|
+
const provider = engine.ai.projectConfig.aiProvider;
|
|
70
|
+
const key = keyFor(provider);
|
|
71
|
+
logger.bullet(
|
|
72
|
+
`${key ? "✅" : "⚠️ "} ${envNameFor(provider)} (provider ${provider})`,
|
|
73
|
+
);
|
|
74
|
+
logger.bullet(
|
|
75
|
+
`IA: provider=${provider}, model=${engine.ai.modelId}, ` +
|
|
76
|
+
`disponível=${engine.ai.available ? "sim" : "não"}`,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
await this.reportAiCredential(context, engine.ai, provider, key, noPing);
|
|
80
|
+
await this.reportJevCredential(context, noPing);
|
|
81
|
+
|
|
82
|
+
logger.step("Persistência:");
|
|
83
|
+
logger.bullet(`provider: ${engine.storage.kind}`);
|
|
84
|
+
logger.bullet(storageLocation(engine.config));
|
|
85
|
+
logger.bullet(`repo: ${engine.repo.key}`);
|
|
37
86
|
return 0;
|
|
38
87
|
}
|
|
39
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Valida a credencial do provider de IA com um ping mínimo. Fail-open:
|
|
91
|
+
* problemas de credencial são diagnóstico e não alteram o exit code.
|
|
92
|
+
*/
|
|
93
|
+
private async reportAiCredential(
|
|
94
|
+
context: CliContext,
|
|
95
|
+
ai: AiClient,
|
|
96
|
+
provider: AiProviderKind,
|
|
97
|
+
key: string | undefined,
|
|
98
|
+
noPing: boolean,
|
|
99
|
+
): Promise<void> {
|
|
100
|
+
if (!key) {
|
|
101
|
+
context.logger.warn(
|
|
102
|
+
`IA: ${provider} sem credencial — features de IA ficam fail-open.`,
|
|
103
|
+
);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (noPing) {
|
|
107
|
+
context.logger.info("IA: validação pulada (--no-ping).");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
context.logger.step("Validando credencial de IA...");
|
|
111
|
+
const check = await ai.validateCredential();
|
|
112
|
+
if (check.ok) {
|
|
113
|
+
context.logger.success(
|
|
114
|
+
`IA: credencial válida (modelo ${check.model ?? provider}).`,
|
|
115
|
+
);
|
|
116
|
+
} else if (check.kind === "invalid") {
|
|
117
|
+
context.logger.error(`IA: credencial inválida — ${check.detail}`);
|
|
118
|
+
} else {
|
|
119
|
+
context.logger.warn(`IA: não foi possível validar — ${check.detail}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
40
123
|
/**
|
|
41
124
|
* Reporta o estado da credencial Jev: presença e validade (chamada mínima).
|
|
42
125
|
* Fail-open — nunca altera o exit code (`doctor` é diagnóstico).
|
|
43
126
|
*/
|
|
44
|
-
private async reportJevCredential(
|
|
127
|
+
private async reportJevCredential(
|
|
128
|
+
context: CliContext,
|
|
129
|
+
noPing: boolean,
|
|
130
|
+
): Promise<void> {
|
|
45
131
|
const { JevClient } = await import("../../core/jev");
|
|
46
132
|
const client = new JevClient();
|
|
47
133
|
if (!client.isConfigured()) {
|
|
48
|
-
context.
|
|
134
|
+
context.logger.warn(
|
|
135
|
+
`${ENV_NAMES.typeSafeApiKey} (opcional — pré-análise Jev)`,
|
|
136
|
+
);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (noPing) {
|
|
140
|
+
context.logger.info("Jev: validação pulada (--no-ping).");
|
|
49
141
|
return;
|
|
50
142
|
}
|
|
51
143
|
const check = await client.validateCredential();
|
|
52
144
|
if (check.ok) {
|
|
53
|
-
context.
|
|
145
|
+
context.logger.success(`Jev: credencial válida (modelo ${check.model}).`);
|
|
54
146
|
} else if (check.kind === "invalid") {
|
|
55
|
-
context.
|
|
147
|
+
context.logger.error(`Jev: credencial inválida — ${check.detail}`);
|
|
56
148
|
} else {
|
|
57
|
-
context.
|
|
149
|
+
context.logger.warn(`Jev: não foi possível validar — ${check.detail}`);
|
|
58
150
|
}
|
|
59
151
|
}
|
|
60
152
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Comando `hunt` — revisão nativa com IA (harness próprio). */
|
|
2
|
+
|
|
3
|
+
import { HuntHarness } from "../../harness";
|
|
4
|
+
import { parseHuntArgs } from "../../harness/hunt/hunt-options";
|
|
5
|
+
import type { CliContext, Command } from "./command";
|
|
6
|
+
|
|
7
|
+
/** Executa o harness nativo. */
|
|
8
|
+
export class HuntCommand implements Command {
|
|
9
|
+
readonly name = "hunt";
|
|
10
|
+
|
|
11
|
+
async run(args: string[], context: CliContext): Promise<number> {
|
|
12
|
+
const options = parseHuntArgs(args);
|
|
13
|
+
const engine = await context.engine();
|
|
14
|
+
try {
|
|
15
|
+
const harness = await HuntHarness.from(engine, options, context.logger);
|
|
16
|
+
const result = await harness.run();
|
|
17
|
+
for (const message of result.messages) context.logger.info(message);
|
|
18
|
+
if (result.status === "failed") return 1;
|
|
19
|
+
if (options.failOnBlocking && result.blockers > 0) return 1;
|
|
20
|
+
return 0;
|
|
21
|
+
} finally {
|
|
22
|
+
engine.close();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Comando `status` — mostra o status da run ou renova o heartbeat (`status ping`). */
|
|
2
|
+
|
|
3
|
+
import { pingStatus, readStatus } from "../../core/commands/status";
|
|
4
|
+
import { ArgParser } from "../arg-parser";
|
|
5
|
+
import type { CliContext, Command } from "./command";
|
|
6
|
+
|
|
7
|
+
export class StatusCommand implements Command {
|
|
8
|
+
readonly name = "status";
|
|
9
|
+
|
|
10
|
+
async run(args: string[], context: CliContext): Promise<number> {
|
|
11
|
+
const parser = ArgParser.from(args);
|
|
12
|
+
const action = parser.command();
|
|
13
|
+
const branch = parser.value("--branch");
|
|
14
|
+
|
|
15
|
+
if (action && action !== "ping" && action !== "show") {
|
|
16
|
+
context.err(
|
|
17
|
+
`Ação desconhecida em 'status': '${action}'. Use 'status' ou 'status ping'.`,
|
|
18
|
+
);
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const engine = await context.engine();
|
|
23
|
+
const result =
|
|
24
|
+
action === "ping"
|
|
25
|
+
? pingStatus(engine.storage, engine.repo, branch)
|
|
26
|
+
: readStatus(engine.storage, engine.repo, branch);
|
|
27
|
+
|
|
28
|
+
if (action === "ping") {
|
|
29
|
+
context.out(
|
|
30
|
+
result.heartbeatAt
|
|
31
|
+
? `Heartbeat atualizado — ${result.branch} · ${result.status ?? "sem status"} · ${result.heartbeatAt}`
|
|
32
|
+
: `Sem execução ativa — ${result.branch} · ${result.status ?? "sem status"}.`,
|
|
33
|
+
);
|
|
34
|
+
} else {
|
|
35
|
+
context.out(
|
|
36
|
+
[
|
|
37
|
+
`Branch: ${result.branch}`,
|
|
38
|
+
`Status: ${result.status ?? "—"}`,
|
|
39
|
+
`Heartbeat: ${result.heartbeatAt ?? "—"}`,
|
|
40
|
+
`Atualizado: ${result.updatedAt}`,
|
|
41
|
+
].join("\n"),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/cli/help.ts
CHANGED
|
@@ -5,7 +5,9 @@ Comandos:
|
|
|
5
5
|
(nenhum) Prepara a revisão (classification + briefs + manifesto)
|
|
6
6
|
--finalize Consolida os findings em review.md
|
|
7
7
|
context Coleta o contexto da PR via gh (Passo 0)
|
|
8
|
+
hunt Revisão nativa com IA (harness próprio): prepare + agentes + finalize
|
|
8
9
|
findings <sub> Triagem/correção dos findings (list · show · set · done · reset · sync · summary · batch · verify · submit · path)
|
|
10
|
+
status [ping] Mostra o status da run (ping renova o heartbeat da execução)
|
|
9
11
|
server Sobe a UI local de triagem (--port, --host, --open)
|
|
10
12
|
setup Cria a configuração inicial do projeto (config + skills)
|
|
11
13
|
update Sincroniza os arquivos auto-gerados (.houndsman/core)
|
|
@@ -21,8 +23,16 @@ Flags:
|
|
|
21
23
|
--clean Descarta findings brutos do staging anterior
|
|
22
24
|
--full Força review completo (ignora o modo incremental)
|
|
23
25
|
--no-jev Desativa a pré-análise Jev
|
|
26
|
+
--no-ast Desativa a análise estrutural (tree-sitter)
|
|
27
|
+
--no-ai Desativa as features de IA do fluxo normal
|
|
24
28
|
--fail-on-blocking Exit 1 se houver bloqueantes (com --finalize)
|
|
25
29
|
|
|
30
|
+
Flags do hunt:
|
|
31
|
+
--base <b> · --head <ref> · --branch <nome> · --watchdog <id>
|
|
32
|
+
--full · --clean · --no-ast · --with-jev
|
|
33
|
+
--resume · --allow-partial · --dry-run · --fail-on-blocking
|
|
34
|
+
--max-calls <n> · --concurrency <n> · --provider <p> · --model <m>
|
|
35
|
+
|
|
26
36
|
Flags de findings:
|
|
27
37
|
--decision <d> pending | execute | ignore
|
|
28
38
|
--exec <e> pending | in_progress | done | stale
|
|
@@ -40,8 +50,15 @@ Flags de findings:
|
|
|
40
50
|
--fail-on-persisting (verify) Exit 1 se algum blocking persistir
|
|
41
51
|
--include-sent (submit) Reenvia findings já enviados à PR
|
|
42
52
|
--dry-run (submit) Só a prévia do envio, sem postar
|
|
53
|
+
--no-ping (doctor) Pula a validação de rede das credenciais
|
|
43
54
|
|
|
44
55
|
Persistência:
|
|
45
56
|
Runs, findings e artefatos ficam no storage global (SQLite em
|
|
46
57
|
<HOUNDSMAN_HOME>/houndsman.db). Use storage.provider: "file" na config para
|
|
47
|
-
voltar ao backend de arquivos. Nada é gravado em .houndsman/.runs
|
|
58
|
+
voltar ao backend de arquivos. Nada é gravado em .houndsman/.runs.
|
|
59
|
+
|
|
60
|
+
Ambiente (o Bun carrega .env automaticamente):
|
|
61
|
+
HOUNDSMAN_TYPE_SAFE_API_KEY Pré-análise Jev (opcional)
|
|
62
|
+
HOUNDSMAN_DEEP_SEEK_API_KEY IA (provider deepseek, opcional)
|
|
63
|
+
HOUNDSMAN_OPENAI_API_KEY IA (provider openai, opcional)
|
|
64
|
+
HOUNDSMAN_ANTHROPIC_API_KEY IA (provider anthropic, opcional)`;
|
package/src/cli/index.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* --finalize Passo 3 — consolidar os findings em review.md
|
|
16
16
|
* context Passo 0 — coletar o contexto da PR via `gh`
|
|
17
17
|
* findings <sub> Triagem/correção dos findings (list/set/done/sync/batch/verify/submit/...)
|
|
18
|
+
* status [ping] Mostra o status da run; `ping` renova o heartbeat da execução
|
|
18
19
|
* server Sobe a UI local de triagem (Bun.serve)
|
|
19
20
|
* setup Cria a configuração inicial do projeto (config + skills)
|
|
20
21
|
* update Sincroniza os arquivos auto-gerados (.houndsman/core)
|
|
@@ -23,21 +24,24 @@
|
|
|
23
24
|
* watchdog <id> Contexto de um watchdog (`--rules`, `--json`)
|
|
24
25
|
*
|
|
25
26
|
* Flags: `--base <b>` · `--head <ref>` · `--branch <b>` · `--config <path>` ·
|
|
26
|
-
* `--clean` · `--full` · `--no-jev` · `--fail-on-blocking`.
|
|
27
|
+
* `--clean` · `--full` · `--no-jev` · `--no-ast` · `--fail-on-blocking`.
|
|
27
28
|
*/
|
|
28
29
|
|
|
29
30
|
import { loadConfig } from "../core/config-loader";
|
|
30
31
|
import { HoundsmanEngine } from "../core/engine/houndsman-engine";
|
|
32
|
+
import { logger } from "../core/logging";
|
|
31
33
|
import { ArgParser } from "./arg-parser";
|
|
32
34
|
import type { CliContext } from "./commands/command";
|
|
33
35
|
import { ContextCommand } from "./commands/context-command";
|
|
34
36
|
import { DoctorCommand } from "./commands/doctor-command";
|
|
35
37
|
import { FindingsCommand } from "./commands/findings-command";
|
|
36
38
|
import { HelpCommand } from "./commands/help-command";
|
|
39
|
+
import { HuntCommand } from "./commands/hunt-command";
|
|
37
40
|
import { CommandRegistry } from "./commands/registry";
|
|
38
41
|
import { ReviewCommand } from "./commands/review-command";
|
|
39
42
|
import { ServerCommand } from "./commands/server-command";
|
|
40
43
|
import { SetupCommand, TestCommand, UpdateCommand } from "./commands/setup-command";
|
|
44
|
+
import { StatusCommand } from "./commands/status-command";
|
|
41
45
|
import { WatchdogCommand } from "./commands/watchdog-command";
|
|
42
46
|
import { HELP } from "./help";
|
|
43
47
|
|
|
@@ -51,8 +55,10 @@ function buildRegistry(): CommandRegistry {
|
|
|
51
55
|
return new CommandRegistry()
|
|
52
56
|
.register(new ReviewCommand(), { fallback: true })
|
|
53
57
|
.register(new HelpCommand())
|
|
58
|
+
.register(new HuntCommand())
|
|
54
59
|
.register(new ContextCommand())
|
|
55
60
|
.register(new FindingsCommand())
|
|
61
|
+
.register(new StatusCommand())
|
|
56
62
|
.register(new WatchdogCommand())
|
|
57
63
|
.register(new ServerCommand())
|
|
58
64
|
.register(new SetupCommand())
|
|
@@ -74,15 +80,16 @@ export async function main(): Promise<void> {
|
|
|
74
80
|
const command = registry.resolve(commandName);
|
|
75
81
|
|
|
76
82
|
if (!command) {
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
logger.error(`Subcomando desconhecido: '${commandName}'.\n`);
|
|
84
|
+
logger.info(HELP);
|
|
79
85
|
process.exit(1);
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
const context: CliContext = {
|
|
83
89
|
cwd: process.cwd(),
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
logger,
|
|
91
|
+
out: (message) => logger.info(message),
|
|
92
|
+
err: (message) => logger.error(message),
|
|
86
93
|
engine: async () => {
|
|
87
94
|
const { config } = await loadConfig({
|
|
88
95
|
configPath: parser.value("--config"),
|
|
@@ -97,7 +104,7 @@ export async function main(): Promise<void> {
|
|
|
97
104
|
|
|
98
105
|
if (import.meta.main) {
|
|
99
106
|
await main().catch((error) => {
|
|
100
|
-
|
|
107
|
+
logger.error(error instanceof Error ? error.message : String(error));
|
|
101
108
|
process.exit(1);
|
|
102
109
|
});
|
|
103
110
|
}
|