@payconductor/houndsman 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -4
- package/assets/grammars/tree-sitter-javascript.wasm +0 -0
- package/assets/grammars/tree-sitter-tsx.wasm +0 -0
- package/assets/grammars/tree-sitter-typescript.wasm +0 -0
- package/assets/grammars/web-tree-sitter.wasm +0 -0
- package/package.json +11 -3
- package/src/cli/commands/command.ts +5 -0
- package/src/cli/commands/doctor-command.ts +116 -24
- package/src/cli/commands/hunt-command.ts +25 -0
- package/src/cli/help.ts +17 -1
- package/src/cli/index.ts +10 -6
- package/src/core/ai/ai-client.ts +221 -0
- package/src/core/ai/ai-sdk-model-provider.ts +101 -0
- package/src/core/ai/errors.ts +36 -0
- package/src/core/ai/features/brief-enricher.ts +62 -0
- package/src/core/ai/features/classifier.ts +50 -0
- package/src/core/ai/features/context-summarizer.ts +31 -0
- package/src/core/ai/features/pre-analyzer.ts +51 -0
- package/src/core/ai/features/verifier.ts +45 -0
- package/src/core/ai/index.ts +56 -0
- package/src/core/ai/model-factory.ts +137 -0
- package/src/core/ai/prompts/base.ts +36 -0
- package/src/core/ai/prompts/brief.ts +87 -0
- package/src/core/ai/prompts/classification.ts +36 -0
- package/src/core/ai/prompts/context.ts +29 -0
- package/src/core/ai/prompts/pre-analysis.ts +49 -0
- package/src/core/ai/prompts/verify.ts +40 -0
- package/src/core/ai/schemas.ts +110 -0
- package/src/core/ai/types.ts +76 -0
- package/src/core/ai/usage-budget.ts +75 -0
- package/src/core/ai/usage-recorder.ts +79 -0
- package/src/core/assets.ts +16 -0
- package/src/core/ast/diff.ts +220 -0
- package/src/core/ast/impact.ts +127 -0
- package/src/core/ast/parser.ts +167 -0
- package/src/core/ast/service.ts +233 -0
- package/src/core/ast/symbols.ts +294 -0
- package/src/core/ast/types.ts +125 -0
- package/src/core/brief.ts +138 -0
- package/src/core/commands/findings.ts +59 -1
- package/src/core/commands/review.ts +2 -0
- package/src/core/commands/scaffold.ts +159 -10
- package/src/core/commands/update.ts +5 -3
- package/src/core/config.ts +85 -0
- package/src/core/defaults.ts +27 -0
- package/src/core/domain/watchdog.ts +43 -5
- package/src/core/engine/houndsman-engine.ts +16 -1
- package/src/core/engine/review-scope.ts +52 -6
- package/src/core/engine/review-session.ts +27 -2
- package/src/core/env.ts +101 -0
- package/src/core/file-classifier.ts +25 -4
- package/src/core/jev/jev-client.ts +8 -7
- package/src/core/jev/jev-verify.ts +2 -1
- package/src/core/logging/index.ts +17 -0
- package/src/core/logging/logger.ts +187 -0
- package/src/core/orchestrator.ts +357 -9
- package/src/core/project-config.ts +123 -1
- package/src/core/registry-check.ts +148 -6
- package/src/core/renderers/manifest-writer.ts +33 -0
- package/src/core/rule-text.ts +97 -0
- package/src/core/services/file-classifier.ts +10 -3
- package/src/core/services/verifier.ts +14 -3
- package/src/core/storage/paths.ts +6 -5
- package/src/core/storage/types.ts +6 -0
- package/src/core/storage/work-area.ts +12 -0
- package/src/core/types.ts +37 -1
- package/src/core/verify.ts +17 -5
- package/src/harness/README.md +15 -0
- package/src/harness/agent/agent-context.ts +33 -0
- package/src/harness/agent/agent-errors.ts +30 -0
- package/src/harness/agent/agent-factory.ts +45 -0
- package/src/harness/agent/agent-reporter.ts +34 -0
- package/src/harness/agent/agent-result.ts +16 -0
- package/src/harness/agent/agent-runner.ts +32 -0
- package/src/harness/agent/agent-scheduler.ts +37 -0
- package/src/harness/agent/review-agent.ts +391 -0
- package/src/harness/config/harness-config.ts +84 -0
- package/src/harness/hunt/hunt-harness.ts +83 -0
- package/src/harness/hunt/hunt-options.ts +75 -0
- package/src/harness/hunt/hunt-result.ts +17 -0
- package/src/harness/hunt/hunt-session.ts +372 -0
- package/src/harness/hunt/review-planner.ts +98 -0
- package/src/harness/index.ts +36 -0
- package/src/harness/investigation/agent-state.ts +134 -0
- package/src/harness/investigation/evidence-verifier.ts +129 -0
- package/src/harness/investigation/evidence.ts +110 -0
- package/src/harness/investigation/finding-critic.ts +96 -0
- package/src/harness/investigation/investigation-planner.ts +118 -0
- package/src/harness/investigation/investigation-task.ts +10 -0
- package/src/harness/investigation/review-journal.ts +87 -0
- package/src/harness/investigation/rule-analyzer.ts +178 -0
- package/src/harness/investigation/rule-signals.ts +110 -0
- package/src/harness/prompts/agent-schemas.ts +57 -0
- package/src/harness/prompts/reviewer-prompt-builder.ts +101 -0
- package/src/harness/prompts/states/conclude-prompt.ts +16 -0
- package/src/harness/prompts/states/investigate-prompt.ts +37 -0
- package/src/harness/prompts/states/orient-prompt.ts +15 -0
- package/src/harness/prompts/states/verify-prompt.ts +22 -0
- package/src/harness/tools/agent-tool.ts +102 -0
- package/src/harness/tools/builtin/changed-symbols-tool.ts +48 -0
- package/src/harness/tools/builtin/doc-context-tool.ts +74 -0
- package/src/harness/tools/builtin/file-outline-tool.ts +66 -0
- package/src/harness/tools/builtin/find-references-tool.ts +66 -0
- package/src/harness/tools/builtin/find-symbol-tool.ts +91 -0
- package/src/harness/tools/builtin/helpers.ts +115 -0
- package/src/harness/tools/builtin/impact-tool.ts +48 -0
- package/src/harness/tools/builtin/imports-exports-tool.ts +53 -0
- package/src/harness/tools/builtin/index.ts +59 -0
- package/src/harness/tools/builtin/list-files-tool.ts +85 -0
- package/src/harness/tools/builtin/pr-context-tool.ts +41 -0
- package/src/harness/tools/builtin/read-file-range-tool.ts +73 -0
- package/src/harness/tools/builtin/rule-signals-tool.ts +36 -0
- package/src/harness/tools/builtin/search-code-tool.ts +65 -0
- package/src/harness/tools/builtin/symbol-context-tool.ts +106 -0
- package/src/harness/tools/builtin/tests-for-symbol-tool.ts +61 -0
- package/src/harness/tools/path-guard.ts +90 -0
- package/src/harness/tools/result-limiter.ts +49 -0
- package/src/harness/tools/secret-redactor.ts +54 -0
- package/src/harness/tools/tool-cache.ts +50 -0
- package/src/harness/tools/tool-call-budget.ts +60 -0
- package/src/harness/tools/tool-context.ts +5 -0
- package/src/harness/tools/tool-middleware.ts +105 -0
- package/src/harness/tools/tool-policy.ts +51 -0
- package/src/harness/tools/tool-registry.ts +112 -0
- package/src/harness/tools/tool-result.ts +15 -0
- package/src/harness/tools/toolbox.ts +36 -0
- package/src/harness/validation/finding-validator.ts +119 -0
- package/src/index.ts +46 -0
- package/src/server/client/utils/constants.ts +27 -5
- package/src/server/client/utils/types.ts +6 -0
- package/src/server/index.ts +3 -2
- package/src/skill/OUTPUT-FORMAT.md +12 -0
- package/src/skill/SETUP.md +6 -2
- package/src/skill/hunt.md +14 -2
- package/src/skill/verify.md +1 -1
- package/src/tests/__snapshots__/baseline.test.ts.snap +96 -14
- package/src/tests/ai-core.test.ts +120 -0
- package/src/tests/ai-features.test.ts +183 -0
- package/src/tests/ast.test.ts +335 -0
- package/src/tests/env.test.ts +69 -0
- package/src/tests/fallback.test.ts +172 -0
- package/src/tests/fixtures/fake-agent-tools.ts +69 -0
- package/src/tests/fixtures/fake-investigation.ts +64 -0
- package/src/tests/fixtures/fake-model-provider.ts +48 -0
- package/src/tests/harness-agent.test.ts +122 -0
- package/src/tests/harness-investigation.test.ts +206 -0
- package/src/tests/harness-tools.test.ts +148 -0
- package/src/tests/hunt-command.test.ts +212 -0
- package/src/tests/logger.test.ts +83 -0
- package/src/tests/review-scope.test.ts +11 -11
- package/src/tests/rule-analyzer.test.ts +61 -0
- package/src/tests/tool-contracts.test.ts +82 -0
- package/src/tests/verify.test.ts +1 -1
package/src/core/types.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* `Rule` e `WatchdogMeta` descrevem as regras consumidas pela renderização.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import type { BriefPlan } from "./ai/schemas";
|
|
10
|
+
import type { AstFileBrief } from "./ast/types";
|
|
9
11
|
import type { JevRuleSpec } from "./jev/jev-types";
|
|
10
12
|
|
|
11
13
|
/** Severidade de um achado. `blocking` bloqueia o merge. */
|
|
@@ -27,6 +29,19 @@ export const SEVERITY_EMOJI: Record<Severity, string> = {
|
|
|
27
29
|
nit: "🔎",
|
|
28
30
|
};
|
|
29
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Sinais opcionais de uma regra: ajudam o `RuleAnalyzer` a transformar texto
|
|
34
|
+
* em checagens concretas (símbolos, imports, decorators, paths e tools).
|
|
35
|
+
*/
|
|
36
|
+
export type RuleHints = {
|
|
37
|
+
symbols?: string[];
|
|
38
|
+
imports?: string[];
|
|
39
|
+
decorators?: string[];
|
|
40
|
+
paths?: string[];
|
|
41
|
+
/** Nomes de tools sugeridos (ex.: `symbol_context`). */
|
|
42
|
+
checks?: string[];
|
|
43
|
+
};
|
|
44
|
+
|
|
30
45
|
/**
|
|
31
46
|
* Regra do watchdog: severidade + texto. O texto declara a polaridade de forma
|
|
32
47
|
* explícita — `Promover:` (algo que DEVE acontecer) ou `Evitar:`/`Não`/`Nunca`
|
|
@@ -39,6 +54,7 @@ export type Rule = {
|
|
|
39
54
|
severity: Severity;
|
|
40
55
|
text: string;
|
|
41
56
|
jev?: JevRuleSpec;
|
|
57
|
+
hints?: RuleHints;
|
|
42
58
|
};
|
|
43
59
|
|
|
44
60
|
/**
|
|
@@ -48,9 +64,15 @@ export type Rule = {
|
|
|
48
64
|
*
|
|
49
65
|
* - `filename` (padrão) → padrão aplicado ao caminho do arquivo.
|
|
50
66
|
* - `content` → padrão aplicado ao conteúdo do arquivo.
|
|
67
|
+
* - `symbol` → padrão aplicado aos nomes de símbolos (AST).
|
|
68
|
+
* - `import` → padrão aplicado às fontes/nomes de imports (AST).
|
|
69
|
+
* - `export` → padrão aplicado aos nomes exportados (AST).
|
|
70
|
+
* - `decorator` → padrão aplicado aos decorators (AST).
|
|
71
|
+
*
|
|
72
|
+
* Regras AST exigem grammar disponível; sem ela, a regra simplesmente não casa.
|
|
51
73
|
*/
|
|
52
74
|
export type ClassifyRule = {
|
|
53
|
-
type?: "filename" | "content";
|
|
75
|
+
type?: "filename" | "content" | "symbol" | "import" | "export" | "decorator";
|
|
54
76
|
value: string;
|
|
55
77
|
};
|
|
56
78
|
|
|
@@ -116,6 +138,10 @@ export type WatchdogRenderArgs = {
|
|
|
116
138
|
docs: readonly string[];
|
|
117
139
|
context?: WatchdogRenderContext | null;
|
|
118
140
|
candidates?: readonly WatchdogRenderCandidate[];
|
|
141
|
+
/** Estrutura/delta dos arquivos selecionados (AST). */
|
|
142
|
+
ast?: readonly AstFileBrief[];
|
|
143
|
+
/** Plano de revisão sugerido por IA (nunca substitui as regras). */
|
|
144
|
+
aiPlan?: BriefPlan;
|
|
119
145
|
};
|
|
120
146
|
|
|
121
147
|
/** Definição completa de um watchdog — 1 por arquivo em `project/watchdogs/`. */
|
|
@@ -126,6 +152,16 @@ export type WatchdogDefinition = WatchdogMeta & {
|
|
|
126
152
|
description: string;
|
|
127
153
|
/** Watchdog deve checar breaking change em arquivos deletados do seu domínio. */
|
|
128
154
|
breakingChange?: boolean;
|
|
155
|
+
/**
|
|
156
|
+
* Watchdog de último recurso: só assume arquivos que nenhum outro casou.
|
|
157
|
+
* Útil para padrões gerais/lógica; `classify` é ignorado.
|
|
158
|
+
*/
|
|
159
|
+
fallback?: boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Permite findings emergentes (`Lógica:`/`Domínio:`) fora das regras
|
|
162
|
+
* explícitas. Exige evidência e severidade máxima `attention`.
|
|
163
|
+
*/
|
|
164
|
+
allowEmergentFindings?: boolean;
|
|
129
165
|
/** Prioridade de arquivos/paths (ex-`O Que Revisar`). Opcional. */
|
|
130
166
|
reviewFocus?: readonly string[];
|
|
131
167
|
/** O que NUNCA carregar/enviar (ex-`NÃO carregar`). Opcional. */
|
package/src/core/verify.ts
CHANGED
|
@@ -25,8 +25,8 @@ import type { Severity } from "./types";
|
|
|
25
25
|
/** Resultado da verificação de um finding. */
|
|
26
26
|
export type VerifyStatus = "resolved" | "persisting" | "uncertain" | "file-missing";
|
|
27
27
|
|
|
28
|
-
/** Mecanismo que decidiu o status (reancoragem local ou
|
|
29
|
-
export type VerifyBasis = "anchor" | "jev";
|
|
28
|
+
/** Mecanismo que decidiu o status (reancoragem local, Jev ou IA). */
|
|
29
|
+
export type VerifyBasis = "anchor" | "jev" | "ai";
|
|
30
30
|
|
|
31
31
|
/** Valores canônicos de status (ordem de exibição). */
|
|
32
32
|
export const VERIFY_STATUSES: readonly VerifyStatus[] = [
|
|
@@ -121,21 +121,24 @@ export function verifyLocal(
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
|
-
* Tier 1 — aplica
|
|
124
|
+
* Tier 1 genérico — aplica confirmações externas (Jev ou IA) aos casos ambíguos.
|
|
125
125
|
* `confidence[id] >= threshold` ⇒ a violação persiste; abaixo ⇒ resolvida.
|
|
126
126
|
* Ids sem resposta mantêm o provisório local (fail-open). Devolve o nº aplicado.
|
|
127
|
+
*
|
|
128
|
+
* `basis` identifica a fonte da decisão (`jev` ou `ai`).
|
|
127
129
|
*/
|
|
128
|
-
export function
|
|
130
|
+
export function applyConfidence(
|
|
129
131
|
entries: VerifyEntry[],
|
|
130
132
|
confidence: Map<string, number>,
|
|
131
133
|
threshold: number,
|
|
134
|
+
basis: VerifyBasis = "jev",
|
|
132
135
|
): number {
|
|
133
136
|
let applied = 0;
|
|
134
137
|
for (const entry of entries) {
|
|
135
138
|
if (!entry.ambiguous) continue;
|
|
136
139
|
const value = confidence.get(entry.id);
|
|
137
140
|
if (value === undefined) continue;
|
|
138
|
-
entry.basis =
|
|
141
|
+
entry.basis = basis;
|
|
139
142
|
entry.confidence = value;
|
|
140
143
|
entry.status = value >= threshold ? "persisting" : "resolved";
|
|
141
144
|
applied++;
|
|
@@ -143,6 +146,15 @@ export function applyJev(
|
|
|
143
146
|
return applied;
|
|
144
147
|
}
|
|
145
148
|
|
|
149
|
+
/** Compatibilidade: confirmação via Jev. */
|
|
150
|
+
export function applyJev(
|
|
151
|
+
entries: VerifyEntry[],
|
|
152
|
+
confidence: Map<string, number>,
|
|
153
|
+
threshold: number,
|
|
154
|
+
): number {
|
|
155
|
+
return applyConfidence(entries, confidence, threshold, "jev");
|
|
156
|
+
}
|
|
157
|
+
|
|
146
158
|
export type VerifySummary = Record<VerifyStatus, number>;
|
|
147
159
|
|
|
148
160
|
/** Contagem por status. */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# `src/harness` — harness nativo do houndsman
|
|
2
|
+
|
|
3
|
+
Este diretório é o **runtime de agentes do próprio houndsman** (`houndsman hunt`):
|
|
4
|
+
tools read-only, investigação, máquina de estados e o agente revisor.
|
|
5
|
+
|
|
6
|
+
> O **harness externo** (skill `/houndsman` para ClaudeCode/Copilot/agents) continua em
|
|
7
|
+
> `src/skill/`. Os dois coexistem; o nativo é opt-in.
|
|
8
|
+
|
|
9
|
+
Regras de dependência:
|
|
10
|
+
|
|
11
|
+
- `harness/` pode importar `core/` (inclusive `core/ai`).
|
|
12
|
+
- `core/` **nunca** importa `harness/`.
|
|
13
|
+
- `cli/` importa ambos.
|
|
14
|
+
|
|
15
|
+
Documentação detalhada: [`docs/HARNESS.md`](../../docs/HARNESS.md).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contexto de um agente de revisão (um watchdog).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AstAnalysis, AstImpact } from "../../core/ast/types";
|
|
6
|
+
import type { DiffSource } from "../../core/diff-source";
|
|
7
|
+
import type { Watchdog } from "../../core/domain/watchdog";
|
|
8
|
+
import type { WatchdogRenderContext } from "../../core/types";
|
|
9
|
+
import type { RuleAnalysis } from "../investigation/rule-analyzer";
|
|
10
|
+
|
|
11
|
+
/** Dados imutáveis entregues ao agente. */
|
|
12
|
+
export type AgentContext = {
|
|
13
|
+
watchdog: Watchdog;
|
|
14
|
+
projectName: string;
|
|
15
|
+
branch: string;
|
|
16
|
+
/** Arquivos do domínio (dentro do orçamento). */
|
|
17
|
+
files: readonly string[];
|
|
18
|
+
/** Arquivos do domínio fora do orçamento (não ler; registrar em skipped). */
|
|
19
|
+
overflow: readonly string[];
|
|
20
|
+
deleted: readonly string[];
|
|
21
|
+
docs: readonly string[];
|
|
22
|
+
context?: WatchdogRenderContext | null;
|
|
23
|
+
ast: ReadonlyMap<string, AstAnalysis>;
|
|
24
|
+
impact?: AstImpact;
|
|
25
|
+
/** Caminho de saída do findings (staging). */
|
|
26
|
+
outputPath: string;
|
|
27
|
+
maxSteps: number;
|
|
28
|
+
maxCalls: number;
|
|
29
|
+
repoRoot: string;
|
|
30
|
+
diff: DiffSource;
|
|
31
|
+
/** Análises determinísticas das regras do watchdog. */
|
|
32
|
+
rules: readonly RuleAnalysis[];
|
|
33
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Erros do harness nativo.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Falha de configuração/execução do agente. */
|
|
6
|
+
export class AgentError extends Error {
|
|
7
|
+
constructor(
|
|
8
|
+
message: string,
|
|
9
|
+
readonly watchdogId?: string,
|
|
10
|
+
) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "AgentError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Orçamento de IA/tools esgotado durante a investigação. */
|
|
17
|
+
export class AgentBudgetError extends AgentError {
|
|
18
|
+
constructor(message: string, watchdogId?: string) {
|
|
19
|
+
super(message, watchdogId);
|
|
20
|
+
this.name = "AgentBudgetError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Saída do modelo inválida após as tentativas permitidas. */
|
|
25
|
+
export class AgentOutputError extends AgentError {
|
|
26
|
+
constructor(message: string, watchdogId?: string) {
|
|
27
|
+
super(message, watchdogId);
|
|
28
|
+
this.name = "AgentOutputError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory de agentes — injeta provider, tools e limites resolvidos.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ModelProvider } from "../../core/ai";
|
|
6
|
+
import type { AstService } from "../../core/ast/service";
|
|
7
|
+
import type { HarnessConfig } from "../config/harness-config";
|
|
8
|
+
import type { ToolPolicy } from "../tools/tool-policy";
|
|
9
|
+
import type { ToolRegistry } from "../tools/tool-registry";
|
|
10
|
+
import type { AgentContext } from "./agent-context";
|
|
11
|
+
import type { AgentReporter } from "./agent-reporter";
|
|
12
|
+
import { ReviewAgent } from "./review-agent";
|
|
13
|
+
|
|
14
|
+
/** Dependências compartilhadas por todos os agentes. */
|
|
15
|
+
export type AgentFactoryDeps = {
|
|
16
|
+
provider: ModelProvider;
|
|
17
|
+
registry: ToolRegistry;
|
|
18
|
+
policy?: ToolPolicy;
|
|
19
|
+
reporter: AgentReporter;
|
|
20
|
+
config: HarnessConfig;
|
|
21
|
+
ast: AstService;
|
|
22
|
+
readFile?: (path: string) => string | null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Cria `ReviewAgent`s a partir do contexto de cada watchdog. */
|
|
26
|
+
export class AgentFactory {
|
|
27
|
+
constructor(private readonly deps: AgentFactoryDeps) {}
|
|
28
|
+
|
|
29
|
+
/** Instancia um agente para o contexto. */
|
|
30
|
+
create(context: AgentContext): ReviewAgent {
|
|
31
|
+
const { config } = this.deps;
|
|
32
|
+
return new ReviewAgent({
|
|
33
|
+
context,
|
|
34
|
+
provider: this.deps.provider,
|
|
35
|
+
registry: this.deps.registry,
|
|
36
|
+
policy: this.deps.policy,
|
|
37
|
+
reporter: this.deps.reporter,
|
|
38
|
+
maxOutputTokens: config.maxOutputTokensFor(context.watchdog.id),
|
|
39
|
+
temperature: config.temperature,
|
|
40
|
+
timeoutMs: config.timeoutMs,
|
|
41
|
+
ast: this.deps.ast,
|
|
42
|
+
readFile: this.deps.readFile,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reporter de progresso de um agente.
|
|
3
|
+
*
|
|
4
|
+
* O journal é sempre coletado (`harness-transcript.json`); o console é opcional.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { LoggerLike } from "../../core/logging";
|
|
8
|
+
import { ReviewJournal } from "../investigation/review-journal";
|
|
9
|
+
|
|
10
|
+
/** Interface de progresso. */
|
|
11
|
+
export interface AgentReporter {
|
|
12
|
+
readonly journal: ReviewJournal;
|
|
13
|
+
progress(message: string): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Reporter silencioso (testes). */
|
|
17
|
+
export class SilentReporter implements AgentReporter {
|
|
18
|
+
readonly journal = new ReviewJournal();
|
|
19
|
+
|
|
20
|
+
progress(): void {
|
|
21
|
+
// intencionalmente silencioso
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Reporter apoiado no logger único do CLI. */
|
|
26
|
+
export class LoggerReporter implements AgentReporter {
|
|
27
|
+
readonly journal = new ReviewJournal();
|
|
28
|
+
|
|
29
|
+
constructor(private readonly logger: LoggerLike) {}
|
|
30
|
+
|
|
31
|
+
progress(message: string): void {
|
|
32
|
+
this.logger.progress(message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resultado de um agente (watchdog).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AiUsage } from "../../core/ai";
|
|
6
|
+
import type { WatchdogReport } from "../../core/report-builder";
|
|
7
|
+
|
|
8
|
+
/** Resultado da execução de um watchdog. */
|
|
9
|
+
export type AgentResult = {
|
|
10
|
+
watchdogId: string;
|
|
11
|
+
status: "done" | "skipped" | "failed";
|
|
12
|
+
report?: WatchdogReport;
|
|
13
|
+
usage: AiUsage;
|
|
14
|
+
error?: string;
|
|
15
|
+
reason?: string;
|
|
16
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executor de um agente — retry curto para falhas transitórias.
|
|
3
|
+
*
|
|
4
|
+
* 429/5xx/timeout são tentados uma vez mais; 4xx não. O resultado é sempre
|
|
5
|
+
* estruturado (`failed` com motivo), nunca uma exceção solta.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentContext } from "./agent-context";
|
|
9
|
+
import type { AgentFactory } from "./agent-factory";
|
|
10
|
+
import type { AgentResult } from "./agent-result";
|
|
11
|
+
import type { ReviewAgentOutcome } from "./review-agent";
|
|
12
|
+
|
|
13
|
+
/** Executor com retry. */
|
|
14
|
+
export class AgentRunner {
|
|
15
|
+
constructor(private readonly factory: AgentFactory) {}
|
|
16
|
+
|
|
17
|
+
/** Executa um agente (com uma retentativa em falha transitória). */
|
|
18
|
+
async run(context: AgentContext): Promise<ReviewAgentOutcome> {
|
|
19
|
+
const first = await this.factory.create(context).run();
|
|
20
|
+
if (first.status !== "failed" || !isTransient(first.error)) return first;
|
|
21
|
+
const second = await this.factory.create(context).run();
|
|
22
|
+
return second;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Erro transitório (rate limit, 5xx, timeout). */
|
|
27
|
+
function isTransient(message: string | undefined): boolean {
|
|
28
|
+
if (!message) return false;
|
|
29
|
+
return /429|5\d\d|timeout|temporar|ECONNRESET|ETIMEDOUT|abort/i.test(message);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type { AgentResult };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduler de agentes — concorrência limitada (default 2).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AgentContext } from "./agent-context";
|
|
6
|
+
import type { AgentReporter } from "./agent-reporter";
|
|
7
|
+
import type { AgentRunner } from "./agent-runner";
|
|
8
|
+
import type { ReviewAgentOutcome } from "./review-agent";
|
|
9
|
+
|
|
10
|
+
/** Executa agentes com concorrência limitada. */
|
|
11
|
+
export class AgentScheduler {
|
|
12
|
+
constructor(
|
|
13
|
+
private readonly runner: AgentRunner,
|
|
14
|
+
private readonly concurrency: number,
|
|
15
|
+
private readonly reporter?: AgentReporter,
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
/** Executa todos os contextos e devolve os resultados na ordem original. */
|
|
19
|
+
async run(contexts: readonly AgentContext[]): Promise<ReviewAgentOutcome[]> {
|
|
20
|
+
const results = new Array<ReviewAgentOutcome>(contexts.length);
|
|
21
|
+
let cursor = 0;
|
|
22
|
+
const workers = Math.max(1, Math.min(this.concurrency, contexts.length));
|
|
23
|
+
|
|
24
|
+
const worker = async (): Promise<void> => {
|
|
25
|
+
while (cursor < contexts.length) {
|
|
26
|
+
const index = cursor++;
|
|
27
|
+
const context = contexts[index];
|
|
28
|
+
if (!context) return;
|
|
29
|
+
this.reporter?.progress(`[${context.watchdog.id}] investigando...`);
|
|
30
|
+
results[index] = await this.runner.run(context);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
await Promise.all(Array.from({ length: workers }, () => worker()));
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
}
|