@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
@@ -1,51 +1,106 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import {
3
- existsSync,
4
- mkdirSync,
5
- mkdtempSync,
6
- readFileSync,
7
- rmSync,
8
- writeFileSync,
9
- } from "node:fs";
2
+ import { mkdtempSync, rmSync } from "node:fs";
10
3
  import { tmpdir } from "node:os";
11
4
  import { join } from "node:path";
12
5
  import { runFindingsCommand, runVerifyCommand } from "../core/commands/findings";
6
+ import { buildStateFromFindings, fileReader } from "../core/findings-state";
7
+ import {
8
+ dedupeFindings,
9
+ parseWatchdogReport,
10
+ type WatchdogReport,
11
+ } from "../core/report-builder";
12
+ import { buildRepoIdentity } from "../core/storage/repo-identity";
13
+ import { SQLiteStorageProvider } from "../core/storage/sqlite/sqlite-storage";
14
+ import type { RepoIdentity } from "../core/storage/types";
13
15
  import { fakeHoundsmanConfig } from "./fixtures/fake-project";
14
16
 
15
- /** Run fake em disco: classification.json + findings/alpha.json. */
16
- function makeRun(findings: unknown[]): { dir: string; cleanup: () => void } {
17
+ /** Ambiente temporário: banco + review semeada com os findings informados. */
18
+ type Env = {
19
+ storage: SQLiteStorageProvider;
20
+ repo: RepoIdentity;
21
+ reviewId: string;
22
+ dir: string;
23
+ cleanup: () => void;
24
+ };
25
+
26
+ let activeEnv: Env | null = null;
27
+
28
+ function makeRun(reports: WatchdogReport[], branch = "main"): Env {
17
29
  const dir = mkdtempSync(join(tmpdir(), "houndsman-findings-"));
18
- const outDir = join(dir, "main");
19
- mkdirSync(join(outDir, "findings"), { recursive: true });
20
- writeFileSync(
21
- join(outDir, "classification.json"),
22
- JSON.stringify({ watchdogs: { alpha: ["src/alpha/f.ts"] } }),
23
- );
24
- writeFileSync(
25
- join(outDir, "findings", "alpha.json"),
26
- JSON.stringify({
27
- watchdogId: "alpha",
28
- reviewedFiles: ["src/alpha/f.ts"],
29
- skipped: [],
30
- findings,
31
- }),
30
+ const storage = new SQLiteStorageProvider(join(dir, "houndsman.db"));
31
+ const repo = buildRepoIdentity(null, dir);
32
+ const review = storage.getOrCreateReview({ repo, branch, slug: branch });
33
+ const state = buildStateFromFindings(dedupeFindings(reports), {
34
+ branch,
35
+ readFile: fileReader(process.cwd()),
36
+ });
37
+ storage.writeFindings(review.id, state);
38
+ const env: Env = {
39
+ storage,
40
+ repo,
41
+ reviewId: review.id,
42
+ dir,
43
+ cleanup: () => {
44
+ storage.close();
45
+ rmSync(dir, { recursive: true, force: true });
46
+ },
47
+ };
48
+ activeEnv = env;
49
+ return env;
50
+ }
51
+
52
+ function reportOf(
53
+ watchdogId: string,
54
+ findings: unknown[],
55
+ files: string[],
56
+ ): WatchdogReport {
57
+ const report = parseWatchdogReport(
58
+ { watchdogId, reviewedFiles: files, skipped: [], findings },
59
+ `${watchdogId}.json`,
32
60
  );
33
- return { dir, cleanup: () => rmSync(dir, { recursive: true, force: true }) };
61
+ if (!report) throw new Error("relatório de teste inválido");
62
+ return report;
34
63
  }
35
64
 
36
65
  const ONE_FINDING = [
37
- {
38
- rule: "Nunca usar any",
39
- severity: "blocking",
40
- file: "src/alpha/f.ts",
41
- line: 2,
42
- message: "any detectado",
43
- action: "tipar",
44
- },
66
+ reportOf(
67
+ "alpha",
68
+ [
69
+ {
70
+ rule: "Nunca usar any",
71
+ severity: "blocking",
72
+ file: "src/alpha/f.ts",
73
+ line: 2,
74
+ message: "any detectado",
75
+ action: "tipar",
76
+ },
77
+ ],
78
+ ["src/alpha/f.ts"],
79
+ ),
45
80
  ];
46
81
 
82
+ /** Remove `--dir` (flag extinta) dos args legados usados nos testes. */
83
+ function stripDir(args: string[]): string[] {
84
+ const out: string[] = [];
85
+ for (let i = 0; i < args.length; i++) {
86
+ if (args[i] === "--dir") {
87
+ i++;
88
+ continue;
89
+ }
90
+ out.push(args[i]);
91
+ }
92
+ return out;
93
+ }
94
+
95
+ /** Executa o comando contra o ambiente ativo. */
47
96
  function run(args: string[]) {
48
- return runFindingsCommand(fakeHoundsmanConfig, args);
97
+ if (!activeEnv) throw new Error("makeRun não foi chamado");
98
+ return runFindingsCommand(
99
+ fakeHoundsmanConfig,
100
+ activeEnv.storage,
101
+ activeEnv.repo,
102
+ stripDir(args),
103
+ );
49
104
  }
50
105
 
51
106
  describe("findings command", () => {
@@ -196,11 +251,18 @@ describe("findings command", () => {
196
251
  expect(missing.output).toContain("não encontrado");
197
252
 
198
253
  const empty = mkdtempSync(join(tmpdir(), "houndsman-empty-"));
254
+ const emptyStorage = new SQLiteStorageProvider(join(empty, "db.sqlite"));
199
255
  try {
200
- const absent = run(["list", "--dir", empty, "--branch", "main"]);
256
+ const absent = runFindingsCommand(
257
+ fakeHoundsmanConfig,
258
+ emptyStorage,
259
+ buildRepoIdentity(null, empty),
260
+ ["list", "--branch", "main"],
261
+ );
201
262
  expect(absent.exitCode).toBe(1);
202
263
  expect(absent.output).toContain("--finalize");
203
264
  } finally {
265
+ emptyStorage.close();
204
266
  rmSync(empty, { recursive: true, force: true });
205
267
  }
206
268
  } finally {
@@ -208,13 +270,14 @@ describe("findings command", () => {
208
270
  }
209
271
  });
210
272
 
211
- test("path devolve o caminho do state.json", () => {
273
+ test("path devolve a referência do storage", () => {
212
274
  const run0 = makeRun(ONE_FINDING);
213
275
  try {
214
276
  run(["list", "--dir", run0.dir, "--branch", "main"]);
215
277
  const result = run(["path", "--dir", run0.dir, "--branch", "main"]);
216
278
  expect(result.exitCode).toBe(0);
217
- expect(result.output.replace(/\\/g, "/")).toContain("main/state.json");
279
+ expect(result.output).toContain("sqlite:");
280
+ expect(result.output).toContain(run0.reviewId);
218
281
  } finally {
219
282
  run0.cleanup();
220
283
  }
@@ -232,20 +295,10 @@ describe("findings command", () => {
232
295
  });
233
296
 
234
297
  test("batch agrupa a fila e devolve o grupo atual + outros grupos", () => {
235
- const dir = mkdtempSync(join(tmpdir(), "houndsman-batch-"));
236
- const outDir = join(dir, "main");
237
- mkdirSync(join(outDir, "findings"), { recursive: true });
238
- writeFileSync(
239
- join(outDir, "classification.json"),
240
- JSON.stringify({ watchdogs: { alpha: ["src/a.ts"], beta: ["src/b.ts"] } }),
241
- );
242
- writeFileSync(
243
- join(outDir, "findings", "alpha.json"),
244
- JSON.stringify({
245
- watchdogId: "alpha",
246
- reviewedFiles: ["src/a.ts"],
247
- skipped: [],
248
- findings: [
298
+ const env = makeRun([
299
+ reportOf(
300
+ "alpha",
301
+ [
249
302
  {
250
303
  rule: "R1",
251
304
  severity: "blocking",
@@ -279,15 +332,11 @@ describe("findings command", () => {
279
332
  action: "a",
280
333
  },
281
334
  ],
282
- }),
283
- );
284
- writeFileSync(
285
- join(outDir, "findings", "beta.json"),
286
- JSON.stringify({
287
- watchdogId: "beta",
288
- reviewedFiles: ["src/b.ts"],
289
- skipped: [],
290
- findings: [
335
+ ["src/a.ts"],
336
+ ),
337
+ reportOf(
338
+ "beta",
339
+ [
291
340
  {
292
341
  rule: "R5",
293
342
  severity: "attention",
@@ -305,26 +354,16 @@ describe("findings command", () => {
305
354
  action: "a",
306
355
  },
307
356
  ],
308
- }),
309
- );
357
+ ["src/b.ts"],
358
+ ),
359
+ ]);
310
360
  try {
311
- const list = JSON.parse(
312
- run(["list", "--dir", dir, "--branch", "main", "--json"]).output,
313
- );
361
+ const list = JSON.parse(run(["list", "--branch", "main", "--json"]).output);
314
362
  for (const item of list) {
315
- run([
316
- "set",
317
- item.id,
318
- "--decision",
319
- "execute",
320
- "--dir",
321
- dir,
322
- "--branch",
323
- "main",
324
- ]);
363
+ run(["set", item.id, "--decision", "execute", "--branch", "main"]);
325
364
  }
326
365
 
327
- const result = run(["batch", "--dir", dir, "--branch", "main", "--json"]);
366
+ const result = run(["batch", "--branch", "main", "--json"]);
328
367
  expect(result.exitCode).toBe(0);
329
368
  const parsed = JSON.parse(result.output);
330
369
  expect(parsed.dimension).toBe("file");
@@ -336,7 +375,7 @@ describe("findings command", () => {
336
375
  expect.objectContaining({ key: "file:src/b.ts", count: 2 }),
337
376
  ]);
338
377
 
339
- const markdown = run(["batch", "--dir", dir, "--branch", "main"]);
378
+ const markdown = run(["batch", "--branch", "main"]);
340
379
  expect(markdown.output).toContain("Grupo atual");
341
380
  expect(markdown.output).toContain("Outros grupos");
342
381
 
@@ -344,25 +383,23 @@ describe("findings command", () => {
344
383
  "batch",
345
384
  "--group",
346
385
  "file:src/b.ts",
347
- "--dir",
348
- dir,
349
386
  "--branch",
350
387
  "main",
351
388
  "--json",
352
389
  ]);
353
390
  expect(JSON.parse(scoped.output).current.key).toBe("file:src/b.ts");
354
391
  } finally {
355
- rmSync(dir, { recursive: true, force: true });
392
+ env.cleanup();
356
393
  }
357
394
  });
358
395
 
359
- test("mutações fazem ping no run.json (status)", () => {
396
+ test("mutações fazem ping no status da review", () => {
360
397
  const run0 = makeRun(ONE_FINDING);
361
398
  try {
362
399
  const id = JSON.parse(
363
400
  run(["list", "--dir", run0.dir, "--branch", "main", "--json"]).output,
364
401
  )[0].id;
365
- const runJson = join(run0.dir, "main", "run.json");
402
+ const status = () => run0.storage.getReview(run0.reviewId)?.status;
366
403
 
367
404
  run([
368
405
  "set",
@@ -374,7 +411,7 @@ describe("findings command", () => {
374
411
  "--branch",
375
412
  "main",
376
413
  ]);
377
- expect(JSON.parse(readFileSync(runJson, "utf8")).status).toBe("review_done");
414
+ expect(status()).toBe("review_done");
378
415
 
379
416
  run([
380
417
  "set",
@@ -386,10 +423,10 @@ describe("findings command", () => {
386
423
  "--branch",
387
424
  "main",
388
425
  ]);
389
- expect(JSON.parse(readFileSync(runJson, "utf8")).status).toBe("fixing");
426
+ expect(status()).toBe("fixing");
390
427
 
391
428
  run(["done", id, "--dir", run0.dir, "--branch", "main"]);
392
- expect(JSON.parse(readFileSync(runJson, "utf8")).status).toBe("fix_done");
429
+ expect(status()).toBe("fix_done");
393
430
  } finally {
394
431
  run0.cleanup();
395
432
  }
@@ -400,55 +437,44 @@ describe("findings verify", () => {
400
437
  const FILE = "src/tests/fixtures/fake-project.ts";
401
438
 
402
439
  /** Run fake apontando para um arquivo real (relativo ao cwd → âncora resolvível). */
403
- function makeVerifyRun(): { dir: string; outDir: string; cleanup: () => void } {
404
- const dir = mkdtempSync(join(tmpdir(), "houndsman-verify-"));
405
- const outDir = join(dir, "main");
406
- mkdirSync(join(outDir, "findings"), { recursive: true });
407
- writeFileSync(
408
- join(outDir, "classification.json"),
409
- JSON.stringify({ watchdogs: { alpha: [FILE] } }),
440
+ function makeVerifyRun(): Env {
441
+ return makeRun(
442
+ [
443
+ reportOf(
444
+ "alpha",
445
+ [
446
+ {
447
+ rule: "regra",
448
+ severity: "attention",
449
+ file: FILE,
450
+ line: 1,
451
+ message: "msg",
452
+ action: "ação",
453
+ },
454
+ ],
455
+ [FILE],
456
+ ),
457
+ ],
458
+ "main",
410
459
  );
411
- writeFileSync(
412
- join(outDir, "findings", "alpha.json"),
413
- JSON.stringify({
414
- watchdogId: "alpha",
415
- reviewedFiles: [FILE],
416
- skipped: [],
417
- findings: [
418
- {
419
- rule: "regra",
420
- severity: "attention",
421
- file: FILE,
422
- line: 1,
423
- message: "msg",
424
- action: "ação",
425
- },
426
- ],
427
- }),
428
- );
429
- return {
430
- dir,
431
- outDir,
432
- cleanup: () => rmSync(dir, { recursive: true, force: true }),
433
- };
434
460
  }
435
461
 
436
462
  test("reporta persisting e grava o verify.md (tier local)", async () => {
437
463
  const run0 = makeVerifyRun();
438
464
  try {
439
- const result = await runVerifyCommand(fakeHoundsmanConfig, [
440
- "verify",
441
- "--dir",
442
- run0.dir,
443
- "--branch",
444
- "main",
445
- "--json",
446
- ]);
465
+ const result = await runVerifyCommand(
466
+ fakeHoundsmanConfig,
467
+ run0.storage,
468
+ run0.repo,
469
+ ["verify", "--branch", "main", "--json"],
470
+ );
447
471
  expect(result.exitCode).toBe(0);
448
472
  const parsed = JSON.parse(result.output);
449
473
  expect(parsed.summary.persisting).toBe(1);
450
474
  expect(parsed.entries[0].basis).toBe("anchor");
451
- expect(existsSync(join(run0.outDir, "verify.md"))).toBe(true);
475
+ expect(
476
+ run0.storage.readArtifact(run0.reviewId, "verify.md")?.content,
477
+ ).toContain("# Verificação");
452
478
  } finally {
453
479
  run0.cleanup();
454
480
  }
@@ -457,17 +483,13 @@ describe("findings verify", () => {
457
483
  test("sem --apply não altera o estado", async () => {
458
484
  const run0 = makeVerifyRun();
459
485
  try {
460
- await runVerifyCommand(fakeHoundsmanConfig, [
486
+ await runVerifyCommand(fakeHoundsmanConfig, run0.storage, run0.repo, [
461
487
  "verify",
462
- "--dir",
463
- run0.dir,
464
488
  "--branch",
465
489
  "main",
466
490
  ]);
467
- const state = JSON.parse(
468
- readFileSync(join(run0.outDir, "state.json"), "utf8"),
469
- ) as { findings: Record<string, { execState: string }> };
470
- const only = Object.values(state.findings)[0];
491
+ const state = run0.storage.readFindings(run0.reviewId);
492
+ const only = Object.values(state?.findings ?? {})[0];
471
493
  expect(only.execState).toBe("pending");
472
494
  } finally {
473
495
  run0.cleanup();
@@ -112,7 +112,7 @@ describe("buildStateFromFindings", () => {
112
112
  }
113
113
  });
114
114
 
115
- test("finding que sumiu fica ausente e vira done se era 'execute'", () => {
115
+ test("finding que sumiu vira done (corrigido entre revisões)", () => {
116
116
  const project = makeProject({ "src/f.ts": DUPLICATED });
117
117
  try {
118
118
  const reader = fileReader(project.cwd);
@@ -121,7 +121,7 @@ describe("buildStateFromFindings", () => {
121
121
  { branch: "main", readFile: reader },
122
122
  );
123
123
  const id = Object.keys(first.findings)[0];
124
- applyFindingUpdate(first.findings[id], { decision: "execute" });
124
+ expect(first.findings[id].decision).toBe("pending");
125
125
 
126
126
  const second = buildStateFromFindings([], {
127
127
  branch: "main",
@@ -135,6 +135,54 @@ describe("buildStateFromFindings", () => {
135
135
  }
136
136
  });
137
137
 
138
+ test("finding ausente e marcado como 'ignore' preserva o estado", () => {
139
+ const project = makeProject({ "src/f.ts": DUPLICATED });
140
+ try {
141
+ const reader = fileReader(project.cwd);
142
+ const first = buildStateFromFindings(
143
+ [merged({ file: "src/f.ts", rule: "r", line: 2 })],
144
+ { branch: "main", readFile: reader },
145
+ );
146
+ const id = Object.keys(first.findings)[0];
147
+ applyFindingUpdate(first.findings[id], { decision: "ignore" });
148
+
149
+ const second = buildStateFromFindings([], {
150
+ branch: "main",
151
+ readFile: reader,
152
+ previous: first,
153
+ });
154
+ expect(second.findings[id].present).toBe(false);
155
+ expect(second.findings[id].execState).toBe("pending");
156
+ expect(second.findings[id].decision).toBe("ignore");
157
+ } finally {
158
+ project.cleanup();
159
+ }
160
+ });
161
+
162
+ test("arquivo fora da revisão (orçamento/skipped) não vira done", () => {
163
+ const project = makeProject({ "src/f.ts": DUPLICATED });
164
+ try {
165
+ const reader = fileReader(project.cwd);
166
+ const first = buildStateFromFindings(
167
+ [merged({ file: "src/f.ts", rule: "r", line: 2 })],
168
+ { branch: "main", readFile: reader },
169
+ );
170
+ const id = Object.keys(first.findings)[0];
171
+
172
+ const second = buildStateFromFindings([], {
173
+ branch: "main",
174
+ readFile: reader,
175
+ previous: first,
176
+ // Nenhum arquivo foi revisado neste ciclo: ausência não prova resolução.
177
+ reviewedPaths: new Set(["src/outro.ts"]),
178
+ });
179
+ expect(second.findings[id].present).toBe(false);
180
+ expect(second.findings[id].execState).toBe("pending");
181
+ } finally {
182
+ project.cleanup();
183
+ }
184
+ });
185
+
138
186
  test("ocorrências idênticas (mesmo contexto) viram um grupo com count", () => {
139
187
  const content = [
140
188
  "const a = 1;",
@@ -237,14 +285,14 @@ describe("FindingsStateStore", () => {
237
285
  const project = makeProject({ "src/f.ts": DUPLICATED });
238
286
  const outDir = join(project.cwd, "artifacts");
239
287
  try {
240
- const store = FindingsStateStore.at(outDir, "feature/x");
288
+ const store = new FindingsStateStore(join(outDir, "state.json"), "feature/x");
241
289
  const state = buildStateFromFindings(
242
290
  [merged({ file: "src/f.ts", rule: "r", line: 2 })],
243
291
  { branch: "feature/x", readFile: fileReader(project.cwd) },
244
292
  );
245
293
  store.write(state);
246
294
 
247
- expect(store.filePath).toBe(join(outDir, "feature-x", "state.json"));
295
+ expect(store.filePath).toBe(join(outDir, "state.json"));
248
296
  expect(existsSync(store.filePath)).toBe(true);
249
297
  const read = store.read();
250
298
  expect(read?.slug).toBe("feature-x");
@@ -257,7 +305,10 @@ describe("FindingsStateStore", () => {
257
305
  test("read devolve null quando o arquivo não existe", () => {
258
306
  const project = makeProject({});
259
307
  try {
260
- const store = FindingsStateStore.at(join(project.cwd, "artifacts"), "main");
308
+ const store = new FindingsStateStore(
309
+ join(project.cwd, "artifacts", "state.json"),
310
+ "main",
311
+ );
261
312
  expect(store.read()).toBeNull();
262
313
  expect(store.update(() => {})).toBeTruthy();
263
314
  } finally {
@@ -56,7 +56,6 @@ export type FakeWatchdogId = keyof typeof FAKE_WATCHDOG_DEFINITIONS;
56
56
  export const fakeHoundsmanConfig: HoundsmanConfig<FakeWatchdogId> =
57
57
  defineHoundsmanConfig<FakeWatchdogId>({
58
58
  projectName: "Fake Project",
59
- artifactDir: "tmp/fake-review",
60
59
  watchdogs: FAKE_WATCHDOG_DEFINITIONS,
61
60
  exclusions: [/^dist\//, /\.lock$/],
62
61
  docMappings: [[/^src\//, ["README.md"]]],