@payconductor/houndsman 0.3.0 → 0.5.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 (91) hide show
  1. package/README.md +25 -39
  2. package/package.json +1 -1
  3. package/src/cli/commands/context-command.ts +11 -9
  4. package/src/cli/commands/doctor-command.ts +5 -0
  5. package/src/cli/commands/findings-command.ts +13 -3
  6. package/src/cli/commands/review-command.ts +1 -2
  7. package/src/cli/commands/status-command.ts +46 -0
  8. package/src/cli/help.ts +8 -3
  9. package/src/cli/index.ts +5 -2
  10. package/src/core/commands/context.ts +29 -13
  11. package/src/core/commands/findings.ts +216 -125
  12. package/src/core/commands/review.ts +7 -8
  13. package/src/core/commands/scaffold.ts +15 -16
  14. package/src/core/commands/status.ts +63 -0
  15. package/src/core/config.ts +16 -7
  16. package/src/core/engine/houndsman-engine.ts +15 -1
  17. package/src/core/engine/review-scope.ts +26 -8
  18. package/src/core/engine/review-session.ts +37 -9
  19. package/src/core/findings-state.ts +53 -75
  20. package/src/core/github-review.ts +620 -57
  21. package/src/core/jev/jev-candidates.ts +49 -35
  22. package/src/core/orchestrator.ts +271 -134
  23. package/src/core/paths.ts +26 -17
  24. package/src/core/pr-context.ts +19 -5
  25. package/src/core/project-config.ts +4 -5
  26. package/src/core/renderers/manifest-writer.ts +29 -18
  27. package/src/core/renderers/resume-renderer.ts +151 -0
  28. package/src/core/report-builder.ts +7 -4
  29. package/src/core/run-layout.ts +4 -104
  30. package/src/core/run-status.ts +59 -50
  31. package/src/core/services/git-repository.ts +16 -0
  32. package/src/core/services/project-files.ts +13 -1
  33. package/src/core/storage/factory.ts +24 -0
  34. package/src/core/storage/file/file-storage.ts +271 -0
  35. package/src/core/storage/paths.ts +64 -0
  36. package/src/core/storage/repo-identity.ts +70 -0
  37. package/src/core/storage/sqlite/mappers.ts +162 -0
  38. package/src/core/storage/sqlite/schema.ts +116 -0
  39. package/src/core/storage/sqlite/sqlite-storage.ts +431 -0
  40. package/src/core/storage/types.ts +152 -0
  41. package/src/core/storage/work-area.ts +191 -0
  42. package/src/index.ts +27 -6
  43. package/src/server/artifacts.ts +8 -61
  44. package/src/server/client/App.tsx +26 -9
  45. package/src/server/client/ArtifactsPanel.tsx +144 -23
  46. package/src/server/client/AutoRefresh.tsx +1 -1
  47. package/src/server/client/Filters.tsx +7 -2
  48. package/src/server/client/FindingCard.tsx +50 -15
  49. package/src/server/client/FindingsList.tsx +2 -2
  50. package/src/server/client/MarkdownText.tsx +181 -0
  51. package/src/server/client/ResumePanel.tsx +368 -0
  52. package/src/server/client/RunList.tsx +3 -3
  53. package/src/server/client/SubmitModal.tsx +58 -10
  54. package/src/server/client/SummaryBar.tsx +1 -1
  55. package/src/server/client/icons.tsx +17 -0
  56. package/src/server/client/utils/code-highlight.ts +422 -0
  57. package/src/server/client/utils/constants.ts +226 -0
  58. package/src/server/client/utils/json-highlight.ts +55 -0
  59. package/src/server/client/utils/markdown.ts +319 -0
  60. package/src/server/client/{types.ts → utils/types.ts} +81 -0
  61. package/src/server/index.ts +154 -16
  62. package/src/server/run-repository.ts +153 -136
  63. package/src/skill/OUTPUT-FORMAT.md +6 -4
  64. package/src/skill/README.md +22 -14
  65. package/src/skill/SETUP.md +4 -4
  66. package/src/skill/example-review.md +22 -14
  67. package/src/skill/fix.md +5 -5
  68. package/src/skill/hunt.md +55 -32
  69. package/src/skill/train.md +1 -1
  70. package/src/skill/verify.md +7 -6
  71. package/src/tests/__snapshots__/baseline.test.ts.snap +74 -61
  72. package/src/tests/baseline.test.ts +79 -34
  73. package/src/tests/cli.test.ts +40 -19
  74. package/src/tests/code-highlight.test.ts +123 -0
  75. package/src/tests/domain.test.ts +1 -1
  76. package/src/tests/findings-commands.test.ts +153 -131
  77. package/src/tests/findings-state.test.ts +56 -5
  78. package/src/tests/fixtures/fake-project.ts +0 -1
  79. package/src/tests/github-review.test.ts +409 -60
  80. package/src/tests/infrastructure.test.ts +33 -38
  81. package/src/tests/markdown.test.ts +149 -0
  82. package/src/tests/review-resume.test.ts +101 -0
  83. package/src/tests/review-scope.test.ts +302 -0
  84. package/src/tests/review-scripts.test.ts +81 -5
  85. package/src/tests/run-status-view.test.ts +94 -0
  86. package/src/tests/run-status.test.ts +86 -23
  87. package/src/tests/server.test.ts +184 -96
  88. package/src/tests/storage.test.ts +449 -0
  89. package/src/server/client/constants.ts +0 -91
  90. /package/src/server/client/{api.ts → utils/api.ts} +0 -0
  91. /package/src/server/client/{format.ts → utils/format.ts} +0 -0
package/README.md CHANGED
@@ -26,8 +26,8 @@ diferente das PRs. O motor só coordena a caçada — as regras são problema se
26
26
 
27
27
  - **Bun** — runtime obrigatório.
28
28
  - **git** — o diff usa `git diff <base>...<head>`.
29
- - Opcionais: **`gh`** (contexto da PR) e **`TYPESAFE_API_KEY`** (pré-análise Jev). A revisão
30
- funciona normalmente sem eles; o `houndsman doctor` valida a credencial Jev quando presente.
29
+ - **gh** (Opcional) — Obtém contexto da PR e dispara comentários para o GitHub
30
+ - **`TYPESAFE_API_KEY`** (Opcional) — Env a ser informada na raiz, usada em pré-analise com JEV
31
31
 
32
32
  ### 2. Instale o CLI (global)
33
33
 
@@ -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 grava o relatório em `.houndsman/.runs/<branch-slug>/review.md`.
78
- Não precisa rodar nenhum script à mão.
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,62 +87,49 @@ 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 + state.json
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 → run.json (fix_done)
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
 
95
96
  O skill tem **4 capabilities**, todas em `/houndsman`:
96
97
 
97
- - **`hunt`** — revisa a PR (default: `/houndsman` = `/houndsman hunt`).
98
+ - **`hunt`** — revisa a PR.
98
99
  - **`train`** — mantém/atualiza as regras dos watchdogs.
99
- - **`fix`** — corrige os findings marcados (`/houndsman fix`).
100
- - **`verify`** — confere se as violações foram resolvidas (`/houndsman verify`), sem re-revisar.
100
+ - **`fix`** — corrige os findings marcados.
101
+ - **`verify`** — confere se as violações foram resolvidas, sem re-revisar.
101
102
 
102
- - Revisão normal: `/houndsman` ou `/houndsman hunt` (a branch é detectada; base default `main`).
103
103
  - Revisar outra branch: `/houndsman hunt feature/minha-branch`.
104
- - Validar e triar na UI: `houndsman server --open` — marque os achados a executar (`execute`).
105
- - Manual (sem skill): `houndsman context` → `houndsman --base main` → *(subagentes)* →
106
- `houndsman --finalize`.
107
- - CI: `houndsman --finalize --fail-on-blocking` (exit 1 se houver bloqueantes).
104
+ - Validar na UI: `houndsman server --open` — marque os findings a executar (`execute`).
108
105
 
109
106
  ---
110
107
 
111
- ## Triando os findings (opcional)
108
+ ## Validando os findings (opcional)
112
109
 
113
- Depois do `--finalize`, cada finding tem um `id` curto e um `fingerprint` de **conteúdo**
114
- no `.houndsman/.runs/<branch-slug>/state.json`. Você decide o que fazer com cada um — pela
115
- **UI local** ou pelo **agente** (auto-fix) — sem abrir o JSON à mão:
110
+ Você decide o que fazer com cada finding pela **UI local**, informando se quer `Executar` ou `Ignorar`
116
111
 
117
112
  ```bash
118
113
  bun run houndsman server --open # UI: triar, comentar, marcar feito, ler artefatos
119
114
  ```
120
115
 
121
- - **decision:** `pending` · `execute` · `ignore` (+ comentário).
122
- - **execState:** `pending` · `in_progress` · `done` · `stale`.
123
-
124
- **Auto-fix pelo agente** — as linhas mudam depois do fix, e o motor reancora por conteúdo:
125
-
126
- ```bash
127
- bun run houndsman findings batch --json # grupo atual (agrupamento automático) + outros
128
- # ... o agente corrige apenas esse grupo ...
129
- bun run houndsman findings sync # reancora por conteúdo
130
- bun run houndsman findings done <id>
131
- bun run houndsman findings verify # confere o que resolveu (barato, sem re-revisar)
132
- ```
133
-
134
- > A partir da 2ª revisão o `hunt` é **incremental**: revisa só os arquivos que mudaram desde
135
- > o último `--finalize` e **carrega** os findings dos arquivos inalterados. `--full` força
136
- > o review completo.
137
-
138
- > A identidade do finding é o **conteúdo** (fingerprint), não a linha: itens deslocados
139
- > pelo fix não se perdem. O estado é local e mora em `.runs/<slug>/state.json` (gitignored).
116
+ > A partir da 2ª revisão o `hunt` é **incremental**: revisa apenas os arquivos que mudaram desde
117
+ > a última revisão. `--full` força o review completo.
140
118
 
141
119
  ---
142
120
 
143
121
  ## Mantendo a configuração
144
122
 
123
+ - **Persistência** → por default, SQLite global em `<HOUNDSMAN_HOME>/houndsman.db`
124
+ (`HOUNDSMAN_HOME` sobrescreve o diretório de dados). Caso queira storage base em arquivos, na config, troque provider para `file`:
125
+
126
+ ```ts
127
+ export const houndsmanConfig = {
128
+ // ...
129
+ storage: { provider: "file", path: "./houndsman/runs" },
130
+ };
131
+ ```
132
+
145
133
  - **Alterar regras** → use a capability **`train`** (`/houndsman train`). Ela prova no código
146
134
  que o padrão existe (evidência) antes de editar e roda as validações.
147
135
  - **Atualizou a lib?** Sincronize os arquivos auto-gerados sem tocar nas suas regras:
@@ -153,8 +141,6 @@ bun run houndsman findings verify # confere o que resolveu (barato, se
153
141
 
154
142
  O `update` reescreve **só** `.houndsman/core/**` e `.houndsman/.gitignore`. Seus arquivos
155
143
  (`watchdogs/`, `exclusions.ts`, `doc-mappings.ts`, `houndsman.config.ts`) nunca são tocados.
156
- - **Recomeçar a camada do zero** → `houndsman setup --force` (sobrescreve só os arquivos do
157
- usuário; sem `--force` o `setup` é idempotente).
158
144
 
159
145
  ---
160
146
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payconductor/houndsman",
3
- "version": "0.3.0",
3
+ "version": "0.5.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 { relFromRoot } from "../../core/paths";
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(engine.config, {
15
- branch: parser.value("--branch"),
16
- dir: parser.value("--dir"),
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: ${relFromRoot(paths.context)}`);
28
- context.out(`[houndsman] brief: ${relFromRoot(paths.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 ${relFromRoot(paths.summary)}`,
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: ${relFromRoot(paths.context)}`);
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(engine.config, args)
22
- : runFindingsCommand(engine.config, args);
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, resolveArtifactDir(engine.config));
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);
@@ -0,0 +1,46 @@
1
+ /** Comando `status` — mostra o status da run ou renova o heartbeat (`status ping`). */
2
+
3
+ import { pingStatus, readStatus } from "../../core/commands/status";
4
+ import { ArgParser } from "../arg-parser";
5
+ import type { CliContext, Command } from "./command";
6
+
7
+ export class StatusCommand implements Command {
8
+ readonly name = "status";
9
+
10
+ async run(args: string[], context: CliContext): Promise<number> {
11
+ const parser = ArgParser.from(args);
12
+ const action = parser.command();
13
+ const branch = parser.value("--branch");
14
+
15
+ if (action && action !== "ping" && action !== "show") {
16
+ context.err(
17
+ `Ação desconhecida em 'status': '${action}'. Use 'status' ou 'status ping'.`,
18
+ );
19
+ return 1;
20
+ }
21
+
22
+ const engine = await context.engine();
23
+ const result =
24
+ action === "ping"
25
+ ? pingStatus(engine.storage, engine.repo, branch)
26
+ : readStatus(engine.storage, engine.repo, branch);
27
+
28
+ if (action === "ping") {
29
+ context.out(
30
+ result.heartbeatAt
31
+ ? `Heartbeat atualizado — ${result.branch} · ${result.status ?? "sem status"} · ${result.heartbeatAt}`
32
+ : `Sem execução ativa — ${result.branch} · ${result.status ?? "sem status"}.`,
33
+ );
34
+ } else {
35
+ context.out(
36
+ [
37
+ `Branch: ${result.branch}`,
38
+ `Status: ${result.status ?? "—"}`,
39
+ `Heartbeat: ${result.heartbeatAt ?? "—"}`,
40
+ `Atualizado: ${result.updatedAt}`,
41
+ ].join("\n"),
42
+ );
43
+ }
44
+ return 0;
45
+ }
46
+ }
package/src/cli/help.ts CHANGED
@@ -6,6 +6,7 @@ Comandos:
6
6
  --finalize Consolida os findings em review.md
7
7
  context Coleta o contexto da PR via gh (Passo 0)
8
8
  findings <sub> Triagem/correção dos findings (list · show · set · done · reset · sync · summary · batch · verify · submit · path)
9
+ status [ping] Mostra o status da run (ping renova o heartbeat da execução)
9
10
  server Sobe a UI local de triagem (--port, --host, --open)
10
11
  setup Cria a configuração inicial do projeto (config + skills)
11
12
  update Sincroniza os arquivos auto-gerados (.houndsman/core)
@@ -18,8 +19,7 @@ Flags:
18
19
  --base <branch> Base do diff (default: main)
19
20
  --head <ref> Head do diff (default: HEAD)
20
21
  --branch <nome> Branch alvo (default: detecta do git)
21
- --dir <dir> Diretório dos artefatos
22
- --clean Descarta findings anteriores
22
+ --clean Descarta findings brutos do staging anterior
23
23
  --full Força review completo (ignora o modo incremental)
24
24
  --no-jev Desativa a pré-análise Jev
25
25
  --fail-on-blocking Exit 1 se houver bloqueantes (com --finalize)
@@ -40,4 +40,9 @@ Flags de findings:
40
40
  --apply (verify) Marca os resolvidos como done
41
41
  --fail-on-persisting (verify) Exit 1 se algum blocking persistir
42
42
  --include-sent (submit) Reenvia findings já enviados à PR
43
- --dry-run (submit) Só a prévia do envio, sem postar`;
43
+ --dry-run (submit) Só a prévia do envio, sem postar
44
+
45
+ Persistência:
46
+ Runs, findings e artefatos ficam no storage global (SQLite em
47
+ <HOUNDSMAN_HOME>/houndsman.db). Use storage.provider: "file" na config para
48
+ voltar ao backend de arquivos. Nada é gravado em .houndsman/.runs.`;
package/src/cli/index.ts CHANGED
@@ -15,6 +15,7 @@
15
15
  * --finalize Passo 3 — consolidar os findings em review.md
16
16
  * context Passo 0 — coletar o contexto da PR via `gh`
17
17
  * findings <sub> Triagem/correção dos findings (list/set/done/sync/batch/verify/submit/...)
18
+ * status [ping] Mostra o status da run; `ping` renova o heartbeat da execução
18
19
  * server Sobe a UI local de triagem (Bun.serve)
19
20
  * setup Cria a configuração inicial do projeto (config + skills)
20
21
  * update Sincroniza os arquivos auto-gerados (.houndsman/core)
@@ -22,8 +23,8 @@
22
23
  * test Roda a suíte de testes da lib
23
24
  * watchdog <id> Contexto de um watchdog (`--rules`, `--json`)
24
25
  *
25
- * Flags: `--base <b>` · `--head <ref>` · `--branch <b>` · `--dir <d>` ·
26
- * `--config <path>` · `--clean` · `--full` · `--no-jev` · `--fail-on-blocking`.
26
+ * Flags: `--base <b>` · `--head <ref>` · `--branch <b>` · `--config <path>` ·
27
+ * `--clean` · `--full` · `--no-jev` · `--fail-on-blocking`.
27
28
  */
28
29
 
29
30
  import { loadConfig } from "../core/config-loader";
@@ -38,6 +39,7 @@ import { CommandRegistry } from "./commands/registry";
38
39
  import { ReviewCommand } from "./commands/review-command";
39
40
  import { ServerCommand } from "./commands/server-command";
40
41
  import { SetupCommand, TestCommand, UpdateCommand } from "./commands/setup-command";
42
+ import { StatusCommand } from "./commands/status-command";
41
43
  import { WatchdogCommand } from "./commands/watchdog-command";
42
44
  import { HELP } from "./help";
43
45
 
@@ -53,6 +55,7 @@ function buildRegistry(): CommandRegistry {
53
55
  .register(new HelpCommand())
54
56
  .register(new ContextCommand())
55
57
  .register(new FindingsCommand())
58
+ .register(new StatusCommand())
56
59
  .register(new WatchdogCommand())
57
60
  .register(new ServerCommand())
58
61
  .register(new SetupCommand())
@@ -1,19 +1,20 @@
1
1
  /**
2
2
  * Subcomando `context` — coleta o contexto da PR via `gh` (Passo 0).
3
3
  *
4
- * Biblioteca: grava os artefatos e devolve os dados; o CLI faz o log/exit.
4
+ * Biblioteca: grava no staging da review + artefato no storage; o CLI faz o
5
+ * log/exit.
5
6
  */
6
7
 
7
- import { mkdirSync, writeFileSync } from "node:fs";
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
- config: HoundsmanConfig,
26
- opts: { branch?: string; dir?: string } = {},
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 dir = opts.dir ?? resolveArtifactDir(config);
30
- const paths = contextPaths(dir, branch);
31
-
32
- mkdirSync(paths.dir, { recursive: true });
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
- writeFileSync(paths.context, `${JSON.stringify(context, null, 2)}\n`);
36
- writeFileSync(paths.brief, `${renderContextBrief(context, branch, paths)}\n`);
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
  }