Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. ---
  2. name: 'step-04-analyze-gaps'
  3. description: 'Complete Phase 1 with adaptive orchestration (agent-team, subagent, or sequential)'
  4. nextStepFile: '{skill-root}/steps-c/step-05-gate-decision.md'
  5. outputFile: '{test_artifacts}/traceability-matrix.md'
  6. tempOutputFile: '/tmp/tea-trace-coverage-matrix-{{timestamp}}.json'
  7. ---
  8. # Step 4: Complete Phase 1 - Coverage Matrix Generation
  9. ## STEP GOAL
  10. **Phase 1 Final Step:** Analyze coverage gaps (including endpoint/auth/error-path blind spots), generate recommendations, and output complete coverage matrix to temp file for Phase 2 (gate decision).
  11. ---
  12. ## MANDATORY EXECUTION RULES
  13. - 📖 Read the entire step file before acting
  14. - ✅ Speak in `{communication_language}`
  15. - ✅ Output coverage matrix to temp file
  16. - ✅ Resolve execution mode from explicit user request first, then config
  17. - ✅ Apply fallback rules deterministically when requested mode is unsupported
  18. - ❌ Do NOT make gate decision (that's Phase 2 - Step 5)
  19. ---
  20. ## EXECUTION PROTOCOLS:
  21. - 🎯 Follow the MANDATORY SEQUENCE exactly
  22. - 💾 Record outputs before proceeding
  23. - 📖 Load the next step only when instructed
  24. ## CONTEXT BOUNDARIES:
  25. - Available context: resolved oracle items from Step 1, tests from Step 2, traceability matrix from Step 3
  26. - Focus: gap analysis and matrix completion
  27. - Limits: do not make gate decision (Phase 2 responsibility)
  28. ---
  29. ## MANDATORY SEQUENCE
  30. ### 0. Resolve Execution Mode (User Override First)
  31. ```javascript
  32. const parseBooleanFlag = (value, defaultValue = true) => {
  33. if (typeof value === 'string') {
  34. const normalized = value.trim().toLowerCase();
  35. if (['false', '0', 'off', 'no'].includes(normalized)) return false;
  36. if (['true', '1', 'on', 'yes'].includes(normalized)) return true;
  37. }
  38. if (value === undefined || value === null) return defaultValue;
  39. return Boolean(value);
  40. };
  41. const orchestrationContext = {
  42. config: {
  43. execution_mode: config.tea_execution_mode || 'auto', // "auto" | "subagent" | "agent-team" | "sequential"
  44. capability_probe: parseBooleanFlag(config.tea_capability_probe, true), // supports booleans and "false"/"true" strings
  45. },
  46. timestamp: new Date().toISOString().replace(/[:.]/g, '-'),
  47. };
  48. const normalizeUserExecutionMode = (mode) => {
  49. if (typeof mode !== 'string') return null;
  50. const normalized = mode.trim().toLowerCase().replace(/[-_]/g, ' ').replace(/\s+/g, ' ');
  51. if (normalized === 'auto') return 'auto';
  52. if (normalized === 'sequential') return 'sequential';
  53. if (normalized === 'subagent' || normalized === 'sub agent' || normalized === 'subagents' || normalized === 'sub agents') {
  54. return 'subagent';
  55. }
  56. if (normalized === 'agent team' || normalized === 'agent teams' || normalized === 'agentteam') {
  57. return 'agent-team';
  58. }
  59. return null;
  60. };
  61. const normalizeConfigExecutionMode = (mode) => {
  62. if (mode === 'subagent') return 'subagent';
  63. if (mode === 'auto' || mode === 'sequential' || mode === 'subagent' || mode === 'agent-team') {
  64. return mode;
  65. }
  66. return null;
  67. };
  68. // Explicit user instruction in the active run takes priority over config.
  69. const explicitModeFromUser = normalizeUserExecutionMode(runtime.getExplicitExecutionModeHint?.() || null);
  70. const requestedMode = explicitModeFromUser || normalizeConfigExecutionMode(orchestrationContext.config.execution_mode) || 'auto';
  71. const probeEnabled = orchestrationContext.config.capability_probe;
  72. const supports = { subagent: false, agentTeam: false };
  73. if (probeEnabled) {
  74. supports.subagent = runtime.canLaunchSubagents?.() === true;
  75. supports.agentTeam = runtime.canLaunchAgentTeams?.() === true;
  76. }
  77. let resolvedMode = requestedMode;
  78. if (requestedMode === 'auto') {
  79. if (supports.agentTeam) resolvedMode = 'agent-team';
  80. else if (supports.subagent) resolvedMode = 'subagent';
  81. else resolvedMode = 'sequential';
  82. } else if (probeEnabled && requestedMode === 'agent-team' && !supports.agentTeam) {
  83. resolvedMode = supports.subagent ? 'subagent' : 'sequential';
  84. } else if (probeEnabled && requestedMode === 'subagent' && !supports.subagent) {
  85. resolvedMode = 'sequential';
  86. }
  87. ```
  88. Resolution precedence:
  89. 1. Explicit user request in this run (`agent team` => `agent-team`; `subagent` => `subagent`; `sequential`; `auto`)
  90. 2. `tea_execution_mode` from config
  91. 3. Runtime capability fallback (when probing enabled)
  92. ### 1. Gap Analysis
  93. **Identify uncovered requirements:**
  94. ```javascript
  95. const uncoveredRequirements = traceabilityMatrix.filter((req) => req.coverage === 'NONE');
  96. const partialCoverage = traceabilityMatrix.filter((req) => req.coverage === 'PARTIAL');
  97. const unitOnlyCoverage = traceabilityMatrix.filter((req) => req.coverage === 'UNIT-ONLY');
  98. ```
  99. **Prioritize gaps by risk:**
  100. ```javascript
  101. const criticalGaps = uncoveredRequirements.filter((req) => req.priority === 'P0');
  102. const highGaps = uncoveredRequirements.filter((req) => req.priority === 'P1');
  103. const mediumGaps = uncoveredRequirements.filter((req) => req.priority === 'P2');
  104. const lowGaps = uncoveredRequirements.filter((req) => req.priority === 'P3');
  105. ```
  106. ---
  107. ### 2. Coverage Heuristics Checks
  108. Use the heuristics inventory from Step 2 and mapped criteria from Step 3 to flag common coverage blind spots:
  109. ```javascript
  110. const endpointCoverageGaps = coverageHeuristics?.endpoints_without_tests || [];
  111. const authCoverageGaps = coverageHeuristics?.auth_missing_negative_paths || [];
  112. const errorPathGaps = coverageHeuristics?.criteria_happy_path_only || [];
  113. const uiJourneyGaps = coverageHeuristics?.ui_journeys_without_e2e || [];
  114. const uiStateGaps = coverageHeuristics?.ui_states_missing_coverage || [];
  115. const heuristicGapCounts = {
  116. endpoints_without_tests: endpointCoverageGaps.length,
  117. auth_missing_negative_paths: authCoverageGaps.length,
  118. happy_path_only_criteria: errorPathGaps.length,
  119. ui_journeys_without_e2e: uiJourneyGaps.length,
  120. ui_states_missing_coverage: uiStateGaps.length,
  121. };
  122. ```
  123. Heuristics are advisory but must influence gap severity and recommendations, especially for P0/P1 criteria.
  124. ---
  125. ### 3. Generate Recommendations
  126. **Based on gap analysis:**
  127. ```javascript
  128. const progressDoc = fs.existsSync('{outputFile}') ? fs.readFileSync('{outputFile}', 'utf8') : '';
  129. const progressFrontmatterMatch = progressDoc.match(/^---\n([\s\S]*?)\n---/);
  130. const progressFrontmatter = progressFrontmatterMatch ? yaml.parse(progressFrontmatterMatch[1]) : {};
  131. const isUnresolved = (value) => typeof value === 'string' && value.startsWith('{') && value.endsWith('}');
  132. const normalizeResolvedToken = (value) => {
  133. if (value === undefined || value === null) return null;
  134. const normalized = String(value).trim().toLowerCase();
  135. if (!normalized || normalized === 'auto' || isUnresolved(normalized)) return null;
  136. return normalized;
  137. };
  138. const firstResolvedToken = (...values) => {
  139. for (const value of values) {
  140. const normalized = normalizeResolvedToken(value);
  141. if (normalized) return normalized;
  142. }
  143. return null;
  144. };
  145. const oracleResolutionMode =
  146. firstResolvedToken(runtime.getOracleResolutionMode?.(), progressFrontmatter.oracleResolutionMode) || 'formal_requirements';
  147. const resolvedCoverageBasis =
  148. firstResolvedToken(runtime.getResolvedCoverageBasis?.(), progressFrontmatter.coverageBasis) ||
  149. {
  150. formal_requirements: 'acceptance_criteria',
  151. spec_artifact: 'openapi_endpoints',
  152. external_pointer: 'acceptance_criteria',
  153. synthetic_source: 'user_journeys',
  154. }[oracleResolutionMode] ||
  155. 'acceptance_criteria';
  156. const resolvedOracleConfidence =
  157. firstResolvedToken(runtime.getResolvedOracleConfidence?.(), progressFrontmatter.oracleConfidence) ||
  158. {
  159. formal_requirements: 'high',
  160. spec_artifact: 'high',
  161. external_pointer: 'medium',
  162. synthetic_source: 'medium',
  163. }[oracleResolutionMode] ||
  164. 'medium';
  165. const oracleSources = runtime.getOracleSources?.() || progressFrontmatter.oracleSources || [];
  166. const externalPointerStatus =
  167. firstResolvedToken(runtime.getExternalPointerStatus?.(), progressFrontmatter.externalPointerStatus) || 'not_used';
  168. const recommendations = [];
  169. // Critical gaps (P0)
  170. if (criticalGaps.length > 0) {
  171. recommendations.push({
  172. priority: 'URGENT',
  173. action: `Run /bmad:tea:atdd for ${criticalGaps.length} P0 requirements`,
  174. requirements: criticalGaps.map((r) => r.id),
  175. });
  176. }
  177. // High priority gaps (P1)
  178. if (highGaps.length > 0) {
  179. recommendations.push({
  180. priority: 'HIGH',
  181. action: `Run /bmad:tea:automate to expand coverage for ${highGaps.length} P1 requirements`,
  182. requirements: highGaps.map((r) => r.id),
  183. });
  184. }
  185. // Partial coverage
  186. if (partialCoverage.length > 0) {
  187. recommendations.push({
  188. priority: 'MEDIUM',
  189. action: `Complete coverage for ${partialCoverage.length} partially covered requirements`,
  190. requirements: partialCoverage.map((r) => r.id),
  191. });
  192. }
  193. if (endpointCoverageGaps.length > 0) {
  194. recommendations.push({
  195. priority: 'HIGH',
  196. action: `Add API tests for ${endpointCoverageGaps.length} uncovered endpoint(s)`,
  197. requirements: endpointCoverageGaps.map((r) => r.id || r.endpoint || 'unknown'),
  198. });
  199. }
  200. if (authCoverageGaps.length > 0) {
  201. recommendations.push({
  202. priority: 'HIGH',
  203. action: `Add negative-path auth/authz tests for ${authCoverageGaps.length} requirement(s)`,
  204. requirements: authCoverageGaps.map((r) => r.id || 'unknown'),
  205. });
  206. }
  207. if (errorPathGaps.length > 0) {
  208. recommendations.push({
  209. priority: 'MEDIUM',
  210. action: `Add error/edge scenario tests for ${errorPathGaps.length} happy-path-only criterion/criteria`,
  211. requirements: errorPathGaps.map((r) => r.id || 'unknown'),
  212. });
  213. }
  214. if (uiJourneyGaps.length > 0) {
  215. recommendations.push({
  216. priority: 'HIGH',
  217. action: `Add E2E or component coverage for ${uiJourneyGaps.length} inferred UI journey(s)`,
  218. requirements: uiJourneyGaps.map((r) => r.id || r.route || r.journey || 'unknown'),
  219. });
  220. }
  221. if (uiStateGaps.length > 0) {
  222. recommendations.push({
  223. priority: 'MEDIUM',
  224. action: `Add loading/empty/error/permission state coverage for ${uiStateGaps.length} UI journey(s)`,
  225. requirements: uiStateGaps.map((r) => r.id || r.route || r.journey || 'unknown'),
  226. });
  227. }
  228. // Quality issues
  229. recommendations.push({
  230. priority: 'LOW',
  231. action: 'Run /bmad:tea:test-review to assess test quality',
  232. requirements: [],
  233. });
  234. if (oracleResolutionMode === 'synthetic_source') {
  235. recommendations.push({
  236. priority: 'MEDIUM',
  237. action: 'Promote inferred journeys into formal acceptance criteria when the team confirms they reflect intended behavior',
  238. requirements: traceabilityMatrix.map((r) => r.id),
  239. });
  240. }
  241. ```
  242. ---
  243. ### 4. Calculate Coverage Statistics
  244. ```javascript
  245. const totalRequirements = traceabilityMatrix.length;
  246. const coveredRequirements = traceabilityMatrix.filter((r) => r.coverage === 'FULL' || r.coverage === 'PARTIAL').length;
  247. const fullyCovered = traceabilityMatrix.filter((r) => r.coverage === 'FULL').length;
  248. const safePct = (covered, total) => (total > 0 ? Math.round((covered / total) * 100) : 100);
  249. const coveragePercentage = safePct(fullyCovered, totalRequirements);
  250. // Priority-specific coverage
  251. const p0Total = traceabilityMatrix.filter((r) => r.priority === 'P0').length;
  252. const p0Covered = traceabilityMatrix.filter((r) => r.priority === 'P0' && r.coverage === 'FULL').length;
  253. const p1Total = traceabilityMatrix.filter((r) => r.priority === 'P1').length;
  254. const p1Covered = traceabilityMatrix.filter((r) => r.priority === 'P1' && r.coverage === 'FULL').length;
  255. const p2Total = traceabilityMatrix.filter((r) => r.priority === 'P2').length;
  256. const p2Covered = traceabilityMatrix.filter((r) => r.priority === 'P2' && r.coverage === 'FULL').length;
  257. const p3Total = traceabilityMatrix.filter((r) => r.priority === 'P3').length;
  258. const p3Covered = traceabilityMatrix.filter((r) => r.priority === 'P3' && r.coverage === 'FULL').length;
  259. const p0CoveragePercentage = safePct(p0Covered, p0Total);
  260. const p1CoveragePercentage = safePct(p1Covered, p1Total);
  261. const p2CoveragePercentage = safePct(p2Covered, p2Total);
  262. const p3CoveragePercentage = safePct(p3Covered, p3Total);
  263. ```
  264. ---
  265. ### 4b. Build Deduplicated Test Inventory and Trace Metadata
  266. Persist the unique discovered tests in Phase 1 so Step 5 does not need to reconstruct counts from per-requirement mappings.
  267. ```javascript
  268. const coverageEligibleStatuses = new Set(['FULL', 'PARTIAL', 'UNIT-ONLY', 'INTEGRATION-ONLY']);
  269. const byLevel = {
  270. e2e: { tests: 0, criteria_covered: 0 },
  271. api: { tests: 0, criteria_covered: 0 },
  272. component: { tests: 0, criteria_covered: 0 },
  273. unit: { tests: 0, criteria_covered: 0 },
  274. other: { tests: 0, criteria_covered: 0 }, // captures tests with unrecognized or empty level
  275. };
  276. const normalizeTestStatus = (test) => {
  277. const explicitStatus = String(test.status || '')
  278. .trim()
  279. .toLowerCase();
  280. if (['skipped', 'pending', 'fixme'].includes(explicitStatus)) return explicitStatus;
  281. if (test.fixme === true) return 'fixme';
  282. if (test.pending === true) return 'pending';
  283. if (test.skipped === true) return 'skipped';
  284. return 'active';
  285. };
  286. const uniqueTests = new Map();
  287. (traceabilityMatrix || []).forEach((req) => {
  288. (req.tests || []).forEach((test, index) => {
  289. // Do NOT use the per-requirement `index` as a fallback — the same test can appear
  290. // at different indices across requirements, producing spurious duplicate entries.
  291. // Use only stable, test-intrinsic fields; omit line when unavailable.
  292. const stableId =
  293. test.id ||
  294. [test.file, test.title || test.name, test.line].filter((value) => value !== undefined && value !== null && value !== '').join(':') ||
  295. null; // unresolvable — skip rather than manufacture a key
  296. if (stableId === null || uniqueTests.has(stableId)) return;
  297. const status = normalizeTestStatus(test);
  298. uniqueTests.set(stableId, {
  299. id: stableId,
  300. file: test.file || '',
  301. line: test.line ?? null,
  302. title: test.title || test.name || stableId,
  303. level: String(test.level || '')
  304. .trim()
  305. .toLowerCase(),
  306. status: status,
  307. skipped: status === 'skipped',
  308. fixme: status === 'fixme',
  309. pending: status === 'pending',
  310. blocker_reason: test.skip_reason || test.blocker_reason || test.fixme_reason || test.pending_reason || '',
  311. });
  312. });
  313. });
  314. [...uniqueTests.values()].forEach((test) => {
  315. const bucket = byLevel[test.level] ? test.level : 'other';
  316. if (bucket === 'other' && test.level) {
  317. console.warn(`[trace] unknown test level "${test.level}" for test "${test.id}" — counted in "other"`);
  318. }
  319. byLevel[bucket].tests += 1;
  320. });
  321. (traceabilityMatrix || []).forEach((req) => {
  322. if (!coverageEligibleStatuses.has(req.coverage)) return;
  323. const requirementLevels = new Set(
  324. (req.tests || []).map((test) => {
  325. const level = String(test.level || '')
  326. .trim()
  327. .toLowerCase();
  328. return byLevel[level] ? level : 'other';
  329. }),
  330. );
  331. requirementLevels.forEach((level) => {
  332. byLevel[level].criteria_covered += 1;
  333. });
  334. });
  335. const deduplicatedTests = [...uniqueTests.values()];
  336. const deduplicatedTestInventory = {
  337. summary: {
  338. files: [...new Set(deduplicatedTests.map((test) => test.file).filter(Boolean))].length,
  339. cases: deduplicatedTests.length,
  340. skipped_cases: deduplicatedTests.filter((test) => test.skipped).length,
  341. fixme_cases: deduplicatedTests.filter((test) => test.fixme).length,
  342. pending_cases: deduplicatedTests.filter((test) => test.pending).length,
  343. by_level: byLevel,
  344. },
  345. tests: deduplicatedTests,
  346. blockers: deduplicatedTests
  347. .filter((test) => ['skipped', 'pending', 'fixme'].includes(test.status))
  348. .map((test) => ({
  349. id: test.id,
  350. severity: test.status === 'skipped' ? 'high' : 'medium',
  351. reason: test.blocker_reason || `Test marked ${test.status} during trace collection`,
  352. test_file: test.file,
  353. test_title: test.title,
  354. })),
  355. };
  356. const extractedTargetId = runtime.getTraceTargetId?.() || null;
  357. const extractedTargetLabel = runtime.getTraceTargetLabel?.() || null;
  358. const traceTarget = {
  359. type: '{gate_type}',
  360. id: extractedTargetId, // story_id / epic_num / release_version / hotfix identifier from Step 1
  361. label: extractedTargetLabel || null,
  362. };
  363. ```
  364. ---
  365. ### 5. Generate Complete Coverage Matrix
  366. **Compile all Phase 1 outputs:**
  367. ```javascript
  368. const coverageMatrix = {
  369. phase: 'PHASE_1_COMPLETE',
  370. generated_at: new Date().toISOString(),
  371. trace_target: traceTarget,
  372. collection_mode: '{collection_mode}',
  373. allow_gate: '{allow_gate}',
  374. coverage_basis: resolvedCoverageBasis,
  375. summary_confidence: resolvedOracleConfidence,
  376. oracle: {
  377. resolution_mode: oracleResolutionMode,
  378. confidence: resolvedOracleConfidence,
  379. sources: oracleSources,
  380. external_pointer_status: externalPointerStatus,
  381. synthetic: oracleResolutionMode === 'synthetic_source',
  382. },
  383. requirements: traceabilityMatrix, // Full matrix from Step 3
  384. coverage_statistics: {
  385. total_requirements: totalRequirements,
  386. fully_covered: fullyCovered,
  387. partially_covered: partialCoverage.length,
  388. uncovered: uncoveredRequirements.length,
  389. overall_coverage_percentage: coveragePercentage,
  390. priority_breakdown: {
  391. P0: { total: p0Total, covered: p0Covered, percentage: p0CoveragePercentage },
  392. P1: { total: p1Total, covered: p1Covered, percentage: p1CoveragePercentage },
  393. P2: { total: p2Total, covered: p2Covered, percentage: p2CoveragePercentage },
  394. P3: { total: p3Total, covered: p3Covered, percentage: p3CoveragePercentage },
  395. },
  396. },
  397. gap_analysis: {
  398. critical_gaps: criticalGaps,
  399. high_gaps: highGaps,
  400. medium_gaps: mediumGaps,
  401. low_gaps: lowGaps,
  402. partial_coverage_items: partialCoverage,
  403. unit_only_items: unitOnlyCoverage,
  404. },
  405. coverage_heuristics: {
  406. endpoint_gaps: endpointCoverageGaps,
  407. auth_negative_path_gaps: authCoverageGaps,
  408. happy_path_only_gaps: errorPathGaps,
  409. ui_journey_gaps: uiJourneyGaps,
  410. ui_state_gaps: uiStateGaps,
  411. counts: heuristicGapCounts,
  412. },
  413. test_inventory: deduplicatedTestInventory,
  414. blockers: deduplicatedTestInventory.blockers,
  415. recommendations: recommendations,
  416. };
  417. ```
  418. ---
  419. ### 6. Output Coverage Matrix to Temp File
  420. **Write to temp file for Phase 2:**
  421. ```javascript
  422. const outputPath = '{tempOutputFile}';
  423. fs.writeFileSync(outputPath, JSON.stringify(coverageMatrix, null, 2), 'utf8');
  424. console.log(`✅ Phase 1 Complete: Coverage matrix saved to ${outputPath}`);
  425. ```
  426. **Record the resolved path in the progress document** so Step 5 can read the exact same file rather than re-evaluating the timestamp expression:
  427. After writing the temp file, update the YAML frontmatter in `{outputFile}` to include:
  428. ```yaml
  429. tempCoverageMatrixPath: '<resolved outputPath>'
  430. ```
  431. Step 5 reads `tempCoverageMatrixPath` from the frontmatter first; falls back to reconstructing `{tempOutputFile}` only when the key is absent.
  432. ---
  433. ### 7. Display Phase 1 Summary
  434. ```
  435. ✅ Phase 1 Complete: Coverage Matrix Generated
  436. 📊 Coverage Statistics:
  437. - Total Requirements: {totalRequirements}
  438. - Fully Covered: {fullyCovered} ({coveragePercentage}%)
  439. - Partially Covered: {partialCoverage.length}
  440. - Uncovered: {uncoveredRequirements.length}
  441. 🎯 Priority Coverage:
  442. - P0: {p0Covered}/{p0Total} ({p0CoveragePercentage}%)
  443. - P1: {p1Covered}/{p1Total} ({p1CoveragePercentage}%)
  444. - P2: {p2Covered}/{p2Total} ({p2CoveragePercentage}%)
  445. - P3: {p3Covered}/{p3Total} ({p3CoveragePercentage}%)
  446. ⚠️ Gaps Identified:
  447. - Critical (P0): {criticalGaps.length}
  448. - High (P1): {highGaps.length}
  449. - Medium (P2): {mediumGaps.length}
  450. - Low (P3): {lowGaps.length}
  451. 🔍 Coverage Heuristics:
  452. - Endpoints without tests: {endpointCoverageGaps.length}
  453. - Auth negative-path gaps: {authCoverageGaps.length}
  454. - Happy-path-only criteria: {errorPathGaps.length}
  455. 📝 Recommendations: {recommendations.length}
  456. 🔄 Phase 2: Gate decision (next step)
  457. ```
  458. ### Orchestration Notes for This Step
  459. When `resolvedMode` is `agent-team` or `subagent`, parallelize only dependency-safe sections:
  460. - Worker A: gap classification (section 1)
  461. - Worker B: heuristics gap extraction (section 2)
  462. - Worker C: coverage statistics (section 4)
  463. Section 3 (recommendation synthesis) depends on outputs from sections 1 and 2, so run it only after Workers A and B complete.
  464. Section 5 remains the deterministic merge point after sections 1-4 are finished.
  465. If `resolvedMode` is `sequential`, execute sections 1→7 in order.
  466. ---
  467. ## EXIT CONDITION
  468. **PHASE 1 COMPLETE when:**
  469. - ✅ Gap analysis complete
  470. - ✅ Recommendations generated
  471. - ✅ Coverage statistics calculated
  472. - ✅ Coverage matrix saved to temp file
  473. - ✅ Summary displayed
  474. **Proceed to Phase 2 (Step 5: Gate Decision)**
  475. ---
  476. ### 8. Save Progress
  477. **Save this step's accumulated work to `{outputFile}`.**
  478. - **If `{outputFile}` does not exist** (first save), create it using the workflow template (if available) with YAML frontmatter:
  479. ```yaml
  480. ---
  481. stepsCompleted: ['step-04-analyze-gaps']
  482. lastStep: 'step-04-analyze-gaps'
  483. lastSaved: '{date}'
  484. ---
  485. ```
  486. Then write this step's output below the frontmatter.
  487. - **If `{outputFile}` already exists**, update:
  488. - Add `'step-04-analyze-gaps'` to `stepsCompleted` array (only if not already present)
  489. - Set `lastStep: 'step-04-analyze-gaps'`
  490. - Set `lastSaved: '{date}'`
  491. - Append this step's output to the appropriate section of the document.
  492. Load next step: `{nextStepFile}`
  493. ---
  494. ## 🚨 PHASE 1 SUCCESS METRICS
  495. ### ✅ SUCCESS:
  496. - Coverage matrix complete and accurate
  497. - All gaps identified and prioritized
  498. - Recommendations actionable
  499. - Temp file output valid JSON
  500. ### ❌ FAILURE:
  501. - Coverage matrix incomplete
  502. - Gap analysis missing
  503. - Invalid JSON output
  504. **Master Rule:** Phase 1 MUST output complete coverage matrix to temp file before Phase 2 can proceed.