@payconductor/houndsman 0.2.0 → 0.4.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 +18 -6
- package/package.json +1 -1
- package/src/cli/arg-parser.ts +8 -0
- package/src/cli/commands/command.ts +23 -0
- package/src/cli/commands/context-command.ts +40 -0
- package/src/cli/commands/doctor-command.ts +60 -0
- package/src/cli/commands/findings-command.ts +38 -0
- package/src/cli/commands/help-command.ts +13 -0
- package/src/cli/commands/registry.ts +34 -0
- package/src/cli/commands/review-command.ts +21 -0
- package/src/cli/commands/server-command.ts +37 -0
- package/src/cli/commands/setup-command.ts +34 -0
- package/src/cli/commands/watchdog-command.ts +34 -0
- package/src/cli/help.ts +47 -0
- package/src/cli/index.ts +60 -268
- package/src/core/arg-parser.ts +48 -0
- package/src/core/commands/context.ts +29 -13
- package/src/core/commands/findings.ts +185 -260
- package/src/core/commands/review.ts +21 -39
- package/src/core/commands/scaffold.ts +189 -80
- package/src/core/commands/setup.ts +16 -11
- package/src/core/commands/update.ts +7 -7
- package/src/core/commands/watchdog.ts +8 -80
- package/src/core/config.ts +24 -25
- package/src/core/defaults.ts +20 -0
- package/src/core/diff-source.ts +1 -13
- package/src/core/doc-discoverer.ts +25 -15
- package/src/core/domain/budget.ts +42 -0
- package/src/core/domain/classification.ts +94 -0
- package/src/core/domain/diff.ts +53 -0
- package/src/core/domain/watchdog.ts +96 -0
- package/src/core/engine/carryover.ts +67 -0
- package/src/core/engine/houndsman-engine.ts +65 -0
- package/src/core/engine/review-scope.ts +95 -0
- package/src/core/engine/review-session.ts +79 -0
- package/src/core/file-classifier.ts +11 -67
- package/src/core/findings-state.ts +193 -100
- package/src/core/git-revision.ts +14 -40
- package/src/core/git-runner.ts +46 -0
- package/src/core/github-review.ts +4 -33
- package/src/core/jev/jev-candidates.ts +49 -35
- package/src/core/orchestrator.ts +247 -421
- package/src/core/paths.ts +26 -11
- package/src/core/pr-context.ts +39 -51
- package/src/core/project-config.ts +56 -0
- package/src/core/renderers/batch-presenter.ts +40 -0
- package/src/core/renderers/brief-renderer.ts +23 -0
- package/src/core/renderers/findings-presenter.ts +74 -0
- package/src/core/renderers/manifest-writer.ts +123 -0
- package/src/core/renderers/report-renderer.ts +66 -0
- package/src/core/renderers/verify-presenter.ts +50 -0
- package/src/core/renderers/watchdog-presenter.ts +100 -0
- package/src/core/report-builder.ts +12 -7
- package/src/core/run-layout.ts +8 -0
- package/src/core/run-status.ts +29 -59
- package/src/core/services/clock.ts +12 -0
- package/src/core/services/file-classifier.ts +34 -0
- package/src/core/services/fix-batch-builder.ts +13 -0
- package/src/core/services/gh-client.ts +54 -0
- package/src/core/services/git-repository.ts +139 -0
- package/src/core/services/project-files.ts +91 -0
- package/src/core/services/verifier.ts +58 -0
- package/src/core/storage/factory.ts +24 -0
- package/src/core/storage/file/file-storage.ts +269 -0
- package/src/core/storage/paths.ts +64 -0
- package/src/core/storage/repo-identity.ts +70 -0
- package/src/core/storage/sqlite/mappers.ts +160 -0
- package/src/core/storage/sqlite/schema.ts +108 -0
- package/src/core/storage/sqlite/sqlite-storage.ts +427 -0
- package/src/core/storage/types.ts +143 -0
- package/src/core/storage/work-area.ts +191 -0
- package/src/core/verify.ts +2 -1
- package/src/core/watchdog-registry.ts +58 -16
- package/src/index.ts +57 -6
- package/src/server/artifacts.ts +8 -61
- package/src/server/client/ArtifactsPanel.tsx +137 -23
- package/src/server/client/FindingCard.tsx +37 -6
- package/src/server/client/MarkdownText.tsx +101 -0
- package/src/server/client/SubmitModal.tsx +9 -4
- package/src/server/client/code-highlight.ts +422 -0
- package/src/server/client/constants.ts +130 -24
- package/src/server/client/json-highlight.ts +55 -0
- package/src/server/client/markdown.ts +153 -0
- package/src/server/client/types.ts +23 -0
- package/src/server/index.ts +155 -245
- package/src/server/run-repository.ts +299 -0
- package/src/skill/OUTPUT-FORMAT.md +6 -4
- package/src/skill/README.md +22 -14
- package/src/skill/SETUP.md +4 -4
- package/src/skill/fix.md +5 -5
- package/src/skill/hunt.md +27 -18
- package/src/skill/train.md +1 -1
- package/src/skill/verify.md +5 -5
- package/src/tests/__snapshots__/baseline.test.ts.snap +825 -0
- package/src/tests/baseline.test.ts +555 -493
- package/src/tests/cli.test.ts +40 -19
- package/src/tests/code-highlight.test.ts +123 -0
- package/src/tests/domain.test.ts +328 -0
- package/src/tests/findings-commands.test.ts +153 -131
- package/src/tests/findings-state.test.ts +6 -3
- package/src/tests/fixtures/fake-project.ts +0 -1
- package/src/tests/infrastructure.test.ts +205 -0
- package/src/tests/markdown.test.ts +89 -0
- package/src/tests/review-scripts.test.ts +8 -5
- package/src/tests/run-status.test.ts +33 -23
- package/src/tests/server.test.ts +108 -75
- package/src/tests/storage.test.ts +386 -0
package/README.md
CHANGED
|
@@ -74,8 +74,9 @@ No seu harness (Claude Code / agents), invoque o skill:
|
|
|
74
74
|
/houndsman hunt
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
O skill orquestra tudo e
|
|
78
|
-
|
|
77
|
+
O skill orquestra tudo e persiste o relatório no storage global (SQLite em
|
|
78
|
+
`<HOUNDSMAN_HOME>/houndsman.db`); a UI lê os artefatos direto do storage. Não precisa rodar
|
|
79
|
+
nenhum script à mão.
|
|
79
80
|
|
|
80
81
|
---
|
|
81
82
|
|
|
@@ -86,9 +87,9 @@ Não precisa rodar nenhum script à mão.
|
|
|
86
87
|
├─ Passo 0 contexto da PR (gh, opcional)
|
|
87
88
|
├─ Passo 1 classifica o diff → briefs por watchdog (incremental quando possível)
|
|
88
89
|
├─ Passo 2 N subagentes revisam em paralelo
|
|
89
|
-
├─ Passo 3 consolida em review.md +
|
|
90
|
+
├─ Passo 3 consolida em review.md + triagem no storage
|
|
90
91
|
├─ Passo 4 valida na UI (server) e marca os achados a executar
|
|
91
|
-
├─ Passo 5 fix corrige o grupo atual →
|
|
92
|
+
├─ Passo 5 fix corrige o grupo atual → status fix_done
|
|
92
93
|
└─ Passo 6 verify confere o que resolveu, sem re-revisar
|
|
93
94
|
```
|
|
94
95
|
|
|
@@ -111,7 +112,7 @@ O skill tem **4 capabilities**, todas em `/houndsman`:
|
|
|
111
112
|
## Triando os findings (opcional)
|
|
112
113
|
|
|
113
114
|
Depois do `--finalize`, cada finding tem um `id` curto e um `fingerprint` de **conteúdo**
|
|
114
|
-
no
|
|
115
|
+
no storage global. Você decide o que fazer com cada um — pela
|
|
115
116
|
**UI local** ou pelo **agente** (auto-fix) — sem abrir o JSON à mão:
|
|
116
117
|
|
|
117
118
|
```bash
|
|
@@ -136,12 +137,23 @@ bun run houndsman findings verify # confere o que resolveu (barato, se
|
|
|
136
137
|
> o review completo.
|
|
137
138
|
|
|
138
139
|
> A identidade do finding é o **conteúdo** (fingerprint), não a linha: itens deslocados
|
|
139
|
-
> pelo fix não se perdem. O estado
|
|
140
|
+
> pelo fix não se perdem. O estado mora no storage global (`<HOUNDSMAN_HOME>/houndsman.db`).
|
|
140
141
|
|
|
141
142
|
---
|
|
142
143
|
|
|
143
144
|
## Mantendo a configuração
|
|
144
145
|
|
|
146
|
+
- **Persistência** → por default, SQLite global em `<HOUNDSMAN_HOME>/houndsman.db`
|
|
147
|
+
(`HOUNDSMAN_HOME` sobrescreve o diretório de dados). Para voltar ao backend de arquivos,
|
|
148
|
+
troque só o provider — a interface `StorageProvider` é a mesma:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
export const houndsmanConfig = {
|
|
152
|
+
// ...
|
|
153
|
+
storage: { provider: "file", path: "./houndsman-runs" },
|
|
154
|
+
};
|
|
155
|
+
```
|
|
156
|
+
|
|
145
157
|
- **Alterar regras** → use a capability **`train`** (`/houndsman train`). Ela prova no código
|
|
146
158
|
que o padrão existe (evidência) antes de editar e roda as validações.
|
|
147
159
|
- **Atualizou a lib?** Sincronize os arquivos auto-gerados sem tocar nas suas regras:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payconductor/houndsman",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Orquestrador de revisão de Pull Requests: classifica o diff, despacha watchdogs especializados por domínio e consolida o relatório.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser de argv do CLI — reexporta a implementação de `core/`.
|
|
3
|
+
*
|
|
4
|
+
* O parser vive em `core/` para ser reutilizado pelos comandos da biblioteca
|
|
5
|
+
* (`parseReviewArgs`) sem inverter a camada `core → cli`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { ArgParser } from "../core/arg-parser";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contrato dos comandos do CLI.
|
|
3
|
+
*
|
|
4
|
+
* `core/` continua devolvendo dados; os comandos decidem o que imprimir e o
|
|
5
|
+
* exit code. O engine é carregado sob demanda (comandos sem projeto não pagam).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { HoundsmanEngine } from "../../core/engine/houndsman-engine";
|
|
9
|
+
|
|
10
|
+
/** Contexto de execução de um comando. */
|
|
11
|
+
export type CliContext = {
|
|
12
|
+
cwd: string;
|
|
13
|
+
/** Carrega a config do projeto e devolve o engine (lazy). */
|
|
14
|
+
engine: () => Promise<HoundsmanEngine>;
|
|
15
|
+
out: (message: string) => void;
|
|
16
|
+
err: (message: string) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Um subcomando do binário. */
|
|
20
|
+
export interface Command {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
run(args: string[], context: CliContext): Promise<number>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Comando `context` — coleta o contexto da PR via `gh` (Passo 0). */
|
|
2
|
+
|
|
3
|
+
import { runContextCommand } from "../../core/commands/context";
|
|
4
|
+
import { displayPath } from "../../core/paths";
|
|
5
|
+
import { ArgParser } from "../arg-parser";
|
|
6
|
+
import type { CliContext, Command } from "./command";
|
|
7
|
+
|
|
8
|
+
export class ContextCommand implements Command {
|
|
9
|
+
readonly name = "context";
|
|
10
|
+
|
|
11
|
+
async run(args: string[], context: CliContext): Promise<number> {
|
|
12
|
+
const engine = await context.engine();
|
|
13
|
+
const parser = ArgParser.from(args);
|
|
14
|
+
const { paths, context: pr } = runContextCommand(
|
|
15
|
+
engine.config,
|
|
16
|
+
engine.storage,
|
|
17
|
+
engine.repo,
|
|
18
|
+
{ branch: parser.value("--branch") },
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
if (pr.available) {
|
|
22
|
+
const src = pr.source;
|
|
23
|
+
const label = [src?.number ? `#${src.number}` : "", src?.title ?? ""]
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.join(" ");
|
|
26
|
+
context.out(
|
|
27
|
+
`[houndsman] PR ${label} (${src?.head} → ${src?.base}), ${pr.stats?.files ?? 0} arquivo(s).`,
|
|
28
|
+
);
|
|
29
|
+
context.out(`[houndsman] contexto: ${displayPath(paths.context)}`);
|
|
30
|
+
context.out(`[houndsman] brief: ${displayPath(paths.brief)}`);
|
|
31
|
+
context.out(
|
|
32
|
+
`[houndsman] próximo passo: subagente resume em ${displayPath(paths.summary)}`,
|
|
33
|
+
);
|
|
34
|
+
} else {
|
|
35
|
+
context.out(`[houndsman] contexto indisponível (${pr.reason}): ${pr.detail}`);
|
|
36
|
+
context.out(`[houndsman] marcador: ${displayPath(paths.context)}`);
|
|
37
|
+
}
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Comando `doctor` (alias `check`) — valida config, ambiente e credencial Jev. */
|
|
2
|
+
|
|
3
|
+
import { checkRegistry } from "../../core/registry-check";
|
|
4
|
+
import { storageLocation } from "../../core/storage/paths";
|
|
5
|
+
import type { CliContext, Command } from "./command";
|
|
6
|
+
|
|
7
|
+
export class DoctorCommand implements Command {
|
|
8
|
+
readonly name = "doctor";
|
|
9
|
+
|
|
10
|
+
async run(_args: string[], context: CliContext): Promise<number> {
|
|
11
|
+
const engine = await context.engine();
|
|
12
|
+
context.out("→ Validando configuração do projeto...");
|
|
13
|
+
const { problems, warnings, jevQuestions } = checkRegistry(engine.config);
|
|
14
|
+
|
|
15
|
+
if (problems.length > 0) {
|
|
16
|
+
context.err("\n❌ Problemas na configuração de revisão:\n");
|
|
17
|
+
for (const problem of problems) context.err(` - ${problem}`);
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
context.out(
|
|
21
|
+
` ✅ ${engine.config.watchdogIds.length} watchdogs, ${jevQuestions} pergunta(s) Jev.`,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
if (warnings.length > 0) {
|
|
25
|
+
context.out(`\n⚠️ ${warnings.length} aviso(s) (não bloqueantes):`);
|
|
26
|
+
for (const warning of warnings) context.out(` - ${warning}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
context.out("\n→ Ambiente:");
|
|
30
|
+
context.out(` ${Bun.which("git") ? "✅" : "❌"} git`);
|
|
31
|
+
context.out(` ${Bun.which("gh") ? "✅" : "⚠️ "} gh (opcional — contexto da PR)`);
|
|
32
|
+
context.out("\n→ Persistência:");
|
|
33
|
+
context.out(` provider: ${engine.storage.kind}`);
|
|
34
|
+
context.out(` ${storageLocation(engine.config)}`);
|
|
35
|
+
context.out(` repo: ${engine.repo.key}`);
|
|
36
|
+
await this.reportJevCredential(context);
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Reporta o estado da credencial Jev: presença e validade (chamada mínima).
|
|
42
|
+
* Fail-open — nunca altera o exit code (`doctor` é diagnóstico).
|
|
43
|
+
*/
|
|
44
|
+
private async reportJevCredential(context: CliContext): Promise<void> {
|
|
45
|
+
const { JevClient } = await import("../../core/jev");
|
|
46
|
+
const client = new JevClient();
|
|
47
|
+
if (!client.isConfigured()) {
|
|
48
|
+
context.out(" ⚠️ TYPESAFE_API_KEY (opcional — pré-análise Jev)");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const check = await client.validateCredential();
|
|
52
|
+
if (check.ok) {
|
|
53
|
+
context.out(` ✅ Jev: credencial válida (modelo ${check.model}).`);
|
|
54
|
+
} else if (check.kind === "invalid") {
|
|
55
|
+
context.out(` ❌ Jev: credencial inválida — ${check.detail}`);
|
|
56
|
+
} else {
|
|
57
|
+
context.out(` ⚠️ Jev: não foi possível validar — ${check.detail}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Comando `findings` — triagem/correção (inclui verify/submit). */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
runFindingsCommand,
|
|
5
|
+
runSubmitCommand,
|
|
6
|
+
runVerifyCommand,
|
|
7
|
+
} from "../../core/commands/findings";
|
|
8
|
+
import { ArgParser } from "../arg-parser";
|
|
9
|
+
import type { CliContext, Command } from "./command";
|
|
10
|
+
|
|
11
|
+
export class FindingsCommand implements Command {
|
|
12
|
+
readonly name = "findings";
|
|
13
|
+
|
|
14
|
+
async run(args: string[], context: CliContext): Promise<number> {
|
|
15
|
+
const engine = await context.engine();
|
|
16
|
+
const sub = ArgParser.from(args).command();
|
|
17
|
+
const result =
|
|
18
|
+
sub === "verify"
|
|
19
|
+
? await runVerifyCommand(engine.config, engine.storage, engine.repo, args)
|
|
20
|
+
: sub === "submit"
|
|
21
|
+
? await runSubmitCommand(
|
|
22
|
+
engine.config,
|
|
23
|
+
engine.storage,
|
|
24
|
+
engine.repo,
|
|
25
|
+
args,
|
|
26
|
+
)
|
|
27
|
+
: runFindingsCommand(
|
|
28
|
+
engine.config,
|
|
29
|
+
engine.storage,
|
|
30
|
+
engine.repo,
|
|
31
|
+
args,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (result.exitCode !== 0) context.err(result.output);
|
|
35
|
+
else context.out(result.output);
|
|
36
|
+
return result.exitCode;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Comando `help` — imprime o uso e encerra. */
|
|
2
|
+
|
|
3
|
+
import { HELP } from "../help";
|
|
4
|
+
import type { CliContext, Command } from "./command";
|
|
5
|
+
|
|
6
|
+
export class HelpCommand implements Command {
|
|
7
|
+
readonly name = "help";
|
|
8
|
+
|
|
9
|
+
async run(_args: string[], context: CliContext): Promise<number> {
|
|
10
|
+
context.out(HELP);
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry de comandos (CLI) — resolve nome → comando e guarda o fallback.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Command } from "./command";
|
|
6
|
+
|
|
7
|
+
/** Mapa de comandos por nome, com um comando default. */
|
|
8
|
+
export class CommandRegistry {
|
|
9
|
+
private readonly commands = new Map<string, Command>();
|
|
10
|
+
private fallback: Command | undefined;
|
|
11
|
+
|
|
12
|
+
/** Registra um comando (ou o fallback, quando `fallback`). */
|
|
13
|
+
register(command: Command, options: { fallback?: boolean } = {}): this {
|
|
14
|
+
if (options.fallback) this.fallback = command;
|
|
15
|
+
else this.commands.set(command.name, command);
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Registra o mesmo comando sob outro nome (alias). */
|
|
20
|
+
registerAs(name: string, command: Command): this {
|
|
21
|
+
this.commands.set(name, command);
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Resolve o comando pelo nome; sem nome → fallback. */
|
|
26
|
+
resolve(name: string | undefined): Command | undefined {
|
|
27
|
+
return name ? this.commands.get(name) : this.fallback;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Nomes registrados (sem o fallback). */
|
|
31
|
+
names(): string[] {
|
|
32
|
+
return [...this.commands.keys()];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Comando default — prepara ou finaliza a revisão. */
|
|
2
|
+
|
|
3
|
+
import { parseReviewArgs } from "../../core/commands/review";
|
|
4
|
+
import type { CliContext, Command } from "./command";
|
|
5
|
+
|
|
6
|
+
export class ReviewCommand implements Command {
|
|
7
|
+
readonly name = "";
|
|
8
|
+
|
|
9
|
+
async run(args: string[], context: CliContext): Promise<number> {
|
|
10
|
+
const engine = await context.engine();
|
|
11
|
+
const options = parseReviewArgs(args);
|
|
12
|
+
const result = await engine.session(options).run();
|
|
13
|
+
|
|
14
|
+
for (const message of result.messages) context.out(message);
|
|
15
|
+
|
|
16
|
+
if ("blockers" in result && options.failOnBlocking && result.blockers > 0) {
|
|
17
|
+
return 1;
|
|
18
|
+
}
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Comando `server` — sobe a UI local de triagem. */
|
|
2
|
+
|
|
3
|
+
import { ArgParser } from "../arg-parser";
|
|
4
|
+
import type { CliContext, Command } from "./command";
|
|
5
|
+
|
|
6
|
+
/** Abre a URL no navegador padrão (best-effort, por plataforma). */
|
|
7
|
+
function openBrowser(url: string): void {
|
|
8
|
+
try {
|
|
9
|
+
const command =
|
|
10
|
+
process.platform === "win32"
|
|
11
|
+
? ["cmd", "/c", "start", "", url]
|
|
12
|
+
: process.platform === "darwin"
|
|
13
|
+
? ["open", url]
|
|
14
|
+
: ["xdg-open", url];
|
|
15
|
+
Bun.spawn(command, { stdout: "ignore", stderr: "ignore" });
|
|
16
|
+
} catch {
|
|
17
|
+
// best-effort — se não abrir, o usuário copia a URL.
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class ServerCommand implements Command {
|
|
22
|
+
readonly name = "server";
|
|
23
|
+
|
|
24
|
+
async run(args: string[], context: CliContext): Promise<number> {
|
|
25
|
+
const engine = await context.engine();
|
|
26
|
+
const parser = ArgParser.from(args);
|
|
27
|
+
const port = Number(parser.value("--port")) || 7331;
|
|
28
|
+
const host = parser.value("--host") ?? "127.0.0.1";
|
|
29
|
+
|
|
30
|
+
const { startServer } = await import("../../server");
|
|
31
|
+
const { url } = startServer({ config: engine.config, port, host });
|
|
32
|
+
context.out(`[houndsman] servidor em ${url}`);
|
|
33
|
+
context.out("[houndsman] Ctrl+C para encerrar.");
|
|
34
|
+
if (parser.has("--open")) openBrowser(url);
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Comandos que não dependem da config do projeto: setup, update e test. */
|
|
2
|
+
|
|
3
|
+
import type { CliContext, Command } from "./command";
|
|
4
|
+
|
|
5
|
+
export class SetupCommand implements Command {
|
|
6
|
+
readonly name = "setup";
|
|
7
|
+
|
|
8
|
+
async run(args: string[], context: CliContext): Promise<number> {
|
|
9
|
+
const { runSetupCommand } = await import("../../core/commands/setup");
|
|
10
|
+
const messages = await runSetupCommand({ cwd: context.cwd, args });
|
|
11
|
+
for (const message of messages) context.out(message);
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class UpdateCommand implements Command {
|
|
17
|
+
readonly name = "update";
|
|
18
|
+
|
|
19
|
+
async run(args: string[], context: CliContext): Promise<number> {
|
|
20
|
+
const { runUpdateCommand } = await import("../../core/commands/update");
|
|
21
|
+
const messages = await runUpdateCommand({ cwd: context.cwd, args });
|
|
22
|
+
for (const message of messages) context.out(message);
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class TestCommand implements Command {
|
|
28
|
+
readonly name = "test";
|
|
29
|
+
|
|
30
|
+
async run(_args: string[], _context: CliContext): Promise<number> {
|
|
31
|
+
const { runLibTests } = await import("../../core/commands/test");
|
|
32
|
+
return runLibTests();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Comando `watchdog` — contexto de um watchdog. */
|
|
2
|
+
|
|
3
|
+
import { renderWatchdogContext } from "../../core/commands/watchdog";
|
|
4
|
+
import { ArgParser } from "../arg-parser";
|
|
5
|
+
import type { CliContext, Command } from "./command";
|
|
6
|
+
|
|
7
|
+
export class WatchdogCommand implements Command {
|
|
8
|
+
readonly name = "watchdog";
|
|
9
|
+
|
|
10
|
+
async run(args: string[], context: CliContext): Promise<number> {
|
|
11
|
+
const engine = await context.engine();
|
|
12
|
+
const parser = ArgParser.from(args);
|
|
13
|
+
const id = args.find((arg) => !arg.startsWith("-"));
|
|
14
|
+
|
|
15
|
+
if (!id) {
|
|
16
|
+
context.err("Informe o id do watchdog: houndsman watchdog <id>");
|
|
17
|
+
context.err(`Ids válidos: ${engine.config.watchdogIds.join(", ")}`);
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const output = renderWatchdogContext(engine.config, id, {
|
|
22
|
+
withRules: parser.has("--rules"),
|
|
23
|
+
json: parser.has("--json"),
|
|
24
|
+
});
|
|
25
|
+
if (output === null) {
|
|
26
|
+
context.err(`Watchdog '${id}' não existe.`);
|
|
27
|
+
context.err(`Ids válidos: ${engine.config.watchdogIds.join(", ")}`);
|
|
28
|
+
return 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
context.out(output);
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/cli/help.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Texto de ajuda do CLI (mantido fora do bootstrap para reduzir acoplamento). */
|
|
2
|
+
export const HELP = `Uso: houndsman [comando] [flags]
|
|
3
|
+
|
|
4
|
+
Comandos:
|
|
5
|
+
(nenhum) Prepara a revisão (classification + briefs + manifesto)
|
|
6
|
+
--finalize Consolida os findings em review.md
|
|
7
|
+
context Coleta o contexto da PR via gh (Passo 0)
|
|
8
|
+
findings <sub> Triagem/correção dos findings (list · show · set · done · reset · sync · summary · batch · verify · submit · path)
|
|
9
|
+
server Sobe a UI local de triagem (--port, --host, --open)
|
|
10
|
+
setup Cria a configuração inicial do projeto (config + skills)
|
|
11
|
+
update Sincroniza os arquivos auto-gerados (.houndsman/core)
|
|
12
|
+
doctor Valida a configuração, o ambiente e a credencial Jev
|
|
13
|
+
test Roda a suíte de testes da lib
|
|
14
|
+
watchdog <id> Contexto de um watchdog (--rules, --json)
|
|
15
|
+
|
|
16
|
+
Flags:
|
|
17
|
+
--config <path> Caminho do houndsman.config.* (default: detecta no cwd)
|
|
18
|
+
--base <branch> Base do diff (default: main)
|
|
19
|
+
--head <ref> Head do diff (default: HEAD)
|
|
20
|
+
--branch <nome> Branch alvo (default: detecta do git)
|
|
21
|
+
--clean Descarta findings brutos do staging anterior
|
|
22
|
+
--full Força review completo (ignora o modo incremental)
|
|
23
|
+
--no-jev Desativa a pré-análise Jev
|
|
24
|
+
--fail-on-blocking Exit 1 se houver bloqueantes (com --finalize)
|
|
25
|
+
|
|
26
|
+
Flags de findings:
|
|
27
|
+
--decision <d> pending | execute | ignore
|
|
28
|
+
--exec <e> pending | in_progress | done | stale
|
|
29
|
+
--comment <text> Comentário/instrução adicional
|
|
30
|
+
--watchdog <id> Filtra por watchdog (list)
|
|
31
|
+
--severity <s> Filtra por severidade (list, batch)
|
|
32
|
+
--file <text> Filtra por trecho do caminho (list)
|
|
33
|
+
--all Inclui findings ausentes do último relatório
|
|
34
|
+
--json Saída JSON
|
|
35
|
+
--limit <n> Limita o nº de itens (list)
|
|
36
|
+
--group <key> Grupo alvo no batch (default: o primeiro)
|
|
37
|
+
--offset <n> Paginação dentro do grupo (batch)
|
|
38
|
+
--include-done Inclui já concluídos no batch
|
|
39
|
+
--apply (verify) Marca os resolvidos como done
|
|
40
|
+
--fail-on-persisting (verify) Exit 1 se algum blocking persistir
|
|
41
|
+
--include-sent (submit) Reenvia findings já enviados à PR
|
|
42
|
+
--dry-run (submit) Só a prévia do envio, sem postar
|
|
43
|
+
|
|
44
|
+
Persistência:
|
|
45
|
+
Runs, findings e artefatos ficam no storage global (SQLite em
|
|
46
|
+
<HOUNDSMAN_HOME>/houndsman.db). Use storage.provider: "file" na config para
|
|
47
|
+
voltar ao backend de arquivos. Nada é gravado em .houndsman/.runs.`;
|