gm-codex 2.0.201 → 2.0.204

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/agents/gm.md CHANGED
@@ -9,10 +9,10 @@ enforce: critical
9
9
 
10
10
  **Invoke the `gm` skill immediately.** Use the Skill tool with `skill: "gm"`.
11
11
 
12
- **CRITICAL: Skills are invoked via the Skill tool ONLY. Do NOT use the Agent tool to load skills. Skills are not agents. Use: `Skill tool` with `skill: "gm"` (or `"planning"`, `"gm-execute"`, `"gm-emit"`, `"gm-complete"`). Using the Agent tool for skills is a violation.**
12
+ **CRITICAL: Skills are invoked via the Skill tool ONLY. Do NOT use the Agent tool to load skills. Skills are not agents. Use: `Skill tool` with `skill: "gm"` (or `"planning"`, `"gm-execute"`, `"gm-emit"`, `"gm-complete"`, `"update-docs"`). Using the Agent tool for skills is a violation.**
13
13
 
14
14
  All work coordination, planning, execution, and verification happens through the skill tree:
15
- - `gm` skill → `planning` skill → `gm-execute` skill → `gm-emit` skill → `gm-complete` skill
15
+ - `gm` skill → `planning` skill → `gm-execute` skill → `gm-emit` skill → `gm-complete` skill → `update-docs` skill
16
16
 
17
17
  All code execution uses `exec:<lang>` via the Bash tool — never direct `Bash(node ...)` or `Bash(npm ...)`.
18
18
 
@@ -104,6 +104,24 @@ async function ensureTools() {
104
104
  // Run tool installation (fire-and-forget, won't block hook)
105
105
  ensureTools().catch(() => {});
106
106
 
107
+ // ─── Lang plugin loader ───────────────────────────────────────────────────────
108
+ function loadLangPlugins(projectDir) {
109
+ if (!projectDir) return [];
110
+ const langDir = path.join(projectDir, 'lang');
111
+ if (!fs.existsSync(langDir)) return [];
112
+ try {
113
+ return fs.readdirSync(langDir)
114
+ .filter(f => f.endsWith('.js') && f !== 'loader.js' && f !== 'SPEC.md')
115
+ .reduce((acc, f) => {
116
+ try {
117
+ const p = require(path.join(langDir, f));
118
+ if (p && typeof p.id === 'string' && p.exec && p.exec.match instanceof RegExp && typeof p.exec.run === 'function') acc.push(p);
119
+ } catch (_) {}
120
+ return acc;
121
+ }, []);
122
+ } catch (_) { return []; }
123
+ }
124
+
107
125
  // Helper: run a local binary (falls back to bunx if not installed)
108
126
  function runLocal(name, args, opts = {}) {
109
127
  const bin = localBin(name);
@@ -225,6 +243,29 @@ const run = () => {
225
243
  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.`);
226
244
  }
227
245
  const cwd = tool_input?.cwd;
246
+
247
+ // ─── Lang plugin dispatch ─────────────────────────────────────────────
248
+ if (rawLang) {
249
+ const builtins = new Set(['js','javascript','ts','typescript','node','nodejs','py','python','sh','bash','shell','zsh','powershell','ps1','go','rust','c','cpp','java','deno','cmd','browser','ab','agent-browser','codesearch','search','status','sleep','close','runner','type','pm2list']);
250
+ if (!builtins.has(rawLang)) {
251
+ const plugins = loadLangPlugins(projectDir);
252
+ const plugin = plugins.find(p => p.exec.match.test(`exec:${rawLang}\n${code}`));
253
+ if (plugin) {
254
+ const runnerCode = `
255
+ const plugin = require(${JSON.stringify(path.join(projectDir, 'lang', plugin.id + '.js'))});
256
+ Promise.resolve(plugin.exec.run(${JSON.stringify(code)}, ${JSON.stringify(cwd || projectDir || process.cwd())}))
257
+ .then(out => process.stdout.write(String(out || '')))
258
+ .catch(e => { process.stderr.write(e.message || String(e)); process.exit(1); });
259
+ `;
260
+ const r = spawnSync('bun', ['-e', runnerCode], { encoding: 'utf-8', timeout: 30000, windowsHide: true });
261
+ const out = (r.stdout || '').trimEnd();
262
+ const err = (r.stderr || '').trimEnd();
263
+ if (r.status !== 0 || r.error) return allowWithNoop(`exec:${rawLang} error:\n\n${r.error ? r.error.message : (err || 'exec failed')}`);
264
+ return allowWithNoop(`exec:${rawLang} output:\n\n${out || '(no output)'}`);
265
+ }
266
+ }
267
+ }
268
+ // ─────────────────────────────────────────────────────────────────────
228
269
  const detectLang = (src) => {
229
270
  if (/^\s*(import |from |export |const |let |var |function |class |async |await |console\.|process\.)/.test(src)) return 'nodejs';
230
271
  if (/^\s*(import |def |print\(|class |if __name__)/.test(src)) return 'python';
@@ -28,6 +28,69 @@ function runLocal(name, args, opts = {}) {
28
28
 
29
29
  const projectDir = process.env.CLAUDE_PROJECT_DIR || process.env.GEMINI_PROJECT_DIR || process.env.OC_PROJECT_DIR || process.env.KILO_PROJECT_DIR;
30
30
 
31
+ function loadLangPlugins(dir) {
32
+ if (!dir) return [];
33
+ const langDir = path.join(dir, 'lang');
34
+ if (!fs.existsSync(langDir)) return [];
35
+ try {
36
+ return fs.readdirSync(langDir)
37
+ .filter(f => f.endsWith('.js') && f !== 'loader.js' && f !== 'SPEC.md')
38
+ .reduce((acc, f) => {
39
+ try {
40
+ const p = require(path.join(langDir, f));
41
+ if (p && typeof p.id === 'string' && p.exec && p.exec.match instanceof RegExp && typeof p.exec.run === 'function') acc.push(p);
42
+ } catch (_) {}
43
+ return acc;
44
+ }, []);
45
+ } catch (_) { return []; }
46
+ }
47
+
48
+ function walkFiles(dir, exts, depth) {
49
+ if (depth <= 0 || !fs.existsSync(dir)) return [];
50
+ let results = [];
51
+ try {
52
+ for (const f of fs.readdirSync(dir)) {
53
+ if (f.startsWith('.') || f === 'node_modules') continue;
54
+ const full = path.join(dir, f);
55
+ const stat = fs.statSync(full);
56
+ if (stat.isDirectory()) results = results.concat(walkFiles(full, exts, depth - 1));
57
+ else if (exts.some(e => f.endsWith(e))) results.push({ path: full, mtime: stat.mtimeMs });
58
+ }
59
+ } catch (_) {}
60
+ return results;
61
+ }
62
+
63
+ function getLangPluginContext(dir) {
64
+ const plugins = loadLangPlugins(dir);
65
+ if (!plugins.length) return '';
66
+ const parts = [];
67
+ for (const p of plugins) {
68
+ if (p.context) {
69
+ const ctx = typeof p.context === 'function' ? p.context() : p.context;
70
+ if (ctx) parts.push(String(ctx).slice(0, 2000));
71
+ }
72
+ if (p.lsp && p.extensions && p.extensions.length) {
73
+ try {
74
+ const files = walkFiles(dir, p.extensions, 4)
75
+ .sort((a, b) => b.mtime - a.mtime)
76
+ .slice(0, 3);
77
+ const diags = [];
78
+ for (const f of files) {
79
+ try {
80
+ const code = fs.readFileSync(f.path, 'utf-8');
81
+ const results = p.lsp.check(code, dir);
82
+ if (Array.isArray(results)) {
83
+ for (const d of results) diags.push(`${path.relative(dir, f.path)}:${d.line}:${d.col}: ${d.severity}: ${d.message}`);
84
+ }
85
+ } catch (_) {}
86
+ }
87
+ if (diags.length) parts.push(`=== ${p.id} LSP diagnostics ===\n${diags.join('\n').slice(0, 3000)}`);
88
+ } catch (_) {}
89
+ }
90
+ }
91
+ return parts.join('\n\n');
92
+ }
93
+
31
94
  const ensureGitignore = () => {
32
95
  if (!projectDir) return;
33
96
  const gitignorePath = path.join(projectDir, '.gitignore');
@@ -94,6 +157,9 @@ try {
94
157
  const thorns = runThorns();
95
158
  if (thorns) parts.push(thorns);
96
159
 
160
+ const langCtx = getLangPluginContext(projectDir);
161
+ if (langCtx) parts.push(langCtx);
162
+
97
163
  emit(parts.join('\n\n'));
98
164
  } catch (error) {
99
165
  emit('Invoke the `gm` skill to begin. Hook error: ' + error.message);
@@ -2,10 +2,73 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const { execSync } = require('child_process');
5
+ const os = require('os');
6
+ const { spawnSync } = require('child_process');
7
+
8
+ const IS_WIN = process.platform === 'win32';
9
+ const TOOLS_DIR = path.join(os.homedir(), '.claude', 'gm-tools');
10
+
11
+ function localBin(name) {
12
+ const ext = IS_WIN ? '.exe' : '';
13
+ return path.join(TOOLS_DIR, 'node_modules', '.bin', name + ext);
14
+ }
15
+
16
+ function runLocal(name, args, opts = {}) {
17
+ const bin = localBin(name);
18
+ if (fs.existsSync(bin)) {
19
+ return spawnSync(bin, args, { encoding: 'utf8', windowsHide: true, timeout: 30000, ...opts });
20
+ }
21
+ return spawnSync('bun', ['x', name, ...args], { encoding: 'utf8', windowsHide: true, timeout: 30000, ...opts });
22
+ }
23
+
24
+ const MANAGED_PKGS = ['gm-exec', 'codebasesearch', 'mcp-thorns', 'agent-browser'];
25
+ const PKG_JSON = path.join(TOOLS_DIR, 'package.json');
26
+
27
+ function ensureTools() {
28
+ try { fs.mkdirSync(TOOLS_DIR, { recursive: true }); } catch {}
29
+ if (!fs.existsSync(PKG_JSON)) {
30
+ try { fs.writeFileSync(PKG_JSON, JSON.stringify({ name: 'gm-tools', version: '1.0.0', private: true })); } catch {}
31
+ }
32
+ const missing = MANAGED_PKGS.filter(p => !fs.existsSync(localBin(p)));
33
+ if (missing.length > 0) {
34
+ try {
35
+ spawnSync('bun', ['add', ...missing.map(p => p + '@latest')], {
36
+ cwd: TOOLS_DIR, encoding: 'utf8', timeout: 180000, windowsHide: true
37
+ });
38
+ } catch {}
39
+ }
40
+ }
41
+
42
+ ensureTools();
6
43
 
7
44
  const projectDir = process.env.CLAUDE_PROJECT_DIR || process.env.GEMINI_PROJECT_DIR || process.env.OC_PROJECT_DIR || process.env.KILO_PROJECT_DIR;
8
45
 
46
+ function loadLangPlugins(dir) {
47
+ if (!dir) return [];
48
+ const langDir = path.join(dir, 'lang');
49
+ if (!fs.existsSync(langDir)) return [];
50
+ try {
51
+ return fs.readdirSync(langDir)
52
+ .filter(f => f.endsWith('.js') && f !== 'loader.js' && f !== 'SPEC.md')
53
+ .reduce((acc, f) => {
54
+ try {
55
+ const p = require(path.join(langDir, f));
56
+ if (p && typeof p.id === 'string' && p.exec && p.exec.match instanceof RegExp && typeof p.exec.run === 'function') acc.push(p);
57
+ } catch (_) {}
58
+ return acc;
59
+ }, []);
60
+ } catch (_) { return []; }
61
+ }
62
+
63
+ function getLangPluginContext(dir) {
64
+ const plugins = loadLangPlugins(dir);
65
+ return plugins
66
+ .filter(p => p.context)
67
+ .map(p => { const ctx = typeof p.context === 'function' ? p.context() : p.context; return ctx ? String(ctx).slice(0, 2000) : ''; })
68
+ .filter(Boolean)
69
+ .join('\n\n');
70
+ }
71
+
9
72
  const ensureGitignore = () => {
10
73
  if (!projectDir) return;
11
74
  const gitignorePath = path.join(projectDir, '.gitignore');
@@ -33,29 +96,17 @@ try {
33
96
 
34
97
  if (projectDir && fs.existsSync(projectDir)) {
35
98
  try {
36
- let thornOutput;
37
- try {
38
- thornOutput = execSync(`bun x mcp-thorns@latest`, {
39
- encoding: 'utf-8',
40
- stdio: ['pipe', 'pipe', 'pipe'],
41
- cwd: projectDir,
42
- timeout: 15000,
43
- killSignal: 'SIGTERM'
44
- });
45
- } catch (bunErr) {
46
- thornOutput = bunErr.killed
47
- ? '=== mcp-thorns ===\nSkipped (timeout)'
48
- : `=== mcp-thorns ===\nSkipped (error: ${bunErr.message.split('\n')[0]})`;
49
- }
50
- outputs.push(`=== This is your initial insight of the repository, look at every possible aspect of this for initial opinionation and to offset the need for code exploration ===\n${thornOutput}`);
51
- } catch (e) {
52
- if (e.killed && e.signal === 'SIGTERM') {
53
- outputs.push(`=== mcp-thorns ===\nSkipped (3min timeout)`);
54
- } else {
55
- outputs.push(`=== mcp-thorns ===\nSkipped (error: ${e.message.split('\n')[0]})`);
99
+ const r = runLocal('mcp-thorns', [projectDir], { timeout: 15000 });
100
+ const thornOutput = ((r.stdout || '') + (r.stderr || '')).trim();
101
+ if (thornOutput) {
102
+ outputs.push(`=== This is your initial insight of the repository, look at every possible aspect of this for initial opinionation and to offset the need for code exploration ===\n${thornOutput}`);
56
103
  }
57
- }
104
+ } catch (e) {}
58
105
  }
106
+
107
+ const langCtx = getLangPluginContext(projectDir);
108
+ if (langCtx) outputs.push(langCtx);
109
+
59
110
  const additionalContext = outputs.join('\n\n');
60
111
 
61
112
  const isGemini = process.env.GEMINI_PROJECT_DIR !== undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-codex",
3
- "version": "2.0.201",
3
+ "version": "2.0.204",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.201",
3
+ "version": "2.0.204",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": {
6
6
  "name": "AnEntrypoint",