@payconductor/houndsman 0.5.0 → 0.7.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/README.md +24 -4
- package/assets/grammars/tree-sitter-javascript.wasm +0 -0
- package/assets/grammars/tree-sitter-tsx.wasm +0 -0
- package/assets/grammars/tree-sitter-typescript.wasm +0 -0
- package/assets/grammars/web-tree-sitter.wasm +0 -0
- package/package.json +11 -3
- package/src/cli/commands/command.ts +5 -0
- package/src/cli/commands/doctor-command.ts +116 -24
- package/src/cli/commands/hunt-command.ts +25 -0
- package/src/cli/commands/server-command.ts +7 -1
- package/src/cli/help.ts +18 -2
- package/src/cli/index.ts +10 -6
- package/src/core/ai/ai-client.ts +221 -0
- package/src/core/ai/ai-sdk-model-provider.ts +101 -0
- package/src/core/ai/errors.ts +36 -0
- package/src/core/ai/features/brief-enricher.ts +62 -0
- package/src/core/ai/features/classifier.ts +50 -0
- package/src/core/ai/features/context-summarizer.ts +31 -0
- package/src/core/ai/features/pre-analyzer.ts +51 -0
- package/src/core/ai/features/verifier.ts +45 -0
- package/src/core/ai/index.ts +56 -0
- package/src/core/ai/model-factory.ts +137 -0
- package/src/core/ai/prompts/base.ts +36 -0
- package/src/core/ai/prompts/brief.ts +87 -0
- package/src/core/ai/prompts/classification.ts +36 -0
- package/src/core/ai/prompts/context.ts +29 -0
- package/src/core/ai/prompts/pre-analysis.ts +49 -0
- package/src/core/ai/prompts/verify.ts +40 -0
- package/src/core/ai/schemas.ts +110 -0
- package/src/core/ai/types.ts +76 -0
- package/src/core/ai/usage-budget.ts +75 -0
- package/src/core/ai/usage-recorder.ts +79 -0
- package/src/core/assets.ts +16 -0
- package/src/core/ast/diff.ts +220 -0
- package/src/core/ast/impact.ts +127 -0
- package/src/core/ast/parser.ts +167 -0
- package/src/core/ast/service.ts +233 -0
- package/src/core/ast/symbols.ts +294 -0
- package/src/core/ast/types.ts +125 -0
- package/src/core/brief.ts +138 -0
- package/src/core/commands/findings.ts +65 -2
- package/src/core/commands/review.ts +2 -0
- package/src/core/commands/scaffold.ts +159 -10
- package/src/core/commands/update.ts +5 -3
- package/src/core/config.ts +85 -0
- package/src/core/defaults.ts +27 -0
- package/src/core/domain/watchdog.ts +43 -5
- package/src/core/engine/houndsman-engine.ts +16 -1
- package/src/core/engine/review-scope.ts +52 -6
- package/src/core/engine/review-session.ts +27 -2
- package/src/core/env.ts +101 -0
- package/src/core/file-classifier.ts +25 -4
- package/src/core/git-runner.ts +25 -13
- package/src/core/github-review.ts +10 -7
- package/src/core/jev/jev-client.ts +8 -7
- package/src/core/jev/jev-verify.ts +2 -1
- package/src/core/logging/index.ts +17 -0
- package/src/core/logging/logger.ts +187 -0
- package/src/core/orchestrator.ts +357 -9
- package/src/core/project-config.ts +123 -1
- package/src/core/registry-check.ts +148 -6
- package/src/core/renderers/manifest-writer.ts +33 -0
- package/src/core/rule-text.ts +97 -0
- package/src/core/services/file-classifier.ts +10 -3
- package/src/core/services/gh-client.ts +38 -22
- package/src/core/services/verifier.ts +14 -3
- package/src/core/storage/paths.ts +6 -5
- package/src/core/storage/types.ts +6 -0
- package/src/core/storage/work-area.ts +12 -0
- package/src/core/types.ts +37 -1
- package/src/core/verify.ts +17 -5
- package/src/harness/README.md +15 -0
- package/src/harness/agent/agent-context.ts +33 -0
- package/src/harness/agent/agent-errors.ts +30 -0
- package/src/harness/agent/agent-factory.ts +45 -0
- package/src/harness/agent/agent-reporter.ts +34 -0
- package/src/harness/agent/agent-result.ts +16 -0
- package/src/harness/agent/agent-runner.ts +32 -0
- package/src/harness/agent/agent-scheduler.ts +37 -0
- package/src/harness/agent/review-agent.ts +391 -0
- package/src/harness/config/harness-config.ts +84 -0
- package/src/harness/hunt/hunt-harness.ts +83 -0
- package/src/harness/hunt/hunt-options.ts +75 -0
- package/src/harness/hunt/hunt-result.ts +17 -0
- package/src/harness/hunt/hunt-session.ts +372 -0
- package/src/harness/hunt/review-planner.ts +98 -0
- package/src/harness/index.ts +36 -0
- package/src/harness/investigation/agent-state.ts +134 -0
- package/src/harness/investigation/evidence-verifier.ts +129 -0
- package/src/harness/investigation/evidence.ts +110 -0
- package/src/harness/investigation/finding-critic.ts +96 -0
- package/src/harness/investigation/investigation-planner.ts +118 -0
- package/src/harness/investigation/investigation-task.ts +10 -0
- package/src/harness/investigation/review-journal.ts +87 -0
- package/src/harness/investigation/rule-analyzer.ts +178 -0
- package/src/harness/investigation/rule-signals.ts +110 -0
- package/src/harness/prompts/agent-schemas.ts +57 -0
- package/src/harness/prompts/reviewer-prompt-builder.ts +101 -0
- package/src/harness/prompts/states/conclude-prompt.ts +16 -0
- package/src/harness/prompts/states/investigate-prompt.ts +37 -0
- package/src/harness/prompts/states/orient-prompt.ts +15 -0
- package/src/harness/prompts/states/verify-prompt.ts +22 -0
- package/src/harness/tools/agent-tool.ts +102 -0
- package/src/harness/tools/builtin/changed-symbols-tool.ts +48 -0
- package/src/harness/tools/builtin/doc-context-tool.ts +74 -0
- package/src/harness/tools/builtin/file-outline-tool.ts +66 -0
- package/src/harness/tools/builtin/find-references-tool.ts +66 -0
- package/src/harness/tools/builtin/find-symbol-tool.ts +91 -0
- package/src/harness/tools/builtin/helpers.ts +115 -0
- package/src/harness/tools/builtin/impact-tool.ts +48 -0
- package/src/harness/tools/builtin/imports-exports-tool.ts +53 -0
- package/src/harness/tools/builtin/index.ts +59 -0
- package/src/harness/tools/builtin/list-files-tool.ts +85 -0
- package/src/harness/tools/builtin/pr-context-tool.ts +41 -0
- package/src/harness/tools/builtin/read-file-range-tool.ts +73 -0
- package/src/harness/tools/builtin/rule-signals-tool.ts +36 -0
- package/src/harness/tools/builtin/search-code-tool.ts +65 -0
- package/src/harness/tools/builtin/symbol-context-tool.ts +106 -0
- package/src/harness/tools/builtin/tests-for-symbol-tool.ts +61 -0
- package/src/harness/tools/path-guard.ts +90 -0
- package/src/harness/tools/result-limiter.ts +49 -0
- package/src/harness/tools/secret-redactor.ts +54 -0
- package/src/harness/tools/tool-cache.ts +50 -0
- package/src/harness/tools/tool-call-budget.ts +60 -0
- package/src/harness/tools/tool-context.ts +5 -0
- package/src/harness/tools/tool-middleware.ts +105 -0
- package/src/harness/tools/tool-policy.ts +51 -0
- package/src/harness/tools/tool-registry.ts +112 -0
- package/src/harness/tools/tool-result.ts +15 -0
- package/src/harness/tools/toolbox.ts +36 -0
- package/src/harness/validation/finding-validator.ts +119 -0
- package/src/index.ts +46 -0
- package/src/server/client/App.tsx +38 -12
- package/src/server/client/CommentsPanel.tsx +255 -0
- package/src/server/client/FindingCard.tsx +12 -4
- package/src/server/client/FindingsList.tsx +4 -1
- package/src/server/client/Sidebar.tsx +397 -0
- package/src/server/client/SubmitModal.tsx +61 -5
- package/src/server/client/icons.tsx +19 -0
- package/src/server/client/index.tsx +1 -1
- package/src/server/client/utils/constants.ts +29 -5
- package/src/server/client/utils/format.ts +47 -2
- package/src/server/client/utils/types.ts +88 -0
- package/src/server/editor.ts +129 -0
- package/src/server/index.ts +115 -5
- package/src/server/pr-comments.ts +256 -0
- package/src/server/pr-inbox.ts +202 -0
- package/src/server/run-repository.ts +33 -1
- package/src/skill/OUTPUT-FORMAT.md +12 -0
- package/src/skill/SETUP.md +6 -2
- package/src/skill/hunt.md +14 -2
- package/src/skill/verify.md +1 -1
- package/src/tests/__snapshots__/baseline.test.ts.snap +96 -14
- package/src/tests/ai-core.test.ts +120 -0
- package/src/tests/ai-features.test.ts +183 -0
- package/src/tests/ast.test.ts +335 -0
- package/src/tests/cli.test.ts +33 -0
- package/src/tests/env.test.ts +69 -0
- package/src/tests/fallback.test.ts +172 -0
- package/src/tests/fixtures/fake-agent-tools.ts +69 -0
- package/src/tests/fixtures/fake-investigation.ts +64 -0
- package/src/tests/fixtures/fake-model-provider.ts +48 -0
- package/src/tests/github-review.test.ts +9 -0
- package/src/tests/harness-agent.test.ts +122 -0
- package/src/tests/harness-investigation.test.ts +206 -0
- package/src/tests/harness-tools.test.ts +148 -0
- package/src/tests/hunt-command.test.ts +212 -0
- package/src/tests/logger.test.ts +83 -0
- package/src/tests/review-scope.test.ts +11 -11
- package/src/tests/rule-analyzer.test.ts +61 -0
- package/src/tests/server-editor.test.ts +100 -0
- package/src/tests/server.test.ts +295 -2
- package/src/tests/tool-contracts.test.ts +82 -0
- package/src/tests/verify.test.ts +1 -1
- package/src/server/client/RunList.tsx +0 -75
|
@@ -64,6 +64,12 @@ export type ArtifactKind =
|
|
|
64
64
|
| "resume"
|
|
65
65
|
| "domain-review"
|
|
66
66
|
| "verify"
|
|
67
|
+
| "ast"
|
|
68
|
+
| "ast-impact"
|
|
69
|
+
| "ai"
|
|
70
|
+
| "ai-usage"
|
|
71
|
+
| "ai-brief"
|
|
72
|
+
| "harness-transcript"
|
|
67
73
|
| "other";
|
|
68
74
|
|
|
69
75
|
/** Metadados de artefato retornados pela API (sem conteúdo). */
|
|
@@ -91,6 +97,86 @@ export type FindingsResponse = {
|
|
|
91
97
|
summary: Summary;
|
|
92
98
|
};
|
|
93
99
|
|
|
100
|
+
/** Editor detectado pelo servidor (base dos links de código). */
|
|
101
|
+
export type EditorKind = "vscode" | "jetbrains" | "none";
|
|
102
|
+
|
|
103
|
+
export type EditorContext = {
|
|
104
|
+
kind: EditorKind;
|
|
105
|
+
project?: string;
|
|
106
|
+
product?: string;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/** Estado da árvore de trabalho (bloqueia o checkout quando suja). */
|
|
110
|
+
export type WorktreeState = {
|
|
111
|
+
available: boolean;
|
|
112
|
+
clean: boolean;
|
|
113
|
+
changes: number;
|
|
114
|
+
sample: string[];
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/** PR aberta exibida na sidebar. */
|
|
118
|
+
export type PrListItem = {
|
|
119
|
+
number: number;
|
|
120
|
+
title: string;
|
|
121
|
+
headRefName: string;
|
|
122
|
+
baseRefName: string;
|
|
123
|
+
author: string;
|
|
124
|
+
updatedAt?: string;
|
|
125
|
+
isDraft: boolean;
|
|
126
|
+
reviewDecision?: string;
|
|
127
|
+
url?: string;
|
|
128
|
+
runSlug?: string;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export type PrListResponse = {
|
|
132
|
+
available: boolean;
|
|
133
|
+
reason?: string;
|
|
134
|
+
currentBranch: string | null;
|
|
135
|
+
worktree: WorktreeState;
|
|
136
|
+
prs: PrListItem[];
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export type CheckoutResponse =
|
|
140
|
+
| { ok: true; number: number; branch: string | null }
|
|
141
|
+
| {
|
|
142
|
+
ok: false;
|
|
143
|
+
reason: "dirty" | "git" | "gh";
|
|
144
|
+
detail: string;
|
|
145
|
+
changes?: string[];
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** Comentário normalizado da PR (thread ou geral). */
|
|
149
|
+
export type PrCommentView = {
|
|
150
|
+
id: number;
|
|
151
|
+
author: string;
|
|
152
|
+
authorType: string;
|
|
153
|
+
body: string;
|
|
154
|
+
createdAt?: string;
|
|
155
|
+
url?: string;
|
|
156
|
+
path?: string;
|
|
157
|
+
line: number | null;
|
|
158
|
+
absFile?: string;
|
|
159
|
+
replyTo?: number | null;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export type PrCommentThread = {
|
|
163
|
+
id: number;
|
|
164
|
+
path: string;
|
|
165
|
+
line: number | null;
|
|
166
|
+
absFile: string;
|
|
167
|
+
comments: PrCommentView[];
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export type PrCommentsResponse = {
|
|
171
|
+
available: boolean;
|
|
172
|
+
reason?: string;
|
|
173
|
+
target?: { number: number; repo: string; url?: string };
|
|
174
|
+
fetchedAt: string;
|
|
175
|
+
threads: PrCommentThread[];
|
|
176
|
+
issueComments: PrCommentView[];
|
|
177
|
+
counts: { threads: number; inline: number; issue: number };
|
|
178
|
+
};
|
|
179
|
+
|
|
94
180
|
/** Manifesto da run (subset consumido pela aba de resumo). */
|
|
95
181
|
export type RunManifest = {
|
|
96
182
|
branch?: string;
|
|
@@ -143,6 +229,8 @@ export type SubmitCandidate = {
|
|
|
143
229
|
file: string;
|
|
144
230
|
line: number | null;
|
|
145
231
|
severity: Severity;
|
|
232
|
+
/** Decisão da triagem (`execute` = "a executar"). */
|
|
233
|
+
decision: Decision;
|
|
146
234
|
execState: ExecState;
|
|
147
235
|
rule: string;
|
|
148
236
|
message: string;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detecção do editor que hospeda o terminal do servidor (biblioteca do servidor).
|
|
3
|
+
*
|
|
4
|
+
* VS Code e JetBrains expõem variáveis próprias no terminal integrado. O
|
|
5
|
+
* contexto alimenta os links de código da UI (`vscode://` / `jetbrains://`).
|
|
6
|
+
* A detecção é pura e fail-open: sem pistas, o editor fica `none`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { basename, resolve } from "node:path";
|
|
10
|
+
|
|
11
|
+
/** Editores com esquema de URL próprio. */
|
|
12
|
+
export type EditorKind = "vscode" | "jetbrains" | "none";
|
|
13
|
+
|
|
14
|
+
/** Contexto usado pelo cliente para montar os links de código. */
|
|
15
|
+
export type EditorContext = {
|
|
16
|
+
kind: EditorKind;
|
|
17
|
+
/** Nome do projeto aberto no IDE (basename da raiz do repositório). */
|
|
18
|
+
project?: string;
|
|
19
|
+
/** Código do produto JetBrains (`idea`, `webstorm`, `pycharm`…). */
|
|
20
|
+
product?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type Env = Record<string, string | undefined>;
|
|
24
|
+
|
|
25
|
+
/** Bundle macOS → código do produto usado no esquema `jetbrains://`. */
|
|
26
|
+
const BUNDLE_PRODUCTS: Record<string, string> = {
|
|
27
|
+
"com.jetbrains.intellij": "idea",
|
|
28
|
+
"com.jetbrains.intellij.ce": "idea",
|
|
29
|
+
"com.jetbrains.pycharm": "pycharm",
|
|
30
|
+
"com.jetbrains.pycharm.ce": "pycharm",
|
|
31
|
+
"com.jetbrains.webstorm": "webstorm",
|
|
32
|
+
"com.jetbrains.goland": "goland",
|
|
33
|
+
"com.jetbrains.clion": "clion",
|
|
34
|
+
"com.jetbrains.rider": "rider",
|
|
35
|
+
"com.jetbrains.phpstorm": "phpstorm",
|
|
36
|
+
"com.jetbrains.rubymine": "rubymine",
|
|
37
|
+
"com.jetbrains.datagrip": "datagrip",
|
|
38
|
+
"com.jetbrains.appcode": "appcode",
|
|
39
|
+
"com.jetbrains.rustrover": "rustrover",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Variáveis `*_INITIAL_DIRECTORY` → produto JetBrains. */
|
|
43
|
+
const DIR_PRODUCTS: [string, string][] = [
|
|
44
|
+
["IDEA_INITIAL_DIRECTORY", "idea"],
|
|
45
|
+
["PYCHARM_INITIAL_DIRECTORY", "pycharm"],
|
|
46
|
+
["WEBSTORM_INITIAL_DIRECTORY", "webstorm"],
|
|
47
|
+
["GOLAND_INITIAL_DIRECTORY", "goland"],
|
|
48
|
+
["CLION_INITIAL_DIRECTORY", "clion"],
|
|
49
|
+
["RIDER_INITIAL_DIRECTORY", "rider"],
|
|
50
|
+
["PHPSTORM_INITIAL_DIRECTORY", "phpstorm"],
|
|
51
|
+
["RUBYMINE_INITIAL_DIRECTORY", "rubymine"],
|
|
52
|
+
["DATAGRIP_INITIAL_DIRECTORY", "datagrip"],
|
|
53
|
+
["RUSTROVER_INITIAL_DIRECTORY", "rustrover"],
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
function projectName(repoRoot: string): string {
|
|
57
|
+
return basename(resolve(repoRoot));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Produto JetBrains detectado no ambiente (undefined quando ambíguo). */
|
|
61
|
+
export function detectJetBrainsProduct(env: Env): string | undefined {
|
|
62
|
+
const bundle = env.__CFBundleIdentifier?.toLowerCase();
|
|
63
|
+
if (bundle) {
|
|
64
|
+
const exact = BUNDLE_PRODUCTS[bundle];
|
|
65
|
+
if (exact) return exact;
|
|
66
|
+
const prefix = Object.keys(BUNDLE_PRODUCTS).find((key) => bundle.startsWith(key));
|
|
67
|
+
if (prefix) return BUNDLE_PRODUCTS[prefix];
|
|
68
|
+
}
|
|
69
|
+
for (const [name, product] of DIR_PRODUCTS) {
|
|
70
|
+
if (env[name]) return product;
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isJetBrains(env: Env): boolean {
|
|
76
|
+
const bundle = env.__CFBundleIdentifier?.toLowerCase() ?? "";
|
|
77
|
+
return (
|
|
78
|
+
/jetbrains/i.test(env.TERMINAL_EMULATOR ?? "") ||
|
|
79
|
+
bundle.startsWith("com.jetbrains.") ||
|
|
80
|
+
DIR_PRODUCTS.some(([name]) => Boolean(env[name]))
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isVsCode(env: Env): boolean {
|
|
85
|
+
return (
|
|
86
|
+
env.TERM_PROGRAM === "vscode" ||
|
|
87
|
+
env.TERM_PROGRAM === "vscode-insiders" ||
|
|
88
|
+
Boolean(env.VSCODE_PID || env.VSCODE_IPC_HOOK_CLI || env.VSCODE_GIT_IPC_HANDLE)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Detecta o editor a partir do ambiente do terminal integrado. */
|
|
93
|
+
export function detectEditor(env: Env, repoRoot: string): EditorContext {
|
|
94
|
+
const project = projectName(repoRoot);
|
|
95
|
+
if (isJetBrains(env)) {
|
|
96
|
+
return {
|
|
97
|
+
kind: "jetbrains",
|
|
98
|
+
project,
|
|
99
|
+
product: detectJetBrainsProduct(env) ?? "idea",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (isVsCode(env)) return { kind: "vscode", project };
|
|
103
|
+
return { kind: "none", project };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Resolve a preferência do `--editor`.
|
|
108
|
+
*
|
|
109
|
+
* `auto` (ou ausente) detecta do ambiente; `vscode`/`jetbrains`/`none` forçam
|
|
110
|
+
* o comportamento. Valores desconhecidos caem na detecção automática.
|
|
111
|
+
*/
|
|
112
|
+
export function resolveEditor(
|
|
113
|
+
preference: string | undefined,
|
|
114
|
+
env: Env,
|
|
115
|
+
repoRoot: string,
|
|
116
|
+
): EditorContext {
|
|
117
|
+
const value = preference?.trim().toLowerCase();
|
|
118
|
+
const project = projectName(repoRoot);
|
|
119
|
+
if (value === "vscode") return { kind: "vscode", project };
|
|
120
|
+
if (value === "jetbrains") {
|
|
121
|
+
return {
|
|
122
|
+
kind: "jetbrains",
|
|
123
|
+
project,
|
|
124
|
+
product: detectJetBrainsProduct(env) ?? "idea",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (value === "none") return { kind: "none", project };
|
|
128
|
+
return detectEditor(env, repoRoot);
|
|
129
|
+
}
|
package/src/server/index.ts
CHANGED
|
@@ -9,15 +9,20 @@
|
|
|
9
9
|
import { mediaPath } from "../core/assets";
|
|
10
10
|
import type { HoundsmanConfig } from "../core/config";
|
|
11
11
|
import type { FindingUpdate } from "../core/findings-state";
|
|
12
|
+
import { createGitRunner, type GitRunner } from "../core/git-runner";
|
|
12
13
|
import {
|
|
13
14
|
defaultGhRunner,
|
|
14
15
|
type GhRunner,
|
|
15
16
|
type SubmitSelection,
|
|
16
17
|
} from "../core/github-review";
|
|
18
|
+
import { logger } from "../core/logging";
|
|
19
|
+
import { GitRepository } from "../core/services/git-repository";
|
|
17
20
|
import { createStorage } from "../core/storage/factory";
|
|
18
21
|
import { resolveRepoIdentity } from "../core/storage/repo-identity";
|
|
19
22
|
import type { RepoIdentity, StorageProvider } from "../core/storage/types";
|
|
20
23
|
import { buildClientBundle } from "./client-build";
|
|
24
|
+
import { type EditorContext, resolveEditor } from "./editor";
|
|
25
|
+
import { PrInbox } from "./pr-inbox";
|
|
21
26
|
import { RunRepository } from "./run-repository";
|
|
22
27
|
|
|
23
28
|
type BunServer = ReturnType<typeof Bun.serve>;
|
|
@@ -58,8 +63,13 @@ function notFound(detail = "não encontrado"): Response {
|
|
|
58
63
|
/** Repositório das runs (dados) — ver `run-repository.ts`. */
|
|
59
64
|
type ServerDeps = {
|
|
60
65
|
ghRunner?: GhRunner;
|
|
66
|
+
gitRunner?: GitRunner;
|
|
61
67
|
storage?: StorageProvider;
|
|
62
68
|
repo?: RepoIdentity;
|
|
69
|
+
/** Contexto do editor injetado (testes). */
|
|
70
|
+
editor?: EditorContext;
|
|
71
|
+
/** Preferência do `--editor` (CLI). */
|
|
72
|
+
editorPreference?: string;
|
|
63
73
|
};
|
|
64
74
|
|
|
65
75
|
// ---------------------------------------------------------------------------
|
|
@@ -82,6 +92,8 @@ async function route(
|
|
|
82
92
|
request: Request,
|
|
83
93
|
runs: RunRepository,
|
|
84
94
|
ghRunner: GhRunner,
|
|
95
|
+
prs: PrInbox,
|
|
96
|
+
editor: EditorContext,
|
|
85
97
|
): Promise<Response> {
|
|
86
98
|
const url = new URL(request.url);
|
|
87
99
|
const path = url.pathname;
|
|
@@ -94,6 +106,21 @@ async function route(
|
|
|
94
106
|
}
|
|
95
107
|
if (path === `/assets/${LOGO_FILE}`) return serveLogo();
|
|
96
108
|
if (path === "/api/health") return json({ ok: true });
|
|
109
|
+
if (path === "/api/editor" && request.method === "GET") return json(editor);
|
|
110
|
+
if (path === "/api/prs" && request.method === "GET") {
|
|
111
|
+
return json(prs.list(runs.branchIndex()));
|
|
112
|
+
}
|
|
113
|
+
if (path === "/api/prs/checkout" && request.method === "POST") {
|
|
114
|
+
const body = (await request.json().catch(() => ({}))) as { number?: unknown };
|
|
115
|
+
const number = Number(body.number);
|
|
116
|
+
if (!Number.isInteger(number) || number <= 0) {
|
|
117
|
+
return json(
|
|
118
|
+
{ ok: false, reason: "invalid", detail: "Número de PR inválido." },
|
|
119
|
+
400,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return json(prs.checkout(number));
|
|
123
|
+
}
|
|
97
124
|
|
|
98
125
|
if (path === "/api/runs" && request.method === "GET") {
|
|
99
126
|
return json({ runs: runs.list() });
|
|
@@ -107,6 +134,11 @@ async function route(
|
|
|
107
134
|
|
|
108
135
|
if (rest === "" || rest === "/") return json(runs.detail(slug));
|
|
109
136
|
|
|
137
|
+
if (rest === "/comments" && request.method === "GET") {
|
|
138
|
+
return json(
|
|
139
|
+
runs.comments(slug, { refresh: url.searchParams.get("refresh") === "1" }),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
110
142
|
if (rest === "/findings" && request.method === "GET") {
|
|
111
143
|
return json(runs.findings(slug, url.searchParams));
|
|
112
144
|
}
|
|
@@ -118,6 +150,7 @@ async function route(
|
|
|
118
150
|
const body = (await request.json().catch(() => ({}))) as SubmitSelection;
|
|
119
151
|
const selection: SubmitSelection = {
|
|
120
152
|
severities: body.severities,
|
|
153
|
+
decisions: body.decisions,
|
|
121
154
|
execStates: body.execStates,
|
|
122
155
|
includeSent: body.includeSent,
|
|
123
156
|
};
|
|
@@ -160,19 +193,27 @@ async function route(
|
|
|
160
193
|
export class HoundsmanServer {
|
|
161
194
|
private readonly runs: RunRepository;
|
|
162
195
|
private readonly ghRunner: GhRunner;
|
|
196
|
+
private readonly prs: PrInbox;
|
|
197
|
+
private readonly editor: EditorContext;
|
|
163
198
|
|
|
164
199
|
constructor(config: HoundsmanConfig, deps: ServerDeps = {}) {
|
|
200
|
+
const repo = deps.repo ?? resolveRepoIdentity();
|
|
201
|
+
this.ghRunner = deps.ghRunner ?? defaultGhRunner;
|
|
165
202
|
this.runs = new RunRepository(
|
|
166
203
|
deps.storage ?? createStorage(config),
|
|
167
|
-
|
|
204
|
+
repo,
|
|
205
|
+
this.ghRunner,
|
|
168
206
|
);
|
|
169
|
-
this.
|
|
207
|
+
this.editor =
|
|
208
|
+
deps.editor ?? resolveEditor(deps.editorPreference, process.env, repo.root);
|
|
209
|
+
const git = new GitRepository(deps.gitRunner ?? createGitRunner(repo.root));
|
|
210
|
+
this.prs = new PrInbox(this.ghRunner, git);
|
|
170
211
|
}
|
|
171
212
|
|
|
172
213
|
/** Handler de requisições (testável sem abrir porta). */
|
|
173
214
|
readonly handler = async (request: Request): Promise<Response> => {
|
|
174
215
|
try {
|
|
175
|
-
return await route(request, this.runs, this.ghRunner);
|
|
216
|
+
return await route(request, this.runs, this.ghRunner, this.prs, this.editor);
|
|
176
217
|
} catch (error) {
|
|
177
218
|
return json(
|
|
178
219
|
{ error: error instanceof Error ? error.message : String(error) },
|
|
@@ -190,8 +231,8 @@ export class HoundsmanServer {
|
|
|
190
231
|
try {
|
|
191
232
|
this.runs.maintain();
|
|
192
233
|
} catch (error) {
|
|
193
|
-
|
|
194
|
-
`
|
|
234
|
+
logger.warn(
|
|
235
|
+
`VACUUM falhou (seguindo sem compactar): ${
|
|
195
236
|
error instanceof Error ? error.message : String(error)
|
|
196
237
|
}`,
|
|
197
238
|
);
|
|
@@ -222,17 +263,26 @@ export type ServerOptions = {
|
|
|
222
263
|
host?: string;
|
|
223
264
|
/** Runner do `gh` (injetável nos testes). */
|
|
224
265
|
ghRunner?: GhRunner;
|
|
266
|
+
/** Runner do git (injetável nos testes). */
|
|
267
|
+
gitRunner?: GitRunner;
|
|
225
268
|
/** Provider/storage injetável (testes). */
|
|
226
269
|
storage?: StorageProvider;
|
|
227
270
|
repo?: RepoIdentity;
|
|
271
|
+
/** Contexto de editor injetado (testes). */
|
|
272
|
+
editor?: EditorContext;
|
|
273
|
+
/** Preferência do `--editor` (auto|vscode|jetbrains|none). */
|
|
274
|
+
editorPreference?: string;
|
|
228
275
|
};
|
|
229
276
|
|
|
230
277
|
/** Sobe o servidor e devolve a instância + URL. */
|
|
231
278
|
export function startServer(options: ServerOptions): { server: BunServer; url: string } {
|
|
232
279
|
return new HoundsmanServer(options.config, {
|
|
233
280
|
ghRunner: options.ghRunner,
|
|
281
|
+
gitRunner: options.gitRunner,
|
|
234
282
|
storage: options.storage,
|
|
235
283
|
repo: options.repo,
|
|
284
|
+
editor: options.editor,
|
|
285
|
+
editorPreference: options.editorPreference,
|
|
236
286
|
}).start({
|
|
237
287
|
port: options.port,
|
|
238
288
|
host: options.host,
|
|
@@ -294,6 +344,66 @@ code, pre, .mono { font-family:var(--font-mono); tab-size:4; }
|
|
|
294
344
|
.status-review_done { color:var(--suggestion); }
|
|
295
345
|
.status-fix_done { color:var(--success); }
|
|
296
346
|
.status-timeout { color:var(--blocking); }
|
|
347
|
+
.side-tabs { display:flex; gap:4px; padding:4px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
|
|
348
|
+
.side-tab { flex:1; display:inline-flex; align-items:center; justify-content:center; gap:7px; font:inherit; font-size:12px; font-weight:600; cursor:pointer; color:var(--muted);
|
|
349
|
+
padding:7px 10px; background:transparent; border:1px solid transparent; border-radius:999px; transition:color .15s ease, background .15s ease; }
|
|
350
|
+
.side-tab:hover { color:var(--fg); }
|
|
351
|
+
.side-tab.active { color:#1b1305; background:linear-gradient(180deg, var(--accent-strong), var(--accent)); }
|
|
352
|
+
.side-count { padding:1px 6px; font-family:var(--font-mono); font-size:10px; color:var(--muted); background:rgba(255,255,255,.08); border-radius:999px; }
|
|
353
|
+
.side-tab.active .side-count { color:#1b1305; background:rgba(0,0,0,.14); }
|
|
354
|
+
.icon-btn { display:inline-grid; place-items:center; width:26px; height:26px; color:var(--muted); background:transparent; border:1px solid var(--border); border-radius:8px; cursor:pointer; text-decoration:none; }
|
|
355
|
+
.icon-btn:hover { color:var(--fg); border-color:var(--border-strong); }
|
|
356
|
+
.icon-btn:disabled { opacity:.5; cursor:not-allowed; }
|
|
357
|
+
.pr-toolbar { display:flex; align-items:center; justify-content:space-between; gap:8px; }
|
|
358
|
+
.pr-toolbar .section-label { margin:0; }
|
|
359
|
+
.prs { display:flex; flex-direction:column; gap:8px; }
|
|
360
|
+
.pr { display:flex; flex-direction:column; gap:7px; padding:11px 12px; background:var(--card); border:1px solid var(--border); border-radius:var(--radius); }
|
|
361
|
+
.pr.current { background:var(--accent-soft); border-color:var(--accent-line); }
|
|
362
|
+
.pr-head { display:flex; align-items:center; gap:7px; font-family:var(--font-mono); font-size:11px; color:var(--muted); }
|
|
363
|
+
.pr-icon { color:var(--muted-2); }
|
|
364
|
+
.pr.current .pr-icon { color:var(--accent); }
|
|
365
|
+
.pr-number { font-weight:600; color:var(--fg); }
|
|
366
|
+
.pr-current { padding:1px 6px; font-size:9px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--accent); background:rgba(240,165,44,.12); border-radius:999px; }
|
|
367
|
+
.pr-title { margin:0; font-size:12.5px; font-weight:600; line-height:1.4; word-break:break-word; }
|
|
368
|
+
.pr-meta { display:flex; flex-wrap:wrap; align-items:center; gap:1px 5px; margin:0; font-family:var(--font-mono); font-size:10.5px; color:var(--muted-2); }
|
|
369
|
+
.pr-branch { color:var(--suggestion); }
|
|
370
|
+
.pr-review { margin:0; font-size:10.5px; font-weight:600; }
|
|
371
|
+
.pr-review-APPROVED { color:var(--success); }
|
|
372
|
+
.pr-review-CHANGES_REQUESTED { color:var(--blocking); }
|
|
373
|
+
.pr-review-REVIEW_REQUIRED { color:var(--attention); }
|
|
374
|
+
.pr-actions { display:flex; flex-wrap:wrap; gap:6px; margin-top:2px; }
|
|
375
|
+
.pr-btn { display:inline-flex; align-items:center; gap:5px; font:inherit; font-size:10.5px; font-weight:600; cursor:pointer; padding:4px 9px; color:var(--muted);
|
|
376
|
+
background:transparent; border:1px solid var(--border-strong); border-radius:999px; text-decoration:none; }
|
|
377
|
+
.pr-btn:hover { color:var(--fg); border-color:var(--accent-line); }
|
|
378
|
+
.pr-btn:disabled { opacity:.45; cursor:not-allowed; }
|
|
379
|
+
.pr-btn.danger { color:var(--accent); background:var(--accent-soft); border-color:var(--accent-line); }
|
|
380
|
+
.pr-warning { margin:0; padding:8px 10px; font-size:11px; line-height:1.5; color:var(--attention);
|
|
381
|
+
background:rgba(237,161,58,.08); border:1px solid rgba(237,161,58,.24); border-radius:10px; }
|
|
382
|
+
.pr-notice { margin:0; padding:8px 10px; font-size:11px; line-height:1.5; color:var(--success);
|
|
383
|
+
background:rgba(69,192,122,.08); border:1px solid rgba(69,192,122,.24); border-radius:10px; }
|
|
384
|
+
.pr-current-branch { margin:0; font-family:var(--font-mono); font-size:10.5px; color:var(--muted-2); }
|
|
385
|
+
.pr-current-branch code { color:var(--fg); }
|
|
386
|
+
.comments { display:flex; flex-direction:column; gap:18px; }
|
|
387
|
+
.comments-toolbar { display:flex; align-items:center; justify-content:space-between; gap:12px; }
|
|
388
|
+
.comments-counts { display:flex; flex-wrap:wrap; align-items:center; gap:12px; font-size:11.5px; color:var(--muted); }
|
|
389
|
+
.comments-counts b { font-family:var(--font-mono); font-weight:600; color:var(--fg); }
|
|
390
|
+
.comment-group { display:flex; flex-direction:column; gap:10px; }
|
|
391
|
+
.comment-group-title { margin:0; font-size:11px; font-weight:700; letter-spacing:.16em; text-transform:uppercase; color:var(--muted-2); }
|
|
392
|
+
.comment-file-group { display:flex; flex-direction:column; gap:8px; }
|
|
393
|
+
.comment-group-label { display:flex; align-items:center; gap:8px; margin:0; font-family:var(--font-mono); font-size:11.5px; color:var(--suggestion); }
|
|
394
|
+
.comment-thread { display:flex; flex-direction:column; overflow:hidden; background:var(--card); border:1px solid var(--border); border-radius:var(--radius-lg); }
|
|
395
|
+
.comment-thread-head { display:flex; align-items:center; gap:8px; padding:9px 13px; font-family:var(--font-mono); font-size:11px; color:var(--muted);
|
|
396
|
+
background:var(--surface-2); border-bottom:1px solid var(--border); }
|
|
397
|
+
.comment-file { overflow:hidden; color:var(--fg); text-overflow:ellipsis; white-space:nowrap; }
|
|
398
|
+
.pr-comment { display:flex; flex-direction:column; gap:7px; padding:12px 14px; border-top:1px solid var(--border); }
|
|
399
|
+
.comment-thread .pr-comment:first-of-type { border-top:0; }
|
|
400
|
+
.pr-comment.reply { margin-left:26px; background:rgba(255,255,255,.012); border-left:2px solid var(--accent-line); }
|
|
401
|
+
.pr-comment-head { display:flex; align-items:center; gap:9px; font-size:11px; color:var(--muted-2); }
|
|
402
|
+
.pr-comment-author { font-size:12px; font-weight:600; color:var(--fg); }
|
|
403
|
+
.pr-comment-link { display:inline-flex; align-items:center; gap:5px; font-size:11px; color:var(--muted); text-decoration:none; }
|
|
404
|
+
.pr-comment-link:hover { color:var(--accent); }
|
|
405
|
+
.pr-comment-body { font-size:12.5px; line-height:1.6; color:#f0ece6; }
|
|
406
|
+
.pr-comment-body .md-p, .pr-comment-body .md-list { font-size:12.5px; }
|
|
297
407
|
.main { flex:1; min-width:0; display:flex; flex-direction:column; overflow-y:auto; }
|
|
298
408
|
.topbar { position:sticky; top:0; z-index:10; display:flex; align-items:center; justify-content:space-between; gap:16px;
|
|
299
409
|
padding:13px 24px; border-bottom:1px solid var(--border); background:rgba(16,16,20,.74);
|