@payconductor/houndsman 0.11.0 → 0.13.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 (140) hide show
  1. package/README.md +14 -13
  2. package/package.json +1 -1
  3. package/src/cli/commands/doctor-command.ts +22 -0
  4. package/src/cli/commands/hunt-command.ts +440 -29
  5. package/src/cli/commands/review-command.ts +20 -2
  6. package/src/cli/help.ts +16 -8
  7. package/src/cli/index.ts +15 -2
  8. package/src/cli/progress.ts +45 -0
  9. package/src/core/ai/ai-client.ts +3 -7
  10. package/src/core/ai/ai-sdk-model-provider.ts +43 -24
  11. package/src/core/ai/features/brief-enricher.ts +0 -1
  12. package/src/core/ai/features/classifier.ts +0 -1
  13. package/src/core/ai/features/context-summarizer.ts +0 -1
  14. package/src/core/ai/features/pre-analyzer.ts +0 -1
  15. package/src/core/ai/features/technical-summarizer.ts +0 -1
  16. package/src/core/ai/features/verifier.ts +0 -1
  17. package/src/core/ai/index.ts +6 -4
  18. package/src/core/ai/prompts/pre-analysis.ts +3 -2
  19. package/src/core/ai/schemas.ts +1 -23
  20. package/src/core/ai/types.ts +4 -15
  21. package/src/core/ai/usage-budget.ts +1 -1
  22. package/src/core/ast/diff.ts +37 -2
  23. package/src/core/ast/service.ts +1 -1
  24. package/src/core/brief.ts +54 -5
  25. package/src/core/changed-scope.ts +207 -0
  26. package/src/core/commands/scaffold.ts +12 -18
  27. package/src/core/config.ts +30 -15
  28. package/src/core/defaults.ts +14 -5
  29. package/src/core/engine/review-scope.ts +131 -17
  30. package/src/core/engine/review-session.ts +7 -3
  31. package/src/core/engine/review-workspace.ts +14 -0
  32. package/src/core/env.ts +14 -0
  33. package/src/core/hunt/hunt-runner.ts +328 -0
  34. package/src/core/hunt/index.ts +59 -0
  35. package/src/core/hunt/maki-install.ts +331 -0
  36. package/src/core/hunt/maki-model.ts +101 -0
  37. package/src/core/hunt/maki-prompt.ts +71 -0
  38. package/src/core/hunt/maki-release.ts +104 -0
  39. package/src/core/hunt/maki-runner.ts +276 -0
  40. package/src/core/hunt/pool.ts +30 -0
  41. package/src/core/jev/jev-detector.ts +7 -2
  42. package/src/core/logging/index.ts +3 -1
  43. package/src/core/logging/logger.ts +22 -3
  44. package/src/core/logging/progress.ts +282 -0
  45. package/src/core/orchestrator.ts +173 -59
  46. package/src/core/project-config.ts +34 -24
  47. package/src/core/registry-check.ts +2 -12
  48. package/src/core/rule-text.ts +3 -55
  49. package/src/core/services/git-repository.ts +8 -0
  50. package/src/core/storage/types.ts +1 -1
  51. package/src/core/types.ts +3 -14
  52. package/src/index.ts +74 -3
  53. package/src/server/client/utils/constants.ts +3 -6
  54. package/src/server/client/utils/types.ts +0 -1
  55. package/src/skill/OUTPUT-FORMAT.md +5 -0
  56. package/src/skill/README.md +14 -1
  57. package/src/skill/SETUP.md +22 -2
  58. package/src/skill/SKILL.md +4 -0
  59. package/src/skill/fix.md +1 -1
  60. package/src/skill/hunt.md +38 -6
  61. package/src/tests/__snapshots__/baseline.test.ts.snap +69 -32
  62. package/src/tests/ai-core.test.ts +38 -1
  63. package/src/tests/changed-scope.test.ts +197 -0
  64. package/src/tests/cli.test.ts +1 -0
  65. package/src/tests/fallback.test.ts +1 -96
  66. package/src/tests/fixtures/fake-model-provider.ts +0 -1
  67. package/src/tests/hunt-cli.test.ts +185 -0
  68. package/src/tests/hunt-install.test.ts +153 -0
  69. package/src/tests/hunt-maki.test.ts +467 -0
  70. package/src/tests/progress.test.ts +117 -0
  71. package/src/tests/review-scope.test.ts +95 -0
  72. package/src/tests/worktree.test.ts +1 -3
  73. package/src/harness/README.md +0 -15
  74. package/src/harness/agent/agent-context.ts +0 -33
  75. package/src/harness/agent/agent-errors.ts +0 -30
  76. package/src/harness/agent/agent-factory.ts +0 -46
  77. package/src/harness/agent/agent-reporter.ts +0 -34
  78. package/src/harness/agent/agent-result.ts +0 -16
  79. package/src/harness/agent/agent-runner.ts +0 -32
  80. package/src/harness/agent/agent-scheduler.ts +0 -37
  81. package/src/harness/agent/review-agent.ts +0 -401
  82. package/src/harness/config/harness-config.ts +0 -89
  83. package/src/harness/hunt/hunt-harness.ts +0 -83
  84. package/src/harness/hunt/hunt-options.ts +0 -92
  85. package/src/harness/hunt/hunt-result.ts +0 -17
  86. package/src/harness/hunt/hunt-session.ts +0 -359
  87. package/src/harness/hunt/review-planner.ts +0 -98
  88. package/src/harness/index.ts +0 -36
  89. package/src/harness/investigation/agent-state.ts +0 -134
  90. package/src/harness/investigation/evidence-verifier.ts +0 -129
  91. package/src/harness/investigation/evidence.ts +0 -110
  92. package/src/harness/investigation/finding-critic.ts +0 -96
  93. package/src/harness/investigation/investigation-planner.ts +0 -118
  94. package/src/harness/investigation/investigation-task.ts +0 -10
  95. package/src/harness/investigation/review-journal.ts +0 -87
  96. package/src/harness/investigation/rule-analyzer.ts +0 -178
  97. package/src/harness/investigation/rule-signals.ts +0 -110
  98. package/src/harness/prompts/agent-schemas.ts +0 -57
  99. package/src/harness/prompts/reviewer-prompt-builder.ts +0 -101
  100. package/src/harness/prompts/states/conclude-prompt.ts +0 -16
  101. package/src/harness/prompts/states/investigate-prompt.ts +0 -37
  102. package/src/harness/prompts/states/orient-prompt.ts +0 -15
  103. package/src/harness/prompts/states/verify-prompt.ts +0 -22
  104. package/src/harness/tools/agent-tool.ts +0 -102
  105. package/src/harness/tools/builtin/changed-symbols-tool.ts +0 -48
  106. package/src/harness/tools/builtin/doc-context-tool.ts +0 -74
  107. package/src/harness/tools/builtin/file-outline-tool.ts +0 -66
  108. package/src/harness/tools/builtin/find-references-tool.ts +0 -66
  109. package/src/harness/tools/builtin/find-symbol-tool.ts +0 -91
  110. package/src/harness/tools/builtin/helpers.ts +0 -115
  111. package/src/harness/tools/builtin/impact-tool.ts +0 -48
  112. package/src/harness/tools/builtin/imports-exports-tool.ts +0 -53
  113. package/src/harness/tools/builtin/index.ts +0 -59
  114. package/src/harness/tools/builtin/list-files-tool.ts +0 -85
  115. package/src/harness/tools/builtin/pr-context-tool.ts +0 -41
  116. package/src/harness/tools/builtin/read-file-range-tool.ts +0 -73
  117. package/src/harness/tools/builtin/rule-signals-tool.ts +0 -36
  118. package/src/harness/tools/builtin/search-code-tool.ts +0 -65
  119. package/src/harness/tools/builtin/symbol-context-tool.ts +0 -106
  120. package/src/harness/tools/builtin/tests-for-symbol-tool.ts +0 -61
  121. package/src/harness/tools/path-guard.ts +0 -90
  122. package/src/harness/tools/result-limiter.ts +0 -49
  123. package/src/harness/tools/secret-redactor.ts +0 -54
  124. package/src/harness/tools/tool-cache.ts +0 -50
  125. package/src/harness/tools/tool-call-budget.ts +0 -60
  126. package/src/harness/tools/tool-context.ts +0 -5
  127. package/src/harness/tools/tool-middleware.ts +0 -105
  128. package/src/harness/tools/tool-policy.ts +0 -51
  129. package/src/harness/tools/tool-registry.ts +0 -112
  130. package/src/harness/tools/tool-result.ts +0 -15
  131. package/src/harness/tools/toolbox.ts +0 -36
  132. package/src/harness/validation/finding-validator.ts +0 -119
  133. package/src/tests/fixtures/fake-agent-tools.ts +0 -69
  134. package/src/tests/fixtures/fake-investigation.ts +0 -64
  135. package/src/tests/harness-agent.test.ts +0 -141
  136. package/src/tests/harness-investigation.test.ts +0 -206
  137. package/src/tests/harness-tools.test.ts +0 -148
  138. package/src/tests/hunt-command.test.ts +0 -212
  139. package/src/tests/rule-analyzer.test.ts +0 -61
  140. package/src/tests/tool-contracts.test.ts +0 -82
package/src/core/brief.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { AstDelta } from "./ast/types";
9
+ import { formatRanges, formatSymbols } from "./changed-scope";
9
10
  import {
10
11
  type Rule,
11
12
  SEVERITY_EMOJI,
@@ -21,10 +22,10 @@ import {
21
22
  */
22
23
  export const PROCEDURE: readonly string[] = [
23
24
  "1. **FILTER** — descarte o que não for do seu domínio (ver *Prioridade de leitura* e *Não carregar*).",
24
- "2. **READ HEAD** — leia os arquivos seletivamente (primeiras ~30 linhas). NUNCA o diff completo.",
25
- "3. **GREP** — use as regras críticas/anti-padrões como heurística para localizar violações.",
26
- "4. **DEEP-READ** — aprofunde apenas os arquivos com suspeita de violação.",
27
- '5. **CONCLUDE** — grave o JSON no caminho de output, com `reviewedFiles` preenchido. Sem achados → `"findings": []` (arquivo obrigatório). Não escreva markdown livre.',
25
+ "2. **READ SCOPE** — carregue apenas as faixas alteradas em *Escopo do diff*; o restante do arquivo é só contexto.",
26
+ "3. **GREP** — use as regras críticas/anti-padrões como heurística para localizar violações nas faixas alteradas.",
27
+ "4. **DEEP-READ** — aprofunde apenas as faixas com suspeita de violação dentro do escopo.",
28
+ '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.',
28
29
  ];
29
30
 
30
31
  /** Limites do contexto injetado em cada brief (o resumo final é maior). */
@@ -83,6 +84,52 @@ export function renderBriefCandidates(
83
84
  return lines;
84
85
  }
85
86
 
87
+ /**
88
+ * Escopo do diff — as únicas faixas que podem virar finding.
89
+ *
90
+ * Sem ele, o watchdog tende a revisar o arquivo inteiro e reportar problemas
91
+ * pré-existentes (fora do que a PR alterou).
92
+ */
93
+ function changedScopeSection(args: WatchdogRenderArgs): string[] {
94
+ const scope = args.changedScope;
95
+ if (!scope) return [];
96
+ const files = args.files.filter((file) => scope[file]);
97
+ if (files.length === 0) return [];
98
+ const lines: string[] = [
99
+ "## Escopo do diff (símbolos alterados)",
100
+ "",
101
+ "> **Regra de escopo:** um finding só é válido quando a `line` está nas faixas",
102
+ "> abaixo. Código fora delas é contexto — use para entender o impacto, nunca",
103
+ "> como achado. Se a mudança quebra código não alterado, ancore o finding na",
104
+ "> linha alterada que causou o problema.",
105
+ "",
106
+ ];
107
+ let rendered = 0;
108
+ for (const file of files) {
109
+ const entry = scope[file];
110
+ if (!entry) continue;
111
+ if (entry.ranges.length === 0) {
112
+ lines.push(
113
+ `- \`${file}\` — sem linhas alteradas (renomeado/binário): não reportar`,
114
+ );
115
+ rendered++;
116
+ if (rendered >= 40) break;
117
+ continue;
118
+ }
119
+ const fallback = entry.source === "hunks" ? " _(faixas do diff)_" : "";
120
+ lines.push(`- \`${file}\` — linhas ${formatRanges(entry.ranges)}${fallback}`);
121
+ const symbols = formatSymbols(entry.symbols);
122
+ if (symbols) lines.push(` símbolos: ${symbols}`);
123
+ rendered++;
124
+ if (rendered >= 40) break;
125
+ }
126
+ if (files.length > rendered) {
127
+ lines.push(`- … +${files.length - rendered} arquivo(s)`);
128
+ }
129
+ lines.push("");
130
+ return lines;
131
+ }
132
+
86
133
  /** Linha de lista em code-span: `- \`valor\``. */
87
134
  function codeItem(value: string): string {
88
135
  return `- \`${value}\``;
@@ -293,7 +340,9 @@ export function defaultRenderBrief(
293
340
  ];
294
341
 
295
342
  if (def.description) lines.push("## Objetivo", "", def.description, "");
296
- lines.push("## Escopo", "", def.scope, "");
343
+ lines.push("## Escopo do domínio", "", def.scope, "");
344
+
345
+ lines.push(...changedScopeSection(args));
297
346
 
298
347
  if (args.context) lines.push(...renderBriefContext(args.context));
299
348
 
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Escopo do diff (engine) — o que esta revisão realmente alterou.
3
+ *
4
+ * Traduz os hunks do diff (`-U0`) em faixas de linhas revisáveis por arquivo e,
5
+ * quando o AST está disponível, no menor símbolo folha que contém cada âncora.
6
+ * É a fonte única usada pelo brief e pela pré-análise para evitar
7
+ * achados em código pré-existente.
8
+ *
9
+ * Fail-open: sem AST (grammar ausente/`--no-ast`/binário) o escopo cai nos
10
+ * hunks; sem patch, o arquivo fica sem faixas.
11
+ */
12
+
13
+ import { changedAnchors } from "./ast/diff";
14
+ import type { AstAnalysis, AstSymbol, AstSymbolKind } from "./ast/types";
15
+ import type { DiffSource } from "./diff-source";
16
+
17
+ /** Intervalo de linhas (1-based, inclusivo). */
18
+ export type LineRange = { start: number; end: number };
19
+
20
+ /** Símbolo alterado (o menor símbolo folha que contém uma âncora). */
21
+ export type ChangedSymbol = {
22
+ name: string;
23
+ kind: AstSymbolKind;
24
+ startLine: number;
25
+ endLine: number;
26
+ };
27
+
28
+ /** Origem do escopo: AST (símbolos) ou hunks (fallback determinístico). */
29
+ export type ChangedScopeSource = "ast" | "hunks";
30
+
31
+ /** Escopo de um arquivo alterado. */
32
+ export type FileChangedScope = {
33
+ /** Faixas revisáveis (símbolos alterados + âncoras soltas), ordenadas. */
34
+ ranges: LineRange[];
35
+ /** Símbolos folha tocados pelas âncoras. */
36
+ symbols: ChangedSymbol[];
37
+ source: ChangedScopeSource;
38
+ };
39
+
40
+ /** Escopo do diff: caminho do arquivo → faixas revisáveis. */
41
+ export type ChangedScope = Record<string, FileChangedScope>;
42
+
43
+ /** Entrada mínima do builder (status do `git diff --name-status`). */
44
+ export type ScopeEntry = { path: string; status: string };
45
+
46
+ export type BuildChangedScopeInput = {
47
+ entries: readonly ScopeEntry[];
48
+ /** Patch por arquivo (idealmente `git diff -U0`, sem truncar). */
49
+ diff: DiffSource;
50
+ /** Análises da versão atual (`null`/ausente → fallback nos hunks). */
51
+ ast?: ReadonlyMap<string, AstAnalysis>;
52
+ };
53
+
54
+ /** Espécies que representam containers: não expandem sozinhas o escopo. */
55
+ const CONTAINER_KINDS: ReadonlySet<AstSymbolKind> = new Set([
56
+ "class",
57
+ "interface",
58
+ "enum",
59
+ ]);
60
+
61
+ /** Tamanho do intervalo de um símbolo (menor = mais específico). */
62
+ function span(symbol: AstSymbol): number {
63
+ return symbol.endLine - symbol.startLine;
64
+ }
65
+
66
+ /** Menor símbolo folha que contém a linha (containers são ignorados). */
67
+ function leafAt(symbols: readonly AstSymbol[], line: number): AstSymbol | undefined {
68
+ let best: AstSymbol | undefined;
69
+ for (const symbol of symbols) {
70
+ if (CONTAINER_KINDS.has(symbol.kind)) continue;
71
+ if (line < symbol.startLine || line > symbol.endLine) continue;
72
+ if (!best || span(symbol) < span(best)) best = symbol;
73
+ }
74
+ return best;
75
+ }
76
+
77
+ /** Converte um símbolo AST no shape do escopo. */
78
+ function toChangedSymbol(symbol: AstSymbol): ChangedSymbol {
79
+ return {
80
+ name: symbol.name,
81
+ kind: symbol.kind,
82
+ startLine: symbol.startLine,
83
+ endLine: symbol.endLine,
84
+ };
85
+ }
86
+
87
+ /** Deduplica símbolos por identidade e posição, mantendo a ordem de leitura. */
88
+ function uniqueSymbols(symbols: readonly AstSymbol[]): ChangedSymbol[] {
89
+ const seen = new Set<string>();
90
+ const result: ChangedSymbol[] = [];
91
+ for (const symbol of symbols) {
92
+ const key = `${symbol.kind}:${symbol.name}:${symbol.startLine}:${symbol.endLine}`;
93
+ if (seen.has(key)) continue;
94
+ seen.add(key);
95
+ result.push(toChangedSymbol(symbol));
96
+ }
97
+ return result.sort((a, b) => a.startLine - b.startLine || a.endLine - b.endLine);
98
+ }
99
+
100
+ /** `true` se a linha está dentro de alguma faixa. */
101
+ export function isLineInScope(scope: ChangedScope, file: string, line: number): boolean {
102
+ const entry = scope[file];
103
+ if (!entry) return false;
104
+ return entry.ranges.some((range) => line >= range.start && line <= range.end);
105
+ }
106
+
107
+ /** Une intervalos sobrepostos/adjacentes (ordem estável por início). */
108
+ export function mergeRanges(ranges: readonly LineRange[]): LineRange[] {
109
+ const sorted = [...ranges]
110
+ .filter((range) => range.end >= range.start)
111
+ .sort((a, b) => a.start - b.start || a.end - b.end);
112
+ const merged: LineRange[] = [];
113
+ for (const range of sorted) {
114
+ const last = merged.at(-1);
115
+ if (last && range.start <= last.end + 1) {
116
+ last.end = Math.max(last.end, range.end);
117
+ continue;
118
+ }
119
+ merged.push({ ...range });
120
+ }
121
+ return merged;
122
+ }
123
+
124
+ /**
125
+ * Escopo por arquivo alterado. Arquivos deletados/renomeados sem hunks ficam
126
+ * com faixas vazias (não há código atual para ancorar finding).
127
+ */
128
+ export function buildChangedScope(input: BuildChangedScopeInput): ChangedScope {
129
+ const scope: ChangedScope = {};
130
+
131
+ for (const entry of input.entries) {
132
+ if (entry.status === "deleted") continue;
133
+ const analysis = input.ast?.get(entry.path);
134
+ if (analysis?.deleted) continue;
135
+
136
+ const patch = input.diff.byFile.get(entry.path) ?? "";
137
+ const { added, deletedAt } = changedAnchors(patch);
138
+ const anchors = new Set<number>([...added, ...deletedAt]);
139
+ const source: ChangedScopeSource = analysis ? "ast" : "hunks";
140
+
141
+ const touched: AstSymbol[] = [];
142
+ if (analysis) {
143
+ for (const line of anchors) {
144
+ const symbol = leafAt(analysis.facts.symbols, line);
145
+ if (symbol) touched.push(symbol);
146
+ }
147
+ }
148
+ const symbols = uniqueSymbols(touched);
149
+
150
+ // Âncoras fora de símbolo folha (imports, topo do módulo, cabeçalho de
151
+ // classe) continuam revisáveis como faixas avulsas.
152
+ const covered = (line: number): boolean =>
153
+ symbols.some((symbol) => line >= symbol.startLine && line <= symbol.endLine);
154
+ const loose = [...anchors].filter((line) => !covered(line));
155
+
156
+ scope[entry.path] = {
157
+ ranges: mergeRanges([
158
+ ...symbols.map((symbol) => ({
159
+ start: symbol.startLine,
160
+ end: symbol.endLine,
161
+ })),
162
+ ...loose.map((line) => ({ start: line, end: line })),
163
+ ]),
164
+ symbols,
165
+ source,
166
+ };
167
+ }
168
+
169
+ return scope;
170
+ }
171
+
172
+ /** Totais do escopo (mensagens, tool e testes). */
173
+ export function summarizeScope(scope: ChangedScope): {
174
+ files: number;
175
+ ranges: number;
176
+ symbols: number;
177
+ } {
178
+ let ranges = 0;
179
+ let symbols = 0;
180
+ for (const entry of Object.values(scope)) {
181
+ ranges += entry.ranges.length;
182
+ symbols += entry.symbols.length;
183
+ }
184
+ return { files: Object.keys(scope).length, ranges, symbols };
185
+ }
186
+
187
+ /** Faixas em texto compacto (`10-14, 22`) para prompts e briefs. */
188
+ export function formatRanges(ranges: readonly LineRange[], max = 8): string {
189
+ if (ranges.length === 0) return "sem linhas alteradas";
190
+ const shown = ranges
191
+ .slice(0, max)
192
+ .map((range) =>
193
+ range.start === range.end ? `${range.start}` : `${range.start}-${range.end}`,
194
+ );
195
+ const rest = ranges.length > max ? ` (+${ranges.length - max})` : "";
196
+ return `${shown.join(", ")}${rest}`;
197
+ }
198
+
199
+ /** Símbolos em texto compacto (`` `foo` (10-40)``) para prompts e briefs. */
200
+ export function formatSymbols(symbols: readonly ChangedSymbol[], max = 6): string {
201
+ if (symbols.length === 0) return "";
202
+ const shown = symbols
203
+ .slice(0, max)
204
+ .map((symbol) => `\`${symbol.name}\` (${symbol.startLine}-${symbol.endLine})`);
205
+ const rest = symbols.length > max ? ` (+${symbols.length - max})` : "";
206
+ return `${shown.join(", ")}${rest}`;
207
+ }
@@ -131,16 +131,6 @@ export type Rule = {
131
131
  severity: Severity;
132
132
  text: string;
133
133
  jev?: JevRuleSpec;
134
- hints?: RuleHints;
135
- };
136
-
137
- /** Sinais opcionais de uma regra (símbolos, imports, decorators, paths, checks). */
138
- export type RuleHints = {
139
- symbols?: string[];
140
- imports?: string[];
141
- decorators?: string[];
142
- paths?: string[];
143
- checks?: string[];
144
134
  };
145
135
 
146
136
  /** Como o watchdog casa um arquivo alterado. \`value\` é sempre regex. */
@@ -235,20 +225,15 @@ export type HoundsmanAiFeatures = {
235
225
  /** Overrides de IA por watchdog. */
236
226
  export type HoundsmanAiWatchdog = {
237
227
  model?: string;
238
- maxSteps?: number;
239
- maxOutputTokens?: number;
240
228
  };
241
229
 
242
- /** Configuração da camada de IA (briefs, contexto, classificação e hunt nativo). */
230
+ /** Configuração da camada de IA (briefs, contexto, classificação, pré-análise, verify). */
243
231
  export type HoundsmanAi = {
244
232
  enabled?: boolean;
245
233
  provider?: AiProviderKind;
246
234
  model?: string;
247
235
  baseURL?: string;
248
236
  concurrency?: number;
249
- maxSteps?: number;
250
- maxOutputTokens?: number;
251
- temperature?: number;
252
237
  providerOptions?: AiProviderOptions;
253
238
  timeoutMs?: number;
254
239
  maxCallsPerReview?: number;
@@ -257,6 +242,16 @@ export type HoundsmanAi = {
257
242
  watchdogs?: Record<string, HoundsmanAiWatchdog>;
258
243
  };
259
244
 
245
+ /** Harness local da capability \`hunt\` (\`houndsman hunt\`) — opcional. */
246
+ export type HoundsmanHunt = {
247
+ enabled?: boolean;
248
+ model?: string;
249
+ concurrency?: number;
250
+ timeoutMs?: number;
251
+ retries?: number;
252
+ binaryPath?: string;
253
+ };
254
+
260
255
  /** Contrato de configuração do motor. */
261
256
  export type HoundsmanConfig<TId extends string = string> = {
262
257
  projectName: string;
@@ -270,6 +265,7 @@ export type HoundsmanConfig<TId extends string = string> = {
270
265
  concurrency?: HoundsmanConcurrency;
271
266
  ast?: HoundsmanAst;
272
267
  ai?: HoundsmanAi;
268
+ hunt?: HoundsmanHunt;
273
269
  };
274
270
 
275
271
  /** Helper de autoria (identidade) — apenas tipagem. */
@@ -337,7 +333,6 @@ export const defaultWatchdog = defineWatchdog({
337
333
  {
338
334
  severity: "attention",
339
335
  text: "Promover: nome, contrato e documentação do símbolo correspondem ao comportamento real.",
340
- hints: { checks: ["symbol_context"] },
341
336
  },
342
337
  {
343
338
  severity: "attention",
@@ -350,7 +345,6 @@ export const defaultWatchdog = defineWatchdog({
350
345
  {
351
346
  severity: "suggestion",
352
347
  text: "Evitar: lógica de negócio duplicada ou divergente entre camadas.",
353
- hints: { checks: ["find_references"] },
354
348
  },
355
349
  {
356
350
  severity: "suggestion",
@@ -13,15 +13,17 @@ import type { WatchdogDefinition } from "./types";
13
13
  export {
14
14
  DEFAULT_AI_CONCURRENCY,
15
15
  DEFAULT_AI_MAX_CALLS_PER_REVIEW,
16
- DEFAULT_AI_MAX_OUTPUT_TOKENS,
17
- DEFAULT_AI_MAX_STEPS,
18
16
  DEFAULT_AI_MODEL,
19
17
  DEFAULT_AI_PROVIDER,
20
18
  DEFAULT_AI_TIMEOUT_MS,
21
19
  DEFAULT_ALWAYS_INCLUDE_DOCS,
22
20
  DEFAULT_AST_MAX_FILE_BYTES,
23
21
  DEFAULT_AST_MAX_IMPACT_FILES,
22
+ DEFAULT_HUNT_CONCURRENCY,
23
+ DEFAULT_HUNT_RETRIES,
24
+ DEFAULT_HUNT_TIMEOUT_MS,
24
25
  DEFAULT_JEV_CONCURRENCY,
26
+ DEFAULT_MAKI_VERSION,
25
27
  DEFAULT_MAX_FILES_PER_BRIEF,
26
28
  } from "./defaults";
27
29
 
@@ -85,11 +87,9 @@ export type HoundsmanAiFeatures = {
85
87
  /** Overrides de IA por watchdog. */
86
88
  export type HoundsmanAiWatchdog = {
87
89
  model?: string;
88
- maxSteps?: number;
89
- maxOutputTokens?: number;
90
90
  };
91
91
 
92
- /** Configuração da camada de IA (genérica; o harness nativo também a consome). */
92
+ /** Configuração da camada de IA (briefs, contexto, classificação, pré-análise, verify). */
93
93
  export type HoundsmanAi = {
94
94
  /** Liga a IA do fluxo normal (default: `true`; sem chave é fail-open). */
95
95
  enabled?: boolean;
@@ -101,15 +101,6 @@ export type HoundsmanAi = {
101
101
  baseURL?: string;
102
102
  /** Chamadas simultâneas (default: 2). */
103
103
  concurrency?: number;
104
- /** Passos por agente (default: 12). */
105
- maxSteps?: number;
106
- /** Teto de tokens de saída por chamada. */
107
- maxOutputTokens?: number;
108
- /**
109
- * Temperatura (default: 0). Ignorada quando o provider usa thinking
110
- * (DeepSeek V4 liga thinking por default) — nesse caso o motor não a envia.
111
- */
112
- temperature?: number;
113
104
  /** Opções do AI SDK por provider (repassadas sem interpretação). */
114
105
  providerOptions?: AiProviderOptions;
115
106
  /** Timeout por chamada em ms (default: 120_000). */
@@ -135,6 +126,28 @@ export type HoundsmanWorktrees = {
135
126
  location?: "home" | "workspace" | string;
136
127
  };
137
128
 
129
+ /**
130
+ * Harness local da capability `hunt` (`houndsman hunt`) — opcional.
131
+ *
132
+ * Sem esta config, o comando usa defaults seguros: modelo derivado de `ai`,
133
+ * 4 processos simultâneos e timeout de 15 min por watchdog. A skill
134
+ * `/houndsman hunt` (harness externo) não depende deste bloco.
135
+ */
136
+ export type HoundsmanHunt = {
137
+ /** Desliga o harness local sem desinstalar o binário (default: ligado). */
138
+ enabled?: boolean;
139
+ /** Modelo no formato do maki (`provider/model-id`). Default: derivado de `ai`. */
140
+ model?: string;
141
+ /** Processos maki simultâneos (default: 4). */
142
+ concurrency?: number;
143
+ /** Timeout por watchdog em ms (default: 900_000). */
144
+ timeoutMs?: number;
145
+ /** Tentativas extras quando a saída do watchdog é inválida (default: 1). */
146
+ retries?: number;
147
+ /** Caminho explícito do binário (`HOUNDSMAN_MAKI_PATH` tem precedência). */
148
+ binaryPath?: string;
149
+ };
150
+
138
151
  /** Backend de persistência (reviews, findings e artefatos). */
139
152
  export type HoundsmanStorage = {
140
153
  /**
@@ -180,8 +193,10 @@ export type HoundsmanConfig<TId extends string = string> = {
180
193
  concurrency?: HoundsmanConcurrency;
181
194
  /** AST determinístico (classificação estrutural, brief e impacto). */
182
195
  ast?: HoundsmanAst;
183
- /** Camada de IA (briefs, contexto, classificação e o harness nativo). */
196
+ /** Camada de IA (briefs, contexto, classificação, pré-análise e verify). */
184
197
  ai?: HoundsmanAi;
198
+ /** Harness local da capability `hunt` (maki) — opcional. */
199
+ hunt?: HoundsmanHunt;
185
200
  };
186
201
 
187
202
  /** Entrada de `defineHoundsmanConfig` — `watchdogIds` é derivado, não informado. */
@@ -41,14 +41,23 @@ export const DEFAULT_AI_MODEL = "deepseek-v4-pro";
41
41
  /** Concorrência default das chamadas de IA. */
42
42
  export const DEFAULT_AI_CONCURRENCY = 2;
43
43
 
44
- /** Passos default por agente (loop de tools + saída). */
45
- export const DEFAULT_AI_MAX_STEPS = 12;
46
-
47
44
  /** Timeout default de uma chamada de modelo (ms). */
48
45
  export const DEFAULT_AI_TIMEOUT_MS = 120_000;
49
46
 
50
47
  /** Teto default de chamadas de IA por review. */
51
48
  export const DEFAULT_AI_MAX_CALLS_PER_REVIEW = 40;
52
49
 
53
- /** Teto default de tokens de saída por chamada. */
54
- export const DEFAULT_AI_MAX_OUTPUT_TOKENS = 4096;
50
+ /** Processos maki simultâneos no harness local (`houndsman hunt`). */
51
+ export const DEFAULT_HUNT_CONCURRENCY = 4;
52
+
53
+ /** Timeout default de um watchdog no harness local (15 min). */
54
+ export const DEFAULT_HUNT_TIMEOUT_MS = 900_000;
55
+
56
+ /** Retentativas default quando a saída do watchdog é inválida. */
57
+ export const DEFAULT_HUNT_RETRIES = 1;
58
+
59
+ /**
60
+ * Versão do maki fixada pelo houndsman. O binário é baixado por
61
+ * `houndsman hunt install`; `HOUNDSMAN_MAKI_VERSION` sobrescreve.
62
+ */
63
+ export const DEFAULT_MAKI_VERSION = "0.6.5";