@sandrinio/vbounce 1.8.0 → 2.0.0

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 (39) hide show
  1. package/README.md +439 -159
  2. package/bin/vbounce.mjs +255 -25
  3. package/brains/AGENTS.md +62 -24
  4. package/brains/CHANGELOG.md +24 -2
  5. package/brains/CLAUDE.md +124 -30
  6. package/brains/GEMINI.md +64 -27
  7. package/brains/SETUP.md +15 -15
  8. package/brains/claude-agents/architect.md +1 -1
  9. package/brains/claude-agents/developer.md +7 -5
  10. package/brains/claude-agents/devops.md +1 -1
  11. package/brains/claude-agents/qa.md +1 -1
  12. package/brains/claude-agents/scribe.md +1 -1
  13. package/brains/copilot/copilot-instructions.md +8 -3
  14. package/brains/cursor-rules/vbounce-docs.mdc +2 -2
  15. package/brains/cursor-rules/vbounce-process.mdc +6 -3
  16. package/brains/cursor-rules/vbounce-rules.mdc +2 -2
  17. package/brains/windsurf/.windsurfrules +7 -2
  18. package/docs/HOTFIX_EDGE_CASES.md +1 -1
  19. package/package.json +5 -5
  20. package/scripts/close_sprint.mjs +32 -1
  21. package/scripts/doctor.mjs +3 -3
  22. package/scripts/hotfix_manager.sh +2 -2
  23. package/scripts/init_gate_config.sh +1 -1
  24. package/scripts/post_sprint_improve.mjs +486 -0
  25. package/scripts/pre_gate_common.sh +1 -1
  26. package/scripts/pre_gate_runner.sh +1 -1
  27. package/scripts/suggest_improvements.mjs +207 -44
  28. package/scripts/validate_report.mjs +1 -1
  29. package/scripts/verify_framework.mjs +1 -1
  30. package/skills/agent-team/SKILL.md +48 -25
  31. package/skills/agent-team/references/discovery.md +97 -0
  32. package/skills/doc-manager/SKILL.md +146 -22
  33. package/skills/improve/SKILL.md +149 -58
  34. package/skills/lesson/SKILL.md +14 -0
  35. package/templates/epic.md +19 -16
  36. package/templates/spike.md +143 -0
  37. package/templates/sprint.md +32 -12
  38. package/templates/sprint_report.md +6 -4
  39. package/templates/story.md +23 -8
@@ -2,7 +2,11 @@
2
2
 
3
3
  /**
4
4
  * suggest_improvements.mjs
5
- * Generates improvement suggestions from sprint trends + lessons.
5
+ * Generates human-readable improvement suggestions from:
6
+ * 1. Improvement manifest (post_sprint_improve.mjs output)
7
+ * 2. Sprint trends
8
+ * 3. LESSONS.md
9
+ *
6
10
  * Overwrites (not appends) to prevent stale suggestion accumulation.
7
11
  *
8
12
  * Usage:
@@ -14,6 +18,7 @@
14
18
  import fs from 'fs';
15
19
  import path from 'path';
16
20
  import { fileURLToPath } from 'url';
21
+ import { spawnSync } from 'child_process';
17
22
 
18
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
24
  const ROOT = path.resolve(__dirname, '..');
@@ -26,41 +31,76 @@ if (!sprintId) {
26
31
 
27
32
  const today = new Date().toISOString().split('T')[0];
28
33
 
29
- // 1. Read trends if available
34
+ // ---------------------------------------------------------------------------
35
+ // 0. Run post_sprint_improve.mjs to generate fresh manifest
36
+ // ---------------------------------------------------------------------------
37
+
38
+ const analyzerScript = path.join(__dirname, 'post_sprint_improve.mjs');
39
+ if (fs.existsSync(analyzerScript)) {
40
+ console.log('Running post-sprint improvement analyzer...');
41
+ const result = spawnSync(process.execPath, [analyzerScript, sprintId], {
42
+ stdio: 'inherit',
43
+ cwd: process.cwd(),
44
+ });
45
+ if (result.status !== 0) {
46
+ console.warn('⚠ Analyzer returned non-zero — continuing with available data.');
47
+ }
48
+ console.log('');
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // 1. Read improvement manifest (from post_sprint_improve.mjs)
53
+ // ---------------------------------------------------------------------------
54
+
55
+ const manifestPath = path.join(ROOT, '.bounce', 'improvement-manifest.json');
56
+ let manifest = null;
57
+ if (fs.existsSync(manifestPath)) {
58
+ try {
59
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
60
+ } catch {
61
+ console.warn('⚠ Could not parse improvement-manifest.json');
62
+ }
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // 2. Read trends if available
67
+ // ---------------------------------------------------------------------------
68
+
30
69
  const trendsFile = path.join(ROOT, '.bounce', 'trends.md');
31
70
  let trendsContent = null;
32
71
  if (fs.existsSync(trendsFile)) {
33
72
  trendsContent = fs.readFileSync(trendsFile, 'utf8');
34
73
  }
35
74
 
36
- // 2. Read LESSONS.md
75
+ // ---------------------------------------------------------------------------
76
+ // 3. Read LESSONS.md
77
+ // ---------------------------------------------------------------------------
78
+
37
79
  const lessonsFile = path.join(ROOT, 'LESSONS.md');
38
80
  let lessonCount = 0;
39
81
  let oldLessons = [];
40
82
  if (fs.existsSync(lessonsFile)) {
41
83
  const lines = fs.readFileSync(lessonsFile, 'utf8').split('\n');
42
- // Count lessons by counting ### entries
43
84
  const lessonEntries = lines.filter(l => /^###\s+\[\d{4}-\d{2}-\d{2}\]/.test(l));
44
85
  lessonCount = lessonEntries.length;
45
86
 
46
- // Flag lessons older than 90 days
47
87
  const cutoff = new Date();
48
88
  cutoff.setDate(cutoff.getDate() - 90);
49
89
  oldLessons = lessonEntries.filter(entry => {
50
90
  const dateMatch = entry.match(/\[(\d{4}-\d{2}-\d{2})\]/);
51
- if (dateMatch) {
52
- return new Date(dateMatch[1]) < cutoff;
53
- }
91
+ if (dateMatch) return new Date(dateMatch[1]) < cutoff;
54
92
  return false;
55
93
  });
56
94
  }
57
95
 
58
- // 3. Read improvement-log for rejected items (to avoid re-suggesting)
96
+ // ---------------------------------------------------------------------------
97
+ // 4. Read improvement-log for rejected items
98
+ // ---------------------------------------------------------------------------
99
+
59
100
  const improvementLog = path.join(ROOT, '.bounce', 'improvement-log.md');
60
101
  let rejectedItems = [];
61
102
  if (fs.existsSync(improvementLog)) {
62
103
  const logContent = fs.readFileSync(improvementLog, 'utf8');
63
- // Simple extraction: look for table rows in "Rejected" section
64
104
  const rejectedMatch = logContent.match(/## Rejected\n[\s\S]*?(?=\n## |$)/);
65
105
  if (rejectedMatch) {
66
106
  rejectedItems = rejectedMatch[0].split('\n')
@@ -70,7 +110,10 @@ if (fs.existsSync(improvementLog)) {
70
110
  }
71
111
  }
72
112
 
73
- // 4. Parse sprint stats from trends
113
+ // ---------------------------------------------------------------------------
114
+ // 5. Parse sprint stats from trends
115
+ // ---------------------------------------------------------------------------
116
+
74
117
  let lastSprintStats = null;
75
118
  if (trendsContent) {
76
119
  const rows = trendsContent.split('\n').filter(l => l.match(/^\| S-\d{2} \|/));
@@ -86,18 +129,90 @@ if (trendsContent) {
86
129
  }
87
130
  }
88
131
 
132
+ // ---------------------------------------------------------------------------
133
+ // 6. Build suggestions
134
+ // ---------------------------------------------------------------------------
135
+
89
136
  const suggestions = [];
90
137
  let itemNum = 1;
91
138
 
92
- // 5. Generate suggestions based on data
139
+ // Impact level badge
140
+ function badge(impact) {
141
+ const badges = {
142
+ P0: '🔴 P0 Critical',
143
+ P1: '🟠 P1 High',
144
+ P2: '🟡 P2 Medium',
145
+ P3: '⚪ P3 Low',
146
+ };
147
+ return badges[impact?.level] || '⚪ Unrated';
148
+ }
149
+
150
+ // --- Manifest-driven suggestions (retro + lessons + effectiveness) ---
151
+ if (manifest && manifest.proposals) {
152
+ for (const proposal of manifest.proposals) {
153
+ // Skip previously rejected
154
+ if (rejectedItems.some(r => proposal.title.includes(r))) continue;
155
+
156
+ if (proposal.source === 'retro') {
157
+ suggestions.push({
158
+ num: itemNum++,
159
+ category: 'Retro',
160
+ impact: proposal.impact,
161
+ title: proposal.title,
162
+ detail: [
163
+ `**Area:** ${proposal.area}`,
164
+ `**Source Agent:** ${proposal.sourceAgent}`,
165
+ `**Severity:** ${proposal.severity}`,
166
+ proposal.recurring ? `**Recurring:** Yes — found in ${proposal.recurrenceSprints.join(', ')} (${proposal.recurrenceCount}x)` : null,
167
+ `**Suggested Fix:** ${proposal.suggestedFix}`,
168
+ ].filter(Boolean).join('\n'),
169
+ target: mapAreaToTarget(proposal.area),
170
+ effort: proposal.severity === 'Blocker' ? 'Medium' : 'Low',
171
+ });
172
+ } else if (proposal.source === 'lesson') {
173
+ suggestions.push({
174
+ num: itemNum++,
175
+ category: 'Lesson → Automation',
176
+ impact: proposal.impact,
177
+ title: proposal.title,
178
+ detail: [
179
+ `**Rule:** ${proposal.rule}`,
180
+ `**What happened:** ${proposal.whatHappened}`,
181
+ `**Active for:** ${proposal.ageSprints} sprint(s) (since ${proposal.lessonDate})`,
182
+ `**Automation type:** ${proposal.automationType}`,
183
+ `**Action:** ${proposal.automationDetail?.action}`,
184
+ `**Rationale:** ${proposal.automationDetail?.rationale}`,
185
+ ].filter(Boolean).join('\n'),
186
+ target: mapAutomationTypeToTarget(proposal.automationType),
187
+ effort: proposal.automationDetail?.effort || 'Low',
188
+ });
189
+ } else if (proposal.source === 'effectiveness_check') {
190
+ suggestions.push({
191
+ num: itemNum++,
192
+ category: 'Effectiveness',
193
+ impact: proposal.impact,
194
+ title: proposal.title,
195
+ detail: [
196
+ `**Status:** ${proposal.detail}`,
197
+ `**Originally applied in:** ${proposal.appliedInSprint}`,
198
+ '**Action:** Re-examine the original fix — it did not resolve the underlying issue.',
199
+ ].join('\n'),
200
+ target: 'Review original improvement in .bounce/improvement-log.md',
201
+ effort: 'Medium',
202
+ });
203
+ }
204
+ }
205
+ }
206
+
207
+ // --- Metric-driven suggestions (from trends) ---
93
208
  if (lastSprintStats) {
94
209
  if (lastSprintStats.firstPassRate < 80) {
95
210
  suggestions.push({
96
211
  num: itemNum++,
97
- category: 'Process',
212
+ category: 'Metrics',
213
+ impact: { level: 'P1', label: 'High' },
98
214
  title: `Low first-pass rate (${lastSprintStats.firstPassRate}%)`,
99
215
  detail: `First-pass rate was below 80% in ${lastSprintStats.sprintId}. This suggests spec ambiguity or insufficient context packs.`,
100
- recommendation: 'Add spec quality gate to `validate_bounce_readiness.mjs`: check Story §1 word count > 50 and §2 has ≥ 2 Gherkin scenarios.',
101
216
  target: 'scripts/validate_bounce_readiness.mjs',
102
217
  effort: 'Low',
103
218
  });
@@ -106,10 +221,10 @@ if (lastSprintStats) {
106
221
  if (lastSprintStats.avgTax > 10) {
107
222
  suggestions.push({
108
223
  num: itemNum++,
109
- category: 'Process',
224
+ category: 'Metrics',
225
+ impact: { level: 'P1', label: 'High' },
110
226
  title: `High correction tax (${lastSprintStats.avgTax}% average)`,
111
- detail: 'Average correction tax exceeded 10%, indicating significant human intervention was needed.',
112
- recommendation: 'Auto-flag stories with more than 3 files expected in Sprint Plan §2 Risk Flags. Consider splitting before bouncing.',
227
+ detail: 'Average correction tax exceeded 10%, indicating significant human intervention.',
113
228
  target: 'skills/agent-team/SKILL.md Step 1',
114
229
  effort: 'Low',
115
230
  });
@@ -118,70 +233,117 @@ if (lastSprintStats) {
118
233
  if (lastSprintStats.avgBounces > 0.5) {
119
234
  suggestions.push({
120
235
  num: itemNum++,
121
- category: 'Process',
236
+ category: 'Metrics',
237
+ impact: { level: 'P2', label: 'Medium' },
122
238
  title: `High bounce rate (${lastSprintStats.avgBounces} avg per story)`,
123
- detail: 'Run `vbounce trends` to see root cause breakdown and identify recurring patterns.',
124
- recommendation: 'Review root_cause field in archived QA/Arch FAIL reports to identify systemic issues.',
239
+ detail: 'Run `vbounce trends` to see root cause breakdown.',
125
240
  target: 'scripts/sprint_trends.mjs',
126
241
  effort: 'Low',
127
242
  });
128
243
  }
129
244
  }
130
245
 
131
- // Old lessons suggestion
246
+ // --- Lesson graduation ---
132
247
  if (oldLessons.length > 0) {
133
248
  const notRejected = oldLessons.filter(l => !rejectedItems.some(r => l.includes(r)));
134
249
  if (notRejected.length > 0) {
135
250
  suggestions.push({
136
251
  num: itemNum++,
137
- category: 'Framework',
138
- title: `${notRejected.length} lessons older than 90 days`,
252
+ category: 'Graduation',
253
+ impact: { level: 'P2', label: 'Medium' },
254
+ title: `${notRejected.length} lesson(s) older than 90 days — graduation candidates`,
139
255
  detail: notRejected.map(l => ` - ${l}`).join('\n'),
140
- recommendation: 'Review these lessons. Lessons not triggered in 3+ sprints should be archived to LESSONS_ARCHIVE.md. Lessons proven over 3+ sprints should be graduated to agent configs.',
141
- target: 'LESSONS.md',
142
- effort: 'Trivial',
256
+ target: 'LESSONS.md brains/claude-agents/',
257
+ effort: 'Low',
143
258
  });
144
259
  }
145
260
  }
146
261
 
147
- // General framework suggestions
148
- suggestions.push({
149
- num: itemNum++,
150
- category: 'Framework',
151
- title: 'Review lesson graduation candidates',
152
- detail: `You have ${lessonCount} lessons in LESSONS.md. Lessons proven over 3+ sprints should graduate to permanent agent config rules.`,
153
- recommendation: 'Run a review: which lessons have prevented recurrences for 3+ sprints? Graduate those to `.claude/agents/*.md` or `brains/claude-agents/*.md`.',
154
- target: 'LESSONS.md + brains/claude-agents/',
155
- effort: 'Low',
156
- });
157
-
262
+ // --- Health check ---
158
263
  suggestions.push({
159
264
  num: itemNum++,
160
265
  category: 'Health',
266
+ impact: { level: 'P3', label: 'Low' },
161
267
  title: 'Run vbounce doctor',
162
- detail: 'Verify the V-Bounce OS installation is healthy after this sprint.',
163
- recommendation: 'Run: `vbounce doctor` — checks brain files, templates, scripts, state.json validity.',
268
+ detail: 'Verify the V-Bounce Engine installation is healthy after this sprint.',
164
269
  target: 'scripts/doctor.mjs',
165
270
  effort: 'Trivial',
166
271
  });
167
272
 
168
- // 6. Format output
273
+ // ---------------------------------------------------------------------------
274
+ // 7. Format output
275
+ // ---------------------------------------------------------------------------
276
+
277
+ function mapAreaToTarget(area) {
278
+ const map = {
279
+ 'Templates': 'templates/*.md',
280
+ 'Agent Handoffs': 'brains/claude-agents/*.md',
281
+ 'RAG Pipeline': 'scripts/prep_*.mjs',
282
+ 'Skills': 'skills/*/SKILL.md',
283
+ 'Process Flow': 'skills/agent-team/SKILL.md',
284
+ 'Tooling & Scripts': 'scripts/*',
285
+ };
286
+ return map[area] || area;
287
+ }
288
+
289
+ function mapAutomationTypeToTarget(type) {
290
+ const map = {
291
+ 'gate_check': '.bounce/gate-checks.json OR scripts/pre_gate_runner.sh',
292
+ 'script': 'scripts/',
293
+ 'template_field': 'templates/*.md',
294
+ 'agent_config': 'brains/claude-agents/*.md',
295
+ };
296
+ return map[type] || type;
297
+ }
298
+
169
299
  const suggestionBlocks = suggestions.map(s => {
170
- const rejectedNote = rejectedItems.some(r => s.title.includes(r)) ? '\n> ⚠ This was previously rejected — skipping.' : '';
171
- return `### ${s.num}. [${s.category}] ${s.title}${rejectedNote}
300
+ return `### ${s.num}. [${badge(s.impact)}] [${s.category}] ${s.title}
172
301
  ${s.detail}
173
302
 
174
- **Recommendation:** ${s.recommendation}
175
303
  **Target:** \`${s.target}\`
176
304
  **Effort:** ${s.effort}`;
177
305
  }).join('\n\n---\n\n');
178
306
 
307
+ // Impact level reference
308
+ const impactRef = `## Impact Levels
309
+
310
+ | Level | Label | Meaning | Timeline |
311
+ |-------|-------|---------|----------|
312
+ | **P0** | 🔴 Critical | Blocks agent work or causes incorrect output | Fix before next sprint |
313
+ | **P1** | 🟠 High | Causes rework — bounces, wasted tokens, repeated manual steps | Fix this improvement cycle |
314
+ | **P2** | 🟡 Medium | Friction that slows agents but does not block | Fix within 2 sprints |
315
+ | **P3** | ⚪ Low | Polish — nice-to-have, batch with other improvements | Batch when convenient |`;
316
+
317
+ // Summary stats
318
+ const summarySection = manifest ? `## Summary
319
+
320
+ | Source | Count |
321
+ |--------|-------|
322
+ | Retro (§5 findings) | ${manifest.summary.bySource.retro} |
323
+ | Lesson → Automation | ${manifest.summary.bySource.lesson} |
324
+ | Effectiveness checks | ${manifest.summary.bySource.effectiveness_check} |
325
+ | Metric-driven | ${suggestions.filter(s => s.category === 'Metrics').length} |
326
+ | **Total** | **${suggestions.length}** |
327
+
328
+ | Impact | Count |
329
+ |--------|-------|
330
+ | 🔴 P0 Critical | ${manifest.summary.byImpact.P0} |
331
+ | 🟠 P1 High | ${manifest.summary.byImpact.P1 + suggestions.filter(s => s.category === 'Metrics' && s.impact.level === 'P1').length} |
332
+ | 🟡 P2 Medium | ${manifest.summary.byImpact.P2 + suggestions.filter(s => s.category === 'Metrics' && s.impact.level === 'P2').length} |
333
+ | ⚪ P3 Low | ${manifest.summary.byImpact.P3 + suggestions.filter(s => s.category === 'Health').length} |` : '';
334
+
179
335
  const output = [
180
336
  `# Improvement Suggestions (post ${sprintId})`,
181
337
  `> Generated: ${today}. Review each item. Approved items are applied by the Lead at sprint boundary.`,
182
338
  `> Rejected items go to \`.bounce/improvement-log.md\` with reason.`,
183
339
  `> Applied items go to \`.bounce/improvement-log.md\` under Applied.`,
184
340
  '',
341
+ impactRef,
342
+ '',
343
+ summarySection,
344
+ '',
345
+ '---',
346
+ '',
185
347
  suggestionBlocks || '_No suggestions generated — all metrics look healthy!_',
186
348
  '',
187
349
  '---',
@@ -192,9 +354,10 @@ const output = [
192
354
  `- **Defer** → Record in \`.bounce/improvement-log.md\` under Deferred`,
193
355
  '',
194
356
  `> Framework changes (brains/, skills/, templates/) are applied at sprint boundaries only — never mid-sprint.`,
357
+ `> Use \`/improve\` skill to have the Team Lead apply approved changes with brain-file sync.`,
195
358
  ].join('\n');
196
359
 
197
360
  const outputFile = path.join(ROOT, '.bounce', 'improvement-suggestions.md');
198
- fs.writeFileSync(outputFile, output); // overwrite, not append
361
+ fs.writeFileSync(outputFile, output);
199
362
  console.log(`✓ Improvement suggestions written to .bounce/improvement-suggestions.md`);
200
363
  console.log(` ${suggestions.length} suggestion(s) generated`);
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * validate_report.mjs
5
5
  *
6
- * Strict YAML Frontmatter validation for V-Bounce OS Agent Reports.
6
+ * Strict YAML Frontmatter validation for V-Bounce Engine Agent Reports.
7
7
  * Fails loudly if an agent hallucinates formatting or omits required fields,
8
8
  * so the orchestrator can bounce the prompt back.
9
9
  */
@@ -63,7 +63,7 @@ const EXPECTED_PROMPT_SIGNATURES = {
63
63
 
64
64
  function main() {
65
65
  console.log("===========================================");
66
- console.log(" V-Bounce OS: Framework Integrity Check");
66
+ console.log(" V-Bounce Engine: Framework Integrity Check");
67
67
  console.log("===========================================\n");
68
68
 
69
69
  let hasErrors = false;
@@ -176,24 +176,25 @@ Examples:
176
176
  ## The Bounce Sequence
177
177
 
178
178
  ### Step 0: Sprint Setup
179
+
180
+ **Prerequisite:** Sprint Planning (Phase 2) must be complete. The Sprint Plan must be in "Confirmed" status with human approval before proceeding.
181
+
179
182
  ```
180
183
  1. Cut sprint branch from main:
181
184
  git checkout -b sprint/S-01 main
182
185
  mkdir -p .bounce/archive
183
186
 
184
- 2. Read RISK_REGISTRY.md — check for open risks relevant to this sprint's stories.
185
- If high-severity risks affect planned stories, flag to human before proceeding.
186
-
187
- 3. Read sprint-{XX}.md check §2 Sprint Open Questions.
188
- If unresolved questions block stories in this sprint, flag to human.
189
- Do NOT start bouncing stories with unresolved blocking questions.
187
+ 2. Verify Sprint Plan:
188
+ - Sprint Plan status must be "Confirmed" (human-approved in Phase 2)
189
+ - §0 Sprint Readiness Gate must be fully checked
190
+ - §3 Sprint Open Questions must have no unresolved blocking items
191
+ If any check fails, return to Phase 2 (Sprint Planning).
190
192
 
191
- 4. If vdocs/_manifest.json exists, read it.
193
+ 3. If vdocs/_manifest.json exists, read it.
192
194
  Understand what's already documented — this informs which stories
193
195
  may require doc updates after the sprint.
194
196
 
195
- 5. Triage Requests: If an incoming task is an L1 Trivial change (1-2 files),
196
- use the **Hotfix Path**:
197
+ 4. **Hotfix Path** (L1 Trivial tasks only triaged during Phase 1):
197
198
  a. Create `HOTFIX-{Date}-{Name}.md` using the template.
198
199
  b. Delegate to Developer (no worktree needed if acting on active branch).
199
200
  c. Developer runs `hotfix_manager.sh ledger "{Title}" "{Description}"` after implementation.
@@ -201,26 +202,35 @@ Examples:
201
202
  e. DevOps runs `hotfix_manager.sh sync` to update any active story worktrees.
202
203
  f. Update Delivery Plan Status to "Done".
203
204
 
204
- 6. **Gate Config Check**:
205
+ 5. **Gate Config Check**:
205
206
  - If `.bounce/gate-checks.json` does not exist, run `./scripts/init_gate_config.sh` to auto-detect the project stack and generate default gate checks.
206
207
  - If it exists, verify it's current (stack detection may have changed).
207
208
 
208
- 7. **Parallel Readiness Check** (before bouncing multiple stories simultaneously):
209
+ 6. **Parallel Readiness Check** (before bouncing multiple stories simultaneously):
209
210
  - Verify test runner config excludes `.worktrees/` (vitest, jest, pytest, etc.)
210
211
  - Verify no shared mutable state between worktrees (e.g., shared temp files, singletons writing to same path)
211
212
  - Verify `.gitignore` includes `.worktrees/`
212
213
  If any check fails, fix before spawning parallel stories. Intermittent test failures from worktree cross-contamination erode trust in the test suite fast.
213
214
 
214
- 8. **Dependency Check & Execution Mode**:
215
- - For each story, check the `Depends On:` field in its template.
216
- - If Story B depends on Story A, you MUST execute them sequentially. Do not create Story B's worktree or spawn its Developer until Story A has successfully passed the DevOps merge step.
217
- - Determine Execution Mode:
218
- - **Full Bounce (Default)**: Normal L2-L4 stories go through full Dev → QA → Architect → DevOps flow.
219
- - **Fast Track (L1/L2 Minor)**: For cosmetic UI tweaks or isolated refactors, execute Dev → DevOps only. Skip QA and Architect loops to save overhead. Validate manually during Sprint Review.
220
-
221
- 9. Update sprint-{XX}.md: Status → "Active"
215
+ 7. Update sprint-{XX}.md: Status "Active"
222
216
  ```
223
217
 
218
+ **Note:** Risk assessment, dependency checks, scope selection, and execution mode decisions all happen during Sprint Planning (Phase 2), not here. Step 0 executes the confirmed plan.
219
+
220
+ ### Step 0.5: Discovery Check (L4 / 🔴 Stories Only)
221
+
222
+ Before moving any story to Ready to Bounce:
223
+
224
+ 1. For each story with `complexity_label: L4` or `ambiguity: 🔴 High`:
225
+ - Check for linked spikes in `product_plans/backlog/EPIC-{NNN}_{name}/SPIKE-*.md`
226
+ - If no spikes exist → create them (invoke doc-manager ambiguity rubric)
227
+ - If spikes exist but are not Validated/Closed → execute discovery sub-flow
228
+ - See `skills/agent-team/references/discovery.md`
229
+
230
+ 2. Once all spikes are Validated/Closed:
231
+ - Update story `ambiguity` frontmatter (should now be 🟡 or 🟢)
232
+ - Transition: Probing/Spiking → Refinement → Ready to Bounce
233
+
224
234
  ### Step 1: Story Initialization
225
235
  For each story with V-Bounce State "Ready to Bounce":
226
236
  ```bash
@@ -313,6 +323,18 @@ mkdir -p .worktrees/STORY-{ID}-{StoryName}/.bounce/{tasks,reports}
313
323
  ```
314
324
  Update sprint-{XX}.md: V-Bounce State → "Done"
315
325
 
326
+ ### Step 5.5: Immediate Lesson Recording
327
+ After each story merge, before proceeding to the next story:
328
+ ```
329
+ 1. Read Dev report — check `lessons_flagged` field
330
+ 2. Read QA report — check for lessons flagged during validation
331
+ 3. For each flagged lesson:
332
+ - Present to the human for approval
333
+ - If approved → record to LESSONS.md immediately (follow lesson skill format)
334
+ - If rejected → note as "No" in Sprint Report §4
335
+ 4. Do NOT defer this to Step 7 — context decays fast
336
+ ```
337
+
316
338
  ### Step 6: Sprint Integration Audit
317
339
  After ALL stories are merged into `sprint/S-01`:
318
340
  ```
@@ -336,12 +358,12 @@ After ALL stories are merged into `sprint/S-01`:
336
358
  ```
337
359
  4. V-Bounce State → "Sprint Review" for all stories
338
360
  4. Present Sprint Report to human
339
- 5. **BLOCKING STEP — Lesson Approval:**
340
- Review and approve/reject ALL flagged lessons from §4 of the Sprint Report.
341
- Do NOT proceed to Sprint Release until every lesson has a status of "Yes" or "No".
342
- Stale lessons lose context approve them while the sprint is fresh.
343
- Present each lesson to the human and record approved ones to LESSONS.md immediately.
344
- 6. After approval → Spawn devops subagent for Sprint Release:
361
+ 5. **Lesson Review (non-blocking):**
362
+ Most lessons should already be recorded to LESSONS.md during Step 5.5.
363
+ Review §4 of the Sprint Report confirm all flagged lessons have a status.
364
+ If any lessons were missed during Step 5.5, present them now and record approved ones.
365
+ This is a review step, not a first-time approval gate.
366
+ 6. After review → Spawn devops subagent for Sprint Release:
345
367
  - Merge sprint/S-01 → main (--no-ff)
346
368
  - Tag release: v{VERSION}
347
369
  - Run full test suite + build + lint on main
@@ -501,6 +523,7 @@ If merging story branch into sprint branch creates conflicts:
501
523
  - **Check risks before bouncing.** Read RISK_REGISTRY.md at sprint start. Flag high-severity risks that affect planned stories.
502
524
  - **Resolve open questions first.** Read the active `sprint-{XX}.md` §2 Sprint Open Questions at sprint start. Do not bounce stories with unresolved blocking questions.
503
525
  - **Know what's documented.** If `vdocs/_manifest.json` exists, read it at sprint start. Pass relevant doc references to agents. Offer documentation updates after sprints that deliver new features.
526
+ - **Resolve discovery before bouncing.** L4 stories and 🔴 ambiguity stories MUST complete spikes before entering the bounce sequence. See `skills/agent-team/references/discovery.md`.
504
527
 
505
528
  ## Keywords
506
529
 
@@ -0,0 +1,97 @@
1
+ # Discovery Phase — Spike Execution Protocol
2
+
3
+ > On-demand reference from agent-team/SKILL.md. When and how to run discovery spikes for ambiguous work.
4
+
5
+ ## When Discovery Triggers
6
+
7
+ 1. **Epic-level 🔴 ambiguity** — detected during Epic creation or review via the doc-manager ambiguity assessment rubric.
8
+ 2. **Story labeled L4** — transitioning to Probing/Spiking state. L4 stories MUST have at least one linked spike before they can progress.
9
+ 3. **Blocking Open Questions** — Epic §8 has items marked "Blocking" with no ADR or prior decision.
10
+
11
+ ## Spike Lifecycle
12
+
13
+ ```
14
+ Open → Investigating → Findings Ready → Validated → Closed
15
+ ```
16
+
17
+ | Status | Who Acts | What Happens |
18
+ |--------|----------|-------------|
19
+ | **Open** | Team Lead | Spike created from `templates/spike.md`, linked in Epic §9 |
20
+ | **Investigating** | Developer | Code exploration, prototyping, benchmarks. Fills §4 Findings and §5 Decision |
21
+ | **Findings Ready** | Developer → Architect | Developer marks complete. Awaiting Architect validation |
22
+ | **Validated** | Architect | Confirms findings against Safe Zone and ADRs. PASS → Validated. FAIL → back to Investigating |
23
+ | **Closed** | Team Lead | All §7 Affected Documents checked off. Findings propagated to Epic, Roadmap, Risk Registry |
24
+
25
+ ## Execution Protocol
26
+
27
+ ### Step 1: Create Spike (Team Lead)
28
+
29
+ 1. Identify the blocking unknown (from Epic §8 Open Questions or 🔴 ambiguity signals)
30
+ 2. Create spike document from `templates/spike.md`
31
+ 3. Fill §1 Question, §2 Constraints, §3 Approach
32
+ 4. Set status → Open
33
+ 5. Link spike in parent Epic §9 Artifact Links
34
+ 6. Set time box (default: 4 hours for focused questions, 1 day for broader exploration)
35
+
36
+ ### Step 2: Investigate (Developer)
37
+
38
+ 1. Read the spike §1 Question, §2 Constraints, §3 Approach
39
+ 2. Read parent Epic §4 Technical Context for existing knowledge
40
+ 3. Investigate using the specified approach (code exploration, prototyping, benchmarks, doc research)
41
+ 4. Fill §4 Findings with evidence and data
42
+ 5. Fill §5 Decision with chosen approach, rationale, and rejected alternatives
43
+ 6. Mark §5 ADR Required if the decision is architectural
44
+ 7. Fill §6 Residual Risk if unknowns remain
45
+ 8. Set status → Findings Ready
46
+
47
+ ### Step 3: Validate (Architect)
48
+
49
+ 1. Read the spike §4 Findings and §5 Decision
50
+ 2. Validate against:
51
+ - Safe Zone compliance (no unauthorized patterns or libraries)
52
+ - Existing ADRs in Roadmap §3 (no contradictions)
53
+ - Risk profile (§6 Residual Risk is acceptable)
54
+ 3. If **PASS** → status → Validated
55
+ 4. If **FAIL** → provide specific feedback, status remains Findings Ready, Developer re-investigates
56
+
57
+ ### Step 4: Close & Propagate (Team Lead)
58
+
59
+ 1. Walk through §7 Affected Documents checklist:
60
+ - Update Epic §4 Technical Context with findings
61
+ - Mark Epic §8 Open Questions as resolved
62
+ - Add spike reference to Epic §9 Artifact Links
63
+ - If §5 ADR Required → create new ADR row in Roadmap §3
64
+ - If §6 Residual Risk has entries → add to Risk Registry §1
65
+ - If story-level spike → update Story §3 Implementation Guide
66
+ 2. Check off all items in §7
67
+ 3. Set status → Closed
68
+ 4. Parent story transitions: Probing/Spiking → Refinement
69
+
70
+ ## Timing Rules
71
+
72
+ - Spikes happen during **planning/refinement**, NOT during sprint execution
73
+ - No worktrees needed — spikes produce documents, not code
74
+ - Time box is enforced — if the time box expires without findings, the spike is escalated to the human with a status report
75
+ - Prototypes created during spikes are **throwaway** — they are NOT committed to any branch
76
+
77
+ ## What Spikes Are NOT
78
+
79
+ - **Not production code.** Spikes produce findings and decisions, not shippable code.
80
+ - **Not QA/DevOps passes.** No bounce sequence, no gate reports, no merge operations.
81
+ - **Not a worktree activity.** Spikes are document-level work, not branch-level work.
82
+ - **Not open-ended research.** Every spike has a time box and a specific question. If the question is too broad, split into multiple spikes.
83
+
84
+ ## Integration with Bounce Sequence
85
+
86
+ Spikes gate the transition from Probing/Spiking → Refinement → Ready to Bounce:
87
+
88
+ ```
89
+ Story (L4 / 🔴) → Probing/Spiking
90
+ └── Spike(s) created
91
+ └── Developer investigates → Architect validates → Team Lead propagates
92
+ └── All spikes Validated/Closed
93
+ └── Story ambiguity updated (should now be 🟡 or 🟢)
94
+ └── Story → Refinement → Ready to Bounce
95
+ ```
96
+
97
+ No story may enter Ready to Bounce while it has linked spikes in Open, Investigating, or Findings Ready status.