@payconductor/houndsman 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +6 -1
  2. package/package.json +1 -1
  3. package/src/cli/commands/compare-command.ts +112 -0
  4. package/src/cli/commands/context-command.ts +1 -0
  5. package/src/cli/commands/findings-command.ts +11 -1
  6. package/src/cli/commands/hunt-command.ts +20 -3
  7. package/src/cli/commands/review-command.ts +38 -0
  8. package/src/cli/commands/status-command.ts +3 -2
  9. package/src/cli/help.ts +3 -1
  10. package/src/cli/index.ts +3 -1
  11. package/src/cli/progress.ts +7 -2
  12. package/src/core/ai/ai-client.ts +38 -2
  13. package/src/core/ai/features/brief-enricher.ts +1 -1
  14. package/src/core/ai/features/business-analyzer.ts +61 -0
  15. package/src/core/ai/features/business-summarizer.ts +44 -0
  16. package/src/core/ai/index.ts +8 -0
  17. package/src/core/ai/prompts/brief.ts +33 -7
  18. package/src/core/ai/prompts/business.ts +76 -0
  19. package/src/core/ai/provider-capabilities.ts +12 -0
  20. package/src/core/ai/schemas.ts +70 -2
  21. package/src/core/ai/usage-recorder.ts +53 -1
  22. package/src/core/brief.ts +246 -12
  23. package/src/core/builtins.ts +108 -0
  24. package/src/core/business-summary.ts +583 -0
  25. package/src/core/commands/compare.ts +233 -0
  26. package/src/core/commands/context.ts +6 -2
  27. package/src/core/commands/findings.ts +38 -30
  28. package/src/core/commands/review.ts +6 -0
  29. package/src/core/commands/scaffold.ts +27 -0
  30. package/src/core/commands/status.ts +9 -4
  31. package/src/core/compare.ts +243 -0
  32. package/src/core/concurrency.ts +30 -0
  33. package/src/core/config.ts +31 -3
  34. package/src/core/defaults.ts +50 -4
  35. package/src/core/domain/budget.ts +62 -4
  36. package/src/core/domain/classification.ts +12 -2
  37. package/src/core/engine/carryover.ts +4 -0
  38. package/src/core/engine/finalize-pipeline.ts +347 -0
  39. package/src/core/engine/prepare-pipeline.ts +1294 -0
  40. package/src/core/engine/review-context.ts +51 -0
  41. package/src/core/engine/review-options.ts +47 -0
  42. package/src/core/engine/review-scope.ts +14 -1
  43. package/src/core/engine/review-session.ts +11 -13
  44. package/src/core/engine/run-artifacts.ts +196 -0
  45. package/src/core/engine/run-format.ts +4 -0
  46. package/src/core/findings-state.ts +13 -1
  47. package/src/core/github-review.ts +5 -2
  48. package/src/core/hunt/hunt-runner.ts +90 -0
  49. package/src/core/hunt/index.ts +2 -0
  50. package/src/core/hunt/maki-intent-prompt.ts +50 -0
  51. package/src/core/hunt/maki-prompt.ts +13 -1
  52. package/src/core/hunt/pool.ts +4 -26
  53. package/src/core/logging/index.ts +6 -1
  54. package/src/core/logging/progress.ts +85 -1
  55. package/src/core/orchestrator.ts +33 -1289
  56. package/src/core/paths.ts +6 -3
  57. package/src/core/pr-context.ts +2 -4
  58. package/src/core/prepare-report.ts +61 -0
  59. package/src/core/project-config.ts +8 -1
  60. package/src/core/propagation.ts +127 -0
  61. package/src/core/registry-check.ts +60 -21
  62. package/src/core/renderers/manifest-writer.ts +13 -0
  63. package/src/core/renderers/resume-renderer.ts +151 -11
  64. package/src/core/report-builder.ts +108 -278
  65. package/src/core/storage/file/file-storage.ts +7 -3
  66. package/src/core/storage/sqlite/mappers.ts +10 -0
  67. package/src/core/storage/sqlite/schema.ts +104 -23
  68. package/src/core/storage/sqlite/sqlite-storage.ts +22 -9
  69. package/src/core/storage/types.ts +16 -4
  70. package/src/core/storage/work-area.ts +59 -1
  71. package/src/core/types.ts +31 -0
  72. package/src/core/watchdog-registry.ts +21 -3
  73. package/src/index.ts +29 -4
  74. package/src/server/client/ArtifactsPanel.tsx +4 -2
  75. package/src/server/client/FindingCard.tsx +21 -1
  76. package/src/server/client/RunsSection.tsx +5 -0
  77. package/src/server/client/utils/constants.ts +21 -11
  78. package/src/server/client/utils/markdown.ts +1 -1
  79. package/src/server/client/utils/types.ts +14 -2
  80. package/src/server/run-repository.ts +35 -14
  81. package/src/server/styles.css +2 -0
  82. package/src/skill/OUTPUT-FORMAT.md +36 -10
  83. package/src/skill/README.md +18 -14
  84. package/src/skill/SETUP.md +1 -0
  85. package/src/skill/SKILL.md +13 -4
  86. package/src/skill/example-resume.md +43 -0
  87. package/src/skill/hunt.md +145 -58
  88. package/src/tests/__snapshots__/baseline.test.ts.snap +187 -178
  89. package/src/tests/ai-core.test.ts +46 -0
  90. package/src/tests/ai-features.test.ts +95 -4
  91. package/src/tests/baseline.test.ts +29 -121
  92. package/src/tests/brief-scope.test.ts +250 -0
  93. package/src/tests/business-summary.test.ts +258 -0
  94. package/src/tests/compare.test.ts +233 -0
  95. package/src/tests/fixtures/fake-model-provider.ts +2 -1
  96. package/src/tests/hunt-maki.test.ts +58 -0
  97. package/src/tests/markdown.test.ts +3 -3
  98. package/src/tests/progress.test.ts +29 -1
  99. package/src/tests/propagation.test.ts +119 -0
  100. package/src/tests/review-resume.test.ts +60 -1
  101. package/src/tests/review-scope.test.ts +38 -29
  102. package/src/tests/review-scripts.test.ts +106 -218
  103. package/src/tests/server.test.ts +7 -4
  104. package/src/tests/storage.test.ts +194 -9
  105. package/src/core/renderers/report-renderer.ts +0 -66
  106. package/src/skill/example-review.md +0 -66
@@ -40,11 +40,11 @@ src/alpha/**
40
40
  - \`src/alpha/a.ts\` (modified, +3/−1)
41
41
  - \`src/alpha/b.ts\` (added, +2/−0)
42
42
 
43
- ## Fora do orçamento (NÃO revisar)
43
+ ## Fora do recorte (NÃO revisar)
44
44
 
45
- > 1 arquivo(s) do seu domínio ficaram fora do orçamento
46
- > (menor churn). Não os leia: registre-os em \`skipped\` com o motivo
47
- > "budget" para que a cobertura do relatório fique honesta.
45
+ > 1 arquivo(s) do escopo ficaram fora do recorte
46
+ > (orçamento ou prioridade de fluxo). Não os leia: registre-os em \`skipped\`
47
+ > com o motivo (\`budget\` ou \`fora do fluxo\`) para que a cobertura fique honesta.
48
48
 
49
49
  - \`src/alpha/c.ts\`
50
50
 
@@ -60,111 +60,22 @@ src/alpha/**
60
60
 
61
61
  - \`src/alpha/old.ts\`
62
62
 
63
- ## Procedimento
64
-
65
- 1. **FILTER** — descarte o que não for do seu domínio (ver *Prioridade de leitura* e *Não carregar*).
66
- 2. **READ SCOPE** — carregue apenas as faixas alteradas em *Escopo do diff*; o restante do arquivo é só contexto.
67
- 3. **GREP** — use as regras críticas/anti-padrões como heurística para localizar violações nas faixas alteradas.
68
- 4. **DEEP-READ** — aprofunde apenas as faixas com suspeita de violação dentro do escopo.
69
- 5. **CONCLUDE** — grave o JSON no caminho de output, com \`reviewedFiles\` preenchido e \`line\` dentro das faixas alteradas. Sem achados → \`"findings": []\` (arquivo obrigatório). Não escreva markdown livre."
70
- `;
71
-
72
- exports[`baseline — relatório renderReport consolidado 1`] = `
73
- "# PR Review — feature/x
74
-
75
- **Data:** <date>
76
- **Watchdogs ativos:** alpha, beta
77
- **Achados:** 1 bloqueantes · 1 atenção · 0 sugestões · 0 nits
78
-
79
- ## Contexto da PR
80
-
81
- **PR:** #42 Entrega X — https://example.test/pull/42
82
- **Branch:** \`feature/x\` → \`main\`
83
- **Objetivo:** Entregar X
84
- **Motivação:** Reduzir Y
85
-
86
- **Resumo das alterações:**
87
-
88
- - Faz A
89
- - Remove B
90
-
91
- ## Relatórios por domínio
92
-
93
- | Watchdog | Achados | Revisados | Relatório |
94
- |---|---|---|---|
95
- | Alpha | 2 | 2/2 | [\`review.alpha.md\`](review.alpha.md) |
96
-
97
- ## Cobertura
98
-
99
- | Watchdog | Arquivos revisados | Findings |
100
- |---|---|---|
101
- | Alpha | 2 | 2 |
102
- | Beta | 1 | 0 |
103
-
104
- ## Conclusão
105
-
106
- **Status:** ❌ Mudanças Necessárias
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;\`
63
+ ## Orçamento de exploração
117
64
 
118
- ## ⚠️ attention
65
+ > O diff já está no brief. Limite **neste brief**: 6 leitura(s) de arquivo
66
+ > e 6 busca(s) (grep).
67
+ > Abra arquivo só para chamador/contexto de um símbolo citado nos trechos.
68
+ > Se a prova não estiver no brief nem nesse orçamento, **não é achado** —
69
+ > registre a dúvida no campo \`action\` do finding mais próximo ou descarte.
119
70
 
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
- "
126
- `;
127
-
128
- exports[`baseline — relatório renderDomainReport isolado 1`] = `
129
- "# Review — Alpha (\`alpha\`) — feature/x
130
-
131
- **Data:** <date>
132
- **Arquivos revisados:** 2/2
133
- **Achados:** 2
134
-
135
- ## Contexto da PR
136
-
137
- **PR:** #42 Entrega X — https://example.test/pull/42
138
- **Branch:** \`feature/x\` → \`main\`
139
- **Objetivo:** Entregar X
140
- **Motivação:** Reduzir Y
141
-
142
- **Resumo das alterações:**
143
-
144
- - Faz A
145
- - Remove B
146
-
147
- ## ❌ blocking
148
-
149
- ### \`src/alpha/a.ts\`
150
-
151
- - **Nunca usar \`any\`.** — \`src/alpha/a.ts:1\` · _Alpha_ · \`id-alpha\`
152
- any detectado
153
- **Ação:** tipar
154
- **Evidência:** \`export const a: any = 1;\`
155
-
156
- ## ⚠️ attention
157
-
158
- ### \`src/alpha/b.ts\`
159
-
160
- - **Promover: validar a entrada.** — \`src/alpha/b.ts:2\` · _Alpha, Beta_ · \`id-beta\`
161
- sem validação
162
- **Ação:** adicionar schema
163
-
164
- ## Não revisados
71
+ ## Procedimento
165
72
 
166
- - \`src/alpha/c.ts\`
167
- "
73
+ 1. **RECONSTRUCT** — leia o recorte do *Modelo de intenção (negócio)* e diga para si mesmo o que a mudança DEVE entregar (fluxos, invariantes, contratos). Sem modelo, derive do contexto da PR.
74
+ 2. **SCOPE** — os *Trechos alterados (diff)* já estão no brief: **não releia o arquivo inteiro** para vê-los. Trecho com o marcador \`… [trecho truncado]\` pode ser aberto, uma vez, para o restante.
75
+ 3. **TRACE** — siga cada símbolo alterado até seus chamadores, adapters e tipos compartilhados **dentro do seu domínio** (use os importadores do AST), **dentro do orçamento de exploração** (seção acima). Priorize os arquivos de maior churn.
76
+ 4. **GREP** — use as regras críticas/anti-padrões como heurística para localizar violações nas faixas alteradas, **dentro do orçamento**. Pare quando tiver a prova: repetir busca não aumenta certeza.
77
+ 5. **PROVE** — para cada suspeita, descreva gatilho → comportamento observado → invariante/regra violada. Sem prova, não é achado. Trecho com o marcador de truncamento só sustenta achado depois de abrir o arquivo para ver o restante.
78
+ 6. **CONCLUDE** — grave o JSON no caminho de output. \`reviewedFiles\` é um subconjunto de \`## Arquivos do seu domínio\` (só o que você revisou; **não** liste chamadores/importadores nem arquivos de outros domínios) e o que ficou de fora entra em \`skipped\` ("budget", "truncated" ou "fora do fluxo"). Máximo de 20 achados: priorize severidade e agrupe ocorrências idênticas. \`line\` dentro das faixas alteradas. Sem achados → \`"findings": []\` (arquivo obrigatório). Não escreva markdown livre."
168
79
  `;
169
80
 
170
81
  exports[`baseline — camada gerada setup gera os mesmos arquivos e conteúdos 1`] = `
@@ -180,7 +91,7 @@ exports[`baseline — camada gerada setup gera os mesmos arquivos e conteúdos 1
180
91
  ".houndsman/core/skill/OUTPUT-FORMAT.md",
181
92
  ".houndsman/core/skill/README.md",
182
93
  ".houndsman/core/skill/SETUP.md",
183
- ".houndsman/core/skill/example-review.md",
94
+ ".houndsman/core/skill/example-resume.md",
184
95
  ".houndsman/core/skill/fix.md",
185
96
  ".houndsman/core/skill/hunt.md",
186
97
  ".houndsman/core/skill/train.md",
@@ -214,6 +125,11 @@ consolida tudo num relatório. O motor é o CLI **\`houndsman\`**; as regras do
214
125
  **Terminologia:** **houndsman** = orquestrador/treinador (CLI + motor) · **watchdog** = agente
215
126
  especialista por domínio · **finding** = um achado da revisão · **Jev** = pré-análise opcional.
216
127
 
128
+ > **Duas etapas são obrigatórias em toda revisão:** o **modelo de intenção**
129
+ > (\`business-summary.json\`) e o revisor de fluxo **\`general\`** (id reservado, sempre ativo).
130
+ > O modelo vem da IA no prepare quando há chave; sem chave, um subagente (Passo 1.5) ou o
131
+ > pré-passe maki o produz. Nenhum dos dois é configurável pelo usuário.
132
+
217
133
  ## Capabilities
218
134
 
219
135
  | Capability | Objetivo | Documento |
@@ -260,12 +176,16 @@ Interprete o alvo em linguagem natural **antes** de rodar o CLI:
260
176
  Sequência para uma PR (ex.: 456):
261
177
 
262
178
  \`\`\`bash
263
- houndsman context --pr 456
264
- houndsman --pr 456 # cria/reusa o worktree e imprime o caminho
265
- # despache os briefs (cada um traz **Worktree:** <path>)
179
+ houndsman --pr 456 # cria/reusa o worktree, coleta o contexto e prepara
180
+ # despache os briefs em paralelo (cada um traz **Worktree:** <path>)
266
181
  houndsman --pr 456 --finalize
267
182
  \`\`\`
268
183
 
184
+ - O contexto da PR é coletado pelo próprio prepare (via \`gh\`); com IA ligada ele também é
185
+ resumido ali — não gaste um subagente nisso. Desative com \`--no-context\`.
186
+ - Para comparar duas execuções, use \`--variant <label>\` em cada uma e depois
187
+ \`houndsman compare --branch <head> --a <a> --b <b>\` (ou \`compare --list\`).
188
+
269
189
  - O worktree vive em \`<repo>/.houndsman/worktrees/<repo>/<branch>\` (detached no commit da PR)
270
190
  e é reutilizado nas próximas execuções da mesma branch. Ele fica **dentro do workspace**
271
191
  (e o staging também) para o agente ler/executar sem prompts de permissão.
@@ -283,7 +203,7 @@ houndsman --pr 456 --finalize
283
203
  - \`.houndsman/core/skill/OUTPUT-FORMAT.md\` — schema do JSON de saída dos watchdogs.
284
204
  - \`.houndsman/core/skill/AUDIT-PROTOCOL.md\` — taxonomia da auditoria de regras (Modo B do \`train\`).
285
205
  - \`.houndsman/core/skill/SETUP.md\` · \`.houndsman/core/skill/README.md\` — configuração e visão geral.
286
- - \`.houndsman/core/skill/example-review.md\` — exemplo de relatório.
206
+ - \`.houndsman/core/skill/example-resume.md\` — exemplo do resumo final.
287
207
  "
288
208
  ,
289
209
  ".claude/skills/houndsman/SKILL.md":
@@ -303,6 +223,11 @@ consolida tudo num relatório. O motor é o CLI **\`houndsman\`**; as regras do
303
223
  **Terminologia:** **houndsman** = orquestrador/treinador (CLI + motor) · **watchdog** = agente
304
224
  especialista por domínio · **finding** = um achado da revisão · **Jev** = pré-análise opcional.
305
225
 
226
+ > **Duas etapas são obrigatórias em toda revisão:** o **modelo de intenção**
227
+ > (\`business-summary.json\`) e o revisor de fluxo **\`general\`** (id reservado, sempre ativo).
228
+ > O modelo vem da IA no prepare quando há chave; sem chave, um subagente (Passo 1.5) ou o
229
+ > pré-passe maki o produz. Nenhum dos dois é configurável pelo usuário.
230
+
306
231
  ## Capabilities
307
232
 
308
233
  | Capability | Objetivo | Documento |
@@ -349,12 +274,16 @@ Interprete o alvo em linguagem natural **antes** de rodar o CLI:
349
274
  Sequência para uma PR (ex.: 456):
350
275
 
351
276
  \`\`\`bash
352
- houndsman context --pr 456
353
- houndsman --pr 456 # cria/reusa o worktree e imprime o caminho
354
- # despache os briefs (cada um traz **Worktree:** <path>)
277
+ houndsman --pr 456 # cria/reusa o worktree, coleta o contexto e prepara
278
+ # despache os briefs em paralelo (cada um traz **Worktree:** <path>)
355
279
  houndsman --pr 456 --finalize
356
280
  \`\`\`
357
281
 
282
+ - O contexto da PR é coletado pelo próprio prepare (via \`gh\`); com IA ligada ele também é
283
+ resumido ali — não gaste um subagente nisso. Desative com \`--no-context\`.
284
+ - Para comparar duas execuções, use \`--variant <label>\` em cada uma e depois
285
+ \`houndsman compare --branch <head> --a <a> --b <b>\` (ou \`compare --list\`).
286
+
358
287
  - O worktree vive em \`<repo>/.houndsman/worktrees/<repo>/<branch>\` (detached no commit da PR)
359
288
  e é reutilizado nas próximas execuções da mesma branch. Ele fica **dentro do workspace**
360
289
  (e o staging também) para o agente ler/executar sem prompts de permissão.
@@ -372,7 +301,7 @@ houndsman --pr 456 --finalize
372
301
  - \`.houndsman/core/skill/OUTPUT-FORMAT.md\` — schema do JSON de saída dos watchdogs.
373
302
  - \`.houndsman/core/skill/AUDIT-PROTOCOL.md\` — taxonomia da auditoria de regras (Modo B do \`train\`).
374
303
  - \`.houndsman/core/skill/SETUP.md\` · \`.houndsman/core/skill/README.md\` — configuração e visão geral.
375
- - \`.houndsman/core/skill/example-review.md\` — exemplo de relatório.
304
+ - \`.houndsman/core/skill/example-resume.md\` — exemplo do resumo final.
376
305
  "
377
306
  ,
378
307
  ".houndsman/.gitignore":
@@ -497,6 +426,7 @@ export type AiProviderOptions = Record<string, Record<string, unknown>>;
497
426
  /** Features de IA do fluxo normal (todas fail-open). */
498
427
  export type HoundsmanAiFeatures = {
499
428
  context?: boolean;
429
+ business?: boolean;
500
430
  brief?: boolean;
501
431
  technical?: boolean;
502
432
  classification?: boolean;
@@ -534,6 +464,18 @@ export type HoundsmanHunt = {
534
464
  binaryPath?: string;
535
465
  };
536
466
 
467
+ /**
468
+ * Prioridade por caminho.
469
+ *
470
+ * \`bonus\` promove (linhas de churn equivalentes) e \`maxRiskFromChurn\` satura o
471
+ * superchurn de caminhos de baixa criticidade.
472
+ */
473
+ export type HoundsmanPriority = {
474
+ pattern: RegExp;
475
+ bonus?: number;
476
+ maxRiskFromChurn?: number;
477
+ };
478
+
537
479
  /** Contrato de configuração do motor. */
538
480
  export type HoundsmanConfig<TId extends string = string> = {
539
481
  projectName: string;
@@ -542,6 +484,8 @@ export type HoundsmanConfig<TId extends string = string> = {
542
484
  watchdogs: Record<TId, WatchdogDefinition>;
543
485
  exclusions: readonly RegExp[];
544
486
  docMappings: readonly (readonly [RegExp, readonly string[]])[];
487
+ /** Prioridade por caminho (opcional; a primeira regra que casar vence). */
488
+ priorities?: readonly HoundsmanPriority[];
545
489
  alwaysIncludeDocs?: readonly string[];
546
490
  budgets?: HoundsmanBudgets;
547
491
  concurrency?: HoundsmanConcurrency;
@@ -626,6 +570,9 @@ import { exampleWatchdog } from "./example";
626
570
  /**
627
571
  * Definições dos watchdogs — as chaves definem o union \`WatchdogId\`.
628
572
  * Adicionar um watchdog = criar \`<id>.ts\` e registrá-lo aqui.
573
+ *
574
+ * NÃO declare um watchdog com id \`general\`: ele é reservado pelo motor
575
+ * (revisor de fluxo obrigatório) e é injetado em todo ciclo.
629
576
  */
630
577
  export const WATCHDOG_DEFINITIONS = {
631
578
  default: defaultWatchdog,
@@ -653,6 +600,15 @@ export const houndsmanConfig = {
653
600
  watchdogs: WATCHDOG_DEFINITIONS,
654
601
  exclusions: EXCLUSION_PATTERNS,
655
602
  docMappings: DOC_MAPPINGS,
603
+ // Prioridade por caminho (opcional): "linhas de churn equivalentes".
604
+ // \`bonus\` promove caminho crítico; \`maxRiskFromChurn\` satura o superchurn de
605
+ // caminho de baixa criticidade (risco = min(churn, teto) + bonus).
606
+ // A primeira regra que casar vence; sem regra, o risco é o churn puro.
607
+ // priorities: [
608
+ // { pattern: /^src\\/(db|repositories)\\//, bonus: 300 },
609
+ // { pattern: /^src\\/(services|domain)\\//, bonus: 200 },
610
+ // { pattern: /^src\\/(components|styles)\\//, maxRiskFromChurn: 120 },
611
+ // ],
656
612
  } satisfies HoundsmanConfig;
657
613
 
658
614
  export default houndsmanConfig;
@@ -669,19 +625,19 @@ exports[`baseline — camada gerada setup gera os mesmos arquivos e conteúdos 3
669
625
  },
670
626
  {
671
627
  "file": ".houndsman/core/skill/OUTPUT-FORMAT.md",
672
- "sha256": "a78ed16d4d211889",
628
+ "sha256": "49d36854d10f795c",
673
629
  },
674
630
  {
675
631
  "file": ".houndsman/core/skill/README.md",
676
- "sha256": "2ecf43f34a624011",
632
+ "sha256": "4d200cf9e5c1d748",
677
633
  },
678
634
  {
679
635
  "file": ".houndsman/core/skill/SETUP.md",
680
- "sha256": "5720c48ede6c84ea",
636
+ "sha256": "e7f587d1c8e459d1",
681
637
  },
682
638
  {
683
- "file": ".houndsman/core/skill/example-review.md",
684
- "sha256": "d9e75054cc9811dc",
639
+ "file": ".houndsman/core/skill/example-resume.md",
640
+ "sha256": "4993f56afdf2ffd1",
685
641
  },
686
642
  {
687
643
  "file": ".houndsman/core/skill/fix.md",
@@ -689,7 +645,7 @@ exports[`baseline — camada gerada setup gera os mesmos arquivos e conteúdos 3
689
645
  },
690
646
  {
691
647
  "file": ".houndsman/core/skill/hunt.md",
692
- "sha256": "d31198580d894427",
648
+ "sha256": "0a81003a5b39b84a",
693
649
  },
694
650
  {
695
651
  "file": ".houndsman/core/skill/train.md",
@@ -702,25 +658,31 @@ exports[`baseline — camada gerada setup gera os mesmos arquivos e conteúdos 3
702
658
  ]
703
659
  `;
704
660
 
705
- exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, review e state 1`] = `
706
- "Discovery: 1 watchdog(s) ativado(s), 3 arquivo(s), 1 deletado(s).
661
+ exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, resumo e state 1`] = `
662
+ "Discovery: 1 watchdog(s) de domínio + revisor general, 3 arquivo(s), 1 deletado(s).
707
663
  Briefs: <cwd>/.home/work/<uuid>/briefs/
708
664
  Deletado (breaking change): src/alpha/old.ts
709
665
  Escopo: 2 arquivo(s), 0 símbolo(s) em 2 faixa(s) alterada(s).
666
+ Escopo: 2 arquivo(s) em mais de um domínio (máx. 2) — cada domínio revisa o arquivo com o seu recorte.
710
667
  Jev: pulado (desativado (--no-jev)).
711
668
  AST: desabilitado ou sem grammar compatível.
712
669
  Sem contexto da PR (rode \`bun run houndsman context\` antes, se houver PR).
670
+ Intenção da PR determinística (1 fluxo(s)) — enriquecida no passo de intenção (<cwd>/.home/work/<uuid>/business-brief.md).
713
671
 
714
672
  Próximo passo: despachar os sub-agentes (um por brief em <cwd>/.home/work/<uuid>/briefs/).
715
673
  Quando os findings estiverem gravados: bun run houndsman --finalize"
716
674
  `;
717
675
 
718
- exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, review e state: classification.json 1`] = `
676
+ exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, resumo e state: classification.json 1`] = `
719
677
  "{
720
678
  "watchdogs": {
721
679
  "alpha": [
722
680
  "src/alpha/a.ts",
723
681
  "src/alpha/b.ts"
682
+ ],
683
+ "general": [
684
+ "src/alpha/a.ts",
685
+ "src/alpha/b.ts"
724
686
  ]
725
687
  },
726
688
  "files": {
@@ -754,7 +716,8 @@ exports[`baseline — artefatos de prepare/finalize gera classification, brief,
754
716
  "src/alpha/old.ts"
755
717
  ],
756
718
  "stats": {
757
- "alpha": 2
719
+ "alpha": 2,
720
+ "general": 2
758
721
  },
759
722
  "excludedCount": 0,
760
723
  "headSha": "<sha>",
@@ -785,7 +748,7 @@ exports[`baseline — artefatos de prepare/finalize gera classification, brief,
785
748
  "
786
749
  `;
787
750
 
788
- exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, review e state: manifest.json 1`] = `
751
+ exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, resumo e state: manifest.json 1`] = `
789
752
  "{
790
753
  "branch": "main",
791
754
  "slug": "main",
@@ -814,12 +777,31 @@ exports[`baseline — artefatos de prepare/finalize gera classification, brief,
814
777
  "brief": "<cwd>\\\\.home\\\\work\\\\<uuid>\\\\briefs\\\\alpha.md",
815
778
  "findings": "<cwd>\\\\.home\\\\work\\\\<uuid>\\\\findings\\\\alpha.json",
816
779
  "status": "pending"
780
+ },
781
+ {
782
+ "id": "general",
783
+ "name": "Lógica e domínio (fluxo)",
784
+ "files": [
785
+ "src/alpha/a.ts",
786
+ "src/alpha/b.ts"
787
+ ],
788
+ "overflow": 0,
789
+ "brief": "<cwd>\\\\.home\\\\work\\\\<uuid>\\\\briefs\\\\general.md",
790
+ "findings": "<cwd>\\\\.home\\\\work\\\\<uuid>\\\\findings\\\\general.json",
791
+ "status": "pending"
817
792
  }
818
793
  ],
819
794
  "context": {
820
795
  "summarized": false,
821
796
  "source": null
822
797
  },
798
+ "business": {
799
+ "source": "deterministic",
800
+ "flows": 1,
801
+ "invariants": 0,
802
+ "contracts": 0,
803
+ "states": 0
804
+ },
823
805
  "jev": {
824
806
  "available": false,
825
807
  "reason": "desativado (--no-jev)"
@@ -843,7 +825,7 @@ exports[`baseline — artefatos de prepare/finalize gera classification, brief,
843
825
  "
844
826
  `;
845
827
 
846
- exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, review e state: briefs/alpha.md 1`] = `
828
+ exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, resumo e state: briefs/alpha.md 1`] = `
847
829
  "# Brief — Alpha (\`alpha\`)
848
830
 
849
831
  **Branch:** main
@@ -868,6 +850,47 @@ src/alpha/**
868
850
  - \`src/alpha/a.ts\` — linhas 1-2 _(faixas do diff)_
869
851
  - \`src/alpha/b.ts\` — linhas 1 _(faixas do diff)_
870
852
 
853
+ ## Modelo de intenção (negócio)
854
+
855
+ > Fonte: determinístico. Use para saber o que a mudança **deve** entregar; não é achado.
856
+
857
+ > Recorte do seu domínio: fluxos/arquivos de outros domínios pertencem a outros watchdogs — não os investigue.
858
+
859
+ **Fluxos (1):**
860
+ - **Alpha**
861
+ arquivos: src/alpha/a.ts, src/alpha/b.ts
862
+
863
+ ## Trechos alterados (diff)
864
+
865
+ > Trechos já extraídos do diff (\`-U0\`, só linhas alteradas). Eles bastam
866
+ > para achar violações: **não abra os arquivos apenas para reler o que já
867
+ > está aqui**. Use o arquivo só para contexto adjacente ou chamadores.
868
+
869
+ ### \`src/alpha/a.ts\`
870
+
871
+ \`\`\`diff
872
+ diff --git a/src/alpha/a.ts b/src/alpha/a.ts
873
+ index cc798ff..bc84e2e 100644
874
+ --- a/src/alpha/a.ts
875
+ +++ b/src/alpha/a.ts
876
+ @@ -1 +1,2 @@
877
+ -export const a = 1;
878
+ +export const a: any = 1;
879
+ +export const b = 2;
880
+ \`\`\`
881
+
882
+ ### \`src/alpha/b.ts\`
883
+
884
+ \`\`\`diff
885
+ diff --git a/src/alpha/b.ts b/src/alpha/b.ts
886
+ new file mode 100644
887
+ index 0000000..f4082f0
888
+ --- /dev/null
889
+ +++ b/src/alpha/b.ts
890
+ @@ -0,0 +1 @@
891
+ +export const b = 3;
892
+ \`\`\`
893
+
871
894
  ## Regras (por severidade)
872
895
 
873
896
  - ❌ **blocking** — Nunca usar \`any\`.
@@ -886,77 +909,62 @@ src/alpha/**
886
909
 
887
910
  - \`src/alpha/old.ts\`
888
911
 
912
+ ## Orçamento de exploração
913
+
914
+ > O diff já está no brief. Limite **neste brief**: 6 leitura(s) de arquivo
915
+ > e 6 busca(s) (grep).
916
+ > Abra arquivo só para chamador/contexto de um símbolo citado nos trechos.
917
+ > Se a prova não estiver no brief nem nesse orçamento, **não é achado** —
918
+ > registre a dúvida no campo \`action\` do finding mais próximo ou descarte.
919
+
889
920
  ## Procedimento
890
921
 
891
- 1. **FILTER** — descarte o que não for do seu domínio (ver *Prioridade de leitura* e *Não carregar*).
892
- 2. **READ SCOPE** — carregue apenas as faixas alteradas em *Escopo do diff*; o restante do arquivo é só contexto.
893
- 3. **GREP** — use as regras críticas/anti-padrões como heurística para localizar violações nas faixas alteradas.
894
- 4. **DEEP-READ** — aprofunde apenas as faixas com suspeita de violação dentro do escopo.
895
- 5. **CONCLUDE** — grave o JSON no caminho de output, com \`reviewedFiles\` preenchido e \`line\` dentro das faixas alteradas. Sem achados → \`"findings": []\` (arquivo obrigatório). Não escreva markdown livre.
922
+ 1. **RECONSTRUCT** — leia o recorte do *Modelo de intenção (negócio)* e diga para si mesmo o que a mudança DEVE entregar (fluxos, invariantes, contratos). Sem modelo, derive do contexto da PR.
923
+ 2. **SCOPE** — os *Trechos alterados (diff)* já estão no brief: **não releia o arquivo inteiro** para vê-los. Trecho com o marcador \`… [trecho truncado]\` pode ser aberto, uma vez, para o restante.
924
+ 3. **TRACE** — siga cada símbolo alterado até seus chamadores, adapters e tipos compartilhados **dentro do seu domínio** (use os importadores do AST), **dentro do orçamento de exploração** (seção acima). Priorize os arquivos de maior churn.
925
+ 4. **GREP** — use as regras críticas/anti-padrões como heurística para localizar violações nas faixas alteradas, **dentro do orçamento**. Pare quando tiver a prova: repetir busca não aumenta certeza.
926
+ 5. **PROVE** — para cada suspeita, descreva gatilho → comportamento observado → invariante/regra violada. Sem prova, não é achado. Trecho com o marcador de truncamento só sustenta achado depois de abrir o arquivo para ver o restante.
927
+ 6. **CONCLUDE** — grave o JSON no caminho de output. \`reviewedFiles\` é um subconjunto de \`## Arquivos do seu domínio\` (só o que você revisou; **não** liste chamadores/importadores nem arquivos de outros domínios) e o que ficou de fora entra em \`skipped\` ("budget", "truncated" ou "fora do fluxo"). Máximo de 20 achados: priorize severidade e agrupe ocorrências idênticas. \`line\` dentro das faixas alteradas. Sem achados → \`"findings": []\` (arquivo obrigatório). Não escreva markdown livre.
896
928
  "
897
929
  `;
898
930
 
899
- exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, review e state 2`] = `
931
+ exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, resumo e state 2`] = `
900
932
  "Triagem: sqlite:<cwd>\\houndsman.db#<uuid> — 1 finding(s).
901
- Relatório: sqlite:<cwd>\\houndsman.db#<uuid> — 1 bloqueante(s), 0 atenção."
933
+ Resumo: sqlite:<cwd>\\houndsman.db#<uuid> (review-resume.md) — 1 bloqueante(s), 0 atenção."
902
934
  `;
903
935
 
904
- exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, review e state: review.md 1`] = `
905
- "# PR Review — main
906
-
907
- **Data:** <date>
908
- **Watchdogs ativos:** alpha
909
- **Achados:** 1 bloqueantes · 0 atenção · 0 sugestões · 0 nits
936
+ exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, resumo e state: review-resume.md 1`] = `
937
+ "# Resumo da revisão — main
910
938
 
911
- ## Relatórios por domínio
939
+ **Data:** <date> · **Status:** ❌ Mudanças necessárias
912
940
 
913
- | Watchdog | Achados | Revisados | Relatório |
914
- |---|---|---|---|
915
- | Alpha | 1 | 2/2 | [\`review.alpha.md\`](review.alpha.md) |
941
+ ## Resumo de negócio
916
942
 
917
- ## Cobertura
943
+ **Fluxos (1):**
944
+ - **Alpha**
945
+ arquivos: src/alpha/a.ts, src/alpha/b.ts
918
946
 
919
- | Watchdog | Arquivos revisados | Findings |
920
- |---|---|---|
921
- | Alpha | 2 | 1 |
922
-
923
- ## Conclusão
947
+ ## Pontos técnicos
924
948
 
925
- **Status:** ❌ Mudanças Necessárias
926
- **Resumo:** 1 violações bloqueantes, 0 pontos de atenção, 0 sugestões e 0 nits.
949
+ - **Estrutura** — Novo diretório \`src/alpha\` com 1 arquivo(s).
950
+ - **Remoções** — \`src/alpha/old.ts\`
927
951
 
928
- ## ❌ blocking
952
+ ## Cobertura por domínio
929
953
 
930
- ### \`src/alpha/a.ts\`
954
+ | Domínio | Achados | Revisados | Fora do recorte | Faltam |
955
+ |---|---|---|---|---|
956
+ | Alpha | 1 | 2/2 | — | — |
957
+ | Lógica e domínio (fluxo) | 0 | 2/2 | — | — |
931
958
 
932
- - **Nunca usar \`any\`.** — \`src/alpha/a.ts:1\` · _Alpha_ · \`<id>\`
933
- any detectado
934
- **Ação:** tipar
935
- **Evidência:** \`export const a: any = 1;\`
936
-
937
- "
938
- `;
959
+ > \`Faltam\` = arquivos do escopo sem revisão e sem declaração em \`skipped\`.
939
960
 
940
- exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, review e state: review.alpha.md 1`] = `
941
- "# Review — Alpha (\`alpha\`) — main
942
-
943
- **Data:** <date>
944
- **Arquivos revisados:** 2/2
945
- **Achados:** 1
946
-
947
- ## ❌ blocking
948
-
949
- ### \`src/alpha/a.ts\`
950
-
951
- - **Nunca usar \`any\`.** — \`src/alpha/a.ts:1\` · _Alpha_
952
- any detectado
953
- **Ação:** tipar
954
- **Evidência:** \`export const a: any = 1;\`
961
+ ## Conclusão
955
962
 
963
+ **Resumo:** 1 bloqueantes · 0 atenção · 0 sugestões · 0 nits.
956
964
  "
957
965
  `;
958
966
 
959
- exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, review e state: state.json 1`] = `
967
+ exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, resumo e state: state.json 1`] = `
960
968
  "{
961
969
  "version": 2,
962
970
  "branch": "main",
@@ -979,6 +987,7 @@ exports[`baseline — artefatos de prepare/finalize gera classification, brief,
979
987
  "message": "any detectado",
980
988
  "action": "tipar",
981
989
  "evidence": "export const a: any = 1;",
990
+ "basis": "rule",
982
991
  "anchor": {
983
992
  "snippet": "export const a: any = 1;",
984
993
  "line": 1,
@@ -1001,7 +1010,7 @@ exports[`baseline — artefatos de prepare/finalize gera classification, brief,
1001
1010
  }"
1002
1011
  `;
1003
1012
 
1004
- exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, review e state: run.json 1`] = `
1013
+ exports[`baseline — artefatos de prepare/finalize gera classification, brief, manifest, resumo e state: run.json 1`] = `
1005
1014
  "{
1006
1015
  "id": "<uuid>",
1007
1016
  "repoKey": "path:<cwd>",
@@ -155,6 +155,52 @@ describe("temperature e providerOptions", () => {
155
155
  deepseek: { thinking: { type: "disabled" } },
156
156
  });
157
157
  });
158
+
159
+ test("objetos estruturados desligam thinking por default", async () => {
160
+ const provider = new FakeModelProvider(() => ({ ok: true }));
161
+ const client = new AiClient({ project, provider });
162
+ await client.generateObject("business", {
163
+ prompt: "oi",
164
+ outputSchema: z.object({ ok: z.boolean() }),
165
+ });
166
+ expect(provider.requests[0]?.providerOptions).toEqual({
167
+ deepseek: { thinking: { type: "disabled" } },
168
+ });
169
+ });
170
+
171
+ test("thinking configurado pelo projeto vence o default", async () => {
172
+ const configured = new ProjectConfig({
173
+ ...fakeHoundsmanConfig,
174
+ ai: { providerOptions: { deepseek: { thinking: { type: "enabled" } } } },
175
+ });
176
+ const provider = new FakeModelProvider(() => ({ ok: true }));
177
+ const client = new AiClient({ project: configured, provider });
178
+ await client.generateObject("business", {
179
+ prompt: "oi",
180
+ outputSchema: z.object({ ok: z.boolean() }),
181
+ });
182
+ expect(provider.requests[0]?.providerOptions).toEqual({
183
+ deepseek: { thinking: { type: "enabled" } },
184
+ });
185
+ });
186
+
187
+ test("resposta truncada falha e é registrada sem debitar o budget", async () => {
188
+ const provider = new FakeModelProvider(
189
+ () => ({ ok: true }),
190
+ "fake-model",
191
+ "length",
192
+ );
193
+ const client = new AiClient({ project, provider });
194
+ const result = await client.generateObject("brief", {
195
+ prompt: "oi",
196
+ outputSchema: z.object({ ok: z.boolean() }),
197
+ maxOutputTokens: 100,
198
+ });
199
+ expect(result.ok).toBe(false);
200
+ expect(client.recorder.total.calls).toBe(0);
201
+ expect(client.recorder.failures[0]?.finishReason).toBe("length");
202
+ expect(client.recorder.summary.attempts).toBe(1);
203
+ });
158
204
  });
159
205
 
160
206
  describe("avisos do AI SDK", () => {