gm-oc 2.0.177 → 2.0.178

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.
@@ -11,16 +11,31 @@ const writeTools = ['Write', 'write_file'];
11
11
  const searchTools = ['glob', 'search_file_content', 'Search', 'search'];
12
12
  const forbiddenTools = ['find', 'Find', 'Glob', 'Grep'];
13
13
 
14
+ const allow = (additionalContext) => ({
15
+ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow', ...(additionalContext && { additionalContext }) }
16
+ });
17
+ const deny = (reason) => isGemini
18
+ ? { decision: 'deny', reason }
19
+ : { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason } };
20
+ const allowWithNoop = (context) => ({
21
+ hookSpecificOutput: {
22
+ hookEventName: 'PreToolUse',
23
+ permissionDecision: 'allow',
24
+ additionalContext: context,
25
+ updatedInput: { command: 'echo ""' }
26
+ }
27
+ });
28
+
14
29
  const run = () => {
15
30
  try {
16
31
  const input = fs.readFileSync(0, 'utf-8');
17
32
  const data = JSON.parse(input);
18
33
  const { tool_name, tool_input } = data;
19
34
 
20
- if (!tool_name) return { allow: true };
35
+ if (!tool_name) return allow();
21
36
 
22
37
  if (forbiddenTools.includes(tool_name)) {
23
- return { block: true, reason: 'Use the code-search skill for codebase exploration instead of Grep/Glob/find. Describe what you need in plain language — it understands intent, not just patterns.' };
38
+ return deny('Use the code-search skill for codebase exploration instead of Grep/Glob/find. Describe what you need in plain language — it understands intent, not just patterns.');
24
39
  }
25
40
 
26
41
  if (writeTools.includes(tool_name)) {
@@ -30,7 +45,7 @@ const run = () => {
30
45
  const base = path.basename(file_path).toLowerCase();
31
46
  if ((ext === '.md' || ext === '.txt' || base.startsWith('features_list')) &&
32
47
  !base.startsWith('claude') && !base.startsWith('readme') && !inSkillsDir) {
33
- return { block: true, reason: 'Cannot create documentation files. Only CLAUDE.md and readme.md are maintained. For task-specific notes, use .prd. For permanent reference material, add to CLAUDE.md.' };
48
+ return deny('Cannot create documentation files. Only CLAUDE.md and readme.md are maintained. For task-specific notes, use .prd. For permanent reference material, add to CLAUDE.md.');
34
49
  }
35
50
  if (/\.(test|spec)\.(js|ts|jsx|tsx|mjs|cjs)$/.test(base) ||
36
51
  /^(jest|vitest|mocha|ava|jasmine|tap)\.(config|setup)/.test(base) ||
@@ -38,24 +53,24 @@ const run = () => {
38
53
  file_path.includes('/tests/') || file_path.includes('/fixtures/') ||
39
54
  file_path.includes('/test-data/') || file_path.includes('/__mocks__/') ||
40
55
  /\.(snap|stub|mock|fixture)\.(js|ts|json)$/.test(base)) {
41
- return { block: true, reason: 'Test files forbidden on disk. Use Bash tool with real services for all testing.' };
56
+ return deny('Test files forbidden on disk. Use Bash tool with real services for all testing.');
42
57
  }
43
58
  }
44
59
 
45
- if (searchTools.includes(tool_name)) return { allow: true };
60
+ if (searchTools.includes(tool_name)) return allow();
46
61
 
47
62
  if (tool_name === 'Task' && (tool_input?.subagent_type || '') === 'Explore') {
48
- return { block: true, reason: 'Use gm:thorns-overview for codebase insight, then use gm:code-search' };
63
+ return deny('Use gm:thorns-overview for codebase insight, then use gm:code-search');
49
64
  }
50
65
 
51
66
  if (tool_name === 'EnterPlanMode') {
52
- return { block: true, reason: 'Plan mode is disabled. Use GM agent planning (PLAN→EXECUTE→EMIT→VERIFY→COMPLETE state machine) via gm:gm subagent instead.' };
67
+ return deny('Plan mode is disabled. Use GM agent planning (PLAN→EXECUTE→EMIT→VERIFY→COMPLETE state machine) via gm:gm subagent instead.');
53
68
  }
54
69
 
55
70
  if (tool_name === 'Skill') {
56
71
  const skill = (tool_input?.skill || '').toLowerCase().replace(/^gm:/, '');
57
72
  if (skill === 'explore' || skill === 'search') {
58
- return { block: true, reason: 'Use the code-search skill for codebase exploration. Describe what you need in plain language — it understands intent, not just patterns.' };
73
+ return deny('Use the code-search skill for codebase exploration. Describe what you need in plain language — it understands intent, not just patterns.');
59
74
  }
60
75
  }
61
76
 
@@ -66,7 +81,7 @@ const run = () => {
66
81
  const rawLang = (execMatch[1] || '').toLowerCase();
67
82
  const code = execMatch[2];
68
83
  if (/^\s*agent-browser\s/.test(code)) {
69
- return { block: true, reason: `Do not call agent-browser via exec:bash. Use exec:agent-browser instead:\n\nexec:agent-browser\n<plain JS here>\n\nThe code is piped directly to the browser eval. No base64, no flags, no shell wrapping.` };
84
+ return deny(`Do not call agent-browser via exec:bash. Use exec:agent-browser instead:\n\nexec:agent-browser\n<plain JS here>\n\nThe code is piped directly to the browser eval. No base64, no flags, no shell wrapping.`);
70
85
  }
71
86
  const cwd = tool_input?.cwd;
72
87
  const detectLang = (src) => {
@@ -134,35 +149,31 @@ const run = () => {
134
149
  } else {
135
150
  result = runWithFile(lang, safeCode);
136
151
  }
137
- return { block: true, reason: `exec ran successfully. Output:\n\n${result || '(no output)'}` };
152
+ return allowWithNoop(`exec:${lang} output:\n\n${result || '(no output)'}`);
138
153
  } catch (e) {
139
- return { block: true, reason: `exec ran. Error:\n\n${(e.stdout || '') + (e.stderr || '') || e.message || '(exec failed)'}` };
154
+ return allowWithNoop(`exec:${lang} error:\n\n${(e.stdout || '') + (e.stderr || '') || e.message || '(exec failed)'}`);
140
155
  }
141
156
  }
142
157
 
143
158
  if (!/^exec(\s|:)/.test(command) && !/^bun x gm-exec(@[^\s]*)?(\s|$)/.test(command) && !/^git /.test(command) && !/^bun x codebasesearch/.test(command) && !/(\bclaude\b)/.test(command) && !/^npm install .* \/config\/.gmweb/.test(command) && !/^bun install --cwd \/config\/.gmweb/.test(command)) {
144
159
  let helpText = '';
145
160
  try { helpText = '\n\n' + execSync('bun x gm-exec --help', { timeout: 10000 }).toString().trim(); } catch (e) {}
146
- return { block: true, reason: `Bash is restricted to exec:<lang> and git.\n\nexec:<lang> syntax (lang auto-detected if omitted):\n exec:nodejs / exec:python / exec:bash / exec:typescript\n exec:go / exec:rust / exec:java / exec:c / exec:cpp\n exec:agent-browser ← plain JS piped to browser eval (NO base64)\n exec ← auto-detects language\n\nNEVER encode agent-browser code as base64 — pass plain JS directly.\n\nbun x gm-exec${helpText}\n\nAll other Bash commands are blocked.` };
161
+ return deny(`Bash is restricted to exec:<lang> and git.\n\nexec:<lang> syntax (lang auto-detected if omitted):\n exec:nodejs / exec:python / exec:bash / exec:typescript\n exec:go / exec:rust / exec:java / exec:c / exec:cpp\n exec:agent-browser ← plain JS piped to browser eval (NO base64)\n exec ← auto-detects language\n\nNEVER encode agent-browser code as base64 — pass plain JS directly.\n\nbun x gm-exec${helpText}\n\nAll other Bash commands are blocked.`);
147
162
  }
148
163
  }
149
164
 
150
165
  const allowedTools = ['agent-browser', 'Skill', 'code-search', 'electron', 'TaskOutput', 'ReadMcpResourceTool', 'ListMcpResourcesTool'];
151
- if (allowedTools.includes(tool_name)) return { allow: true };
166
+ if (allowedTools.includes(tool_name)) return allow();
152
167
 
153
- return { allow: true };
168
+ return allow();
154
169
  } catch (error) {
155
- return { allow: true };
170
+ return allow();
156
171
  }
157
172
  };
158
173
 
159
174
  try {
160
175
  const result = run();
161
- if (result.block) {
162
- console.log(JSON.stringify({ decision: isGemini ? 'deny' : 'block', reason: result.reason }));
163
- process.exit(0);
164
- }
165
- if (isGemini) console.log(JSON.stringify({ decision: 'allow' }));
176
+ console.log(JSON.stringify(result));
166
177
  process.exit(0);
167
178
  } catch (error) {
168
179
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-oc",
3
- "version": "2.0.177",
3
+ "version": "2.0.178",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",