@payconductor/houndsman 0.4.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 +35 -41
- 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/commands/status-command.ts +46 -0
- package/src/cli/help.ts +18 -1
- package/src/cli/index.ts +13 -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 +120 -6
- package/src/core/commands/review.ts +2 -0
- package/src/core/commands/scaffold.ts +159 -10
- package/src/core/commands/status.ts +63 -0
- 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 +77 -13
- 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/findings-state.ts +21 -4
- package/src/core/github-review.ts +620 -57
- 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 +454 -16
- package/src/core/paths.ts +3 -0
- 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/renderers/resume-renderer.ts +151 -0
- package/src/core/report-builder.ts +7 -4
- package/src/core/rule-text.ts +97 -0
- package/src/core/run-status.ts +39 -5
- package/src/core/services/file-classifier.ts +10 -3
- package/src/core/services/verifier.ts +14 -3
- package/src/core/storage/file/file-storage.ts +2 -0
- package/src/core/storage/paths.ts +6 -5
- package/src/core/storage/sqlite/mappers.ts +2 -0
- package/src/core/storage/sqlite/schema.ts +9 -1
- package/src/core/storage/sqlite/sqlite-storage.ts +5 -1
- package/src/core/storage/types.ts +15 -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/App.tsx +26 -9
- package/src/server/client/ArtifactsPanel.tsx +12 -5
- package/src/server/client/AutoRefresh.tsx +1 -1
- package/src/server/client/Filters.tsx +7 -2
- package/src/server/client/FindingCard.tsx +14 -10
- package/src/server/client/FindingsList.tsx +2 -2
- package/src/server/client/MarkdownText.tsx +92 -12
- package/src/server/client/ResumePanel.tsx +368 -0
- package/src/server/client/RunList.tsx +3 -3
- package/src/server/client/SubmitModal.tsx +49 -6
- package/src/server/client/SummaryBar.tsx +1 -1
- package/src/server/client/icons.tsx +17 -0
- package/src/server/client/{constants.ts → utils/constants.ts} +61 -10
- package/src/server/client/utils/markdown.ts +319 -0
- package/src/server/client/{types.ts → utils/types.ts} +64 -0
- package/src/server/index.ts +66 -7
- package/src/server/run-repository.ts +9 -0
- package/src/skill/OUTPUT-FORMAT.md +12 -0
- package/src/skill/SETUP.md +6 -2
- package/src/skill/example-review.md +22 -14
- package/src/skill/hunt.md +45 -19
- package/src/skill/verify.md +3 -2
- package/src/tests/__snapshots__/baseline.test.ts.snap +126 -42
- 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/baseline.test.ts +3 -0
- package/src/tests/code-highlight.test.ts +1 -1
- package/src/tests/env.test.ts +69 -0
- package/src/tests/fallback.test.ts +172 -0
- package/src/tests/findings-state.test.ts +50 -2
- 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/github-review.test.ts +409 -60
- 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/markdown.test.ts +61 -1
- package/src/tests/review-resume.test.ts +101 -0
- package/src/tests/review-scope.test.ts +302 -0
- package/src/tests/review-scripts.test.ts +73 -0
- package/src/tests/rule-analyzer.test.ts +61 -0
- package/src/tests/run-status-view.test.ts +94 -0
- package/src/tests/run-status.test.ts +53 -0
- package/src/tests/server.test.ts +76 -21
- package/src/tests/storage.test.ts +63 -0
- package/src/tests/tool-contracts.test.ts +82 -0
- package/src/tests/verify.test.ts +1 -1
- package/src/server/client/markdown.ts +0 -153
- /package/src/server/client/{api.ts → utils/api.ts} +0 -0
- /package/src/server/client/{code-highlight.ts → utils/code-highlight.ts} +0 -0
- /package/src/server/client/{format.ts → utils/format.ts} +0 -0
- /package/src/server/client/{json-highlight.ts → utils/json-highlight.ts} +0 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Texto de regra (biblioteca) — fonte única de polaridade e prefixos reservados.
|
|
3
|
+
*
|
|
4
|
+
* `RuleAnalyzer`, `registry-check` e o harness usam as mesmas listas: evita
|
|
5
|
+
* divergência entre “Promover/Evitar” no analyzer e as validações anti-drift.
|
|
6
|
+
* Também define o vocabulário de findings emergentes (lógica/domínio).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Intenção de uma regra. */
|
|
10
|
+
export type RuleIntent = "promote" | "avoid";
|
|
11
|
+
|
|
12
|
+
/** Palavras que abrem uma regra de “deve acontecer”. */
|
|
13
|
+
export const PROMOTE_PREFIXES = [
|
|
14
|
+
"promover",
|
|
15
|
+
"deve",
|
|
16
|
+
"dever",
|
|
17
|
+
"use",
|
|
18
|
+
"prefira",
|
|
19
|
+
"sempre",
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
/** Palavras que abrem uma regra de “não deve acontecer”. */
|
|
23
|
+
export const AVOID_PREFIXES = [
|
|
24
|
+
"evitar",
|
|
25
|
+
"não",
|
|
26
|
+
"nao",
|
|
27
|
+
"nunca",
|
|
28
|
+
"proibido",
|
|
29
|
+
"jamais",
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
/** Conjunto normalizado (usado para remover polaridade das keywords). */
|
|
33
|
+
export const POLARITY_WORDS: ReadonlySet<string> = new Set([
|
|
34
|
+
...PROMOTE_PREFIXES,
|
|
35
|
+
...AVOID_PREFIXES,
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
/** Escapa um literal para uso em regex. */
|
|
39
|
+
function escapeWord(word: string): string {
|
|
40
|
+
return word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Regex ancorada derivada das listas (sem repetir as palavras). */
|
|
44
|
+
function patternFor(words: readonly string[]): RegExp {
|
|
45
|
+
return new RegExp(`^(${words.map(escapeWord).join("|")})\\b`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const PROMOTE_PATTERN = patternFor(PROMOTE_PREFIXES);
|
|
49
|
+
const AVOID_PATTERN = patternFor(AVOID_PREFIXES);
|
|
50
|
+
|
|
51
|
+
/** Polaridade explícita; sem prefixo conhecido, assume `avoid`. */
|
|
52
|
+
export function polarityOf(text: string): RuleIntent {
|
|
53
|
+
const normalized = text.trim().toLowerCase();
|
|
54
|
+
if (PROMOTE_PATTERN.test(normalized)) return "promote";
|
|
55
|
+
if (AVOID_PATTERN.test(normalized)) return "avoid";
|
|
56
|
+
return "avoid";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** `true` quando a regra declara polaridade explícita. */
|
|
60
|
+
export function hasExplicitPolarity(text: string): boolean {
|
|
61
|
+
const normalized = text.trim().toLowerCase();
|
|
62
|
+
return PROMOTE_PATTERN.test(normalized) || AVOID_PATTERN.test(normalized);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Prefixos reservados para findings emergentes (fora das regras explícitas). */
|
|
66
|
+
export const EMERGENT_PREFIXES = {
|
|
67
|
+
logic: "Lógica:",
|
|
68
|
+
domain: "Domínio:",
|
|
69
|
+
} as const;
|
|
70
|
+
|
|
71
|
+
/** Espécie de finding emergente. */
|
|
72
|
+
export type EmergentKind = keyof typeof EMERGENT_PREFIXES;
|
|
73
|
+
|
|
74
|
+
/** Espécie de um finding emergente (`null` quando não é emergente). */
|
|
75
|
+
export function emergentKind(rule: string): EmergentKind | null {
|
|
76
|
+
const normalized = rule.trim();
|
|
77
|
+
if (normalized.startsWith(EMERGENT_PREFIXES.logic)) return "logic";
|
|
78
|
+
if (normalized.startsWith(EMERGENT_PREFIXES.domain)) return "domain";
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** `true` quando a regra usa um prefixo emergente. */
|
|
83
|
+
export function isEmergentRule(rule: string): boolean {
|
|
84
|
+
return emergentKind(rule) !== null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Texto que sobra após o prefixo (vazio é inválido). */
|
|
88
|
+
export function emergentText(rule: string): string {
|
|
89
|
+
const kind = emergentKind(rule);
|
|
90
|
+
if (!kind) return "";
|
|
91
|
+
return rule.trim().slice(EMERGENT_PREFIXES[kind].length).trim();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Monta o texto de um finding emergente. */
|
|
95
|
+
export function emergentRule(kind: EmergentKind, text: string): string {
|
|
96
|
+
return `${EMERGENT_PREFIXES[kind]} ${text.trim()}`;
|
|
97
|
+
}
|
package/src/core/run-status.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Diz à UI em que pé está uma run: `reviewing` (revisando) → `review_done`
|
|
5
5
|
* (revisão finalizada) → `fixing` (corrigindo) → `fix_done` (correção finalizada).
|
|
6
6
|
*
|
|
7
|
-
* Toda ação que muda o estado
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Toda ação que muda o estado pinga o `updatedAt`. Execuções ativas têm um
|
|
8
|
+
* `heartbeatAt` próprio (renovado por `status ping` ou pela correção); a UI usa
|
|
9
|
+
* esse campo — não o `updatedAt` — para detectar timeout (display-only).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { FindingsStateFile } from "./findings-state";
|
|
@@ -24,6 +24,11 @@ export const RUN_STATUSES: readonly RunStatus[] = [
|
|
|
24
24
|
"fix_done",
|
|
25
25
|
];
|
|
26
26
|
|
|
27
|
+
/** `true` para status de execução (recebem heartbeat e podem dar timeout). */
|
|
28
|
+
export function isActiveRunStatus(status: RunStatus | null): boolean {
|
|
29
|
+
return status === "reviewing" || status === "fixing";
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
/** Lê o status atual (null se a review não existir). */
|
|
28
33
|
export function readRunStatus(
|
|
29
34
|
storage: StorageProvider,
|
|
@@ -36,6 +41,11 @@ export type WriteRunStatusOptions = {
|
|
|
36
41
|
status: RunStatus;
|
|
37
42
|
/** Reinicia o `startedAt` — usado no início de uma nova revisão. */
|
|
38
43
|
resetStartedAt?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Marca a execução como viva agora. Implícito no início de um ciclo
|
|
46
|
+
* (`resetStartedAt`); pings explícitos usam `touchRunStatus`.
|
|
47
|
+
*/
|
|
48
|
+
touch?: boolean;
|
|
39
49
|
now?: string;
|
|
40
50
|
};
|
|
41
51
|
|
|
@@ -45,15 +55,35 @@ export function writeRunStatus(
|
|
|
45
55
|
reviewId: string,
|
|
46
56
|
options: WriteRunStatusOptions,
|
|
47
57
|
): ReviewRecord {
|
|
58
|
+
const now = options.now ?? systemClock();
|
|
59
|
+
const active = isActiveRunStatus(options.status);
|
|
60
|
+
const touch = options.touch ?? options.resetStartedAt ?? false;
|
|
48
61
|
const updated = storage.updateReview(reviewId, {
|
|
49
62
|
status: options.status,
|
|
50
63
|
resetStartedAt: options.resetStartedAt,
|
|
51
|
-
|
|
64
|
+
heartbeatAt: active && touch ? now : undefined,
|
|
65
|
+
resetHeartbeat: !active,
|
|
66
|
+
updatedAt: now,
|
|
52
67
|
});
|
|
53
68
|
if (!updated) throw new Error(`Review '${reviewId}' não encontrada.`);
|
|
54
69
|
return updated;
|
|
55
70
|
}
|
|
56
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Heartbeat explícito de uma execução em andamento (skill viva).
|
|
74
|
+
* Só age em `reviewing`/`fixing`; status terminal é devolvido sem alteração.
|
|
75
|
+
*/
|
|
76
|
+
export function touchRunStatus(
|
|
77
|
+
storage: StorageProvider,
|
|
78
|
+
reviewId: string,
|
|
79
|
+
now: string = systemClock(),
|
|
80
|
+
): ReviewRecord {
|
|
81
|
+
const review = storage.getReview(reviewId);
|
|
82
|
+
if (!review) throw new Error(`Review '${reviewId}' não encontrada.`);
|
|
83
|
+
if (!isActiveRunStatus(review.status)) return review;
|
|
84
|
+
return storage.updateReview(reviewId, { heartbeatAt: now, updatedAt: now }) ?? review;
|
|
85
|
+
}
|
|
86
|
+
|
|
57
87
|
/**
|
|
58
88
|
* Deriva o status após uma mudança nos findings (triagem/correção).
|
|
59
89
|
* `fixing` exige correção em andamento (`in_progress`) ou retomada de um fix;
|
|
@@ -87,7 +117,11 @@ export function pingFindingsStatus(
|
|
|
87
117
|
state: FindingsStateFile,
|
|
88
118
|
): ReviewRecord {
|
|
89
119
|
const previous = storage.getReview(reviewId)?.status ?? null;
|
|
120
|
+
const status = statusAfterFindingsChange(previous, state);
|
|
90
121
|
return writeRunStatus(storage, reviewId, {
|
|
91
|
-
status
|
|
122
|
+
status,
|
|
123
|
+
// Só a correção em andamento é uma execução: triagem/reviewing não
|
|
124
|
+
// devem renovar o heartbeat (era a origem do "timeout falso").
|
|
125
|
+
touch: status === "fixing",
|
|
92
126
|
});
|
|
93
127
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* pública e os testes; a classe devolve `Classification` (domínio).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { AstFacts } from "../ast/types";
|
|
8
9
|
import type { HoundsmanConfig } from "../config";
|
|
9
10
|
import type { DiffSource } from "../diff-source";
|
|
10
11
|
import { Classification } from "../domain/classification";
|
|
@@ -23,12 +24,18 @@ export class FileClassifier {
|
|
|
23
24
|
classify(
|
|
24
25
|
entries: ReturnType<typeof parseEntries>,
|
|
25
26
|
churn: ChurnMap = {},
|
|
27
|
+
astFacts?: ReadonlyMap<string, AstFacts>,
|
|
26
28
|
): Classification {
|
|
27
|
-
return new Classification(classifyEntries(entries, this.config, churn));
|
|
29
|
+
return new Classification(classifyEntries(entries, this.config, churn, astFacts));
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
/** Executa o diff e classifica os arquivos entre `base` e `head`. */
|
|
31
|
-
classifyRange(
|
|
32
|
-
|
|
33
|
+
classifyRange(
|
|
34
|
+
base: string,
|
|
35
|
+
head: string,
|
|
36
|
+
diff?: DiffSource,
|
|
37
|
+
astFacts?: ReadonlyMap<string, AstFacts>,
|
|
38
|
+
): Classification {
|
|
39
|
+
return new Classification(classifyRange(base, head, this.config, diff, astFacts));
|
|
33
40
|
}
|
|
34
41
|
}
|
|
@@ -12,10 +12,11 @@ import type {
|
|
|
12
12
|
FindingsStateFile,
|
|
13
13
|
} from "../findings-state";
|
|
14
14
|
import {
|
|
15
|
-
|
|
15
|
+
applyConfidence,
|
|
16
16
|
applyResolutions,
|
|
17
17
|
selectVerifiable,
|
|
18
18
|
summarizeVerify,
|
|
19
|
+
type VerifyBasis,
|
|
19
20
|
type VerifyEntry,
|
|
20
21
|
type VerifySummary,
|
|
21
22
|
verifyLocal,
|
|
@@ -33,13 +34,23 @@ export class Verifier {
|
|
|
33
34
|
return verifyLocal(findings, readFile);
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
/** Tier 1 — aplica
|
|
37
|
+
/** Tier 1 — aplica confirmações externas (Jev ou IA) aos casos ambíguos. */
|
|
38
|
+
applyConfidence(
|
|
39
|
+
entries: VerifyEntry[],
|
|
40
|
+
confidence: Map<string, number>,
|
|
41
|
+
threshold: number,
|
|
42
|
+
basis: VerifyBasis = "jev",
|
|
43
|
+
): number {
|
|
44
|
+
return applyConfidence(entries, confidence, threshold, basis);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Compatibilidade: confirmação via Jev. */
|
|
37
48
|
applyJev(
|
|
38
49
|
entries: VerifyEntry[],
|
|
39
50
|
confidence: Map<string, number>,
|
|
40
51
|
threshold: number,
|
|
41
52
|
): number {
|
|
42
|
-
return
|
|
53
|
+
return applyConfidence(entries, confidence, threshold, "jev");
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
/** Contagem por status. */
|
|
@@ -102,6 +102,8 @@ export class FileStorageProvider implements StorageProvider {
|
|
|
102
102
|
if (patch.status !== undefined) updated.status = patch.status;
|
|
103
103
|
if (patch.resetStartedAt) updated.startedAt = now;
|
|
104
104
|
else if (patch.startedAt !== undefined) updated.startedAt = patch.startedAt;
|
|
105
|
+
if (patch.resetHeartbeat) updated.heartbeatAt = undefined;
|
|
106
|
+
else if (patch.heartbeatAt !== undefined) updated.heartbeatAt = patch.heartbeatAt;
|
|
105
107
|
updated.updatedAt = patch.updatedAt ?? now;
|
|
106
108
|
return this.writeReview(updated);
|
|
107
109
|
}
|
|
@@ -9,17 +9,18 @@
|
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { join, resolve } from "node:path";
|
|
11
11
|
import type { HoundsmanConfig } from "../config";
|
|
12
|
+
import { ENV_NAMES, env } from "../env";
|
|
12
13
|
|
|
13
|
-
export const HOUNDSMAN_HOME_ENV =
|
|
14
|
+
export const HOUNDSMAN_HOME_ENV = ENV_NAMES.home;
|
|
14
15
|
|
|
15
16
|
/** Diretório-base global do houndsman. */
|
|
16
17
|
export function houndsmanHome(): string {
|
|
17
|
-
const override =
|
|
18
|
-
if (override
|
|
18
|
+
const override = env.home;
|
|
19
|
+
if (override) return resolve(override);
|
|
19
20
|
|
|
20
21
|
const home = homedir();
|
|
21
22
|
if (process.platform === "win32") {
|
|
22
|
-
const local =
|
|
23
|
+
const local = env.localAppData;
|
|
23
24
|
return join(
|
|
24
25
|
local && local.length > 0 ? local : join(home, "AppData", "Local"),
|
|
25
26
|
"houndsman",
|
|
@@ -28,7 +29,7 @@ export function houndsmanHome(): string {
|
|
|
28
29
|
if (process.platform === "darwin") {
|
|
29
30
|
return join(home, "Library", "Application Support", "houndsman");
|
|
30
31
|
}
|
|
31
|
-
const xdg =
|
|
32
|
+
const xdg = env.xdgDataHome;
|
|
32
33
|
return join(xdg && xdg.length > 0 ? xdg : join(home, ".local", "share"), "houndsman");
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -30,6 +30,7 @@ export type ReviewRow = {
|
|
|
30
30
|
reviewed_at: string | null;
|
|
31
31
|
status: string | null;
|
|
32
32
|
started_at: string | null;
|
|
33
|
+
heartbeat_at: string | null;
|
|
33
34
|
created_at: string;
|
|
34
35
|
updated_at: string;
|
|
35
36
|
};
|
|
@@ -96,6 +97,7 @@ export function reviewFromRow(row: ReviewRow): ReviewRecord {
|
|
|
96
97
|
reviewedAt: row.reviewed_at ?? undefined,
|
|
97
98
|
status: (row.status as ReviewRecord["status"]) ?? null,
|
|
98
99
|
startedAt: row.started_at ?? undefined,
|
|
100
|
+
heartbeatAt: row.heartbeat_at ?? undefined,
|
|
99
101
|
createdAt: row.created_at,
|
|
100
102
|
updatedAt: row.updated_at,
|
|
101
103
|
};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { Database } from "bun:sqlite";
|
|
9
9
|
|
|
10
|
-
export const SCHEMA_VERSION =
|
|
10
|
+
export const SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
const DDL = `
|
|
13
13
|
CREATE TABLE IF NOT EXISTS reviews (
|
|
@@ -24,6 +24,7 @@ CREATE TABLE IF NOT EXISTS reviews (
|
|
|
24
24
|
reviewed_at TEXT,
|
|
25
25
|
status TEXT,
|
|
26
26
|
started_at TEXT,
|
|
27
|
+
heartbeat_at TEXT,
|
|
27
28
|
created_at TEXT NOT NULL,
|
|
28
29
|
updated_at TEXT NOT NULL,
|
|
29
30
|
UNIQUE(repo_key, branch)
|
|
@@ -103,6 +104,13 @@ export function migrate(db: Database, path: string): void {
|
|
|
103
104
|
|
|
104
105
|
db.transaction(() => {
|
|
105
106
|
db.exec(DDL);
|
|
107
|
+
// v2: heartbeat explícito da execução (bancos v1 não tinham a coluna).
|
|
108
|
+
const columns = db.query("PRAGMA table_info(reviews)").all() as {
|
|
109
|
+
name: string;
|
|
110
|
+
}[];
|
|
111
|
+
if (!columns.some((column) => column.name === "heartbeat_at")) {
|
|
112
|
+
db.exec("ALTER TABLE reviews ADD COLUMN heartbeat_at TEXT");
|
|
113
|
+
}
|
|
106
114
|
db.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
|
|
107
115
|
})();
|
|
108
116
|
}
|
|
@@ -33,7 +33,7 @@ import { migrate } from "./schema";
|
|
|
33
33
|
|
|
34
34
|
/** Colunas mutáveis de review (camel → snake). */
|
|
35
35
|
const REVIEW_PATCH_COLUMNS: Record<
|
|
36
|
-
Exclude<keyof ReviewPatch, "resetStartedAt">,
|
|
36
|
+
Exclude<keyof ReviewPatch, "resetStartedAt" | "resetHeartbeat">,
|
|
37
37
|
string
|
|
38
38
|
> = {
|
|
39
39
|
branch: "branch",
|
|
@@ -45,6 +45,7 @@ const REVIEW_PATCH_COLUMNS: Record<
|
|
|
45
45
|
reviewedAt: "reviewed_at",
|
|
46
46
|
status: "status",
|
|
47
47
|
startedAt: "started_at",
|
|
48
|
+
heartbeatAt: "heartbeat_at",
|
|
48
49
|
updatedAt: "updated_at",
|
|
49
50
|
};
|
|
50
51
|
|
|
@@ -159,6 +160,9 @@ export class SQLiteStorageProvider implements StorageProvider {
|
|
|
159
160
|
sets.push("started_at = ?");
|
|
160
161
|
params.push(now);
|
|
161
162
|
}
|
|
163
|
+
if (patch.resetHeartbeat) {
|
|
164
|
+
sets.push("heartbeat_at = NULL");
|
|
165
|
+
}
|
|
162
166
|
if (patch.updatedAt === undefined) {
|
|
163
167
|
sets.push("updated_at = ?");
|
|
164
168
|
params.push(now);
|
|
@@ -36,6 +36,11 @@ export type ReviewRecord = {
|
|
|
36
36
|
reviewedAt?: string;
|
|
37
37
|
status: ReviewStatus | null;
|
|
38
38
|
startedAt?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Último sinal de vida de uma execução ativa (`reviewing`/`fixing`).
|
|
41
|
+
* Diferente de `updatedAt`: não muda com triagem/sync — só com a execução.
|
|
42
|
+
*/
|
|
43
|
+
heartbeatAt?: string;
|
|
39
44
|
createdAt: string;
|
|
40
45
|
updatedAt: string;
|
|
41
46
|
};
|
|
@@ -53,11 +58,14 @@ export type ReviewPatch = Partial<
|
|
|
53
58
|
| "reviewedAt"
|
|
54
59
|
| "status"
|
|
55
60
|
| "startedAt"
|
|
61
|
+
| "heartbeatAt"
|
|
56
62
|
| "updatedAt"
|
|
57
63
|
>
|
|
58
64
|
> & {
|
|
59
65
|
/** Reinicia o `startedAt` no início de um novo ciclo de revisão. */
|
|
60
66
|
resetStartedAt?: boolean;
|
|
67
|
+
/** Limpa o heartbeat (execução terminou). */
|
|
68
|
+
resetHeartbeat?: boolean;
|
|
61
69
|
};
|
|
62
70
|
|
|
63
71
|
/** Entrada de `getOrCreateReview`. */
|
|
@@ -81,8 +89,15 @@ export type ArtifactKind =
|
|
|
81
89
|
| "context-summary"
|
|
82
90
|
| "finding-report"
|
|
83
91
|
| "review"
|
|
92
|
+
| "resume"
|
|
84
93
|
| "domain-review"
|
|
85
94
|
| "verify"
|
|
95
|
+
| "ast"
|
|
96
|
+
| "ast-impact"
|
|
97
|
+
| "ai"
|
|
98
|
+
| "ai-usage"
|
|
99
|
+
| "ai-brief"
|
|
100
|
+
| "harness-transcript"
|
|
86
101
|
| "other";
|
|
87
102
|
|
|
88
103
|
/** Artefato persistido de uma review. */
|
|
@@ -106,12 +106,24 @@ export class ReviewWorkArea {
|
|
|
106
106
|
writeFileSync(this.briefFile(id), content);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/** Grava o relatório JSON de um watchdog (staging dos subagentes). */
|
|
110
|
+
writeFinding(id: string, content: string): void {
|
|
111
|
+
mkdirSync(this.findingsDir, { recursive: true });
|
|
112
|
+
writeFileSync(this.findingFile(id), content);
|
|
113
|
+
}
|
|
114
|
+
|
|
109
115
|
writeContext(context: unknown, brief: string): void {
|
|
110
116
|
mkdirSync(this.root, { recursive: true });
|
|
111
117
|
writeFileSync(this.contextPaths.context, `${JSON.stringify(context, null, 2)}\n`);
|
|
112
118
|
writeFileSync(this.contextPaths.brief, `${brief}\n`);
|
|
113
119
|
}
|
|
114
120
|
|
|
121
|
+
/** Grava o resumo do contexto (gerado pelo subagente ou pela IA). */
|
|
122
|
+
writeContextSummary(summary: unknown): void {
|
|
123
|
+
mkdirSync(this.root, { recursive: true });
|
|
124
|
+
writeFileSync(this.contextPaths.summary, `${JSON.stringify(summary, null, 2)}\n`);
|
|
125
|
+
}
|
|
126
|
+
|
|
115
127
|
/** Finds brutos gravados pelos subagentes (um JSON por watchdog). */
|
|
116
128
|
readFindings(activeIds: string[]): FindingsIngest {
|
|
117
129
|
const reports: WatchdogReport[] = [];
|
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
|
+
}
|