@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.
- package/README.md +14 -13
- package/package.json +1 -1
- package/src/cli/commands/doctor-command.ts +22 -0
- package/src/cli/commands/hunt-command.ts +440 -29
- package/src/cli/commands/review-command.ts +20 -2
- package/src/cli/help.ts +16 -8
- package/src/cli/index.ts +15 -2
- package/src/cli/progress.ts +45 -0
- package/src/core/ai/ai-client.ts +3 -7
- package/src/core/ai/ai-sdk-model-provider.ts +43 -24
- package/src/core/ai/features/brief-enricher.ts +0 -1
- package/src/core/ai/features/classifier.ts +0 -1
- package/src/core/ai/features/context-summarizer.ts +0 -1
- package/src/core/ai/features/pre-analyzer.ts +0 -1
- package/src/core/ai/features/technical-summarizer.ts +0 -1
- package/src/core/ai/features/verifier.ts +0 -1
- package/src/core/ai/index.ts +6 -4
- package/src/core/ai/prompts/pre-analysis.ts +3 -2
- package/src/core/ai/schemas.ts +1 -23
- package/src/core/ai/types.ts +4 -15
- package/src/core/ai/usage-budget.ts +1 -1
- package/src/core/ast/diff.ts +37 -2
- package/src/core/ast/service.ts +1 -1
- package/src/core/brief.ts +54 -5
- package/src/core/changed-scope.ts +207 -0
- package/src/core/commands/scaffold.ts +12 -18
- package/src/core/config.ts +30 -15
- package/src/core/defaults.ts +14 -5
- package/src/core/engine/review-scope.ts +131 -17
- package/src/core/engine/review-session.ts +7 -3
- package/src/core/engine/review-workspace.ts +14 -0
- package/src/core/env.ts +14 -0
- package/src/core/hunt/hunt-runner.ts +328 -0
- package/src/core/hunt/index.ts +59 -0
- package/src/core/hunt/maki-install.ts +331 -0
- package/src/core/hunt/maki-model.ts +101 -0
- package/src/core/hunt/maki-prompt.ts +71 -0
- package/src/core/hunt/maki-release.ts +104 -0
- package/src/core/hunt/maki-runner.ts +276 -0
- package/src/core/hunt/pool.ts +30 -0
- package/src/core/jev/jev-detector.ts +7 -2
- package/src/core/logging/index.ts +3 -1
- package/src/core/logging/logger.ts +22 -3
- package/src/core/logging/progress.ts +282 -0
- package/src/core/orchestrator.ts +173 -59
- package/src/core/project-config.ts +34 -24
- package/src/core/registry-check.ts +2 -12
- package/src/core/rule-text.ts +3 -55
- package/src/core/services/git-repository.ts +8 -0
- package/src/core/storage/types.ts +1 -1
- package/src/core/types.ts +3 -14
- package/src/index.ts +74 -3
- package/src/server/client/utils/constants.ts +3 -6
- package/src/server/client/utils/types.ts +0 -1
- package/src/skill/OUTPUT-FORMAT.md +5 -0
- package/src/skill/README.md +14 -1
- package/src/skill/SETUP.md +22 -2
- package/src/skill/SKILL.md +4 -0
- package/src/skill/fix.md +1 -1
- package/src/skill/hunt.md +38 -6
- package/src/tests/__snapshots__/baseline.test.ts.snap +69 -32
- package/src/tests/ai-core.test.ts +38 -1
- package/src/tests/changed-scope.test.ts +197 -0
- package/src/tests/cli.test.ts +1 -0
- package/src/tests/fallback.test.ts +1 -96
- package/src/tests/fixtures/fake-model-provider.ts +0 -1
- package/src/tests/hunt-cli.test.ts +185 -0
- package/src/tests/hunt-install.test.ts +153 -0
- package/src/tests/hunt-maki.test.ts +467 -0
- package/src/tests/progress.test.ts +117 -0
- package/src/tests/review-scope.test.ts +95 -0
- package/src/tests/worktree.test.ts +1 -3
- package/src/harness/README.md +0 -15
- package/src/harness/agent/agent-context.ts +0 -33
- package/src/harness/agent/agent-errors.ts +0 -30
- package/src/harness/agent/agent-factory.ts +0 -46
- package/src/harness/agent/agent-reporter.ts +0 -34
- package/src/harness/agent/agent-result.ts +0 -16
- package/src/harness/agent/agent-runner.ts +0 -32
- package/src/harness/agent/agent-scheduler.ts +0 -37
- package/src/harness/agent/review-agent.ts +0 -401
- package/src/harness/config/harness-config.ts +0 -89
- package/src/harness/hunt/hunt-harness.ts +0 -83
- package/src/harness/hunt/hunt-options.ts +0 -92
- package/src/harness/hunt/hunt-result.ts +0 -17
- package/src/harness/hunt/hunt-session.ts +0 -359
- package/src/harness/hunt/review-planner.ts +0 -98
- package/src/harness/index.ts +0 -36
- package/src/harness/investigation/agent-state.ts +0 -134
- package/src/harness/investigation/evidence-verifier.ts +0 -129
- package/src/harness/investigation/evidence.ts +0 -110
- package/src/harness/investigation/finding-critic.ts +0 -96
- package/src/harness/investigation/investigation-planner.ts +0 -118
- package/src/harness/investigation/investigation-task.ts +0 -10
- package/src/harness/investigation/review-journal.ts +0 -87
- package/src/harness/investigation/rule-analyzer.ts +0 -178
- package/src/harness/investigation/rule-signals.ts +0 -110
- package/src/harness/prompts/agent-schemas.ts +0 -57
- package/src/harness/prompts/reviewer-prompt-builder.ts +0 -101
- package/src/harness/prompts/states/conclude-prompt.ts +0 -16
- package/src/harness/prompts/states/investigate-prompt.ts +0 -37
- package/src/harness/prompts/states/orient-prompt.ts +0 -15
- package/src/harness/prompts/states/verify-prompt.ts +0 -22
- package/src/harness/tools/agent-tool.ts +0 -102
- package/src/harness/tools/builtin/changed-symbols-tool.ts +0 -48
- package/src/harness/tools/builtin/doc-context-tool.ts +0 -74
- package/src/harness/tools/builtin/file-outline-tool.ts +0 -66
- package/src/harness/tools/builtin/find-references-tool.ts +0 -66
- package/src/harness/tools/builtin/find-symbol-tool.ts +0 -91
- package/src/harness/tools/builtin/helpers.ts +0 -115
- package/src/harness/tools/builtin/impact-tool.ts +0 -48
- package/src/harness/tools/builtin/imports-exports-tool.ts +0 -53
- package/src/harness/tools/builtin/index.ts +0 -59
- package/src/harness/tools/builtin/list-files-tool.ts +0 -85
- package/src/harness/tools/builtin/pr-context-tool.ts +0 -41
- package/src/harness/tools/builtin/read-file-range-tool.ts +0 -73
- package/src/harness/tools/builtin/rule-signals-tool.ts +0 -36
- package/src/harness/tools/builtin/search-code-tool.ts +0 -65
- package/src/harness/tools/builtin/symbol-context-tool.ts +0 -106
- package/src/harness/tools/builtin/tests-for-symbol-tool.ts +0 -61
- package/src/harness/tools/path-guard.ts +0 -90
- package/src/harness/tools/result-limiter.ts +0 -49
- package/src/harness/tools/secret-redactor.ts +0 -54
- package/src/harness/tools/tool-cache.ts +0 -50
- package/src/harness/tools/tool-call-budget.ts +0 -60
- package/src/harness/tools/tool-context.ts +0 -5
- package/src/harness/tools/tool-middleware.ts +0 -105
- package/src/harness/tools/tool-policy.ts +0 -51
- package/src/harness/tools/tool-registry.ts +0 -112
- package/src/harness/tools/tool-result.ts +0 -15
- package/src/harness/tools/toolbox.ts +0 -36
- package/src/harness/validation/finding-validator.ts +0 -119
- package/src/tests/fixtures/fake-agent-tools.ts +0 -69
- package/src/tests/fixtures/fake-investigation.ts +0 -64
- package/src/tests/harness-agent.test.ts +0 -141
- package/src/tests/harness-investigation.test.ts +0 -206
- package/src/tests/harness-tools.test.ts +0 -148
- package/src/tests/hunt-command.test.ts +0 -212
- package/src/tests/rule-analyzer.test.ts +0 -61
- 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
|
|
25
|
-
"3. **GREP** — use as regras críticas/anti-padrões como heurística para localizar violações.",
|
|
26
|
-
"4. **DEEP-READ** — aprofunde apenas
|
|
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
|
|
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",
|
package/src/core/config.ts
CHANGED
|
@@ -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 (
|
|
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
|
|
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. */
|
package/src/core/defaults.ts
CHANGED
|
@@ -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
|
-
/**
|
|
54
|
-
export const
|
|
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";
|