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.
- package/README.md +3 -3
- package/dist/agents/qa-tester.d.ts +16 -0
- package/dist/agents/qa-tester.d.ts.map +1 -0
- package/dist/agents/qa-tester.js +367 -0
- package/dist/agents/qa-tester.js.map +1 -0
- package/dist/hooks/plugin-patterns/index.d.ts.map +1 -1
- package/dist/hooks/plugin-patterns/index.js +16 -3
- package/dist/hooks/plugin-patterns/index.js.map +1 -1
- package/dist/installer/index.d.ts +1 -1
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +21 -1
- package/dist/installer/index.js.map +1 -1
- package/dist/tools/lsp/servers.d.ts.map +1 -1
- package/dist/tools/lsp/servers.js +14 -1
- package/dist/tools/lsp/servers.js.map +1 -1
- package/package.json +1 -1
- package/scripts/install.sh +1 -1
- package/scripts/keyword-detector.mjs +209 -0
- package/scripts/persistent-mode.mjs +241 -0
- package/scripts/post-tool-verifier.mjs +217 -0
- package/scripts/pre-tool-enforcer.mjs +99 -0
|
@@ -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();
|