start-vibing 2.0.19 → 2.0.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-vibing",
3
- "version": "2.0.19",
3
+ "version": "2.0.21",
4
4
  "description": "Setup Claude Code agents, skills, and hooks in your project. Smart copy that preserves your custom domains and configurations.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,9 +3,13 @@
3
3
  * Universal Hook Runner
4
4
  *
5
5
  * Runs hooks with multiple runtime fallbacks:
6
- * 1. python3 (primary - user's preferred)
7
- * 2. python (fallback)
8
- * 3. npx tsx (TypeScript execution)
6
+ * 1. bun (primary - fastest TypeScript execution)
7
+ * 2. npx tsx (TypeScript fallback)
8
+ * 3. python3 (Python fallback)
9
+ * 4. python (Python fallback)
10
+ *
11
+ * IMPORTANT: TypeScript files are the source of truth.
12
+ * Python files are only for environments without Node.js/Bun.
9
13
  *
10
14
  * Usage: npx tsx run-hook.ts <hook-name>
11
15
  * The hook-name should be without extension (e.g., "stop-validator")
@@ -96,11 +100,10 @@ async function runHook(hookName: string, stdinData: string): Promise<void> {
96
100
  const tsPath = join(HOOKS_DIR, `${hookName}.ts`);
97
101
  const pyPath = join(HOOKS_DIR, `${hookName}.py`);
98
102
 
99
- // Runtime detection order - Python FIRST, then fallbacks
103
+ // Runtime detection order - TypeScript ONLY (source of truth)
104
+ // Python files are deprecated and should be removed
100
105
  const runtimes: Array<{ name: string; cmd: string; ext: string }> = [
101
- { name: 'python3', cmd: 'python3', ext: '.py' },
102
- { name: 'python', cmd: 'python', ext: '.py' },
103
- { name: 'bun-ts', cmd: 'bun', ext: '.ts' },
106
+ { name: 'bun', cmd: 'bun', ext: '.ts' },
104
107
  { name: 'npx-tsx', cmd: 'npx tsx', ext: '.ts' },
105
108
  ];
106
109
 
@@ -130,7 +133,7 @@ async function runHook(hookName: string, stdinData: string): Promise<void> {
130
133
 
131
134
  // No runtime available - return safe default
132
135
  console.error(`[run-hook] No runtime available to run hook: ${hookName}`);
133
- console.error('[run-hook] Please install one of: python3, python, bun, or Node.js');
136
+ console.error('[run-hook] Please install bun or Node.js (for npx tsx)');
134
137
  const safeDefault = JSON.stringify({
135
138
  decision: 'approve',
136
139
  continue: true,
@@ -171,17 +174,22 @@ async function readStdinWithTimeout(timeoutMs: number): Promise<string> {
171
174
 
172
175
  // Main
173
176
  async function main(): Promise<void> {
177
+ // Log hook invocation for debugging (writes to stderr so it doesn't affect JSON output)
178
+ const hookName = process.argv[2];
179
+ const timestamp = new Date().toISOString();
180
+ console.error(`[run-hook] ${timestamp} - Hook invoked: ${hookName || 'none'}`);
181
+
174
182
  // Clean up deprecated files on every hook run
175
183
  cleanupDeprecatedFiles();
176
184
 
177
- const hookName = process.argv[2];
178
185
  if (!hookName) {
179
- console.error('Usage: bun run-hook.ts <hook-name>');
186
+ console.error('[run-hook] Usage: bun run-hook.ts <hook-name>');
180
187
  process.exit(1);
181
188
  }
182
189
 
183
190
  // Read stdin with timeout to avoid hanging
184
191
  const stdinData = await readStdinWithTimeout(2000);
192
+ console.error(`[run-hook] ${hookName} - stdin received, length: ${stdinData.length}`);
185
193
  await runHook(hookName, stdinData);
186
194
  }
187
195
 
@@ -965,11 +965,20 @@ async function readStdinWithTimeout(timeoutMs: number): Promise<string> {
965
965
  }
966
966
 
967
967
  async function main(): Promise<void> {
968
+ // Debug logging - always output to stderr for visibility
969
+ console.error('[stop-validator] ========================================');
970
+ console.error('[stop-validator] Starting validation...');
971
+ console.error(`[stop-validator] CWD: ${process.cwd()}`);
972
+ console.error(`[stop-validator] PROJECT_DIR: ${PROJECT_DIR}`);
973
+ console.error(`[stop-validator] CLAUDE_MD exists: ${existsSync(CLAUDE_MD_PATH)}`);
974
+
968
975
  let hookInput: HookInput = {};
969
976
  try {
970
977
  const stdin = await readStdinWithTimeout(1000);
978
+ console.error(`[stop-validator] stdin received: ${stdin.length} chars`);
971
979
  if (stdin && stdin.trim()) {
972
980
  hookInput = JSON.parse(stdin);
981
+ console.error(`[stop-validator] Parsed input keys: ${Object.keys(hookInput).join(', ') || 'none'}`);
973
982
  }
974
983
  } catch {
975
984
  hookInput = {};
@@ -993,6 +1002,11 @@ async function main(): Promise<void> {
993
1002
  const isCleanTree = modifiedFiles.length === 0;
994
1003
 
995
1004
  // Run all validations
1005
+ console.error('[stop-validator] Running validations...');
1006
+ console.error(`[stop-validator] Branch: ${currentBranch}, isMain: ${isMainBranch}`);
1007
+ console.error(`[stop-validator] Modified files: ${modifiedFiles.length}`);
1008
+ console.error(`[stop-validator] Source files: ${sourceFiles.length}`);
1009
+
996
1010
  const errors: ValidationError[] = [];
997
1011
 
998
1012
  // Validation order matters - most critical first
@@ -1036,6 +1050,11 @@ async function main(): Promise<void> {
1036
1050
  // OUTPUT RESULTS
1037
1051
  // ============================================================================
1038
1052
 
1053
+ console.error(`[stop-validator] Validation complete. Errors found: ${errors.length}`);
1054
+ if (errors.length > 0) {
1055
+ console.error(`[stop-validator] Error types: ${errors.map((e) => e.type).join(', ')}`);
1056
+ }
1057
+
1039
1058
  if (errors.length > 0) {
1040
1059
  let output = `
1041
1060
  ################################################################################
@@ -1073,9 +1092,10 @@ Before completing, ask yourself:
1073
1092
  Update CLAUDE.md with any learnings from this session.
1074
1093
  `;
1075
1094
 
1095
+ // IMPORTANT: For blocking, output to STDERR and exit with code 2
1076
1096
  const result: HookResult = { decision: 'block', reason: output.trim() };
1077
- console.log(JSON.stringify(result));
1078
- process.exit(0);
1097
+ console.error(JSON.stringify(result));
1098
+ process.exit(2); // Exit code 2 = block and show to Claude
1079
1099
  }
1080
1100
 
1081
1101
  // All validations passed
@@ -1,268 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Check Documentation Hook
4
-
5
- Verifies that all modified files are properly documented.
6
- Runs regex search across documentation to ensure file mentions.
7
-
8
- IGNORE PATTERNS:
9
- - .next/
10
- - node_modules/
11
- - dist/
12
- - build/
13
- - coverage/
14
- - .git/
15
- - *.lock
16
- - *.log
17
- """
18
-
19
- import json
20
- import sys
21
- import os
22
- import re
23
- from pathlib import Path
24
- from typing import List, Set, Tuple
25
-
26
- # Directories to ignore
27
- IGNORE_DIRS = {
28
- '.next',
29
- 'node_modules',
30
- 'dist',
31
- 'build',
32
- 'coverage',
33
- '.git',
34
- '__pycache__',
35
- '.turbo',
36
- '.cache',
37
- '.husky/_',
38
- '.claude', # System config
39
- 'packages' # Monorepo packages
40
- }
41
-
42
- # File patterns to ignore
43
- IGNORE_PATTERNS = [
44
- r'.*\.lock$',
45
- r'.*\.log$',
46
- r'.*\.map$',
47
- r'.*\.min\.js$',
48
- r'.*\.min\.css$',
49
- r'package-lock\.json$',
50
- r'bun\.lockb$',
51
- r'\.DS_Store$',
52
- r'Thumbs\.db$',
53
- ]
54
-
55
- # Documentation directories to search
56
- DOC_DIRS = [
57
- 'docs',
58
- '.claude/skills/codebase-knowledge/domains',
59
- '.claude/skills/docs-tracker',
60
- ]
61
-
62
- # Documentation file extensions
63
- DOC_EXTENSIONS = {'.md', '.mdx', '.txt', '.rst'}
64
-
65
-
66
- def should_ignore_file(file_path: str) -> bool:
67
- """Check if file should be ignored from documentation check"""
68
- path = Path(file_path)
69
-
70
- # Check if in ignored directory
71
- for part in path.parts:
72
- if part in IGNORE_DIRS:
73
- return True
74
-
75
- # Check if matches ignore pattern
76
- for pattern in IGNORE_PATTERNS:
77
- if re.match(pattern, file_path):
78
- return True
79
-
80
- # Ignore documentation files themselves
81
- if path.suffix in DOC_EXTENSIONS:
82
- return True
83
-
84
- # Ignore config files that don't need documentation
85
- if path.name in {'.gitignore', '.eslintrc.json', '.prettierrc', 'tsconfig.json'}:
86
- return True
87
-
88
- return False
89
-
90
-
91
- def get_modified_files(project_dir: str) -> List[str]:
92
- """Get list of modified files from git diff"""
93
- import subprocess
94
-
95
- try:
96
- # Get files changed compared to main branch
97
- result = subprocess.run(
98
- ['git', 'diff', '--name-only', 'main...HEAD'],
99
- cwd=project_dir,
100
- capture_output=True,
101
- text=True
102
- )
103
-
104
- if result.returncode != 0:
105
- # Fallback: get staged + unstaged changes
106
- result = subprocess.run(
107
- ['git', 'diff', '--name-only', 'HEAD'],
108
- cwd=project_dir,
109
- capture_output=True,
110
- text=True
111
- )
112
-
113
- files = [f.strip() for f in result.stdout.strip().split('\n') if f.strip()]
114
-
115
- # Also get staged files
116
- staged = subprocess.run(
117
- ['git', 'diff', '--name-only', '--cached'],
118
- cwd=project_dir,
119
- capture_output=True,
120
- text=True
121
- )
122
- staged_files = [f.strip() for f in staged.stdout.strip().split('\n') if f.strip()]
123
-
124
- # Combine and deduplicate
125
- all_files = list(set(files + staged_files))
126
- return [f for f in all_files if f and not should_ignore_file(f)]
127
-
128
- except Exception as e:
129
- print(f"Error getting modified files: {e}", file=sys.stderr)
130
- return []
131
-
132
-
133
- def search_in_docs(project_dir: str, file_path: str) -> List[str]:
134
- """Search for file mentions in documentation"""
135
- found_in = []
136
- file_name = Path(file_path).name
137
- file_stem = Path(file_path).stem
138
-
139
- # Patterns to search for
140
- patterns = [
141
- re.escape(file_path), # Full path
142
- re.escape(file_name), # Just filename
143
- re.escape(file_stem), # Filename without extension
144
- ]
145
-
146
- # Search in documentation directories
147
- for doc_dir in DOC_DIRS:
148
- doc_path = Path(project_dir) / doc_dir
149
- if not doc_path.exists():
150
- continue
151
-
152
- for doc_file in doc_path.rglob('*'):
153
- if doc_file.suffix not in DOC_EXTENSIONS:
154
- continue
155
- if not doc_file.is_file():
156
- continue
157
-
158
- try:
159
- content = doc_file.read_text(encoding='utf-8', errors='ignore')
160
- for pattern in patterns:
161
- if re.search(pattern, content, re.IGNORECASE):
162
- found_in.append(str(doc_file.relative_to(project_dir)))
163
- break
164
- except Exception:
165
- continue
166
-
167
- return found_in
168
-
169
-
170
- def check_documentation(project_dir: str) -> Tuple[List[str], List[str]]:
171
- """
172
- Check if modified files are documented.
173
-
174
- Returns:
175
- Tuple of (undocumented_files, documented_files)
176
- """
177
- modified_files = get_modified_files(project_dir)
178
- undocumented = []
179
- documented = []
180
-
181
- for file_path in modified_files:
182
- found_in = search_in_docs(project_dir, file_path)
183
- if found_in:
184
- documented.append((file_path, found_in))
185
- else:
186
- undocumented.append(file_path)
187
-
188
- return undocumented, documented
189
-
190
-
191
- def main():
192
- """Main entry point for the hook"""
193
- project_dir = os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())
194
-
195
- # Check if this is being called from a hook (stdin has JSON) or directly
196
- try:
197
- hook_input = json.load(sys.stdin)
198
- except (json.JSONDecodeError, Exception):
199
- hook_input = {}
200
-
201
- undocumented, documented = check_documentation(project_dir)
202
-
203
- if undocumented:
204
- # Build error message
205
- error_msg = f"""
206
- ================================================================================
207
- DOCUMENTATION CHECK FAILED
208
- ================================================================================
209
-
210
- The following {len(undocumented)} file(s) have been modified but are NOT mentioned
211
- in any documentation:
212
-
213
- """
214
- for f in undocumented:
215
- error_msg += f" - {f}\n"
216
-
217
- error_msg += """
218
- ================================================================================
219
- ACTION REQUIRED
220
- ================================================================================
221
-
222
- You MUST run the documenter agent to update documentation before completing:
223
-
224
- Task(subagent_type="documenter", prompt="Update documentation for modified files")
225
-
226
- The documenter will:
227
- 1. Detect all changed files via git diff
228
- 2. Update relevant domain files in .claude/skills/codebase-knowledge/domains/
229
- 3. Update docs/CHANGELOG.md (if exists)
230
- 4. Ensure all modified files are properly documented
231
-
232
- DOCUMENTATION IS MANDATORY. This check will BLOCK task completion until all
233
- modified source files are mentioned in the documentation.
234
-
235
- ================================================================================
236
- """
237
-
238
- # Return as blocking error
239
- result = {
240
- "continue": False,
241
- "error": error_msg.strip()
242
- }
243
- print(json.dumps(result))
244
- sys.exit(1)
245
-
246
- else:
247
- # All files documented
248
- success_msg = f"""
249
- ================================================================================
250
- DOCUMENTATION CHECK PASSED
251
- ================================================================================
252
-
253
- All {len(documented)} modified file(s) are properly documented.
254
-
255
- """
256
- for file_path, doc_locations in documented:
257
- success_msg += f" - {file_path} -> {', '.join(doc_locations)}\n"
258
-
259
- result = {
260
- "continue": True,
261
- "systemMessage": success_msg.strip()
262
- }
263
- print(json.dumps(result))
264
- sys.exit(0)
265
-
266
-
267
- if __name__ == '__main__':
268
- main()