@payconductor/houndsman 0.13.0 → 0.16.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/golden-command.ts +92 -0
- package/src/cli/commands/hunt-command.ts +105 -4
- package/src/cli/commands/review-command.ts +38 -0
- package/src/cli/commands/status-command.ts +3 -2
- package/src/cli/help.ts +6 -1
- package/src/cli/index.ts +5 -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 +278 -16
- package/src/core/builtins.ts +108 -0
- package/src/core/business-summary.ts +583 -0
- package/src/core/commands/compare.ts +266 -0
- package/src/core/commands/context.ts +6 -2
- package/src/core/commands/findings.ts +38 -30
- package/src/core/commands/golden.ts +176 -0
- package/src/core/commands/review.ts +6 -0
- package/src/core/commands/scaffold.ts +29 -0
- package/src/core/commands/status.ts +9 -4
- package/src/core/compare.ts +310 -0
- package/src/core/concurrency.ts +30 -0
- package/src/core/config.ts +41 -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 +371 -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 +22 -2
- package/src/core/github-review.ts +5 -2
- package/src/core/golden.ts +346 -0
- package/src/core/hunt/hunt-runner.ts +621 -34
- package/src/core/hunt/index.ts +40 -1
- package/src/core/hunt/maki-intent-prompt.ts +50 -0
- package/src/core/hunt/maki-prompt.ts +298 -5
- package/src/core/hunt/maki-runner.ts +93 -16
- package/src/core/hunt/pool.ts +4 -26
- package/src/core/hunt/review-checks.ts +460 -0
- 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 +18 -1
- package/src/core/propagation.ts +127 -0
- package/src/core/registry-check.ts +90 -27
- package/src/core/renderers/manifest-writer.ts +13 -0
- package/src/core/renderers/resume-renderer.ts +151 -11
- package/src/core/report-builder.ts +116 -278
- package/src/core/severity.ts +245 -0
- 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 +17 -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 +51 -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 +55 -10
- package/src/skill/README.md +19 -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 +205 -58
- package/src/skill/train.md +3 -0
- package/src/tests/__snapshots__/baseline.test.ts.snap +208 -184
- 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 +256 -0
- package/src/tests/findings-state.test.ts +82 -0
- package/src/tests/fixtures/fake-model-provider.ts +2 -1
- package/src/tests/golden.test.ts +195 -0
- package/src/tests/hunt-maki.test.ts +710 -2
- 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 +125 -218
- package/src/tests/server.test.ts +7 -4
- package/src/tests/severity.test.ts +235 -0
- 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
|
@@ -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 {
|
|
@@ -187,6 +214,9 @@ export {
|
|
|
187
214
|
} from "./core/findings-state";
|
|
188
215
|
export type { GitResult, GitRunner } from "./core/git-runner";
|
|
189
216
|
export type {
|
|
217
|
+
BusinessDecision,
|
|
218
|
+
CheckCoverage,
|
|
219
|
+
CheckVerdict,
|
|
190
220
|
HuntEvent,
|
|
191
221
|
HuntReport,
|
|
192
222
|
HuntRunnerOptions,
|
|
@@ -203,32 +233,51 @@ export type {
|
|
|
203
233
|
MakiRunRequest,
|
|
204
234
|
MakiRunResult,
|
|
205
235
|
MakiUsage,
|
|
236
|
+
ReviewCheck,
|
|
237
|
+
ReviewRule,
|
|
238
|
+
RuleCheck,
|
|
206
239
|
} from "./core/hunt";
|
|
207
240
|
export {
|
|
241
|
+
briefRules,
|
|
242
|
+
businessScore,
|
|
243
|
+
chooseBusinessSummary,
|
|
244
|
+
expectedReviewItems,
|
|
208
245
|
extractJsonObject,
|
|
209
246
|
HuntRunner,
|
|
210
247
|
houndsmanKeyEnvFor,
|
|
211
248
|
installMaki,
|
|
212
249
|
MAKI_API_KEY_ENV,
|
|
213
250
|
MAKI_DISALLOWED_TOOLS,
|
|
251
|
+
MAKI_REVIEW_SYSTEM_PROMPT,
|
|
214
252
|
makiArgs,
|
|
215
253
|
makiCurrentFile,
|
|
216
254
|
makiInstallDir,
|
|
217
255
|
makiSourceLabel,
|
|
218
256
|
makiToolsRoot,
|
|
219
257
|
mapPool,
|
|
258
|
+
mergeFindings,
|
|
259
|
+
mergeRuleChecks,
|
|
220
260
|
metricsFrom,
|
|
261
|
+
missingReviewItems,
|
|
262
|
+
missingRuleChecks,
|
|
221
263
|
parseMakiOutput,
|
|
264
|
+
parseReviewChecks,
|
|
265
|
+
parseRuleChecks,
|
|
222
266
|
probeMakiVersion,
|
|
223
267
|
readMakiInstall,
|
|
268
|
+
renderChecklistSuffix,
|
|
224
269
|
renderHuntPrompt,
|
|
270
|
+
renderResumeSuffix,
|
|
225
271
|
renderRetrySuffix,
|
|
226
272
|
requireMakiBinary,
|
|
227
273
|
requireMakiModel,
|
|
228
274
|
resolveMakiBinary,
|
|
229
275
|
resolveMakiModel,
|
|
230
276
|
SpawnMakiRunner,
|
|
277
|
+
summarizeChecks,
|
|
278
|
+
summarizeRuleChecks,
|
|
231
279
|
uninstallMaki,
|
|
280
|
+
withInferredRule,
|
|
232
281
|
} from "./core/hunt";
|
|
233
282
|
export {
|
|
234
283
|
CORE_DIR,
|
|
@@ -242,9 +291,7 @@ export { ProjectConfig } from "./core/project-config";
|
|
|
242
291
|
export { checkRegistry } from "./core/registry-check";
|
|
243
292
|
export { BriefRenderer } from "./core/renderers/brief-renderer";
|
|
244
293
|
export { ManifestWriter } from "./core/renderers/manifest-writer";
|
|
245
|
-
export {
|
|
246
|
-
export type { DomainReport } from "./core/report-builder";
|
|
247
|
-
export { renderDomainReport } from "./core/report-builder";
|
|
294
|
+
export { RESUME_MAX_CHARS, renderReviewResume } from "./core/renderers/resume-renderer";
|
|
248
295
|
export type { Clock } from "./core/services/clock";
|
|
249
296
|
export { systemClock } from "./core/services/clock";
|
|
250
297
|
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;
|
|
@@ -48,6 +48,8 @@ import { fetchPrComments, type PrCommentsResponse } from "./pr-comments";
|
|
|
48
48
|
export type RunListItem = {
|
|
49
49
|
slug: string;
|
|
50
50
|
branch: string;
|
|
51
|
+
/** Variante A/B (ausente = run principal da branch). */
|
|
52
|
+
variant?: string;
|
|
51
53
|
/** Worktree onde a run foi executada (ausente = clone principal). */
|
|
52
54
|
worktreePath?: string;
|
|
53
55
|
/** PR revisada (quando iniciada via `--pr`). */
|
|
@@ -63,6 +65,8 @@ export type RunListItem = {
|
|
|
63
65
|
export type RunDetail = {
|
|
64
66
|
slug: string;
|
|
65
67
|
branch: string;
|
|
68
|
+
/** Variante A/B (ausente = run principal da branch). */
|
|
69
|
+
variant?: string;
|
|
66
70
|
/** Worktree onde a run foi executada (ausente = clone principal). */
|
|
67
71
|
worktreePath?: string;
|
|
68
72
|
/** PR revisada (quando iniciada via `--pr`). */
|
|
@@ -129,6 +133,7 @@ export class RunRepository {
|
|
|
129
133
|
return this.storage.listReviews(this.repo.key).map((review) => {
|
|
130
134
|
const state = this.storage.readFindings(review.id);
|
|
131
135
|
return {
|
|
136
|
+
variant: review.variant,
|
|
132
137
|
slug: review.slug,
|
|
133
138
|
branch: review.branch,
|
|
134
139
|
worktreePath: review.worktreePath,
|
|
@@ -144,11 +149,11 @@ export class RunRepository {
|
|
|
144
149
|
|
|
145
150
|
/** Mapa branch → slug das runs (usado para casar PRs com execuções). */
|
|
146
151
|
branchIndex(): Map<string, string> {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
const index = new Map<string, string>();
|
|
153
|
+
for (const review of this.reviewsForBranchLinks()) {
|
|
154
|
+
if (!index.has(review.branch)) index.set(review.branch, review.slug);
|
|
155
|
+
}
|
|
156
|
+
return index;
|
|
152
157
|
}
|
|
153
158
|
|
|
154
159
|
/** Mapa branch → run + worktree (sidebar de PRs). */
|
|
@@ -156,15 +161,30 @@ export class RunRepository {
|
|
|
156
161
|
string,
|
|
157
162
|
{ slug: string; worktreePath?: string; prNumber?: number }
|
|
158
163
|
> {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
const index = new Map<
|
|
165
|
+
string,
|
|
166
|
+
{ slug: string; worktreePath?: string; prNumber?: number }
|
|
167
|
+
>();
|
|
168
|
+
for (const review of this.reviewsForBranchLinks()) {
|
|
169
|
+
if (index.has(review.branch)) continue;
|
|
170
|
+
index.set(review.branch, {
|
|
171
|
+
slug: review.slug,
|
|
172
|
+
worktreePath: review.worktreePath,
|
|
173
|
+
prNumber: review.prNumber,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return index;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Runs ordenadas para links de PR: a principal da branch primeiro (uma
|
|
181
|
+
* variante A/B não deve "roubar" o atalho da PR), depois as mais recentes.
|
|
182
|
+
*/
|
|
183
|
+
private reviewsForBranchLinks(): ReviewRecord[] {
|
|
184
|
+
return [...this.storage.listReviews(this.repo.key)].sort(
|
|
185
|
+
(a, b) =>
|
|
186
|
+
Number(Boolean(a.variant)) - Number(Boolean(b.variant)) ||
|
|
187
|
+
b.updatedAt.localeCompare(a.updatedAt),
|
|
168
188
|
);
|
|
169
189
|
}
|
|
170
190
|
|
|
@@ -180,6 +200,7 @@ export class RunRepository {
|
|
|
180
200
|
}>(review, "classification.json");
|
|
181
201
|
return {
|
|
182
202
|
slug: review.slug,
|
|
203
|
+
variant: review.variant,
|
|
183
204
|
branch: review.branch,
|
|
184
205
|
worktreePath: review.worktreePath,
|
|
185
206
|
prNumber: review.prNumber,
|
package/src/server/styles.css
CHANGED
|
@@ -77,6 +77,8 @@ body.resizing-sidebar { cursor:col-resize; user-select:none; }
|
|
|
77
77
|
.run-icon { flex:none; margin-top:2px; color:var(--muted-2); }
|
|
78
78
|
.run.active .run-icon { color:var(--accent); }
|
|
79
79
|
.run-branch { font-size:12.5px; font-weight:600; line-height:1.35; word-break:break-word; }
|
|
80
|
+
.run-variant { flex:none; align-self:flex-start; padding:1px 6px; border-radius:999px; font-family:var(--font-mono);
|
|
81
|
+
font-size:9.5px; color:var(--accent); background:var(--accent-soft); border:1px solid var(--accent-line); }
|
|
80
82
|
.run-meta { display:flex; align-items:center; gap:10px; font-family:var(--font-mono); font-size:10.5px; color:var(--muted); }
|
|
81
83
|
.run-when { display:inline-flex; align-items:center; gap:6px; }
|
|
82
84
|
.run-count { margin-left:auto; color:var(--muted-2); }
|