@sandrinio/vbounce 1.9.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.
- package/README.md +287 -15
- package/bin/vbounce.mjs +21 -0
- package/brains/AGENTS.md +59 -21
- package/brains/CHANGELOG.md +22 -0
- package/brains/CLAUDE.md +121 -27
- package/brains/GEMINI.md +60 -23
- package/brains/claude-agents/developer.md +6 -4
- package/brains/copilot/copilot-instructions.md +5 -0
- package/brains/cursor-rules/vbounce-process.mdc +3 -0
- package/brains/windsurf/.windsurfrules +5 -0
- package/package.json +1 -1
- package/scripts/close_sprint.mjs +32 -1
- package/scripts/post_sprint_improve.mjs +486 -0
- package/scripts/suggest_improvements.mjs +206 -43
- package/skills/agent-team/SKILL.md +48 -25
- package/skills/agent-team/references/discovery.md +97 -0
- package/skills/doc-manager/SKILL.md +142 -18
- package/skills/improve/SKILL.md +149 -58
- package/skills/lesson/SKILL.md +14 -0
- package/templates/epic.md +19 -16
- package/templates/spike.md +143 -0
- package/templates/sprint.md +32 -12
- package/templates/sprint_report.md +6 -4
- package/templates/story.md +23 -8
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* suggest_improvements.mjs
|
|
5
|
-
* Generates improvement suggestions from
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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: '
|
|
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: '
|
|
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
|
|
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: '
|
|
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
|
|
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
|
-
//
|
|
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: '
|
|
138
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
effort: 'Trivial',
|
|
256
|
+
target: 'LESSONS.md → brains/claude-agents/',
|
|
257
|
+
effort: 'Low',
|
|
143
258
|
});
|
|
144
259
|
}
|
|
145
260
|
}
|
|
146
261
|
|
|
147
|
-
//
|
|
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
268
|
detail: 'Verify the V-Bounce Engine installation is healthy after this sprint.',
|
|
163
|
-
recommendation: 'Run: `vbounce doctor` — checks brain files, templates, scripts, state.json validity.',
|
|
164
269
|
target: 'scripts/doctor.mjs',
|
|
165
270
|
effort: 'Trivial',
|
|
166
271
|
});
|
|
167
272
|
|
|
168
|
-
//
|
|
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
|
-
|
|
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);
|
|
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`);
|
|
@@ -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.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
3
|
|
188
|
-
If
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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. **
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
6. After
|
|
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.
|