@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.
- package/CHANGELOG.md +49 -0
- package/README.md +37 -23
- package/dist/agent-init.js +2 -2
- package/dist/agent-init.js.map +1 -1
- package/dist/behavior-intent.d.ts +6 -0
- package/dist/behavior-intent.js +183 -0
- package/dist/behavior-intent.js.map +1 -0
- package/dist/behavior-manifest.d.ts +6 -0
- package/dist/behavior-manifest.js +221 -0
- package/dist/behavior-manifest.js.map +1 -0
- package/dist/behavior.d.ts +151 -0
- package/dist/behavior.js +458 -0
- package/dist/behavior.js.map +1 -0
- package/dist/change-intent.d.ts +81 -0
- package/dist/change-intent.js +884 -0
- package/dist/change-intent.js.map +1 -0
- package/dist/cli.js +13 -8
- package/dist/cli.js.map +1 -1
- package/dist/e2e.d.ts +16 -1
- package/dist/e2e.js +290 -25
- package/dist/e2e.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/qa.d.ts +1 -0
- package/dist/qa.js +293 -88
- package/dist/qa.js.map +1 -1
- package/dist/terminal.js +1 -1
- package/dist/terminal.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/adoption.md +11 -10
- package/docs/agent-format.md +16 -10
- package/docs/agent-skill.md +8 -5
- package/docs/architecture.md +131 -0
- package/docs/benchmarking.md +23 -4
- package/docs/commands.md +13 -7
- package/docs/e2e-output-examples.md +29 -28
- package/docs/quickstart-demo.md +29 -17
- package/docs/release-validation.md +49 -6
- package/docs/releasing.md +26 -3
- package/docs/roadmap.md +20 -10
- package/package.json +4 -2
- package/schema/qamap-agent.schema.json +122 -3
- package/schema/qamap-behavior.schema.json +339 -0
- 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,
|
|
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
|
|
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,
|
|
@@ -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
|
-
|
|
60
|
-
|
|
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
|
|
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 `${
|
|
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(`-
|
|
299
|
+
lines.push(`- QA proposal: ${primaryFlow.draftSteps.length || fallbackDraftSteps(primaryFlow).length} review steps; executable automation remains optional.`);
|
|
120
300
|
}
|
|
121
301
|
}
|
|
122
|
-
|
|
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("-
|
|
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(`-
|
|
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
|
-
|
|
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
|
|
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(`-
|
|
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("###
|
|
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
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
:
|
|
383
|
-
? `Review the
|
|
384
|
-
:
|
|
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
|
-
:
|
|
412
|
-
? `
|
|
413
|
-
:
|
|
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
|
}
|