@payconductor/houndsman 0.4.0 → 0.6.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 (196) hide show
  1. package/README.md +35 -41
  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/status-command.ts +46 -0
  11. package/src/cli/help.ts +18 -1
  12. package/src/cli/index.ts +13 -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 +120 -6
  42. package/src/core/commands/review.ts +2 -0
  43. package/src/core/commands/scaffold.ts +159 -10
  44. package/src/core/commands/status.ts +63 -0
  45. package/src/core/commands/update.ts +5 -3
  46. package/src/core/config.ts +85 -0
  47. package/src/core/defaults.ts +27 -0
  48. package/src/core/domain/watchdog.ts +43 -5
  49. package/src/core/engine/houndsman-engine.ts +16 -1
  50. package/src/core/engine/review-scope.ts +77 -13
  51. package/src/core/engine/review-session.ts +27 -2
  52. package/src/core/env.ts +101 -0
  53. package/src/core/file-classifier.ts +25 -4
  54. package/src/core/findings-state.ts +21 -4
  55. package/src/core/github-review.ts +620 -57
  56. package/src/core/jev/jev-client.ts +8 -7
  57. package/src/core/jev/jev-verify.ts +2 -1
  58. package/src/core/logging/index.ts +17 -0
  59. package/src/core/logging/logger.ts +187 -0
  60. package/src/core/orchestrator.ts +454 -16
  61. package/src/core/paths.ts +3 -0
  62. package/src/core/project-config.ts +123 -1
  63. package/src/core/registry-check.ts +148 -6
  64. package/src/core/renderers/manifest-writer.ts +33 -0
  65. package/src/core/renderers/resume-renderer.ts +151 -0
  66. package/src/core/report-builder.ts +7 -4
  67. package/src/core/rule-text.ts +97 -0
  68. package/src/core/run-status.ts +39 -5
  69. package/src/core/services/file-classifier.ts +10 -3
  70. package/src/core/services/verifier.ts +14 -3
  71. package/src/core/storage/file/file-storage.ts +2 -0
  72. package/src/core/storage/paths.ts +6 -5
  73. package/src/core/storage/sqlite/mappers.ts +2 -0
  74. package/src/core/storage/sqlite/schema.ts +9 -1
  75. package/src/core/storage/sqlite/sqlite-storage.ts +5 -1
  76. package/src/core/storage/types.ts +15 -0
  77. package/src/core/storage/work-area.ts +12 -0
  78. package/src/core/types.ts +37 -1
  79. package/src/core/verify.ts +17 -5
  80. package/src/harness/README.md +15 -0
  81. package/src/harness/agent/agent-context.ts +33 -0
  82. package/src/harness/agent/agent-errors.ts +30 -0
  83. package/src/harness/agent/agent-factory.ts +45 -0
  84. package/src/harness/agent/agent-reporter.ts +34 -0
  85. package/src/harness/agent/agent-result.ts +16 -0
  86. package/src/harness/agent/agent-runner.ts +32 -0
  87. package/src/harness/agent/agent-scheduler.ts +37 -0
  88. package/src/harness/agent/review-agent.ts +391 -0
  89. package/src/harness/config/harness-config.ts +84 -0
  90. package/src/harness/hunt/hunt-harness.ts +83 -0
  91. package/src/harness/hunt/hunt-options.ts +75 -0
  92. package/src/harness/hunt/hunt-result.ts +17 -0
  93. package/src/harness/hunt/hunt-session.ts +372 -0
  94. package/src/harness/hunt/review-planner.ts +98 -0
  95. package/src/harness/index.ts +36 -0
  96. package/src/harness/investigation/agent-state.ts +134 -0
  97. package/src/harness/investigation/evidence-verifier.ts +129 -0
  98. package/src/harness/investigation/evidence.ts +110 -0
  99. package/src/harness/investigation/finding-critic.ts +96 -0
  100. package/src/harness/investigation/investigation-planner.ts +118 -0
  101. package/src/harness/investigation/investigation-task.ts +10 -0
  102. package/src/harness/investigation/review-journal.ts +87 -0
  103. package/src/harness/investigation/rule-analyzer.ts +178 -0
  104. package/src/harness/investigation/rule-signals.ts +110 -0
  105. package/src/harness/prompts/agent-schemas.ts +57 -0
  106. package/src/harness/prompts/reviewer-prompt-builder.ts +101 -0
  107. package/src/harness/prompts/states/conclude-prompt.ts +16 -0
  108. package/src/harness/prompts/states/investigate-prompt.ts +37 -0
  109. package/src/harness/prompts/states/orient-prompt.ts +15 -0
  110. package/src/harness/prompts/states/verify-prompt.ts +22 -0
  111. package/src/harness/tools/agent-tool.ts +102 -0
  112. package/src/harness/tools/builtin/changed-symbols-tool.ts +48 -0
  113. package/src/harness/tools/builtin/doc-context-tool.ts +74 -0
  114. package/src/harness/tools/builtin/file-outline-tool.ts +66 -0
  115. package/src/harness/tools/builtin/find-references-tool.ts +66 -0
  116. package/src/harness/tools/builtin/find-symbol-tool.ts +91 -0
  117. package/src/harness/tools/builtin/helpers.ts +115 -0
  118. package/src/harness/tools/builtin/impact-tool.ts +48 -0
  119. package/src/harness/tools/builtin/imports-exports-tool.ts +53 -0
  120. package/src/harness/tools/builtin/index.ts +59 -0
  121. package/src/harness/tools/builtin/list-files-tool.ts +85 -0
  122. package/src/harness/tools/builtin/pr-context-tool.ts +41 -0
  123. package/src/harness/tools/builtin/read-file-range-tool.ts +73 -0
  124. package/src/harness/tools/builtin/rule-signals-tool.ts +36 -0
  125. package/src/harness/tools/builtin/search-code-tool.ts +65 -0
  126. package/src/harness/tools/builtin/symbol-context-tool.ts +106 -0
  127. package/src/harness/tools/builtin/tests-for-symbol-tool.ts +61 -0
  128. package/src/harness/tools/path-guard.ts +90 -0
  129. package/src/harness/tools/result-limiter.ts +49 -0
  130. package/src/harness/tools/secret-redactor.ts +54 -0
  131. package/src/harness/tools/tool-cache.ts +50 -0
  132. package/src/harness/tools/tool-call-budget.ts +60 -0
  133. package/src/harness/tools/tool-context.ts +5 -0
  134. package/src/harness/tools/tool-middleware.ts +105 -0
  135. package/src/harness/tools/tool-policy.ts +51 -0
  136. package/src/harness/tools/tool-registry.ts +112 -0
  137. package/src/harness/tools/tool-result.ts +15 -0
  138. package/src/harness/tools/toolbox.ts +36 -0
  139. package/src/harness/validation/finding-validator.ts +119 -0
  140. package/src/index.ts +46 -0
  141. package/src/server/client/App.tsx +26 -9
  142. package/src/server/client/ArtifactsPanel.tsx +12 -5
  143. package/src/server/client/AutoRefresh.tsx +1 -1
  144. package/src/server/client/Filters.tsx +7 -2
  145. package/src/server/client/FindingCard.tsx +14 -10
  146. package/src/server/client/FindingsList.tsx +2 -2
  147. package/src/server/client/MarkdownText.tsx +92 -12
  148. package/src/server/client/ResumePanel.tsx +368 -0
  149. package/src/server/client/RunList.tsx +3 -3
  150. package/src/server/client/SubmitModal.tsx +49 -6
  151. package/src/server/client/SummaryBar.tsx +1 -1
  152. package/src/server/client/icons.tsx +17 -0
  153. package/src/server/client/{constants.ts → utils/constants.ts} +61 -10
  154. package/src/server/client/utils/markdown.ts +319 -0
  155. package/src/server/client/{types.ts → utils/types.ts} +64 -0
  156. package/src/server/index.ts +66 -7
  157. package/src/server/run-repository.ts +9 -0
  158. package/src/skill/OUTPUT-FORMAT.md +12 -0
  159. package/src/skill/SETUP.md +6 -2
  160. package/src/skill/example-review.md +22 -14
  161. package/src/skill/hunt.md +45 -19
  162. package/src/skill/verify.md +3 -2
  163. package/src/tests/__snapshots__/baseline.test.ts.snap +126 -42
  164. package/src/tests/ai-core.test.ts +120 -0
  165. package/src/tests/ai-features.test.ts +183 -0
  166. package/src/tests/ast.test.ts +335 -0
  167. package/src/tests/baseline.test.ts +3 -0
  168. package/src/tests/code-highlight.test.ts +1 -1
  169. package/src/tests/env.test.ts +69 -0
  170. package/src/tests/fallback.test.ts +172 -0
  171. package/src/tests/findings-state.test.ts +50 -2
  172. package/src/tests/fixtures/fake-agent-tools.ts +69 -0
  173. package/src/tests/fixtures/fake-investigation.ts +64 -0
  174. package/src/tests/fixtures/fake-model-provider.ts +48 -0
  175. package/src/tests/github-review.test.ts +409 -60
  176. package/src/tests/harness-agent.test.ts +122 -0
  177. package/src/tests/harness-investigation.test.ts +206 -0
  178. package/src/tests/harness-tools.test.ts +148 -0
  179. package/src/tests/hunt-command.test.ts +212 -0
  180. package/src/tests/logger.test.ts +83 -0
  181. package/src/tests/markdown.test.ts +61 -1
  182. package/src/tests/review-resume.test.ts +101 -0
  183. package/src/tests/review-scope.test.ts +302 -0
  184. package/src/tests/review-scripts.test.ts +73 -0
  185. package/src/tests/rule-analyzer.test.ts +61 -0
  186. package/src/tests/run-status-view.test.ts +94 -0
  187. package/src/tests/run-status.test.ts +53 -0
  188. package/src/tests/server.test.ts +76 -21
  189. package/src/tests/storage.test.ts +63 -0
  190. package/src/tests/tool-contracts.test.ts +82 -0
  191. package/src/tests/verify.test.ts +1 -1
  192. package/src/server/client/markdown.ts +0 -153
  193. /package/src/server/client/{api.ts → utils/api.ts} +0 -0
  194. /package/src/server/client/{code-highlight.ts → utils/code-highlight.ts} +0 -0
  195. /package/src/server/client/{format.ts → utils/format.ts} +0 -0
  196. /package/src/server/client/{json-highlight.ts → utils/json-highlight.ts} +0 -0
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import { readFileSync } from "node:fs";
9
+ import type { AstFacts } from "./ast/types";
9
10
  import type { HoundsmanConfig } from "./config";
10
11
  import { churnToRecord, collectChurn, type DiffSource } from "./diff-source";
11
12
  import { matchesClassifyRule } from "./domain/watchdog";
@@ -69,12 +70,13 @@ export function matchWatchdogs<TId extends string>(
69
70
  watchdogs: Record<TId, WatchdogDefinition>,
70
71
  watchdogIds: readonly TId[],
71
72
  content?: string,
73
+ facts?: AstFacts,
72
74
  ): TId[] {
73
75
  const ids: TId[] = [];
74
76
  for (const id of watchdogIds) {
75
77
  if (
76
78
  watchdogs[id]?.classify?.some((rule) =>
77
- matchesClassifyRule(filePath, rule, content),
79
+ matchesClassifyRule(filePath, rule, content, facts),
78
80
  )
79
81
  ) {
80
82
  ids.push(id);
@@ -88,6 +90,13 @@ export function isExcludedPath(filePath: string, patterns: readonly RegExp[]): b
88
90
  return patterns.some((pattern) => pattern.test(filePath));
89
91
  }
90
92
 
93
+ /** Id do watchdog de último recurso (primeiro declarado), se houver. */
94
+ export function fallbackWatchdogId<TId extends string>(
95
+ config: HoundsmanConfig<TId>,
96
+ ): TId | undefined {
97
+ return config.watchdogIds.find((id) => config.watchdogs[id]?.fallback === true);
98
+ }
99
+
91
100
  // ---------------------------------------------------------------------------
92
101
  // Parsing de `git diff --name-status`
93
102
  // ---------------------------------------------------------------------------
@@ -142,12 +151,15 @@ export function classifyEntries<TId extends string>(
142
151
  entries: FileEntry[],
143
152
  config: HoundsmanConfig<TId>,
144
153
  churn: ChurnMap = {},
154
+ astFacts?: ReadonlyMap<string, AstFacts>,
145
155
  ): ClassificationResult {
146
156
  const watchdogFiles: Record<string, string[]> = {};
147
157
  const files: ClassificationResult["files"] = {};
148
158
  const unclassified: string[] = [];
149
159
  const excluded: string[] = [];
150
160
  const deleted: string[] = [];
161
+ // Watchdog de último recurso: cobre apenas arquivos sem outro dono.
162
+ const fallbackId = fallbackWatchdogId(config);
151
163
  // Leitura de conteúdo só é habilitada se algum watchdog declarar regra `content`.
152
164
  const needsContent = hasContentRules(config.watchdogs, config.watchdogIds);
153
165
 
@@ -167,15 +179,23 @@ export function classifyEntries<TId extends string>(
167
179
  }
168
180
 
169
181
  const content = needsContent ? readFileContent(path) : undefined;
170
- const ids: string[] = matchWatchdogs(
182
+ const facts = astFacts?.get(path);
183
+ const matched = matchWatchdogs(
171
184
  path,
172
185
  config.watchdogs,
173
186
  config.watchdogIds,
174
187
  content,
188
+ facts,
175
189
  );
190
+ const ids = fallbackId ? matched.filter((id) => id !== fallbackId) : matched;
176
191
 
177
192
  if (ids.length === 0) {
178
- unclassified.push(path);
193
+ if (fallbackId) {
194
+ watchdogFiles[fallbackId] ??= [];
195
+ watchdogFiles[fallbackId].push(path);
196
+ } else {
197
+ unclassified.push(path);
198
+ }
179
199
  continue;
180
200
  }
181
201
 
@@ -218,10 +238,11 @@ export function classifyRange<TId extends string>(
218
238
  head: string,
219
239
  config: HoundsmanConfig<TId>,
220
240
  diff?: DiffSource,
241
+ astFacts?: ReadonlyMap<string, AstFacts>,
221
242
  ): ClassificationResult {
222
243
  const entries = parseEntries(runGit(["diff", `${base}...${head}`, "--name-status"]));
223
244
  const churn = diff?.churn ?? collectChurn(base, head);
224
- const result = classifyEntries(entries, config, churnToRecord(churn));
245
+ const result = classifyEntries(entries, config, churnToRecord(churn), astFacts);
225
246
  result.base = base;
226
247
  result.head = head;
227
248
  return result;
@@ -76,6 +76,10 @@ export type GitHubLink = {
76
76
  pr: number;
77
77
  /** Quando foi enviado (ISO). */
78
78
  at: string;
79
+ /** Node ID da thread de revisão (GraphQL) — base para resolver a conversa. */
80
+ threadId?: string;
81
+ /** Quando a thread foi marcada como resolvida no GitHub (ISO). */
82
+ resolvedAt?: string;
79
83
  };
80
84
 
81
85
  /** Marco da revisão que originou o estado: commit revisado + quando. */
@@ -283,6 +287,12 @@ export type BuildStateOptions = {
283
287
  reviewed?: FindingsReviewed;
284
288
  /** SHA usado para carimbar `foundAtSha` de findings novos/re-reportados. */
285
289
  headSha?: string;
290
+ /**
291
+ * Arquivos efetivamente revisados neste ciclo (união de `reviewedFiles`).
292
+ * Quando presente, só findings ausentes desses arquivos viram `done` — o que
293
+ * ficou fora do orçamento/skipped não pode ser dado como resolvido.
294
+ */
295
+ reviewedPaths?: ReadonlySet<string>;
286
296
  now?: string;
287
297
  };
288
298
 
@@ -303,14 +313,21 @@ export function buildStateFromFindings(
303
313
 
304
314
  const result: Record<string, FindingState> = {};
305
315
  // 1. Preserva o estado anterior (marca como ausente até provar o contrário).
316
+ const reviewedPaths = options.reviewedPaths;
306
317
  for (const prev of Object.values(previous?.findings ?? {})) {
318
+ // Ausente do novo relatório ⇒ corrigido manualmente entre revisões (ou
319
+ // some junto com o trecho): vira `done`. `ignore` é decisão explícita de
320
+ // não corrigir — preserva o estado original. Arquivo fora da revisão
321
+ // (orçamento/skipped) também preserva: ausência não prova resolução.
322
+ const wasReviewed = !reviewedPaths || reviewedPaths.has(prev.file);
323
+ const resolvedByReview =
324
+ wasReviewed && prev.present && prev.decision !== "ignore";
325
+ const queuedAndGone =
326
+ wasReviewed && prev.decision === "execute" && prev.execState === "pending";
307
327
  result[prev.id] = {
308
328
  ...prev,
309
329
  present: false,
310
- execState:
311
- prev.execState === "pending" && prev.decision === "execute"
312
- ? "done"
313
- : prev.execState,
330
+ execState: resolvedByReview || queuedAndGone ? "done" : prev.execState,
314
331
  updatedAt: now,
315
332
  };
316
333
  }