@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,30 @@
1
+ # V-Bounce OS — Windsurf Rules
2
+
3
+ This project uses V-Bounce OS. You are operating in Tier 4 (Awareness) mode.
4
+
5
+ ## Before Writing Code
6
+
7
+ 1. Read `LESSONS.md` at the project root — treat as hard constraints
8
+ 2. Read the Story spec you're implementing (§1 + §3)
9
+ 3. Check `brains/CLAUDE.md` for full process context
10
+
11
+ ## Document Rules
12
+
13
+ - Planning documents in `product_plans/` follow strict templates in `templates/`
14
+ - Agent reports in `.bounce/reports/` MUST have YAML frontmatter
15
+ - Never modify `brains/`, `skills/`, or `templates/` without recording in `brains/CHANGELOG.md`
16
+
17
+ ## State Management
18
+
19
+ ```bash
20
+ vbounce state show # where is the sprint right now?
21
+ vbounce validate report <f> # is this report valid?
22
+ vbounce doctor # is the framework healthy?
23
+ ```
24
+
25
+ ## Critical Rules
26
+
27
+ - Read LESSONS.md before coding. No exceptions.
28
+ - No gold-plating. Implement exactly what's specified.
29
+ - Reports are the only agent handoff. No direct agent communication.
30
+ - YAML frontmatter is mandatory on all agent reports.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sandrinio/vbounce",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "V-Bounce OS: Turn your AI coding assistant into a full engineering team through structured SDLC skills.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,10 +40,8 @@
40
40
  "docs"
41
41
  ],
42
42
  "dependencies": {
43
- "@lancedb/lancedb": "^0.26.2",
44
- "@xenova/transformers": "^2.17.2",
45
43
  "commander": "^14.0.3",
46
44
  "js-yaml": "^4.1.1",
47
45
  "marked": "^17.0.3"
48
46
  }
49
- }
47
+ }
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * close_sprint.mjs
5
+ * Sprint close automation — validates, archives, updates state.json.
6
+ *
7
+ * Usage:
8
+ * ./scripts/close_sprint.mjs S-05
9
+ */
10
+
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import { fileURLToPath } from 'url';
14
+
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+ const ROOT = path.resolve(__dirname, '..');
17
+
18
+ const args = process.argv.slice(2);
19
+ if (args.length < 1) {
20
+ console.error('Usage: close_sprint.mjs S-XX');
21
+ process.exit(1);
22
+ }
23
+
24
+ const sprintId = args[0];
25
+ if (!/^S-\d{2}$/.test(sprintId)) {
26
+ console.error(`ERROR: sprint_id "${sprintId}" must match S-XX format`);
27
+ process.exit(1);
28
+ }
29
+
30
+ const sprintNum = sprintId.replace('S-', '');
31
+ const stateFile = path.join(ROOT, '.bounce', 'state.json');
32
+
33
+ // 1. Read state.json
34
+ if (!fs.existsSync(stateFile)) {
35
+ console.error(`ERROR: .bounce/state.json not found`);
36
+ process.exit(1);
37
+ }
38
+
39
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
40
+
41
+ if (state.sprint_id !== sprintId) {
42
+ console.error(`ERROR: state.json is for sprint ${state.sprint_id}, not ${sprintId}`);
43
+ process.exit(1);
44
+ }
45
+
46
+ // 2. Check all stories are terminal
47
+ const activeStories = Object.entries(state.stories || {}).filter(
48
+ ([, s]) => !['Done', 'Escalated', 'Parking Lot'].includes(s.state)
49
+ );
50
+
51
+ if (activeStories.length > 0) {
52
+ console.warn(`⚠ ${activeStories.length} stories are not in a terminal state:`);
53
+ activeStories.forEach(([id, s]) => console.warn(` - ${id}: ${s.state}`));
54
+ console.warn(' Proceed? These stories will be left incomplete.');
55
+ }
56
+
57
+ // 3. Create archive directory
58
+ const archiveDir = path.join(ROOT, '.bounce', 'archive', sprintId);
59
+ fs.mkdirSync(archiveDir, { recursive: true });
60
+
61
+ // 4. Move sprint report if it exists
62
+ const reportSrc = path.join(ROOT, '.bounce', `sprint-report-${sprintId}.md`);
63
+ const reportLegacy = path.join(ROOT, '.bounce', 'sprint-report.md');
64
+ const reportDst = path.join(archiveDir, `sprint-report-${sprintId}.md`);
65
+
66
+ if (fs.existsSync(reportSrc)) {
67
+ fs.copyFileSync(reportSrc, reportDst);
68
+ console.log(`✓ Archived sprint report → .bounce/archive/${sprintId}/sprint-report-${sprintId}.md`);
69
+ } else if (fs.existsSync(reportLegacy)) {
70
+ fs.copyFileSync(reportLegacy, reportDst);
71
+ console.log(`✓ Archived sprint report → .bounce/archive/${sprintId}/sprint-report-${sprintId}.md`);
72
+ }
73
+
74
+ // 5. Update state.json
75
+ state.last_action = `Sprint ${sprintId} closed`;
76
+ state.updated_at = new Date().toISOString();
77
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
78
+ console.log(`✓ Updated state.json`);
79
+
80
+ // 6. Print manual steps
81
+ const sprintPlanPath = `product_plans/sprints/sprint-${sprintNum}`;
82
+ const archivePath = `product_plans/archive/sprints/sprint-${sprintNum}`;
83
+
84
+ console.log('');
85
+ console.log('Manual steps remaining:');
86
+ console.log(` 1. Archive sprint plan folder:`);
87
+ console.log(` mv ${sprintPlanPath}/ ${archivePath}/`);
88
+ console.log(` 2. Update Delivery Plan §4 Completed Sprints with a summary row`);
89
+ console.log(` 3. Remove delivered stories from Delivery Plan §3 Backlog`);
90
+ console.log(` 4. Delete sprint branch (after merge to main):`);
91
+ console.log(` git branch -d sprint/${sprintId}`);
92
+ console.log(` 5. Run: vbounce trends && vbounce suggest ${sprintId}`);
93
+ console.log('');
94
+ console.log(`✓ Sprint ${sprintId} closed.`);
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * complete_story.mjs
5
+ * Mark a story as Done — updates Sprint Plan §1 + §4, and state.json atomically.
6
+ *
7
+ * Usage:
8
+ * ./scripts/complete_story.mjs STORY-005-02 --qa-bounces 1 --arch-bounces 0 --correction-tax 5 --notes "Missing validation fixed"
9
+ */
10
+
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import { fileURLToPath } from 'url';
14
+
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+ const ROOT = path.resolve(__dirname, '..');
17
+
18
+ function parseArgs(argv) {
19
+ const result = { storyId: null, qaBounces: 0, archBounces: 0, correctionTax: '0%', notes: '' };
20
+ const args = argv.slice(2);
21
+ result.storyId = args[0];
22
+ for (let i = 1; i < args.length; i++) {
23
+ if (args[i] === '--qa-bounces') result.qaBounces = parseInt(args[++i], 10) || 0;
24
+ else if (args[i] === '--arch-bounces') result.archBounces = parseInt(args[++i], 10) || 0;
25
+ else if (args[i] === '--correction-tax') result.correctionTax = args[++i] + (args[i].includes('%') ? '' : '%');
26
+ else if (args[i] === '--notes') result.notes = args[++i];
27
+ }
28
+ return result;
29
+ }
30
+
31
+ const { storyId, qaBounces, archBounces, correctionTax, notes } = parseArgs(process.argv);
32
+
33
+ if (!storyId) {
34
+ console.error('Usage: complete_story.mjs STORY-ID [--qa-bounces N] [--arch-bounces N] [--correction-tax N] [--notes "text"]');
35
+ process.exit(1);
36
+ }
37
+
38
+ // 1. Update state.json
39
+ const stateFile = path.join(ROOT, '.bounce', 'state.json');
40
+ if (!fs.existsSync(stateFile)) {
41
+ console.error('ERROR: .bounce/state.json not found');
42
+ process.exit(1);
43
+ }
44
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
45
+ if (!state.stories[storyId]) {
46
+ console.error(`ERROR: Story "${storyId}" not found in state.json`);
47
+ process.exit(1);
48
+ }
49
+ state.stories[storyId].state = 'Done';
50
+ state.stories[storyId].qa_bounces = qaBounces;
51
+ state.stories[storyId].arch_bounces = archBounces;
52
+ state.stories[storyId].worktree = null;
53
+ state.last_action = `${storyId} completed`;
54
+ state.updated_at = new Date().toISOString();
55
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
56
+ console.log(`✓ Updated state.json: ${storyId} → Done`);
57
+
58
+ // 2. Find sprint plan
59
+ const sprintNum = state.sprint_id.replace('S-', '');
60
+ const sprintPlanPath = path.join(ROOT, 'product_plans', 'sprints', `sprint-${sprintNum}`, `sprint-${sprintNum}.md`);
61
+
62
+ if (!fs.existsSync(sprintPlanPath)) {
63
+ console.warn(`⚠ Sprint plan not found at ${sprintPlanPath}. Update §1 and §4 manually.`);
64
+ process.exit(0);
65
+ }
66
+
67
+ let content = fs.readFileSync(sprintPlanPath, 'utf8');
68
+
69
+ // 3. Update §1 table — find the row with storyId and change V-Bounce State to Done
70
+ const tableRowRegex = new RegExp(`(\\|[^|]*\\|[^|]*${storyId.replace(/[-]/g, '[-]')}[^|]*\\|[^|]*\\|[^|]*\\|)([^|]+)(\\|[^|]*\\|)`, 'g');
71
+ let updated = false;
72
+ content = content.replace(tableRowRegex, (match, before, stateCell, after) => {
73
+ updated = true;
74
+ return `${before} Done ${after}`;
75
+ });
76
+
77
+ if (!updated) {
78
+ console.warn(`⚠ Could not find ${storyId} row in §1 table. Update V-Bounce State manually.`);
79
+ }
80
+
81
+ // 4. Add row to §4 Execution Log
82
+ const logStart = '<!-- EXECUTION_LOG_START -->';
83
+ const logEnd = '<!-- EXECUTION_LOG_END -->';
84
+ const newRow = `| ${storyId} | Done | ${qaBounces} | ${archBounces} | ${correctionTax} | ${notes || '—'} |`;
85
+
86
+ if (content.includes(logStart)) {
87
+ // Find the table in the execution log section and append a row
88
+ const startIdx = content.indexOf(logStart);
89
+ const endIdx = content.indexOf(logEnd);
90
+
91
+ if (endIdx > startIdx) {
92
+ const before = content.substring(0, endIdx);
93
+ const after = content.substring(endIdx);
94
+
95
+ // Check if header row exists, if not add it
96
+ const section = before.substring(startIdx);
97
+ if (!section.includes('| Story |')) {
98
+ const headerRow = `\n| Story | Final State | QA Bounces | Arch Bounces | Correction Tax | Notes |\n|-------|-------------|------------|--------------|----------------|-------|`;
99
+ content = before + headerRow + '\n' + newRow + '\n' + after;
100
+ } else {
101
+ content = before + newRow + '\n' + after;
102
+ }
103
+ console.log(`✓ Added row to §4 Execution Log`);
104
+ }
105
+ } else {
106
+ // Append §4 section at end
107
+ content += `\n\n<!-- EXECUTION_LOG_START -->\n## 4. Execution Log\n\n| Story | Final State | QA Bounces | Arch Bounces | Correction Tax | Notes |\n|-------|-------------|------------|--------------|----------------|-------|\n${newRow}\n<!-- EXECUTION_LOG_END -->\n`;
108
+ console.log(`✓ Created §4 Execution Log with first row`);
109
+ }
110
+
111
+ fs.writeFileSync(sprintPlanPath, content);
112
+ console.log(`✓ Updated sprint plan: ${storyId} Done`);
113
+ console.log(`\n QA bounces: ${qaBounces} | Arch bounces: ${archBounces} | Correction tax: ${correctionTax}`);
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * doctor.mjs
5
+ * V-Bounce OS Health Check — validates all configs, templates, state files
6
+ * Usage: vbounce doctor
7
+ */
8
+
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ const ROOT = path.resolve(__dirname, '..');
15
+
16
+ const checks = [];
17
+ let issueCount = 0;
18
+
19
+ function pass(msg) {
20
+ checks.push(` ✓ ${msg}`);
21
+ }
22
+
23
+ function fail(msg, fix) {
24
+ checks.push(` ✗ ${msg}${fix ? `\n → Fix: ${fix}` : ''}`);
25
+ issueCount++;
26
+ }
27
+
28
+ function warn(msg) {
29
+ checks.push(` ⚠ ${msg}`);
30
+ }
31
+
32
+ // Check LESSONS.md
33
+ if (fs.existsSync(path.join(ROOT, 'LESSONS.md'))) {
34
+ pass('LESSONS.md exists');
35
+ } else {
36
+ fail('LESSONS.md missing', 'Create LESSONS.md at project root');
37
+ }
38
+
39
+ // Check templates
40
+ const requiredTemplates = ['sprint.md', 'delivery_plan.md', 'sprint_report.md', 'story.md', 'epic.md', 'charter.md', 'roadmap.md', 'risk_registry.md'];
41
+ const templatesDir = path.join(ROOT, 'templates');
42
+ let templateCount = 0;
43
+ for (const t of requiredTemplates) {
44
+ if (fs.existsSync(path.join(templatesDir, t))) templateCount++;
45
+ else fail(`templates/${t} missing`, `Create from V-Bounce OS template`);
46
+ }
47
+ if (templateCount === requiredTemplates.length) pass(`templates/ complete (${templateCount}/${requiredTemplates.length})`);
48
+
49
+ // Check .bounce directory
50
+ if (fs.existsSync(path.join(ROOT, '.bounce'))) {
51
+ pass('.bounce/ directory exists');
52
+
53
+ // Check state.json
54
+ const stateFile = path.join(ROOT, '.bounce', 'state.json');
55
+ if (fs.existsSync(stateFile)) {
56
+ try {
57
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
58
+ pass(`state.json valid (sprint ${state.sprint_id}, ${Object.keys(state.stories || {}).length} stories)`);
59
+ } catch (e) {
60
+ fail('state.json exists but is invalid JSON', 'Run: vbounce validate state');
61
+ }
62
+ } else {
63
+ warn('state.json not found — run: vbounce sprint init S-XX D-XX');
64
+ }
65
+ } else {
66
+ warn('.bounce/ directory missing — run: vbounce sprint init S-XX D-XX');
67
+ }
68
+
69
+ // Check brain files
70
+ const brainFiles = [
71
+ ['brains/CLAUDE.md', 'Tier 1 (Claude Code)'],
72
+ ['brains/GEMINI.md', 'Tier 2 (Gemini CLI)'],
73
+ ['brains/AGENTS.md', 'Tier 2 (Codex CLI)'],
74
+ ];
75
+ for (const [f, tier] of brainFiles) {
76
+ if (fs.existsSync(path.join(ROOT, f))) pass(`Brain file: ${f} (${tier})`);
77
+ else fail(`Brain file: ${f} missing`, `Run: vbounce init --tool ${f.includes('GEMINI') ? 'gemini' : f.includes('AGENTS') ? 'codex' : 'claude'}`);
78
+ }
79
+
80
+ // Check optional brain files
81
+ const optionalBrains = [
82
+ ['brains/copilot/copilot-instructions.md', 'copilot'],
83
+ ['brains/windsurf/.windsurfrules', 'windsurf'],
84
+ ];
85
+ for (const [f, tool] of optionalBrains) {
86
+ if (fs.existsSync(path.join(ROOT, f))) pass(`Brain file: ${f} (Tier 4)`);
87
+ else warn(`Brain file: ${f} not found (optional) — run: vbounce init --tool ${tool}`);
88
+ }
89
+
90
+ // Check skills
91
+ const requiredSkills = ['agent-team', 'doc-manager', 'lesson', 'vibe-code-review', 'react-best-practices', 'write-skill', 'improve'];
92
+ const skillsDir = path.join(ROOT, 'skills');
93
+ let skillCount = 0;
94
+ for (const s of requiredSkills) {
95
+ const skillFile = path.join(skillsDir, s, 'SKILL.md');
96
+ if (fs.existsSync(skillFile)) skillCount++;
97
+ else fail(`skills/${s}/SKILL.md missing`);
98
+ }
99
+ if (skillCount === requiredSkills.length) pass(`Skills: ${skillCount}/${requiredSkills.length} installed`);
100
+
101
+ // Check scripts
102
+ const requiredScripts = [
103
+ 'validate_report.mjs', 'update_state.mjs', 'validate_state.mjs',
104
+ 'validate_sprint_plan.mjs', 'validate_bounce_readiness.mjs',
105
+ 'init_sprint.mjs', 'close_sprint.mjs', 'complete_story.mjs',
106
+ 'prep_qa_context.mjs', 'prep_arch_context.mjs', 'prep_sprint_context.mjs',
107
+ 'prep_sprint_summary.mjs', 'sprint_trends.mjs', 'suggest_improvements.mjs',
108
+ 'hotfix_manager.sh'
109
+ ];
110
+ const scriptsDir = path.join(ROOT, 'scripts');
111
+ let scriptCount = 0;
112
+ for (const s of requiredScripts) {
113
+ if (fs.existsSync(path.join(scriptsDir, s))) scriptCount++;
114
+ else fail(`scripts/${s} missing`);
115
+ }
116
+ if (scriptCount === requiredScripts.length) pass(`Scripts: ${scriptCount}/${requiredScripts.length} available`);
117
+
118
+ // Check product_plans structure
119
+ if (fs.existsSync(path.join(ROOT, 'product_plans'))) {
120
+ pass('product_plans/ directory exists');
121
+ } else {
122
+ warn('product_plans/ directory missing — create it to store planning documents');
123
+ }
124
+
125
+ // Check vbounce.config.json
126
+ if (fs.existsSync(path.join(ROOT, 'vbounce.config.json'))) {
127
+ pass('vbounce.config.json found');
128
+ } else {
129
+ warn('vbounce.config.json not found — using default context limits');
130
+ }
131
+
132
+ // Print results
133
+ console.log('\nV-Bounce OS Health Check');
134
+ console.log('========================');
135
+ checks.forEach(c => console.log(c));
136
+ console.log('');
137
+ if (issueCount === 0) {
138
+ console.log('✓ All checks passed.');
139
+ } else {
140
+ console.log(`Issues: ${issueCount}`);
141
+ console.log('Run suggested commands to fix.');
142
+ }
143
+
144
+ process.exit(issueCount > 0 ? 1 : 0);
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env bash
2
+ # init_gate_config.sh — Auto-detect project stack and generate .bounce/gate-checks.json
3
+ # Usage: ./scripts/init_gate_config.sh [project-path]
4
+ #
5
+ # Run once during project setup or when the improve skill suggests new checks.
6
+ # Safe to re-run — merges with existing config (preserves custom checks).
7
+
8
+ set -euo pipefail
9
+
10
+ PROJECT_PATH="${1:-.}"
11
+ PROJECT_PATH="$(cd "$PROJECT_PATH" && pwd)"
12
+ CONFIG_PATH="${PROJECT_PATH}/.bounce/gate-checks.json"
13
+
14
+ RED='\033[0;31m'
15
+ GREEN='\033[0;32m'
16
+ YELLOW='\033[1;33m'
17
+ CYAN='\033[0;36m'
18
+ NC='\033[0m'
19
+
20
+ echo -e "${CYAN}V-Bounce OS Gate Config Initializer${NC}"
21
+ echo -e "Project: ${PROJECT_PATH}"
22
+ echo ""
23
+
24
+ # ── Detect stack ─────────────────────────────────────────────────────
25
+
26
+ LANGUAGE="unknown"
27
+ FRAMEWORK="unknown"
28
+ TEST_RUNNER="unknown"
29
+ BUILD_CMD=""
30
+ LINT_CMD=""
31
+ TEST_CMD=""
32
+
33
+ # Language detection
34
+ if [[ -f "${PROJECT_PATH}/tsconfig.json" ]]; then
35
+ LANGUAGE="typescript"
36
+ elif [[ -f "${PROJECT_PATH}/package.json" ]]; then
37
+ LANGUAGE="javascript"
38
+ elif [[ -f "${PROJECT_PATH}/pyproject.toml" || -f "${PROJECT_PATH}/setup.py" || -f "${PROJECT_PATH}/requirements.txt" ]]; then
39
+ LANGUAGE="python"
40
+ elif [[ -f "${PROJECT_PATH}/Cargo.toml" ]]; then
41
+ LANGUAGE="rust"
42
+ elif [[ -f "${PROJECT_PATH}/go.mod" ]]; then
43
+ LANGUAGE="go"
44
+ elif [[ -f "${PROJECT_PATH}/build.gradle" || -f "${PROJECT_PATH}/pom.xml" ]]; then
45
+ LANGUAGE="java"
46
+ elif [[ -f "${PROJECT_PATH}/Package.swift" ]]; then
47
+ LANGUAGE="swift"
48
+ fi
49
+
50
+ echo -e "Language: ${GREEN}${LANGUAGE}${NC}"
51
+
52
+ # Framework detection (JS/TS ecosystem)
53
+ if [[ -f "${PROJECT_PATH}/package.json" ]]; then
54
+ PKG_CONTENT=$(cat "${PROJECT_PATH}/package.json")
55
+
56
+ if echo "$PKG_CONTENT" | grep -q '"next"'; then FRAMEWORK="nextjs"
57
+ elif echo "$PKG_CONTENT" | grep -q '"react"'; then FRAMEWORK="react"
58
+ elif echo "$PKG_CONTENT" | grep -q '"vue"'; then FRAMEWORK="vue"
59
+ elif echo "$PKG_CONTENT" | grep -q '"svelte"'; then FRAMEWORK="svelte"
60
+ elif echo "$PKG_CONTENT" | grep -q '"express"'; then FRAMEWORK="express"
61
+ elif echo "$PKG_CONTENT" | grep -q '"fastify"'; then FRAMEWORK="fastify"
62
+ elif echo "$PKG_CONTENT" | grep -q '"@angular/core"'; then FRAMEWORK="angular"
63
+ fi
64
+
65
+ # Test runner
66
+ if echo "$PKG_CONTENT" | grep -q '"vitest"'; then TEST_RUNNER="vitest"
67
+ elif echo "$PKG_CONTENT" | grep -q '"jest"'; then TEST_RUNNER="jest"
68
+ elif echo "$PKG_CONTENT" | grep -q '"mocha"'; then TEST_RUNNER="mocha"
69
+ elif echo "$PKG_CONTENT" | grep -q '"ava"'; then TEST_RUNNER="ava"
70
+ fi
71
+
72
+ # Commands from scripts
73
+ BUILD_CMD=$(node -e "try{const p=require('${PROJECT_PATH}/package.json');console.log(p.scripts&&p.scripts.build||'')}catch(e){}" 2>/dev/null || echo "")
74
+ LINT_CMD=$(node -e "try{const p=require('${PROJECT_PATH}/package.json');console.log(p.scripts&&p.scripts.lint||'')}catch(e){}" 2>/dev/null || echo "")
75
+ TEST_CMD=$(node -e "try{const p=require('${PROJECT_PATH}/package.json');const t=p.scripts&&p.scripts.test||'';if(t&&!t.includes('no test specified'))console.log(t);else console.log('')}catch(e){}" 2>/dev/null || echo "")
76
+ elif [[ "$LANGUAGE" == "python" ]]; then
77
+ if command -v pytest &>/dev/null; then TEST_RUNNER="pytest"; fi
78
+ if command -v ruff &>/dev/null; then LINT_CMD="ruff check ."; fi
79
+ elif [[ "$LANGUAGE" == "rust" ]]; then
80
+ TEST_RUNNER="cargo"
81
+ BUILD_CMD="cargo build"
82
+ LINT_CMD="cargo clippy"
83
+ elif [[ "$LANGUAGE" == "go" ]]; then
84
+ TEST_RUNNER="go"
85
+ BUILD_CMD="go build ./..."
86
+ LINT_CMD="golangci-lint run"
87
+ fi
88
+
89
+ echo -e "Framework: ${GREEN}${FRAMEWORK}${NC}"
90
+ echo -e "Test runner: ${GREEN}${TEST_RUNNER}${NC}"
91
+ [[ -n "$BUILD_CMD" ]] && echo -e "Build: ${GREEN}${BUILD_CMD}${NC}"
92
+ [[ -n "$LINT_CMD" ]] && echo -e "Lint: ${GREEN}${LINT_CMD}${NC}"
93
+ echo ""
94
+
95
+ # ── Generate config ──────────────────────────────────────────────────
96
+
97
+ # Build QA checks array
98
+ QA_CHECKS='[
99
+ { "id": "tests_exist", "enabled": true, "description": "Verify test files exist for modified source files" },
100
+ { "id": "tests_pass", "enabled": true, "description": "Run test suite" },
101
+ { "id": "build", "enabled": true, "description": "Run build command" },
102
+ { "id": "lint", "enabled": true, "description": "Run linter" },
103
+ { "id": "no_debug_output", "enabled": true, "description": "No debug statements in modified files" },
104
+ { "id": "no_todo_fixme", "enabled": true, "description": "No TODO/FIXME in modified files" },
105
+ { "id": "exports_have_docs", "enabled": true, "description": "Exported symbols have doc comments" }
106
+ ]'
107
+
108
+ # Build Architect checks array
109
+ ARCH_CHECKS='[
110
+ { "id": "tests_exist", "enabled": true, "description": "Verify test files exist for modified source files" },
111
+ { "id": "tests_pass", "enabled": true, "description": "Run test suite" },
112
+ { "id": "build", "enabled": true, "description": "Run build command" },
113
+ { "id": "lint", "enabled": true, "description": "Run linter" },
114
+ { "id": "no_debug_output", "enabled": true, "description": "No debug statements in modified files" },
115
+ { "id": "no_todo_fixme", "enabled": true, "description": "No TODO/FIXME in modified files" },
116
+ { "id": "exports_have_docs", "enabled": true, "description": "Exported symbols have doc comments" },
117
+ { "id": "no_new_deps", "enabled": true, "description": "No new dependencies without review" },
118
+ { "id": "file_size", "enabled": true, "max_lines": 500, "description": "Source files under 500 lines" }
119
+ ]'
120
+
121
+ # Write config
122
+ mkdir -p "$(dirname "$CONFIG_PATH")"
123
+
124
+ cat > "$CONFIG_PATH" << HEREDOC
125
+ {
126
+ "version": 1,
127
+ "detected_stack": {
128
+ "language": "${LANGUAGE}",
129
+ "framework": "${FRAMEWORK}",
130
+ "test_runner": "${TEST_RUNNER}",
131
+ "build_cmd": "${BUILD_CMD}",
132
+ "lint_cmd": "${LINT_CMD}",
133
+ "test_cmd": "${TEST_CMD}"
134
+ },
135
+ "qa_checks": ${QA_CHECKS},
136
+ "arch_checks": ${ARCH_CHECKS},
137
+ "custom_checks": []
138
+ }
139
+ HEREDOC
140
+
141
+ echo -e "${GREEN}Generated: ${CONFIG_PATH}${NC}"
142
+ echo ""
143
+ echo "Universal checks enabled. To add project-specific checks:"
144
+ echo " 1. Run sprints and let agents collect Process Feedback"
145
+ echo " 2. Use the 'improve' skill to propose new checks"
146
+ echo " 3. Or manually add entries to the custom_checks array"
147
+ echo ""
148
+ echo -e "Example custom check (add to custom_checks):"
149
+ echo ' { "id": "custom_grep", "gate": "arch", "enabled": true,'
150
+ echo ' "pattern": "var\\(--my-prefix-", "glob": "*.tsx",'
151
+ echo ' "should_find": false, "description": "No raw CSS vars in components" }'
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * init_sprint.mjs
5
+ * Sprint setup automation — creates state.json, sprint plan dir, and prints git commands.
6
+ *
7
+ * Usage:
8
+ * ./scripts/init_sprint.mjs S-06 D-02 --stories STORY-011-05,STORY-005-01,STORY-005-02
9
+ */
10
+
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import { fileURLToPath } from 'url';
14
+
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+ const ROOT = path.resolve(__dirname, '..');
17
+
18
+ const args = process.argv.slice(2);
19
+
20
+ if (args.length < 2) {
21
+ console.error('Usage: init_sprint.mjs S-XX D-NN [--stories STORY-ID1,STORY-ID2,...]');
22
+ process.exit(1);
23
+ }
24
+
25
+ const sprintId = args[0]; // e.g. S-06
26
+ const deliveryId = args[1]; // e.g. D-02
27
+
28
+ if (!/^S-\d{2}$/.test(sprintId)) {
29
+ console.error(`ERROR: sprint_id "${sprintId}" must match S-XX format`);
30
+ process.exit(1);
31
+ }
32
+ if (!/^D-\d{2}$/.test(deliveryId)) {
33
+ console.error(`ERROR: delivery_id "${deliveryId}" must match D-NN format`);
34
+ process.exit(1);
35
+ }
36
+
37
+ const storiesArg = args.indexOf('--stories');
38
+ const storyIds = storiesArg !== -1 ? args[storiesArg + 1].split(',') : [];
39
+
40
+ // 1. Create .bounce/ directory
41
+ const bounceDir = path.join(ROOT, '.bounce');
42
+ fs.mkdirSync(bounceDir, { recursive: true });
43
+ fs.mkdirSync(path.join(bounceDir, 'archive'), { recursive: true });
44
+ fs.mkdirSync(path.join(bounceDir, 'reports'), { recursive: true });
45
+
46
+ // 2. Create state.json
47
+ const stateFile = path.join(bounceDir, 'state.json');
48
+ if (fs.existsSync(stateFile)) {
49
+ console.warn(`⚠ state.json already exists. Overwriting...`);
50
+ }
51
+
52
+ const sprintNum = sprintId.replace('S-', '');
53
+ const stories = {};
54
+ for (const id of storyIds) {
55
+ stories[id.trim()] = {
56
+ state: 'Draft',
57
+ qa_bounces: 0,
58
+ arch_bounces: 0,
59
+ worktree: null
60
+ };
61
+ }
62
+
63
+ const state = {
64
+ sprint_id: sprintId,
65
+ delivery_id: deliveryId,
66
+ sprint_plan: `product_plans/sprints/sprint-${sprintNum}/sprint-${sprintNum}.md`,
67
+ delivery_plan: `product_plans/strategy/${deliveryId}_DELIVERY_PLAN.md`,
68
+ stories,
69
+ phase: 'Phase 1',
70
+ last_action: `Sprint ${sprintId} initialized`,
71
+ updated_at: new Date().toISOString()
72
+ };
73
+
74
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
75
+ console.log(`✓ Created .bounce/state.json`);
76
+
77
+ // 3. Create sprint plan directory
78
+ const sprintDir = path.join(ROOT, 'product_plans', 'sprints', `sprint-${sprintNum}`);
79
+ fs.mkdirSync(sprintDir, { recursive: true });
80
+
81
+ const sprintPlanFile = path.join(sprintDir, `sprint-${sprintNum}.md`);
82
+ if (!fs.existsSync(sprintPlanFile)) {
83
+ // Copy from template
84
+ const templateFile = path.join(ROOT, 'templates', 'sprint.md');
85
+ if (fs.existsSync(templateFile)) {
86
+ let content = fs.readFileSync(templateFile, 'utf8');
87
+ // Replace placeholders
88
+ content = content.replace(/sprint-\{XX\}/g, `sprint-${sprintNum}`);
89
+ content = content.replace(/S-\{XX\}/g, sprintId);
90
+ content = content.replace(/D-\{NN\}/g, deliveryId);
91
+ content = content.replace(/status: "Planning \/ Active \/ Completed"/, 'status: "Planning"');
92
+ // Strip instructions block
93
+ content = content.replace(/<instructions>[\s\S]*?<\/instructions>\n\n/, '');
94
+ fs.writeFileSync(sprintPlanFile, content);
95
+ console.log(`✓ Created product_plans/sprints/sprint-${sprintNum}/sprint-${sprintNum}.md`);
96
+ } else {
97
+ // Create minimal sprint plan
98
+ const minimal = `---\nsprint_id: "${sprintId}"\nsprint_goal: "TBD"\ndates: "TBD"\nstatus: "Planning"\ndelivery: "${deliveryId}"\n---\n\n# Sprint ${sprintId} Plan\n\n## 1. Active Scope\n\n| Priority | Story | Epic | Label | V-Bounce State | Blocker |\n|----------|-------|------|-------|----------------|---------|\n${storyIds.map((id, i) => `| ${i + 1} | ${id.trim()} | — | L2 | Draft | — |`).join('\n')}\n\n### Escalated / Parking Lot\n- (none)\n\n---\n\n## 2. Execution Strategy\n\n### Phase Plan\n- **Phase 1 (parallel)**: ${storyIds.join(', ')}\n\n### Risk Flags\n- (TBD)\n\n---\n\n## 3. Sprint Open Questions\n\n| Question | Options | Impact | Owner | Status |\n|----------|---------|--------|-------|--------|\n\n---\n\n<!-- EXECUTION_LOG_START -->\n## 4. Execution Log\n\n| Story | Final State | QA Bounces | Arch Bounces | Correction Tax | Notes |\n|-------|-------------|------------|--------------|----------------|-------|\n<!-- EXECUTION_LOG_END -->\n`;
99
+ fs.writeFileSync(sprintPlanFile, minimal);
100
+ console.log(`✓ Created product_plans/sprints/sprint-${sprintNum}/sprint-${sprintNum}.md (minimal — template not found)`);
101
+ }
102
+ } else {
103
+ console.log(` Sprint plan already exists: product_plans/sprints/sprint-${sprintNum}/sprint-${sprintNum}.md`);
104
+ }
105
+
106
+ // 4. Print git commands (don't execute)
107
+ console.log('');
108
+ console.log('Run these git commands to initialize the sprint branch:');
109
+ console.log(` git checkout -b sprint/${sprintId} main`);
110
+ if (storyIds.length > 0) {
111
+ console.log('');
112
+ console.log('Then create worktrees for each story:');
113
+ storyIds.forEach(id => {
114
+ const trimmed = id.trim();
115
+ console.log(` git worktree add .worktrees/${trimmed} -b story/${trimmed} sprint/${sprintId}`);
116
+ console.log(` mkdir -p .worktrees/${trimmed}/.bounce/{tasks,reports}`);
117
+ });
118
+ }
119
+
120
+ console.log('');
121
+ console.log(`✓ Sprint ${sprintId} initialized. Stories: ${storyIds.length > 0 ? storyIds.join(', ') : 'none (add manually)'}`);