@payconductor/houndsman 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +23 -2
  2. package/package.json +1 -1
  3. package/src/cli/commands/compare-command.ts +112 -0
  4. package/src/cli/commands/context-command.ts +1 -0
  5. package/src/cli/commands/doctor-command.ts +22 -0
  6. package/src/cli/commands/findings-command.ts +11 -1
  7. package/src/cli/commands/hunt-command.ts +479 -0
  8. package/src/cli/commands/review-command.ts +58 -2
  9. package/src/cli/commands/status-command.ts +3 -2
  10. package/src/cli/help.ts +20 -3
  11. package/src/cli/index.ts +20 -3
  12. package/src/cli/progress.ts +50 -0
  13. package/src/core/ai/ai-client.ts +38 -2
  14. package/src/core/ai/ai-sdk-model-provider.ts +39 -0
  15. package/src/core/ai/features/brief-enricher.ts +1 -1
  16. package/src/core/ai/features/business-analyzer.ts +61 -0
  17. package/src/core/ai/features/business-summarizer.ts +44 -0
  18. package/src/core/ai/index.ts +14 -1
  19. package/src/core/ai/prompts/brief.ts +33 -7
  20. package/src/core/ai/prompts/business.ts +76 -0
  21. package/src/core/ai/provider-capabilities.ts +12 -0
  22. package/src/core/ai/schemas.ts +70 -2
  23. package/src/core/ai/types.ts +2 -0
  24. package/src/core/ai/usage-recorder.ts +53 -1
  25. package/src/core/brief.ts +246 -12
  26. package/src/core/builtins.ts +108 -0
  27. package/src/core/business-summary.ts +583 -0
  28. package/src/core/commands/compare.ts +233 -0
  29. package/src/core/commands/context.ts +6 -2
  30. package/src/core/commands/findings.ts +38 -30
  31. package/src/core/commands/review.ts +6 -0
  32. package/src/core/commands/scaffold.ts +38 -0
  33. package/src/core/commands/status.ts +9 -4
  34. package/src/core/compare.ts +243 -0
  35. package/src/core/concurrency.ts +30 -0
  36. package/src/core/config.ts +57 -1
  37. package/src/core/defaults.ts +63 -2
  38. package/src/core/domain/budget.ts +62 -4
  39. package/src/core/domain/classification.ts +12 -2
  40. package/src/core/engine/carryover.ts +4 -0
  41. package/src/core/engine/finalize-pipeline.ts +347 -0
  42. package/src/core/engine/prepare-pipeline.ts +1294 -0
  43. package/src/core/engine/review-context.ts +51 -0
  44. package/src/core/engine/review-options.ts +47 -0
  45. package/src/core/engine/review-scope.ts +106 -26
  46. package/src/core/engine/review-session.ts +18 -16
  47. package/src/core/engine/review-workspace.ts +14 -0
  48. package/src/core/engine/run-artifacts.ts +196 -0
  49. package/src/core/engine/run-format.ts +4 -0
  50. package/src/core/env.ts +14 -0
  51. package/src/core/findings-state.ts +13 -1
  52. package/src/core/github-review.ts +5 -2
  53. package/src/core/hunt/hunt-runner.ts +418 -0
  54. package/src/core/hunt/index.ts +61 -0
  55. package/src/core/hunt/maki-install.ts +331 -0
  56. package/src/core/hunt/maki-intent-prompt.ts +50 -0
  57. package/src/core/hunt/maki-model.ts +101 -0
  58. package/src/core/hunt/maki-prompt.ts +83 -0
  59. package/src/core/hunt/maki-release.ts +104 -0
  60. package/src/core/hunt/maki-runner.ts +276 -0
  61. package/src/core/hunt/pool.ts +8 -0
  62. package/src/core/logging/index.ts +7 -0
  63. package/src/core/logging/logger.ts +19 -0
  64. package/src/core/logging/progress.ts +366 -0
  65. package/src/core/orchestrator.ts +33 -1180
  66. package/src/core/paths.ts +6 -3
  67. package/src/core/pr-context.ts +2 -4
  68. package/src/core/prepare-report.ts +61 -0
  69. package/src/core/project-config.ts +46 -1
  70. package/src/core/propagation.ts +127 -0
  71. package/src/core/registry-check.ts +60 -21
  72. package/src/core/renderers/manifest-writer.ts +13 -0
  73. package/src/core/renderers/resume-renderer.ts +151 -11
  74. package/src/core/report-builder.ts +108 -278
  75. package/src/core/storage/file/file-storage.ts +7 -3
  76. package/src/core/storage/sqlite/mappers.ts +10 -0
  77. package/src/core/storage/sqlite/schema.ts +104 -23
  78. package/src/core/storage/sqlite/sqlite-storage.ts +22 -9
  79. package/src/core/storage/types.ts +17 -4
  80. package/src/core/storage/work-area.ts +59 -1
  81. package/src/core/types.ts +31 -0
  82. package/src/core/watchdog-registry.ts +21 -3
  83. package/src/index.ts +78 -4
  84. package/src/server/client/ArtifactsPanel.tsx +4 -2
  85. package/src/server/client/FindingCard.tsx +21 -1
  86. package/src/server/client/RunsSection.tsx +5 -0
  87. package/src/server/client/utils/constants.ts +21 -11
  88. package/src/server/client/utils/markdown.ts +1 -1
  89. package/src/server/client/utils/types.ts +14 -2
  90. package/src/server/run-repository.ts +35 -14
  91. package/src/server/styles.css +2 -0
  92. package/src/skill/OUTPUT-FORMAT.md +36 -10
  93. package/src/skill/README.md +30 -14
  94. package/src/skill/SETUP.md +22 -1
  95. package/src/skill/SKILL.md +17 -4
  96. package/src/skill/example-resume.md +43 -0
  97. package/src/skill/hunt.md +181 -58
  98. package/src/tests/__snapshots__/baseline.test.ts.snap +206 -178
  99. package/src/tests/ai-core.test.ts +84 -1
  100. package/src/tests/ai-features.test.ts +95 -4
  101. package/src/tests/baseline.test.ts +29 -121
  102. package/src/tests/brief-scope.test.ts +250 -0
  103. package/src/tests/business-summary.test.ts +258 -0
  104. package/src/tests/cli.test.ts +1 -0
  105. package/src/tests/compare.test.ts +233 -0
  106. package/src/tests/fixtures/fake-model-provider.ts +2 -1
  107. package/src/tests/hunt-cli.test.ts +185 -0
  108. package/src/tests/hunt-install.test.ts +153 -0
  109. package/src/tests/hunt-maki.test.ts +525 -0
  110. package/src/tests/markdown.test.ts +3 -3
  111. package/src/tests/progress.test.ts +145 -0
  112. package/src/tests/propagation.test.ts +119 -0
  113. package/src/tests/review-resume.test.ts +60 -1
  114. package/src/tests/review-scope.test.ts +90 -19
  115. package/src/tests/review-scripts.test.ts +106 -218
  116. package/src/tests/server.test.ts +7 -4
  117. package/src/tests/storage.test.ts +194 -9
  118. package/src/core/renderers/report-renderer.ts +0 -66
  119. package/src/skill/example-review.md +0 -66
package/README.md CHANGED
@@ -15,6 +15,11 @@ agente especialista por domínio que audita o diff · **review/revisão** = o pr
15
15
  **Jev** = pré-análise opcional. O vocabulário completo (brief, findings, manifest, budget,
16
16
  overflow, churn, candidato…) está em [`docs/GLOSSARY.md`](docs/GLOSSARY.md).
17
17
 
18
+ > Toda revisão tem duas etapas fixas: um **modelo de intenção** da PR
19
+ > (`business-summary.json`) e o revisor de fluxo **`general`**. Nenhum dos dois é
20
+ > configurável — o primeiro enriquece os briefs; o segundo procura gaps de lógica/domínio,
21
+ > não só violações de regra.
22
+
18
23
  ---
19
24
 
20
25
  ## Como iniciar
@@ -28,9 +33,11 @@ diferente das PRs. O motor só coordena a caçada — as regras são problema se
28
33
  - **git** — o diff usa `git diff <base>...<head>`.
29
34
  - **gh** (Opcional) — Obtém contexto da PR e dispara comentários para o GitHub
30
35
  - **`HOUNDSMAN_TYPE_SAFE_API_KEY`** (opcional) — pré-análise Jev.
31
- - **`HOUNDSMAN_DEEP_SEEK_API_KEY`** (opcional) — features de IA (briefs, contexto, classificação, resumo técnico).
36
+ - **`HOUNDSMAN_DEEP_SEEK_API_KEY`** (opcional) — features de IA (briefs, contexto, classificação, resumo técnico) e harness local `hunt`.
32
37
  - **`HOUNDSMAN_OPENAI_API_KEY`** / **`HOUNDSMAN_ANTHROPIC_API_KEY`** (opcional) — apenas se
33
38
  `ai.provider` for `openai`/`anthropic`.
39
+ - **maki** (opcional) — só para o harness local `houndsman hunt`; a skill `/houndsman hunt`
40
+ não precisa dele. Instale com `houndsman hunt install`.
34
41
 
35
42
  > Coloque as chaves em um `.env` na raiz do projeto; o Bun carrega automaticamente.
36
43
 
@@ -84,6 +91,20 @@ O skill orquestra tudo e persiste o relatório no storage global (SQLite em
84
91
  `<HOUNDSMAN_HOME>/houndsman.db`); a UI lê os artefatos direto do storage. Não precisa rodar
85
92
  nenhum script à mão.
86
93
 
94
+ #### Alternativa: rodar 100% pelo CLI (opcional)
95
+
96
+ Quem não quer usar um agente externo pode executar a mesma revisão pelo terminal, com o
97
+ maki como harness local. A instalação é única e fica fora do projeto:
98
+
99
+ ```bash
100
+ houndsman hunt install # baixa o maki fixado em <HOUNDSMAN_HOME>/tools/maki
101
+ houndsman hunt doctor # diagnóstico (não falha se o maki não existir)
102
+ houndsman hunt --pr 456 # prepare → maki (1 processo por brief) → finalize
103
+ ```
104
+
105
+ Os dois caminhos produzem os mesmos artefatos e usam o mesmo staging. A skill
106
+ `/houndsman hunt` continua sendo o caminho canônico para agentes externos.
107
+
87
108
  ### 7. Várias PRs ao mesmo tempo (worktrees)
88
109
 
89
110
  Cada revisão pode rodar em um **worktree dedicado** (detached no commit da PR), sem
@@ -112,7 +133,7 @@ mostra o worktree ativo de cada PR e permite criá-lo pela sidebar.
112
133
  ├─ Passo 0 contexto da PR (gh, opcional)
113
134
  ├─ Passo 1 classifica o diff → briefs por watchdog (incremental quando possível)
114
135
  ├─ Passo 2 N subagentes revisam em paralelo
115
- ├─ Passo 3 consolida em review.md + triagem no storage
136
+ ├─ Passo 3 grava o resumo final (review-resume.md) + triagem no storage
116
137
  ├─ Passo 4 valida na UI (server) e marca os achados a executar
117
138
  ├─ Passo 5 fix corrige o grupo atual → status fix_done
118
139
  └─ Passo 6 verify confere o que resolveu, sem re-revisar
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payconductor/houndsman",
3
- "version": "0.12.0",
3
+ "version": "0.14.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,112 @@
1
+ /**
2
+ * Comando `compare` — teste A/B entre duas runs (por slug ou branch+variantes).
3
+ *
4
+ * Exemplo:
5
+ * houndsman compare --branch feature/x --a baseline --b otimizado
6
+ * houndsman compare feature-x--baseline-1a2b3c feature-x--otimizado-9f8e7d
7
+ */
8
+
9
+ import { listRunsForCompare, runCompare } from "../../core/commands/compare";
10
+ import { ArgParser } from "../arg-parser";
11
+ import type { CliContext, Command } from "./command";
12
+
13
+ /** Flags com valor (não entram nos positionais). */
14
+ const VALUE_FLAGS = new Set(["--branch", "--a", "--b", "--config"]);
15
+
16
+ export const COMPARE_HELP = `Uso: houndsman compare [<slugA> <slugB>] [flags]
17
+
18
+ Compara duas runs da mesma branch e grava o relatório na run B.
19
+
20
+ Flags:
21
+ --branch <nome> Branch alvo (default: a atual do git)
22
+ --a <variante> Rótulo A (default: run principal)
23
+ --b <variante> Rótulo B (obrigatório sem slugs)
24
+ --list Lista as runs/variantes com contagem por severidade
25
+ --json Saída JSON (sem markdown)
26
+
27
+ Exemplos:
28
+ houndsman compare --branch feature/x --a baseline --b otimizado
29
+ houndsman compare feature-x--baseline-1a2b3c feature-x--otimizado-9f8e7d
30
+ houndsman compare --list --branch feature/x`;
31
+
32
+ /** Argumentos posicionais (ignora flags com valor). */
33
+ function positionals(args: readonly string[]): string[] {
34
+ const result: string[] = [];
35
+ for (let index = 0; index < args.length; index++) {
36
+ const arg = args[index];
37
+ if (arg.startsWith("--")) {
38
+ if (VALUE_FLAGS.has(arg)) index++;
39
+ continue;
40
+ }
41
+ result.push(arg);
42
+ }
43
+ return result;
44
+ }
45
+
46
+ export class CompareCommand implements Command {
47
+ readonly name = "compare";
48
+
49
+ async run(args: string[], context: CliContext): Promise<number> {
50
+ const parser = ArgParser.from(args);
51
+ if (parser.has("--help") || parser.has("-h")) {
52
+ context.out(COMPARE_HELP);
53
+ return 0;
54
+ }
55
+
56
+ const engine = await context.engine();
57
+ const branch = parser.value("--branch");
58
+
59
+ if (parser.has("--list")) {
60
+ const runs = listRunsForCompare(engine.storage, engine.repo, branch);
61
+ if (runs.length === 0) {
62
+ context.out("Nenhuma run encontrada.");
63
+ engine.close();
64
+ return 0;
65
+ }
66
+ context.logger.table([
67
+ ["slug", "variante", "findings", "blocking", "attention", "atualizada"],
68
+ ...runs.map((run) => [
69
+ run.slug,
70
+ run.variant ?? "(principal)",
71
+ String(run.findings),
72
+ String(run.bySeverity.blocking),
73
+ String(run.bySeverity.attention),
74
+ run.updatedAt,
75
+ ]),
76
+ ]);
77
+ engine.close();
78
+ return 0;
79
+ }
80
+
81
+ const [slugA, slugB] = positionals(args);
82
+ if (slugA && !slugB) {
83
+ context.err(
84
+ "Passe dois slugs (`houndsman compare <a> <b>`) ou use --b <variante>.",
85
+ );
86
+ return 1;
87
+ }
88
+ try {
89
+ const result = runCompare(engine.storage, engine.repo, {
90
+ slugA,
91
+ slugB,
92
+ branch,
93
+ variantA: parser.value("--a"),
94
+ variantB: parser.value("--b"),
95
+ });
96
+ if (parser.has("--json")) {
97
+ context.out(result.json.trimEnd());
98
+ } else {
99
+ context.out(result.markdown.trimEnd());
100
+ context.logger.success(
101
+ `Comparação salva em ${result.artifacts.markdown} (run ${result.b.slug}).`,
102
+ );
103
+ }
104
+ return 0;
105
+ } catch (error) {
106
+ context.err(error instanceof Error ? error.message : String(error));
107
+ return 1;
108
+ } finally {
109
+ engine.close();
110
+ }
111
+ }
112
+ }
@@ -51,6 +51,7 @@ export class ContextCommand implements Command {
51
51
  engine.repo,
52
52
  {
53
53
  branch: workspace?.branch ?? parser.value("--branch"),
54
+ variant: parser.value("--variant"),
54
55
  worktreePath: workspace?.worktreePath,
55
56
  prNumber: workspace?.prNumber,
56
57
  },
@@ -1,9 +1,11 @@
1
1
  /** Comando `doctor` (alias `check`) — valida config, ambiente e credenciais. */
2
2
 
3
+ import { existsSync } from "node:fs";
3
4
  import type { AiClient } from "../../core/ai/ai-client";
4
5
  import { ArgParser } from "../../core/arg-parser";
5
6
  import type { AiProviderKind } from "../../core/config";
6
7
  import { ENV_NAMES, env } from "../../core/env";
8
+ import { displayPath } from "../../core/paths";
7
9
  import { checkRegistry } from "../../core/registry-check";
8
10
  import { storageLocation } from "../../core/storage/paths";
9
11
  import type { CliContext, Command } from "./command";
@@ -66,6 +68,26 @@ export class DoctorCommand implements Command {
66
68
  const ast = await engine.ast.check();
67
69
  logger.bullet(`${ast.ok ? "✅" : "⚠️ "} AST: ${ast.detail}`);
68
70
 
71
+ // Harness local é opcional: a ausência do maki nunca falha o doctor.
72
+ const { makiSourceLabel, probeMakiVersion, resolveMakiBinary } = await import(
73
+ "../../core/hunt/maki-install"
74
+ );
75
+ const maki = resolveMakiBinary(engine.config.hunt?.binaryPath);
76
+ if (!maki) {
77
+ logger.bullet(
78
+ "⚠️ maki (hunt local): não instalado (opcional — `houndsman hunt install`).",
79
+ );
80
+ } else {
81
+ const exists = existsSync(maki.path);
82
+ const version = exists
83
+ ? (maki.version ?? probeMakiVersion(maki.path))
84
+ : undefined;
85
+ logger.bullet(
86
+ `${exists ? "✅" : "❌"} maki (hunt local): ${displayPath(maki.path)} ` +
87
+ `(${makiSourceLabel(maki.source)}${version ? `, v${version}` : ""}).`,
88
+ );
89
+ }
90
+
69
91
  const provider = engine.ai.projectConfig.aiProvider;
70
92
  const key = keyFor(provider);
71
93
  logger.bullet(
@@ -14,21 +14,31 @@ export class FindingsCommand implements Command {
14
14
  async run(args: string[], context: CliContext): Promise<number> {
15
15
  const engine = await context.engine();
16
16
  const sub = ArgParser.from(args).command();
17
+ // Raiz/git do engine: o comando nunca lê `process.cwd()` implicitamente.
18
+ const options = { root: engine.root, git: engine.git };
17
19
  const result =
18
20
  sub === "verify"
19
- ? await runVerifyCommand(engine.config, engine.storage, engine.repo, args)
21
+ ? await runVerifyCommand(
22
+ engine.config,
23
+ engine.storage,
24
+ engine.repo,
25
+ args,
26
+ options,
27
+ )
20
28
  : sub === "submit"
21
29
  ? await runSubmitCommand(
22
30
  engine.config,
23
31
  engine.storage,
24
32
  engine.repo,
25
33
  args,
34
+ options,
26
35
  )
27
36
  : runFindingsCommand(
28
37
  engine.config,
29
38
  engine.storage,
30
39
  engine.repo,
31
40
  args,
41
+ options,
32
42
  );
33
43
 
34
44
  if (result.exitCode !== 0) context.err(result.output);