@payconductor/houndsman 0.7.0 → 0.10.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 (88) hide show
  1. package/README.md +20 -0
  2. package/package.json +1 -1
  3. package/src/cli/commands/command.ts +10 -1
  4. package/src/cli/commands/context-command.ts +40 -2
  5. package/src/cli/commands/hunt-command.ts +28 -2
  6. package/src/cli/commands/review-command.ts +30 -2
  7. package/src/cli/commands/worktree-command.ts +118 -0
  8. package/src/cli/help.ts +12 -2
  9. package/src/cli/index.ts +14 -4
  10. package/src/core/ai/ai-client.ts +21 -0
  11. package/src/core/ai/ai-sdk-model-provider.ts +43 -18
  12. package/src/core/ai/features/technical-summarizer.ts +43 -0
  13. package/src/core/ai/index.ts +4 -1
  14. package/src/core/ai/prompts/context.ts +3 -3
  15. package/src/core/ai/prompts/technical.ts +30 -0
  16. package/src/core/ai/provider-capabilities.ts +45 -0
  17. package/src/core/ai/schemas.ts +19 -3
  18. package/src/core/ai/types.ts +3 -1
  19. package/src/core/ast/service.ts +7 -2
  20. package/src/core/brief.ts +26 -4
  21. package/src/core/commands/context.ts +9 -1
  22. package/src/core/commands/findings.ts +11 -5
  23. package/src/core/commands/review.ts +13 -0
  24. package/src/core/commands/scaffold.ts +9 -2
  25. package/src/core/config.ts +30 -1
  26. package/src/core/defaults.ts +7 -0
  27. package/src/core/engine/houndsman-engine.ts +26 -3
  28. package/src/core/engine/review-scope.ts +17 -22
  29. package/src/core/engine/review-session.ts +25 -2
  30. package/src/core/engine/review-workspace.ts +204 -0
  31. package/src/core/git-runner.ts +8 -1
  32. package/src/core/jev/jev-detector.ts +10 -2
  33. package/src/core/orchestrator.ts +176 -15
  34. package/src/core/paths.ts +3 -0
  35. package/src/core/pr-context.ts +11 -6
  36. package/src/core/project-config.ts +21 -1
  37. package/src/core/renderers/resume-renderer.ts +17 -34
  38. package/src/core/report-builder.ts +6 -6
  39. package/src/core/services/git-repository.ts +46 -0
  40. package/src/core/services/pr-resolver.ts +147 -0
  41. package/src/core/services/worktree-manager.ts +293 -0
  42. package/src/core/storage/file/file-storage.ts +14 -1
  43. package/src/core/storage/paths.ts +30 -1
  44. package/src/core/storage/sqlite/mappers.ts +4 -0
  45. package/src/core/storage/sqlite/schema.ts +13 -3
  46. package/src/core/storage/sqlite/sqlite-storage.ts +28 -8
  47. package/src/core/storage/types.ts +13 -0
  48. package/src/core/storage/work-area.ts +20 -1
  49. package/src/core/technical-summary.ts +398 -0
  50. package/src/core/types.ts +2 -0
  51. package/src/harness/agent/agent-factory.ts +1 -0
  52. package/src/harness/agent/review-agent.ts +11 -1
  53. package/src/harness/config/harness-config.ts +6 -1
  54. package/src/harness/hunt/hunt-options.ts +17 -0
  55. package/src/harness/hunt/hunt-session.ts +7 -20
  56. package/src/index.ts +33 -0
  57. package/src/server/client/App.tsx +51 -12
  58. package/src/server/client/DeleteRunModal.tsx +163 -0
  59. package/src/server/client/PrsSection.tsx +296 -0
  60. package/src/server/client/ResumePanel.tsx +17 -20
  61. package/src/server/client/RunsSection.tsx +73 -0
  62. package/src/server/client/Sidebar.tsx +171 -358
  63. package/src/server/client/icons.tsx +10 -0
  64. package/src/server/client/sidebar-sections.tsx +73 -0
  65. package/src/server/client/utils/constants.ts +7 -2
  66. package/src/server/client/utils/sidebar-prefs.ts +64 -0
  67. package/src/server/client/utils/types.ts +27 -0
  68. package/src/server/index.ts +113 -406
  69. package/src/server/pr-inbox.ts +15 -4
  70. package/src/server/run-repository.ts +103 -2
  71. package/src/server/styles.css +454 -0
  72. package/src/skill/README.md +11 -3
  73. package/src/skill/SKILL.md +44 -10
  74. package/src/skill/hunt.md +33 -7
  75. package/src/tests/__snapshots__/baseline.test.ts.snap +99 -24
  76. package/src/tests/ai-core.test.ts +71 -0
  77. package/src/tests/ai-features.test.ts +36 -0
  78. package/src/tests/baseline.test.ts +4 -1
  79. package/src/tests/cli.test.ts +2 -1
  80. package/src/tests/harness-agent.test.ts +19 -0
  81. package/src/tests/review-resume.test.ts +36 -8
  82. package/src/tests/review-scope.test.ts +67 -0
  83. package/src/tests/review-scripts.test.ts +2 -2
  84. package/src/tests/server.test.ts +54 -1
  85. package/src/tests/sidebar-sections.test.ts +94 -0
  86. package/src/tests/technical-summary.test.ts +176 -0
  87. package/src/tests/worktree.test.ts +404 -0
  88. package/assets/logo-legacy.png +0 -0
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Registry das seções da sidebar — fonte única da navegação.
3
+ *
4
+ * Adicionar uma seção é incluir um item em `SIDEBAR_SECTIONS`: a rail e o
5
+ * painel passam a renderizá-la sem condicionais em `Sidebar.tsx`. O `Content`
6
+ * fica montado mesmo fora de foco, então deve buscar dados apenas quando
7
+ * `active` for `true` (preserva estado ao alternar, sem refetch).
8
+ */
9
+
10
+ import type { ComponentType } from "preact";
11
+ import { GitPullRequest, type IconProps, ListIcon } from "./icons";
12
+ import { PrsSection } from "./PrsSection";
13
+ import { RunsSection } from "./RunsSection";
14
+ import type { RunSummary } from "./utils/types";
15
+
16
+ /** Estado e ações compartilhados por todas as seções da sidebar. */
17
+ export type SidebarSectionContext = {
18
+ runs: RunSummary[];
19
+ slug: string | null;
20
+ onSelect: (slug: string) => void;
21
+ onRefreshRuns: () => void;
22
+ /** Seção visível no momento — use para adiar fetch e polling. */
23
+ active: boolean;
24
+ };
25
+
26
+ export type SidebarSection = {
27
+ id: string;
28
+ /** Nome curto da tooltip da rail. */
29
+ label: string;
30
+ /** Título exibido no topo do painel. */
31
+ title: string;
32
+ /** Linha de apoio abaixo do título (contexto da seção). */
33
+ hint?: string;
34
+ icon: ComponentType<IconProps>;
35
+ /** Contador opcional da rail (ex.: quantidade de execuções). */
36
+ badge?: (ctx: SidebarSectionContext) => number | null;
37
+ Content: ComponentType<SidebarSectionContext>;
38
+ };
39
+
40
+ export const SIDEBAR_SECTIONS: SidebarSection[] = [
41
+ {
42
+ id: "runs",
43
+ label: "Execuções",
44
+ title: "Execuções",
45
+ hint: "Revisões executadas pelo Houndsman",
46
+ icon: ListIcon,
47
+ badge: ({ runs }) => (runs.length > 0 ? runs.length : null),
48
+ Content: RunsSection,
49
+ },
50
+ {
51
+ id: "prs",
52
+ label: "PRs",
53
+ title: "PRs",
54
+ hint: "Pull requests abertas no repositório",
55
+ icon: GitPullRequest,
56
+ Content: PrsSection,
57
+ },
58
+ ];
59
+
60
+ /** Seção inicial quando não há preferência salva (ou ela é inválida). */
61
+ export const DEFAULT_SIDEBAR_SECTION = SIDEBAR_SECTIONS[0].id;
62
+
63
+ /** Resolve um id de seção salvo, caindo no default quando desconhecido. */
64
+ export function resolveSidebarSectionId(id: string | null | undefined): string {
65
+ return id && SIDEBAR_SECTIONS.some((section) => section.id === id)
66
+ ? id
67
+ : DEFAULT_SIDEBAR_SECTION;
68
+ }
69
+
70
+ /** Definição da seção (o id é sempre resolvido antes, via registry). */
71
+ export function getSidebarSection(id: string): SidebarSection {
72
+ return SIDEBAR_SECTIONS.find((section) => section.id === id) ?? SIDEBAR_SECTIONS[0];
73
+ }
@@ -85,6 +85,7 @@ export function describeRunStatus(
85
85
  const KIND_ORDER: ArtifactKind[] = [
86
86
  "review",
87
87
  "resume",
88
+ "technical",
88
89
  "domain-review",
89
90
  "verify",
90
91
  "brief",
@@ -109,6 +110,7 @@ const GROUP_BY_KIND: Record<ArtifactKind, { key: string; label: string; order: n
109
110
  {
110
111
  review: { key: "report", label: "Relatórios", order: 0 },
111
112
  resume: { key: "report", label: "Relatórios", order: 0 },
113
+ technical: { key: "core", label: "Núcleo da revisão", order: 3 },
112
114
  "domain-review": { key: "report", label: "Relatórios", order: 0 },
113
115
  verify: { key: "verify", label: "Verificação", order: 1 },
114
116
  brief: { key: "brief", label: "Briefs (auditoria)", order: 2 },
@@ -132,6 +134,7 @@ const GROUP_BY_KIND: Record<ArtifactKind, { key: string; label: string; order: n
132
134
  export const ARTIFACT_KIND_LABELS: Record<ArtifactKind, string> = {
133
135
  review: "relatório",
134
136
  resume: "resumo",
137
+ technical: "pontos técnicos",
135
138
  "domain-review": "domínio",
136
139
  verify: "verify",
137
140
  brief: "brief",
@@ -164,7 +167,9 @@ export function describeArtifact(meta: ArtifactMeta): string {
164
167
  case "review":
165
168
  return "Relatório consolidado da revisão — o entregável.";
166
169
  case "resume":
167
- return "Resumo final da revisão: visão de negócio e pontos técnicos diretos (leitura humana).";
170
+ return "Resumo final da revisão: visão de negócio e estrutura técnica (leitura humana).";
171
+ case "technical":
172
+ return "Destaques estruturais da mudança (módulos, contratos, dependências) usados no resumo final.";
168
173
  case "domain-review":
169
174
  return "Relatório isolado do domínio: achados daquele watchdog.";
170
175
  case "verify":
@@ -240,7 +245,7 @@ export function groupArtifacts(
240
245
 
241
246
  /** Legenda única de cada aba (explica o que a aba é; mostrada quando selecionada). */
242
247
  export const TAB_CAPTIONS = {
243
- resume: "Resumo principal da revisão: o que muda em termos de negócio, números diretos (arquivos, churn, domínios) e os pontos técnicos que exigem ação.",
248
+ resume: "Resumo principal da revisão: visão de negócio, números diretos (arquivos, churn, domínios) e os pontos técnicos estruturais da mudança.",
244
249
  findings:
245
250
  "Triagem dos achados da revisão: decida Executar, Ignorar ou deixe Pendente — o comentário orienta o auto-fix. Agente: houndsman findings list --decision pending --json · findings done <id>.",
246
251
  artifacts:
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Preferências locais da sidebar (largura do painel, colapso e seção ativa).
3
+ *
4
+ * Persistidas em `localStorage` e lidas de forma falha-aberta: storage
5
+ * indisponível ou JSON inválido caem nos defaults, sem quebrar a UI.
6
+ */
7
+
8
+ export const SIDEBAR_MIN_WIDTH = 320;
9
+ export const SIDEBAR_MAX_WIDTH = 560;
10
+ export const SIDEBAR_DEFAULT_WIDTH = 360;
11
+
12
+ /** Chave única — o valor é um JSON com `width`, `collapsed` e `section`. */
13
+ export const SIDEBAR_STORAGE_KEY = "houndsman.sidebar";
14
+
15
+ export type SidebarPrefs = {
16
+ width: number;
17
+ collapsed: boolean;
18
+ section: string | null;
19
+ };
20
+
21
+ /** Preferências usadas quando não há nada salvo. */
22
+ export function defaultSidebarPrefs(): SidebarPrefs {
23
+ return {
24
+ width: SIDEBAR_DEFAULT_WIDTH,
25
+ collapsed: false,
26
+ section: null,
27
+ };
28
+ }
29
+
30
+ /** Mantém a largura dentro de limites seguros (NaN/infinito viram o default). */
31
+ export function clampSidebarWidth(value: number): number {
32
+ if (!Number.isFinite(value)) return SIDEBAR_DEFAULT_WIDTH;
33
+ return Math.min(SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, Math.round(value)));
34
+ }
35
+
36
+ /** Lê as preferências salvas; qualquer falha retorna os defaults. */
37
+ export function readSidebarPrefs(): SidebarPrefs {
38
+ const fallback = defaultSidebarPrefs();
39
+ try {
40
+ if (typeof localStorage === "undefined") return fallback;
41
+ const raw = localStorage.getItem(SIDEBAR_STORAGE_KEY);
42
+ if (!raw) return fallback;
43
+ const parsed: unknown = JSON.parse(raw);
44
+ if (!parsed || typeof parsed !== "object") return fallback;
45
+ const value = parsed as Record<string, unknown>;
46
+ return {
47
+ width: clampSidebarWidth(Number(value.width)),
48
+ collapsed: value.collapsed === true,
49
+ section: typeof value.section === "string" ? value.section : null,
50
+ };
51
+ } catch {
52
+ return fallback;
53
+ }
54
+ }
55
+
56
+ /** Grava as preferências; falhas (modo privado, cota) são ignoradas. */
57
+ export function saveSidebarPrefs(prefs: SidebarPrefs): void {
58
+ try {
59
+ if (typeof localStorage === "undefined") return;
60
+ localStorage.setItem(SIDEBAR_STORAGE_KEY, JSON.stringify(prefs));
61
+ } catch {
62
+ // fail-open: preferências são cosméticas.
63
+ }
64
+ }
@@ -62,6 +62,7 @@ export type ArtifactKind =
62
62
  | "finding-report"
63
63
  | "review"
64
64
  | "resume"
65
+ | "technical"
65
66
  | "domain-review"
66
67
  | "verify"
67
68
  | "ast"
@@ -83,6 +84,9 @@ export type ArtifactMeta = {
83
84
  export type RunSummary = {
84
85
  slug: string;
85
86
  branch: string;
87
+ /** Worktree onde a run foi executada (ausente = clone principal). */
88
+ worktreePath?: string;
89
+ prNumber?: number;
86
90
  updatedAt?: string;
87
91
  startedAt?: string;
88
92
  /** Último sinal de vida de uma execução ativa (revisão/correção). */
@@ -126,6 +130,8 @@ export type PrListItem = {
126
130
  reviewDecision?: string;
127
131
  url?: string;
128
132
  runSlug?: string;
133
+ /** Worktree ativo da PR (revisão paralela). */
134
+ worktreePath?: string;
129
135
  };
130
136
 
131
137
  export type PrListResponse = {
@@ -145,6 +151,27 @@ export type CheckoutResponse =
145
151
  changes?: string[];
146
152
  };
147
153
 
154
+ /** Resultado de `DELETE /api/runs/:slug`. */
155
+ export type DeleteRunResponse = {
156
+ deleted: boolean;
157
+ worktreeRequested: boolean;
158
+ worktreeRemoved: boolean;
159
+ worktreePath?: string;
160
+ warning?: string;
161
+ };
162
+
163
+ export type PrWorktreeResponse =
164
+ | {
165
+ ok: true;
166
+ number: number;
167
+ branch: string;
168
+ path: string;
169
+ head: string;
170
+ created: boolean;
171
+ warning?: string;
172
+ }
173
+ | { ok: false; reason: string; detail: string };
174
+
148
175
  /** Comentário normalizado da PR (thread ou geral). */
149
176
  export type PrCommentView = {
150
177
  id: number;