@ivorycanvas/qamap 0.3.5 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +37 -23
  3. package/dist/agent-init.js +2 -2
  4. package/dist/agent-init.js.map +1 -1
  5. package/dist/behavior-intent.d.ts +6 -0
  6. package/dist/behavior-intent.js +183 -0
  7. package/dist/behavior-intent.js.map +1 -0
  8. package/dist/behavior-manifest.d.ts +6 -0
  9. package/dist/behavior-manifest.js +221 -0
  10. package/dist/behavior-manifest.js.map +1 -0
  11. package/dist/behavior.d.ts +151 -0
  12. package/dist/behavior.js +458 -0
  13. package/dist/behavior.js.map +1 -0
  14. package/dist/change-intent.d.ts +81 -0
  15. package/dist/change-intent.js +884 -0
  16. package/dist/change-intent.js.map +1 -0
  17. package/dist/cli.js +13 -8
  18. package/dist/cli.js.map +1 -1
  19. package/dist/e2e.d.ts +16 -1
  20. package/dist/e2e.js +290 -25
  21. package/dist/e2e.js.map +1 -1
  22. package/dist/index.d.ts +11 -3
  23. package/dist/index.js +5 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/qa.d.ts +1 -0
  26. package/dist/qa.js +293 -88
  27. package/dist/qa.js.map +1 -1
  28. package/dist/terminal.js +1 -1
  29. package/dist/terminal.js.map +1 -1
  30. package/dist/test-plan.d.ts +18 -0
  31. package/dist/test-plan.js +123 -13
  32. package/dist/test-plan.js.map +1 -1
  33. package/dist/version.d.ts +1 -1
  34. package/dist/version.js +1 -1
  35. package/docs/adoption.md +11 -10
  36. package/docs/agent-format.md +16 -10
  37. package/docs/agent-skill.md +8 -5
  38. package/docs/architecture.md +131 -0
  39. package/docs/benchmarking.md +23 -4
  40. package/docs/commands.md +13 -7
  41. package/docs/e2e-output-examples.md +29 -28
  42. package/docs/quickstart-demo.md +29 -17
  43. package/docs/release-validation.md +49 -6
  44. package/docs/releasing.md +26 -3
  45. package/docs/roadmap.md +20 -10
  46. package/package.json +4 -2
  47. package/schema/qamap-agent.schema.json +122 -3
  48. package/schema/qamap-behavior.schema.json +339 -0
  49. package/skills/qamap-pr-qa/SKILL.md +20 -14
package/dist/index.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  export { buildAgentQaSection, formatAgentInitReport, initAgentSetup } from "./agent-init.js";
2
+ export { analyzeBehaviorGraph, behaviorEdgeKinds, behaviorEvidenceKinds, behaviorGraphSchemaVersion, behaviorGraphSchemaUrl, behaviorNodeKinds, behaviorSurfaceKinds, createBehaviorEdge, createBehaviorNodeId, createInferredFlowBehaviorAdapter, mergeBehaviorGraphFragments, } from "./behavior.js";
3
+ export { createManifestBehaviorAdapter } from "./behavior-manifest.js";
4
+ export { createChangeIntentBehaviorAdapter } from "./behavior-intent.js";
5
+ export { analyzeChangeIntents } from "./change-intent.js";
2
6
  export { defaultConfig, loadConfig, writeDefaultConfig } from "./config.js";
3
7
  export { detectDlxCommand, generateAgentContext } from "./context.js";
4
8
  export { buildDomainLanguageSummary } from "./domain-language.js";
@@ -16,13 +20,17 @@ export { formatAgentQaDraft, formatMarkdownQaDraft, generateQaDraft } from "./qa
16
20
  export { formatMarkdownReviewReport, formatReviewReport, reviewProject } from "./review.js";
17
21
  export { scanProject } from "./scanner.js";
18
22
  export { collectTestSuiteInventory, evaluateFlowCoverageEvidence, summarizeTestSuiteInventory } from "./test-evidence.js";
19
- export { formatMarkdownTestPlan, generateTestPlan } from "./test-plan.js";
23
+ export { addedDiffTextFromEvidence, collectAddedDiffEvidence, collectAddedDiffText, formatMarkdownTestPlan, generateTestPlan, } from "./test-plan.js";
20
24
  export { formatMarkdownVerifyReport, formatVerifyReport, verifyChange } from "./verify.js";
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
+ export type { ManifestBehaviorAdapterOptions } from "./behavior-manifest.js";
27
+ export type { ChangeIntentBehaviorAdapterOptions } from "./behavior-intent.js";
28
+ export type { BehaviorLifecycleStage, BehaviorLifecycleStageKind, ChangeIntent, ChangeIntentAnalysis, ChangeIntentAnalysisOptions, ChangeIntentCommit, ChangeIntentConfidence, ChangeIntentEvidence, ChangeIntentEvidenceKind, ChangeIntentEvidenceRelation, IntentQaScenario, IntentQaScenarioKind, IntentQaScenarioPriority, } from "./change-intent.js";
21
29
  export type { CoverageEvidence, CoverageEvidenceConfidence, CoverageEvidenceStatus, FlowCoverageInput, TestSuiteEvidenceFile, TestSuiteInventory, TestSuiteSummary, } from "./test-evidence.js";
22
30
  export type { DoctorArea, DoctorPriority, DoctorResult } from "./doctor.js";
23
31
  export type { DomainLanguageConfidence, DomainLanguageSource, DomainLanguageSummary, DomainLanguageTerm, DomainScenarioSuggestion, } from "./domain-language.js";
24
32
  export type { DomainDefinition, DomainManifest, DomainScenarioDefinition, MatchedDomain, } from "./domains.js";
25
- export type { E2eCoveragePriority, E2eCoverageTarget, E2eBootstrapPlan, E2eBootstrapStep, E2eBootstrapStepCategory, E2eBootstrapStepStatus, E2eEntrypoint, E2eEntrypointConfidence, E2eEntrypointKind, E2eFixtureReadiness, E2eFixtureReadinessStatus, E2eFlow, E2eSelector, E2eSelectorKind, E2eSetupHint, E2eSetupHintConfidence, E2eSetupHintKind, E2eValidationMatrix, E2eValidationMatrixCategory, E2eValidationMatrixRow, E2eValidationMatrixStatus, E2eDraftFile, E2eDraftOptions, E2eDraftResult, E2ePlanOptions, E2ePlanResult, E2eProjectProfile, E2eProjectType, E2eRunnerName, E2eRunnerRecommendation, E2eRunnerSetupProposal, E2eRunnerSetupStatus, E2eSetupOptions, E2eSetupResult, } from "./e2e.js";
33
+ export type { E2eCoveragePriority, E2eCoverageTarget, E2eBootstrapPlan, E2eBootstrapStep, E2eBootstrapStepCategory, E2eBootstrapStepStatus, E2eEntrypoint, E2eEntrypointConfidence, E2eEntrypointKind, E2eFixtureReadiness, E2eFixtureReadinessStatus, E2eFlow, E2eFlowKind, E2eSelector, E2eSelectorKind, E2eSetupHint, E2eSetupHintConfidence, E2eSetupHintKind, E2eValidationMatrix, E2eValidationMatrixCategory, E2eValidationMatrixRow, E2eValidationMatrixStatus, E2eDraftFile, E2eDraftOptions, E2eDraftResult, E2ePlanOptions, E2ePlanResult, E2eProjectProfile, E2eProjectType, E2eRunnerName, E2eRunnerRecommendation, E2eRunnerSetupProposal, E2eRunnerSetupStatus, E2eSetupOptions, E2eSetupResult, } from "./e2e.js";
26
34
  export type { EvalCheck, EvalCheckStatus, EvalOptions, EvalRating, EvalResult } from "./eval.js";
27
35
  export type { CoreFlowDefinition, CoreFlowManifest, CoreFlowPriority, MatchedCoreFlow, } from "./flows.js";
28
36
  export type { GitHubActionMode, GitHubActionOptions, GitHubActionResult } from "./github.js";
@@ -30,7 +38,7 @@ export type { E2ePlanHistorySnapshot, LocalHistoryInitResult, LocalHistoryRefere
30
38
  export type { DomainManifestSuggestionResult, FlowManifestSuggestionResult, ManifestPromotionCandidate, ManifestPromotionPlan, ManifestPromotionStatus, ManifestSuggestionOptions, } from "./manifest-suggestions.js";
31
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";
32
40
  export type { ChangedFile, ChangedRiskyFinding, ReviewOptions, ReviewResult } from "./review.js";
33
- 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";
34
42
  export type { QAMapConfig, Finding, ScanCounts, ScanOptions, ScanResult, Severity } from "./types.js";
35
43
  export type { VerifyOptions, VerifyResult } from "./verify.js";
36
44
  export type { QaDraftFlow, QaDraftMissingEvidence, QaDraftOptions, QaDraftResult } from "./qa.js";
package/dist/index.js CHANGED
@@ -1,4 +1,8 @@
1
1
  export { buildAgentQaSection, formatAgentInitReport, initAgentSetup } from "./agent-init.js";
2
+ export { analyzeBehaviorGraph, behaviorEdgeKinds, behaviorEvidenceKinds, behaviorGraphSchemaVersion, behaviorGraphSchemaUrl, behaviorNodeKinds, behaviorSurfaceKinds, createBehaviorEdge, createBehaviorNodeId, createInferredFlowBehaviorAdapter, mergeBehaviorGraphFragments, } from "./behavior.js";
3
+ export { createManifestBehaviorAdapter } from "./behavior-manifest.js";
4
+ export { createChangeIntentBehaviorAdapter } from "./behavior-intent.js";
5
+ export { analyzeChangeIntents } from "./change-intent.js";
2
6
  export { defaultConfig, loadConfig, writeDefaultConfig } from "./config.js";
3
7
  export { detectDlxCommand, generateAgentContext } from "./context.js";
4
8
  export { buildDomainLanguageSummary } from "./domain-language.js";
@@ -16,6 +20,6 @@ export { formatAgentQaDraft, formatMarkdownQaDraft, generateQaDraft } from "./qa
16
20
  export { formatMarkdownReviewReport, formatReviewReport, reviewProject } from "./review.js";
17
21
  export { scanProject } from "./scanner.js";
18
22
  export { collectTestSuiteInventory, evaluateFlowCoverageEvidence, summarizeTestSuiteInventory } from "./test-evidence.js";
19
- export { formatMarkdownTestPlan, generateTestPlan } from "./test-plan.js";
23
+ export { addedDiffTextFromEvidence, collectAddedDiffEvidence, collectAddedDiffText, formatMarkdownTestPlan, generateTestPlan, } from "./test-plan.js";
20
24
  export { formatMarkdownVerifyReport, formatVerifyReport, verifyChange } from "./verify.js";
21
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,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.d.ts CHANGED
@@ -18,6 +18,7 @@ export interface QaDraftResult {
18
18
  testSuite: E2eDraftResult["plan"]["testSuite"];
19
19
  bootstrap: E2eDraftResult["plan"]["bootstrap"];
20
20
  runnerSetup: E2eDraftResult["plan"]["runnerSetup"];
21
+ changeAnalysis: E2eDraftResult["plan"]["changeAnalysis"];
21
22
  readiness: E2eDraftReadinessSummary;
22
23
  flows: QaDraftFlow[];
23
24
  missingEvidence: QaDraftMissingEvidence[];
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,
@@ -26,6 +27,7 @@ export async function generateQaDraft(rootInput, options = {}) {
26
27
  testSuite: draft.plan.testSuite,
27
28
  bootstrap: draft.plan.bootstrap,
28
29
  runnerSetup: draft.plan.runnerSetup,
30
+ changeAnalysis: draft.plan.changeAnalysis,
29
31
  readiness: draft.readinessSummary,
30
32
  flows,
31
33
  missingEvidence,
@@ -35,6 +37,7 @@ export async function generateQaDraft(rootInput, options = {}) {
35
37
  };
36
38
  }
37
39
  const agentListLimit = 6;
40
+ const agentPayloadByteLimit = 8 * 1024 - 1;
38
41
  function truncateForAgent(value, maxLength = 140) {
39
42
  return value.length > maxLength ? `${value.slice(0, maxLength - 1)}…` : value;
40
43
  }
@@ -44,7 +47,7 @@ export function formatAgentQaDraft(result) {
44
47
  .slice(0, 8)
45
48
  .map((item) => ({ flow: truncateForAgent(item.flowTitle, 80), kind: item.kind, title: truncateForAgent(item.title) }));
46
49
  const requiredBootstrap = result.bootstrap.steps
47
- .filter((step) => step.status === "required")
50
+ .filter((step) => step.status === "required" && step.category !== "runner")
48
51
  .slice(0, 3)
49
52
  .map((step) => ({ title: truncateForAgent(step.title, 80), action: truncateForAgent(step.action) }));
50
53
  const summary = {
@@ -56,17 +59,49 @@ export function formatAgentQaDraft(result) {
56
59
  manifest: result.manifestPath ?? null,
57
60
  readiness: { score: result.readiness.score, level: result.readiness.level },
58
61
  testSuite: { present: result.testSuite.hasTestSuite, files: result.testSuite.testFileCount },
59
- firstDraftCommand: !result.testSuite.hasTestSuite && needsGeneratedDraft(result)
60
- ? firstDraftCreateCommand(result)
62
+ intentCount: result.changeAnalysis.intents.length,
63
+ omittedIntentCount: Math.max(0, result.changeAnalysis.intents.length - 3),
64
+ intents: result.changeAnalysis.intents.slice(0, 3).map((intent) => ({
65
+ title: truncateForAgent(intent.title, 100),
66
+ confidence: intent.confidence,
67
+ reviewRequired: intent.reviewRequired,
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) => ({
71
+ phase: stage.kind,
72
+ label: truncateForAgent(stage.label, 120),
73
+ })),
74
+ scenarioCount: intent.scenarios.length,
75
+ omittedScenarioCount: Math.max(0, intent.scenarios.length - 2),
76
+ scenarios: intent.scenarios.slice(0, 2).map((scenario) => ({
77
+ priority: scenario.priority,
78
+ kind: scenario.kind,
79
+ title: truncateForAgent(scenario.title, 100),
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)),
84
+ })),
85
+ })),
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
+ }
61
94
  : undefined,
95
+ flowCount: result.flows.length,
96
+ omittedFlowCount: Math.max(0, result.flows.length - agentListLimit),
62
97
  flows: result.flows.slice(0, agentListLimit).map((flow) => ({
63
98
  title: truncateForAgent(flow.title, 80),
64
- source: flow.source,
65
- draft: flow.draftPath,
99
+ source: truncateForAgent(flow.source, 60),
100
+ draft: truncateForAgent(flow.draftPath, 140),
66
101
  runnable: flow.runnableStatus,
67
102
  verificationMode: flow.verificationMode,
68
- entry: flow.entrypointHints[0],
69
- 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)),
70
105
  reviewQuestion: flow.userJourney?.reviewQuestion
71
106
  ? truncateForAgent(flow.userJourney.reviewQuestion, 180)
72
107
  : undefined,
@@ -75,16 +110,147 @@ export function formatAgentQaDraft(result) {
75
110
  : undefined,
76
111
  steps: flow.draftSteps.slice(0, agentListLimit).map((step) => truncateForAgent(step)),
77
112
  selectors: flow.selectorHints.slice(0, 5).map((selector) => truncateForAgent(selector, 100)),
78
- 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,
79
116
  evidence: flow.why.slice(0, 2).map((reason) => truncateForAgent(reason)),
80
117
  })),
81
118
  requiredEvidence,
82
119
  recommendedEvidenceCount: result.missingEvidence.filter((item) => item.priority === "recommended").length,
83
120
  requiredBootstrap,
84
121
  prChecklist: result.prChecklist.slice(0, agentListLimit).map((item) => truncateForAgent(item)),
85
- commands: result.suggestedCommands.slice(0, 4),
122
+ commands: result.suggestedCommands.slice(0, 4).map((command) => truncateForAgent(command, 180)),
86
123
  };
87
- 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;
88
254
  }
89
255
  export function formatMarkdownQaDraft(result) {
90
256
  const lines = [];
@@ -94,6 +260,20 @@ export function formatMarkdownQaDraft(result) {
94
260
  lines.push("");
95
261
  lines.push("## At a Glance");
96
262
  lines.push("");
263
+ const primaryIntent = result.changeAnalysis.intents[0];
264
+ if (primaryIntent) {
265
+ lines.push(`- Change intent: ${escapeMarkdownInline(primaryIntent.title)} [${primaryIntent.confidence}]`);
266
+ const lifecycle = summarizeIntentLifecycle(primaryIntent.lifecycle);
267
+ if (lifecycle) {
268
+ lines.push(`- Behavior lifecycle: ${escapeMarkdownInline(lifecycle)}`);
269
+ }
270
+ if (primaryIntent.reviewRequired) {
271
+ lines.push("- Intent confidence: human review is required before treating generated scenarios as regression policy.");
272
+ }
273
+ }
274
+ else {
275
+ lines.push("- Change intent: not inferred; heuristic flow suggestions remain review-only.");
276
+ }
97
277
  if (result.flows.length === 0) {
98
278
  lines.push("- Affected behavior: no changed flow candidate was generated from this diff.");
99
279
  }
@@ -116,17 +296,20 @@ export function formatMarkdownQaDraft(result) {
116
296
  lines.push(`- Verification mode: ${formatVerificationMode(primaryFlow.verificationMode)}; no new product-journey E2E draft is proposed.`);
117
297
  }
118
298
  else {
119
- 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.`);
120
300
  }
121
301
  }
122
- lines.push(`- Next command: \`${escapeMarkdownInline(nextStepCommand(result))}\``);
302
+ const nextCommand = nextStepCommand(result);
303
+ if (nextCommand) {
304
+ lines.push(`- Repository validation: \`${escapeMarkdownInline(nextCommand)}\``);
305
+ }
123
306
  const blocking = result.missingEvidence.filter((item) => item.priority === "required").slice(0, 2);
124
307
  if (blocking.length === 0) {
125
- 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.");
126
309
  }
127
310
  else {
128
311
  for (const [index, item] of blocking.entries()) {
129
- 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)}`);
130
313
  }
131
314
  }
132
315
  lines.push("");
@@ -136,36 +319,11 @@ export function formatMarkdownQaDraft(result) {
136
319
  lines.push(`- Base: \`${escapeMarkdownInline(result.base)}\``);
137
320
  lines.push(`- Head: \`${escapeMarkdownInline(result.head)}\``);
138
321
  lines.push(`- Project: ${formatProjectType(result.project)}`);
139
- lines.push(`- Recommended runner: ${formatRunnerName(result.runner)}`);
140
322
  lines.push(`- Manifest: ${result.manifestPath ? `\`${escapeMarkdownInline(result.manifestPath)}\`` : "not found; using repo signals and PR diff only"}`);
141
323
  lines.push(`- Stage: ${formatDraftReadinessStage(result.readiness)}`);
142
324
  lines.push(`- Draft flows: ${result.flows.length}`);
143
325
  lines.push("");
144
- if (!result.testSuite.hasTestSuite && needsGeneratedDraft(result)) {
145
- lines.push("## First E2E Draft Bootstrap");
146
- lines.push("");
147
- 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.`);
148
- lines.push("");
149
- lines.push(`- Recommended first runner: ${formatRunnerName(result.runner)}`);
150
- lines.push(`- Create command: \`${escapeMarkdownInline(firstDraftCreateCommand(result))}\``);
151
- const installCommand = result.runnerSetup.installCommands[0];
152
- if (installCommand) {
153
- lines.push(`- Install command: \`${escapeMarkdownInline(installCommand)}\``);
154
- }
155
- const filesToCreate = uniqueStrings([...result.runnerSetup.filesToCreate, ...result.flows.map((flow) => flow.draftPath)]);
156
- if (filesToCreate.length > 0) {
157
- lines.push("- Draft files QAMap can create:");
158
- for (const file of filesToCreate.slice(0, 6)) {
159
- lines.push(` - \`${escapeMarkdownInline(file)}\``);
160
- }
161
- }
162
- const filesToUpdate = result.runnerSetup.filesToUpdate;
163
- if (filesToUpdate.length > 0) {
164
- lines.push(`- Files to update: ${filesToUpdate.map((file) => `\`${escapeMarkdownInline(file)}\``).join(", ")}`);
165
- }
166
- lines.push("- Generated drafts are starter E2E code; keep them review-only until the changed flow has real assertions, fixtures, and selectors.");
167
- lines.push("");
168
- }
326
+ appendQaChangeIntentMarkdown(lines, result);
169
327
  lines.push("## PR Comment Draft");
170
328
  lines.push("");
171
329
  lines.push("### Affected Flow");
@@ -190,7 +348,7 @@ export function formatMarkdownQaDraft(result) {
190
348
  }
191
349
  }
192
350
  lines.push("");
193
- lines.push("### Suggested E2E / QA Draft");
351
+ lines.push("### Suggested QA Scenarios");
194
352
  lines.push("");
195
353
  for (const flow of result.flows) {
196
354
  if (flow.existingEvidencePaths.length > 0) {
@@ -200,7 +358,7 @@ export function formatMarkdownQaDraft(result) {
200
358
  lines.push(`- Run ${formatVerificationMode(flow.verificationMode)} with the suggested repository validation command; no new product-journey E2E draft is proposed.`);
201
359
  }
202
360
  else {
203
- lines.push(`- \`${escapeMarkdownInline(flow.draftPath)}\`: ${formatRunnableStatus(flow.runnableStatus)}`);
361
+ lines.push(`- ${escapeMarkdownInline(flow.title)}`);
204
362
  }
205
363
  const routeHint = flow.entrypointHints.find((hint) => hint.startsWith("route:"));
206
364
  if (routeHint) {
@@ -218,7 +376,7 @@ export function formatMarkdownQaDraft(result) {
218
376
  }
219
377
  }
220
378
  lines.push("");
221
- lines.push("### Missing Evidence Before Trusting This PR");
379
+ lines.push("### Evidence Gaps In This QA Proposal");
222
380
  lines.push("");
223
381
  if (result.missingEvidence.length === 0) {
224
382
  lines.push("- No required evidence gap was detected in the generated QA draft. Still run the project validation command before merge.");
@@ -244,18 +402,88 @@ export function formatMarkdownQaDraft(result) {
244
402
  lines.push(`- ${escapeMarkdownInline(item)}`);
245
403
  }
246
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
+ }
247
419
  return lines.join("\n");
248
420
  }
249
- function nextStepCommand(result) {
250
- if (!result.testSuite.hasTestSuite && needsGeneratedDraft(result)) {
251
- return firstDraftCreateCommand(result);
421
+ function appendQaChangeIntentMarkdown(lines, result) {
422
+ lines.push("## Change Intent Evidence");
423
+ lines.push("");
424
+ if (result.changeAnalysis.intents.length === 0) {
425
+ lines.push("No behavior-bearing commit intent was found. QAMap did not promote inferred names into trusted QA scenarios.");
426
+ lines.push("");
427
+ return;
252
428
  }
429
+ for (const intent of result.changeAnalysis.intents.slice(0, 3)) {
430
+ lines.push(`### ${escapeMarkdownInline(intent.title)}`);
431
+ lines.push("");
432
+ lines.push(`- Confidence: ${intent.confidence}${intent.reviewRequired ? "; review required" : ""}`);
433
+ for (const commit of intent.commits.slice(0, 5)) {
434
+ lines.push(`- Evidence: \`${commit.sha.slice(0, 12)}\` ${escapeMarkdownInline(commit.subject)}`);
435
+ }
436
+ lines.push("- Lifecycle:");
437
+ for (const stage of intent.lifecycle.slice(0, 10)) {
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}`);
441
+ }
442
+ lines.push("- QA scenarios:");
443
+ for (const scenario of intent.scenarios.slice(0, 4)) {
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
+ }
452
+ for (const assertion of scenario.assertions.slice(0, 2)) {
453
+ lines.push(` - Assert: ${escapeMarkdownInline(assertion)}`);
454
+ }
455
+ }
456
+ lines.push("");
457
+ }
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
+ }
473
+ function summarizeIntentLifecycle(lifecycle) {
474
+ const preferredPhases = ["trigger", "condition", "state-change", "side-effect", "observable-outcome"];
475
+ const selected = preferredPhases
476
+ .map((phase) => lifecycle.find((stage) => stage.kind === phase))
477
+ .filter((stage) => Boolean(stage));
478
+ const fallback = selected.length > 0 ? selected : lifecycle.slice(0, 5);
479
+ return fallback.map((stage) => `${stage.kind}: ${stage.label}`).join(" -> ");
480
+ }
481
+ function nextStepCommand(result) {
253
482
  const validationCommand = result.suggestedCommands[0];
254
483
  if (validationCommand) {
255
484
  return validationCommand;
256
485
  }
257
- const draftPath = result.flows[0]?.draftPath;
258
- return draftPath ? `review ${draftPath}` : "qamap e2e draft . --dry-run";
486
+ return undefined;
259
487
  }
260
488
  function atAGlanceEvidence(flow) {
261
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];
@@ -266,12 +494,6 @@ function atAGlanceEvidence(flow) {
266
494
  ].filter((value) => Boolean(value));
267
495
  return uniqueStrings(evidence).slice(0, 3);
268
496
  }
269
- function firstDraftCreateCommand(result) {
270
- if (result.runnerSetup.setupCommand) {
271
- return result.runnerSetup.setupCommand;
272
- }
273
- return `qamap e2e draft . --base ${result.base} --head ${result.head}`;
274
- }
275
497
  function qaFlowFromDraftFile(file) {
276
498
  const verificationMode = verificationModeForTitle(file.flowTitle);
277
499
  return {
@@ -318,20 +540,10 @@ function buildMissingEvidence(files) {
318
540
  const evidence = [];
319
541
  for (const file of files) {
320
542
  for (const item of file.actionItems ?? []) {
321
- evidence.push(missingEvidenceFromAction(file, item));
322
- }
323
- const actionText = normalizeEvidenceText((file.actionItems ?? []).map((item) => `${item.title} ${item.detail}`).join(" "));
324
- for (const blocker of file.executionBlockers ?? []) {
325
- if (actionTextCoversBlocker(actionText, blocker)) {
543
+ if (item.kind === "runner" || item.kind === "validation") {
326
544
  continue;
327
545
  }
328
- evidence.push({
329
- flowTitle: file.flowTitle,
330
- priority: "required",
331
- kind: "blocker",
332
- title: "Resolve execution blocker",
333
- detail: blocker,
334
- });
546
+ evidence.push(missingEvidenceFromAction(file, item));
335
547
  }
336
548
  }
337
549
  const unique = uniqueMissingEvidence(evidence);
@@ -339,20 +551,6 @@ function buildMissingEvidence(files) {
339
551
  const recommended = unique.filter((item) => item.priority !== "required");
340
552
  return [...required, ...recommended].slice(0, 12);
341
553
  }
342
- function normalizeEvidenceText(value) {
343
- return value.toLowerCase().replace(/[^a-z0-9\uAC00-\uD7A3 ]+/g, " ").replace(/\s+/g, " ").trim();
344
- }
345
- function actionTextCoversBlocker(actionText, blocker) {
346
- if (!actionText) {
347
- return false;
348
- }
349
- const blockerTokens = normalizeEvidenceText(blocker).split(" ").filter((token) => token.length > 3);
350
- if (blockerTokens.length === 0) {
351
- return false;
352
- }
353
- const covered = blockerTokens.filter((token) => actionText.includes(token)).length;
354
- return covered / blockerTokens.length >= 0.7;
355
- }
356
554
  function missingEvidenceFromAction(file, item) {
357
555
  return {
358
556
  flowTitle: file.flowTitle,
@@ -379,9 +577,11 @@ function buildPrChecklist(draft, flows, missingEvidence) {
379
577
  ? `Run the changed test evidence: ${flows[0].existingEvidencePaths.slice(0, 4).join(", ")}.`
380
578
  : flows[0]?.verificationMode
381
579
  ? `Run ${formatVerificationMode(flows[0].verificationMode)} with ${draft.plan.suggestedCommands[0] ?? "the nearest repository validation command"}.`
382
- : flows.length > 0
383
- ? `Review the generated draft path: ${flows.map((flow) => flow.draftPath).slice(0, 3).join(", ")}.`
384
- : "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.",
385
585
  flows[0]?.userJourney?.reviewQuestion
386
586
  ? `Answer the reviewer question: ${flows[0].userJourney.reviewQuestion}`
387
587
  : "Name the user-visible behavior or contract this PR can break.",
@@ -408,9 +608,11 @@ function buildAgentHandoff(draft, flows, missingEvidence) {
408
608
  ? `Run the changed test evidence (${flows[0].existingEvidencePaths.slice(0, 3).join(", ")}) and record the result before handoff.`
409
609
  : flows[0]?.verificationMode
410
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.`
411
- : flows.length > 0
412
- ? `Start from ${flows[0].draftPath} and close required evidence before treating it as regression coverage.`
413
- : 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,
414
616
  missingEvidence.length > 0 ? "Close required evidence gaps before treating this QA draft as merge proof." : undefined,
415
617
  flows.some((flow) => !flow.verificationMode)
416
618
  ? "A wrong flow recommendation should become a manifest correction, so future PRs improve without another prompt."
@@ -468,6 +670,9 @@ function formatDraftSource(source) {
468
670
  if (source === "domain-language") {
469
671
  return "domain-language";
470
672
  }
673
+ if (source === "change-intent") {
674
+ return "commit-and-diff-intent";
675
+ }
471
676
  if (source === "core-flow") {
472
677
  return "core-flow";
473
678
  }