@sandrinio/vbounce 1.5.0 → 1.7.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 (50) hide show
  1. package/README.md +108 -18
  2. package/bin/vbounce.mjs +291 -146
  3. package/brains/AGENTS.md +12 -10
  4. package/brains/CHANGELOG.md +99 -1
  5. package/brains/CLAUDE.md +29 -22
  6. package/brains/GEMINI.md +47 -9
  7. package/brains/SETUP.md +11 -5
  8. package/brains/claude-agents/architect.md +22 -6
  9. package/brains/claude-agents/developer.md +2 -2
  10. package/brains/claude-agents/devops.md +3 -0
  11. package/brains/claude-agents/qa.md +25 -9
  12. package/brains/copilot/copilot-instructions.md +49 -0
  13. package/brains/cursor-rules/vbounce-process.mdc +9 -7
  14. package/brains/windsurf/.windsurfrules +30 -0
  15. package/package.json +2 -4
  16. package/scripts/close_sprint.mjs +94 -0
  17. package/scripts/complete_story.mjs +113 -0
  18. package/scripts/doctor.mjs +144 -0
  19. package/scripts/init_gate_config.sh +151 -0
  20. package/scripts/init_sprint.mjs +121 -0
  21. package/scripts/pre_gate_common.sh +576 -0
  22. package/scripts/pre_gate_runner.sh +176 -0
  23. package/scripts/prep_arch_context.mjs +178 -0
  24. package/scripts/prep_qa_context.mjs +134 -0
  25. package/scripts/prep_sprint_context.mjs +118 -0
  26. package/scripts/prep_sprint_summary.mjs +154 -0
  27. package/scripts/sprint_trends.mjs +160 -0
  28. package/scripts/suggest_improvements.mjs +200 -0
  29. package/scripts/update_state.mjs +132 -0
  30. package/scripts/validate_bounce_readiness.mjs +125 -0
  31. package/scripts/validate_report.mjs +39 -2
  32. package/scripts/validate_sprint_plan.mjs +117 -0
  33. package/scripts/validate_state.mjs +99 -0
  34. package/skills/agent-team/SKILL.md +56 -21
  35. package/skills/agent-team/references/cleanup.md +42 -0
  36. package/skills/agent-team/references/delivery-sync.md +43 -0
  37. package/skills/agent-team/references/git-strategy.md +52 -0
  38. package/skills/agent-team/references/mid-sprint-triage.md +71 -0
  39. package/skills/agent-team/references/report-naming.md +34 -0
  40. package/skills/doc-manager/SKILL.md +5 -4
  41. package/skills/improve/SKILL.md +27 -1
  42. package/skills/lesson/SKILL.md +23 -0
  43. package/templates/delivery_plan.md +1 -1
  44. package/templates/hotfix.md +1 -1
  45. package/templates/sprint.md +65 -13
  46. package/templates/sprint_report.md +8 -1
  47. package/templates/story.md +1 -1
  48. package/scripts/pre_bounce_sync.sh +0 -37
  49. package/scripts/vbounce_ask.mjs +0 -98
  50. package/scripts/vbounce_index.mjs +0 -184
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * validate_bounce_readiness.mjs
5
+ * Pre-bounce gate check — verifies a story is ready to bounce.
6
+ *
7
+ * Usage:
8
+ * ./scripts/validate_bounce_readiness.mjs STORY-005-02
9
+ */
10
+
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import { fileURLToPath } from 'url';
14
+ import { execSync } from 'child_process';
15
+
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+ const ROOT = path.resolve(__dirname, '..');
18
+
19
+ const storyId = process.argv[2];
20
+ if (!storyId) {
21
+ console.error('Usage: validate_bounce_readiness.mjs STORY-ID');
22
+ process.exit(1);
23
+ }
24
+
25
+ const errors = [];
26
+ const warnings = [];
27
+
28
+ // 1. Check state.json
29
+ const stateFile = path.join(ROOT, '.bounce', 'state.json');
30
+ if (!fs.existsSync(stateFile)) {
31
+ errors.push('.bounce/state.json not found — run: vbounce sprint init S-XX D-XX');
32
+ } else {
33
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
34
+ if (!state.stories[storyId]) {
35
+ errors.push(`Story "${storyId}" not found in state.json`);
36
+ } else {
37
+ const story = state.stories[storyId];
38
+ if (story.state !== 'Ready to Bounce') {
39
+ errors.push(`Story state is "${story.state}" — must be "Ready to Bounce" before bouncing`);
40
+ }
41
+ }
42
+ }
43
+
44
+ // 2. Find sprint plan
45
+ const sprintsDir = path.join(ROOT, 'product_plans', 'sprints');
46
+ let sprintPlanFound = false;
47
+ if (fs.existsSync(sprintsDir)) {
48
+ const sprintDirs = fs.readdirSync(sprintsDir);
49
+ for (const dir of sprintDirs) {
50
+ const planFile = path.join(sprintsDir, dir, `${dir}.md`);
51
+ if (fs.existsSync(planFile)) {
52
+ sprintPlanFound = true;
53
+ break;
54
+ }
55
+ }
56
+ }
57
+ if (!sprintPlanFound) {
58
+ warnings.push('No active Sprint Plan found in product_plans/sprints/');
59
+ }
60
+
61
+ // 3. Find story spec
62
+ let storyFile = null;
63
+ function findFile(dir, id) {
64
+ if (!fs.existsSync(dir)) return null;
65
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
66
+ for (const e of entries) {
67
+ if (e.isDirectory()) {
68
+ const found = findFile(path.join(dir, e.name), id);
69
+ if (found) return found;
70
+ } else if (e.name.includes(id)) {
71
+ return path.join(dir, e.name);
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+
77
+ storyFile = findFile(path.join(ROOT, 'product_plans'), storyId);
78
+ if (!storyFile) {
79
+ errors.push(`Story spec not found for "${storyId}" in product_plans/`);
80
+ } else {
81
+ const storyContent = fs.readFileSync(storyFile, 'utf8');
82
+
83
+ // Check for §1, §2, §3
84
+ const hasSpec = /##\s*(1\.|§1|The Spec)/i.test(storyContent);
85
+ const hasCriteria = /##\s*(2\.|§2|The Truth|Acceptance)/i.test(storyContent);
86
+ const hasGuide = /##\s*(3\.|§3|Implementation)/i.test(storyContent);
87
+
88
+ if (!hasSpec) errors.push(`Story ${storyId}: §1 (spec) section not found`);
89
+ if (!hasCriteria) errors.push(`Story ${storyId}: §2 (acceptance criteria) section not found`);
90
+ if (!hasGuide) errors.push(`Story ${storyId}: §3 (implementation guide) section not found`);
91
+
92
+ // Check for minimum content in each section
93
+ const specMatch = storyContent.match(/##\s*(1\.|§1|The Spec)[^\n]*\n([\s\S]*?)(?=\n##|\n---|\Z)/i);
94
+ if (specMatch && specMatch[2].trim().length < 30) {
95
+ warnings.push(`Story ${storyId}: §1 spec section appears very short — verify it's complete`);
96
+ }
97
+ }
98
+
99
+ // 4. Check worktree
100
+ const worktreeDir = path.join(ROOT, '.worktrees', storyId);
101
+ if (!fs.existsSync(worktreeDir)) {
102
+ warnings.push(`.worktrees/${storyId}/ not found — create with: git worktree add .worktrees/${storyId} -b story/${storyId} sprint/S-XX`);
103
+ }
104
+
105
+ // Print results
106
+ console.log(`Bounce readiness check: ${storyId}`);
107
+ console.log('');
108
+
109
+ if (errors.length === 0 && warnings.length === 0) {
110
+ console.log(`✓ ${storyId} is READY TO BOUNCE`);
111
+ process.exit(0);
112
+ }
113
+
114
+ if (warnings.length > 0) {
115
+ warnings.forEach(w => console.warn(` ⚠ ${w}`));
116
+ }
117
+
118
+ if (errors.length > 0) {
119
+ errors.forEach(e => console.error(` ✗ ${e}`));
120
+ console.error(`\nNOT READY: Fix ${errors.length} error(s) before bouncing ${storyId}`);
121
+ process.exit(1);
122
+ } else {
123
+ console.log(` ✓ ${storyId} is ready (with warnings)`);
124
+ process.exit(0);
125
+ }
@@ -13,15 +13,21 @@ import path from 'path';
13
13
  import yaml from 'js-yaml';
14
14
 
15
15
  // Defined schemas for each report type
16
+ const ROOT_CAUSE_ENUM = [
17
+ 'missing_tests', 'missing_validation', 'spec_ambiguity', 'adr_violation',
18
+ 'gold_plating', 'logic_error', 'integration_gap', 'type_error',
19
+ 'state_management', 'error_handling', 'coupling', 'duplication'
20
+ ];
21
+
16
22
  const SCHEMAS = {
17
23
  dev: ['status', 'correction_tax', 'tokens_used', 'tests_written', 'files_modified', 'lessons_flagged'],
18
24
  qa: {
19
25
  base: ['status', 'bounce_count', 'tokens_used', 'bugs_found', 'gold_plating_detected'],
20
- conditional: { 'FAIL': ['failed_scenarios'] }
26
+ conditional: { 'FAIL': ['failed_scenarios', 'root_cause'] }
21
27
  },
22
28
  arch: {
23
29
  base: ['status', 'tokens_used'],
24
- conditional: { 'PASS': ['safe_zone_score', 'ai_isms_detected', 'regression_risk'], 'FAIL': ['bounce_count', 'critical_failures'] }
30
+ conditional: { 'PASS': ['safe_zone_score', 'ai_isms_detected', 'regression_risk'], 'FAIL': ['bounce_count', 'critical_failures', 'root_cause'] }
25
31
  },
26
32
  devops: {
27
33
  base: ['type', 'status', 'tokens_used'],
@@ -45,6 +51,29 @@ function validateDev(data) {
45
51
  if (!Array.isArray(data.files_modified)) throw new Error(`DEV_SCHEMA_ERROR: 'files_modified' must be an array.`);
46
52
  }
47
53
 
54
+ function validateBugsArray(bugs, prefix) {
55
+ if (!Array.isArray(bugs)) throw new Error(`${prefix}: 'bugs' must be an array.`);
56
+ bugs.forEach((bug, i) => {
57
+ const bugRequired = ['scenario', 'expected', 'actual', 'files', 'severity'];
58
+ const bugMissing = bugRequired.filter(k => !(k in bug));
59
+ if (bugMissing.length > 0) throw new Error(`${prefix}: bugs[${i}] missing keys: ${bugMissing.join(', ')}`);
60
+ if (!Array.isArray(bug.files)) throw new Error(`${prefix}: bugs[${i}].files must be an array.`);
61
+ const validSeverities = ['Critical', 'High', 'Medium', 'Low'];
62
+ if (!validSeverities.includes(bug.severity)) throw new Error(`${prefix}: bugs[${i}].severity must be one of: ${validSeverities.join(', ')}`);
63
+ });
64
+ }
65
+
66
+ function validateFailuresArray(failures, prefix) {
67
+ if (!Array.isArray(failures)) throw new Error(`${prefix}: 'failures' must be an array.`);
68
+ const validDimensions = ['Architectural Consistency', 'Error Handling', 'Data Flow', 'Duplication', 'Test Quality', 'Coupling'];
69
+ failures.forEach((f, i) => {
70
+ const fRequired = ['dimension', 'severity', 'what_wrong', 'fix_required'];
71
+ const fMissing = fRequired.filter(k => !(k in f));
72
+ if (fMissing.length > 0) throw new Error(`${prefix}: failures[${i}] missing keys: ${fMissing.join(', ')}`);
73
+ if (!validDimensions.includes(f.dimension)) throw new Error(`${prefix}: failures[${i}].dimension must be one of: ${validDimensions.join(', ')}`);
74
+ });
75
+ }
76
+
48
77
  function validateQA(data) {
49
78
  const missing = SCHEMAS.qa.base.filter(k => !(k in data));
50
79
  if (missing.length > 0) throw new Error(`QA_SCHEMA_ERROR: Missing required keys: ${missing.join(', ')}`);
@@ -52,6 +81,10 @@ function validateQA(data) {
52
81
  if (data.status === 'FAIL') {
53
82
  const conditionalMissing = SCHEMAS.qa.conditional.FAIL.filter(k => !(k in data));
54
83
  if (conditionalMissing.length > 0) throw new Error(`QA_SCHEMA_ERROR: 'FAIL' status requires keys: ${conditionalMissing.join(', ')}`);
84
+ if (data.root_cause && !ROOT_CAUSE_ENUM.includes(data.root_cause)) {
85
+ throw new Error(`QA_SCHEMA_ERROR: Invalid root_cause '${data.root_cause}'. Must be one of: ${ROOT_CAUSE_ENUM.join(', ')}`);
86
+ }
87
+ if ('bugs' in data) validateBugsArray(data.bugs, 'QA_SCHEMA_ERROR');
55
88
  }
56
89
  }
57
90
 
@@ -62,6 +95,10 @@ function validateArch(data) {
62
95
  const s = data.status === 'PASS' ? 'PASS' : 'FAIL';
63
96
  const conditionalMissing = SCHEMAS.arch.conditional[s].filter(k => !(k in data));
64
97
  if (conditionalMissing.length > 0) throw new Error(`ARCH_SCHEMA_ERROR: '${s}' status requires keys: ${conditionalMissing.join(', ')}`);
98
+ if (s === 'FAIL' && data.root_cause && !ROOT_CAUSE_ENUM.includes(data.root_cause)) {
99
+ throw new Error(`ARCH_SCHEMA_ERROR: Invalid root_cause '${data.root_cause}'. Must be one of: ${ROOT_CAUSE_ENUM.join(', ')}`);
100
+ }
101
+ if (s === 'FAIL' && 'failures' in data) validateFailuresArray(data.failures, 'ARCH_SCHEMA_ERROR');
65
102
  }
66
103
 
67
104
  function validateDevops(data) {
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * validate_sprint_plan.mjs
5
+ * Validates a Sprint Plan markdown file structure and cross-checks with state.json.
6
+ *
7
+ * Usage:
8
+ * ./scripts/validate_sprint_plan.mjs product_plans/sprints/sprint-05/sprint-05.md
9
+ */
10
+
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import { fileURLToPath } from 'url';
14
+ import yaml from 'js-yaml';
15
+
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+ const ROOT = path.resolve(__dirname, '..');
18
+
19
+ const filePath = process.argv[2];
20
+ if (!filePath) {
21
+ console.error('Usage: validate_sprint_plan.mjs <path-to-sprint-plan.md>');
22
+ process.exit(1);
23
+ }
24
+
25
+ const absPath = path.resolve(filePath);
26
+ if (!fs.existsSync(absPath)) {
27
+ console.error(`ERROR: File not found: ${absPath}`);
28
+ process.exit(1);
29
+ }
30
+
31
+ const content = fs.readFileSync(absPath, 'utf8');
32
+ const errors = [];
33
+ const warnings = [];
34
+
35
+ // 1. Extract YAML frontmatter
36
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
37
+ if (!fmMatch) {
38
+ errors.push('Missing YAML frontmatter (--- delimiters)');
39
+ } else {
40
+ let fm;
41
+ try {
42
+ fm = yaml.load(fmMatch[1]);
43
+ } catch (e) {
44
+ errors.push(`Invalid YAML frontmatter: ${e.message}`);
45
+ fm = {};
46
+ }
47
+
48
+ const required = ['sprint_id', 'sprint_goal', 'dates', 'status', 'delivery'];
49
+ for (const f of required) {
50
+ if (!fm[f]) errors.push(`Frontmatter missing required field: "${f}"`);
51
+ }
52
+
53
+ // 2. Cross-check with state.json
54
+ const stateFile = path.join(ROOT, '.bounce', 'state.json');
55
+ if (fs.existsSync(stateFile)) {
56
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
57
+
58
+ // Find story IDs in §1 table
59
+ const tableRowRegex = /\|\s*\d+\s*\|\s*\[?(STORY-[\w-]+)/g;
60
+ const planStoryIds = new Set();
61
+ let m;
62
+ while ((m = tableRowRegex.exec(content)) !== null) {
63
+ planStoryIds.add(m[1]);
64
+ }
65
+
66
+ const stateStoryIds = new Set(Object.keys(state.stories || {}));
67
+
68
+ // Check for stories in plan but not in state
69
+ for (const id of planStoryIds) {
70
+ if (!stateStoryIds.has(id)) {
71
+ warnings.push(`Story ${id} is in Sprint Plan §1 but NOT in state.json`);
72
+ }
73
+ }
74
+
75
+ // Check for stories in state but not in plan
76
+ for (const id of stateStoryIds) {
77
+ if (!planStoryIds.has(id)) {
78
+ warnings.push(`Story ${id} is in state.json but NOT in Sprint Plan §1`);
79
+ }
80
+ }
81
+ }
82
+
83
+ // 3. Check §4 Execution Log if sprint is Completed
84
+ if (fm.status === 'Completed') {
85
+ if (!content.includes('<!-- EXECUTION_LOG_START -->') && !content.includes('## 4.') && !content.includes('## §4')) {
86
+ errors.push('Sprint is Completed but §4 Execution Log section is missing');
87
+ }
88
+ }
89
+ }
90
+
91
+ // 4. Check §1 table columns
92
+ if (!content.includes('| Priority |') && !content.includes('|Priority|')) {
93
+ errors.push('§1 Active Scope table missing or malformed (expected "Priority" column header)');
94
+ }
95
+ if (!content.includes('V-Bounce State')) {
96
+ errors.push('§1 Active Scope table missing "V-Bounce State" column');
97
+ }
98
+
99
+ // Print results
100
+ console.log(`Validating: ${filePath}`);
101
+ if (errors.length === 0 && warnings.length === 0) {
102
+ console.log('✓ Sprint Plan is valid');
103
+ process.exit(0);
104
+ }
105
+
106
+ if (warnings.length > 0) {
107
+ console.warn('Warnings:');
108
+ warnings.forEach(w => console.warn(` ⚠ ${w}`));
109
+ }
110
+
111
+ if (errors.length > 0) {
112
+ console.error('Errors:');
113
+ errors.forEach(e => console.error(` ✗ ${e}`));
114
+ process.exit(1);
115
+ } else {
116
+ process.exit(0);
117
+ }
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * validate_state.mjs
5
+ * Validates .bounce/state.json schema.
6
+ * Usage: ./scripts/validate_state.mjs
7
+ * Also exportable: import { validateState } from './validate_state.mjs'
8
+ */
9
+
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import { fileURLToPath } from 'url';
13
+
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ const ROOT = path.resolve(__dirname, '..');
16
+ const STATE_FILE = path.join(ROOT, '.bounce', 'state.json');
17
+
18
+ const VALID_STATES = [
19
+ 'Draft', 'Refinement', 'Ready to Bounce', 'Bouncing',
20
+ 'QA Passed', 'Architect Passed', 'Done', 'Escalated', 'Parking Lot'
21
+ ];
22
+
23
+ /**
24
+ * Validates a state object. Returns { valid, errors }.
25
+ * @param {object} state
26
+ * @returns {{ valid: boolean, errors: string[] }}
27
+ */
28
+ export function validateState(state) {
29
+ const errors = [];
30
+
31
+ if (!state || typeof state !== 'object') {
32
+ return { valid: false, errors: ['state.json must be a JSON object'] };
33
+ }
34
+
35
+ if (!state.sprint_id || !/^S-\d{2}$/.test(state.sprint_id)) {
36
+ errors.push(`sprint_id "${state.sprint_id}" must match S-XX format (e.g. S-05)`);
37
+ }
38
+
39
+ if (!state.delivery_id || !/^D-\d{2}$/.test(state.delivery_id)) {
40
+ errors.push(`delivery_id "${state.delivery_id}" must match D-NN format (e.g. D-02)`);
41
+ }
42
+
43
+ if (!state.stories || typeof state.stories !== 'object') {
44
+ errors.push('stories field must be an object');
45
+ } else {
46
+ for (const [id, story] of Object.entries(state.stories)) {
47
+ if (!VALID_STATES.includes(story.state)) {
48
+ errors.push(`Story ${id}: invalid state "${story.state}". Must be one of: ${VALID_STATES.join(', ')}`);
49
+ }
50
+ if (typeof story.qa_bounces !== 'number' || !Number.isInteger(story.qa_bounces) || story.qa_bounces < 0) {
51
+ errors.push(`Story ${id}: qa_bounces must be a non-negative integer, got "${story.qa_bounces}"`);
52
+ }
53
+ if (typeof story.arch_bounces !== 'number' || !Number.isInteger(story.arch_bounces) || story.arch_bounces < 0) {
54
+ errors.push(`Story ${id}: arch_bounces must be a non-negative integer, got "${story.arch_bounces}"`);
55
+ }
56
+ if (story.state === 'Done' && story.worktree) {
57
+ errors.push(`Story ${id}: state is "Done" but worktree "${story.worktree}" is still set (should be null)`);
58
+ }
59
+ }
60
+ }
61
+
62
+ if (state.updated_at) {
63
+ const d = new Date(state.updated_at);
64
+ if (isNaN(d.getTime())) {
65
+ errors.push(`updated_at "${state.updated_at}" is not a valid ISO 8601 timestamp`);
66
+ }
67
+ } else {
68
+ errors.push('updated_at field is required');
69
+ }
70
+
71
+ return { valid: errors.length === 0, errors };
72
+ }
73
+
74
+ // CLI entry point
75
+ if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
76
+ if (!fs.existsSync(STATE_FILE)) {
77
+ console.error(`ERROR: ${STATE_FILE} not found. Run: vbounce sprint init S-XX D-XX`);
78
+ process.exit(1);
79
+ }
80
+
81
+ let state;
82
+ try {
83
+ state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
84
+ } catch (e) {
85
+ console.error(`ERROR: state.json is not valid JSON — ${e.message}`);
86
+ process.exit(1);
87
+ }
88
+
89
+ const { valid, errors } = validateState(state);
90
+
91
+ if (valid) {
92
+ console.log(`VALID: state.json — sprint ${state.sprint_id}, ${Object.keys(state.stories || {}).length} stories`);
93
+ process.exit(0);
94
+ } else {
95
+ console.error('INVALID: state.json has errors:');
96
+ errors.forEach(e => console.error(` - ${e}`));
97
+ process.exit(1);
98
+ }
99
+ }
@@ -61,12 +61,12 @@ repo/ ← main working directory
61
61
 
62
62
  └── .bounce/
63
63
  ├── reports/ ← active working reports (GITIGNORED)
64
- ├── sprint-report.md ← current sprint report (GITIGNORED)
64
+ ├── sprint-report-S-{XX}.md ← current sprint report (GITIGNORED)
65
65
  └── archive/ ← completed sprint history (COMMITTED TO GIT)
66
66
  └── S-01/
67
67
  ├── STORY-001-01/ ← all agent reports for this story
68
- ├── sprint-report.md ← final sprint report
69
- └── sprint-devops.md ← release report
68
+ ├── sprint-report-S-{XX}.md ← final sprint report
69
+ └── sprint-S-{XX}-devops.md ← release report
70
70
  ```
71
71
 
72
72
  ### V-Bounce State → Git Operations
@@ -154,7 +154,7 @@ For tools without native subagent support (Cursor, Codex, Gemini, Antigravity):
154
154
 
155
155
  **After story completes:** Reports are archived to the shared `.bounce/archive/S-{XX}/STORY-{ID}-{StoryName}/` in the main repo before the worktree is removed.
156
156
 
157
- **Sprint Report:** Always written to `.bounce/sprint-report.md` in the main repo (not in any worktree).
157
+ **Sprint Report:** Always written to `.bounce/sprint-report-S-{XX}.md` in the main repo (not in any worktree).
158
158
 
159
159
  ### Report File Naming
160
160
 
@@ -201,20 +201,24 @@ Examples:
201
201
  e. DevOps runs `hotfix_manager.sh sync` to update any active story worktrees.
202
202
  f. Update Delivery Plan Status to "Done".
203
203
 
204
- 6. **Parallel Readiness Check** (before bouncing multiple stories simultaneously):
204
+ 6. **Gate Config Check**:
205
+ - 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
+ - If it exists, verify it's current (stack detection may have changed).
207
+
208
+ 7. **Parallel Readiness Check** (before bouncing multiple stories simultaneously):
205
209
  - Verify test runner config excludes `.worktrees/` (vitest, jest, pytest, etc.)
206
210
  - Verify no shared mutable state between worktrees (e.g., shared temp files, singletons writing to same path)
207
211
  - Verify `.gitignore` includes `.worktrees/`
208
212
  If any check fails, fix before spawning parallel stories. Intermittent test failures from worktree cross-contamination erode trust in the test suite fast.
209
213
 
210
- 7. **Dependency Check & Execution Mode**:
214
+ 8. **Dependency Check & Execution Mode**:
211
215
  - For each story, check the `Depends On:` field in its template.
212
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.
213
217
  - Determine Execution Mode:
214
218
  - **Full Bounce (Default)**: Normal L2-L4 stories go through full Dev → QA → Architect → DevOps flow.
215
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.
216
220
 
217
- 8. Update sprint-{XX}.md: Status → "Active"
221
+ 9. Update sprint-{XX}.md: Status → "Active"
218
222
  ```
219
223
 
220
224
  ### Step 1: Story Initialization
@@ -245,11 +249,18 @@ mkdir -p .worktrees/STORY-{ID}-{StoryName}/.bounce/{tasks,reports}
245
249
 
246
250
  ### Step 3: QA Pass
247
251
  ```
252
+ 0. Run pre-QA gate scan:
253
+ ./scripts/pre_gate_runner.sh qa .worktrees/STORY-{ID}-{StoryName}/ sprint/S-{XX}
254
+ - If scan FAILS on trivial issues (debug statements, missing JSDoc, TODOs):
255
+ Return to Developer for quick fix. Do NOT spawn QA for mechanical failures.
256
+ - If scan PASSES: Include scan output path in the QA task file.
248
257
  1. Spawn qa subagent in .worktrees/STORY-{ID}-{StoryName}/ with:
249
258
  - Developer Implementation Report
259
+ - Pre-QA scan results (.bounce/reports/pre-qa-scan.txt)
250
260
  - Story §2 The Truth (acceptance criteria)
251
261
  - LESSONS.md
252
262
  2. QA validates against Gherkin scenarios, runs vibe-code-review
263
+ (skipping checks already covered by pre-qa-scan.txt)
253
264
  3. If FAIL:
254
265
  - QA writes Bug Report (STORY-{ID}-{StoryName}-qa-bounce{N}.md)
255
266
  - Increment bounce counter
@@ -262,8 +273,14 @@ mkdir -p .worktrees/STORY-{ID}-{StoryName}/.bounce/{tasks,reports}
262
273
 
263
274
  ### Step 4: Architect Pass
264
275
  ```
276
+ 0. Run pre-Architect gate scan:
277
+ ./scripts/pre_gate_runner.sh arch .worktrees/STORY-{ID}-{StoryName}/ sprint/S-{XX}
278
+ - If scan reveals new dependencies or structural violations:
279
+ Return to Developer for resolution. Do NOT spawn Architect for mechanical failures.
280
+ - If scan PASSES: Include scan output path in the Architect task file.
265
281
  1. Spawn architect subagent in .worktrees/STORY-{ID}-{StoryName}/ with:
266
282
  - All reports for this story
283
+ - Pre-Architect scan results (.bounce/reports/pre-arch-scan.txt)
267
284
  - Full Story spec + Roadmap §3 ADRs
268
285
  - LESSONS.md
269
286
  2. If FAIL:
@@ -286,7 +303,7 @@ mkdir -p .worktrees/STORY-{ID}-{StoryName}/.bounce/{tasks,reports}
286
303
  - Pre-merge checks (worktree clean, gate reports verified)
287
304
  - Archive reports to .bounce/archive/S-{XX}/STORY-{ID}-{StoryName}/
288
305
  - Merge story branch into sprint branch (--no-ff)
289
- - Post-merge validation (tests + build on sprint branch)
306
+ - Post-merge validation (tests + lint + build on sprint branch)
290
307
  - Worktree removal and story branch cleanup
291
308
  3. DevOps writes Merge Report to .bounce/archive/S-{XX}/STORY-{ID}-{StoryName}/STORY-{ID}-{StoryName}-devops.md
292
309
  4. If merge conflicts:
@@ -310,7 +327,7 @@ After ALL stories are merged into `sprint/S-01`:
310
327
  ```
311
328
  1. Read all archived reports in .bounce/archive/S-{XX}/
312
329
  2. **Sum the `tokens_used` field** from every agent report to calculate the sprint's total resource cost.
313
- 3. Generate Sprint Report to .bounce/sprint-report.md:
330
+ 3. Generate Sprint Report to .bounce/sprint-report-S-{XX}.md:
314
331
  - Ensure the Sprint Report starts with a YAML frontmatter block containing:
315
332
  ```yaml
316
333
  ---
@@ -330,9 +347,9 @@ After ALL stories are merged into `sprint/S-01`:
330
347
  - Run full test suite + build + lint on main
331
348
  - Sprint branch cleanup
332
349
  - Environment verification (if applicable)
333
- - DevOps writes Sprint Release Report to .bounce/archive/S-{XX}/sprint-devops.md
350
+ - DevOps writes Sprint Release Report to .bounce/archive/S-{XX}/sprint-S-{XX}-devops.md
334
351
  6. Lead finalizes:
335
- - Move sprint-report.md to .bounce/archive/S-{XX}/
352
+ - Move sprint-report-S-{XX}.md to .bounce/archive/S-{XX}/
336
353
  - Record lessons (with user approval)
337
354
  - Update delivery_plan.md to reflect the completed sprint.
338
355
  7. **Framework Self-Assessment** (aggregated from agent reports):
@@ -403,15 +420,18 @@ When ALL sprints in a delivery (release) are done:
403
420
 
404
421
  The Team Lead MUST update the active `sprint-{XX}.md` at every state transition. This is the source of truth for execution.
405
422
 
406
- | Action | Sprint Plan Update |
407
- |--------|---------------------|
408
- | Worktree created | §1 Active Scope: V-Bounce State → "Bouncing" |
409
- | Dev report written | No update (still "Bouncing") |
410
- | QA passes | §1 Active Scope: V-Bounce State → "QA Passed" |
411
- | Architect passes | §1 Active Scope: V-Bounce State → "Architect Passed" |
412
- | DevOps merges story | §1 Active Scope: V-Bounce State → "Done" |
413
- | Escalated | §1 Escalated table |
414
- | DevOps merges sprint | Update `delivery_plan.md` to flag sprint delivered |
423
+ | Action | Sprint Plan Update | Delivery Plan Update |
424
+ |--------|-------------------|--------------------|
425
+ | Worktree created | §1: V-Bounce State → "Bouncing" | **Nothing** — Sprint Plan is source of truth |
426
+ | Dev report written | No update (still "Bouncing") | **Nothing** |
427
+ | QA passes | §1: V-Bounce State → "QA Passed" | **Nothing** |
428
+ | Architect passes | §1: V-Bounce State → "Architect Passed" | **Nothing** |
429
+ | DevOps merges story | §1: V-Bounce State → "Done". §4: Add Execution Log row (via `vbounce story complete`) | **Nothing** |
430
+ | Escalated | §1: Move story to Escalated section | **Nothing** |
431
+ | Sprint CLOSES | Status → "Completed" in frontmatter | §2: sprint → Completed. §4: add summary. §3: remove delivered stories |
432
+
433
+ > **Key rule**: The Delivery Plan is updated ONLY at sprint close, never during active bouncing.
434
+ > See `skills/agent-team/references/delivery-sync.md` for full sync rules.
415
435
 
416
436
  ---
417
437
 
@@ -431,6 +451,21 @@ When QA bounce count >= 3 OR Architect bounce count >= 3:
431
451
  - **If returned to Refinement:** The spec has been rewritten. You MUST reset the QA and Architect bounce counters to 0 for this story.
432
452
  - If killed: `git worktree remove`, branch preserved unmerged
433
453
 
454
+ ### Mid-Sprint Change Requests
455
+ When the user provides input mid-bounce that isn't a direct answer to an agent question (e.g., "this is broken", "change the approach", "I meant X not Y"), the Team Lead MUST triage it before acting.
456
+
457
+ > See `skills/agent-team/references/mid-sprint-triage.md` for the full triage flow, routing rules, and logging requirements.
458
+
459
+ **Quick reference — categories:**
460
+ | Category | Route | Bounce Impact |
461
+ |----------|-------|---------------|
462
+ | **Bug** | Hotfix or bug-fix task in current story | No bounce increment |
463
+ | **Spec Clarification** | Update Story spec, continue bounce | No impact |
464
+ | **Scope Change** | Pause, update spec, confirm with user | Resets Dev pass |
465
+ | **Approach Change** | Update §3 Implementation Guide, re-delegate | Resets Dev pass |
466
+
467
+ Every change request is logged in `sprint-{XX}.md` §4 Execution Log with event type `CR` and reported in Sprint Report §2.1.
468
+
434
469
  ### Mid-Sprint Strategic Changes
435
470
  Charter and Roadmap are typically **frozen** during active sprints. However, if an emergency requires modifying them:
436
471
  1. You MUST pause active bouncing across all stories.
@@ -453,7 +488,7 @@ If merging story branch into sprint branch creates conflicts:
453
488
  - **Reports are the only handoff.** No agent communicates with another directly.
454
489
  - **One bounce = one report.** Every agent pass produces exactly one report file.
455
490
  - **Archive before remove.** Always copy reports to shared archive before removing a worktree.
456
- - **Sync the Delivery Plan.** Update V-Bounce state at EVERY transition. The Delivery Plan is the source of truth.
491
+ - **Sync the Sprint Plan.** Update V-Bounce State in sprint-{XX}.md §1 at EVERY transition. The Sprint Plan is the source of truth DURING the sprint. The Delivery Plan is updated at sprint boundaries only — see `skills/agent-team/references/delivery-sync.md`.
457
492
  - **Track bounce counts.** QA and Architect bounces are tracked separately per story.
458
493
  - **Git tracking rules.** `.worktrees/`, `.bounce/reports/`, and `.bounce/sprint-report.md` are gitignored (ephemeral). `.bounce/archive/` is **committed to git** (permanent audit trail).
459
494
  - **Check risks before bouncing.** Read RISK_REGISTRY.md at sprint start. Flag high-severity risks that affect planned stories.
@@ -0,0 +1,42 @@
1
+ # Cleanup & Archive Rules
2
+
3
+ > On-demand reference from agent-team/SKILL.md. Read during sprint close or when archiving.
4
+
5
+ ## After Each Story Completes (DevOps Step 5)
6
+
7
+ 1. Archive all reports to `.bounce/archive/S-{XX}/STORY-{ID}-{StoryName}/`
8
+ 2. Merge story branch into sprint branch (--no-ff)
9
+ 3. Validate tests/build on sprint branch
10
+ 4. Remove worktree: `git worktree remove .worktrees/STORY-{ID}-{StoryName}`
11
+ 5. Delete story branch: `git branch -d story/STORY-{ID}-{StoryName}`
12
+ 6. Write DevOps Merge Report to `.bounce/archive/S-{XX}/STORY-{ID}-{StoryName}/`
13
+
14
+ ## After Sprint Completes (DevOps Step 7)
15
+
16
+ 1. Merge sprint branch into main (--no-ff)
17
+ 2. Tag release: `git tag -a v{VERSION}`
18
+ 3. Run full validation (tests + build + lint)
19
+ 4. Delete sprint branch: `git branch -d sprint/S-{XX}`
20
+ 5. Verify `.worktrees/` is empty
21
+ 6. Write Sprint Release Report to `.bounce/archive/S-{XX}/sprint-S-{XX}-devops.md`
22
+ 7. Lead archives Sprint Plan: `mv product_plans/sprints/sprint-{XX}/ product_plans/archive/sprints/sprint-{XX}/`
23
+ 8. Lead archives sprint report: `mv .bounce/sprint-report-S-{XX}.md .bounce/archive/S-{XX}/`
24
+ 9. Run: `vbounce trends && vbounce suggest S-{XX}` — generates improvement recommendations
25
+
26
+ ## Retention Policy
27
+
28
+ | Location | Status | Rule |
29
+ |----------|--------|------|
30
+ | `.bounce/archive/` | **Committed to git** | Permanent audit trail — never delete |
31
+ | `.bounce/reports/` | **Gitignored** | Active working files only — ephemeral |
32
+ | `.bounce/sprint-report-S-{XX}.md` | **Gitignored** | Active sprint report — archived on close |
33
+ | `.bounce/sprint-context-*.md` | **Gitignored** | Regenerated each session |
34
+ | `.worktrees/` | **Gitignored** | Ephemeral — exists only during active bouncing |
35
+ | `product_plans/archive/` | **Committed** | Completed deliveries with all docs |
36
+
37
+ ## After Delivery Completes (Team Lead)
38
+
39
+ 1. Verify all stories in delivery are "Done" in Delivery Plan §4
40
+ 2. Move delivery folder: `mv product_plans/D-{NN}_{name}/ product_plans/archive/D-{NN}_{name}/`
41
+ 3. Add Delivery Log entry to Roadmap §7
42
+ 4. Update Roadmap §2 Release Plan: status → "Delivered"