@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.
Files changed (175) hide show
  1. package/README.md +24 -4
  2. package/assets/grammars/tree-sitter-javascript.wasm +0 -0
  3. package/assets/grammars/tree-sitter-tsx.wasm +0 -0
  4. package/assets/grammars/tree-sitter-typescript.wasm +0 -0
  5. package/assets/grammars/web-tree-sitter.wasm +0 -0
  6. package/package.json +11 -3
  7. package/src/cli/commands/command.ts +5 -0
  8. package/src/cli/commands/doctor-command.ts +116 -24
  9. package/src/cli/commands/hunt-command.ts +25 -0
  10. package/src/cli/commands/server-command.ts +7 -1
  11. package/src/cli/help.ts +18 -2
  12. package/src/cli/index.ts +10 -6
  13. package/src/core/ai/ai-client.ts +221 -0
  14. package/src/core/ai/ai-sdk-model-provider.ts +101 -0
  15. package/src/core/ai/errors.ts +36 -0
  16. package/src/core/ai/features/brief-enricher.ts +62 -0
  17. package/src/core/ai/features/classifier.ts +50 -0
  18. package/src/core/ai/features/context-summarizer.ts +31 -0
  19. package/src/core/ai/features/pre-analyzer.ts +51 -0
  20. package/src/core/ai/features/verifier.ts +45 -0
  21. package/src/core/ai/index.ts +56 -0
  22. package/src/core/ai/model-factory.ts +137 -0
  23. package/src/core/ai/prompts/base.ts +36 -0
  24. package/src/core/ai/prompts/brief.ts +87 -0
  25. package/src/core/ai/prompts/classification.ts +36 -0
  26. package/src/core/ai/prompts/context.ts +29 -0
  27. package/src/core/ai/prompts/pre-analysis.ts +49 -0
  28. package/src/core/ai/prompts/verify.ts +40 -0
  29. package/src/core/ai/schemas.ts +110 -0
  30. package/src/core/ai/types.ts +76 -0
  31. package/src/core/ai/usage-budget.ts +75 -0
  32. package/src/core/ai/usage-recorder.ts +79 -0
  33. package/src/core/assets.ts +16 -0
  34. package/src/core/ast/diff.ts +220 -0
  35. package/src/core/ast/impact.ts +127 -0
  36. package/src/core/ast/parser.ts +167 -0
  37. package/src/core/ast/service.ts +233 -0
  38. package/src/core/ast/symbols.ts +294 -0
  39. package/src/core/ast/types.ts +125 -0
  40. package/src/core/brief.ts +138 -0
  41. package/src/core/commands/findings.ts +65 -2
  42. package/src/core/commands/review.ts +2 -0
  43. package/src/core/commands/scaffold.ts +159 -10
  44. package/src/core/commands/update.ts +5 -3
  45. package/src/core/config.ts +85 -0
  46. package/src/core/defaults.ts +27 -0
  47. package/src/core/domain/watchdog.ts +43 -5
  48. package/src/core/engine/houndsman-engine.ts +16 -1
  49. package/src/core/engine/review-scope.ts +52 -6
  50. package/src/core/engine/review-session.ts +27 -2
  51. package/src/core/env.ts +101 -0
  52. package/src/core/file-classifier.ts +25 -4
  53. package/src/core/git-runner.ts +25 -13
  54. package/src/core/github-review.ts +10 -7
  55. package/src/core/jev/jev-client.ts +8 -7
  56. package/src/core/jev/jev-verify.ts +2 -1
  57. package/src/core/logging/index.ts +17 -0
  58. package/src/core/logging/logger.ts +187 -0
  59. package/src/core/orchestrator.ts +357 -9
  60. package/src/core/project-config.ts +123 -1
  61. package/src/core/registry-check.ts +148 -6
  62. package/src/core/renderers/manifest-writer.ts +33 -0
  63. package/src/core/rule-text.ts +97 -0
  64. package/src/core/services/file-classifier.ts +10 -3
  65. package/src/core/services/gh-client.ts +38 -22
  66. package/src/core/services/verifier.ts +14 -3
  67. package/src/core/storage/paths.ts +6 -5
  68. package/src/core/storage/types.ts +6 -0
  69. package/src/core/storage/work-area.ts +12 -0
  70. package/src/core/types.ts +37 -1
  71. package/src/core/verify.ts +17 -5
  72. package/src/harness/README.md +15 -0
  73. package/src/harness/agent/agent-context.ts +33 -0
  74. package/src/harness/agent/agent-errors.ts +30 -0
  75. package/src/harness/agent/agent-factory.ts +45 -0
  76. package/src/harness/agent/agent-reporter.ts +34 -0
  77. package/src/harness/agent/agent-result.ts +16 -0
  78. package/src/harness/agent/agent-runner.ts +32 -0
  79. package/src/harness/agent/agent-scheduler.ts +37 -0
  80. package/src/harness/agent/review-agent.ts +391 -0
  81. package/src/harness/config/harness-config.ts +84 -0
  82. package/src/harness/hunt/hunt-harness.ts +83 -0
  83. package/src/harness/hunt/hunt-options.ts +75 -0
  84. package/src/harness/hunt/hunt-result.ts +17 -0
  85. package/src/harness/hunt/hunt-session.ts +372 -0
  86. package/src/harness/hunt/review-planner.ts +98 -0
  87. package/src/harness/index.ts +36 -0
  88. package/src/harness/investigation/agent-state.ts +134 -0
  89. package/src/harness/investigation/evidence-verifier.ts +129 -0
  90. package/src/harness/investigation/evidence.ts +110 -0
  91. package/src/harness/investigation/finding-critic.ts +96 -0
  92. package/src/harness/investigation/investigation-planner.ts +118 -0
  93. package/src/harness/investigation/investigation-task.ts +10 -0
  94. package/src/harness/investigation/review-journal.ts +87 -0
  95. package/src/harness/investigation/rule-analyzer.ts +178 -0
  96. package/src/harness/investigation/rule-signals.ts +110 -0
  97. package/src/harness/prompts/agent-schemas.ts +57 -0
  98. package/src/harness/prompts/reviewer-prompt-builder.ts +101 -0
  99. package/src/harness/prompts/states/conclude-prompt.ts +16 -0
  100. package/src/harness/prompts/states/investigate-prompt.ts +37 -0
  101. package/src/harness/prompts/states/orient-prompt.ts +15 -0
  102. package/src/harness/prompts/states/verify-prompt.ts +22 -0
  103. package/src/harness/tools/agent-tool.ts +102 -0
  104. package/src/harness/tools/builtin/changed-symbols-tool.ts +48 -0
  105. package/src/harness/tools/builtin/doc-context-tool.ts +74 -0
  106. package/src/harness/tools/builtin/file-outline-tool.ts +66 -0
  107. package/src/harness/tools/builtin/find-references-tool.ts +66 -0
  108. package/src/harness/tools/builtin/find-symbol-tool.ts +91 -0
  109. package/src/harness/tools/builtin/helpers.ts +115 -0
  110. package/src/harness/tools/builtin/impact-tool.ts +48 -0
  111. package/src/harness/tools/builtin/imports-exports-tool.ts +53 -0
  112. package/src/harness/tools/builtin/index.ts +59 -0
  113. package/src/harness/tools/builtin/list-files-tool.ts +85 -0
  114. package/src/harness/tools/builtin/pr-context-tool.ts +41 -0
  115. package/src/harness/tools/builtin/read-file-range-tool.ts +73 -0
  116. package/src/harness/tools/builtin/rule-signals-tool.ts +36 -0
  117. package/src/harness/tools/builtin/search-code-tool.ts +65 -0
  118. package/src/harness/tools/builtin/symbol-context-tool.ts +106 -0
  119. package/src/harness/tools/builtin/tests-for-symbol-tool.ts +61 -0
  120. package/src/harness/tools/path-guard.ts +90 -0
  121. package/src/harness/tools/result-limiter.ts +49 -0
  122. package/src/harness/tools/secret-redactor.ts +54 -0
  123. package/src/harness/tools/tool-cache.ts +50 -0
  124. package/src/harness/tools/tool-call-budget.ts +60 -0
  125. package/src/harness/tools/tool-context.ts +5 -0
  126. package/src/harness/tools/tool-middleware.ts +105 -0
  127. package/src/harness/tools/tool-policy.ts +51 -0
  128. package/src/harness/tools/tool-registry.ts +112 -0
  129. package/src/harness/tools/tool-result.ts +15 -0
  130. package/src/harness/tools/toolbox.ts +36 -0
  131. package/src/harness/validation/finding-validator.ts +119 -0
  132. package/src/index.ts +46 -0
  133. package/src/server/client/App.tsx +38 -12
  134. package/src/server/client/CommentsPanel.tsx +255 -0
  135. package/src/server/client/FindingCard.tsx +12 -4
  136. package/src/server/client/FindingsList.tsx +4 -1
  137. package/src/server/client/Sidebar.tsx +397 -0
  138. package/src/server/client/SubmitModal.tsx +61 -5
  139. package/src/server/client/icons.tsx +19 -0
  140. package/src/server/client/index.tsx +1 -1
  141. package/src/server/client/utils/constants.ts +29 -5
  142. package/src/server/client/utils/format.ts +47 -2
  143. package/src/server/client/utils/types.ts +88 -0
  144. package/src/server/editor.ts +129 -0
  145. package/src/server/index.ts +115 -5
  146. package/src/server/pr-comments.ts +256 -0
  147. package/src/server/pr-inbox.ts +202 -0
  148. package/src/server/run-repository.ts +33 -1
  149. package/src/skill/OUTPUT-FORMAT.md +12 -0
  150. package/src/skill/SETUP.md +6 -2
  151. package/src/skill/hunt.md +14 -2
  152. package/src/skill/verify.md +1 -1
  153. package/src/tests/__snapshots__/baseline.test.ts.snap +96 -14
  154. package/src/tests/ai-core.test.ts +120 -0
  155. package/src/tests/ai-features.test.ts +183 -0
  156. package/src/tests/ast.test.ts +335 -0
  157. package/src/tests/cli.test.ts +33 -0
  158. package/src/tests/env.test.ts +69 -0
  159. package/src/tests/fallback.test.ts +172 -0
  160. package/src/tests/fixtures/fake-agent-tools.ts +69 -0
  161. package/src/tests/fixtures/fake-investigation.ts +64 -0
  162. package/src/tests/fixtures/fake-model-provider.ts +48 -0
  163. package/src/tests/github-review.test.ts +9 -0
  164. package/src/tests/harness-agent.test.ts +122 -0
  165. package/src/tests/harness-investigation.test.ts +206 -0
  166. package/src/tests/harness-tools.test.ts +148 -0
  167. package/src/tests/hunt-command.test.ts +212 -0
  168. package/src/tests/logger.test.ts +83 -0
  169. package/src/tests/review-scope.test.ts +11 -11
  170. package/src/tests/rule-analyzer.test.ts +61 -0
  171. package/src/tests/server-editor.test.ts +100 -0
  172. package/src/tests/server.test.ts +295 -2
  173. package/src/tests/tool-contracts.test.ts +82 -0
  174. package/src/tests/verify.test.ts +1 -1
  175. 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 { RunList } from "./RunList";
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
- <RunList runs={runs} slug={slug} onSelect={setSlug} />
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 === "resume" ? (
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 { vscodeFileLink } from "./utils/format";
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 ? vscodeFileLink(finding.absFile, finding.line) : null;
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 class="loc" href={href} title={`Abrir ${location} no VS Code`}>
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
  ))}