@ivorycanvas/qamap 0.4.0 → 0.4.2
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/CHANGELOG.md +40 -0
- package/README.md +25 -16
- package/dist/agent-init.js +2 -2
- package/dist/agent-init.js.map +1 -1
- package/dist/behavior-intent.js +11 -0
- package/dist/behavior-intent.js.map +1 -1
- package/dist/behavior.d.ts +8 -0
- package/dist/behavior.js +1 -1
- package/dist/behavior.js.map +1 -1
- package/dist/change-intent.d.ts +11 -1
- package/dist/change-intent.js +196 -56
- package/dist/change-intent.js.map +1 -1
- package/dist/cli.js +7 -3
- package/dist/cli.js.map +1 -1
- package/dist/e2e.js +76 -17
- package/dist/e2e.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/qa.js +227 -94
- package/dist/qa.js.map +1 -1
- package/dist/test-plan.d.ts +18 -0
- package/dist/test-plan.js +123 -13
- package/dist/test-plan.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/agent-format.md +12 -8
- package/docs/agent-skill.md +4 -4
- package/docs/architecture.md +1 -1
- package/docs/benchmarking.md +13 -2
- package/docs/commands.md +4 -0
- package/docs/e2e-output-examples.md +9 -22
- package/docs/quickstart-demo.md +24 -19
- package/docs/release-validation.md +34 -0
- package/docs/releasing.md +15 -4
- package/docs/roadmap.md +8 -1
- package/package.json +2 -2
- package/schema/qamap-agent.schema.json +80 -2
- package/schema/qamap-behavior.schema.json +32 -0
- package/skills/qamap-pr-qa/SKILL.md +9 -8
package/dist/index.d.ts
CHANGED
|
@@ -20,12 +20,12 @@ export { formatAgentQaDraft, formatMarkdownQaDraft, generateQaDraft } from "./qa
|
|
|
20
20
|
export { formatMarkdownReviewReport, formatReviewReport, reviewProject } from "./review.js";
|
|
21
21
|
export { scanProject } from "./scanner.js";
|
|
22
22
|
export { collectTestSuiteInventory, evaluateFlowCoverageEvidence, summarizeTestSuiteInventory } from "./test-evidence.js";
|
|
23
|
-
export { formatMarkdownTestPlan, generateTestPlan } from "./test-plan.js";
|
|
23
|
+
export { addedDiffTextFromEvidence, collectAddedDiffEvidence, collectAddedDiffText, formatMarkdownTestPlan, generateTestPlan, } from "./test-plan.js";
|
|
24
24
|
export { formatMarkdownVerifyReport, formatVerifyReport, verifyChange } from "./verify.js";
|
|
25
25
|
export type { BehaviorAdapterConfidence, BehaviorAdapterDetection, BehaviorAdapterRun, BehaviorAnalysisContext, BehaviorAnalyzerAdapter, BehaviorAttributeValue, BehaviorChangedFile, BehaviorConfidence, BehaviorDiagnostic, BehaviorDiagnosticSeverity, BehaviorEdge, BehaviorEdgeKind, BehaviorEvidence, BehaviorEvidenceKind, BehaviorGraph, BehaviorGraphFragment, BehaviorGraphSummary, BehaviorImpact, BehaviorImpactKind, BehaviorNode, BehaviorNodeKind, BehaviorSurfaceKind, InferredBehaviorCoverage, InferredBehaviorEntrypoint, InferredBehaviorFlow, InferredBehaviorSelector, InferredFlowAdapterOptions, } from "./behavior.js";
|
|
26
26
|
export type { ManifestBehaviorAdapterOptions } from "./behavior-manifest.js";
|
|
27
27
|
export type { ChangeIntentBehaviorAdapterOptions } from "./behavior-intent.js";
|
|
28
|
-
export type { BehaviorLifecycleStage, BehaviorLifecycleStageKind, ChangeIntent, ChangeIntentAnalysis, ChangeIntentAnalysisOptions, ChangeIntentCommit, ChangeIntentConfidence, ChangeIntentEvidence, ChangeIntentEvidenceKind, IntentQaScenario, IntentQaScenarioKind, IntentQaScenarioPriority, } from "./change-intent.js";
|
|
28
|
+
export type { BehaviorLifecycleStage, BehaviorLifecycleStageKind, ChangeIntent, ChangeIntentAnalysis, ChangeIntentAnalysisOptions, ChangeIntentCommit, ChangeIntentConfidence, ChangeIntentEvidence, ChangeIntentEvidenceKind, ChangeIntentEvidenceRelation, IntentQaScenario, IntentQaScenarioKind, IntentQaScenarioPriority, } from "./change-intent.js";
|
|
29
29
|
export type { CoverageEvidence, CoverageEvidenceConfidence, CoverageEvidenceStatus, FlowCoverageInput, TestSuiteEvidenceFile, TestSuiteInventory, TestSuiteSummary, } from "./test-evidence.js";
|
|
30
30
|
export type { DoctorArea, DoctorPriority, DoctorResult } from "./doctor.js";
|
|
31
31
|
export type { DomainLanguageConfidence, DomainLanguageSource, DomainLanguageSummary, DomainLanguageTerm, DomainScenarioSuggestion, } from "./domain-language.js";
|
|
@@ -38,7 +38,7 @@ export type { E2ePlanHistorySnapshot, LocalHistoryInitResult, LocalHistoryRefere
|
|
|
38
38
|
export type { DomainManifestSuggestionResult, FlowManifestSuggestionResult, ManifestPromotionCandidate, ManifestPromotionPlan, ManifestPromotionStatus, ManifestSuggestionOptions, } from "./manifest-suggestions.js";
|
|
39
39
|
export type { LoadedVerificationManifest, VerificationManifest, VerificationManifestAnchor, VerificationManifestAnchorKind, VerificationManifestCheck, VerificationManifestCheckType, VerificationManifestConfidence, VerificationManifestContext, VerificationManifestContextOptions, VerificationManifestContextResult, VerificationManifestContextRoleSummary, VerificationManifestCriticality, VerificationManifestDomain, VerificationManifestFlow, VerificationManifestInitOptions, VerificationManifestInitResult, VerificationManifestInstructionFile, VerificationManifestInstructionKind, VerificationManifestInstructionRole, VerificationManifestLoadOptions, VerificationManifestExplainOptions, VerificationManifestExplainResult, VerificationManifestMatch, VerificationManifestMatchKind, VerificationManifestRunner, VerificationManifestSource, VerificationManifestSourceKind, VerificationManifestValidationIssue, VerificationManifestValidationResult, VerificationManifestValidationSeverity, VerificationManifestValidationStatus, } from "./manifest.js";
|
|
40
40
|
export type { ChangedFile, ChangedRiskyFinding, ReviewOptions, ReviewResult } from "./review.js";
|
|
41
|
-
export type { TestPlanChangedFile, TestPlanItem, TestPlanOptions, TestPlanResult } from "./test-plan.js";
|
|
41
|
+
export type { AddedDiffEvidence, AddedDiffHunk, AddedDiffLine, AddedDiffTextOptions, TestPlanChangedFile, TestPlanItem, TestPlanOptions, TestPlanResult, } from "./test-plan.js";
|
|
42
42
|
export type { QAMapConfig, Finding, ScanCounts, ScanOptions, ScanResult, Severity } from "./types.js";
|
|
43
43
|
export type { VerifyOptions, VerifyResult } from "./verify.js";
|
|
44
44
|
export type { QaDraftFlow, QaDraftMissingEvidence, QaDraftOptions, QaDraftResult } from "./qa.js";
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,6 @@ export { formatAgentQaDraft, formatMarkdownQaDraft, generateQaDraft } from "./qa
|
|
|
20
20
|
export { formatMarkdownReviewReport, formatReviewReport, reviewProject } from "./review.js";
|
|
21
21
|
export { scanProject } from "./scanner.js";
|
|
22
22
|
export { collectTestSuiteInventory, evaluateFlowCoverageEvidence, summarizeTestSuiteInventory } from "./test-evidence.js";
|
|
23
|
-
export { formatMarkdownTestPlan, generateTestPlan } from "./test-plan.js";
|
|
23
|
+
export { addedDiffTextFromEvidence, collectAddedDiffEvidence, collectAddedDiffText, formatMarkdownTestPlan, generateTestPlan, } from "./test-plan.js";
|
|
24
24
|
export { formatMarkdownVerifyReport, formatVerifyReport, verifyChange } from "./verify.js";
|
|
25
25
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,0BAA0B,EAC1B,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,iCAAiC,EACjC,2BAA2B,GAC5B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,iCAAiC,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EACL,yBAAyB,EACzB,kBAAkB,EAClB,YAAY,EACZ,0BAA0B,GAC3B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAChG,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,cAAc,EACd,4BAA4B,GAC7B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,6BAA6B,EAC7B,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kCAAkC,EAClC,gCAAgC,EAChC,8BAA8B,EAC9B,4BAA4B,EAC5B,gCAAgC,EAChC,8BAA8B,EAC9B,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,kCAAkC,EAClC,kCAAkC,EAClC,+BAA+B,EAC/B,2BAA2B,EAC3B,uCAAuC,EACvC,uCAAuC,EACvC,oCAAoC,EACpC,0CAA0C,EAC1C,8BAA8B,EAC9B,wBAAwB,EACxB,yBAAyB,EACzB,4BAA4B,EAC5B,6BAA6B,EAC7B,iCAAiC,GAClC,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC9G,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AACrF,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,yBAAyB,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAC1H,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,0BAA0B,EAC1B,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,iCAAiC,EACjC,2BAA2B,GAC5B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,iCAAiC,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EACL,yBAAyB,EACzB,kBAAkB,EAClB,YAAY,EACZ,0BAA0B,GAC3B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAChG,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,cAAc,EACd,4BAA4B,GAC7B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,6BAA6B,EAC7B,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kCAAkC,EAClC,gCAAgC,EAChC,8BAA8B,EAC9B,4BAA4B,EAC5B,gCAAgC,EAChC,8BAA8B,EAC9B,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,kCAAkC,EAClC,kCAAkC,EAClC,+BAA+B,EAC/B,2BAA2B,EAC3B,uCAAuC,EACvC,uCAAuC,EACvC,oCAAoC,EACpC,0CAA0C,EAC1C,8BAA8B,EAC9B,wBAAwB,EACxB,yBAAyB,EACzB,4BAA4B,EAC5B,6BAA6B,EAC7B,iCAAiC,GAClC,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC9G,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AACrF,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,yBAAyB,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAC1H,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/qa.js
CHANGED
|
@@ -7,8 +7,9 @@ export async function generateQaDraft(rootInput, options = {}) {
|
|
|
7
7
|
...options,
|
|
8
8
|
dryRun: true,
|
|
9
9
|
});
|
|
10
|
-
const
|
|
11
|
-
const
|
|
10
|
+
const qaFiles = draft.plan.changedFiles.length > 0 ? draft.files : [];
|
|
11
|
+
const flows = qaFiles.map((file) => qaFlowFromDraftFile(file));
|
|
12
|
+
const missingEvidence = buildMissingEvidence(qaFiles);
|
|
12
13
|
return {
|
|
13
14
|
tool: {
|
|
14
15
|
name: TOOL_NAME,
|
|
@@ -36,6 +37,7 @@ export async function generateQaDraft(rootInput, options = {}) {
|
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
const agentListLimit = 6;
|
|
40
|
+
const agentPayloadByteLimit = 8 * 1024 - 1;
|
|
39
41
|
function truncateForAgent(value, maxLength = 140) {
|
|
40
42
|
return value.length > maxLength ? `${value.slice(0, maxLength - 1)}…` : value;
|
|
41
43
|
}
|
|
@@ -45,7 +47,7 @@ export function formatAgentQaDraft(result) {
|
|
|
45
47
|
.slice(0, 8)
|
|
46
48
|
.map((item) => ({ flow: truncateForAgent(item.flowTitle, 80), kind: item.kind, title: truncateForAgent(item.title) }));
|
|
47
49
|
const requiredBootstrap = result.bootstrap.steps
|
|
48
|
-
.filter((step) => step.status === "required")
|
|
50
|
+
.filter((step) => step.status === "required" && step.category !== "runner")
|
|
49
51
|
.slice(0, 3)
|
|
50
52
|
.map((step) => ({ title: truncateForAgent(step.title, 80), action: truncateForAgent(step.action) }));
|
|
51
53
|
const summary = {
|
|
@@ -57,33 +59,49 @@ export function formatAgentQaDraft(result) {
|
|
|
57
59
|
manifest: result.manifestPath ?? null,
|
|
58
60
|
readiness: { score: result.readiness.score, level: result.readiness.level },
|
|
59
61
|
testSuite: { present: result.testSuite.hasTestSuite, files: result.testSuite.testFileCount },
|
|
62
|
+
intentCount: result.changeAnalysis.intents.length,
|
|
63
|
+
omittedIntentCount: Math.max(0, result.changeAnalysis.intents.length - 3),
|
|
60
64
|
intents: result.changeAnalysis.intents.slice(0, 3).map((intent) => ({
|
|
61
65
|
title: truncateForAgent(intent.title, 100),
|
|
62
66
|
confidence: intent.confidence,
|
|
63
67
|
reviewRequired: intent.reviewRequired,
|
|
64
|
-
evidence: intent.evidence.slice(0,
|
|
65
|
-
|
|
68
|
+
evidence: intent.evidence.slice(0, 2).map((item) => truncateForAgent(item.value, 100)),
|
|
69
|
+
sources: strongestEvidence(intent.evidence, 1).map(formatAgentEvidenceSource),
|
|
70
|
+
lifecycle: intent.lifecycle.slice(0, 6).map((stage) => ({
|
|
66
71
|
phase: stage.kind,
|
|
67
72
|
label: truncateForAgent(stage.label, 120),
|
|
68
73
|
})),
|
|
69
|
-
|
|
74
|
+
scenarioCount: intent.scenarios.length,
|
|
75
|
+
omittedScenarioCount: Math.max(0, intent.scenarios.length - 2),
|
|
76
|
+
scenarios: intent.scenarios.slice(0, 2).map((scenario) => ({
|
|
70
77
|
priority: scenario.priority,
|
|
71
78
|
kind: scenario.kind,
|
|
72
79
|
title: truncateForAgent(scenario.title, 100),
|
|
73
|
-
|
|
80
|
+
confidence: scenario.confidence ?? "low",
|
|
81
|
+
reviewRequired: scenario.reviewRequired ?? true,
|
|
82
|
+
sources: strongestEvidence(scenario.evidence, 1).map(formatAgentEvidenceSource),
|
|
83
|
+
assertions: scenario.assertions.slice(0, 2).map((assertion) => truncateForAgent(assertion, 120)),
|
|
74
84
|
})),
|
|
75
85
|
})),
|
|
76
|
-
|
|
77
|
-
?
|
|
86
|
+
automation: needsGeneratedDraft(result)
|
|
87
|
+
? {
|
|
88
|
+
optIn: true,
|
|
89
|
+
adapter: result.runner,
|
|
90
|
+
setupStatus: result.runnerSetup.status,
|
|
91
|
+
draftCommand: `qamap e2e draft . --base ${result.base} --head ${result.head}`,
|
|
92
|
+
setupCommand: result.runnerSetup.status === "proposed" ? result.runnerSetup.setupCommand : undefined,
|
|
93
|
+
}
|
|
78
94
|
: undefined,
|
|
95
|
+
flowCount: result.flows.length,
|
|
96
|
+
omittedFlowCount: Math.max(0, result.flows.length - agentListLimit),
|
|
79
97
|
flows: result.flows.slice(0, agentListLimit).map((flow) => ({
|
|
80
98
|
title: truncateForAgent(flow.title, 80),
|
|
81
|
-
source: flow.source,
|
|
82
|
-
draft: flow.draftPath,
|
|
99
|
+
source: truncateForAgent(flow.source, 60),
|
|
100
|
+
draft: truncateForAgent(flow.draftPath, 140),
|
|
83
101
|
runnable: flow.runnableStatus,
|
|
84
102
|
verificationMode: flow.verificationMode,
|
|
85
|
-
entry: flow.entrypointHints[0],
|
|
86
|
-
changedFiles: flow.changedFiles.slice(0, 4),
|
|
103
|
+
entry: flow.entrypointHints[0] ? truncateForAgent(flow.entrypointHints[0], 140) : undefined,
|
|
104
|
+
changedFiles: flow.changedFiles.slice(0, 4).map((file) => truncateForAgent(file, 140)),
|
|
87
105
|
reviewQuestion: flow.userJourney?.reviewQuestion
|
|
88
106
|
? truncateForAgent(flow.userJourney.reviewQuestion, 180)
|
|
89
107
|
: undefined,
|
|
@@ -92,16 +110,147 @@ export function formatAgentQaDraft(result) {
|
|
|
92
110
|
: undefined,
|
|
93
111
|
steps: flow.draftSteps.slice(0, agentListLimit).map((step) => truncateForAgent(step)),
|
|
94
112
|
selectors: flow.selectorHints.slice(0, 5).map((selector) => truncateForAgent(selector, 100)),
|
|
95
|
-
existingEvidence: flow.existingEvidencePaths.length > 0
|
|
113
|
+
existingEvidence: flow.existingEvidencePaths.length > 0
|
|
114
|
+
? flow.existingEvidencePaths.slice(0, 4).map((file) => truncateForAgent(file, 140))
|
|
115
|
+
: undefined,
|
|
96
116
|
evidence: flow.why.slice(0, 2).map((reason) => truncateForAgent(reason)),
|
|
97
117
|
})),
|
|
98
118
|
requiredEvidence,
|
|
99
119
|
recommendedEvidenceCount: result.missingEvidence.filter((item) => item.priority === "recommended").length,
|
|
100
120
|
requiredBootstrap,
|
|
101
121
|
prChecklist: result.prChecklist.slice(0, agentListLimit).map((item) => truncateForAgent(item)),
|
|
102
|
-
commands: result.suggestedCommands.slice(0, 4),
|
|
122
|
+
commands: result.suggestedCommands.slice(0, 4).map((command) => truncateForAgent(command, 180)),
|
|
103
123
|
};
|
|
104
|
-
return `${
|
|
124
|
+
return `${serializeAgentSummary(summary)}\n`;
|
|
125
|
+
}
|
|
126
|
+
function serializeAgentSummary(summary) {
|
|
127
|
+
const payload = JSON.stringify(summary);
|
|
128
|
+
if (Buffer.byteLength(payload) <= agentPayloadByteLimit) {
|
|
129
|
+
return payload;
|
|
130
|
+
}
|
|
131
|
+
const compact = {
|
|
132
|
+
...summary,
|
|
133
|
+
intents: summary.intents.slice(0, 2).map((intent) => ({
|
|
134
|
+
...intent,
|
|
135
|
+
lifecycle: intent.lifecycle.slice(0, 4),
|
|
136
|
+
scenarios: intent.scenarios.slice(0, 2).map((scenario) => ({
|
|
137
|
+
...scenario,
|
|
138
|
+
assertions: scenario.assertions.slice(0, 1),
|
|
139
|
+
})),
|
|
140
|
+
})),
|
|
141
|
+
flows: summary.flows.slice(0, 3).map((flow) => ({
|
|
142
|
+
...flow,
|
|
143
|
+
changedFiles: flow.changedFiles.slice(0, 2),
|
|
144
|
+
steps: flow.steps.slice(0, 3),
|
|
145
|
+
selectors: flow.selectors.slice(0, 2),
|
|
146
|
+
existingEvidence: flow.existingEvidence?.slice(0, 2),
|
|
147
|
+
evidence: flow.evidence.slice(0, 1),
|
|
148
|
+
})),
|
|
149
|
+
omittedIntentCount: Math.max(0, numericCount(summary.intentCount) - Math.min(2, summary.intents.length)),
|
|
150
|
+
omittedFlowCount: Math.max(0, numericCount(summary.flowCount) - Math.min(3, summary.flows.length)),
|
|
151
|
+
requiredEvidence: summary.requiredEvidence.slice(0, 5),
|
|
152
|
+
requiredBootstrap: summary.requiredBootstrap.slice(0, 2),
|
|
153
|
+
prChecklist: summary.prChecklist.slice(0, 4),
|
|
154
|
+
commands: summary.commands.slice(0, 3),
|
|
155
|
+
compaction: { maxBytes: agentPayloadByteLimit, originalBytes: Buffer.byteLength(payload) },
|
|
156
|
+
};
|
|
157
|
+
const compactPayload = JSON.stringify(compact);
|
|
158
|
+
if (Buffer.byteLength(compactPayload) <= agentPayloadByteLimit) {
|
|
159
|
+
return compactPayload;
|
|
160
|
+
}
|
|
161
|
+
const minimalIntents = compact.intents.slice(0, 1).map((intent) => ({
|
|
162
|
+
...intent,
|
|
163
|
+
lifecycle: intent.lifecycle.slice(0, 3),
|
|
164
|
+
omittedScenarioCount: Math.max(0, (intent.scenarioCount ?? intent.scenarios.length) - 1),
|
|
165
|
+
scenarios: intent.scenarios.slice(0, 1),
|
|
166
|
+
}));
|
|
167
|
+
const minimalFlows = compact.flows.slice(0, 1).map((flow) => ({
|
|
168
|
+
...flow,
|
|
169
|
+
steps: flow.steps.slice(0, 2),
|
|
170
|
+
selectors: flow.selectors.slice(0, 1),
|
|
171
|
+
existingEvidence: flow.existingEvidence?.slice(0, 1),
|
|
172
|
+
}));
|
|
173
|
+
const minimalPayload = JSON.stringify({
|
|
174
|
+
...compact,
|
|
175
|
+
omittedIntentCount: Math.max(0, numericCount(summary.intentCount) - minimalIntents.length),
|
|
176
|
+
intents: minimalIntents,
|
|
177
|
+
omittedFlowCount: Math.max(0, numericCount(summary.flowCount) - minimalFlows.length),
|
|
178
|
+
flows: minimalFlows,
|
|
179
|
+
requiredEvidence: compact.requiredEvidence.slice(0, 3),
|
|
180
|
+
requiredBootstrap: compact.requiredBootstrap.slice(0, 1),
|
|
181
|
+
prChecklist: compact.prChecklist.slice(0, 2),
|
|
182
|
+
commands: compact.commands.slice(0, 2),
|
|
183
|
+
});
|
|
184
|
+
if (Buffer.byteLength(minimalPayload) <= agentPayloadByteLimit) {
|
|
185
|
+
return minimalPayload;
|
|
186
|
+
}
|
|
187
|
+
return JSON.stringify({
|
|
188
|
+
schema: summary.schema,
|
|
189
|
+
base: truncateForAgent(String(summary.base ?? ""), 180),
|
|
190
|
+
head: truncateForAgent(String(summary.head ?? ""), 180),
|
|
191
|
+
project: summary.project,
|
|
192
|
+
runner: summary.runner,
|
|
193
|
+
manifest: summary.manifest ? truncateForAgent(String(summary.manifest), 180) : null,
|
|
194
|
+
readiness: summary.readiness,
|
|
195
|
+
testSuite: summary.testSuite,
|
|
196
|
+
intentCount: summary.intentCount,
|
|
197
|
+
omittedIntentCount: summary.intentCount,
|
|
198
|
+
intents: [],
|
|
199
|
+
flowCount: summary.flowCount,
|
|
200
|
+
omittedFlowCount: summary.flowCount,
|
|
201
|
+
flows: [],
|
|
202
|
+
requiredEvidence: [],
|
|
203
|
+
recommendedEvidenceCount: summary.recommendedEvidenceCount,
|
|
204
|
+
requiredBootstrap: [],
|
|
205
|
+
prChecklist: [],
|
|
206
|
+
commands: [],
|
|
207
|
+
compaction: { maxBytes: agentPayloadByteLimit, originalBytes: Buffer.byteLength(payload), emergency: true },
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
function numericCount(value) {
|
|
211
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
212
|
+
}
|
|
213
|
+
function formatAgentEvidenceSource(evidence) {
|
|
214
|
+
const source = {
|
|
215
|
+
kind: evidence.kind,
|
|
216
|
+
reason: truncateForAgent(evidence.value, 90),
|
|
217
|
+
};
|
|
218
|
+
if (evidence.commit)
|
|
219
|
+
source.commit = evidence.commit.slice(0, 12);
|
|
220
|
+
if (evidence.file)
|
|
221
|
+
source.file = evidence.file;
|
|
222
|
+
if (evidence.previousFile)
|
|
223
|
+
source.previousFile = evidence.previousFile;
|
|
224
|
+
if (evidence.symbol)
|
|
225
|
+
source.symbol = evidence.symbol;
|
|
226
|
+
if (evidence.relation)
|
|
227
|
+
source.relation = evidence.relation;
|
|
228
|
+
if (evidence.side)
|
|
229
|
+
source.side = evidence.side;
|
|
230
|
+
if (evidence.startLine !== undefined)
|
|
231
|
+
source.startLine = evidence.startLine;
|
|
232
|
+
if (evidence.endLine !== undefined)
|
|
233
|
+
source.endLine = evidence.endLine;
|
|
234
|
+
if (evidence.hunkHeader)
|
|
235
|
+
source.hunk = truncateForAgent(evidence.hunkHeader, 80);
|
|
236
|
+
return source;
|
|
237
|
+
}
|
|
238
|
+
function strongestEvidence(evidence, limit) {
|
|
239
|
+
return evidence
|
|
240
|
+
.map((item, index) => ({ item, index }))
|
|
241
|
+
.sort((left, right) => evidenceStrength(right.item) - evidenceStrength(left.item) || left.index - right.index)
|
|
242
|
+
.slice(0, limit)
|
|
243
|
+
.map(({ item }) => item);
|
|
244
|
+
}
|
|
245
|
+
function evidenceStrength(evidence) {
|
|
246
|
+
const relationScore = evidence.relation === "direct" ? 2 : evidence.relation === "supporting" ? 1 : 0;
|
|
247
|
+
if (evidence.kind === "diff" && evidence.file && evidence.startLine !== undefined)
|
|
248
|
+
return 4 + relationScore;
|
|
249
|
+
if (evidence.kind === "diff" && evidence.file)
|
|
250
|
+
return 2 + relationScore;
|
|
251
|
+
if (evidence.commit)
|
|
252
|
+
return 1;
|
|
253
|
+
return 0;
|
|
105
254
|
}
|
|
106
255
|
export function formatMarkdownQaDraft(result) {
|
|
107
256
|
const lines = [];
|
|
@@ -147,17 +296,20 @@ export function formatMarkdownQaDraft(result) {
|
|
|
147
296
|
lines.push(`- Verification mode: ${formatVerificationMode(primaryFlow.verificationMode)}; no new product-journey E2E draft is proposed.`);
|
|
148
297
|
}
|
|
149
298
|
else {
|
|
150
|
-
lines.push(`-
|
|
299
|
+
lines.push(`- QA proposal: ${primaryFlow.draftSteps.length || fallbackDraftSteps(primaryFlow).length} review steps; executable automation remains optional.`);
|
|
151
300
|
}
|
|
152
301
|
}
|
|
153
|
-
|
|
302
|
+
const nextCommand = nextStepCommand(result);
|
|
303
|
+
if (nextCommand) {
|
|
304
|
+
lines.push(`- Repository validation: \`${escapeMarkdownInline(nextCommand)}\``);
|
|
305
|
+
}
|
|
154
306
|
const blocking = result.missingEvidence.filter((item) => item.priority === "required").slice(0, 2);
|
|
155
307
|
if (blocking.length === 0) {
|
|
156
|
-
lines.push("-
|
|
308
|
+
lines.push("- QA proposal gaps: no required evidence gap detected; review the scenario sources and run repository validation.");
|
|
157
309
|
}
|
|
158
310
|
else {
|
|
159
311
|
for (const [index, item] of blocking.entries()) {
|
|
160
|
-
lines.push(`-
|
|
312
|
+
lines.push(`- QA proposal gap${blocking.length > 1 ? ` ${index + 1}` : ""}: ${escapeMarkdownInline(item.title)}: ${escapeMarkdownInline(item.detail)}`);
|
|
161
313
|
}
|
|
162
314
|
}
|
|
163
315
|
lines.push("");
|
|
@@ -167,37 +319,11 @@ export function formatMarkdownQaDraft(result) {
|
|
|
167
319
|
lines.push(`- Base: \`${escapeMarkdownInline(result.base)}\``);
|
|
168
320
|
lines.push(`- Head: \`${escapeMarkdownInline(result.head)}\``);
|
|
169
321
|
lines.push(`- Project: ${formatProjectType(result.project)}`);
|
|
170
|
-
lines.push(`- Automation adapter: ${formatRunnerName(result.runner)}`);
|
|
171
322
|
lines.push(`- Manifest: ${result.manifestPath ? `\`${escapeMarkdownInline(result.manifestPath)}\`` : "not found; using repo signals and PR diff only"}`);
|
|
172
323
|
lines.push(`- Stage: ${formatDraftReadinessStage(result.readiness)}`);
|
|
173
324
|
lines.push(`- Draft flows: ${result.flows.length}`);
|
|
174
325
|
lines.push("");
|
|
175
326
|
appendQaChangeIntentMarkdown(lines, result);
|
|
176
|
-
if (!result.testSuite.hasTestSuite && needsGeneratedDraft(result)) {
|
|
177
|
-
lines.push("## First E2E Draft Bootstrap");
|
|
178
|
-
lines.push("");
|
|
179
|
-
lines.push(`QAMap did not find committed test files for this target. The next step is to create the first runnable starter draft, not to stop at a checklist.`);
|
|
180
|
-
lines.push("");
|
|
181
|
-
lines.push(`- Recommended first runner: ${formatRunnerName(result.runner)}`);
|
|
182
|
-
lines.push(`- Create command: \`${escapeMarkdownInline(firstDraftCreateCommand(result))}\``);
|
|
183
|
-
const installCommand = result.runnerSetup.installCommands[0];
|
|
184
|
-
if (installCommand) {
|
|
185
|
-
lines.push(`- Install command: \`${escapeMarkdownInline(installCommand)}\``);
|
|
186
|
-
}
|
|
187
|
-
const filesToCreate = uniqueStrings([...result.runnerSetup.filesToCreate, ...result.flows.map((flow) => flow.draftPath)]);
|
|
188
|
-
if (filesToCreate.length > 0) {
|
|
189
|
-
lines.push("- Draft files QAMap can create:");
|
|
190
|
-
for (const file of filesToCreate.slice(0, 6)) {
|
|
191
|
-
lines.push(` - \`${escapeMarkdownInline(file)}\``);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
const filesToUpdate = result.runnerSetup.filesToUpdate;
|
|
195
|
-
if (filesToUpdate.length > 0) {
|
|
196
|
-
lines.push(`- Files to update: ${filesToUpdate.map((file) => `\`${escapeMarkdownInline(file)}\``).join(", ")}`);
|
|
197
|
-
}
|
|
198
|
-
lines.push("- Generated drafts are starter E2E code; keep them review-only until the changed flow has real assertions, fixtures, and selectors.");
|
|
199
|
-
lines.push("");
|
|
200
|
-
}
|
|
201
327
|
lines.push("## PR Comment Draft");
|
|
202
328
|
lines.push("");
|
|
203
329
|
lines.push("### Affected Flow");
|
|
@@ -222,7 +348,7 @@ export function formatMarkdownQaDraft(result) {
|
|
|
222
348
|
}
|
|
223
349
|
}
|
|
224
350
|
lines.push("");
|
|
225
|
-
lines.push("### Suggested
|
|
351
|
+
lines.push("### Suggested QA Scenarios");
|
|
226
352
|
lines.push("");
|
|
227
353
|
for (const flow of result.flows) {
|
|
228
354
|
if (flow.existingEvidencePaths.length > 0) {
|
|
@@ -232,7 +358,7 @@ export function formatMarkdownQaDraft(result) {
|
|
|
232
358
|
lines.push(`- Run ${formatVerificationMode(flow.verificationMode)} with the suggested repository validation command; no new product-journey E2E draft is proposed.`);
|
|
233
359
|
}
|
|
234
360
|
else {
|
|
235
|
-
lines.push(`-
|
|
361
|
+
lines.push(`- ${escapeMarkdownInline(flow.title)}`);
|
|
236
362
|
}
|
|
237
363
|
const routeHint = flow.entrypointHints.find((hint) => hint.startsWith("route:"));
|
|
238
364
|
if (routeHint) {
|
|
@@ -250,7 +376,7 @@ export function formatMarkdownQaDraft(result) {
|
|
|
250
376
|
}
|
|
251
377
|
}
|
|
252
378
|
lines.push("");
|
|
253
|
-
lines.push("###
|
|
379
|
+
lines.push("### Evidence Gaps In This QA Proposal");
|
|
254
380
|
lines.push("");
|
|
255
381
|
if (result.missingEvidence.length === 0) {
|
|
256
382
|
lines.push("- No required evidence gap was detected in the generated QA draft. Still run the project validation command before merge.");
|
|
@@ -276,6 +402,20 @@ export function formatMarkdownQaDraft(result) {
|
|
|
276
402
|
lines.push(`- ${escapeMarkdownInline(item)}`);
|
|
277
403
|
}
|
|
278
404
|
lines.push("");
|
|
405
|
+
if (needsGeneratedDraft(result)) {
|
|
406
|
+
lines.push("## Optional Automation");
|
|
407
|
+
lines.push("");
|
|
408
|
+
lines.push("The QA judgment above does not require adopting this adapter. Use this section only when the team wants to turn an accepted scenario into executable coverage.");
|
|
409
|
+
lines.push("");
|
|
410
|
+
lines.push(`- Adapter candidate: ${formatRunnerName(result.runner)}`);
|
|
411
|
+
lines.push(`- Draft target: \`${escapeMarkdownInline(result.flows[0]?.draftPath ?? "generated E2E file")}\` (${formatRunnableStatus(result.flows[0]?.runnableStatus)})`);
|
|
412
|
+
lines.push(`- Preview or create a draft: \`qamap e2e draft . --base ${escapeMarkdownInline(result.base)} --head ${escapeMarkdownInline(result.head)}\``);
|
|
413
|
+
if (result.runnerSetup.status === "proposed" && result.runnerSetup.setupCommand) {
|
|
414
|
+
lines.push(`- If the team accepts this adapter, inspect its setup proposal: \`${escapeMarkdownInline(result.runnerSetup.setupCommand)}\``);
|
|
415
|
+
}
|
|
416
|
+
lines.push("- Treat generated code as review-only until its scenario sources, assertions, fixtures, and selectors are confirmed.");
|
|
417
|
+
lines.push("");
|
|
418
|
+
}
|
|
279
419
|
return lines.join("\n");
|
|
280
420
|
}
|
|
281
421
|
function appendQaChangeIntentMarkdown(lines, result) {
|
|
@@ -295,11 +435,20 @@ function appendQaChangeIntentMarkdown(lines, result) {
|
|
|
295
435
|
}
|
|
296
436
|
lines.push("- Lifecycle:");
|
|
297
437
|
for (const stage of intent.lifecycle.slice(0, 10)) {
|
|
298
|
-
|
|
438
|
+
const source = stage.evidence.find((item) => item.file || item.commit);
|
|
439
|
+
const sourceSuffix = source ? ` (${formatEvidenceReference(source)})` : "";
|
|
440
|
+
lines.push(` - ${stage.kind}: ${escapeMarkdownInline(stage.label)}${sourceSuffix}`);
|
|
299
441
|
}
|
|
300
442
|
lines.push("- QA scenarios:");
|
|
301
443
|
for (const scenario of intent.scenarios.slice(0, 4)) {
|
|
302
|
-
|
|
444
|
+
const confidence = scenario.confidence ?? "low";
|
|
445
|
+
const reviewRequired = scenario.reviewRequired ?? true;
|
|
446
|
+
lines.push(` - [${scenario.priority}] ${escapeMarkdownInline(scenario.title)} ` +
|
|
447
|
+
`(confidence: ${confidence}${reviewRequired ? "; review required" : ""})`);
|
|
448
|
+
lines.push(` - Why: ${escapeMarkdownInline(scenario.rationale)}`);
|
|
449
|
+
for (const source of strongestEvidence(scenario.evidence, 3)) {
|
|
450
|
+
lines.push(` - Source: ${formatEvidenceReference(source)}: ${escapeMarkdownInline(source.value)}`);
|
|
451
|
+
}
|
|
303
452
|
for (const assertion of scenario.assertions.slice(0, 2)) {
|
|
304
453
|
lines.push(` - Assert: ${escapeMarkdownInline(assertion)}`);
|
|
305
454
|
}
|
|
@@ -307,6 +456,20 @@ function appendQaChangeIntentMarkdown(lines, result) {
|
|
|
307
456
|
lines.push("");
|
|
308
457
|
}
|
|
309
458
|
}
|
|
459
|
+
function formatEvidenceReference(evidence) {
|
|
460
|
+
if (evidence.commit) {
|
|
461
|
+
return `commit \`${evidence.commit.slice(0, 12)}\``;
|
|
462
|
+
}
|
|
463
|
+
const lineRange = evidence.startLine === undefined
|
|
464
|
+
? ""
|
|
465
|
+
: evidence.endLine !== undefined && evidence.endLine !== evidence.startLine
|
|
466
|
+
? `:${evidence.startLine}-${evidence.endLine}`
|
|
467
|
+
: `:${evidence.startLine}`;
|
|
468
|
+
const location = evidence.file ? `\`${escapeMarkdownInline(evidence.file)}${lineRange}\`` : evidence.kind;
|
|
469
|
+
const symbol = evidence.symbol ? ` symbol \`${escapeMarkdownInline(evidence.symbol)}\`` : "";
|
|
470
|
+
const qualifiers = [evidence.relation, evidence.side].filter(Boolean).join(", ");
|
|
471
|
+
return `${location}${symbol}${qualifiers ? ` [${qualifiers}]` : ""}`;
|
|
472
|
+
}
|
|
310
473
|
function summarizeIntentLifecycle(lifecycle) {
|
|
311
474
|
const preferredPhases = ["trigger", "condition", "state-change", "side-effect", "observable-outcome"];
|
|
312
475
|
const selected = preferredPhases
|
|
@@ -316,15 +479,11 @@ function summarizeIntentLifecycle(lifecycle) {
|
|
|
316
479
|
return fallback.map((stage) => `${stage.kind}: ${stage.label}`).join(" -> ");
|
|
317
480
|
}
|
|
318
481
|
function nextStepCommand(result) {
|
|
319
|
-
if (!result.testSuite.hasTestSuite && needsGeneratedDraft(result)) {
|
|
320
|
-
return firstDraftCreateCommand(result);
|
|
321
|
-
}
|
|
322
482
|
const validationCommand = result.suggestedCommands[0];
|
|
323
483
|
if (validationCommand) {
|
|
324
484
|
return validationCommand;
|
|
325
485
|
}
|
|
326
|
-
|
|
327
|
-
return draftPath ? `review ${draftPath}` : "qamap e2e draft . --dry-run";
|
|
486
|
+
return undefined;
|
|
328
487
|
}
|
|
329
488
|
function atAGlanceEvidence(flow) {
|
|
330
489
|
const stableSelector = flow.selectorHints.find((selector) => /^(?:web-test-id|test-id|input-web-test-id|input-test-id|accessibility-label|role-button):/i.test(selector)) ?? flow.selectorHints[0];
|
|
@@ -335,12 +494,6 @@ function atAGlanceEvidence(flow) {
|
|
|
335
494
|
].filter((value) => Boolean(value));
|
|
336
495
|
return uniqueStrings(evidence).slice(0, 3);
|
|
337
496
|
}
|
|
338
|
-
function firstDraftCreateCommand(result) {
|
|
339
|
-
if (result.runnerSetup.setupCommand) {
|
|
340
|
-
return result.runnerSetup.setupCommand;
|
|
341
|
-
}
|
|
342
|
-
return `qamap e2e draft . --base ${result.base} --head ${result.head}`;
|
|
343
|
-
}
|
|
344
497
|
function qaFlowFromDraftFile(file) {
|
|
345
498
|
const verificationMode = verificationModeForTitle(file.flowTitle);
|
|
346
499
|
return {
|
|
@@ -387,20 +540,10 @@ function buildMissingEvidence(files) {
|
|
|
387
540
|
const evidence = [];
|
|
388
541
|
for (const file of files) {
|
|
389
542
|
for (const item of file.actionItems ?? []) {
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
const actionText = normalizeEvidenceText((file.actionItems ?? []).map((item) => `${item.title} ${item.detail}`).join(" "));
|
|
393
|
-
for (const blocker of file.executionBlockers ?? []) {
|
|
394
|
-
if (actionTextCoversBlocker(actionText, blocker)) {
|
|
543
|
+
if (item.kind === "runner" || item.kind === "validation") {
|
|
395
544
|
continue;
|
|
396
545
|
}
|
|
397
|
-
evidence.push(
|
|
398
|
-
flowTitle: file.flowTitle,
|
|
399
|
-
priority: "required",
|
|
400
|
-
kind: "blocker",
|
|
401
|
-
title: "Resolve execution blocker",
|
|
402
|
-
detail: blocker,
|
|
403
|
-
});
|
|
546
|
+
evidence.push(missingEvidenceFromAction(file, item));
|
|
404
547
|
}
|
|
405
548
|
}
|
|
406
549
|
const unique = uniqueMissingEvidence(evidence);
|
|
@@ -408,20 +551,6 @@ function buildMissingEvidence(files) {
|
|
|
408
551
|
const recommended = unique.filter((item) => item.priority !== "required");
|
|
409
552
|
return [...required, ...recommended].slice(0, 12);
|
|
410
553
|
}
|
|
411
|
-
function normalizeEvidenceText(value) {
|
|
412
|
-
return value.toLowerCase().replace(/[^a-z0-9\uAC00-\uD7A3 ]+/g, " ").replace(/\s+/g, " ").trim();
|
|
413
|
-
}
|
|
414
|
-
function actionTextCoversBlocker(actionText, blocker) {
|
|
415
|
-
if (!actionText) {
|
|
416
|
-
return false;
|
|
417
|
-
}
|
|
418
|
-
const blockerTokens = normalizeEvidenceText(blocker).split(" ").filter((token) => token.length > 3);
|
|
419
|
-
if (blockerTokens.length === 0) {
|
|
420
|
-
return false;
|
|
421
|
-
}
|
|
422
|
-
const covered = blockerTokens.filter((token) => actionText.includes(token)).length;
|
|
423
|
-
return covered / blockerTokens.length >= 0.7;
|
|
424
|
-
}
|
|
425
554
|
function missingEvidenceFromAction(file, item) {
|
|
426
555
|
return {
|
|
427
556
|
flowTitle: file.flowTitle,
|
|
@@ -448,9 +577,11 @@ function buildPrChecklist(draft, flows, missingEvidence) {
|
|
|
448
577
|
? `Run the changed test evidence: ${flows[0].existingEvidencePaths.slice(0, 4).join(", ")}.`
|
|
449
578
|
: flows[0]?.verificationMode
|
|
450
579
|
? `Run ${formatVerificationMode(flows[0].verificationMode)} with ${draft.plan.suggestedCommands[0] ?? "the nearest repository validation command"}.`
|
|
451
|
-
:
|
|
452
|
-
? `Review the
|
|
453
|
-
:
|
|
580
|
+
: draft.plan.changeAnalysis.intents[0]
|
|
581
|
+
? `Review the proposed QA scenarios and their sources for: ${draft.plan.changeAnalysis.intents[0].title}.`
|
|
582
|
+
: flows.length > 0
|
|
583
|
+
? `Review the affected-flow evidence for: ${flows.map((flow) => flow.title).slice(0, 3).join(", ")}.`
|
|
584
|
+
: "Run QAMap again after adding branch or working tree changes.",
|
|
454
585
|
flows[0]?.userJourney?.reviewQuestion
|
|
455
586
|
? `Answer the reviewer question: ${flows[0].userJourney.reviewQuestion}`
|
|
456
587
|
: "Name the user-visible behavior or contract this PR can break.",
|
|
@@ -477,9 +608,11 @@ function buildAgentHandoff(draft, flows, missingEvidence) {
|
|
|
477
608
|
? `Run the changed test evidence (${flows[0].existingEvidencePaths.slice(0, 3).join(", ")}) and record the result before handoff.`
|
|
478
609
|
: flows[0]?.verificationMode
|
|
479
610
|
? `Run ${formatVerificationMode(flows[0].verificationMode)} and record the command and result before handoff; do not invent a product-journey E2E for this diff alone.`
|
|
480
|
-
:
|
|
481
|
-
? `
|
|
482
|
-
:
|
|
611
|
+
: draft.plan.changeAnalysis.intents[0]
|
|
612
|
+
? `Review each proposed scenario and its diff sources for ${draft.plan.changeAnalysis.intents[0].title} before using it as PR policy.`
|
|
613
|
+
: flows.length > 0
|
|
614
|
+
? `Review the affected-flow evidence for ${flows[0].title} before using it as PR policy.`
|
|
615
|
+
: undefined,
|
|
483
616
|
missingEvidence.length > 0 ? "Close required evidence gaps before treating this QA draft as merge proof." : undefined,
|
|
484
617
|
flows.some((flow) => !flow.verificationMode)
|
|
485
618
|
? "A wrong flow recommendation should become a manifest correction, so future PRs improve without another prompt."
|