start-vibing 2.0.20 → 2.0.22

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.20",
3
+ "version": "2.0.22",
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")
@@ -63,11 +67,17 @@ function checkRuntime(cmd: string): boolean {
63
67
  }
64
68
  }
65
69
 
70
+ interface RuntimeResult {
71
+ exitCode: number;
72
+ output: string;
73
+ error?: string;
74
+ }
75
+
66
76
  function runWithRuntime(
67
77
  cmd: string,
68
78
  args: string[],
69
79
  input: string
70
- ): { success: boolean; output: string; error?: string } {
80
+ ): RuntimeResult {
71
81
  try {
72
82
  const result = spawnSync(cmd, args, {
73
83
  input,
@@ -79,13 +89,13 @@ function runWithRuntime(
79
89
  });
80
90
 
81
91
  return {
82
- success: result.status === 0,
92
+ exitCode: result.status ?? 1,
83
93
  output: result.stdout?.toString() || '',
84
94
  error: result.stderr?.toString() || undefined,
85
95
  };
86
96
  } catch (err) {
87
97
  return {
88
- success: false,
98
+ exitCode: 1,
89
99
  output: '',
90
100
  error: err instanceof Error ? err.message : 'Unknown error',
91
101
  };
@@ -94,20 +104,16 @@ function runWithRuntime(
94
104
 
95
105
  async function runHook(hookName: string, stdinData: string): Promise<void> {
96
106
  const tsPath = join(HOOKS_DIR, `${hookName}.ts`);
97
- const pyPath = join(HOOKS_DIR, `${hookName}.py`);
98
-
99
- // Runtime detection order - Python FIRST, then fallbacks
100
- 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' },
104
- { name: 'npx-tsx', cmd: 'npx tsx', ext: '.ts' },
107
+
108
+ // Runtime detection order - TypeScript ONLY (source of truth)
109
+ // Python files are deprecated and should be removed
110
+ const runtimes: Array<{ name: string; cmd: string }> = [
111
+ { name: 'bun', cmd: 'bun' },
112
+ { name: 'npx-tsx', cmd: 'npx tsx' },
105
113
  ];
106
114
 
107
115
  for (const runtime of runtimes) {
108
- const hookPath = runtime.ext === '.ts' ? tsPath : pyPath;
109
-
110
- if (!existsSync(hookPath)) {
116
+ if (!existsSync(tsPath)) {
111
117
  continue;
112
118
  }
113
119
 
@@ -115,22 +121,41 @@ async function runHook(hookName: string, stdinData: string): Promise<void> {
115
121
  continue;
116
122
  }
117
123
 
118
- const result = runWithRuntime(runtime.cmd, [hookPath], stdinData);
124
+ const result = runWithRuntime(runtime.cmd, [tsPath], stdinData);
119
125
 
120
- if (result.success || !result.error?.includes('not found')) {
121
- // Runtime worked (success or runtime-specific failure)
126
+ // Handle exit codes according to Claude Code hook specification:
127
+ // - Exit code 0: Success (stdout in transcript)
128
+ // - Exit code 2: Blocking error (stderr feeds back to Claude)
129
+ // - Other: Non-blocking error
130
+
131
+ if (result.exitCode === 0) {
132
+ // Success - output stdout
133
+ process.stdout.write(result.output);
134
+ process.exit(0);
135
+ } else if (result.exitCode === 2) {
136
+ // Blocking error - stderr goes to Claude for action
137
+ if (result.error) {
138
+ process.stderr.write(result.error);
139
+ }
140
+ process.exit(2);
141
+ } else {
142
+ // Non-blocking error or runtime not found
143
+ if (result.error?.includes('not found')) {
144
+ // Runtime not available, try next
145
+ continue;
146
+ }
147
+ // Hook failed but not blocking
122
148
  process.stdout.write(result.output);
123
- if (result.error && !result.success) {
149
+ if (result.error) {
124
150
  process.stderr.write(result.error);
125
151
  }
126
- process.exit(result.success ? 0 : 1);
152
+ process.exit(result.exitCode);
127
153
  }
128
- // Runtime not available, try next
129
154
  }
130
155
 
131
156
  // No runtime available - return safe default
132
157
  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');
158
+ console.error('[run-hook] Please install bun or Node.js (for npx tsx)');
134
159
  const safeDefault = JSON.stringify({
135
160
  decision: 'approve',
136
161
  continue: true,
@@ -936,6 +936,64 @@ interface HookResult {
936
936
  reason: string;
937
937
  }
938
938
 
939
+ /**
940
+ * Maps error types to the subagent that should be launched to fix them.
941
+ */
942
+ const ERROR_TO_SUBAGENT: Record<string, { agent: string; prompt: string }> = {
943
+ FEATURE_BRANCH_NOT_MERGED: {
944
+ agent: 'commit-manager',
945
+ prompt: 'Complete the git workflow: commit all changes, merge to main, sync with remote',
946
+ },
947
+ NOT_ON_MAIN_BRANCH: {
948
+ agent: 'commit-manager',
949
+ prompt: 'Merge current branch to main and checkout main',
950
+ },
951
+ DIRECT_MAIN_COMMIT_FORBIDDEN: {
952
+ agent: 'branch-manager',
953
+ prompt: 'Create a feature branch from the current changes on main',
954
+ },
955
+ GIT_TREE_NOT_CLEAN: {
956
+ agent: 'commit-manager',
957
+ prompt: 'Commit all pending changes with appropriate conventional commit message',
958
+ },
959
+ CLAUDE_MD_MISSING: {
960
+ agent: 'documenter',
961
+ prompt: 'Create CLAUDE.md with required sections: Last Change, 30s Overview, Stack, Architecture',
962
+ },
963
+ CLAUDE_MD_SIZE_EXCEEDED: {
964
+ agent: 'claude-md-compactor',
965
+ prompt: 'Compact CLAUDE.md to under 40k characters while preserving critical project knowledge',
966
+ },
967
+ CLAUDE_MD_TEMPLATE_MERGE_NEEDED: {
968
+ agent: 'documenter',
969
+ prompt: 'Merge existing CLAUDE.md with template in .claude/CLAUDE.template.md, then delete template',
970
+ },
971
+ CLAUDE_MD_MISSING_SECTIONS: {
972
+ agent: 'documenter',
973
+ prompt: 'Add missing required sections to CLAUDE.md',
974
+ },
975
+ CLAUDE_MD_LAST_CHANGE_EMPTY: {
976
+ agent: 'documenter',
977
+ prompt: 'Update the Last Change section in CLAUDE.md with current session info',
978
+ },
979
+ CLAUDE_MD_STACKED_CHANGES: {
980
+ agent: 'documenter',
981
+ prompt: 'Remove stacked Last Change sections in CLAUDE.md, keep only the latest',
982
+ },
983
+ CLAUDE_MD_NOT_UPDATED: {
984
+ agent: 'documenter',
985
+ prompt: 'Update CLAUDE.md Last Change section with current session summary',
986
+ },
987
+ SOURCE_FILES_NOT_DOCUMENTED: {
988
+ agent: 'documenter',
989
+ prompt: 'Update documentation for all modified source files',
990
+ },
991
+ DOMAIN_DOCUMENTATION_INCOMPLETE: {
992
+ agent: 'documenter',
993
+ prompt: 'Update domain documentation for all modified files in .claude/skills/codebase-knowledge/domains/',
994
+ },
995
+ };
996
+
939
997
  async function readStdinWithTimeout(timeoutMs: number): Promise<string> {
940
998
  return new Promise((resolve) => {
941
999
  const timeout = setTimeout(() => {
@@ -964,21 +1022,75 @@ async function readStdinWithTimeout(timeoutMs: number): Promise<string> {
964
1022
  });
965
1023
  }
966
1024
 
1025
+ /**
1026
+ * Run validations in priority order and return the FIRST error found.
1027
+ * This ensures Claude fixes one issue at a time in the correct sequence.
1028
+ */
1029
+ function runValidationsInOrder(
1030
+ currentBranch: string,
1031
+ modifiedFiles: string[],
1032
+ sourceFiles: string[],
1033
+ isMainBranch: boolean,
1034
+ isCleanTree: boolean
1035
+ ): ValidationError | null {
1036
+ // PRIORITY ORDER - most critical first, one at a time
1037
+ // Each validation returns early on first error
1038
+
1039
+ // 1. Branch validation - can't complete on feature branch
1040
+ const branchError = validateBranch(currentBranch, modifiedFiles);
1041
+ if (branchError) return branchError;
1042
+
1043
+ // 2. Git tree - must be clean (only check if on main)
1044
+ if (isMainBranch) {
1045
+ const treeError = validateGitTree(modifiedFiles);
1046
+ if (treeError) return treeError;
1047
+ }
1048
+
1049
+ // 3. CLAUDE.md must exist
1050
+ const claudeMdExistsError = validateClaudeMdExists();
1051
+ if (claudeMdExistsError) return claudeMdExistsError;
1052
+
1053
+ // 4. Check if template merge is needed
1054
+ const templateMergeError = validateClaudeMdTemplateMerge();
1055
+ if (templateMergeError) return templateMergeError;
1056
+
1057
+ // 5. CLAUDE.md size - must be under 40k
1058
+ const sizeError = validateClaudeMdSize();
1059
+ if (sizeError) return sizeError;
1060
+
1061
+ // 6. CLAUDE.md structure - must have required sections
1062
+ const structureError = validateClaudeMdStructure();
1063
+ if (structureError) return structureError;
1064
+
1065
+ // 7. Last Change must not be stacked
1066
+ const lastChangeError = validateClaudeMdLastChange();
1067
+ if (lastChangeError) return lastChangeError;
1068
+
1069
+ // 8. CLAUDE.md must be updated if files changed
1070
+ const updatedError = validateClaudeMdUpdated(modifiedFiles);
1071
+ if (updatedError) return updatedError;
1072
+
1073
+ // 9. Source files must be documented
1074
+ const docError = validateDocumentation(sourceFiles);
1075
+ if (docError) return docError;
1076
+
1077
+ // 10. Domain documentation must be complete
1078
+ const domainDocError = validateDomainDocumentation(modifiedFiles);
1079
+ if (domainDocError) return domainDocError;
1080
+
1081
+ return null; // All validations passed
1082
+ }
1083
+
967
1084
  async function main(): Promise<void> {
968
- // Debug logging - always output to stderr for visibility
969
- console.error('[stop-validator] ========================================');
1085
+ // Debug logging - writes to stderr (visible in claude debug mode)
970
1086
  console.error('[stop-validator] Starting validation...');
971
- console.error(`[stop-validator] CWD: ${process.cwd()}`);
972
1087
  console.error(`[stop-validator] PROJECT_DIR: ${PROJECT_DIR}`);
973
- console.error(`[stop-validator] CLAUDE_MD exists: ${existsSync(CLAUDE_MD_PATH)}`);
974
1088
 
975
1089
  let hookInput: HookInput = {};
976
1090
  try {
977
1091
  const stdin = await readStdinWithTimeout(1000);
978
- console.error(`[stop-validator] stdin received: ${stdin.length} chars`);
979
1092
  if (stdin && stdin.trim()) {
980
1093
  hookInput = JSON.parse(stdin);
981
- console.error(`[stop-validator] Parsed input keys: ${Object.keys(hookInput).join(', ') || 'none'}`);
982
1094
  }
983
1095
  } catch {
984
1096
  hookInput = {};
@@ -1001,101 +1113,54 @@ async function main(): Promise<void> {
1001
1113
  const isMainBranch = currentBranch === 'main' || currentBranch === 'master';
1002
1114
  const isCleanTree = modifiedFiles.length === 0;
1003
1115
 
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
-
1010
- const errors: ValidationError[] = [];
1011
-
1012
- // Validation order matters - most critical first
1013
- const branchError = validateBranch(currentBranch, modifiedFiles);
1014
- if (branchError) errors.push(branchError);
1116
+ console.error(`[stop-validator] Branch: ${currentBranch}, Modified: ${modifiedFiles.length}`);
1015
1117
 
1016
- // Only check these if we're close to completion (on main or clean tree)
1017
- if (isMainBranch || isCleanTree) {
1018
- const treeError = validateGitTree(modifiedFiles);
1019
- if (treeError) errors.push(treeError);
1020
- }
1021
-
1022
- const claudeMdExistsError = validateClaudeMdExists();
1023
- if (claudeMdExistsError) errors.push(claudeMdExistsError);
1024
-
1025
- if (!claudeMdExistsError) {
1026
- // Check if there's a template pending merge (from start-vibing install)
1027
- const templateMergeError = validateClaudeMdTemplateMerge();
1028
- if (templateMergeError) errors.push(templateMergeError);
1029
-
1030
- const sizeError = validateClaudeMdSize();
1031
- if (sizeError) errors.push(sizeError);
1032
-
1033
- const structureError = validateClaudeMdStructure();
1034
- if (structureError) errors.push(structureError);
1035
-
1036
- const lastChangeError = validateClaudeMdLastChange();
1037
- if (lastChangeError) errors.push(lastChangeError);
1038
-
1039
- const updatedError = validateClaudeMdUpdated(modifiedFiles);
1040
- if (updatedError) errors.push(updatedError);
1041
- }
1042
-
1043
- const docError = validateDocumentation(sourceFiles);
1044
- if (docError) errors.push(docError);
1045
-
1046
- const domainDocError = validateDomainDocumentation(modifiedFiles);
1047
- if (domainDocError) errors.push(domainDocError);
1118
+ // Run validations in order - get FIRST error only
1119
+ const error = runValidationsInOrder(
1120
+ currentBranch,
1121
+ modifiedFiles,
1122
+ sourceFiles,
1123
+ isMainBranch,
1124
+ isCleanTree
1125
+ );
1048
1126
 
1049
1127
  // ============================================================================
1050
- // OUTPUT RESULTS
1128
+ // OUTPUT RESULTS - ONE ERROR AT A TIME
1051
1129
  // ============================================================================
1052
1130
 
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
- }
1131
+ if (error) {
1132
+ const subagentInfo = ERROR_TO_SUBAGENT[error.type] || {
1133
+ agent: 'general-purpose',
1134
+ prompt: 'Fix the validation error',
1135
+ };
1057
1136
 
1058
- if (errors.length > 0) {
1059
- let output = `
1137
+ const output = `
1060
1138
  ################################################################################
1061
- # STOP VALIDATOR - TASK COMPLETION BLOCKED #
1139
+ # STOP VALIDATOR - BLOCKED #
1062
1140
  ################################################################################
1063
1141
 
1064
- ${errors.length} validation(s) failed. You MUST fix these before the task can complete.
1142
+ ERROR: ${error.type}
1065
1143
 
1066
- `;
1144
+ ${error.message}
1145
+
1146
+ ${error.action}
1067
1147
 
1068
- for (let i = 0; i < errors.length; i++) {
1069
- const err = errors[i];
1070
- output += `
1071
1148
  --------------------------------------------------------------------------------
1072
- ERROR ${i + 1}/${errors.length}: ${err.type}
1149
+ REQUIRED ACTION: Launch the following subagent to fix this issue
1073
1150
  --------------------------------------------------------------------------------
1074
1151
 
1075
- ${err.message}
1152
+ Task(subagent_type="${subagentInfo.agent}", prompt="${subagentInfo.prompt}")
1076
1153
 
1077
- ${err.action}
1078
- `;
1079
- }
1154
+ After fixing this issue, the stop hook will run again to check for remaining issues.
1080
1155
 
1081
- output += `
1082
- ################################################################################
1083
- # FIX ALL ERRORS ABOVE BEFORE TASK CAN COMPLETE #
1084
1156
  ################################################################################
1085
-
1086
- SYNTHESIS REMINDER:
1087
- Before completing, ask yourself:
1088
- - Did the user mention any preferences I should remember?
1089
- - Did I learn any patterns that should be documented?
1090
- - Were there any corrections I should add as rules?
1091
-
1092
- Update CLAUDE.md with any learnings from this session.
1093
1157
  `;
1094
1158
 
1095
- // IMPORTANT: For blocking, output to STDERR and exit with code 2
1096
- const result: HookResult = { decision: 'block', reason: output.trim() };
1097
- console.error(JSON.stringify(result));
1098
- process.exit(2); // Exit code 2 = block and show to Claude
1159
+ // Write block reason to stderr (Claude sees this)
1160
+ process.stderr.write(output);
1161
+
1162
+ // Exit with code 2 = blocking error, stderr goes to Claude
1163
+ process.exit(2);
1099
1164
  }
1100
1165
 
1101
1166
  // All validations passed