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 +1 -1
- package/template/.claude/hooks/run-hook.ts +49 -24
- package/template/.claude/hooks/stop-validator.ts +147 -82
- package/template/.claude/skills/hook-development/SKILL.md +320 -0
- package/template/.claude/hooks/check-documentation.py +0 -268
- package/template/.claude/hooks/stop-validator.py +0 -336
- package/template/.claude/hooks/user-prompt-submit.py +0 -826
package/package.json
CHANGED
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
* Universal Hook Runner
|
|
4
4
|
*
|
|
5
5
|
* Runs hooks with multiple runtime fallbacks:
|
|
6
|
-
* 1.
|
|
7
|
-
* 2.
|
|
8
|
-
* 3.
|
|
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
|
-
):
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
//
|
|
100
|
-
const runtimes: Array<{ name: string; cmd: string
|
|
101
|
-
{ name: '
|
|
102
|
-
{ name: '
|
|
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
|
-
|
|
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, [
|
|
124
|
+
const result = runWithRuntime(runtime.cmd, [tsPath], stdinData);
|
|
119
125
|
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
149
|
+
if (result.error) {
|
|
124
150
|
process.stderr.write(result.error);
|
|
125
151
|
}
|
|
126
|
-
process.exit(result.
|
|
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
|
|
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 -
|
|
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
|
-
|
|
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
|
-
//
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
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
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
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
|
-
|
|
1059
|
-
let output = `
|
|
1137
|
+
const output = `
|
|
1060
1138
|
################################################################################
|
|
1061
|
-
# STOP VALIDATOR -
|
|
1139
|
+
# STOP VALIDATOR - BLOCKED #
|
|
1062
1140
|
################################################################################
|
|
1063
1141
|
|
|
1064
|
-
${
|
|
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
|
-
|
|
1149
|
+
REQUIRED ACTION: Launch the following subagent to fix this issue
|
|
1073
1150
|
--------------------------------------------------------------------------------
|
|
1074
1151
|
|
|
1075
|
-
${
|
|
1152
|
+
Task(subagent_type="${subagentInfo.agent}", prompt="${subagentInfo.prompt}")
|
|
1076
1153
|
|
|
1077
|
-
|
|
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
|
-
//
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
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
|