@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/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,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
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 flows = draft.files.map((file) => qaFlowFromDraftFile(file));
11
- const missingEvidence = buildMissingEvidence(draft.files);
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, 4).map((item) => truncateForAgent(item.value, 100)),
65
- lifecycle: intent.lifecycle.slice(0, 8).map((stage) => ({
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
- scenarios: intent.scenarios.slice(0, 4).map((scenario) => ({
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
- assertions: scenario.assertions.slice(0, 3).map((assertion) => truncateForAgent(assertion, 120)),
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
- firstDraftCommand: !result.testSuite.hasTestSuite && needsGeneratedDraft(result)
77
- ? firstDraftCreateCommand(result)
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 ? flow.existingEvidencePaths.slice(0, 4) : undefined,
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 `${JSON.stringify(summary)}\n`;
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(`- Proposed draft: \`${escapeMarkdownInline(primaryFlow.draftPath)}\` (${formatRunnableStatus(primaryFlow.runnableStatus)})`);
299
+ lines.push(`- QA proposal: ${primaryFlow.draftSteps.length || fallbackDraftSteps(primaryFlow).length} review steps; executable automation remains optional.`);
151
300
  }
152
301
  }
153
- lines.push(`- Next command: \`${escapeMarkdownInline(nextStepCommand(result))}\``);
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("- Missing before trust: no required evidence gap detected; review the draft and run the validation command.");
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(`- Missing before trust${blocking.length > 1 ? ` ${index + 1}` : ""}: ${escapeMarkdownInline(item.title)} ${escapeMarkdownInline(item.detail)}`);
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 E2E / QA Draft");
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(`- \`${escapeMarkdownInline(flow.draftPath)}\`: ${formatRunnableStatus(flow.runnableStatus)}`);
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("### Missing Evidence Before Trusting This PR");
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
- lines.push(` - ${stage.kind}: ${escapeMarkdownInline(stage.label)}`);
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
- lines.push(` - [${scenario.priority}] ${escapeMarkdownInline(scenario.title)}`);
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
- const draftPath = result.flows[0]?.draftPath;
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
- evidence.push(missingEvidenceFromAction(file, item));
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
- : flows.length > 0
452
- ? `Review the generated draft path: ${flows.map((flow) => flow.draftPath).slice(0, 3).join(", ")}.`
453
- : "Run QAMap again after adding branch or working tree changes.",
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
- : flows.length > 0
481
- ? `Start from ${flows[0].draftPath} and close required evidence before treating it as regression coverage.`
482
- : undefined,
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."