oh-my-claude-sisyphus 1.11.0 → 1.11.2

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,217 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PostToolUse Hook: Verification Reminder System (Node.js)
5
+ * Monitors tool execution and provides contextual guidance
6
+ * Cross-platform: Windows, macOS, Linux
7
+ */
8
+
9
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
10
+ import { join } from 'path';
11
+ import { homedir } from 'os';
12
+
13
+ // State file for session tracking
14
+ const STATE_FILE = join(homedir(), '.claude', '.session-stats.json');
15
+
16
+ // Ensure state directory exists
17
+ try {
18
+ const stateDir = join(homedir(), '.claude');
19
+ if (!existsSync(stateDir)) {
20
+ mkdirSync(stateDir, { recursive: true });
21
+ }
22
+ } catch {}
23
+
24
+ // Read all stdin
25
+ async function readStdin() {
26
+ const chunks = [];
27
+ for await (const chunk of process.stdin) {
28
+ chunks.push(chunk);
29
+ }
30
+ return Buffer.concat(chunks).toString('utf-8');
31
+ }
32
+
33
+ // Load session statistics
34
+ function loadStats() {
35
+ try {
36
+ if (existsSync(STATE_FILE)) {
37
+ return JSON.parse(readFileSync(STATE_FILE, 'utf-8'));
38
+ }
39
+ } catch {}
40
+ return { sessions: {} };
41
+ }
42
+
43
+ // Save session statistics
44
+ function saveStats(stats) {
45
+ try {
46
+ writeFileSync(STATE_FILE, JSON.stringify(stats, null, 2));
47
+ } catch {}
48
+ }
49
+
50
+ // Update stats for this session
51
+ function updateStats(toolName, sessionId) {
52
+ const stats = loadStats();
53
+
54
+ if (!stats.sessions[sessionId]) {
55
+ stats.sessions[sessionId] = {
56
+ tool_counts: {},
57
+ last_tool: '',
58
+ total_calls: 0,
59
+ started_at: Math.floor(Date.now() / 1000)
60
+ };
61
+ }
62
+
63
+ const session = stats.sessions[sessionId];
64
+ session.tool_counts[toolName] = (session.tool_counts[toolName] || 0) + 1;
65
+ session.last_tool = toolName;
66
+ session.total_calls = (session.total_calls || 0) + 1;
67
+ session.updated_at = Math.floor(Date.now() / 1000);
68
+
69
+ saveStats(stats);
70
+ return session.tool_counts[toolName];
71
+ }
72
+
73
+ // Detect failures in Bash output
74
+ function detectBashFailure(output) {
75
+ const errorPatterns = [
76
+ /error:/i,
77
+ /failed/i,
78
+ /cannot/i,
79
+ /permission denied/i,
80
+ /command not found/i,
81
+ /no such file/i,
82
+ /exit code: [1-9]/i,
83
+ /exit status [1-9]/i,
84
+ /fatal:/i,
85
+ /abort/i,
86
+ ];
87
+
88
+ return errorPatterns.some(pattern => pattern.test(output));
89
+ }
90
+
91
+ // Detect background operation
92
+ function detectBackgroundOperation(output) {
93
+ const bgPatterns = [
94
+ /started/i,
95
+ /running/i,
96
+ /background/i,
97
+ /async/i,
98
+ /task_id/i,
99
+ /spawned/i,
100
+ ];
101
+
102
+ return bgPatterns.some(pattern => pattern.test(output));
103
+ }
104
+
105
+ // Detect write failure
106
+ function detectWriteFailure(output) {
107
+ const errorPatterns = [
108
+ /error/i,
109
+ /failed/i,
110
+ /permission denied/i,
111
+ /read-only/i,
112
+ /not found/i,
113
+ ];
114
+
115
+ return errorPatterns.some(pattern => pattern.test(output));
116
+ }
117
+
118
+ // Generate contextual message
119
+ function generateMessage(toolName, toolOutput, sessionId, toolCount) {
120
+ let message = '';
121
+
122
+ switch (toolName) {
123
+ case 'Bash':
124
+ if (detectBashFailure(toolOutput)) {
125
+ message = 'Command failed. Please investigate the error and fix before continuing.';
126
+ } else if (detectBackgroundOperation(toolOutput)) {
127
+ message = 'Background operation detected. Remember to verify results before proceeding.';
128
+ }
129
+ break;
130
+
131
+ case 'Task':
132
+ if (detectWriteFailure(toolOutput)) {
133
+ message = 'Task delegation failed. Verify agent name and parameters.';
134
+ } else if (detectBackgroundOperation(toolOutput)) {
135
+ message = 'Background task launched. Use TaskOutput to check results when needed.';
136
+ } else if (toolCount > 5) {
137
+ message = `Multiple tasks delegated (${toolCount} total). Track their completion status.`;
138
+ }
139
+ break;
140
+
141
+ case 'Edit':
142
+ if (detectWriteFailure(toolOutput)) {
143
+ message = 'Edit operation failed. Verify file exists and content matches exactly.';
144
+ } else {
145
+ message = 'Code modified. Verify changes work as expected before marking complete.';
146
+ }
147
+ break;
148
+
149
+ case 'Write':
150
+ if (detectWriteFailure(toolOutput)) {
151
+ message = 'Write operation failed. Check file permissions and directory existence.';
152
+ } else {
153
+ message = 'File written. Test the changes to ensure they work correctly.';
154
+ }
155
+ break;
156
+
157
+ case 'TodoWrite':
158
+ if (/created|added/i.test(toolOutput)) {
159
+ message = 'Todo list updated. Proceed with next task on the list.';
160
+ } else if (/completed|done/i.test(toolOutput)) {
161
+ message = 'Task marked complete. Continue with remaining todos.';
162
+ } else if (/in_progress/i.test(toolOutput)) {
163
+ message = 'Task marked in progress. Focus on completing this task.';
164
+ }
165
+ break;
166
+
167
+ case 'Read':
168
+ if (toolCount > 10) {
169
+ message = `Extensive reading (${toolCount} files). Consider using Grep for pattern searches.`;
170
+ }
171
+ break;
172
+
173
+ case 'Grep':
174
+ if (/^0$|no matches/i.test(toolOutput)) {
175
+ message = 'No matches found. Verify pattern syntax or try broader search.';
176
+ }
177
+ break;
178
+
179
+ case 'Glob':
180
+ if (!toolOutput.trim() || /no files/i.test(toolOutput)) {
181
+ message = 'No files matched pattern. Verify glob syntax and directory.';
182
+ }
183
+ break;
184
+ }
185
+
186
+ return message;
187
+ }
188
+
189
+ async function main() {
190
+ try {
191
+ const input = await readStdin();
192
+ const data = JSON.parse(input);
193
+
194
+ const toolName = data.toolName || '';
195
+ const toolOutput = data.toolOutput || '';
196
+ const sessionId = data.sessionId || 'unknown';
197
+
198
+ // Update session statistics
199
+ const toolCount = updateStats(toolName, sessionId);
200
+
201
+ // Generate contextual message
202
+ const message = generateMessage(toolName, toolOutput, sessionId, toolCount);
203
+
204
+ // Build response
205
+ const response = { continue: true };
206
+ if (message) {
207
+ response.message = message;
208
+ }
209
+
210
+ console.log(JSON.stringify(response, null, 2));
211
+ } catch (error) {
212
+ // On error, always continue
213
+ console.log(JSON.stringify({ continue: true }));
214
+ }
215
+ }
216
+
217
+ main();
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PreToolUse Hook: Sisyphus Reminder Enforcer (Node.js)
5
+ * Injects contextual reminders before every tool execution
6
+ * Cross-platform: Windows, macOS, Linux
7
+ */
8
+
9
+ import { existsSync, readFileSync } from 'fs';
10
+ import { join } from 'path';
11
+
12
+ // Read all stdin
13
+ async function readStdin() {
14
+ const chunks = [];
15
+ for await (const chunk of process.stdin) {
16
+ chunks.push(chunk);
17
+ }
18
+ return Buffer.concat(chunks).toString('utf-8');
19
+ }
20
+
21
+ // Simple JSON field extraction
22
+ function extractJsonField(input, field, defaultValue = '') {
23
+ try {
24
+ const data = JSON.parse(input);
25
+ return data[field] ?? defaultValue;
26
+ } catch {
27
+ // Fallback regex extraction
28
+ const match = input.match(new RegExp(`"${field}"\\s*:\\s*"([^"]*)"`, 'i'));
29
+ return match ? match[1] : defaultValue;
30
+ }
31
+ }
32
+
33
+ // Get todo status from project
34
+ function getTodoStatus(directory) {
35
+ const todoFile = join(directory, '.sisyphus', 'todos.json');
36
+
37
+ if (!existsSync(todoFile)) {
38
+ return '';
39
+ }
40
+
41
+ try {
42
+ const content = readFileSync(todoFile, 'utf-8');
43
+ const data = JSON.parse(content);
44
+ const todos = data.todos || data;
45
+
46
+ if (!Array.isArray(todos)) {
47
+ return '';
48
+ }
49
+
50
+ const pending = todos.filter(t => t.status === 'pending').length;
51
+ const inProgress = todos.filter(t => t.status === 'in_progress').length;
52
+
53
+ if (pending + inProgress > 0) {
54
+ return `[${inProgress} active, ${pending} pending] `;
55
+ }
56
+ } catch {
57
+ // Ignore errors
58
+ }
59
+
60
+ return '';
61
+ }
62
+
63
+ // Generate contextual message based on tool type
64
+ function generateMessage(toolName, todoStatus) {
65
+ const messages = {
66
+ TodoWrite: `${todoStatus}Mark todos in_progress BEFORE starting, completed IMMEDIATELY after finishing.`,
67
+ Bash: `${todoStatus}Use parallel execution for independent tasks. Use run_in_background for long operations (npm install, builds, tests).`,
68
+ Task: `${todoStatus}Launch multiple agents in parallel when tasks are independent. Use run_in_background for long operations.`,
69
+ Edit: `${todoStatus}Verify changes work after editing. Test functionality before marking complete.`,
70
+ Write: `${todoStatus}Verify changes work after editing. Test functionality before marking complete.`,
71
+ Read: `${todoStatus}Read multiple files in parallel when possible for faster analysis.`,
72
+ Grep: `${todoStatus}Combine searches in parallel when investigating multiple patterns.`,
73
+ Glob: `${todoStatus}Combine searches in parallel when investigating multiple patterns.`,
74
+ };
75
+
76
+ return messages[toolName] || `${todoStatus}The boulder never stops. Continue until all tasks complete.`;
77
+ }
78
+
79
+ async function main() {
80
+ try {
81
+ const input = await readStdin();
82
+
83
+ const toolName = extractJsonField(input, 'toolName', 'unknown');
84
+ const directory = extractJsonField(input, 'directory', process.cwd());
85
+
86
+ const todoStatus = getTodoStatus(directory);
87
+ const message = generateMessage(toolName, todoStatus);
88
+
89
+ console.log(JSON.stringify({
90
+ continue: true,
91
+ message: message
92
+ }, null, 2));
93
+ } catch (error) {
94
+ // On error, always continue
95
+ console.log(JSON.stringify({ continue: true }));
96
+ }
97
+ }
98
+
99
+ main();