@payconductor/houndsman 0.3.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/commands/context-command.ts +11 -9
- package/src/cli/commands/doctor-command.ts +5 -0
- package/src/cli/commands/findings-command.ts +13 -3
- package/src/cli/commands/review-command.ts +1 -2
- package/src/cli/help.ts +7 -3
- package/src/cli/index.ts +2 -2
- package/src/core/commands/context.ts +29 -13
- package/src/core/commands/findings.ts +155 -120
- package/src/core/commands/review.ts +7 -8
- package/src/core/commands/scaffold.ts +15 -16
- package/src/core/config.ts +16 -7
- package/src/core/engine/houndsman-engine.ts +15 -1
- package/src/core/engine/review-session.ts +37 -9
- package/src/core/findings-state.ts +32 -71
- package/src/core/jev/jev-candidates.ts +49 -35
- package/src/core/orchestrator.ts +175 -128
- package/src/core/paths.ts +23 -17
- package/src/core/pr-context.ts +19 -5
- package/src/core/project-config.ts +4 -5
- package/src/core/renderers/manifest-writer.ts +29 -18
- package/src/core/run-layout.ts +4 -104
- package/src/core/run-status.ts +28 -53
- package/src/core/services/git-repository.ts +16 -0
- package/src/core/services/project-files.ts +13 -1
- 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/index.ts +27 -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 +95 -15
- package/src/server/run-repository.ts +144 -136
- 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 +44 -33
- package/src/tests/baseline.test.ts +76 -34
- package/src/tests/cli.test.ts +40 -19
- package/src/tests/code-highlight.test.ts +123 -0
- package/src/tests/domain.test.ts +1 -1
- 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 +33 -38
- 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",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Comando `context` — coleta o contexto da PR via `gh` (Passo 0). */
|
|
2
2
|
|
|
3
3
|
import { runContextCommand } from "../../core/commands/context";
|
|
4
|
-
import {
|
|
4
|
+
import { displayPath } from "../../core/paths";
|
|
5
5
|
import { ArgParser } from "../arg-parser";
|
|
6
6
|
import type { CliContext, Command } from "./command";
|
|
7
7
|
|
|
@@ -11,10 +11,12 @@ export class ContextCommand implements Command {
|
|
|
11
11
|
async run(args: string[], context: CliContext): Promise<number> {
|
|
12
12
|
const engine = await context.engine();
|
|
13
13
|
const parser = ArgParser.from(args);
|
|
14
|
-
const { paths, context: pr } = runContextCommand(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
const { paths, context: pr } = runContextCommand(
|
|
15
|
+
engine.config,
|
|
16
|
+
engine.storage,
|
|
17
|
+
engine.repo,
|
|
18
|
+
{ branch: parser.value("--branch") },
|
|
19
|
+
);
|
|
18
20
|
|
|
19
21
|
if (pr.available) {
|
|
20
22
|
const src = pr.source;
|
|
@@ -24,14 +26,14 @@ export class ContextCommand implements Command {
|
|
|
24
26
|
context.out(
|
|
25
27
|
`[houndsman] PR ${label} (${src?.head} → ${src?.base}), ${pr.stats?.files ?? 0} arquivo(s).`,
|
|
26
28
|
);
|
|
27
|
-
context.out(`[houndsman] contexto: ${
|
|
28
|
-
context.out(`[houndsman] brief: ${
|
|
29
|
+
context.out(`[houndsman] contexto: ${displayPath(paths.context)}`);
|
|
30
|
+
context.out(`[houndsman] brief: ${displayPath(paths.brief)}`);
|
|
29
31
|
context.out(
|
|
30
|
-
`[houndsman] próximo passo: subagente resume em ${
|
|
32
|
+
`[houndsman] próximo passo: subagente resume em ${displayPath(paths.summary)}`,
|
|
31
33
|
);
|
|
32
34
|
} else {
|
|
33
35
|
context.out(`[houndsman] contexto indisponível (${pr.reason}): ${pr.detail}`);
|
|
34
|
-
context.out(`[houndsman] marcador: ${
|
|
36
|
+
context.out(`[houndsman] marcador: ${displayPath(paths.context)}`);
|
|
35
37
|
}
|
|
36
38
|
return 0;
|
|
37
39
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Comando `doctor` (alias `check`) — valida config, ambiente e credencial Jev. */
|
|
2
2
|
|
|
3
3
|
import { checkRegistry } from "../../core/registry-check";
|
|
4
|
+
import { storageLocation } from "../../core/storage/paths";
|
|
4
5
|
import type { CliContext, Command } from "./command";
|
|
5
6
|
|
|
6
7
|
export class DoctorCommand implements Command {
|
|
@@ -28,6 +29,10 @@ export class DoctorCommand implements Command {
|
|
|
28
29
|
context.out("\n→ Ambiente:");
|
|
29
30
|
context.out(` ${Bun.which("git") ? "✅" : "❌"} git`);
|
|
30
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}`);
|
|
31
36
|
await this.reportJevCredential(context);
|
|
32
37
|
return 0;
|
|
33
38
|
}
|
|
@@ -16,10 +16,20 @@ export class FindingsCommand implements Command {
|
|
|
16
16
|
const sub = ArgParser.from(args).command();
|
|
17
17
|
const result =
|
|
18
18
|
sub === "verify"
|
|
19
|
-
? await runVerifyCommand(engine.config, args)
|
|
19
|
+
? await runVerifyCommand(engine.config, engine.storage, engine.repo, args)
|
|
20
20
|
: sub === "submit"
|
|
21
|
-
? await runSubmitCommand(
|
|
22
|
-
|
|
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
|
+
);
|
|
23
33
|
|
|
24
34
|
if (result.exitCode !== 0) context.err(result.output);
|
|
25
35
|
else context.out(result.output);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/** Comando default — prepara ou finaliza a revisão. */
|
|
2
2
|
|
|
3
3
|
import { parseReviewArgs } from "../../core/commands/review";
|
|
4
|
-
import { resolveArtifactDir } from "../../core/config";
|
|
5
4
|
import type { CliContext, Command } from "./command";
|
|
6
5
|
|
|
7
6
|
export class ReviewCommand implements Command {
|
|
@@ -9,7 +8,7 @@ export class ReviewCommand implements Command {
|
|
|
9
8
|
|
|
10
9
|
async run(args: string[], context: CliContext): Promise<number> {
|
|
11
10
|
const engine = await context.engine();
|
|
12
|
-
const options = parseReviewArgs(args
|
|
11
|
+
const options = parseReviewArgs(args);
|
|
13
12
|
const result = await engine.session(options).run();
|
|
14
13
|
|
|
15
14
|
for (const message of result.messages) context.out(message);
|
package/src/cli/help.ts
CHANGED
|
@@ -18,8 +18,7 @@ Flags:
|
|
|
18
18
|
--base <branch> Base do diff (default: main)
|
|
19
19
|
--head <ref> Head do diff (default: HEAD)
|
|
20
20
|
--branch <nome> Branch alvo (default: detecta do git)
|
|
21
|
-
--
|
|
22
|
-
--clean Descarta findings anteriores
|
|
21
|
+
--clean Descarta findings brutos do staging anterior
|
|
23
22
|
--full Força review completo (ignora o modo incremental)
|
|
24
23
|
--no-jev Desativa a pré-análise Jev
|
|
25
24
|
--fail-on-blocking Exit 1 se houver bloqueantes (com --finalize)
|
|
@@ -40,4 +39,9 @@ Flags de findings:
|
|
|
40
39
|
--apply (verify) Marca os resolvidos como done
|
|
41
40
|
--fail-on-persisting (verify) Exit 1 se algum blocking persistir
|
|
42
41
|
--include-sent (submit) Reenvia findings já enviados à PR
|
|
43
|
-
--dry-run (submit) Só a prévia do envio, sem postar
|
|
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.`;
|
package/src/cli/index.ts
CHANGED
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
* test Roda a suíte de testes da lib
|
|
23
23
|
* watchdog <id> Contexto de um watchdog (`--rules`, `--json`)
|
|
24
24
|
*
|
|
25
|
-
* Flags: `--base <b>` · `--head <ref>` · `--branch <b>` · `--
|
|
26
|
-
* `--
|
|
25
|
+
* Flags: `--base <b>` · `--head <ref>` · `--branch <b>` · `--config <path>` ·
|
|
26
|
+
* `--clean` · `--full` · `--no-jev` · `--fail-on-blocking`.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import { loadConfig } from "../core/config-loader";
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Subcomando `context` — coleta o contexto da PR via `gh` (Passo 0).
|
|
3
3
|
*
|
|
4
|
-
* Biblioteca: grava
|
|
4
|
+
* Biblioteca: grava no staging da review + artefato no storage; o CLI faz o
|
|
5
|
+
* log/exit.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
-
import { type HoundsmanConfig, resolveArtifactDir } from "../config";
|
|
8
|
+
import type { HoundsmanConfig } from "../config";
|
|
9
9
|
import {
|
|
10
|
-
type ContextPaths,
|
|
11
10
|
collectPrContext,
|
|
12
|
-
contextPaths,
|
|
13
11
|
currentBranch,
|
|
14
12
|
type PrContextRaw,
|
|
15
13
|
renderContextBrief,
|
|
16
14
|
} from "../pr-context";
|
|
15
|
+
import { sanitizeBranch } from "../report-builder";
|
|
16
|
+
import type { RepoIdentity, StorageProvider } from "../storage/types";
|
|
17
|
+
import { type ContextPaths, ReviewWorkArea } from "../storage/work-area";
|
|
17
18
|
|
|
18
19
|
export type ContextRunResult = {
|
|
19
20
|
paths: ContextPaths;
|
|
@@ -22,18 +23,33 @@ export type ContextRunResult = {
|
|
|
22
23
|
|
|
23
24
|
/** Coleta a PR atual e grava `context.json` + `context-brief.md`. */
|
|
24
25
|
export function runContextCommand(
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
_config: HoundsmanConfig,
|
|
27
|
+
storage: StorageProvider,
|
|
28
|
+
repo: RepoIdentity,
|
|
29
|
+
opts: { branch?: string } = {},
|
|
27
30
|
): ContextRunResult {
|
|
28
31
|
const branch = opts.branch ?? currentBranch();
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
const review = storage.getOrCreateReview({
|
|
33
|
+
repo,
|
|
34
|
+
branch,
|
|
35
|
+
slug: sanitizeBranch(branch),
|
|
36
|
+
});
|
|
37
|
+
const work = new ReviewWorkArea(review);
|
|
38
|
+
const paths = work.contextPaths;
|
|
33
39
|
|
|
34
40
|
const context = collectPrContext(branch);
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
const brief = renderContextBrief(context, branch, paths);
|
|
42
|
+
work.writeContext(context, brief);
|
|
43
|
+
storage.writeArtifact(review.id, {
|
|
44
|
+
name: "context.json",
|
|
45
|
+
kind: "context",
|
|
46
|
+
content: `${JSON.stringify(context, null, 2)}\n`,
|
|
47
|
+
});
|
|
48
|
+
storage.writeArtifact(review.id, {
|
|
49
|
+
name: "context-brief.md",
|
|
50
|
+
kind: "context-brief",
|
|
51
|
+
content: `${brief}\n`,
|
|
52
|
+
});
|
|
37
53
|
|
|
38
54
|
return { paths, context };
|
|
39
55
|
}
|