@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/src/core/paths.ts CHANGED
@@ -2,11 +2,13 @@
2
2
  * Caminhos do houndsman (biblioteca): raiz relativa para logs + layout fixo da
3
3
  * camada de projeto (`.houndsman/`).
4
4
  *
5
- * A camada gerada tem local fixo, então nada aqui é configurável pelo projeto:
6
- * o CLI deriva os artefatos e o schema de output destas constantes.
5
+ * A camada gerada tem local fixo, então nada aqui é configurável pelo projeto.
6
+ * Dados de execução (reviews/findings/artefatos) vivem no storage global; aqui
7
+ * ficam só os paths da camada e os nomes dos artefatos de staging.
7
8
  */
8
9
 
9
- import { relative } from "node:path";
10
+ import { homedir } from "node:os";
11
+ import { relative, sep } from "node:path";
10
12
  import { OUTPUT_FORMAT_FILE } from "./assets";
11
13
 
12
14
  /** Caminho relativo ao cwd, sempre com `/` (portável/legível em logs). */
@@ -14,6 +16,16 @@ export function relFromRoot(absPath: string): string {
14
16
  return relative(process.cwd(), absPath).replace(/\\/g, "/");
15
17
  }
16
18
 
19
+ /** Caminho legível para mensagens: `~/...` quando sob o home, senão absoluto. */
20
+ export function displayPath(absPath: string): string {
21
+ const home = homedir();
22
+ if (absPath === home) return "~";
23
+ if (absPath.startsWith(`${home}${sep}`)) {
24
+ return `~${absPath.slice(home.length).replace(/\\/g, "/")}`;
25
+ }
26
+ return absPath.replace(/\\/g, "/");
27
+ }
28
+
17
29
  /** Diretório da camada de projeto (regras + artefatos). */
18
30
  export const PROJECT_DIR = ".houndsman";
19
31
 
@@ -23,26 +35,23 @@ export const CORE_DIR = `${PROJECT_DIR}/core`;
23
35
  /** Skill canônico (markdown) dentro da camada gerada. */
24
36
  export const CORE_SKILL_DIR = `${CORE_DIR}/skill`;
25
37
 
26
- /** Artefatos da revisão (briefs, findings, review.md) — default de `artifactDir`. */
27
- export const ARTIFACT_DIR = `${PROJECT_DIR}/.runs`;
28
-
29
38
  /** Schema de output dos watchdogs, citado nos briefs. */
30
39
  export const OUTPUT_FORMAT_PATH = `${CORE_SKILL_DIR}/${OUTPUT_FORMAT_FILE}`;
31
40
 
32
- /** Estado de triagem dos findings, dentro da pasta da run (`<slug>/state.json`). */
33
- export const STATE_FILE = "state.json";
34
-
35
- /** Status da run (revisão/correção), dentro da pasta da run (`<slug>/run.json`). */
36
- export const RUN_STATUS_FILE = "run.json";
37
-
38
- /** Relatório da verificação de findings, dentro da pasta da run (`<slug>/verify.md`). */
39
- export const VERIFY_FILE = "verify.md";
40
-
41
- /** Contexto bruto da PR (`gh pr view`), dentro da pasta da run. */
41
+ /** Contexto bruto da PR (`gh pr view`), no staging da review. */
42
42
  export const CONTEXT_FILE = "context.json";
43
43
 
44
- /** Resumo do contexto gravado pelo subagente, dentro da pasta da run. */
44
+ /** Resumo do contexto gravado pelo subagente, no staging da review. */
45
45
  export const CONTEXT_SUMMARY_FILE = "context-summary.json";
46
46
 
47
47
  /** Brief do subagente que resume o contexto da PR. */
48
48
  export const CONTEXT_BRIEF_FILE = "context-brief.md";
49
+
50
+ /** Relatório da verificação de findings (artefato `verify.md`). */
51
+ export const VERIFY_FILE = "verify.md";
52
+
53
+ /** Relatório consolidado da review (artefato `review.md`). */
54
+ export const REVIEW_FILE = "review.md";
55
+
56
+ /** Resumo final da review (artefato `review-resume.md`) — leitura humana apenas. */
57
+ export const REVIEW_RESUME_FILE = "review-resume.md";
@@ -5,7 +5,7 @@
5
5
  * OPCIONAL: sem binário, sem login ou sem PR para a branch, grava
6
6
  * `context.json` com `{ available: false, reason }` e sai com código 0.
7
7
  *
8
- * Artefatos (em --dir, default .houndsman/.runs/<branch-slug>/):
8
+ * Artefatos no staging da review (`<HOUNDSMAN_HOME>/work/<reviewId>/`):
9
9
  * context.json payload bruto do `gh pr view` (ou marcador de indisponível)
10
10
  * context-brief.md instruções para o subagente resumir o contexto
11
11
  *
@@ -22,13 +22,19 @@ import {
22
22
  } from "./paths";
23
23
  import type { ContextSummary } from "./report-builder";
24
24
  import { parseContextSummary } from "./report-builder";
25
- import { type ContextPaths, RunLayout } from "./run-layout";
26
25
  import { systemClock } from "./services/clock";
27
26
  import { defaultGhRunner, GhClient, type GhRunner } from "./services/gh-client";
28
27
  import { GitRepository } from "./services/git-repository";
29
28
 
30
29
  export { CONTEXT_BRIEF_FILE, CONTEXT_FILE, CONTEXT_SUMMARY_FILE };
31
- export type { ContextPaths };
30
+
31
+ /** Caminhos dos artefatos de contexto da PR (staging da review). */
32
+ export type ContextPaths = {
33
+ dir: string;
34
+ context: string;
35
+ summary: string;
36
+ brief: string;
37
+ };
32
38
 
33
39
  const GH_JSON_FIELDS =
34
40
  "number,title,body,url,baseRefName,headRefName,author,commits,files";
@@ -60,8 +66,16 @@ export type PrContextRaw = {
60
66
  };
61
67
 
62
68
  /** Caminhos dos artefatos de contexto para uma branch. */
63
- export function contextPaths(dir: string, branch: string): ContextPaths {
64
- return new RunLayout(dir, branch).contextPaths;
69
+ export function contextFileNames(): {
70
+ context: string;
71
+ summary: string;
72
+ brief: string;
73
+ } {
74
+ return {
75
+ context: CONTEXT_FILE,
76
+ summary: CONTEXT_SUMMARY_FILE,
77
+ brief: CONTEXT_BRIEF_FILE,
78
+ };
65
79
  }
66
80
 
67
81
  /** Detecta a branch atual; lança se não houver (o CLI decide o exit). */
@@ -2,17 +2,16 @@
2
2
  * Configuração do projeto (domínio) — resolve defaults e expõe o registry.
3
3
  *
4
4
  * Evita o "data clump" de passar `HoundsmanConfig` para dezenas de funções:
5
- * quem precisa de `artifactDir`/orçamento/concorrência/watchdogs pede aqui.
5
+ * quem precisa de orçamento/concorrência/watchdogs pede aqui.
6
6
  * O contrato do consumidor (`houndsman.config.ts`) não muda.
7
7
  */
8
8
 
9
- import type { HoundsmanConfig } from "./config";
9
+ import type { HoundsmanConfig, HoundsmanStorage } from "./config";
10
10
  import {
11
11
  DEFAULT_ALWAYS_INCLUDE_DOCS,
12
12
  DEFAULT_JEV_CONCURRENCY,
13
13
  DEFAULT_MAX_FILES_PER_BRIEF,
14
14
  } from "./defaults";
15
- import { ARTIFACT_DIR } from "./paths";
16
15
  import { WatchdogRegistry } from "./watchdog-registry";
17
16
 
18
17
  /** Configuração resolvida do projeto. */
@@ -29,8 +28,8 @@ export class ProjectConfig<TId extends string = string> {
29
28
  return this.raw.projectName;
30
29
  }
31
30
 
32
- get artifactDir(): string {
33
- return this.raw.artifactDir ?? ARTIFACT_DIR;
31
+ get storage(): HoundsmanStorage {
32
+ return this.raw.storage ?? {};
34
33
  }
35
34
 
36
35
  get maxFilesPerBrief(): number {
@@ -1,20 +1,20 @@
1
1
  /**
2
- * Manifesto da run (biblioteca) — monta e grava o `manifest.json`.
2
+ * Manifesto da run (biblioteca) — monta e grava o artefato `manifest.json`.
3
3
  *
4
4
  * Concentra o resumo do que foi ativado (watchdogs, orçamento, churn, contexto,
5
- * Jev e incremental), hoje espalhado em helpers do orquestrador.
5
+ * Jev e incremental). O manifesto vive no storage; os caminhos de briefs e
6
+ * findings apontam para o staging da review.
6
7
  */
7
8
 
8
9
  import { Classification } from "../domain/classification";
9
10
  import type { ClassificationResult } from "../file-classifier";
10
11
  import type { JevCandidatesFile } from "../jev";
11
- import { OUTPUT_FORMAT_PATH, relFromRoot } from "../paths";
12
- import { readContextSummary } from "../pr-context";
12
+ import { OUTPUT_FORMAT_PATH } from "../paths";
13
13
  import type { ProjectConfig } from "../project-config";
14
14
  import type { MergedFinding } from "../report-builder";
15
- import type { RunLayout } from "../run-layout";
16
15
  import { systemClock } from "../services/clock";
17
- import { ProjectFiles } from "../services/project-files";
16
+ import type { ReviewRecord, StorageProvider } from "../storage/types";
17
+ import type { ReviewWorkArea } from "../storage/work-area";
18
18
 
19
19
  /** Opções do manifesto (subset do `ReviewOptions`). */
20
20
  export type ManifestOptions = {
@@ -32,12 +32,22 @@ export type ManifestClassification = ClassificationResult & {
32
32
 
33
33
  /** Escreve o `manifest.json` de uma run. */
34
34
  export class ManifestWriter {
35
- private readonly layout: RunLayout;
35
+ private readonly storage: StorageProvider;
36
+ private readonly review: ReviewRecord;
37
+ private readonly work: ReviewWorkArea;
36
38
  private readonly project: ProjectConfig;
37
39
  private readonly options: ManifestOptions;
38
40
 
39
- constructor(layout: RunLayout, project: ProjectConfig, options: ManifestOptions) {
40
- this.layout = layout;
41
+ constructor(
42
+ storage: StorageProvider,
43
+ review: ReviewRecord,
44
+ work: ReviewWorkArea,
45
+ project: ProjectConfig,
46
+ options: ManifestOptions,
47
+ ) {
48
+ this.storage = storage;
49
+ this.review = review;
50
+ this.work = work;
41
51
  this.project = project;
42
52
  this.options = options;
43
53
  }
@@ -60,16 +70,16 @@ export class ManifestWriter {
60
70
  name: this.project.watchdogs.nameOf(id),
61
71
  files: selection.selected,
62
72
  overflow: selection.overflow.length,
63
- brief: relFromRoot(this.layout.briefFile(id)),
64
- findings: relFromRoot(this.layout.findingFile(id)),
73
+ brief: this.work.briefFile(id),
74
+ findings: this.work.findingFile(id),
65
75
  status: pending.includes(id) ? "pending" : "done",
66
76
  };
67
77
  });
68
78
 
69
- const summary = readContextSummary(this.layout.contextPaths.summary);
79
+ const summary = this.work.readContextSummary();
70
80
  const manifest = {
71
- branch: this.layout.branch,
72
- slug: this.layout.slug,
81
+ branch: this.review.branch,
82
+ slug: this.review.slug,
73
83
  base: this.options.base,
74
84
  head: this.options.head,
75
85
  headSha: classification.headSha,
@@ -104,9 +114,10 @@ export class ManifestWriter {
104
114
  excludedCount: classification.excludedCount,
105
115
  };
106
116
 
107
- new ProjectFiles().write(
108
- this.layout.manifestFile,
109
- `${JSON.stringify(manifest, null, 2)}\n`,
110
- );
117
+ this.storage.writeArtifact(this.review.id, {
118
+ name: "manifest.json",
119
+ kind: "manifest",
120
+ content: `${JSON.stringify(manifest, null, 2)}\n`,
121
+ });
111
122
  }
112
123
  }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Resumo final da revisão (`review-resume.md`).
3
+ *
4
+ * Artefato somente-leitura para humanos: não entra em briefs, não é lido pelos
5
+ * watchdogs e não participa de nenhuma etapa seguinte. Une o contexto de
6
+ * negócio da PR aos números técnicos diretos da revisão, com limite de
7
+ * `RESUME_MAX_CHARS`.
8
+ */
9
+
10
+ import type {
11
+ ContextSummary,
12
+ MergedFinding,
13
+ WatchdogNameResolver,
14
+ } from "../report-builder";
15
+ import { systemClock } from "../services/clock";
16
+
17
+ /** Teto de caracteres do artefato (inclui cabeçalho e quebras de linha). */
18
+ export const RESUME_MAX_CHARS = 2500;
19
+
20
+ /** Dados de classificação usados no resumo. */
21
+ export type ResumeClassification = {
22
+ files: Record<string, { status: string }>;
23
+ watchdogs: Record<string, string[]>;
24
+ };
25
+
26
+ export type ReviewResumeInput = {
27
+ branch: string;
28
+ /** `YYYY-MM-DD`; default: data corrente do sistema. */
29
+ date?: string;
30
+ context?: ContextSummary | null;
31
+ classification: ResumeClassification;
32
+ findings: readonly MergedFinding[];
33
+ nameOf?: WatchdogNameResolver;
34
+ };
35
+
36
+ const identityName: WatchdogNameResolver = (id) => id;
37
+
38
+ const STATUS_BY_SEVERITY: Record<"blocking" | "attention" | "clean", string> = {
39
+ blocking: "❌ Mudanças necessárias",
40
+ attention: "⚠️ Aprovado com ressalvas",
41
+ clean: "✅ Aprovado",
42
+ };
43
+
44
+ /** Corta o texto em `max` caracteres, sinalizando com reticências. */
45
+ function truncate(value: string, max: number): string {
46
+ const text = value.trim().replace(/\s+/g, " ");
47
+ return text.length <= max ? text : `${text.slice(0, max - 1).trimEnd()}…`;
48
+ }
49
+
50
+ function plural(count: number, singular: string, plural: string): string {
51
+ return `${count} ${count === 1 ? singular : plural}`;
52
+ }
53
+
54
+ /** Resumo de negócio: contexto da PR; sem ele, o alcance da branch. */
55
+ function businessSection(input: ReviewResumeInput): string[] {
56
+ const context = input.context;
57
+ const hasContext =
58
+ context?.available === true &&
59
+ Boolean(
60
+ context.objective ||
61
+ context.motivation ||
62
+ (context.changeSummary && context.changeSummary.length > 0),
63
+ );
64
+ const lines = ["## Resumo de negócio", ""];
65
+
66
+ if (!hasContext) {
67
+ const domainCount = Object.keys(input.classification.watchdogs).length;
68
+ lines.push(
69
+ `A branch \`${input.branch}\` altera ${plural(
70
+ Object.keys(input.classification.files).length,
71
+ "arquivo",
72
+ "arquivos",
73
+ )} em ${plural(domainCount, "domínio", "domínios")} de revisão.`,
74
+ "Sem resumo de contexto da PR disponível.",
75
+ "",
76
+ );
77
+ return lines;
78
+ }
79
+
80
+ if (context.objective) lines.push(context.objective, "");
81
+ if (context.motivation) lines.push(`**Por quê:** ${context.motivation}`, "");
82
+ if (context.changeSummary && context.changeSummary.length > 0) {
83
+ for (const item of context.changeSummary.slice(0, 4)) {
84
+ lines.push(`- ${truncate(item, 180)}`);
85
+ }
86
+ lines.push("");
87
+ }
88
+ return lines;
89
+ }
90
+
91
+ /** Achados que exigem ação, em formato direto (máximo 5). */
92
+ function technicalSection(
93
+ findings: readonly MergedFinding[],
94
+ nameOf: WatchdogNameResolver,
95
+ ): string[] {
96
+ const relevant = findings
97
+ .filter(
98
+ (finding) =>
99
+ finding.severity === "blocking" || finding.severity === "attention",
100
+ )
101
+ .slice(0, 5);
102
+ const lines = ["## Pontos técnicos", ""];
103
+ if (relevant.length === 0) {
104
+ lines.push("- Nenhum bloqueio ou ponto de atenção.", "");
105
+ return lines;
106
+ }
107
+ for (const finding of relevant) {
108
+ const location = finding.line ? `${finding.file}:${finding.line}` : finding.file;
109
+ const watchdogs = finding.watchdogs.map((id) => nameOf(id)).join(", ");
110
+ lines.push(
111
+ `- \`${location}\` — ${truncate(finding.message, 130)}${watchdogs ? ` _(${watchdogs})_` : ""}`,
112
+ );
113
+ }
114
+ lines.push("");
115
+ return lines;
116
+ }
117
+
118
+ /** Garante o teto de caracteres cortando em uma quebra de linha. */
119
+ function clamp(markdown: string, max: number): string {
120
+ if (markdown.length <= max) return markdown;
121
+ const head = markdown.slice(0, max - 2);
122
+ const cut = head.lastIndexOf("\n");
123
+ return `${(cut > 0 ? head.slice(0, cut) : head).trimEnd()}\n…`;
124
+ }
125
+
126
+ /**
127
+ * Renderiza o resumo final. Determinístico e sem chamadas externas: o texto
128
+ * nasce do contexto já disponível e dos findings consolidados.
129
+ */
130
+ export function renderReviewResume(input: ReviewResumeInput): string {
131
+ const nameOf = input.nameOf ?? identityName;
132
+ const blockers = input.findings.filter((f) => f.severity === "blocking").length;
133
+ const attention = input.findings.filter((f) => f.severity === "attention").length;
134
+ const status =
135
+ blockers > 0
136
+ ? STATUS_BY_SEVERITY.blocking
137
+ : attention > 0
138
+ ? STATUS_BY_SEVERITY.attention
139
+ : STATUS_BY_SEVERITY.clean;
140
+
141
+ const lines = [
142
+ `# Resumo da revisão — ${input.branch}`,
143
+ "",
144
+ `**Data:** ${input.date ?? systemClock().slice(0, 10)} · **Status:** ${status}`,
145
+ "",
146
+ ...businessSection(input),
147
+ ...technicalSection(input.findings, nameOf),
148
+ ];
149
+
150
+ return clamp(`${lines.join("\n").trimEnd()}\n`, RESUME_MAX_CHARS);
151
+ }
@@ -294,6 +294,7 @@ function renderConclusion(counts: Record<Severity, number>): string[] {
294
294
  "",
295
295
  `**Status:** ${status}`,
296
296
  `**Resumo:** ${counts.blocking} violações bloqueantes, ${counts.attention} pontos de atenção, ${counts.suggestion} sugestões e ${counts.nit} nits.`,
297
+ "",
297
298
  ];
298
299
  }
299
300
 
@@ -453,6 +454,12 @@ export function renderReport(
453
454
 
454
455
  if (context) lines.push(...renderContext(context));
455
456
 
457
+ // Sumário primeiro: quem lê o review.md vê domínios, cobertura e conclusão
458
+ // antes dos pontos específicos.
459
+ lines.push(...renderDomainIndex(domains));
460
+ lines.push(...renderCoverage(reports, coverage, nameOf));
461
+ lines.push(...renderConclusion(counts));
462
+
456
463
  lines.push(...renderFindingsSections(merged, nameOf));
457
464
 
458
465
  if (merged.length === 0) {
@@ -463,9 +470,5 @@ export function renderReport(
463
470
  lines.push(...renderUnconfirmedCandidates(candidates, merged));
464
471
  }
465
472
 
466
- lines.push(...renderDomainIndex(domains));
467
- lines.push(...renderCoverage(reports, coverage, nameOf));
468
- lines.push(...renderConclusion(counts));
469
-
470
473
  return lines.join("\n");
471
474
  }
@@ -1,108 +1,8 @@
1
1
  /**
2
- * Layout de uma run (biblioteca) — único ponto que conhece os nomes/estrutura
3
- * dos artefatos em `.houndsman/.runs/<slug>/`.
2
+ * Removido: o layout de artefatos do projeto deu lugar ao staging global por
3
+ * review (`ReviewWorkArea` em `core/storage/work-area.ts`).
4
4
  *
5
- * Evita o "data clump" de `(dir, branch)`/`(outDir, branch)` espalhado pelo
6
- * motor: quem precisa de um caminho pede ao layout.
5
+ * Mantido apenas para não quebrar imports antigos de tipos.
7
6
  */
8
7
 
9
- import { join } from "node:path";
10
- import {
11
- CONTEXT_BRIEF_FILE,
12
- CONTEXT_FILE,
13
- CONTEXT_SUMMARY_FILE,
14
- RUN_STATUS_FILE,
15
- STATE_FILE,
16
- VERIFY_FILE,
17
- } from "./paths";
18
- import { sanitizeBranch } from "./report-builder";
19
-
20
- /** Caminhos dos artefatos de contexto da PR. */
21
- export type ContextPaths = {
22
- dir: string;
23
- context: string;
24
- summary: string;
25
- brief: string;
26
- };
27
-
28
- /** Diretório e caminhos de uma run (branch-slug). */
29
- export class RunLayout {
30
- readonly artifactDir: string;
31
- readonly branch: string;
32
-
33
- constructor(artifactDir: string, branch: string) {
34
- this.artifactDir = artifactDir;
35
- this.branch = branch;
36
- }
37
-
38
- get slug(): string {
39
- return sanitizeBranch(this.branch);
40
- }
41
-
42
- get outDir(): string {
43
- return join(this.artifactDir, this.slug);
44
- }
45
-
46
- get briefsDir(): string {
47
- return join(this.outDir, "briefs");
48
- }
49
-
50
- get findingsDir(): string {
51
- return join(this.outDir, "findings");
52
- }
53
-
54
- /** Caminho de um arquivo dentro da pasta da run. */
55
- file(name: string): string {
56
- return join(this.outDir, name);
57
- }
58
-
59
- briefFile(id: string): string {
60
- return join(this.briefsDir, `${id}.md`);
61
- }
62
-
63
- findingFile(id: string): string {
64
- return join(this.findingsDir, `${id}.json`);
65
- }
66
-
67
- get classificationFile(): string {
68
- return this.file("classification.json");
69
- }
70
-
71
- get manifestFile(): string {
72
- return this.file("manifest.json");
73
- }
74
-
75
- get candidatesFile(): string {
76
- return this.file("candidates.json");
77
- }
78
-
79
- get reviewFile(): string {
80
- return this.file("review.md");
81
- }
82
-
83
- get stateFile(): string {
84
- return this.file(STATE_FILE);
85
- }
86
-
87
- get runStatusFile(): string {
88
- return this.file(RUN_STATUS_FILE);
89
- }
90
-
91
- get verifyFile(): string {
92
- return this.file(VERIFY_FILE);
93
- }
94
-
95
- /** Relatório isolado de um watchdog (`review.<id>.md`). */
96
- domainReportFile(id: string): string {
97
- return this.file(`review.${sanitizeBranch(id)}.md`);
98
- }
99
-
100
- get contextPaths(): ContextPaths {
101
- return {
102
- dir: this.outDir,
103
- context: this.file(CONTEXT_FILE),
104
- summary: this.file(CONTEXT_SUMMARY_FILE),
105
- brief: this.file(CONTEXT_BRIEF_FILE),
106
- };
107
- }
108
- }
8
+ export type { ContextPaths } from "./storage/work-area";