@payconductor/houndsman 0.13.0 → 0.14.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 +6 -1
- package/package.json +1 -1
- package/src/cli/commands/compare-command.ts +112 -0
- package/src/cli/commands/context-command.ts +1 -0
- package/src/cli/commands/findings-command.ts +11 -1
- package/src/cli/commands/hunt-command.ts +20 -3
- package/src/cli/commands/review-command.ts +38 -0
- package/src/cli/commands/status-command.ts +3 -2
- package/src/cli/help.ts +3 -1
- package/src/cli/index.ts +3 -1
- package/src/cli/progress.ts +7 -2
- package/src/core/ai/ai-client.ts +38 -2
- package/src/core/ai/features/brief-enricher.ts +1 -1
- package/src/core/ai/features/business-analyzer.ts +61 -0
- package/src/core/ai/features/business-summarizer.ts +44 -0
- package/src/core/ai/index.ts +8 -0
- package/src/core/ai/prompts/brief.ts +33 -7
- package/src/core/ai/prompts/business.ts +76 -0
- package/src/core/ai/provider-capabilities.ts +12 -0
- package/src/core/ai/schemas.ts +70 -2
- package/src/core/ai/usage-recorder.ts +53 -1
- package/src/core/brief.ts +246 -12
- package/src/core/builtins.ts +108 -0
- package/src/core/business-summary.ts +583 -0
- package/src/core/commands/compare.ts +233 -0
- package/src/core/commands/context.ts +6 -2
- package/src/core/commands/findings.ts +38 -30
- package/src/core/commands/review.ts +6 -0
- package/src/core/commands/scaffold.ts +27 -0
- package/src/core/commands/status.ts +9 -4
- package/src/core/compare.ts +243 -0
- package/src/core/concurrency.ts +30 -0
- package/src/core/config.ts +31 -3
- package/src/core/defaults.ts +50 -4
- package/src/core/domain/budget.ts +62 -4
- package/src/core/domain/classification.ts +12 -2
- package/src/core/engine/carryover.ts +4 -0
- package/src/core/engine/finalize-pipeline.ts +347 -0
- package/src/core/engine/prepare-pipeline.ts +1294 -0
- package/src/core/engine/review-context.ts +51 -0
- package/src/core/engine/review-options.ts +47 -0
- package/src/core/engine/review-scope.ts +14 -1
- package/src/core/engine/review-session.ts +11 -13
- package/src/core/engine/run-artifacts.ts +196 -0
- package/src/core/engine/run-format.ts +4 -0
- package/src/core/findings-state.ts +13 -1
- package/src/core/github-review.ts +5 -2
- package/src/core/hunt/hunt-runner.ts +90 -0
- package/src/core/hunt/index.ts +2 -0
- package/src/core/hunt/maki-intent-prompt.ts +50 -0
- package/src/core/hunt/maki-prompt.ts +13 -1
- package/src/core/hunt/pool.ts +4 -26
- package/src/core/logging/index.ts +6 -1
- package/src/core/logging/progress.ts +85 -1
- package/src/core/orchestrator.ts +33 -1289
- package/src/core/paths.ts +6 -3
- package/src/core/pr-context.ts +2 -4
- package/src/core/prepare-report.ts +61 -0
- package/src/core/project-config.ts +8 -1
- package/src/core/propagation.ts +127 -0
- package/src/core/registry-check.ts +60 -21
- package/src/core/renderers/manifest-writer.ts +13 -0
- package/src/core/renderers/resume-renderer.ts +151 -11
- package/src/core/report-builder.ts +108 -278
- package/src/core/storage/file/file-storage.ts +7 -3
- package/src/core/storage/sqlite/mappers.ts +10 -0
- package/src/core/storage/sqlite/schema.ts +104 -23
- package/src/core/storage/sqlite/sqlite-storage.ts +22 -9
- package/src/core/storage/types.ts +16 -4
- package/src/core/storage/work-area.ts +59 -1
- package/src/core/types.ts +31 -0
- package/src/core/watchdog-registry.ts +21 -3
- package/src/index.ts +29 -4
- package/src/server/client/ArtifactsPanel.tsx +4 -2
- package/src/server/client/FindingCard.tsx +21 -1
- package/src/server/client/RunsSection.tsx +5 -0
- package/src/server/client/utils/constants.ts +21 -11
- package/src/server/client/utils/markdown.ts +1 -1
- package/src/server/client/utils/types.ts +14 -2
- package/src/server/run-repository.ts +35 -14
- package/src/server/styles.css +2 -0
- package/src/skill/OUTPUT-FORMAT.md +36 -10
- package/src/skill/README.md +18 -14
- package/src/skill/SETUP.md +1 -0
- package/src/skill/SKILL.md +13 -4
- package/src/skill/example-resume.md +43 -0
- package/src/skill/hunt.md +145 -58
- package/src/tests/__snapshots__/baseline.test.ts.snap +187 -178
- package/src/tests/ai-core.test.ts +46 -0
- package/src/tests/ai-features.test.ts +95 -4
- package/src/tests/baseline.test.ts +29 -121
- package/src/tests/brief-scope.test.ts +250 -0
- package/src/tests/business-summary.test.ts +258 -0
- package/src/tests/compare.test.ts +233 -0
- package/src/tests/fixtures/fake-model-provider.ts +2 -1
- package/src/tests/hunt-maki.test.ts +58 -0
- package/src/tests/markdown.test.ts +3 -3
- package/src/tests/progress.test.ts +29 -1
- package/src/tests/propagation.test.ts +119 -0
- package/src/tests/review-resume.test.ts +60 -1
- package/src/tests/review-scope.test.ts +38 -29
- package/src/tests/review-scripts.test.ts +106 -218
- package/src/tests/server.test.ts +7 -4
- package/src/tests/storage.test.ts +194 -9
- package/src/core/renderers/report-renderer.ts +0 -66
- package/src/skill/example-review.md +0 -66
|
@@ -69,7 +69,7 @@ export class SQLiteStorageProvider implements StorageProvider {
|
|
|
69
69
|
// -----------------------------------------------------------------------
|
|
70
70
|
|
|
71
71
|
getOrCreateReview(input: CreateReviewInput): ReviewRecord {
|
|
72
|
-
const existing = this.findReview(input.repo.key, input.branch);
|
|
72
|
+
const existing = this.findReview(input.repo.key, input.branch, input.variant);
|
|
73
73
|
if (existing) {
|
|
74
74
|
// O repo pode ter sido movido; a identidade continua a mesma.
|
|
75
75
|
const moved =
|
|
@@ -102,6 +102,7 @@ export class SQLiteStorageProvider implements StorageProvider {
|
|
|
102
102
|
repoRoot: input.repo.root,
|
|
103
103
|
repoRemote: input.repo.remote,
|
|
104
104
|
branch: input.branch,
|
|
105
|
+
variant: input.variant?.trim() || undefined,
|
|
105
106
|
slug: input.slug,
|
|
106
107
|
base: input.base,
|
|
107
108
|
head: input.head,
|
|
@@ -113,15 +114,16 @@ export class SQLiteStorageProvider implements StorageProvider {
|
|
|
113
114
|
};
|
|
114
115
|
this.db.run(
|
|
115
116
|
`INSERT INTO reviews
|
|
116
|
-
(id, repo_key, repo_root, repo_remote, branch, slug, base, head,
|
|
117
|
+
(id, repo_key, repo_root, repo_remote, branch, variant, slug, base, head,
|
|
117
118
|
worktree_path, pr_number, created_at, updated_at)
|
|
118
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
119
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
119
120
|
[
|
|
120
121
|
record.id,
|
|
121
122
|
record.repoKey,
|
|
122
123
|
record.repoRoot,
|
|
123
124
|
record.repoRemote ?? null,
|
|
124
125
|
record.branch,
|
|
126
|
+
record.variant ?? "",
|
|
125
127
|
record.slug,
|
|
126
128
|
record.base ?? null,
|
|
127
129
|
record.head ?? null,
|
|
@@ -134,10 +136,12 @@ export class SQLiteStorageProvider implements StorageProvider {
|
|
|
134
136
|
return record;
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
findReview(repoKey: string, branch: string): ReviewRecord | null {
|
|
139
|
+
findReview(repoKey: string, branch: string, variant?: string): ReviewRecord | null {
|
|
138
140
|
const row = this.db
|
|
139
|
-
.query(
|
|
140
|
-
|
|
141
|
+
.query(
|
|
142
|
+
"SELECT * FROM reviews WHERE repo_key = ? AND branch = ? AND variant = ?",
|
|
143
|
+
)
|
|
144
|
+
.get(repoKey, branch, variant?.trim() ?? "") as ReviewRow | null;
|
|
141
145
|
return row ? reviewFromRow(row) : null;
|
|
142
146
|
}
|
|
143
147
|
|
|
@@ -253,9 +257,10 @@ export class SQLiteStorageProvider implements StorageProvider {
|
|
|
253
257
|
const upsert = this.db.query(
|
|
254
258
|
`INSERT INTO findings
|
|
255
259
|
(id, review_id, short_id, fingerprint, watchdog_ids, rule, severity,
|
|
256
|
-
file, line, message, action, evidence,
|
|
257
|
-
present, decision, comment, exec_state, github,
|
|
258
|
-
|
|
260
|
+
file, line, message, action, evidence, scenario, impact, confidence, basis,
|
|
261
|
+
anchor, found_at_sha, count, present, decision, comment, exec_state, github,
|
|
262
|
+
created_at, updated_at)
|
|
263
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
259
264
|
ON CONFLICT(id) DO UPDATE SET
|
|
260
265
|
short_id = excluded.short_id,
|
|
261
266
|
fingerprint = excluded.fingerprint,
|
|
@@ -267,6 +272,10 @@ export class SQLiteStorageProvider implements StorageProvider {
|
|
|
267
272
|
message = excluded.message,
|
|
268
273
|
action = excluded.action,
|
|
269
274
|
evidence = excluded.evidence,
|
|
275
|
+
scenario = excluded.scenario,
|
|
276
|
+
impact = excluded.impact,
|
|
277
|
+
confidence = excluded.confidence,
|
|
278
|
+
basis = excluded.basis,
|
|
270
279
|
anchor = excluded.anchor,
|
|
271
280
|
found_at_sha = excluded.found_at_sha,
|
|
272
281
|
count = excluded.count,
|
|
@@ -325,6 +334,10 @@ export class SQLiteStorageProvider implements StorageProvider {
|
|
|
325
334
|
finding.message,
|
|
326
335
|
finding.action,
|
|
327
336
|
finding.evidence ?? null,
|
|
337
|
+
finding.scenario ?? null,
|
|
338
|
+
finding.impact ?? null,
|
|
339
|
+
finding.confidence ?? null,
|
|
340
|
+
finding.basis ?? null,
|
|
328
341
|
finding.anchor ? JSON.stringify(finding.anchor) : null,
|
|
329
342
|
finding.foundAtSha ?? null,
|
|
330
343
|
finding.count,
|
|
@@ -28,6 +28,13 @@ export type ReviewRecord = {
|
|
|
28
28
|
repoRoot: string;
|
|
29
29
|
repoRemote?: string;
|
|
30
30
|
branch: string;
|
|
31
|
+
/**
|
|
32
|
+
* Rótulo da variante A/B (ausente = run principal da branch).
|
|
33
|
+
*
|
|
34
|
+
* Faz parte da identidade junto com `(repoKey, branch)`: permite rodar a
|
|
35
|
+
* mesma PR com dois experimentos sem apagar a revisão anterior.
|
|
36
|
+
*/
|
|
37
|
+
variant?: string;
|
|
31
38
|
slug: string;
|
|
32
39
|
base?: string;
|
|
33
40
|
head?: string;
|
|
@@ -81,6 +88,8 @@ export type CreateReviewInput = {
|
|
|
81
88
|
repo: RepoIdentity;
|
|
82
89
|
branch: string;
|
|
83
90
|
slug: string;
|
|
91
|
+
/** Variante A/B (ausente = run principal da branch). */
|
|
92
|
+
variant?: string;
|
|
84
93
|
base?: string;
|
|
85
94
|
head?: string;
|
|
86
95
|
/** Worktree onde a revisão roda (ausente = clone principal). */
|
|
@@ -99,11 +108,11 @@ export type ArtifactKind =
|
|
|
99
108
|
| "context"
|
|
100
109
|
| "context-brief"
|
|
101
110
|
| "context-summary"
|
|
111
|
+
| "business"
|
|
112
|
+
| "business-brief"
|
|
102
113
|
| "finding-report"
|
|
103
|
-
| "review"
|
|
104
114
|
| "resume"
|
|
105
115
|
| "technical"
|
|
106
|
-
| "domain-review"
|
|
107
116
|
| "verify"
|
|
108
117
|
| "ast"
|
|
109
118
|
| "ast-impact"
|
|
@@ -111,6 +120,8 @@ export type ArtifactKind =
|
|
|
111
120
|
| "ai-usage"
|
|
112
121
|
| "ai-brief"
|
|
113
122
|
| "hunt"
|
|
123
|
+
| "prepare"
|
|
124
|
+
| "compare"
|
|
114
125
|
| "other";
|
|
115
126
|
|
|
116
127
|
/** Artefato persistido de uma review. */
|
|
@@ -142,9 +153,10 @@ export type WriteArtifactInput = {
|
|
|
142
153
|
export interface StorageProvider {
|
|
143
154
|
readonly kind: "sqlite" | "file";
|
|
144
155
|
|
|
145
|
-
/**
|
|
156
|
+
/** Busca por identidade `(repo, branch, variante)`; variante ausente = principal. */
|
|
157
|
+
findReview(repoKey: string, branch: string, variant?: string): ReviewRecord | null;
|
|
158
|
+
/** Run da branch + variante (cria quando não existe). */
|
|
146
159
|
getOrCreateReview(input: CreateReviewInput): ReviewRecord;
|
|
147
|
-
findReview(repoKey: string, branch: string): ReviewRecord | null;
|
|
148
160
|
getReview(id: string): ReviewRecord | null;
|
|
149
161
|
/** Reviews do repositório (mais recentes primeiro); sem chave = todas. */
|
|
150
162
|
listReviews(repoKey?: string): ReviewRecord[];
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
|
+
import { type BusinessSummary, parseBusinessSummary } from "../business-summary";
|
|
12
13
|
import {
|
|
14
|
+
BUSINESS_BRIEF_FILE,
|
|
15
|
+
BUSINESS_SUMMARY_FILE,
|
|
13
16
|
CONTEXT_BRIEF_FILE,
|
|
14
17
|
CONTEXT_FILE,
|
|
15
18
|
CONTEXT_SUMMARY_FILE,
|
|
@@ -97,7 +100,6 @@ export class ReviewWorkArea {
|
|
|
97
100
|
brief: this.file(CONTEXT_BRIEF_FILE),
|
|
98
101
|
};
|
|
99
102
|
}
|
|
100
|
-
|
|
101
103
|
/** Garante os diretórios; `clean` recria a área do zero. */
|
|
102
104
|
ensure(clean = false): void {
|
|
103
105
|
if (clean) this.clean();
|
|
@@ -143,6 +145,62 @@ export class ReviewWorkArea {
|
|
|
143
145
|
writeFileSync(this.contextPaths.summary, `${JSON.stringify(summary, null, 2)}\n`);
|
|
144
146
|
}
|
|
145
147
|
|
|
148
|
+
/** Caminhos do modelo de intenção (JSON + brief do passo). */
|
|
149
|
+
get businessPaths(): { summary: string; brief: string } {
|
|
150
|
+
return {
|
|
151
|
+
summary: this.file(BUSINESS_SUMMARY_FILE),
|
|
152
|
+
brief: this.file(BUSINESS_BRIEF_FILE),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Grava o modelo de intenção (determinístico, IA ou revisor). */
|
|
157
|
+
writeBusinessSummary(summary: unknown): void {
|
|
158
|
+
mkdirSync(this.root, { recursive: true });
|
|
159
|
+
writeFileSync(
|
|
160
|
+
this.businessPaths.summary,
|
|
161
|
+
`${JSON.stringify(summary, null, 2)}\n`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Grava o brief do passo de intenção. */
|
|
166
|
+
writeBusinessBrief(content: string): void {
|
|
167
|
+
mkdirSync(this.root, { recursive: true });
|
|
168
|
+
writeFileSync(this.businessPaths.brief, `${content}\n`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Modelo de intenção gravado (subagente/IA) — null se ausente/inválido. */
|
|
172
|
+
readBusinessSummary(): BusinessSummary | null {
|
|
173
|
+
const raw = this.readBusinessSummaryRaw();
|
|
174
|
+
if (raw === null) return null;
|
|
175
|
+
try {
|
|
176
|
+
return parseBusinessSummary(JSON.parse(raw), "reviewer");
|
|
177
|
+
} catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Conteúdo cru do modelo de intenção (para persistir como artefato). */
|
|
183
|
+
readBusinessSummaryRaw(): string | null {
|
|
184
|
+
const path = this.businessPaths.summary;
|
|
185
|
+
if (!existsSync(path)) return null;
|
|
186
|
+
try {
|
|
187
|
+
return readFileSync(path, "utf8");
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Conteúdo cru do brief de intenção (para persistir como artefato). */
|
|
194
|
+
readBusinessBriefRaw(): string | null {
|
|
195
|
+
const path = this.businessPaths.brief;
|
|
196
|
+
if (!existsSync(path)) return null;
|
|
197
|
+
try {
|
|
198
|
+
return readFileSync(path, "utf8");
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
146
204
|
/** Finds brutos gravados pelos subagentes (um JSON por watchdog). */
|
|
147
205
|
readFindings(activeIds: string[]): FindingsIngest {
|
|
148
206
|
const reports: WatchdogReport[] = [];
|
package/src/core/types.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import type { BriefPlan } from "./ai/schemas";
|
|
10
10
|
import type { AstFileBrief } from "./ast/types";
|
|
11
|
+
import type { BusinessSummary } from "./business-summary";
|
|
11
12
|
import type { ChangedScope } from "./changed-scope";
|
|
12
13
|
import type { JevRuleSpec } from "./jev/jev-types";
|
|
13
14
|
|
|
@@ -131,8 +132,38 @@ export type WatchdogRenderArgs = {
|
|
|
131
132
|
ast?: readonly AstFileBrief[];
|
|
132
133
|
/** Plano de revisão sugerido por IA (nunca substitui as regras). */
|
|
133
134
|
aiPlan?: BriefPlan;
|
|
135
|
+
/** Modelo de intenção da PR (negócio) — insumo do revisor `general`. */
|
|
136
|
+
business?: BusinessSummary;
|
|
137
|
+
/** Caminho do `business-summary.json` no staging (versão canônica). */
|
|
138
|
+
businessSummaryPath?: string;
|
|
139
|
+
/** Trechos alterados (`diff` por arquivo) — só para o revisor de fluxo. */
|
|
140
|
+
snippets?: readonly { file: string; snippet: string }[];
|
|
141
|
+
/** `true` no revisor de fluxo: exige devolver/gravar o modelo de intenção. */
|
|
142
|
+
flowReview?: boolean;
|
|
134
143
|
/** Escopo do diff por arquivo (símbolos alterados; hunks como fallback). */
|
|
135
144
|
changedScope?: ChangedScope;
|
|
145
|
+
/** Micro-alterações iguais em muitos arquivos (propagação mecânica). */
|
|
146
|
+
propagation?: PropagationCluster | null;
|
|
147
|
+
/** Contratos alterados cujos importadores não mudaram (possível lacuna). */
|
|
148
|
+
propagationGaps?: readonly PropagationGap[];
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/** Cluster de propagação mecânica (ex.: novo parâmetro em 100 call sites). */
|
|
152
|
+
export type PropagationCluster = {
|
|
153
|
+
/** Linhas adicionadas normalizadas (o padrão dominante). */
|
|
154
|
+
lines: string[];
|
|
155
|
+
/** Arquivos que seguem o padrão. */
|
|
156
|
+
files: string[];
|
|
157
|
+
/** Arquivos com micro-alteração que NÃO seguem o padrão. */
|
|
158
|
+
outliers: { file: string; lines: string[] }[];
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/** Contrato que mudou e tem importadores fora do diff. */
|
|
162
|
+
export type PropagationGap = {
|
|
163
|
+
/** Arquivo do contrato (o que mudou). */
|
|
164
|
+
file: string;
|
|
165
|
+
/** Importadores que não aparecem no diff. */
|
|
166
|
+
importers: string[];
|
|
136
167
|
};
|
|
137
168
|
|
|
138
169
|
/** Definição completa de um watchdog — 1 por arquivo em `project/watchdogs/`. */
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* adaptadores para a API pública.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { builtinWatchdog, GENERAL_WATCHDOG_ID, mergeGeneralWatchdog } from "./builtins";
|
|
9
10
|
import type { HoundsmanConfig } from "./config";
|
|
10
11
|
import { Watchdog, type WatchdogContext } from "./domain/watchdog";
|
|
11
12
|
import type { WatchdogDefinition } from "./types";
|
|
@@ -23,17 +24,34 @@ export class WatchdogRegistry<TId extends string = string> {
|
|
|
23
24
|
return this.config.watchdogIds;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
/** Definição de um watchdog (
|
|
27
|
+
/** Definição de um watchdog (config vence; senão, built-in). */
|
|
27
28
|
get(watchdogId: string): WatchdogDefinition {
|
|
28
|
-
|
|
29
|
+
const definition = this.definitionOf(watchdogId);
|
|
30
|
+
if (!definition) {
|
|
31
|
+
throw new Error(`Watchdog '${watchdogId}' não encontrado.`);
|
|
32
|
+
}
|
|
33
|
+
return definition;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
/** Busca null-safe por id. */
|
|
32
37
|
find(watchdogId: string): Watchdog | null {
|
|
33
|
-
const definition = this.
|
|
38
|
+
const definition = this.definitionOf(watchdogId);
|
|
34
39
|
return definition ? new Watchdog(definition) : null;
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a definição: config primeiro, built-in depois. O `general` é
|
|
44
|
+
* reservado e sempre inclui o piso de lógica do motor (mesmo se o consumidor
|
|
45
|
+
* declarar um watchdog com esse id).
|
|
46
|
+
*/
|
|
47
|
+
private definitionOf(watchdogId: string): WatchdogDefinition | null {
|
|
48
|
+
const configured = this.config.watchdogs[watchdogId as TId];
|
|
49
|
+
if (watchdogId === GENERAL_WATCHDOG_ID) {
|
|
50
|
+
return mergeGeneralWatchdog(configured);
|
|
51
|
+
}
|
|
52
|
+
return configured ?? builtinWatchdog(watchdogId);
|
|
53
|
+
}
|
|
54
|
+
|
|
37
55
|
/** Nome legível (default: o próprio id). */
|
|
38
56
|
nameOf(watchdogId: string): string {
|
|
39
57
|
return this.find(watchdogId)?.name ?? watchdogId;
|
package/src/index.ts
CHANGED
|
@@ -50,6 +50,29 @@ export type {
|
|
|
50
50
|
AstSymbolKind,
|
|
51
51
|
} from "./core/ast/types";
|
|
52
52
|
export { defaultRenderBrief, PROCEDURE, renderBrief } from "./core/brief";
|
|
53
|
+
export {
|
|
54
|
+
BUILTIN_WATCHDOGS,
|
|
55
|
+
builtinWatchdog,
|
|
56
|
+
GENERAL_WATCHDOG_ID,
|
|
57
|
+
generalWatchdog,
|
|
58
|
+
mergeGeneralWatchdog,
|
|
59
|
+
} from "./core/builtins";
|
|
60
|
+
export {
|
|
61
|
+
BUSINESS_SUMMARY_SCHEMA,
|
|
62
|
+
type BusinessBriefInput,
|
|
63
|
+
type BusinessContract,
|
|
64
|
+
type BusinessFlow,
|
|
65
|
+
type BusinessInput,
|
|
66
|
+
type BusinessState,
|
|
67
|
+
type BusinessSummary,
|
|
68
|
+
type BusinessSummarySource,
|
|
69
|
+
buildBusinessBrief,
|
|
70
|
+
buildBusinessPromptPayload,
|
|
71
|
+
buildBusinessSummary,
|
|
72
|
+
parseBusinessSummary,
|
|
73
|
+
renderBusinessLines,
|
|
74
|
+
summarizeBusiness,
|
|
75
|
+
} from "./core/business-summary";
|
|
53
76
|
export type {
|
|
54
77
|
BuildChangedScopeInput,
|
|
55
78
|
ChangedScope,
|
|
@@ -67,7 +90,10 @@ export {
|
|
|
67
90
|
mergeRanges,
|
|
68
91
|
summarizeScope,
|
|
69
92
|
} from "./core/changed-scope";
|
|
70
|
-
export type {
|
|
93
|
+
export type {
|
|
94
|
+
FindingsCommandResult,
|
|
95
|
+
FindingsContextOptions,
|
|
96
|
+
} from "./core/commands/findings";
|
|
71
97
|
export { runFindingsCommand, runVerifyCommand } from "./core/commands/findings";
|
|
72
98
|
export { ProjectScaffolder } from "./core/commands/scaffold";
|
|
73
99
|
export type {
|
|
@@ -81,6 +107,7 @@ export type {
|
|
|
81
107
|
HoundsmanConfig,
|
|
82
108
|
HoundsmanConfigInput,
|
|
83
109
|
HoundsmanHunt,
|
|
110
|
+
HoundsmanPriority,
|
|
84
111
|
HoundsmanStorage,
|
|
85
112
|
} from "./core/config";
|
|
86
113
|
export {
|
|
@@ -242,9 +269,7 @@ export { ProjectConfig } from "./core/project-config";
|
|
|
242
269
|
export { checkRegistry } from "./core/registry-check";
|
|
243
270
|
export { BriefRenderer } from "./core/renderers/brief-renderer";
|
|
244
271
|
export { ManifestWriter } from "./core/renderers/manifest-writer";
|
|
245
|
-
export {
|
|
246
|
-
export type { DomainReport } from "./core/report-builder";
|
|
247
|
-
export { renderDomainReport } from "./core/report-builder";
|
|
272
|
+
export { RESUME_MAX_CHARS, renderReviewResume } from "./core/renderers/resume-renderer";
|
|
248
273
|
export type { Clock } from "./core/services/clock";
|
|
249
274
|
export { systemClock } from "./core/services/clock";
|
|
250
275
|
export { FileClassifier } from "./core/services/file-classifier";
|
|
@@ -19,9 +19,11 @@ import {
|
|
|
19
19
|
import { formatJson, highlightJson } from "./utils/json-highlight";
|
|
20
20
|
import type { ArtifactMeta } from "./utils/types";
|
|
21
21
|
|
|
22
|
-
/** Prefere o
|
|
22
|
+
/** Prefere o resumo final; senão, o primeiro artefato disponível. */
|
|
23
23
|
function pickDefault(items: ArtifactMeta[]): string {
|
|
24
|
-
return (
|
|
24
|
+
return (
|
|
25
|
+
(items.find((item) => item.name === "review-resume.md") ?? items[0])?.name ?? ""
|
|
26
|
+
);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/** JSON com destaque de sintaxe (tokens em spans, sem lib externa). */
|
|
@@ -157,7 +157,7 @@ export function FindingCard({
|
|
|
157
157
|
</span>
|
|
158
158
|
)}
|
|
159
159
|
{!finding.present && (
|
|
160
|
-
<span class="tag" title="Não aparece
|
|
160
|
+
<span class="tag" title="Não aparece na última revisão">
|
|
161
161
|
ausente
|
|
162
162
|
</span>
|
|
163
163
|
)}
|
|
@@ -206,6 +206,26 @@ export function FindingCard({
|
|
|
206
206
|
<div class="card-body">
|
|
207
207
|
<div class="meta-row">{finding.rule}</div>
|
|
208
208
|
|
|
209
|
+
{finding.scenario && (
|
|
210
|
+
<div class="action-block">
|
|
211
|
+
<Sparkles size={16} class="action-icon" />
|
|
212
|
+
<div class="action-text">
|
|
213
|
+
<span class="action-label">Cenário:</span>
|
|
214
|
+
<MarkdownText text={finding.scenario} />
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
|
|
219
|
+
{finding.impact && (
|
|
220
|
+
<div class="action-block">
|
|
221
|
+
<Sparkles size={16} class="action-icon" />
|
|
222
|
+
<div class="action-text">
|
|
223
|
+
<span class="action-label">Impacto:</span>
|
|
224
|
+
<MarkdownText text={finding.impact} />
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
|
|
209
229
|
<div class="action-block">
|
|
210
230
|
<Sparkles size={16} class="action-icon" />
|
|
211
231
|
<div class="action-text">
|
|
@@ -30,6 +30,11 @@ function RunItem({
|
|
|
30
30
|
<span class="run-top">
|
|
31
31
|
<BranchIcon size={14} class="run-icon" />
|
|
32
32
|
<span class="run-branch">{run.branch}</span>
|
|
33
|
+
{run.variant ? (
|
|
34
|
+
<span class="run-variant" title="Variante A/B">
|
|
35
|
+
{run.variant}
|
|
36
|
+
</span>
|
|
37
|
+
) : null}
|
|
33
38
|
</span>
|
|
34
39
|
<span class="run-meta">
|
|
35
40
|
<span class="run-when">
|
|
@@ -83,13 +83,13 @@ export function describeRunStatus(
|
|
|
83
83
|
|
|
84
84
|
/** Ordem canônica dos tipos de artefato na lista da UI. */
|
|
85
85
|
const KIND_ORDER: ArtifactKind[] = [
|
|
86
|
-
"
|
|
86
|
+
"compare",
|
|
87
87
|
"resume",
|
|
88
88
|
"technical",
|
|
89
|
-
"domain-review",
|
|
90
89
|
"verify",
|
|
91
90
|
"brief",
|
|
92
91
|
"manifest",
|
|
92
|
+
"prepare",
|
|
93
93
|
"classification",
|
|
94
94
|
"candidates",
|
|
95
95
|
"ast",
|
|
@@ -100,6 +100,8 @@ const KIND_ORDER: ArtifactKind[] = [
|
|
|
100
100
|
"context",
|
|
101
101
|
"context-brief",
|
|
102
102
|
"context-summary",
|
|
103
|
+
"business",
|
|
104
|
+
"business-brief",
|
|
103
105
|
"finding-report",
|
|
104
106
|
"other",
|
|
105
107
|
];
|
|
@@ -107,12 +109,12 @@ const KIND_ORDER: ArtifactKind[] = [
|
|
|
107
109
|
/** Grupo visual de cada tipo (relatórios, briefs, núcleo, contexto…). */
|
|
108
110
|
const GROUP_BY_KIND: Record<ArtifactKind, { key: string; label: string; order: number }> =
|
|
109
111
|
{
|
|
110
|
-
|
|
112
|
+
compare: { key: "report", label: "Relatórios", order: 0 },
|
|
111
113
|
resume: { key: "report", label: "Relatórios", order: 0 },
|
|
112
114
|
technical: { key: "core", label: "Núcleo da revisão", order: 3 },
|
|
113
|
-
"domain-review": { key: "report", label: "Relatórios", order: 0 },
|
|
114
115
|
verify: { key: "verify", label: "Verificação", order: 1 },
|
|
115
116
|
brief: { key: "brief", label: "Briefs (auditoria)", order: 2 },
|
|
117
|
+
prepare: { key: "core", label: "Núcleo da revisão", order: 3 },
|
|
116
118
|
manifest: { key: "core", label: "Núcleo da revisão", order: 3 },
|
|
117
119
|
classification: { key: "core", label: "Núcleo da revisão", order: 3 },
|
|
118
120
|
candidates: { key: "core", label: "Núcleo da revisão", order: 3 },
|
|
@@ -124,17 +126,19 @@ const GROUP_BY_KIND: Record<ArtifactKind, { key: string; label: string; order: n
|
|
|
124
126
|
context: { key: "context", label: "Contexto da PR", order: 5 },
|
|
125
127
|
"context-brief": { key: "context", label: "Contexto da PR", order: 5 },
|
|
126
128
|
"context-summary": { key: "context", label: "Contexto da PR", order: 5 },
|
|
129
|
+
business: { key: "business", label: "Intenção da PR", order: 5 },
|
|
130
|
+
"business-brief": { key: "business", label: "Intenção da PR", order: 5 },
|
|
127
131
|
"finding-report": { key: "other", label: "Outros", order: 6 },
|
|
128
132
|
other: { key: "other", label: "Outros", order: 6 },
|
|
129
133
|
};
|
|
130
134
|
|
|
131
135
|
/** Rótulo curto do tipo (badge do item). */
|
|
132
136
|
export const ARTIFACT_KIND_LABELS: Record<ArtifactKind, string> = {
|
|
133
|
-
|
|
137
|
+
compare: "comparação",
|
|
134
138
|
resume: "resumo",
|
|
135
139
|
technical: "pontos técnicos",
|
|
136
|
-
"domain-review": "domínio",
|
|
137
140
|
verify: "verify",
|
|
141
|
+
prepare: "prepare",
|
|
138
142
|
brief: "brief",
|
|
139
143
|
manifest: "manifesto",
|
|
140
144
|
classification: "classificação",
|
|
@@ -147,6 +151,8 @@ export const ARTIFACT_KIND_LABELS: Record<ArtifactKind, string> = {
|
|
|
147
151
|
context: "contexto",
|
|
148
152
|
"context-brief": "brief ctx",
|
|
149
153
|
"context-summary": "resumo ctx",
|
|
154
|
+
business: "intenção",
|
|
155
|
+
"business-brief": "brief intenção",
|
|
150
156
|
"finding-report": "findings",
|
|
151
157
|
other: "outro",
|
|
152
158
|
};
|
|
@@ -161,16 +167,16 @@ export function displayArtifactName(meta: ArtifactMeta): string {
|
|
|
161
167
|
/** Descrição curta do artefato (cabeçalho do painel). */
|
|
162
168
|
export function describeArtifact(meta: ArtifactMeta): string {
|
|
163
169
|
switch (meta.kind) {
|
|
164
|
-
case "
|
|
165
|
-
return "
|
|
170
|
+
case "compare":
|
|
171
|
+
return "Comparação A/B entre duas runs: achados novos/sumidos, cobertura, tempo e tokens.";
|
|
166
172
|
case "resume":
|
|
167
|
-
return "Resumo final
|
|
173
|
+
return "Resumo final: negócio, pontos técnicos, cobertura por domínio e conclusão.";
|
|
168
174
|
case "technical":
|
|
169
175
|
return "Destaques estruturais da mudança (módulos, contratos, dependências) usados no resumo final.";
|
|
170
|
-
case "domain-review":
|
|
171
|
-
return "Relatório isolado do domínio: achados daquele watchdog.";
|
|
172
176
|
case "verify":
|
|
173
177
|
return "Resultado do `findings verify` (resolvidos, persistentes e incertos).";
|
|
178
|
+
case "prepare":
|
|
179
|
+
return "Duração por etapa do prepare e uso de IA (diagnóstico de performance).";
|
|
174
180
|
case "brief":
|
|
175
181
|
return "Instruções enviadas ao subagente (auditoria do prompt).";
|
|
176
182
|
case "manifest":
|
|
@@ -189,6 +195,10 @@ export function describeArtifact(meta: ArtifactMeta): string {
|
|
|
189
195
|
return "Brief do subagente que resume o contexto da PR.";
|
|
190
196
|
case "context-summary":
|
|
191
197
|
return "Resumo da PR (objetivo, motivação e alterações) gerado no Passo 0.";
|
|
198
|
+
case "business":
|
|
199
|
+
return "Modelo de intenção da PR: o que ela deve entregar (fluxos, invariantes, contratos e estados).";
|
|
200
|
+
case "business-brief":
|
|
201
|
+
return "Brief do passo de intenção (subagente na skill / pré-passe maki).";
|
|
192
202
|
default:
|
|
193
203
|
return meta.name;
|
|
194
204
|
}
|
|
@@ -240,7 +240,7 @@ export function sectionBlocks(text: string, title: string): MarkdownBlock[] {
|
|
|
240
240
|
const FILE_EXTENSION =
|
|
241
241
|
/\.(?:[cm]?[jt]sx?|json|md|ya?ml|toml|css|scss|html?|py|go|rs|java|kt|cs|php|rb|sh|sql|txt|lock)$/i;
|
|
242
242
|
|
|
243
|
-
/** Caminho/arquivo dentro de um texto corrido (`src/foo.ts`, `
|
|
243
|
+
/** Caminho/arquivo dentro de um texto corrido (`src/foo.ts`, `manifest.json`). */
|
|
244
244
|
const PATH_TOKEN =
|
|
245
245
|
/(?:[\w.-]+[\\/])*[\w-]+\.(?:[cm]?[jt]sx?|json|md|ya?ml|toml|css|scss|html?|py|go|rs|java|kt|cs|php|rb|sh|sql|txt|lock)\b/g;
|
|
246
246
|
|
|
@@ -32,6 +32,14 @@ export type Finding = {
|
|
|
32
32
|
message: string;
|
|
33
33
|
action: string;
|
|
34
34
|
evidence?: string;
|
|
35
|
+
/** Gatilho concreto do gap (fora de regra). */
|
|
36
|
+
scenario?: string;
|
|
37
|
+
/** Consequência observável do gap. */
|
|
38
|
+
impact?: string;
|
|
39
|
+
/** Confiança do revisor (0–1). */
|
|
40
|
+
confidence?: number;
|
|
41
|
+
/** Origem do achado: regra, lógica ou domínio. */
|
|
42
|
+
basis?: "rule" | "logic" | "domain";
|
|
35
43
|
count: number;
|
|
36
44
|
present: boolean;
|
|
37
45
|
decision: Decision;
|
|
@@ -59,17 +67,19 @@ export type ArtifactKind =
|
|
|
59
67
|
| "context"
|
|
60
68
|
| "context-brief"
|
|
61
69
|
| "context-summary"
|
|
70
|
+
| "business"
|
|
71
|
+
| "business-brief"
|
|
62
72
|
| "finding-report"
|
|
63
|
-
| "review"
|
|
64
73
|
| "resume"
|
|
65
74
|
| "technical"
|
|
66
|
-
| "domain-review"
|
|
67
75
|
| "verify"
|
|
68
76
|
| "ast"
|
|
69
77
|
| "ast-impact"
|
|
70
78
|
| "ai"
|
|
71
79
|
| "ai-usage"
|
|
72
80
|
| "ai-brief"
|
|
81
|
+
| "prepare"
|
|
82
|
+
| "compare"
|
|
73
83
|
| "other";
|
|
74
84
|
|
|
75
85
|
/** Metadados de artefato retornados pela API (sem conteúdo). */
|
|
@@ -83,6 +93,8 @@ export type ArtifactMeta = {
|
|
|
83
93
|
export type RunSummary = {
|
|
84
94
|
slug: string;
|
|
85
95
|
branch: string;
|
|
96
|
+
/** Variante A/B (ausente = run principal da branch). */
|
|
97
|
+
variant?: string;
|
|
86
98
|
/** Worktree onde a run foi executada (ausente = clone principal). */
|
|
87
99
|
worktreePath?: string;
|
|
88
100
|
prNumber?: number;
|