@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
package/src/index.ts
CHANGED
|
@@ -5,13 +5,53 @@
|
|
|
5
5
|
* (`houndsman.config.ts`): watchdogs, tipos e helpers de autoria.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
export type {
|
|
9
|
+
AiResult,
|
|
10
|
+
AiTool,
|
|
11
|
+
AiUsage,
|
|
12
|
+
ModelProvider,
|
|
13
|
+
ModelRequest,
|
|
14
|
+
ModelResponse,
|
|
15
|
+
} from "./core/ai";
|
|
16
|
+
export {
|
|
17
|
+
AiBriefEnricher,
|
|
18
|
+
AiClassifier,
|
|
19
|
+
AiClient,
|
|
20
|
+
AiContextSummarizer,
|
|
21
|
+
AiPreAnalyzer,
|
|
22
|
+
AiUsageBudget,
|
|
23
|
+
AiUsageRecorder,
|
|
24
|
+
AiVerifier,
|
|
25
|
+
ModelFactory,
|
|
26
|
+
} from "./core/ai";
|
|
8
27
|
export { ArgParser } from "./core/arg-parser";
|
|
9
28
|
export { assetPath, OUTPUT_FORMAT_FILE } from "./core/assets";
|
|
29
|
+
export { computeDelta, emptyDelta, parseHunks } from "./core/ast/diff";
|
|
30
|
+
export { buildImpact, resolveImport } from "./core/ast/impact";
|
|
31
|
+
export { AstParser, languageForPath } from "./core/ast/parser";
|
|
32
|
+
export { AstService } from "./core/ast/service";
|
|
33
|
+
export type {
|
|
34
|
+
AstAnalysis,
|
|
35
|
+
AstDelta,
|
|
36
|
+
AstEntry,
|
|
37
|
+
AstExport,
|
|
38
|
+
AstFacts,
|
|
39
|
+
AstFileBrief,
|
|
40
|
+
AstImpact,
|
|
41
|
+
AstImport,
|
|
42
|
+
AstLanguage,
|
|
43
|
+
AstSymbol,
|
|
44
|
+
AstSymbolKind,
|
|
45
|
+
} from "./core/ast/types";
|
|
10
46
|
export { defaultRenderBrief, PROCEDURE, renderBrief } from "./core/brief";
|
|
11
47
|
export type { FindingsCommandResult } from "./core/commands/findings";
|
|
12
48
|
export { runFindingsCommand, runVerifyCommand } from "./core/commands/findings";
|
|
13
49
|
export { ProjectScaffolder } from "./core/commands/scaffold";
|
|
14
50
|
export type {
|
|
51
|
+
AiProviderKind,
|
|
52
|
+
HoundsmanAi,
|
|
53
|
+
HoundsmanAiFeatures,
|
|
54
|
+
HoundsmanAst,
|
|
15
55
|
HoundsmanBudgets,
|
|
16
56
|
HoundsmanConcurrency,
|
|
17
57
|
HoundsmanConfig,
|
|
@@ -20,6 +60,8 @@ export type {
|
|
|
20
60
|
} from "./core/config";
|
|
21
61
|
export {
|
|
22
62
|
DEFAULT_ALWAYS_INCLUDE_DOCS,
|
|
63
|
+
DEFAULT_AST_MAX_FILE_BYTES,
|
|
64
|
+
DEFAULT_AST_MAX_IMPACT_FILES,
|
|
23
65
|
DEFAULT_JEV_CONCURRENCY,
|
|
24
66
|
DEFAULT_MAX_FILES_PER_BRIEF,
|
|
25
67
|
defineHoundsmanConfig,
|
|
@@ -47,6 +89,8 @@ export { buildCarryover, mergeCarryover } from "./core/engine/carryover";
|
|
|
47
89
|
export { HoundsmanEngine } from "./core/engine/houndsman-engine";
|
|
48
90
|
export { ReviewScopeResolver } from "./core/engine/review-scope";
|
|
49
91
|
export { ReviewSession } from "./core/engine/review-session";
|
|
92
|
+
export type { HoundsmanEnv } from "./core/env";
|
|
93
|
+
export { ENV_NAMES, env, readEnv } from "./core/env";
|
|
50
94
|
export {
|
|
51
95
|
churnOf,
|
|
52
96
|
classifyEntries,
|
|
@@ -159,6 +203,7 @@ export type {
|
|
|
159
203
|
FileStatus,
|
|
160
204
|
FileStatusMap,
|
|
161
205
|
Rule,
|
|
206
|
+
RuleHints,
|
|
162
207
|
Severity,
|
|
163
208
|
WatchdogDefinition,
|
|
164
209
|
WatchdogMeta,
|
|
@@ -172,6 +217,7 @@ export {
|
|
|
172
217
|
SEVERITY_ORDER,
|
|
173
218
|
severityRank,
|
|
174
219
|
} from "./core/types";
|
|
220
|
+
export { applyConfidence, applyJev, summarizeVerify, verifyLocal } from "./core/verify";
|
|
175
221
|
export {
|
|
176
222
|
findWatchdog,
|
|
177
223
|
getWatchdog,
|
|
@@ -7,18 +7,19 @@
|
|
|
7
7
|
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
|
8
8
|
import { ArtifactsPanel } from "./ArtifactsPanel";
|
|
9
9
|
import { AutoRefresh } from "./AutoRefresh";
|
|
10
|
+
import { CommentsPanel } from "./CommentsPanel";
|
|
10
11
|
import { Filters, type FiltersState } from "./Filters";
|
|
11
12
|
import { FindingsList } from "./FindingsList";
|
|
12
|
-
import { FileText, Layers, ListIcon, Refresh, Send } from "./icons";
|
|
13
|
+
import { FileText, Layers, ListIcon, MessageSquare, Refresh, Send } from "./icons";
|
|
13
14
|
import { ResumePanel } from "./ResumePanel";
|
|
14
|
-
import {
|
|
15
|
+
import { Sidebar } from "./Sidebar";
|
|
15
16
|
import { SubmitModal } from "./SubmitModal";
|
|
16
17
|
import { SummaryBar } from "./SummaryBar";
|
|
17
18
|
import { api, computeSummary } from "./utils/api";
|
|
18
19
|
import { AUTO_REFRESH_MS, TAB_CAPTIONS } from "./utils/constants";
|
|
19
|
-
import type { Finding, FindingsResponse, RunSummary } from "./utils/types";
|
|
20
|
+
import type { EditorContext, Finding, FindingsResponse, RunSummary } from "./utils/types";
|
|
20
21
|
|
|
21
|
-
type Tab = "resume" | "findings" | "artifacts";
|
|
22
|
+
type Tab = "resume" | "findings" | "artifacts" | "comments";
|
|
22
23
|
|
|
23
24
|
const TABS: { id: Tab; label: string; hint: string }[] = [
|
|
24
25
|
{
|
|
@@ -36,8 +37,20 @@ const TABS: { id: Tab; label: string; hint: string }[] = [
|
|
|
36
37
|
label: "Artefatos",
|
|
37
38
|
hint: "Arquivos brutos gerados pela revisão (manifesto, classificação, relatório…)",
|
|
38
39
|
},
|
|
40
|
+
{
|
|
41
|
+
id: "comments",
|
|
42
|
+
label: "Comentários",
|
|
43
|
+
hint: "Comentários da PR: threads de revisão e comentários gerais (carregados ao abrir a aba)",
|
|
44
|
+
},
|
|
39
45
|
];
|
|
40
46
|
|
|
47
|
+
function TabIcon({ id }: { id: Tab }) {
|
|
48
|
+
if (id === "resume") return <FileText size={15} />;
|
|
49
|
+
if (id === "findings") return <ListIcon size={15} />;
|
|
50
|
+
if (id === "artifacts") return <Layers size={15} />;
|
|
51
|
+
return <MessageSquare size={15} />;
|
|
52
|
+
}
|
|
53
|
+
|
|
41
54
|
export function App() {
|
|
42
55
|
const [runs, setRuns] = useState<RunSummary[]>([]);
|
|
43
56
|
const [slug, setSlug] = useState<string | null>(null);
|
|
@@ -54,6 +67,8 @@ export function App() {
|
|
|
54
67
|
const [refreshing, setRefreshing] = useState(false);
|
|
55
68
|
const [syncing, setSyncing] = useState(false);
|
|
56
69
|
const [submitOpen, setSubmitOpen] = useState(false);
|
|
70
|
+
/** Editor detectado pelo servidor — base dos links de código. */
|
|
71
|
+
const [editor, setEditor] = useState<EditorContext>({ kind: "vscode" });
|
|
57
72
|
/** Refs espelhando estado volátil — o polling não deve recriar o intervalo. */
|
|
58
73
|
const lastSyncRef = useRef(lastSync);
|
|
59
74
|
const slugRef = useRef<string | null>(null);
|
|
@@ -116,6 +131,14 @@ export function App() {
|
|
|
116
131
|
reloadRuns();
|
|
117
132
|
}, [reloadRuns]);
|
|
118
133
|
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
api<EditorContext>("/api/editor")
|
|
136
|
+
.then(setEditor)
|
|
137
|
+
.catch(() => {
|
|
138
|
+
// fail-open: mantém o fallback VS Code.
|
|
139
|
+
});
|
|
140
|
+
}, []);
|
|
141
|
+
|
|
119
142
|
useEffect(() => {
|
|
120
143
|
if (slug) loadFindings(slug);
|
|
121
144
|
}, [slug, loadFindings]);
|
|
@@ -194,7 +217,12 @@ export function App() {
|
|
|
194
217
|
|
|
195
218
|
return (
|
|
196
219
|
<div class="app">
|
|
197
|
-
<
|
|
220
|
+
<Sidebar
|
|
221
|
+
runs={runs}
|
|
222
|
+
slug={slug}
|
|
223
|
+
onSelect={setSlug}
|
|
224
|
+
onRefreshRuns={() => void reloadRuns()}
|
|
225
|
+
/>
|
|
198
226
|
<main class="main">
|
|
199
227
|
<header class="topbar">
|
|
200
228
|
<div class="segmented">
|
|
@@ -206,13 +234,7 @@ export function App() {
|
|
|
206
234
|
class={`seg-btn${tab === item.id ? " active" : ""}`}
|
|
207
235
|
onClick={() => setTab(item.id)}
|
|
208
236
|
>
|
|
209
|
-
{item.id
|
|
210
|
-
<FileText size={15} />
|
|
211
|
-
) : item.id === "findings" ? (
|
|
212
|
-
<ListIcon size={15} />
|
|
213
|
-
) : (
|
|
214
|
-
<Layers size={15} />
|
|
215
|
-
)}
|
|
237
|
+
<TabIcon id={item.id} />
|
|
216
238
|
{item.label}
|
|
217
239
|
</button>
|
|
218
240
|
))}
|
|
@@ -278,11 +300,15 @@ export function App() {
|
|
|
278
300
|
<FindingsList
|
|
279
301
|
slug={slug}
|
|
280
302
|
findings={findings}
|
|
303
|
+
editor={editor}
|
|
281
304
|
onUpdated={onUpdated}
|
|
282
305
|
/>
|
|
283
306
|
</>
|
|
284
307
|
)}
|
|
285
308
|
{slug && tab === "artifacts" && <ArtifactsPanel slug={slug} />}
|
|
309
|
+
{slug && tab === "comments" && (
|
|
310
|
+
<CommentsPanel slug={slug} editor={editor} />
|
|
311
|
+
)}
|
|
286
312
|
{!slug && !error && <p class="empty">Nenhuma execução para triar.</p>}
|
|
287
313
|
</div>
|
|
288
314
|
{slug && (
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aba "Comentários": threads de revisão e comentários gerais da PR da run.
|
|
3
|
+
*
|
|
4
|
+
* O componente só é montado quando a aba é acessada — o carregamento (e a
|
|
5
|
+
* chamada ao `gh`) acontece no mount, nunca no carregamento da página.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
|
|
9
|
+
import { Clock, CodeIcon, ExternalLink, GitHub, MessageSquare, Refresh } from "./icons";
|
|
10
|
+
import { MarkdownText } from "./MarkdownText";
|
|
11
|
+
import { api } from "./utils/api";
|
|
12
|
+
import { editorFileLink, editorLabel, formatRunTime } from "./utils/format";
|
|
13
|
+
import type {
|
|
14
|
+
EditorContext,
|
|
15
|
+
PrCommentsResponse,
|
|
16
|
+
PrCommentThread,
|
|
17
|
+
PrCommentView,
|
|
18
|
+
} from "./utils/types";
|
|
19
|
+
|
|
20
|
+
function CommentCard({
|
|
21
|
+
comment,
|
|
22
|
+
editor,
|
|
23
|
+
reply,
|
|
24
|
+
}: {
|
|
25
|
+
comment: PrCommentView;
|
|
26
|
+
editor: EditorContext;
|
|
27
|
+
reply: boolean;
|
|
28
|
+
}) {
|
|
29
|
+
const location = comment.path
|
|
30
|
+
? comment.line
|
|
31
|
+
? `${comment.path}:${comment.line}`
|
|
32
|
+
: comment.path
|
|
33
|
+
: null;
|
|
34
|
+
const href = comment.absFile
|
|
35
|
+
? editorFileLink(comment.absFile, comment.line, editor)
|
|
36
|
+
: null;
|
|
37
|
+
return (
|
|
38
|
+
<article class={`pr-comment${reply ? " reply" : ""}`}>
|
|
39
|
+
<header class="pr-comment-head">
|
|
40
|
+
<span class="pr-comment-author">{comment.author}</span>
|
|
41
|
+
{comment.authorType === "Bot" && <span class="tag">bot</span>}
|
|
42
|
+
<span class="run-when">
|
|
43
|
+
<Clock size={12} />
|
|
44
|
+
{formatRunTime(comment.createdAt)}
|
|
45
|
+
</span>
|
|
46
|
+
<span class="spacer" />
|
|
47
|
+
{comment.url && (
|
|
48
|
+
<a
|
|
49
|
+
class="pr-comment-link"
|
|
50
|
+
href={comment.url}
|
|
51
|
+
target="_blank"
|
|
52
|
+
rel="noreferrer"
|
|
53
|
+
title="Abrir o comentário no GitHub"
|
|
54
|
+
>
|
|
55
|
+
<GitHub size={12} /> GitHub
|
|
56
|
+
</a>
|
|
57
|
+
)}
|
|
58
|
+
</header>
|
|
59
|
+
<div class="pr-comment-body">
|
|
60
|
+
<MarkdownText text={comment.body} />
|
|
61
|
+
</div>
|
|
62
|
+
{location &&
|
|
63
|
+
(href ? (
|
|
64
|
+
<a
|
|
65
|
+
class="loc"
|
|
66
|
+
href={href}
|
|
67
|
+
title={`Abrir ${location} no ${editorLabel(editor)}`}
|
|
68
|
+
>
|
|
69
|
+
<CodeIcon size={13} />
|
|
70
|
+
{location}
|
|
71
|
+
<ExternalLink size={12} class="ext" />
|
|
72
|
+
</a>
|
|
73
|
+
) : (
|
|
74
|
+
<code class="loc static">
|
|
75
|
+
<CodeIcon size={13} />
|
|
76
|
+
{location}
|
|
77
|
+
</code>
|
|
78
|
+
))}
|
|
79
|
+
</article>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function ThreadCard({
|
|
84
|
+
thread,
|
|
85
|
+
editor,
|
|
86
|
+
}: {
|
|
87
|
+
thread: PrCommentThread;
|
|
88
|
+
editor: EditorContext;
|
|
89
|
+
}) {
|
|
90
|
+
const location = thread.line
|
|
91
|
+
? `${thread.path}:${thread.line}`
|
|
92
|
+
: thread.path || "arquivo";
|
|
93
|
+
const href = thread.absFile
|
|
94
|
+
? editorFileLink(thread.absFile, thread.line, editor)
|
|
95
|
+
: null;
|
|
96
|
+
return (
|
|
97
|
+
<section class="comment-thread">
|
|
98
|
+
<header class="comment-thread-head">
|
|
99
|
+
<CodeIcon size={13} />
|
|
100
|
+
<span class="comment-file">{location}</span>
|
|
101
|
+
<span class="spacer" />
|
|
102
|
+
{href && (
|
|
103
|
+
<a
|
|
104
|
+
class="icon-btn"
|
|
105
|
+
href={href}
|
|
106
|
+
title={`Abrir ${location} no ${editorLabel(editor)}`}
|
|
107
|
+
>
|
|
108
|
+
<ExternalLink size={13} />
|
|
109
|
+
</a>
|
|
110
|
+
)}
|
|
111
|
+
<span class="count">{thread.comments.length}</span>
|
|
112
|
+
</header>
|
|
113
|
+
{thread.comments.map((comment, index) => (
|
|
114
|
+
<CommentCard
|
|
115
|
+
key={comment.id}
|
|
116
|
+
comment={comment}
|
|
117
|
+
editor={editor}
|
|
118
|
+
reply={index > 0}
|
|
119
|
+
/>
|
|
120
|
+
))}
|
|
121
|
+
</section>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function CommentsPanel({ slug, editor }: { slug: string; editor: EditorContext }) {
|
|
126
|
+
const [data, setData] = useState<PrCommentsResponse | null>(null);
|
|
127
|
+
const [loading, setLoading] = useState(true);
|
|
128
|
+
const [error, setError] = useState("");
|
|
129
|
+
|
|
130
|
+
const load = useCallback(
|
|
131
|
+
async (refresh = false) => {
|
|
132
|
+
setLoading(true);
|
|
133
|
+
setError("");
|
|
134
|
+
try {
|
|
135
|
+
const result = await api<PrCommentsResponse>(
|
|
136
|
+
`/api/runs/${slug}/comments${refresh ? "?refresh=1" : ""}`,
|
|
137
|
+
);
|
|
138
|
+
setData(result);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
setError(String(err));
|
|
141
|
+
} finally {
|
|
142
|
+
setLoading(false);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
[slug],
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
setData(null);
|
|
150
|
+
void load();
|
|
151
|
+
}, [load]);
|
|
152
|
+
|
|
153
|
+
const groups = useMemo(() => {
|
|
154
|
+
const map = new Map<string, PrCommentThread[]>();
|
|
155
|
+
for (const thread of data?.threads ?? []) {
|
|
156
|
+
const key = thread.path || "(sem arquivo)";
|
|
157
|
+
const list = map.get(key) ?? [];
|
|
158
|
+
list.push(thread);
|
|
159
|
+
map.set(key, list);
|
|
160
|
+
}
|
|
161
|
+
return [...map.entries()];
|
|
162
|
+
}, [data]);
|
|
163
|
+
|
|
164
|
+
const empty =
|
|
165
|
+
data?.available && data.threads.length === 0 && data.issueComments.length === 0;
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<div class="comments">
|
|
169
|
+
<div class="comments-toolbar">
|
|
170
|
+
<div class="comments-counts">
|
|
171
|
+
<MessageSquare size={14} />
|
|
172
|
+
{data?.available ? (
|
|
173
|
+
<>
|
|
174
|
+
<span>
|
|
175
|
+
<b>{data.counts.threads}</b> threads
|
|
176
|
+
</span>
|
|
177
|
+
<span>
|
|
178
|
+
<b>{data.counts.inline}</b> inline
|
|
179
|
+
</span>
|
|
180
|
+
<span>
|
|
181
|
+
<b>{data.counts.issue}</b> gerais
|
|
182
|
+
</span>
|
|
183
|
+
</>
|
|
184
|
+
) : (
|
|
185
|
+
<span>Comentários da PR</span>
|
|
186
|
+
)}
|
|
187
|
+
{data?.target?.url && (
|
|
188
|
+
<a
|
|
189
|
+
class="pr-comment-link"
|
|
190
|
+
href={data.target.url}
|
|
191
|
+
target="_blank"
|
|
192
|
+
rel="noreferrer"
|
|
193
|
+
>
|
|
194
|
+
<GitHub size={12} /> PR #{data.target.number}
|
|
195
|
+
</a>
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
<button
|
|
199
|
+
type="button"
|
|
200
|
+
class="btn"
|
|
201
|
+
disabled={loading}
|
|
202
|
+
title="Recarrega os comentários ignorando o cache de 60s"
|
|
203
|
+
onClick={() => void load(true)}
|
|
204
|
+
>
|
|
205
|
+
<Refresh size={14} class={loading ? "icon spin" : undefined} />
|
|
206
|
+
Atualizar
|
|
207
|
+
</button>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
{loading && !data && <p class="empty">Carregando comentários da PR…</p>}
|
|
211
|
+
{error && <p class="error">{error}</p>}
|
|
212
|
+
{data && !data.available && (
|
|
213
|
+
<p class="empty">
|
|
214
|
+
Comentários indisponíveis
|
|
215
|
+
{data.reason ? `: ${data.reason}` : "."}
|
|
216
|
+
</p>
|
|
217
|
+
)}
|
|
218
|
+
{empty && <p class="empty">Nenhum comentário nesta PR.</p>}
|
|
219
|
+
|
|
220
|
+
{data?.available && groups.length > 0 && (
|
|
221
|
+
<section class="comment-group">
|
|
222
|
+
<h2 class="comment-group-title">Comentários de revisão</h2>
|
|
223
|
+
{groups.map(([path, threads]) => (
|
|
224
|
+
<div class="comment-file-group" key={path}>
|
|
225
|
+
<p class="comment-group-label">
|
|
226
|
+
{path} <span class="count">{threads.length}</span>
|
|
227
|
+
</p>
|
|
228
|
+
{threads.map((thread) => (
|
|
229
|
+
<ThreadCard
|
|
230
|
+
key={thread.id}
|
|
231
|
+
thread={thread}
|
|
232
|
+
editor={editor}
|
|
233
|
+
/>
|
|
234
|
+
))}
|
|
235
|
+
</div>
|
|
236
|
+
))}
|
|
237
|
+
</section>
|
|
238
|
+
)}
|
|
239
|
+
|
|
240
|
+
{data?.available && data.issueComments.length > 0 && (
|
|
241
|
+
<section class="comment-group">
|
|
242
|
+
<h2 class="comment-group-title">Comentários gerais</h2>
|
|
243
|
+
{data.issueComments.map((comment) => (
|
|
244
|
+
<CommentCard
|
|
245
|
+
key={comment.id}
|
|
246
|
+
comment={comment}
|
|
247
|
+
editor={editor}
|
|
248
|
+
reply={false}
|
|
249
|
+
/>
|
|
250
|
+
))}
|
|
251
|
+
</section>
|
|
252
|
+
)}
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
@@ -21,8 +21,8 @@ import { MarkdownText } from "./MarkdownText";
|
|
|
21
21
|
import { api } from "./utils/api";
|
|
22
22
|
import { highlightCode, languageFromFile } from "./utils/code-highlight";
|
|
23
23
|
import { DECISION_LABELS, DECISIONS, SEVERITY_LABELS } from "./utils/constants";
|
|
24
|
-
import {
|
|
25
|
-
import type { Finding } from "./utils/types";
|
|
24
|
+
import { editorFileLink, editorLabel } from "./utils/format";
|
|
25
|
+
import type { EditorContext, Finding } from "./utils/types";
|
|
26
26
|
|
|
27
27
|
/** Debounce do auto-save do comentário (ms). */
|
|
28
28
|
const AUTOSAVE_MS = 700;
|
|
@@ -32,10 +32,12 @@ const SAVED_VISIBLE_MS = 1800;
|
|
|
32
32
|
export function FindingCard({
|
|
33
33
|
slug,
|
|
34
34
|
finding,
|
|
35
|
+
editor,
|
|
35
36
|
onUpdated,
|
|
36
37
|
}: {
|
|
37
38
|
slug: string;
|
|
38
39
|
finding: Finding;
|
|
40
|
+
editor: EditorContext;
|
|
39
41
|
onUpdated: (finding: Finding) => void;
|
|
40
42
|
}) {
|
|
41
43
|
const isDone = finding.execState === "done";
|
|
@@ -104,7 +106,9 @@ export function FindingCard({
|
|
|
104
106
|
}, [comment, patch]);
|
|
105
107
|
|
|
106
108
|
const location = finding.line ? `${finding.file}:${finding.line}` : finding.file;
|
|
107
|
-
const href = finding.absFile
|
|
109
|
+
const href = finding.absFile
|
|
110
|
+
? editorFileLink(finding.absFile, finding.line, editor)
|
|
111
|
+
: null;
|
|
108
112
|
const highlighted = useMemo(
|
|
109
113
|
() =>
|
|
110
114
|
finding.evidence
|
|
@@ -132,7 +136,11 @@ export function FindingCard({
|
|
|
132
136
|
</button>
|
|
133
137
|
<span class="sev-badge">{SEVERITY_LABELS[finding.severity]}</span>
|
|
134
138
|
{href ? (
|
|
135
|
-
<a
|
|
139
|
+
<a
|
|
140
|
+
class="loc"
|
|
141
|
+
href={href}
|
|
142
|
+
title={`Abrir ${location} no ${editorLabel(editor)}`}
|
|
143
|
+
>
|
|
136
144
|
<CodeIcon size={13} />
|
|
137
145
|
{location}
|
|
138
146
|
<ExternalLink size={12} class="ext" />
|
|
@@ -6,15 +6,17 @@ import { useMemo } from "preact/hooks";
|
|
|
6
6
|
import { FindingCard } from "./FindingCard";
|
|
7
7
|
import { Alert } from "./icons";
|
|
8
8
|
import { SEVERITY_LABELS, SEVERITY_ORDER } from "./utils/constants";
|
|
9
|
-
import type { Finding, Severity } from "./utils/types";
|
|
9
|
+
import type { EditorContext, Finding, Severity } from "./utils/types";
|
|
10
10
|
|
|
11
11
|
export function FindingsList({
|
|
12
12
|
slug,
|
|
13
13
|
findings,
|
|
14
|
+
editor,
|
|
14
15
|
onUpdated,
|
|
15
16
|
}: {
|
|
16
17
|
slug: string;
|
|
17
18
|
findings: Finding[];
|
|
19
|
+
editor: EditorContext;
|
|
18
20
|
onUpdated: (finding: Finding) => void;
|
|
19
21
|
}) {
|
|
20
22
|
const grouped = useMemo(() => {
|
|
@@ -45,6 +47,7 @@ export function FindingsList({
|
|
|
45
47
|
key={finding.id}
|
|
46
48
|
slug={slug}
|
|
47
49
|
finding={finding}
|
|
50
|
+
editor={editor}
|
|
48
51
|
onUpdated={onUpdated}
|
|
49
52
|
/>
|
|
50
53
|
))}
|