@jaggerxtrm/specialists 2.1.14 → 2.1.15
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/bin/install.js +7 -198
- package/hooks/beads-commit-gate.mjs +55 -0
- package/hooks/beads-edit-gate.mjs +47 -0
- package/hooks/beads-stop-gate.mjs +50 -0
- package/hooks/specialists-main-guard.mjs +63 -0
- package/package.json +3 -2
package/bin/install.js
CHANGED
|
@@ -18,6 +18,7 @@ const GITHUB_PKG = '@jaggerxtrm/specialists';
|
|
|
18
18
|
|
|
19
19
|
// Bundled specialists dir — resolved relative to this file (bin/../specialists/)
|
|
20
20
|
const BUNDLED_SPECIALISTS_DIR = new URL('../specialists', import.meta.url).pathname;
|
|
21
|
+
const BUNDLED_HOOKS_DIR = new URL('../hooks', import.meta.url).pathname;
|
|
21
22
|
|
|
22
23
|
// ── ANSI helpers ──────────────────────────────────────────────────────────────
|
|
23
24
|
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
@@ -81,40 +82,6 @@ function registerMCP() {
|
|
|
81
82
|
|
|
82
83
|
// ── Hook installation ─────────────────────────────────────────────────────────
|
|
83
84
|
|
|
84
|
-
const HOOK_SCRIPT = `#!/usr/bin/env node
|
|
85
|
-
// specialists — Claude Code PreToolUse hook
|
|
86
|
-
// Blocks writes and git commit/push on main/master branch.
|
|
87
|
-
// Exit 0: allow | Exit 2: block (message shown to user)
|
|
88
|
-
//
|
|
89
|
-
// Installed by: npx --package=@jaggerxtrm/specialists install
|
|
90
|
-
|
|
91
|
-
import { execSync } from 'node:child_process';
|
|
92
|
-
import { readFileSync } from 'node:fs';
|
|
93
|
-
|
|
94
|
-
let branch = '';
|
|
95
|
-
try {
|
|
96
|
-
branch = execSync('git branch --show-current', {
|
|
97
|
-
encoding: 'utf8',
|
|
98
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
99
|
-
}).trim();
|
|
100
|
-
} catch {}
|
|
101
|
-
|
|
102
|
-
if (!branch || (branch !== 'main' && branch !== 'master')) {
|
|
103
|
-
process.exit(0);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
let input;
|
|
107
|
-
try {
|
|
108
|
-
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
109
|
-
} catch {
|
|
110
|
-
process.exit(0);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const tool = input.tool_name ?? '';
|
|
114
|
-
const blockMsg =
|
|
115
|
-
\`⛔ Direct edits on '\${branch}' are not allowed.\\n\` +
|
|
116
|
-
\`Create a feature branch first: git checkout -b feature/<name>\`;
|
|
117
|
-
|
|
118
85
|
const WRITE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
|
|
119
86
|
|
|
120
87
|
if (WRITE_TOOLS.has(tool)) {
|
|
@@ -143,164 +110,6 @@ const BEADS_EDIT_GATE_FILE = join(HOOKS_DIR, 'beads-edit-gate.mjs');
|
|
|
143
110
|
const BEADS_COMMIT_GATE_FILE = join(HOOKS_DIR, 'beads-commit-gate.mjs');
|
|
144
111
|
const BEADS_STOP_GATE_FILE = join(HOOKS_DIR, 'beads-stop-gate.mjs');
|
|
145
112
|
|
|
146
|
-
const BEADS_EDIT_GATE_SCRIPT = `#!/usr/bin/env node
|
|
147
|
-
// beads-edit-gate — Claude Code PreToolUse hook
|
|
148
|
-
// Blocks file edits when no beads issue is in_progress.
|
|
149
|
-
// Only active in projects with a .beads/ directory.
|
|
150
|
-
// Exit 0: allow | Exit 2: block (stderr shown to Claude)
|
|
151
|
-
//
|
|
152
|
-
// Installed by: npx --package=@jaggerxtrm/specialists install
|
|
153
|
-
|
|
154
|
-
import { execSync } from 'node:child_process';
|
|
155
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
156
|
-
import { join } from 'node:path';
|
|
157
|
-
|
|
158
|
-
let input;
|
|
159
|
-
try {
|
|
160
|
-
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
161
|
-
} catch {
|
|
162
|
-
process.exit(0);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
166
|
-
if (!existsSync(join(cwd, '.beads'))) process.exit(0);
|
|
167
|
-
|
|
168
|
-
let inProgress = 0;
|
|
169
|
-
try {
|
|
170
|
-
const output = execSync('bd list --status=in_progress', {
|
|
171
|
-
encoding: 'utf8',
|
|
172
|
-
cwd,
|
|
173
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
174
|
-
timeout: 8000,
|
|
175
|
-
});
|
|
176
|
-
inProgress = (output.match(/in_progress/g) ?? []).length;
|
|
177
|
-
} catch {
|
|
178
|
-
process.exit(0);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (inProgress === 0) {
|
|
182
|
-
process.stderr.write(
|
|
183
|
-
'\\u{1F6AB} BEADS GATE: No in_progress issue tracked.\\n' +
|
|
184
|
-
'You MUST create and claim a beads issue BEFORE editing any file:\\n\\n' +
|
|
185
|
-
' bd create --title="<task summary>" --type=task --priority=2\\n' +
|
|
186
|
-
' bd update <id> --status=in_progress\\n\\n' +
|
|
187
|
-
'No exceptions. Momentum is not an excuse.\\n'
|
|
188
|
-
);
|
|
189
|
-
process.exit(2);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
process.exit(0);
|
|
193
|
-
`;
|
|
194
|
-
|
|
195
|
-
const BEADS_COMMIT_GATE_SCRIPT = `#!/usr/bin/env node
|
|
196
|
-
// beads-commit-gate — Claude Code PreToolUse hook
|
|
197
|
-
// Blocks \`git commit\` when in_progress beads issues still exist.
|
|
198
|
-
// Forces: close issues first, THEN commit.
|
|
199
|
-
// Exit 0: allow | Exit 2: block (stderr shown to Claude)
|
|
200
|
-
//
|
|
201
|
-
// Installed by: npx --package=@jaggerxtrm/specialists install
|
|
202
|
-
|
|
203
|
-
import { execSync } from 'node:child_process';
|
|
204
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
205
|
-
import { join } from 'node:path';
|
|
206
|
-
|
|
207
|
-
let input;
|
|
208
|
-
try {
|
|
209
|
-
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
210
|
-
} catch {
|
|
211
|
-
process.exit(0);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const tool = input.tool_name ?? '';
|
|
215
|
-
if (tool !== 'Bash') process.exit(0);
|
|
216
|
-
|
|
217
|
-
const cmd = input.tool_input?.command ?? '';
|
|
218
|
-
if (!/\\bgit\\s+commit\\b/.test(cmd)) process.exit(0);
|
|
219
|
-
|
|
220
|
-
const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
221
|
-
if (!existsSync(join(cwd, '.beads'))) process.exit(0);
|
|
222
|
-
|
|
223
|
-
let inProgress = 0;
|
|
224
|
-
let summary = '';
|
|
225
|
-
try {
|
|
226
|
-
const output = execSync('bd list --status=in_progress', {
|
|
227
|
-
encoding: 'utf8',
|
|
228
|
-
cwd,
|
|
229
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
230
|
-
timeout: 8000,
|
|
231
|
-
});
|
|
232
|
-
inProgress = (output.match(/in_progress/g) ?? []).length;
|
|
233
|
-
summary = output.trim();
|
|
234
|
-
} catch {
|
|
235
|
-
process.exit(0);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (inProgress > 0) {
|
|
239
|
-
process.stderr.write(
|
|
240
|
-
'\\u{1F6AB} BEADS GATE: Cannot commit with open in_progress issues.\\n' +
|
|
241
|
-
'Close them first, THEN commit:\\n\\n' +
|
|
242
|
-
' bd close <id1> <id2> ...\\n' +
|
|
243
|
-
' git add <files> && git commit -m "..."\\n\\n' +
|
|
244
|
-
\`Open issues:\\n\${summary}\\n\`
|
|
245
|
-
);
|
|
246
|
-
process.exit(2);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
process.exit(0);
|
|
250
|
-
`;
|
|
251
|
-
|
|
252
|
-
const BEADS_STOP_GATE_SCRIPT = `#!/usr/bin/env node
|
|
253
|
-
// beads-stop-gate — Claude Code Stop hook
|
|
254
|
-
// Blocks the agent from stopping when in_progress beads issues remain.
|
|
255
|
-
// Forces the session close protocol before declaring done.
|
|
256
|
-
// Exit 0: allow stop | Exit 2: block stop (stderr shown to Claude)
|
|
257
|
-
//
|
|
258
|
-
// Installed by: npx --package=@jaggerxtrm/specialists install
|
|
259
|
-
|
|
260
|
-
import { execSync } from 'node:child_process';
|
|
261
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
262
|
-
import { join } from 'node:path';
|
|
263
|
-
|
|
264
|
-
let input;
|
|
265
|
-
try {
|
|
266
|
-
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
267
|
-
} catch {
|
|
268
|
-
process.exit(0);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
272
|
-
if (!existsSync(join(cwd, '.beads'))) process.exit(0);
|
|
273
|
-
|
|
274
|
-
let inProgress = 0;
|
|
275
|
-
let summary = '';
|
|
276
|
-
try {
|
|
277
|
-
const output = execSync('bd list --status=in_progress', {
|
|
278
|
-
encoding: 'utf8',
|
|
279
|
-
cwd,
|
|
280
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
281
|
-
timeout: 8000,
|
|
282
|
-
});
|
|
283
|
-
inProgress = (output.match(/in_progress/g) ?? []).length;
|
|
284
|
-
summary = output.trim();
|
|
285
|
-
} catch {
|
|
286
|
-
process.exit(0);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (inProgress > 0) {
|
|
290
|
-
process.stderr.write(
|
|
291
|
-
'\\u{1F6AB} BEADS STOP GATE: Cannot stop with unresolved in_progress issues.\\n' +
|
|
292
|
-
'Complete the session close protocol:\\n\\n' +
|
|
293
|
-
' bd close <id1> <id2> ...\\n' +
|
|
294
|
-
' git add <files> && git commit -m "..."\\n' +
|
|
295
|
-
' git push\\n\\n' +
|
|
296
|
-
\`Open issues:\\n\${summary}\\n\`
|
|
297
|
-
);
|
|
298
|
-
process.exit(2);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
process.exit(0);
|
|
302
|
-
`;
|
|
303
|
-
|
|
304
113
|
const BEADS_EDIT_GATE_ENTRY = {
|
|
305
114
|
matcher: 'Edit|Write|MultiEdit|NotebookEdit|mcp__serena__replace_symbol_body|mcp__serena__insert_after_symbol|mcp__serena__insert_before_symbol',
|
|
306
115
|
hooks: [{ type: 'command', command: BEADS_EDIT_GATE_FILE, timeout: 10000 }],
|
|
@@ -316,14 +125,14 @@ const BEADS_STOP_GATE_ENTRY = {
|
|
|
316
125
|
function installHook() {
|
|
317
126
|
mkdirSync(HOOKS_DIR, { recursive: true });
|
|
318
127
|
|
|
319
|
-
//
|
|
320
|
-
|
|
128
|
+
// Copy hook files from bundled hooks/ directory
|
|
129
|
+
copyFileSync(join(BUNDLED_HOOKS_DIR, 'specialists-main-guard.mjs'), HOOK_FILE);
|
|
321
130
|
chmodSync(HOOK_FILE, 0o755);
|
|
322
|
-
|
|
131
|
+
copyFileSync(join(BUNDLED_HOOKS_DIR, 'beads-edit-gate.mjs'), BEADS_EDIT_GATE_FILE);
|
|
323
132
|
chmodSync(BEADS_EDIT_GATE_FILE, 0o755);
|
|
324
|
-
|
|
133
|
+
copyFileSync(join(BUNDLED_HOOKS_DIR, 'beads-commit-gate.mjs'), BEADS_COMMIT_GATE_FILE);
|
|
325
134
|
chmodSync(BEADS_COMMIT_GATE_FILE, 0o755);
|
|
326
|
-
|
|
135
|
+
copyFileSync(join(BUNDLED_HOOKS_DIR, 'beads-stop-gate.mjs'), BEADS_STOP_GATE_FILE);
|
|
327
136
|
chmodSync(BEADS_STOP_GATE_FILE, 0o755);
|
|
328
137
|
|
|
329
138
|
let settings = {};
|
|
@@ -427,7 +236,7 @@ installHook();
|
|
|
427
236
|
hookExisted
|
|
428
237
|
? ok('hooks updated (main-guard + beads gates)')
|
|
429
238
|
: ok('hooks installed → ~/.claude/hooks/');
|
|
430
|
-
info('main-guard: blocks
|
|
239
|
+
info('main-guard: blocks file edits and direct master pushes (enforces PR workflow)');
|
|
431
240
|
info('beads-edit-gate: requires in_progress bead before editing files');
|
|
432
241
|
info('beads-commit-gate: requires issues closed before git commit');
|
|
433
242
|
info('beads-stop-gate: requires issues closed before session end');
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// beads-commit-gate — Claude Code PreToolUse hook
|
|
3
|
+
// Blocks `git commit` when in_progress beads issues still exist.
|
|
4
|
+
// Forces: close issues first, THEN commit.
|
|
5
|
+
// Exit 0: allow | Exit 2: block (stderr shown to Claude)
|
|
6
|
+
//
|
|
7
|
+
// Installed by: npx --package=@jaggerxtrm/specialists install
|
|
8
|
+
|
|
9
|
+
import { execSync } from 'node:child_process';
|
|
10
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
let input;
|
|
14
|
+
try {
|
|
15
|
+
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
16
|
+
} catch {
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const tool = input.tool_name ?? '';
|
|
21
|
+
if (tool !== 'Bash') process.exit(0);
|
|
22
|
+
|
|
23
|
+
const cmd = input.tool_input?.command ?? '';
|
|
24
|
+
if (!/\bgit\s+commit\b/.test(cmd)) process.exit(0);
|
|
25
|
+
|
|
26
|
+
const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
27
|
+
if (!existsSync(join(cwd, '.beads'))) process.exit(0);
|
|
28
|
+
|
|
29
|
+
let inProgress = 0;
|
|
30
|
+
let summary = '';
|
|
31
|
+
try {
|
|
32
|
+
const output = execSync('bd list --status=in_progress', {
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
cwd,
|
|
35
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
36
|
+
timeout: 8000,
|
|
37
|
+
});
|
|
38
|
+
inProgress = (output.match(/in_progress/g) ?? []).length;
|
|
39
|
+
summary = output.trim();
|
|
40
|
+
} catch {
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (inProgress > 0) {
|
|
45
|
+
process.stderr.write(
|
|
46
|
+
'\u{1F6AB} BEADS GATE: Cannot commit with open in_progress issues.\n' +
|
|
47
|
+
'Close them first, THEN commit:\n\n' +
|
|
48
|
+
' bd close <id1> <id2> ...\n' +
|
|
49
|
+
' git add <files> && git commit -m "..."\n\n' +
|
|
50
|
+
`Open issues:\n${summary}\n`
|
|
51
|
+
);
|
|
52
|
+
process.exit(2);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
process.exit(0);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// beads-edit-gate — Claude Code PreToolUse hook
|
|
3
|
+
// Blocks file edits when no beads issue is in_progress.
|
|
4
|
+
// Only active in projects with a .beads/ directory.
|
|
5
|
+
// Exit 0: allow | Exit 2: block (stderr shown to Claude)
|
|
6
|
+
//
|
|
7
|
+
// Installed by: npx --package=@jaggerxtrm/specialists install
|
|
8
|
+
|
|
9
|
+
import { execSync } from 'node:child_process';
|
|
10
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
let input;
|
|
14
|
+
try {
|
|
15
|
+
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
16
|
+
} catch {
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
21
|
+
if (!existsSync(join(cwd, '.beads'))) process.exit(0);
|
|
22
|
+
|
|
23
|
+
let inProgress = 0;
|
|
24
|
+
try {
|
|
25
|
+
const output = execSync('bd list --status=in_progress', {
|
|
26
|
+
encoding: 'utf8',
|
|
27
|
+
cwd,
|
|
28
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
29
|
+
timeout: 8000,
|
|
30
|
+
});
|
|
31
|
+
inProgress = (output.match(/in_progress/g) ?? []).length;
|
|
32
|
+
} catch {
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (inProgress === 0) {
|
|
37
|
+
process.stderr.write(
|
|
38
|
+
'\u{1F6AB} BEADS GATE: No in_progress issue tracked.\n' +
|
|
39
|
+
'You MUST create and claim a beads issue BEFORE editing any file:\n\n' +
|
|
40
|
+
' bd create --title="<task summary>" --type=task --priority=2\n' +
|
|
41
|
+
' bd update <id> --status=in_progress\n\n' +
|
|
42
|
+
'No exceptions. Momentum is not an excuse.\n'
|
|
43
|
+
);
|
|
44
|
+
process.exit(2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
process.exit(0);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// beads-stop-gate — Claude Code Stop hook
|
|
3
|
+
// Blocks the agent from stopping when in_progress beads issues remain.
|
|
4
|
+
// Forces the session close protocol before declaring done.
|
|
5
|
+
// Exit 0: allow stop | Exit 2: block stop (stderr shown to Claude)
|
|
6
|
+
//
|
|
7
|
+
// Installed by: npx --package=@jaggerxtrm/specialists install
|
|
8
|
+
|
|
9
|
+
import { execSync } from 'node:child_process';
|
|
10
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
let input;
|
|
14
|
+
try {
|
|
15
|
+
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
16
|
+
} catch {
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
21
|
+
if (!existsSync(join(cwd, '.beads'))) process.exit(0);
|
|
22
|
+
|
|
23
|
+
let inProgress = 0;
|
|
24
|
+
let summary = '';
|
|
25
|
+
try {
|
|
26
|
+
const output = execSync('bd list --status=in_progress', {
|
|
27
|
+
encoding: 'utf8',
|
|
28
|
+
cwd,
|
|
29
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
30
|
+
timeout: 8000,
|
|
31
|
+
});
|
|
32
|
+
inProgress = (output.match(/in_progress/g) ?? []).length;
|
|
33
|
+
summary = output.trim();
|
|
34
|
+
} catch {
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (inProgress > 0) {
|
|
39
|
+
process.stderr.write(
|
|
40
|
+
'\u{1F6AB} BEADS STOP GATE: Cannot stop with unresolved in_progress issues.\n' +
|
|
41
|
+
'Complete the session close protocol:\n\n' +
|
|
42
|
+
' bd close <id1> <id2> ...\n' +
|
|
43
|
+
' git add <files> && git commit -m "..."\n' +
|
|
44
|
+
' git push\n\n' +
|
|
45
|
+
`Open issues:\n${summary}\n`
|
|
46
|
+
);
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
process.exit(0);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Claude Code PreToolUse hook — block writes and direct master pushes
|
|
3
|
+
// Exit 0: allow | Exit 2: block (message shown to user)
|
|
4
|
+
//
|
|
5
|
+
// Installed by: specialists install
|
|
6
|
+
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
|
|
10
|
+
let branch = '';
|
|
11
|
+
try {
|
|
12
|
+
branch = execSync('git branch --show-current', {
|
|
13
|
+
encoding: 'utf8',
|
|
14
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
15
|
+
}).trim();
|
|
16
|
+
} catch {}
|
|
17
|
+
|
|
18
|
+
// Not in a git repo or not on a protected branch — allow
|
|
19
|
+
if (!branch || (branch !== 'main' && branch !== 'master')) {
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let input;
|
|
24
|
+
try {
|
|
25
|
+
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
26
|
+
} catch {
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const tool = input.tool_name ?? '';
|
|
31
|
+
const blockMsg =
|
|
32
|
+
`⛔ Direct edits on '${branch}' are not allowed.\n` +
|
|
33
|
+
`Create a feature branch first: git checkout -b feature/<name>`;
|
|
34
|
+
|
|
35
|
+
const WRITE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
|
|
36
|
+
|
|
37
|
+
if (WRITE_TOOLS.has(tool)) {
|
|
38
|
+
process.stderr.write(blockMsg + '\n');
|
|
39
|
+
process.exit(2);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Block direct pushes to master — agents must use feature branches + gh pr create/merge
|
|
43
|
+
if (tool === 'Bash') {
|
|
44
|
+
const cmd = (input.tool_input?.command ?? '').trim().replace(/\s+/g, ' ');
|
|
45
|
+
if (/^git push/.test(cmd)) {
|
|
46
|
+
const tokens = cmd.split(' ');
|
|
47
|
+
const lastToken = tokens[tokens.length - 1];
|
|
48
|
+
const explicitMaster = /^(master|main)$/.test(lastToken) || /:(master|main)$/.test(lastToken);
|
|
49
|
+
const impliedMaster = tokens.length <= 3 && (branch === 'main' || branch === 'master');
|
|
50
|
+
if (explicitMaster || impliedMaster) {
|
|
51
|
+
process.stderr.write(
|
|
52
|
+
`⛔ Direct push to '${branch}' is not allowed.\n` +
|
|
53
|
+
`Use the PR workflow instead:\n` +
|
|
54
|
+
` git push -u origin <feature-branch>\n` +
|
|
55
|
+
` gh pr create --fill\n` +
|
|
56
|
+
` gh pr merge --squash\n`
|
|
57
|
+
);
|
|
58
|
+
process.exit(2);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaggerxtrm/specialists",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.15",
|
|
4
4
|
"description": "OmniSpecialist — 7-tool MCP orchestration layer powered by the Specialist System. Discover and execute .specialist.yaml files across project/user/system scopes via pi.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist/index.js",
|
|
9
9
|
"bin/install.js",
|
|
10
|
-
"specialists/"
|
|
10
|
+
"specialists/",
|
|
11
|
+
"hooks/"
|
|
11
12
|
],
|
|
12
13
|
"bin": {
|
|
13
14
|
"specialists": "dist/index.js",
|