@sandrinio/vbounce 1.4.0 → 1.6.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.
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env bash
2
+ # pre_gate_runner.sh — Runs pre-gate checks before QA or Architect agents
3
+ # Usage: ./scripts/pre_gate_runner.sh <qa|arch> [worktree-path] [base-branch]
4
+ #
5
+ # Reads .bounce/gate-checks.json for check configuration.
6
+ # If no config exists, runs universal defaults with auto-detected stack.
7
+
8
+ set -euo pipefail
9
+
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ source "${SCRIPT_DIR}/pre_gate_common.sh"
12
+
13
+ # ── Arguments ────────────────────────────────────────────────────────
14
+
15
+ GATE_TYPE="${1:-}"
16
+ WORKTREE_PATH="${2:-.}"
17
+ BASE_BRANCH="${3:-}"
18
+ PLAIN_RESULTS=""
19
+
20
+ if [[ -z "$GATE_TYPE" ]] || [[ "$GATE_TYPE" != "qa" && "$GATE_TYPE" != "arch" ]]; then
21
+ echo "Usage: ./scripts/pre_gate_runner.sh <qa|arch> [worktree-path] [base-branch]"
22
+ echo ""
23
+ echo " qa — Run QA pre-gate checks (before QA agent)"
24
+ echo " arch — Run Architect pre-gate checks (before Architect agent)"
25
+ echo ""
26
+ echo " worktree-path — Path to story worktree (default: current dir)"
27
+ echo " base-branch — Branch to diff against (default: auto-detect)"
28
+ exit 1
29
+ fi
30
+
31
+ # Resolve to absolute path
32
+ WORKTREE_PATH="$(cd "$WORKTREE_PATH" && pwd)"
33
+
34
+ echo -e "${CYAN}V-Bounce OS Pre-Gate Scanner${NC}"
35
+ echo -e "Gate: ${YELLOW}${GATE_TYPE}${NC}"
36
+ echo -e "Target: ${WORKTREE_PATH}"
37
+ echo ""
38
+
39
+ # ── Auto-detect base branch if not provided ──────────────────────────
40
+
41
+ if [[ -z "$BASE_BRANCH" ]]; then
42
+ cd "$WORKTREE_PATH"
43
+ # Try to find the sprint branch this story branched from
44
+ BASE_BRANCH=$(git log --oneline --merges -1 --format=%H 2>/dev/null || echo "")
45
+ if [[ -z "$BASE_BRANCH" ]]; then
46
+ # Fall back to parent branch detection
47
+ BASE_BRANCH=$(git rev-parse --abbrev-ref HEAD@{upstream} 2>/dev/null || echo "")
48
+ fi
49
+ fi
50
+
51
+ # ── Load config or use defaults ──────────────────────────────────────
52
+
53
+ CONFIG_PATH="${WORKTREE_PATH}/.bounce/gate-checks.json"
54
+ HAS_CONFIG=false
55
+
56
+ if [[ -f "$CONFIG_PATH" ]]; then
57
+ HAS_CONFIG=true
58
+ echo -e "Config: ${GREEN}${CONFIG_PATH}${NC}"
59
+ else
60
+ # Check parent repo too (worktree might not have it)
61
+ REPO_ROOT=$(cd "$WORKTREE_PATH" && git rev-parse --show-toplevel 2>/dev/null || echo "$WORKTREE_PATH")
62
+ CONFIG_PATH="${REPO_ROOT}/.bounce/gate-checks.json"
63
+ if [[ -f "$CONFIG_PATH" ]]; then
64
+ HAS_CONFIG=true
65
+ echo -e "Config: ${GREEN}${CONFIG_PATH}${NC}"
66
+ else
67
+ echo -e "Config: ${YELLOW}None found — using universal defaults${NC}"
68
+ fi
69
+ fi
70
+
71
+ echo ""
72
+
73
+ # ── Get modified files ───────────────────────────────────────────────
74
+
75
+ MODIFIED_FILES=$(get_modified_files "$WORKTREE_PATH" "$BASE_BRANCH")
76
+
77
+ # ── Run checks ───────────────────────────────────────────────────────
78
+
79
+ run_checks_from_config() {
80
+ local gate="$1"
81
+ local checks_key="${gate}_checks"
82
+
83
+ # Parse config with node (available since V-Bounce requires it)
84
+ local check_ids
85
+ check_ids=$(node -e "
86
+ const fs = require('fs');
87
+ const cfg = JSON.parse(fs.readFileSync('${CONFIG_PATH}', 'utf8'));
88
+ const checks = cfg['${checks_key}'] || [];
89
+ checks.filter(c => c.enabled !== false).forEach(c => {
90
+ console.log(JSON.stringify(c));
91
+ });
92
+ " 2>/dev/null)
93
+
94
+ while IFS= read -r check_json; do
95
+ [[ -z "$check_json" ]] && continue
96
+
97
+ local id cmd pattern glob should_find max_lines description
98
+ id=$(echo "$check_json" | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));console.log(d.id||'')" 2>/dev/null)
99
+ cmd=$(echo "$check_json" | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));console.log(d.cmd||'')" 2>/dev/null)
100
+ pattern=$(echo "$check_json" | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));console.log(d.pattern||'')" 2>/dev/null)
101
+ glob=$(echo "$check_json" | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));console.log(d.glob||'')" 2>/dev/null)
102
+ should_find=$(echo "$check_json" | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));console.log(d.should_find||'false')" 2>/dev/null)
103
+ max_lines=$(echo "$check_json" | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));console.log(d.max_lines||'500')" 2>/dev/null)
104
+ description=$(echo "$check_json" | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));console.log(d.description||d.id||'')" 2>/dev/null)
105
+
106
+ case "$id" in
107
+ tests_exist) check_tests_exist "$WORKTREE_PATH" "$MODIFIED_FILES" ;;
108
+ tests_pass) check_tests_pass "$WORKTREE_PATH" ;;
109
+ build) check_build "$WORKTREE_PATH" ;;
110
+ lint) check_lint "$WORKTREE_PATH" ;;
111
+ no_debug_output) check_no_debug_output "$WORKTREE_PATH" "$MODIFIED_FILES" ;;
112
+ no_todo_fixme) check_no_todo_fixme "$WORKTREE_PATH" "$MODIFIED_FILES" ;;
113
+ exports_have_docs) check_exports_have_docs "$WORKTREE_PATH" "$MODIFIED_FILES" ;;
114
+ no_new_deps) check_no_new_dependencies "$WORKTREE_PATH" "$BASE_BRANCH" ;;
115
+ file_size) check_file_size_limit "$WORKTREE_PATH" "$MODIFIED_FILES" "$max_lines" ;;
116
+ custom_cmd) run_custom_check "$WORKTREE_PATH" "$description" "$cmd" "$description" ;;
117
+ custom_grep) run_custom_grep_check "$WORKTREE_PATH" "$description" "$pattern" "$glob" "$should_find" ;;
118
+ *)
119
+ # Unknown built-in — try as custom command if cmd is provided
120
+ if [[ -n "$cmd" ]]; then
121
+ run_custom_check "$WORKTREE_PATH" "$id" "$cmd" "$description"
122
+ else
123
+ record_result "$id" "SKIP" "Unknown check type"
124
+ record_result_plain "$id" "SKIP" "Unknown check type"
125
+ fi
126
+ ;;
127
+ esac
128
+ done <<< "$check_ids"
129
+ }
130
+
131
+ run_universal_defaults() {
132
+ local gate="$1"
133
+
134
+ # QA-level checks (always run)
135
+ check_tests_exist "$WORKTREE_PATH" "$MODIFIED_FILES"
136
+ check_tests_pass "$WORKTREE_PATH"
137
+ check_build "$WORKTREE_PATH"
138
+ check_lint "$WORKTREE_PATH"
139
+ check_no_debug_output "$WORKTREE_PATH" "$MODIFIED_FILES"
140
+ check_no_todo_fixme "$WORKTREE_PATH" "$MODIFIED_FILES"
141
+ check_exports_have_docs "$WORKTREE_PATH" "$MODIFIED_FILES"
142
+
143
+ # Architect-level checks (only for arch gate)
144
+ if [[ "$gate" == "arch" ]]; then
145
+ check_no_new_dependencies "$WORKTREE_PATH" "$BASE_BRANCH"
146
+ check_file_size_limit "$WORKTREE_PATH" "$MODIFIED_FILES" 500
147
+ fi
148
+ }
149
+
150
+ # ── Execute ──────────────────────────────────────────────────────────
151
+
152
+ if [[ "$HAS_CONFIG" == "true" ]]; then
153
+ run_checks_from_config "$GATE_TYPE"
154
+ else
155
+ run_universal_defaults "$GATE_TYPE"
156
+ fi
157
+
158
+ # ── Output ───────────────────────────────────────────────────────────
159
+
160
+ print_summary
161
+
162
+ # Write report
163
+ REPORT_DIR="${WORKTREE_PATH}/.bounce/reports"
164
+ REPORT_FILE="${REPORT_DIR}/pre-${GATE_TYPE}-scan.txt"
165
+ write_report "$REPORT_FILE"
166
+ echo ""
167
+ echo -e "Report: ${CYAN}${REPORT_FILE}${NC}"
168
+
169
+ # Exit code
170
+ if [[ $FAIL_COUNT -gt 0 ]]; then
171
+ echo -e "\n${RED}Gate check failed with ${FAIL_COUNT} failure(s).${NC}"
172
+ exit 1
173
+ else
174
+ echo -e "\n${GREEN}All checks passed.${NC}"
175
+ exit 0
176
+ fi
@@ -14,20 +14,20 @@ import yaml from 'js-yaml';
14
14
 
15
15
  // Defined schemas for each report type
16
16
  const SCHEMAS = {
17
- dev: ['status', 'correction_tax', 'tests_written', 'files_modified', 'lessons_flagged'],
17
+ dev: ['status', 'correction_tax', 'tokens_used', 'tests_written', 'files_modified', 'lessons_flagged'],
18
18
  qa: {
19
- base: ['status', 'bounce_count', 'bugs_found', 'gold_plating_detected'],
19
+ base: ['status', 'bounce_count', 'tokens_used', 'bugs_found', 'gold_plating_detected'],
20
20
  conditional: { 'FAIL': ['failed_scenarios'] }
21
21
  },
22
22
  arch: {
23
- base: ['status'],
23
+ base: ['status', 'tokens_used'],
24
24
  conditional: { 'PASS': ['safe_zone_score', 'ai_isms_detected', 'regression_risk'], 'FAIL': ['bounce_count', 'critical_failures'] }
25
25
  },
26
26
  devops: {
27
- base: ['type', 'status'],
27
+ base: ['type', 'status', 'tokens_used'],
28
28
  conditional: { 'story-merge': ['conflicts_detected'], 'sprint-release': ['version'] }
29
29
  },
30
- scribe: ['mode', 'docs_created', 'docs_updated', 'docs_removed']
30
+ scribe: ['mode', 'tokens_used', 'docs_created', 'docs_updated', 'docs_removed']
31
31
  };
32
32
 
33
33
  function extractFrontmatter(content) {
@@ -82,7 +82,7 @@ async function indexFile(filePath, embedder) {
82
82
  else if (filePath.includes('ROADMAP.md')) type = 'adr';
83
83
  else if (filePath.includes('.bounce/reports')) type = 'report';
84
84
  else if (filePath.includes('product_plans')) type = 'plan';
85
- else if (filePath.includes('product_documentation')) type = 'documentation';
85
+ else if (filePath.includes('vdocs')) type = 'documentation';
86
86
 
87
87
  const metadata = { file: basename, type };
88
88
  const chunks = chunkMarkdown(content, metadata);
@@ -147,7 +147,7 @@ async function main() {
147
147
  if (fs.existsSync('LESSONS.md')) filesToIndex.push('LESSONS.md');
148
148
  if (fs.existsSync('ROADMAP.md')) filesToIndex.push('ROADMAP.md');
149
149
  walkDir('product_plans');
150
- walkDir('product_documentation');
150
+ walkDir('vdocs');
151
151
  walkDir('.bounce/reports');
152
152
  } else if (targetPath) {
153
153
  const stat = fs.statSync(targetPath);
@@ -20,30 +20,41 @@ const EXPECTED_PROMPT_SIGNATURES = {
20
20
  'developer.md': [
21
21
  'status:',
22
22
  'correction_tax:',
23
+ 'tokens_used:',
24
+ 'tests_written:',
23
25
  'files_modified:',
24
26
  'lessons_flagged:'
25
27
  ],
26
28
  'qa.md': [
27
29
  'status: "PASS"',
30
+ 'bounce_count:',
28
31
  'bugs_found: 0',
32
+ 'gold_plating_detected:',
29
33
  'status: "FAIL"',
34
+ 'tokens_used:',
30
35
  'failed_scenarios:'
31
36
  ],
32
37
  'architect.md': [
33
38
  'status: "PASS"',
34
39
  'safe_zone_score:',
40
+ 'ai_isms_detected:',
35
41
  'regression_risk:',
36
42
  'status: "FAIL"',
43
+ 'bounce_count:',
44
+ 'tokens_used:',
37
45
  'critical_failures:'
38
46
  ],
39
47
  'devops.md': [
40
48
  'type: "story-merge"',
49
+ 'status:',
41
50
  'conflicts_detected:',
42
51
  'type: "sprint-release"',
52
+ 'tokens_used:',
43
53
  'version:'
44
54
  ],
45
55
  'scribe.md': [
46
56
  'mode:',
57
+ 'tokens_used:',
47
58
  'docs_created:',
48
59
  'docs_updated:',
49
60
  'docs_removed:'