@payconductor/houndsman 0.11.0 → 0.12.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 +4 -19
- package/package.json +1 -1
- package/src/cli/help.ts +0 -7
- package/src/cli/index.ts +0 -2
- package/src/core/ai/ai-client.ts +3 -7
- package/src/core/ai/ai-sdk-model-provider.ts +4 -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 +0 -3
- package/src/core/ai/prompts/pre-analysis.ts +3 -2
- package/src/core/ai/schemas.ts +1 -23
- package/src/core/ai/types.ts +2 -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 +1 -18
- package/src/core/config.ts +2 -15
- package/src/core/defaults.ts +0 -6
- package/src/core/engine/review-scope.ts +55 -8
- package/src/core/jev/jev-detector.ts +7 -2
- package/src/core/logging/index.ts +1 -1
- package/src/core/logging/logger.ts +3 -3
- package/src/core/orchestrator.ts +19 -14
- package/src/core/project-config.ts +0 -28
- 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 +0 -1
- package/src/core/types.ts +3 -14
- package/src/index.ts +25 -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 +2 -1
- package/src/skill/SETUP.md +1 -1
- package/src/skill/fix.md +1 -1
- package/src/skill/hunt.md +2 -6
- package/src/tests/__snapshots__/baseline.test.ts.snap +50 -32
- package/src/tests/changed-scope.test.ts +197 -0
- package/src/tests/fallback.test.ts +1 -96
- package/src/tests/fixtures/fake-model-provider.ts +0 -1
- package/src/tests/review-scope.test.ts +33 -0
- package/src/tests/worktree.test.ts +1 -3
- package/src/cli/commands/hunt-command.ts +0 -51
- 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/README.md
CHANGED
|
@@ -28,7 +28,7 @@ diferente das PRs. O motor só coordena a caçada — as regras são problema se
|
|
|
28
28
|
- **git** — o diff usa `git diff <base>...<head>`.
|
|
29
29
|
- **gh** (Opcional) — Obtém contexto da PR e dispara comentários para o GitHub
|
|
30
30
|
- **`HOUNDSMAN_TYPE_SAFE_API_KEY`** (opcional) — pré-análise Jev.
|
|
31
|
-
- **`HOUNDSMAN_DEEP_SEEK_API_KEY`** (opcional) —
|
|
31
|
+
- **`HOUNDSMAN_DEEP_SEEK_API_KEY`** (opcional) — features de IA (briefs, contexto, classificação, resumo técnico).
|
|
32
32
|
- **`HOUNDSMAN_OPENAI_API_KEY`** / **`HOUNDSMAN_ANTHROPIC_API_KEY`** (opcional) — apenas se
|
|
33
33
|
`ai.provider` for `openai`/`anthropic`.
|
|
34
34
|
|
|
@@ -74,7 +74,7 @@ bun run houndsman doctor
|
|
|
74
74
|
|
|
75
75
|
### 6. Rode a primeira revisão
|
|
76
76
|
|
|
77
|
-
No seu
|
|
77
|
+
No seu agente (Claude Code / agents), invoque o skill:
|
|
78
78
|
|
|
79
79
|
```
|
|
80
80
|
/houndsman hunt
|
|
@@ -84,28 +84,13 @@ O skill orquestra tudo e persiste o relatório no storage global (SQLite em
|
|
|
84
84
|
`<HOUNDSMAN_HOME>/houndsman.db`); a UI lê os artefatos direto do storage. Não precisa rodar
|
|
85
85
|
nenhum script à mão.
|
|
86
86
|
|
|
87
|
-
### 7.
|
|
88
|
-
|
|
89
|
-
Além do skill, o houndsman tem um **harness próprio**: o `hunt` roda o agente de revisão
|
|
90
|
-
dentro do CLI, com tools read-only, investigação por evidência e a mesma saída `review.md`.
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
bun run houndsman hunt --dry-run # inspeciona watchdogs/arquivos, sem IA
|
|
94
|
-
HOUNDSMAN_DEEP_SEEK_API_KEY=... bun run houndsman hunt
|
|
95
|
-
bun run houndsman hunt --resume # preserva findings válidos do staging
|
|
96
|
-
bun run houndsman hunt --fail-on-blocking
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Detalhes (catálogo de tools, máquina de estados, custos e segurança): [`docs/HARNESS.md`](docs/HARNESS.md).
|
|
100
|
-
|
|
101
|
-
### 8. Várias PRs ao mesmo tempo (worktrees)
|
|
87
|
+
### 7. Várias PRs ao mesmo tempo (worktrees)
|
|
102
88
|
|
|
103
89
|
Cada revisão pode rodar em um **worktree dedicado** (detached no commit da PR), sem
|
|
104
90
|
checkout no clone principal e sem bloquear a revisão das outras PRs:
|
|
105
91
|
|
|
106
92
|
```bash
|
|
107
93
|
houndsman --pr 123 # cria/reusa o worktree e prepara os briefs
|
|
108
|
-
houndsman hunt --pr 123 # revisão nativa no mesmo worktree
|
|
109
94
|
houndsman --pr 123 --finalize # consolida o relatório
|
|
110
95
|
houndsman worktree list # worktrees ativos
|
|
111
96
|
houndsman worktree remove feature/x
|
|
@@ -153,7 +138,7 @@ Você decide o que fazer com cada finding pela **UI local**, informando se quer
|
|
|
153
138
|
bun run houndsman server --open # UI: triar, comentar, marcar feito, ler artefatos
|
|
154
139
|
```
|
|
155
140
|
|
|
156
|
-
> A partir da 2ª revisão o
|
|
141
|
+
> A partir da 2ª revisão o prepare é **incremental**: revisa apenas os arquivos que mudaram desde
|
|
157
142
|
> a última revisão. `--full` força o review completo.
|
|
158
143
|
|
|
159
144
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payconductor/houndsman",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Orquestrador de revisão de Pull Requests: classifica o diff, despacha watchdogs especializados por domínio e consolida o relatório.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/cli/help.ts
CHANGED
|
@@ -5,7 +5,6 @@ Comandos:
|
|
|
5
5
|
(nenhum) Prepara a revisão (classification + briefs + manifesto)
|
|
6
6
|
--finalize Consolida os findings em review.md
|
|
7
7
|
context Coleta o contexto da PR via gh (Passo 0; --pr/--worktree)
|
|
8
|
-
hunt Revisão nativa com IA (harness próprio): prepare + agentes + finalize
|
|
9
8
|
findings <sub> Triagem/correção dos findings (list · show · set · done · reset · sync · summary · batch · verify · submit · path)
|
|
10
9
|
status [ping] Mostra o status da run (ping renova o heartbeat da execução)
|
|
11
10
|
worktree <ação> Lista/cria/remove worktrees de revisão (list · add · remove · prune · path)
|
|
@@ -30,12 +29,6 @@ Flags:
|
|
|
30
29
|
--no-ai Desativa as features de IA do fluxo normal
|
|
31
30
|
--fail-on-blocking Exit 1 se houver bloqueantes (com --finalize)
|
|
32
31
|
|
|
33
|
-
Flags do hunt:
|
|
34
|
-
--base <b> · --head <ref> · --branch <nome> · --watchdog <id> · --pr <n> · --worktree <path>
|
|
35
|
-
--full · --clean · --no-ast · --with-jev
|
|
36
|
-
--resume · --allow-partial · --dry-run · --fail-on-blocking
|
|
37
|
-
--max-calls <n> · --concurrency <n> · --provider <p> · --model <m>
|
|
38
|
-
|
|
39
32
|
Flags de findings:
|
|
40
33
|
--decision <d> pending | execute | ignore
|
|
41
34
|
--exec <e> pending | in_progress | done | stale
|
package/src/cli/index.ts
CHANGED
|
@@ -38,7 +38,6 @@ import { ContextCommand } from "./commands/context-command";
|
|
|
38
38
|
import { DoctorCommand } from "./commands/doctor-command";
|
|
39
39
|
import { FindingsCommand } from "./commands/findings-command";
|
|
40
40
|
import { HelpCommand } from "./commands/help-command";
|
|
41
|
-
import { HuntCommand } from "./commands/hunt-command";
|
|
42
41
|
import { CommandRegistry } from "./commands/registry";
|
|
43
42
|
import { ReviewCommand } from "./commands/review-command";
|
|
44
43
|
import { ServerCommand } from "./commands/server-command";
|
|
@@ -58,7 +57,6 @@ function buildRegistry(): CommandRegistry {
|
|
|
58
57
|
return new CommandRegistry()
|
|
59
58
|
.register(new ReviewCommand(), { fallback: true })
|
|
60
59
|
.register(new HelpCommand())
|
|
61
|
-
.register(new HuntCommand())
|
|
62
60
|
.register(new ContextCommand())
|
|
63
61
|
.register(new FindingsCommand())
|
|
64
62
|
.register(new StatusCommand())
|
package/src/core/ai/ai-client.ts
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* Fachada de IA (`core/ai`) — fail-open, budget e uso.
|
|
3
3
|
*
|
|
4
4
|
* As features do core usam `generateObject`/`generateText` e tratam `ok:false`
|
|
5
|
-
* como "sem IA": a revisão determinística segue idêntica.
|
|
6
|
-
* `requireProvider` e falha com mensagem clara quando a IA não está configurada.
|
|
5
|
+
* como "sem IA": a revisão determinística segue idêntica.
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
8
|
import type { ZodType } from "zod";
|
|
@@ -11,7 +10,7 @@ import type { AiProviderOptions } from "../config";
|
|
|
11
10
|
import type { ProjectConfig } from "../project-config";
|
|
12
11
|
import { AiConfigurationError, errorMessage } from "./errors";
|
|
13
12
|
import { ModelFactory } from "./model-factory";
|
|
14
|
-
import type { AiResult,
|
|
13
|
+
import type { AiResult, AiUsage, ModelProvider, ModelRequest } from "./types";
|
|
15
14
|
import { AiUsageBudget } from "./usage-budget";
|
|
16
15
|
import { AiUsageRecorder } from "./usage-recorder";
|
|
17
16
|
|
|
@@ -30,8 +29,6 @@ export type ObjectRequest<T> = {
|
|
|
30
29
|
instructions?: string;
|
|
31
30
|
prompt: string;
|
|
32
31
|
outputSchema: ZodType<T>;
|
|
33
|
-
tools?: Record<string, AiTool>;
|
|
34
|
-
maxSteps?: number;
|
|
35
32
|
maxOutputTokens?: number;
|
|
36
33
|
temperature?: number;
|
|
37
34
|
/** Opções do AI SDK por provider (mescladas com a config do projeto). */
|
|
@@ -93,7 +90,7 @@ export class AiClient {
|
|
|
93
90
|
return this.factory.isConfigured;
|
|
94
91
|
}
|
|
95
92
|
|
|
96
|
-
/** Provider obrigatório
|
|
93
|
+
/** Provider obrigatório (lança quando a IA não está configurada). */
|
|
97
94
|
async requireProvider(model?: string): Promise<ModelProvider> {
|
|
98
95
|
if (this.override === null) {
|
|
99
96
|
throw new AiConfigurationError(
|
|
@@ -146,7 +143,6 @@ export class AiClient {
|
|
|
146
143
|
);
|
|
147
144
|
const result = await this.generateText("doctor-ping", {
|
|
148
145
|
prompt: "Responda apenas OK.",
|
|
149
|
-
maxSteps: 1,
|
|
150
146
|
maxOutputTokens: 4,
|
|
151
147
|
temperature: 0,
|
|
152
148
|
signal: AbortSignal.timeout(timeoutMs),
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Adapter do AI SDK (`generateText` + `Output.object`
|
|
2
|
+
* Adapter do AI SDK (`generateText` + `Output.object`).
|
|
3
3
|
*
|
|
4
4
|
* O resto do motor depende só de `ModelProvider`; trocar de SDK é trocar este
|
|
5
|
-
* arquivo.
|
|
6
|
-
* via `AbortSignal`.
|
|
5
|
+
* arquivo. Timeout/cancelamento vêm do `AbortSignal`.
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
|
-
import type { LanguageModel
|
|
10
|
-
import { generateText, Output
|
|
8
|
+
import type { LanguageModel } from "ai";
|
|
9
|
+
import { generateText, Output } from "ai";
|
|
11
10
|
import type { AiProviderKind } from "../config";
|
|
12
11
|
import { AiProviderError } from "./errors";
|
|
13
12
|
import { temperatureApplies } from "./provider-capabilities";
|
|
14
13
|
import {
|
|
15
|
-
type AiTool,
|
|
16
14
|
type AiUsage,
|
|
17
15
|
emptyUsage,
|
|
18
16
|
type ModelProvider,
|
|
@@ -20,19 +18,6 @@ import {
|
|
|
20
18
|
type ModelResponse,
|
|
21
19
|
} from "./types";
|
|
22
20
|
|
|
23
|
-
/** Converte as tools do motor para o formato do AI SDK. */
|
|
24
|
-
function toToolSet(tools: Record<string, AiTool>): ToolSet {
|
|
25
|
-
const entries = Object.entries(tools).map(([name, item]) => [
|
|
26
|
-
name,
|
|
27
|
-
tool({
|
|
28
|
-
description: item.description,
|
|
29
|
-
inputSchema: item.inputSchema,
|
|
30
|
-
execute: async (input: unknown) => item.execute(input),
|
|
31
|
-
}),
|
|
32
|
-
]);
|
|
33
|
-
return Object.fromEntries(entries) as ToolSet;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
21
|
/** Mapeia o uso do SDK de forma defensiva (campos opcionais por provider). */
|
|
37
22
|
export function mapUsage(raw: unknown): AiUsage {
|
|
38
23
|
const usage = (raw ?? {}) as Record<string, unknown>;
|
|
@@ -61,9 +46,6 @@ export function buildGenerateTextArgs<T>(
|
|
|
61
46
|
provider: AiProviderKind,
|
|
62
47
|
modelId: string,
|
|
63
48
|
) {
|
|
64
|
-
const tools = request.tools ? toToolSet(request.tools) : undefined;
|
|
65
|
-
const hasTools = tools !== undefined && Object.keys(tools).length > 0;
|
|
66
|
-
const maxSteps = request.maxSteps ?? 1;
|
|
67
49
|
const providerOptions = request.providerOptions
|
|
68
50
|
? (request.providerOptions as GenerateTextArgs["providerOptions"])
|
|
69
51
|
: undefined;
|
|
@@ -76,7 +58,6 @@ export function buildGenerateTextArgs<T>(
|
|
|
76
58
|
model,
|
|
77
59
|
system: request.instructions,
|
|
78
60
|
prompt: request.prompt,
|
|
79
|
-
...(hasTools ? { tools, stopWhen: stepCountIs(maxSteps) } : {}),
|
|
80
61
|
...(request.outputSchema
|
|
81
62
|
? { output: Output.object({ schema: request.outputSchema }) }
|
|
82
63
|
: {}),
|
|
@@ -108,7 +89,6 @@ export class AiSdkModelProvider implements ModelProvider {
|
|
|
108
89
|
return {
|
|
109
90
|
output,
|
|
110
91
|
usage: mapUsage(result.usage),
|
|
111
|
-
steps: result.steps?.length ?? 1,
|
|
112
92
|
finishReason: result.finishReason ?? "unknown",
|
|
113
93
|
};
|
|
114
94
|
} catch (error) {
|
|
@@ -23,7 +23,6 @@ export class AiContextSummarizer {
|
|
|
23
23
|
instructions: contextInstructions(),
|
|
24
24
|
prompt: contextPrompt(raw),
|
|
25
25
|
outputSchema: ContextSummarySchema,
|
|
26
|
-
maxSteps: 1,
|
|
27
26
|
});
|
|
28
27
|
if (!result.ok) return null;
|
|
29
28
|
return parseContextSummary({ ...result.output, available: true });
|
package/src/core/ai/index.ts
CHANGED
|
@@ -28,7 +28,6 @@ export {
|
|
|
28
28
|
type AiCandidate,
|
|
29
29
|
type AiClassification,
|
|
30
30
|
type AiVerdict,
|
|
31
|
-
type AiWatchdogReport,
|
|
32
31
|
type BriefPlan,
|
|
33
32
|
BriefPlanSchema,
|
|
34
33
|
CandidateSchema,
|
|
@@ -39,11 +38,9 @@ export {
|
|
|
39
38
|
type TechnicalSummaryPlan,
|
|
40
39
|
TechnicalSummarySchema,
|
|
41
40
|
VerifySchema,
|
|
42
|
-
WatchdogReportSchema,
|
|
43
41
|
} from "./schemas";
|
|
44
42
|
export {
|
|
45
43
|
type AiResult,
|
|
46
|
-
type AiTool,
|
|
47
44
|
type AiUsage,
|
|
48
45
|
addUsage,
|
|
49
46
|
emptyUsage,
|
|
@@ -20,7 +20,8 @@ export function preAnalysisInstructions(): string {
|
|
|
20
20
|
return buildSystem(
|
|
21
21
|
"Você faz uma pré-análise de possíveis violações de regras em um diff.",
|
|
22
22
|
[
|
|
23
|
-
"Produza candidatos apenas quando houver indício concreto.",
|
|
23
|
+
"Produza candidatos apenas quando houver indício concreto no trecho alterado.",
|
|
24
|
+
"O trecho fornecido é o diff (linhas alteradas); código não alterado é apenas contexto.",
|
|
24
25
|
"Use o texto de uma das regras fornecidas.",
|
|
25
26
|
"Confiança calibrada entre 0 e 1; prefira valores baixos na dúvida.",
|
|
26
27
|
"Não invente arquivos.",
|
|
@@ -36,7 +37,7 @@ export function preAnalysisPrompt(input: PreAnalysisPromptInput): string {
|
|
|
36
37
|
"## Regras",
|
|
37
38
|
...input.rules.map((rule) => `- [${rule.severity}] ${rule.text}`),
|
|
38
39
|
"",
|
|
39
|
-
"## Arquivos e trechos",
|
|
40
|
+
"## Arquivos e trechos alterados (diff)",
|
|
40
41
|
...input.files.flatMap((item) => [
|
|
41
42
|
`### ${item.file}`,
|
|
42
43
|
"```",
|
package/src/core/ai/schemas.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Schemas Zod da camada de IA.
|
|
3
3
|
*
|
|
4
|
-
* Espelham os contratos existentes (`ContextSummary
|
|
5
|
-
* planos das features. Todo schema é fechado por validação: saída inválida do
|
|
4
|
+
* Espelham os contratos existentes (`ContextSummary`) e os planos das features. Todo schema é fechado por validação: saída inválida do
|
|
6
5
|
* modelo vira falha fail-open, nunca dado corrompido.
|
|
7
6
|
*/
|
|
8
7
|
|
|
@@ -89,27 +88,6 @@ export const CandidateSchema = z.object({
|
|
|
89
88
|
});
|
|
90
89
|
export type AiCandidate = z.infer<typeof CandidateSchema>["candidates"][number];
|
|
91
90
|
|
|
92
|
-
/** Relatório de um watchdog (espelha `OUTPUT-FORMAT.md`). */
|
|
93
|
-
export const WatchdogReportSchema = z.object({
|
|
94
|
-
watchdogId: z.string(),
|
|
95
|
-
reviewedFiles: z.array(z.string()).default([]),
|
|
96
|
-
skipped: z.array(z.string()).default([]),
|
|
97
|
-
findings: z
|
|
98
|
-
.array(
|
|
99
|
-
z.object({
|
|
100
|
-
rule: z.string(),
|
|
101
|
-
severity: SeveritySchema,
|
|
102
|
-
file: z.string(),
|
|
103
|
-
line: z.number().int().positive().optional(),
|
|
104
|
-
message: z.string(),
|
|
105
|
-
action: z.string(),
|
|
106
|
-
evidence: z.string().optional(),
|
|
107
|
-
}),
|
|
108
|
-
)
|
|
109
|
-
.default([]),
|
|
110
|
-
});
|
|
111
|
-
export type AiWatchdogReport = z.infer<typeof WatchdogReportSchema>;
|
|
112
|
-
|
|
113
91
|
/** Veredito de verificação (confirma/refuta um finding ambíguo). */
|
|
114
92
|
export const VerifySchema = z.object({
|
|
115
93
|
verdicts: z
|
package/src/core/ai/types.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Contratos genéricos da camada de IA (biblioteca).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* harness nativo (`src/harness/`) consome estes contratos.
|
|
4
|
+
* Expõe modelo, uso e resultado; as features do core são as únicas consumidoras.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
import type { ZodType
|
|
7
|
+
import type { ZodType } from "zod";
|
|
9
8
|
import type { AiProviderKind, AiProviderOptions } from "../config";
|
|
10
9
|
|
|
11
10
|
/** Uso de tokens de uma chamada (ou acumulado de uma review). */
|
|
@@ -31,25 +30,14 @@ export function addUsage(a: AiUsage, b: AiUsage): AiUsage {
|
|
|
31
30
|
};
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
/** Tool exposta ao modelo (execução já vinculada a um contexto). */
|
|
35
|
-
export type AiTool = {
|
|
36
|
-
name: string;
|
|
37
|
-
description: string;
|
|
38
|
-
inputSchema: ZodTypeAny;
|
|
39
|
-
execute(input: unknown): Promise<string>;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
33
|
/** Pedido de geração de modelo (texto ou objeto estruturado). */
|
|
43
34
|
export type ModelRequest<T> = {
|
|
44
35
|
/** Instruções de sistema. */
|
|
45
36
|
instructions?: string;
|
|
46
37
|
/** Prompt do usuário. */
|
|
47
38
|
prompt: string;
|
|
48
|
-
/** Tools disponíveis para o loop do modelo. */
|
|
49
|
-
tools?: Record<string, AiTool>;
|
|
50
39
|
/** Schema de saída (quando ausente, a saída é texto). */
|
|
51
40
|
outputSchema?: ZodType<T>;
|
|
52
|
-
maxSteps?: number;
|
|
53
41
|
maxOutputTokens?: number;
|
|
54
42
|
temperature?: number;
|
|
55
43
|
/** Opções do AI SDK por provider (repassadas ao SDK sem interpretação). */
|
|
@@ -61,7 +49,6 @@ export type ModelRequest<T> = {
|
|
|
61
49
|
export type ModelResponse<T> = {
|
|
62
50
|
output: T;
|
|
63
51
|
usage: AiUsage;
|
|
64
|
-
steps: number;
|
|
65
52
|
finishReason: string;
|
|
66
53
|
};
|
|
67
54
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Orçamento de IA por review (chamadas e tokens).
|
|
3
3
|
*
|
|
4
4
|
* O budget é consultado antes de cada chamada e atualizado com o uso real. Ao
|
|
5
|
-
* estourar, o chamador decide parar (features do core)
|
|
5
|
+
* estourar, o chamador decide parar a operação (features do core).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { AiBudgetError } from "./errors";
|
package/src/core/ast/diff.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
AstSymbol,
|
|
15
15
|
} from "./types";
|
|
16
16
|
|
|
17
|
-
/** Um hunk do patch, com as linhas novas adicionadas. */
|
|
17
|
+
/** Um hunk do patch, com as linhas novas adicionadas e as âncoras de remoção. */
|
|
18
18
|
export type AstHunk = {
|
|
19
19
|
oldStart: number;
|
|
20
20
|
oldLines: number;
|
|
@@ -22,6 +22,11 @@ export type AstHunk = {
|
|
|
22
22
|
newLines: number;
|
|
23
23
|
/** Linhas novas (1-based) introduzidas pelo hunk. */
|
|
24
24
|
addedLines: number[];
|
|
25
|
+
/**
|
|
26
|
+
* Posições (1-based, no lado novo) onde linhas foram removidas. Uma remoção
|
|
27
|
+
* pura não tem linha nova, mas marca o ponto do arquivo que ela alterou.
|
|
28
|
+
*/
|
|
29
|
+
deletedAt: number[];
|
|
25
30
|
};
|
|
26
31
|
|
|
27
32
|
/** Regex do cabeçalho de hunk (`@@ -a,b +c,d @@`). */
|
|
@@ -52,6 +57,7 @@ export function parseHunks(patch: string): AstHunk[] {
|
|
|
52
57
|
newStart: Number.parseInt(match[3], 10),
|
|
53
58
|
newLines: countOf(match[4]),
|
|
54
59
|
addedLines: [],
|
|
60
|
+
deletedAt: [],
|
|
55
61
|
};
|
|
56
62
|
newLine = current.newStart;
|
|
57
63
|
hunks.push(current);
|
|
@@ -68,7 +74,11 @@ export function parseHunks(patch: string): AstHunk[] {
|
|
|
68
74
|
newLine++;
|
|
69
75
|
continue;
|
|
70
76
|
}
|
|
71
|
-
if (line.startsWith("-"))
|
|
77
|
+
if (line.startsWith("-")) {
|
|
78
|
+
// Remoção pura não consome linha nova: ancora no ponto atual do arquivo.
|
|
79
|
+
current.deletedAt.push(Math.max(1, newLine));
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
72
82
|
if (line.startsWith("\\")) continue;
|
|
73
83
|
if (line.startsWith(" ")) newLine++;
|
|
74
84
|
}
|
|
@@ -85,6 +95,31 @@ export function changedNewLines(patch: string): Set<number> {
|
|
|
85
95
|
return lines;
|
|
86
96
|
}
|
|
87
97
|
|
|
98
|
+
/** Âncoras de alteração do patch: adições e posições de remoção no lado novo. */
|
|
99
|
+
export type ChangedAnchors = {
|
|
100
|
+
/** Linhas novas (1-based) adicionadas. */
|
|
101
|
+
added: Set<number>;
|
|
102
|
+
/** Posições (1-based, lado novo) onde houve remoção. */
|
|
103
|
+
deletedAt: Set<number>;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/** Extrai as âncoras de alteração de um patch (falha aberta: patch vazio → vazio). */
|
|
107
|
+
export function changedAnchors(patch: string): ChangedAnchors {
|
|
108
|
+
const added = new Set<number>();
|
|
109
|
+
const deletedAt = new Set<number>();
|
|
110
|
+
for (const hunk of parseHunks(patch)) {
|
|
111
|
+
for (const line of hunk.addedLines) added.add(line);
|
|
112
|
+
for (const line of hunk.deletedAt) deletedAt.add(line);
|
|
113
|
+
}
|
|
114
|
+
return { added, deletedAt };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** União das âncoras (adições + remoções) — o que a mudança tocou no arquivo. */
|
|
118
|
+
export function changedAnchorLines(patch: string): Set<number> {
|
|
119
|
+
const { added, deletedAt } = changedAnchors(patch);
|
|
120
|
+
return new Set([...added, ...deletedAt]);
|
|
121
|
+
}
|
|
122
|
+
|
|
88
123
|
/** Chave estável de um símbolo (espécie + nome). */
|
|
89
124
|
function symbolKey(symbol: AstSymbol): string {
|
|
90
125
|
return `${symbol.kind}:${symbol.name}`;
|
package/src/core/ast/service.ts
CHANGED
|
@@ -162,7 +162,7 @@ export class AstService {
|
|
|
162
162
|
return buildImpact(analyses, importersByFile);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
/** Fatos estruturais do arquivo atual (usado
|
|
165
|
+
/** Fatos estruturais do arquivo atual (usado pelo motor e pelo server). */
|
|
166
166
|
async factsOf(filePath: string): Promise<AstFacts | null> {
|
|
167
167
|
if (!this.enabled) return null;
|
|
168
168
|
const content = this.readWorkingFile(filePath);
|
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
|
|