@payconductor/houndsman 0.8.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.
- package/package.json +1 -1
- package/src/core/ai/ai-client.ts +21 -0
- package/src/core/ai/ai-sdk-model-provider.ts +43 -18
- package/src/core/ai/features/technical-summarizer.ts +43 -0
- package/src/core/ai/index.ts +4 -1
- package/src/core/ai/prompts/context.ts +3 -3
- package/src/core/ai/prompts/technical.ts +30 -0
- package/src/core/ai/provider-capabilities.ts +45 -0
- package/src/core/ai/schemas.ts +19 -3
- package/src/core/ai/types.ts +3 -1
- package/src/core/brief.ts +25 -4
- package/src/core/commands/scaffold.ts +5 -0
- package/src/core/config.ts +17 -1
- package/src/core/git-runner.ts +8 -1
- package/src/core/orchestrator.ts +114 -2
- package/src/core/paths.ts +3 -0
- package/src/core/pr-context.ts +11 -6
- package/src/core/project-config.ts +9 -1
- package/src/core/renderers/resume-renderer.ts +17 -34
- package/src/core/report-builder.ts +6 -6
- package/src/core/services/worktree-manager.ts +29 -5
- package/src/core/storage/types.ts +1 -0
- package/src/core/technical-summary.ts +398 -0
- package/src/harness/agent/agent-factory.ts +1 -0
- package/src/harness/agent/review-agent.ts +11 -1
- package/src/harness/config/harness-config.ts +6 -1
- package/src/index.ts +2 -0
- package/src/server/client/App.tsx +2 -1
- package/src/server/client/PrsSection.tsx +296 -0
- package/src/server/client/ResumePanel.tsx +10 -13
- package/src/server/client/RunsSection.tsx +73 -0
- package/src/server/client/Sidebar.tsx +171 -413
- package/src/server/client/sidebar-sections.tsx +73 -0
- package/src/server/client/utils/constants.ts +7 -2
- package/src/server/client/utils/sidebar-prefs.ts +64 -0
- package/src/server/client/utils/types.ts +1 -0
- package/src/server/index.ts +23 -409
- package/src/server/styles.css +454 -0
- package/src/tests/__snapshots__/baseline.test.ts.snap +5 -0
- package/src/tests/ai-core.test.ts +71 -0
- package/src/tests/ai-features.test.ts +36 -0
- package/src/tests/baseline.test.ts +4 -1
- package/src/tests/harness-agent.test.ts +19 -0
- package/src/tests/review-resume.test.ts +36 -8
- package/src/tests/review-scope.test.ts +67 -0
- package/src/tests/review-scripts.test.ts +2 -2
- package/src/tests/server.test.ts +16 -1
- package/src/tests/sidebar-sections.test.ts +94 -0
- package/src/tests/technical-summary.test.ts +176 -0
- package/src/tests/worktree.test.ts +39 -4
- 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
|
|
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:
|
|
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
|
+
}
|