@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.
Files changed (118) hide show
  1. package/README.md +4 -19
  2. package/package.json +1 -1
  3. package/src/cli/help.ts +0 -7
  4. package/src/cli/index.ts +0 -2
  5. package/src/core/ai/ai-client.ts +3 -7
  6. package/src/core/ai/ai-sdk-model-provider.ts +4 -24
  7. package/src/core/ai/features/brief-enricher.ts +0 -1
  8. package/src/core/ai/features/classifier.ts +0 -1
  9. package/src/core/ai/features/context-summarizer.ts +0 -1
  10. package/src/core/ai/features/pre-analyzer.ts +0 -1
  11. package/src/core/ai/features/technical-summarizer.ts +0 -1
  12. package/src/core/ai/features/verifier.ts +0 -1
  13. package/src/core/ai/index.ts +0 -3
  14. package/src/core/ai/prompts/pre-analysis.ts +3 -2
  15. package/src/core/ai/schemas.ts +1 -23
  16. package/src/core/ai/types.ts +2 -15
  17. package/src/core/ai/usage-budget.ts +1 -1
  18. package/src/core/ast/diff.ts +37 -2
  19. package/src/core/ast/service.ts +1 -1
  20. package/src/core/brief.ts +54 -5
  21. package/src/core/changed-scope.ts +207 -0
  22. package/src/core/commands/scaffold.ts +1 -18
  23. package/src/core/config.ts +2 -15
  24. package/src/core/defaults.ts +0 -6
  25. package/src/core/engine/review-scope.ts +55 -8
  26. package/src/core/jev/jev-detector.ts +7 -2
  27. package/src/core/logging/index.ts +1 -1
  28. package/src/core/logging/logger.ts +3 -3
  29. package/src/core/orchestrator.ts +19 -14
  30. package/src/core/project-config.ts +0 -28
  31. package/src/core/registry-check.ts +2 -12
  32. package/src/core/rule-text.ts +3 -55
  33. package/src/core/services/git-repository.ts +8 -0
  34. package/src/core/storage/types.ts +0 -1
  35. package/src/core/types.ts +3 -14
  36. package/src/index.ts +25 -3
  37. package/src/server/client/utils/constants.ts +3 -6
  38. package/src/server/client/utils/types.ts +0 -1
  39. package/src/skill/OUTPUT-FORMAT.md +5 -0
  40. package/src/skill/README.md +2 -1
  41. package/src/skill/SETUP.md +1 -1
  42. package/src/skill/fix.md +1 -1
  43. package/src/skill/hunt.md +2 -6
  44. package/src/tests/__snapshots__/baseline.test.ts.snap +50 -32
  45. package/src/tests/changed-scope.test.ts +197 -0
  46. package/src/tests/fallback.test.ts +1 -96
  47. package/src/tests/fixtures/fake-model-provider.ts +0 -1
  48. package/src/tests/review-scope.test.ts +33 -0
  49. package/src/tests/worktree.test.ts +1 -3
  50. package/src/cli/commands/hunt-command.ts +0 -51
  51. package/src/harness/README.md +0 -15
  52. package/src/harness/agent/agent-context.ts +0 -33
  53. package/src/harness/agent/agent-errors.ts +0 -30
  54. package/src/harness/agent/agent-factory.ts +0 -46
  55. package/src/harness/agent/agent-reporter.ts +0 -34
  56. package/src/harness/agent/agent-result.ts +0 -16
  57. package/src/harness/agent/agent-runner.ts +0 -32
  58. package/src/harness/agent/agent-scheduler.ts +0 -37
  59. package/src/harness/agent/review-agent.ts +0 -401
  60. package/src/harness/config/harness-config.ts +0 -89
  61. package/src/harness/hunt/hunt-harness.ts +0 -83
  62. package/src/harness/hunt/hunt-options.ts +0 -92
  63. package/src/harness/hunt/hunt-result.ts +0 -17
  64. package/src/harness/hunt/hunt-session.ts +0 -359
  65. package/src/harness/hunt/review-planner.ts +0 -98
  66. package/src/harness/index.ts +0 -36
  67. package/src/harness/investigation/agent-state.ts +0 -134
  68. package/src/harness/investigation/evidence-verifier.ts +0 -129
  69. package/src/harness/investigation/evidence.ts +0 -110
  70. package/src/harness/investigation/finding-critic.ts +0 -96
  71. package/src/harness/investigation/investigation-planner.ts +0 -118
  72. package/src/harness/investigation/investigation-task.ts +0 -10
  73. package/src/harness/investigation/review-journal.ts +0 -87
  74. package/src/harness/investigation/rule-analyzer.ts +0 -178
  75. package/src/harness/investigation/rule-signals.ts +0 -110
  76. package/src/harness/prompts/agent-schemas.ts +0 -57
  77. package/src/harness/prompts/reviewer-prompt-builder.ts +0 -101
  78. package/src/harness/prompts/states/conclude-prompt.ts +0 -16
  79. package/src/harness/prompts/states/investigate-prompt.ts +0 -37
  80. package/src/harness/prompts/states/orient-prompt.ts +0 -15
  81. package/src/harness/prompts/states/verify-prompt.ts +0 -22
  82. package/src/harness/tools/agent-tool.ts +0 -102
  83. package/src/harness/tools/builtin/changed-symbols-tool.ts +0 -48
  84. package/src/harness/tools/builtin/doc-context-tool.ts +0 -74
  85. package/src/harness/tools/builtin/file-outline-tool.ts +0 -66
  86. package/src/harness/tools/builtin/find-references-tool.ts +0 -66
  87. package/src/harness/tools/builtin/find-symbol-tool.ts +0 -91
  88. package/src/harness/tools/builtin/helpers.ts +0 -115
  89. package/src/harness/tools/builtin/impact-tool.ts +0 -48
  90. package/src/harness/tools/builtin/imports-exports-tool.ts +0 -53
  91. package/src/harness/tools/builtin/index.ts +0 -59
  92. package/src/harness/tools/builtin/list-files-tool.ts +0 -85
  93. package/src/harness/tools/builtin/pr-context-tool.ts +0 -41
  94. package/src/harness/tools/builtin/read-file-range-tool.ts +0 -73
  95. package/src/harness/tools/builtin/rule-signals-tool.ts +0 -36
  96. package/src/harness/tools/builtin/search-code-tool.ts +0 -65
  97. package/src/harness/tools/builtin/symbol-context-tool.ts +0 -106
  98. package/src/harness/tools/builtin/tests-for-symbol-tool.ts +0 -61
  99. package/src/harness/tools/path-guard.ts +0 -90
  100. package/src/harness/tools/result-limiter.ts +0 -49
  101. package/src/harness/tools/secret-redactor.ts +0 -54
  102. package/src/harness/tools/tool-cache.ts +0 -50
  103. package/src/harness/tools/tool-call-budget.ts +0 -60
  104. package/src/harness/tools/tool-context.ts +0 -5
  105. package/src/harness/tools/tool-middleware.ts +0 -105
  106. package/src/harness/tools/tool-policy.ts +0 -51
  107. package/src/harness/tools/tool-registry.ts +0 -112
  108. package/src/harness/tools/tool-result.ts +0 -15
  109. package/src/harness/tools/toolbox.ts +0 -36
  110. package/src/harness/validation/finding-validator.ts +0 -119
  111. package/src/tests/fixtures/fake-agent-tools.ts +0 -69
  112. package/src/tests/fixtures/fake-investigation.ts +0 -64
  113. package/src/tests/harness-agent.test.ts +0 -141
  114. package/src/tests/harness-investigation.test.ts +0 -206
  115. package/src/tests/harness-tools.test.ts +0 -148
  116. package/src/tests/hunt-command.test.ts +0 -212
  117. package/src/tests/rule-analyzer.test.ts +0 -61
  118. package/src/tests/tool-contracts.test.ts +0 -82
@@ -1,16 +1,10 @@
1
- /**
2
- * Fallback de classificação e findings emergentes (lógica/domínio).
3
- */
1
+ /** Fallback de classificação (arquivos sem dono). */
4
2
 
5
3
  import { describe, expect, test } from "bun:test";
6
4
  import { defineHoundsmanConfig } from "../core/config";
7
5
  import { classifyEntries } from "../core/file-classifier";
8
6
  import { checkRegistry } from "../core/registry-check";
9
7
  import { defineWatchdog } from "../core/types";
10
- import { AgentState } from "../harness/investigation/agent-state";
11
- import { EvidenceVerifier } from "../harness/investigation/evidence-verifier";
12
- import { FindingCritic } from "../harness/investigation/finding-critic";
13
- import { FindingValidator } from "../harness/validation/finding-validator";
14
8
 
15
9
  const alpha = defineWatchdog({
16
10
  id: "alpha",
@@ -28,7 +22,6 @@ const fallback = defineWatchdog({
28
22
  description: "Fallback de lógica/domínio.",
29
23
  scope: "arquivos sem dono",
30
24
  fallback: true,
31
- allowEmergentFindings: true,
32
25
  rules: [
33
26
  {
34
27
  severity: "attention",
@@ -82,91 +75,3 @@ describe("classificação com fallback", () => {
82
75
  ).toBe(true);
83
76
  });
84
77
  });
85
-
86
- describe("findings emergentes", () => {
87
- const file = "a.ts";
88
- const content = "const x = 1;\n";
89
- const state = new AgentState();
90
- const evidence = state.addEvidence({
91
- file,
92
- startLine: 1,
93
- endLine: 1,
94
- snippet: content.trim(),
95
- kind: "definition",
96
- sourceTool: "test",
97
- confidence: 0.9,
98
- });
99
- const draft = {
100
- rule: "Lógica: a função fazA na verdade fazB",
101
- severity: "blocking" as const,
102
- file,
103
- line: 1,
104
- message: "comportamento diverge do nome",
105
- action: "renomear ou corrigir",
106
- evidenceIds: [evidence.id],
107
- confidence: 0.9,
108
- };
109
- const verifier = new EvidenceVerifier({
110
- allowedFiles: [file],
111
- readFile: (path) => (path === file ? content : null),
112
- });
113
-
114
- test("crítico aceita e limita a severidade quando habilitado", () => {
115
- const critic = new FindingCritic({
116
- knownRules: new Set([fallback.rules[0]?.text ?? ""]),
117
- verifier,
118
- allowEmergentFindings: true,
119
- });
120
- const decision = critic.reviewOne(draft, state);
121
- expect(decision.verdict).toBe("keep");
122
- expect(decision.draft.severity).toBe("attention");
123
- });
124
-
125
- test("crítico descarta quando o opt-in está desligado", () => {
126
- const critic = new FindingCritic({
127
- knownRules: new Set([fallback.rules[0]?.text ?? ""]),
128
- verifier,
129
- });
130
- const decision = critic.reviewOne(draft, state);
131
- expect(decision.verdict).toBe("drop");
132
- expect(decision.reason).toContain("desabilitado");
133
- });
134
-
135
- test("validador espelha a mesma regra", () => {
136
- const report = {
137
- watchdogId: "default",
138
- reviewedFiles: [file],
139
- skipped: [],
140
- findings: [draft],
141
- };
142
- const allowed = new FindingValidator({
143
- watchdogId: "default",
144
- knownRules: new Set([fallback.rules[0]?.text ?? ""]),
145
- allowedFiles: new Set([file]),
146
- overflow: new Set(),
147
- readFile: (path) => (path === file ? content : null),
148
- allowEmergentFindings: true,
149
- }).validate(report);
150
- expect(allowed.report.findings[0]?.severity).toBe("attention");
151
- expect(allowed.dropped).toHaveLength(0);
152
-
153
- const denied = new FindingValidator({
154
- watchdogId: "default",
155
- knownRules: new Set([fallback.rules[0]?.text ?? ""]),
156
- allowedFiles: new Set([file]),
157
- overflow: new Set(),
158
- readFile: (path) => (path === file ? content : null),
159
- }).validate(report);
160
- expect(denied.report.findings).toHaveLength(0);
161
- });
162
-
163
- test("finding emergente sem descrição é descartado", () => {
164
- const critic = new FindingCritic({
165
- knownRules: new Set([fallback.rules[0]?.text ?? ""]),
166
- verifier,
167
- allowEmergentFindings: true,
168
- });
169
- const decision = critic.reviewOne({ ...draft, rule: "Lógica:" }, state);
170
- expect(decision.verdict).toBe("drop");
171
- });
172
- });
@@ -41,7 +41,6 @@ export class FakeModelProvider implements ModelProvider {
41
41
  return {
42
42
  output: output as T,
43
43
  usage: fakeUsage(),
44
- steps: 1,
45
44
  finishReason: "stop",
46
45
  };
47
46
  }
@@ -9,6 +9,7 @@ import { describe, expect, test } from "bun:test";
9
9
  import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
10
10
  import { tmpdir } from "node:os";
11
11
  import { join } from "node:path";
12
+ import { AstService } from "../core/ast/service";
12
13
  import { ReviewScopeResolver, type ScopeOptions } from "../core/engine/review-scope";
13
14
  import type { FindingState, FindingsStateFile } from "../core/findings-state";
14
15
  import { defaultOptions, finalize, prepare, reviewContext } from "../core/orchestrator";
@@ -138,6 +139,35 @@ describe("ReviewScopeResolver", () => {
138
139
  if (scope.kind !== "review") throw new Error("esperava review");
139
140
  expect(scope.classification.mode).toBe("full");
140
141
  expect(scope.classification.headSha).toBe(repo.head);
142
+ expect(scope.classification.changedScope?.["src/alpha/a.ts"]).toEqual({
143
+ ranges: [{ start: 1, end: 1 }],
144
+ symbols: [],
145
+ source: "hunks",
146
+ });
147
+ } finally {
148
+ process.chdir(originalCwd);
149
+ repo.cleanup();
150
+ }
151
+ });
152
+
153
+ test("escopo usa o símbolo AST quando disponível", async () => {
154
+ const repo = makeRepo();
155
+ const originalCwd = process.cwd();
156
+ try {
157
+ process.chdir(repo.cwd);
158
+ const ast = new AstService(fakeHoundsmanConfig);
159
+ const scope = await new ReviewScopeResolver(
160
+ options({ base: repo.base, full: true }),
161
+ fakeHoundsmanConfig,
162
+ ast,
163
+ ).resolve(null, repo.head);
164
+
165
+ expect(scope.kind).toBe("review");
166
+ if (scope.kind !== "review") throw new Error("esperava review");
167
+ const file = scope.classification.changedScope?.["src/alpha/a.ts"];
168
+ expect(file?.source).toBe("ast");
169
+ expect(file?.symbols.map((symbol) => symbol.name)).toEqual(["a"]);
170
+ expect(file?.ranges).toEqual([{ start: 1, end: 1 }]);
141
171
  } finally {
142
172
  process.chdir(originalCwd);
143
173
  repo.cleanup();
@@ -163,6 +193,9 @@ describe("ReviewScopeResolver", () => {
163
193
  expect(scope.classification.mode).toBe("incremental");
164
194
  expect(scope.classification.reviewedFrom).toBe(repo.base);
165
195
  expect(Object.keys(scope.classification.files)).toEqual(["src/alpha/a.ts"]);
196
+ expect(scope.classification.changedScope?.["src/alpha/a.ts"]?.ranges).toEqual(
197
+ [{ start: 1, end: 1 }],
198
+ );
166
199
  expect(scope.classification.carryover?.map((f) => f.file)).toEqual([
167
200
  "src/alpha/keep.ts",
168
201
  ]);
@@ -31,7 +31,6 @@ import {
31
31
  WorktreeManager,
32
32
  } from "../core/services/worktree-manager";
33
33
  import { SQLiteStorageProvider } from "../core/storage/sqlite/sqlite-storage";
34
- import { parseHuntArgs } from "../harness/hunt/hunt-options";
35
34
  import { fakeHoundsmanConfig } from "./fixtures/fake-project";
36
35
 
37
36
  function git(cwd: string, args: string[]): string {
@@ -127,11 +126,10 @@ function fakeGh(
127
126
  }
128
127
 
129
128
  describe("flags de worktree", () => {
130
- test("parse de review/hunt e exclusão mútua", () => {
129
+ test("parse de review e exclusão mútua", () => {
131
130
  expect(parseReviewArgs(["--pr", "7"]).pr).toBe(7);
132
131
  expect(parseReviewArgs(["--worktree", "/tmp/wt"]).worktree).toBe("/tmp/wt");
133
132
  expect(() => parseReviewArgs(["--pr", "7", "--worktree", "/tmp/wt"])).toThrow();
134
- expect(parseHuntArgs(["--pr", "8"]).pr).toBe(8);
135
133
  });
136
134
  });
137
135
 
@@ -1,51 +0,0 @@
1
- /** Comando `hunt` — revisão nativa com IA (harness próprio). */
2
-
3
- import { resolveReviewWorkspace } from "../../core/engine/review-workspace";
4
- import { HuntHarness } from "../../harness";
5
- import { parseHuntArgs } from "../../harness/hunt/hunt-options";
6
- import type { CliContext, Command } from "./command";
7
-
8
- /** Executa o harness nativo. */
9
- export class HuntCommand implements Command {
10
- readonly name = "hunt";
11
-
12
- async run(args: string[], context: CliContext): Promise<number> {
13
- const options = parseHuntArgs(args);
14
- const config = await context.config();
15
- const workspace = resolveReviewWorkspace(
16
- {
17
- cwd: context.cwd,
18
- base: options.base,
19
- head: options.head,
20
- branch: options.branch,
21
- pr: options.pr,
22
- worktree: options.worktree,
23
- },
24
- { worktreesLocation: config.worktrees?.location },
25
- );
26
- for (const message of workspace.messages) context.logger.info(message);
27
-
28
- const engine = await context.engine({
29
- root: workspace.root,
30
- mainRoot: workspace.mainRoot,
31
- });
32
- const resolved = {
33
- ...options,
34
- branch: workspace.branch,
35
- base: workspace.base,
36
- head: workspace.head,
37
- pr: workspace.prNumber,
38
- worktreePath: workspace.worktreePath,
39
- };
40
- try {
41
- const harness = await HuntHarness.from(engine, resolved, context.logger);
42
- const result = await harness.run();
43
- for (const message of result.messages) context.logger.info(message);
44
- if (result.status === "failed") return 1;
45
- if (options.failOnBlocking && result.blockers > 0) return 1;
46
- return 0;
47
- } finally {
48
- engine.close();
49
- }
50
- }
51
- }
@@ -1,15 +0,0 @@
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).
@@ -1,33 +0,0 @@
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
- };
@@ -1,30 +0,0 @@
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
- }
@@ -1,46 +0,0 @@
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
- providerOptions: config.providerOptions,
41
- timeoutMs: config.timeoutMs,
42
- ast: this.deps.ast,
43
- readFile: this.deps.readFile,
44
- });
45
- }
46
- }
@@ -1,34 +0,0 @@
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
- }
@@ -1,16 +0,0 @@
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
- };
@@ -1,32 +0,0 @@
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 };
@@ -1,37 +0,0 @@
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
- }