@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
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
* pipeline de review sem dependências novas de SDK e com tipagem explícita
|
|
6
6
|
* do request/response (`./jev-types`).
|
|
7
7
|
*
|
|
8
|
-
* Config: apenas `
|
|
9
|
-
* fixos no código (constantes abaixo).
|
|
8
|
+
* Config: apenas `HOUNDSMAN_TYPE_SAFE_API_KEY` (via `core/env.ts`). Origem e
|
|
9
|
+
* modelo da API são fixos no código (constantes abaixo).
|
|
10
10
|
*
|
|
11
11
|
* Erros são normalizados em `JevError` — o chamador decide falhar-aberto.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import axios, { type AxiosInstance, isAxiosError } from "axios";
|
|
15
|
+
import { ENV_NAMES, env } from "../env";
|
|
15
16
|
import type { JevEvaluateRequest, JevEvaluateResponse } from "./jev-types";
|
|
16
17
|
|
|
17
18
|
const DEFAULT_BASE_URL = "https://api.typesafe.ai";
|
|
@@ -19,7 +20,7 @@ const DEFAULT_MODEL = "jev-latest";
|
|
|
19
20
|
const DEFAULT_TIMEOUT_MS = 20_000;
|
|
20
21
|
/** Timeout da verificação de credencial do `doctor` (mais curto que o de review). */
|
|
21
22
|
const DEFAULT_PING_TIMEOUT_MS = 10_000;
|
|
22
|
-
/** Caminho fixo do
|
|
23
|
+
/** Caminho fixo do endpoint System One. */
|
|
23
24
|
const JEV_PATH = "/v1/systemone";
|
|
24
25
|
|
|
25
26
|
export class JevError extends Error {
|
|
@@ -39,7 +40,7 @@ export type JevCredentialCheck =
|
|
|
39
40
|
| { ok: false; kind: "invalid" | "unreachable"; detail: string };
|
|
40
41
|
|
|
41
42
|
export type JevClientOptions = {
|
|
42
|
-
/** Chave de API (default: `
|
|
43
|
+
/** Chave de API (default: `env.typeSafeApiKey`). */
|
|
43
44
|
apiKey?: string;
|
|
44
45
|
/** Sobreposição da origem da API (default: constante `DEFAULT_BASE_URL`). */
|
|
45
46
|
baseURL?: string;
|
|
@@ -57,7 +58,7 @@ export class JevClient {
|
|
|
57
58
|
readonly model: string;
|
|
58
59
|
|
|
59
60
|
constructor(options: JevClientOptions = {}) {
|
|
60
|
-
this.apiKey = options.apiKey ??
|
|
61
|
+
this.apiKey = options.apiKey ?? env.typeSafeApiKey;
|
|
61
62
|
this.model = options.model ?? DEFAULT_MODEL;
|
|
62
63
|
const baseURL = options.baseURL ?? DEFAULT_BASE_URL;
|
|
63
64
|
this.http =
|
|
@@ -79,7 +80,7 @@ export class JevClient {
|
|
|
79
80
|
*/
|
|
80
81
|
async evaluate(request: JevEvaluateRequest): Promise<JevEvaluateResponse> {
|
|
81
82
|
if (!this.apiKey) {
|
|
82
|
-
throw new JevError(
|
|
83
|
+
throw new JevError(`${ENV_NAMES.typeSafeApiKey} não configurada.`);
|
|
83
84
|
}
|
|
84
85
|
try {
|
|
85
86
|
const { data } = await this.http.post<JevEvaluateResponse>(
|
|
@@ -105,7 +106,7 @@ export class JevClient {
|
|
|
105
106
|
return {
|
|
106
107
|
ok: false,
|
|
107
108
|
kind: "unreachable",
|
|
108
|
-
detail:
|
|
109
|
+
detail: `${ENV_NAMES.typeSafeApiKey} não configurada.`,
|
|
109
110
|
};
|
|
110
111
|
}
|
|
111
112
|
const request: JevEvaluateRequest = {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* decide. O custo é uma chamada por arquivo ambíguo, não uma revisão completa.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { ENV_NAMES } from "../env";
|
|
10
11
|
import { diffPatch, readFileAt } from "../git-revision";
|
|
11
12
|
import type { JevClient } from "./jev-client";
|
|
12
13
|
import type { JevAnswer, JevQuestion } from "./jev-types";
|
|
@@ -104,7 +105,7 @@ export class JevVerifier {
|
|
|
104
105
|
confidence,
|
|
105
106
|
checked: 0,
|
|
106
107
|
skipped: cases.length,
|
|
107
|
-
reason:
|
|
108
|
+
reason: `${ENV_NAMES.typeSafeApiKey} não configurada`,
|
|
108
109
|
};
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instância pronta do logger (`import { logger } from "../core/logging"`).
|
|
3
|
+
*
|
|
4
|
+
* Infraestrutura compartilhada por `cli`, `harness` e `server`. Módulos de
|
|
5
|
+
* negócio do core não escrevem saída.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Logger } from "./logger";
|
|
9
|
+
|
|
10
|
+
export const logger = new Logger();
|
|
11
|
+
|
|
12
|
+
export type {
|
|
13
|
+
LoggerLike,
|
|
14
|
+
LoggerOptions,
|
|
15
|
+
SpinnerLike,
|
|
16
|
+
} from "./logger";
|
|
17
|
+
export { Logger, SilentLogger } from "./logger";
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger único do CLI/harness (infraestrutura).
|
|
3
|
+
*
|
|
4
|
+
* Usa `chalk` para cor e `ora` para spinner, mas cai para texto puro quando a
|
|
5
|
+
* saída não é TTY, `NO_COLOR` está definido, `TERM=dumb` ou `--quiet`.
|
|
6
|
+
*
|
|
7
|
+
* Não é usado por módulos de negócio do `core/`: só `cli/`, `harness/` e
|
|
8
|
+
* `server/` escrevem saída.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Chalk, type ChalkInstance } from "chalk";
|
|
12
|
+
import ora, { type Ora } from "ora";
|
|
13
|
+
import { env } from "../env";
|
|
14
|
+
|
|
15
|
+
/** Spinner mínimo (compatível com `ora` e com o modo silencioso). */
|
|
16
|
+
export type SpinnerLike = {
|
|
17
|
+
start(): SpinnerLike;
|
|
18
|
+
stop(): void;
|
|
19
|
+
succeed(text?: string): void;
|
|
20
|
+
fail(text?: string): void;
|
|
21
|
+
warn(text?: string): void;
|
|
22
|
+
readonly text: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Superfície de log usada por comandos e reporter. */
|
|
26
|
+
export interface LoggerLike {
|
|
27
|
+
debug(message: string): void;
|
|
28
|
+
info(message: string): void;
|
|
29
|
+
success(message: string): void;
|
|
30
|
+
warn(message: string): void;
|
|
31
|
+
error(message: string): void;
|
|
32
|
+
step(message: string): void;
|
|
33
|
+
bullet(message: string): void;
|
|
34
|
+
table(rows: readonly (readonly string[])[]): void;
|
|
35
|
+
progress(message: string): void;
|
|
36
|
+
spinner(text: string): SpinnerLike | null;
|
|
37
|
+
withSpinner<T>(
|
|
38
|
+
text: string,
|
|
39
|
+
run: (spinner: SpinnerLike | null) => Promise<T>,
|
|
40
|
+
): Promise<T>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Opções do logger (injetáveis nos testes). */
|
|
44
|
+
export type LoggerOptions = {
|
|
45
|
+
/** `auto` (default), `true` (força cor) ou `false` (plain). */
|
|
46
|
+
color?: boolean | "auto";
|
|
47
|
+
quiet?: boolean;
|
|
48
|
+
verbose?: boolean;
|
|
49
|
+
stdout?: NodeJS.WritableStream;
|
|
50
|
+
stderr?: NodeJS.WritableStream;
|
|
51
|
+
/** Sobrescreve a detecção de TTY (testes). */
|
|
52
|
+
isTTY?: boolean;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** `true` quando cores devem ser desabilitadas pelo ambiente. */
|
|
56
|
+
function colorsDisabled(): boolean {
|
|
57
|
+
return Boolean(env.noColor) || env.term === "dumb";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Cria a instância de chalk respeitando as opções. */
|
|
61
|
+
function createChalk(color: LoggerOptions["color"]): ChalkInstance {
|
|
62
|
+
if (color === false || colorsDisabled()) return new Chalk({ level: 0 });
|
|
63
|
+
if (color === true) return new Chalk({ level: 1 });
|
|
64
|
+
return new Chalk();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Logger com saída injetável. */
|
|
68
|
+
export class Logger implements LoggerLike {
|
|
69
|
+
private readonly out: NodeJS.WritableStream;
|
|
70
|
+
private readonly err: NodeJS.WritableStream;
|
|
71
|
+
private readonly chalk: ChalkInstance;
|
|
72
|
+
private readonly quiet: boolean;
|
|
73
|
+
private readonly verbose: boolean;
|
|
74
|
+
private readonly tty: boolean;
|
|
75
|
+
|
|
76
|
+
constructor(options: LoggerOptions = {}) {
|
|
77
|
+
this.out = options.stdout ?? process.stdout;
|
|
78
|
+
this.err = options.stderr ?? process.stderr;
|
|
79
|
+
this.chalk = createChalk(options.color ?? "auto");
|
|
80
|
+
this.quiet = options.quiet === true;
|
|
81
|
+
this.verbose = options.verbose === true;
|
|
82
|
+
this.tty = options.isTTY ?? Boolean(process.stdout.isTTY);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Só aparece em `--verbose`. */
|
|
86
|
+
debug(message: string): void {
|
|
87
|
+
if (!this.verbose || this.quiet) return;
|
|
88
|
+
this.out.write(`${this.chalk.dim(`[debug] ${message}`)}\n`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
info(message: string): void {
|
|
92
|
+
if (this.quiet) return;
|
|
93
|
+
this.out.write(`${message}\n`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
success(message: string): void {
|
|
97
|
+
if (this.quiet) return;
|
|
98
|
+
this.out.write(`${this.chalk.green("✔")} ${message}\n`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
warn(message: string): void {
|
|
102
|
+
if (this.quiet) return;
|
|
103
|
+
this.err.write(`${this.chalk.yellow("⚠")} ${message}\n`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
error(message: string): void {
|
|
107
|
+
this.err.write(`${this.chalk.red("✖")} ${message}\n`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
step(message: string): void {
|
|
111
|
+
if (this.quiet) return;
|
|
112
|
+
this.out.write(`${this.chalk.cyan("→")} ${message}\n`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
bullet(message: string): void {
|
|
116
|
+
if (this.quiet) return;
|
|
117
|
+
this.out.write(` ${this.chalk.dim("•")} ${message}\n`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Tabela simples com colunas alinhadas (sem ANSI nos valores). */
|
|
121
|
+
table(rows: readonly (readonly string[])[]): void {
|
|
122
|
+
if (this.quiet || rows.length === 0) return;
|
|
123
|
+
const width = Math.max(...rows.map((row) => row.length));
|
|
124
|
+
const sizes = Array.from({ length: width }, (_, column) =>
|
|
125
|
+
Math.max(...rows.map((row) => (row[column] ?? "").length)),
|
|
126
|
+
);
|
|
127
|
+
rows.forEach((row, index) => {
|
|
128
|
+
const line = row
|
|
129
|
+
.map((cell, column) => (cell ?? "").padEnd(sizes[column] ?? 0))
|
|
130
|
+
.join(" ")
|
|
131
|
+
.trimEnd();
|
|
132
|
+
this.out.write(`${index === 0 ? this.chalk.bold(line) : line}\n`);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Progresso textual (usado pelo `AgentReporter`). */
|
|
137
|
+
progress(message: string): void {
|
|
138
|
+
this.info(message);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Spinner habilitado apenas em TTY e fora do modo quiet. */
|
|
142
|
+
spinner(text: string): SpinnerLike | null {
|
|
143
|
+
if (!this.tty || this.quiet) return null;
|
|
144
|
+
return ora({ text, stream: this.out as NodeJS.WriteStream, isEnabled: true });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Executa `run` com um spinner opcional; sempre para o spinner. */
|
|
148
|
+
async withSpinner<T>(
|
|
149
|
+
text: string,
|
|
150
|
+
run: (spinner: SpinnerLike | null) => Promise<T>,
|
|
151
|
+
): Promise<T> {
|
|
152
|
+
const spinner = this.spinner(text);
|
|
153
|
+
spinner?.start();
|
|
154
|
+
try {
|
|
155
|
+
const result = await run(spinner);
|
|
156
|
+
spinner?.succeed();
|
|
157
|
+
return result;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
spinner?.fail();
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Logger silencioso para testes/serviços. */
|
|
166
|
+
export class SilentLogger implements LoggerLike {
|
|
167
|
+
debug(_message?: string): void {}
|
|
168
|
+
info(_message?: string): void {}
|
|
169
|
+
success(_message?: string): void {}
|
|
170
|
+
warn(_message?: string): void {}
|
|
171
|
+
error(_message?: string): void {}
|
|
172
|
+
step(_message?: string): void {}
|
|
173
|
+
bullet(_message?: string): void {}
|
|
174
|
+
table(_rows?: readonly (readonly string[])[]): void {}
|
|
175
|
+
progress(_message?: string): void {}
|
|
176
|
+
spinner(_text?: string): SpinnerLike | null {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
async withSpinner<T>(
|
|
180
|
+
_text: string,
|
|
181
|
+
run: (spinner: SpinnerLike | null) => Promise<T>,
|
|
182
|
+
): Promise<T> {
|
|
183
|
+
return run(null);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export type { Ora };
|