gm-cc 2.0.74 → 2.0.76
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +26 -0
- package/cli.js +217 -90
- package/hooks/prompt-submit-hook.js +2 -2
- package/hooks/session-start-hook.js +2 -2
- package/package.json +8 -1
- package/scripts/postinstall.js +35 -72
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"name": "AnEntrypoint"
|
|
5
5
|
},
|
|
6
6
|
"description": "State machine agent with hooks, skills, and automated git enforcement",
|
|
7
|
-
"version": "2.0.
|
|
7
|
+
"version": "2.0.76",
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "State machine agent with hooks, skills, and automated git enforcement"
|
|
10
10
|
},
|
package/README.md
CHANGED
|
@@ -50,6 +50,32 @@ After running `bun x gm-cc@latest`, your project will have:
|
|
|
50
50
|
|
|
51
51
|
Each hook runs automatically at the appropriate session event. No manual trigger needed.
|
|
52
52
|
|
|
53
|
+
### Project Provisioning (Explicit Installation)
|
|
54
|
+
|
|
55
|
+
To explicitly add all gm-cc hooks, agents, and skills to an existing project:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
bun x gm-cc@latest -- -p
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Or with a global installation:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
gm-cc -p
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This creates a project-local `.claude/settings.json` that configures hooks to use `${CLAUDE_PROJECT_DIR}` for project-specific environments. Useful for:
|
|
68
|
+
- Explicitly provisioning gm-cc to an existing project
|
|
69
|
+
- Overriding global plugin settings for a specific project
|
|
70
|
+
- Team workflows requiring consistent project-level configuration
|
|
71
|
+
|
|
72
|
+
The `-p` flag copies:
|
|
73
|
+
- All hooks to `.claude/hooks/`
|
|
74
|
+
- All agents to `.claude/agents/`
|
|
75
|
+
- All skills to `.claude/skills/`
|
|
76
|
+
- MCP configuration to `.claude/.mcp.json`
|
|
77
|
+
- Project-specific hook settings to `.claude/settings.json`
|
|
78
|
+
|
|
53
79
|
## File Installation (Manual Setup)
|
|
54
80
|
|
|
55
81
|
If you prefer manual file management, clone the repository and copy files directly:
|
package/cli.js
CHANGED
|
@@ -3,100 +3,227 @@ const fs = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
fs.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const isProvision = args.includes('-p') || args.includes('--provision');
|
|
8
|
+
|
|
9
|
+
if (isProvision) {
|
|
10
|
+
provisionProject();
|
|
11
|
+
} else {
|
|
12
|
+
installGlobally();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function provisionProject() {
|
|
16
|
+
const projectDir = process.cwd();
|
|
17
|
+
const claudeDir = path.join(projectDir, '.claude');
|
|
18
|
+
const srcDir = __dirname;
|
|
19
|
+
|
|
20
|
+
console.log('Provisioning gm-cc to current project...');
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const filesToCopy = [
|
|
24
|
+
'agents',
|
|
25
|
+
'hooks',
|
|
26
|
+
'skills',
|
|
27
|
+
'.mcp.json'
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
function copyRecursive(src, dst) {
|
|
31
|
+
if (!fs.existsSync(src)) return;
|
|
32
|
+
if (fs.statSync(src).isDirectory()) {
|
|
33
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
34
|
+
fs.readdirSync(src).forEach(f => copyRecursive(path.join(src, f), path.join(dst, f)));
|
|
35
|
+
} else {
|
|
36
|
+
fs.copyFileSync(src, dst);
|
|
37
|
+
}
|
|
36
38
|
}
|
|
37
|
-
}
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
filesToCopy.forEach(name => copyRecursive(path.join(srcDir, name), path.join(claudeDir, name)));
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// Update .gitignore to exclude .gm-stop-verified
|
|
43
|
+
const gitignorePath = path.join(projectDir, '.gitignore');
|
|
44
|
+
const gitignoreEntry = '.gm-stop-verified';
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
46
|
+
let gitignoreContent = '';
|
|
47
|
+
if (fs.existsSync(gitignorePath)) {
|
|
48
|
+
gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!gitignoreContent.includes(gitignoreEntry)) {
|
|
52
|
+
if (gitignoreContent && !gitignoreContent.endsWith('\n')) {
|
|
53
|
+
gitignoreContent += '\n';
|
|
54
|
+
}
|
|
55
|
+
gitignoreContent += gitignoreEntry + '\n';
|
|
56
|
+
fs.writeFileSync(gitignorePath, gitignoreContent, 'utf-8');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Generate project-specific settings.json with hook configuration
|
|
60
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
61
|
+
let settings = {};
|
|
62
|
+
if (fs.existsSync(settingsPath)) {
|
|
63
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); } catch (e) {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Configure hooks to use project-local paths with ${CLAUDE_PROJECT_DIR}
|
|
67
|
+
if (!settings.hooks) settings.hooks = {};
|
|
68
|
+
settings.hooks.PreToolUse = [{
|
|
69
|
+
matcher: '*',
|
|
70
|
+
hooks: [{
|
|
71
|
+
type: 'command',
|
|
72
|
+
command: 'node ${CLAUDE_PROJECT_DIR}/.claude/hooks/pre-tool-use-hook.js',
|
|
73
|
+
timeout: 3600
|
|
74
|
+
}]
|
|
75
|
+
}];
|
|
76
|
+
settings.hooks.SessionStart = [{
|
|
77
|
+
matcher: '*',
|
|
78
|
+
hooks: [{
|
|
79
|
+
type: 'command',
|
|
80
|
+
command: 'node ${CLAUDE_PROJECT_DIR}/.claude/hooks/session-start-hook.js',
|
|
81
|
+
timeout: 10000
|
|
82
|
+
}]
|
|
83
|
+
}];
|
|
84
|
+
settings.hooks.UserPromptSubmit = [{
|
|
85
|
+
matcher: '*',
|
|
86
|
+
hooks: [{
|
|
87
|
+
type: 'command',
|
|
88
|
+
command: 'node ${CLAUDE_PROJECT_DIR}/.claude/hooks/prompt-submit-hook.js',
|
|
89
|
+
timeout: 3600
|
|
90
|
+
}]
|
|
91
|
+
}];
|
|
92
|
+
settings.hooks.Stop = [{
|
|
93
|
+
matcher: '*',
|
|
94
|
+
hooks: [
|
|
95
|
+
{
|
|
96
|
+
type: 'command',
|
|
97
|
+
command: 'node ${CLAUDE_PROJECT_DIR}/.claude/hooks/stop-hook.js',
|
|
98
|
+
timeout: 300000
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
type: 'command',
|
|
102
|
+
command: 'node ${CLAUDE_PROJECT_DIR}/.claude/hooks/stop-hook-git.js',
|
|
103
|
+
timeout: 60000
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
}];
|
|
107
|
+
|
|
108
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
109
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
110
|
+
|
|
111
|
+
console.log('✓ Hooks copied to .claude/hooks/');
|
|
112
|
+
console.log('✓ Agents copied to .claude/agents/');
|
|
113
|
+
console.log('✓ Skills copied to .claude/skills/');
|
|
114
|
+
console.log('✓ MCP configuration written to .claude/.mcp.json');
|
|
115
|
+
console.log('✓ Hook configuration written to .claude/settings.json');
|
|
116
|
+
console.log('✓ .gitignore updated');
|
|
117
|
+
console.log('');
|
|
118
|
+
console.log('Provisioning complete! Your project now has:');
|
|
119
|
+
console.log(' • All gm-cc hooks in .claude/hooks/');
|
|
120
|
+
console.log(' • All agents in .claude/agents/');
|
|
121
|
+
console.log(' • All skills in .claude/skills/');
|
|
122
|
+
console.log(' • Hook configuration in .claude/settings.json');
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log('Restart Claude Code to activate the hooks.');
|
|
125
|
+
} catch (e) {
|
|
126
|
+
console.error('Provisioning failed:', e.message);
|
|
127
|
+
process.exit(1);
|
|
68
128
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function installGlobally() {
|
|
132
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
133
|
+
const claudeDir = path.join(homeDir, '.claude');
|
|
134
|
+
const pluginsDir = path.join(claudeDir, 'plugins');
|
|
135
|
+
const destDir = path.join(pluginsDir, 'gm-cc');
|
|
136
|
+
|
|
137
|
+
const srcDir = __dirname;
|
|
138
|
+
const isUpgrade = fs.existsSync(destDir);
|
|
139
|
+
|
|
140
|
+
console.log(isUpgrade ? 'Upgrading gm-cc plugin...' : 'Installing gm-cc plugin...');
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
144
|
+
|
|
145
|
+
const filesToCopy = [
|
|
146
|
+
'agents',
|
|
147
|
+
'hooks',
|
|
148
|
+
'skills',
|
|
149
|
+
'.mcp.json',
|
|
150
|
+
'.claude-plugin',
|
|
151
|
+
'README.md',
|
|
152
|
+
'CLAUDE.md'
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
function copyRecursive(src, dst) {
|
|
156
|
+
if (!fs.existsSync(src)) return;
|
|
157
|
+
if (fs.statSync(src).isDirectory()) {
|
|
158
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
159
|
+
fs.readdirSync(src).forEach(f => copyRecursive(path.join(src, f), path.join(dst, f)));
|
|
160
|
+
} else {
|
|
161
|
+
fs.copyFileSync(src, dst);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
filesToCopy.forEach(name => copyRecursive(path.join(srcDir, name), path.join(destDir, name)));
|
|
166
|
+
|
|
167
|
+
// Remove stale root-level plugin.json (moved to .claude-plugin/plugin.json)
|
|
168
|
+
const stalePluginJson = path.join(destDir, 'plugin.json');
|
|
169
|
+
if (fs.existsSync(stalePluginJson)) fs.unlinkSync(stalePluginJson);
|
|
170
|
+
|
|
171
|
+
// Register in settings.json (enabledPlugins only, no hook injection)
|
|
172
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
173
|
+
let settings = {};
|
|
174
|
+
if (fs.existsSync(settingsPath)) {
|
|
175
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); } catch (e) {}
|
|
176
|
+
}
|
|
177
|
+
if (!settings.enabledPlugins) settings.enabledPlugins = {};
|
|
178
|
+
settings.enabledPlugins['gm@gm-cc'] = true;
|
|
179
|
+
// Remove stale hook entries (handled by plugin hooks.json)
|
|
180
|
+
if (settings.hooks) delete settings.hooks;
|
|
181
|
+
// Register marketplace so Claude Code resolves gm@gm-cc locally
|
|
182
|
+
if (!settings.extraKnownMarketplaces) settings.extraKnownMarketplaces = {};
|
|
183
|
+
settings.extraKnownMarketplaces['gm-cc'] = { source: { source: 'directory', path: destDir } };
|
|
184
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
185
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
186
|
+
console.log('✓ Plugin registered in ~/.claude/settings.json');
|
|
187
|
+
|
|
188
|
+
// Write installed_plugins.json so Claude Code loads from local cache
|
|
189
|
+
const pluginVersion = require('./package.json').version;
|
|
190
|
+
const installedPluginsPath = path.join(pluginsDir, 'installed_plugins.json');
|
|
191
|
+
let installedPlugins = { version: 2, plugins: {} };
|
|
192
|
+
if (fs.existsSync(installedPluginsPath)) {
|
|
193
|
+
try { installedPlugins = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf-8')); } catch (e) {}
|
|
194
|
+
}
|
|
195
|
+
if (!installedPlugins.plugins || Array.isArray(installedPlugins.plugins)) installedPlugins.plugins = {};
|
|
196
|
+
const now = new Date().toISOString();
|
|
197
|
+
const existing = Array.isArray(installedPlugins.plugins['gm@gm-cc']) ? installedPlugins.plugins['gm@gm-cc'][0] : null;
|
|
198
|
+
// Also write cache dir so Claude Code finds it without network fetch
|
|
199
|
+
const cacheDir = path.join(pluginsDir, 'cache', 'gm-cc', 'gm', pluginVersion);
|
|
200
|
+
const filesToCache = ['agents', 'hooks', 'skills', '.mcp.json', '.claude-plugin', 'README.md', 'CLAUDE.md'];
|
|
201
|
+
function copyRecursiveCache(src, dst) {
|
|
202
|
+
if (!fs.existsSync(src)) return;
|
|
203
|
+
if (fs.statSync(src).isDirectory()) {
|
|
204
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
205
|
+
fs.readdirSync(src).forEach(f => copyRecursiveCache(path.join(src, f), path.join(dst, f)));
|
|
206
|
+
} else { fs.copyFileSync(src, dst); }
|
|
207
|
+
}
|
|
208
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
209
|
+
filesToCache.forEach(name => copyRecursiveCache(path.join(destDir, name), path.join(cacheDir, name)));
|
|
210
|
+
// Remove stale root-level plugin.json from cache (moved to .claude-plugin/plugin.json)
|
|
211
|
+
const staleCachePluginJson = path.join(cacheDir, 'plugin.json');
|
|
212
|
+
if (fs.existsSync(staleCachePluginJson)) fs.unlinkSync(staleCachePluginJson);
|
|
213
|
+
installedPlugins.plugins['gm@gm-cc'] = [{
|
|
214
|
+
scope: 'user',
|
|
215
|
+
installPath: cacheDir,
|
|
216
|
+
version: pluginVersion,
|
|
217
|
+
installedAt: existing?.installedAt || now,
|
|
218
|
+
lastUpdated: now
|
|
219
|
+
}];
|
|
220
|
+
fs.writeFileSync(installedPluginsPath, JSON.stringify(installedPlugins, null, 2), 'utf-8');
|
|
221
|
+
console.log('✓ Plugin registered in installed_plugins.json');
|
|
222
|
+
|
|
223
|
+
console.log(`✓ gm-cc ${isUpgrade ? 'upgraded' : 'installed'} to ${destDir}`);
|
|
224
|
+
console.log('Restart Claude Code to activate the gm plugin.');
|
|
225
|
+
} catch (e) {
|
|
226
|
+
console.error('Installation failed:', e.message);
|
|
227
|
+
process.exit(1);
|
|
81
228
|
}
|
|
82
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
83
|
-
filesToCache.forEach(name => copyRecursiveCache(path.join(destDir, name), path.join(cacheDir, name)));
|
|
84
|
-
// Remove stale root-level plugin.json from cache (moved to .claude-plugin/plugin.json)
|
|
85
|
-
const staleCachePluginJson = path.join(cacheDir, 'plugin.json');
|
|
86
|
-
if (fs.existsSync(staleCachePluginJson)) fs.unlinkSync(staleCachePluginJson);
|
|
87
|
-
installedPlugins.plugins['gm@gm-cc'] = [{
|
|
88
|
-
scope: 'user',
|
|
89
|
-
installPath: cacheDir,
|
|
90
|
-
version: pluginVersion,
|
|
91
|
-
installedAt: existing?.installedAt || now,
|
|
92
|
-
lastUpdated: now
|
|
93
|
-
}];
|
|
94
|
-
fs.writeFileSync(installedPluginsPath, JSON.stringify(installedPlugins, null, 2), 'utf-8');
|
|
95
|
-
console.log('✓ Plugin registered in installed_plugins.json');
|
|
96
|
-
|
|
97
|
-
console.log(`✓ gm-cc ${isUpgrade ? 'upgraded' : 'installed'} to ${destDir}`);
|
|
98
|
-
console.log('Restart Claude Code to activate the gm plugin.');
|
|
99
|
-
} catch (e) {
|
|
100
|
-
console.error('Installation failed:', e.message);
|
|
101
|
-
process.exit(1);
|
|
102
229
|
}
|
|
@@ -31,7 +31,7 @@ const runCodeSearch = (query, cwd) => {
|
|
|
31
31
|
const escaped = query.replace(/"/g, '\\"').substring(0, 200);
|
|
32
32
|
let out;
|
|
33
33
|
try {
|
|
34
|
-
out = execSync(`bun x codebasesearch
|
|
34
|
+
out = execSync(`bun x codebasesearch "${escaped}"`, {
|
|
35
35
|
encoding: 'utf-8',
|
|
36
36
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
37
37
|
cwd,
|
|
@@ -40,7 +40,7 @@ const runCodeSearch = (query, cwd) => {
|
|
|
40
40
|
});
|
|
41
41
|
} catch (bunErr) {
|
|
42
42
|
if (bunErr.killed) return '';
|
|
43
|
-
out = execSync(`npx -y codebasesearch
|
|
43
|
+
out = execSync(`npx -y codebasesearch "${escaped}"`, {
|
|
44
44
|
encoding: 'utf-8',
|
|
45
45
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
46
46
|
cwd,
|
|
@@ -71,7 +71,7 @@ When exploring unfamiliar code, finding similar patterns, understanding integrat
|
|
|
71
71
|
try {
|
|
72
72
|
let thornOutput;
|
|
73
73
|
try {
|
|
74
|
-
thornOutput = execSync(`bun x mcp-thorns
|
|
74
|
+
thornOutput = execSync(`bun x mcp-thorns`, {
|
|
75
75
|
encoding: 'utf-8',
|
|
76
76
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
77
77
|
cwd: projectDir,
|
|
@@ -83,7 +83,7 @@ When exploring unfamiliar code, finding similar patterns, understanding integrat
|
|
|
83
83
|
thornOutput = '=== mcp-thorns ===\nSkipped (3min timeout)';
|
|
84
84
|
} else {
|
|
85
85
|
try {
|
|
86
|
-
thornOutput = execSync(`npx -y mcp-thorns
|
|
86
|
+
thornOutput = execSync(`npx -y mcp-thorns`, {
|
|
87
87
|
encoding: 'utf-8',
|
|
88
88
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
89
89
|
cwd: projectDir,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-cc",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.76",
|
|
4
4
|
"description": "State machine agent with hooks, skills, and automated git enforcement",
|
|
5
5
|
"author": "AnEntrypoint",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,5 +49,12 @@
|
|
|
49
49
|
],
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@anthropic-ai/claude-code": "*"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"mcp-thorns": "latest",
|
|
55
|
+
"codebasesearch": "latest"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"postinstall": "node scripts/postinstall.js"
|
|
52
59
|
}
|
|
53
60
|
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -2,38 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Postinstall script for gm-cc
|
|
8
|
-
* Implements Mode 1: Standalone .claude/ directory installation
|
|
9
|
-
*
|
|
10
|
-
* When installed via npm in a project:
|
|
11
|
-
* - Copies agents/, hooks/, .mcp.json to project's .claude/
|
|
12
|
-
* - Updates .gitignore with .gm-stop-verified
|
|
13
|
-
* - Runs silently, never breaks npm install
|
|
14
|
-
* - Safe to run multiple times (idempotent)
|
|
15
|
-
*/
|
|
5
|
+
const { execSync } = require('child_process');
|
|
16
6
|
|
|
17
7
|
function isInsideNodeModules() {
|
|
18
|
-
// Check if __dirname contains /node_modules/ in its path
|
|
19
|
-
// Example: /project/node_modules/gm-cc/scripts
|
|
20
8
|
return __dirname.includes(path.sep + 'node_modules' + path.sep);
|
|
21
9
|
}
|
|
22
10
|
|
|
23
11
|
function getProjectRoot() {
|
|
24
|
-
|
|
25
|
-
// Navigate to /project
|
|
26
|
-
if (!isInsideNodeModules()) {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Find the node_modules parent (project root)
|
|
12
|
+
if (!isInsideNodeModules()) return null;
|
|
31
13
|
let current = __dirname;
|
|
32
|
-
while (current !== path.dirname(current)) {
|
|
14
|
+
while (current !== path.dirname(current)) {
|
|
33
15
|
current = path.dirname(current);
|
|
34
|
-
const parent = path.dirname(current);
|
|
35
16
|
if (path.basename(current) === 'node_modules') {
|
|
36
|
-
return
|
|
17
|
+
return path.dirname(current);
|
|
37
18
|
}
|
|
38
19
|
}
|
|
39
20
|
return null;
|
|
@@ -43,39 +24,26 @@ function safeCopyFile(src, dst) {
|
|
|
43
24
|
try {
|
|
44
25
|
const content = fs.readFileSync(src, 'utf-8');
|
|
45
26
|
const dstDir = path.dirname(dst);
|
|
46
|
-
if (!fs.existsSync(dstDir)) {
|
|
47
|
-
fs.mkdirSync(dstDir, { recursive: true });
|
|
48
|
-
}
|
|
27
|
+
if (!fs.existsSync(dstDir)) fs.mkdirSync(dstDir, { recursive: true });
|
|
49
28
|
fs.writeFileSync(dst, content, 'utf-8');
|
|
50
29
|
return true;
|
|
51
30
|
} catch (err) {
|
|
52
|
-
// Silently skip errors
|
|
53
31
|
return false;
|
|
54
32
|
}
|
|
55
33
|
}
|
|
56
34
|
|
|
57
35
|
function safeCopyDirectory(src, dst) {
|
|
58
36
|
try {
|
|
59
|
-
if (!fs.existsSync(src))
|
|
60
|
-
return false; // Source doesn't exist, skip
|
|
61
|
-
}
|
|
62
|
-
|
|
37
|
+
if (!fs.existsSync(src)) return false;
|
|
63
38
|
fs.mkdirSync(dst, { recursive: true });
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
entries.forEach(entry => {
|
|
39
|
+
fs.readdirSync(src, { withFileTypes: true }).forEach(entry => {
|
|
67
40
|
const srcPath = path.join(src, entry.name);
|
|
68
41
|
const dstPath = path.join(dst, entry.name);
|
|
69
|
-
|
|
70
|
-
if (entry.
|
|
71
|
-
safeCopyDirectory(srcPath, dstPath);
|
|
72
|
-
} else if (entry.isFile()) {
|
|
73
|
-
safeCopyFile(srcPath, dstPath);
|
|
74
|
-
}
|
|
42
|
+
if (entry.isDirectory()) safeCopyDirectory(srcPath, dstPath);
|
|
43
|
+
else if (entry.isFile()) safeCopyFile(srcPath, dstPath);
|
|
75
44
|
});
|
|
76
45
|
return true;
|
|
77
46
|
} catch (err) {
|
|
78
|
-
// Silently skip errors
|
|
79
47
|
return false;
|
|
80
48
|
}
|
|
81
49
|
}
|
|
@@ -84,45 +52,22 @@ function updateGitignore(projectRoot) {
|
|
|
84
52
|
try {
|
|
85
53
|
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
86
54
|
const entry = '.gm-stop-verified';
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Check if entry already exists
|
|
95
|
-
if (content.includes(entry)) {
|
|
96
|
-
return true; // Already there
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Append entry
|
|
100
|
-
if (content && !content.endsWith('\n')) {
|
|
101
|
-
content += '\n';
|
|
102
|
-
}
|
|
103
|
-
content += entry + '\n';
|
|
104
|
-
|
|
105
|
-
fs.writeFileSync(gitignorePath, content, 'utf-8');
|
|
55
|
+
let content = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf-8') : '';
|
|
56
|
+
if (content.includes(entry)) return true;
|
|
57
|
+
if (content && !content.endsWith('\n')) content += '\n';
|
|
58
|
+
fs.writeFileSync(gitignorePath, content + entry + '\n', 'utf-8');
|
|
106
59
|
return true;
|
|
107
60
|
} catch (err) {
|
|
108
|
-
// Silently skip errors
|
|
109
61
|
return false;
|
|
110
62
|
}
|
|
111
63
|
}
|
|
112
64
|
|
|
113
65
|
function install() {
|
|
114
|
-
|
|
115
|
-
if (!isInsideNodeModules()) {
|
|
116
|
-
return; // Silent exit
|
|
117
|
-
}
|
|
118
|
-
|
|
66
|
+
if (!isInsideNodeModules()) return;
|
|
119
67
|
const projectRoot = getProjectRoot();
|
|
120
|
-
if (!projectRoot)
|
|
121
|
-
return; // Silent exit
|
|
122
|
-
}
|
|
123
|
-
|
|
68
|
+
if (!projectRoot) return;
|
|
124
69
|
const claudeDir = path.join(projectRoot, '.claude');
|
|
125
|
-
const sourceDir = __dirname.replace(/[
|
|
70
|
+
const sourceDir = __dirname.replace(/[/\\]scripts$/, '');
|
|
126
71
|
|
|
127
72
|
// Copy files
|
|
128
73
|
safeCopyDirectory(path.join(sourceDir, 'agents'), path.join(claudeDir, 'agents'));
|
|
@@ -131,8 +76,26 @@ function install() {
|
|
|
131
76
|
|
|
132
77
|
// Update .gitignore
|
|
133
78
|
updateGitignore(projectRoot);
|
|
134
|
-
|
|
79
|
+
|
|
80
|
+
// Warm bun x cache for packages used by hooks
|
|
81
|
+
warmBunCache();
|
|
82
|
+
|
|
135
83
|
// Silent success
|
|
136
84
|
}
|
|
137
85
|
|
|
86
|
+
function warmBunCache() {
|
|
87
|
+
const packages = ['mcp-thorns@latest', 'codebasesearch@latest'];
|
|
88
|
+
for (const pkg of packages) {
|
|
89
|
+
try {
|
|
90
|
+
execSync(`bun x ${pkg} --version`, {
|
|
91
|
+
encoding: 'utf-8',
|
|
92
|
+
stdio: 'pipe',
|
|
93
|
+
timeout: 60000
|
|
94
|
+
});
|
|
95
|
+
} catch (e) {
|
|
96
|
+
// Silent - cache warming is best-effort
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
138
101
|
install();
|