gm-gc 2.0.203 → 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.203",
3
+ "version": "2.0.204",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "homepage": "https://github.com/AnEntrypoint/gm",
@@ -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);
@@ -43,6 +43,32 @@ ensureTools();
43
43
 
44
44
  const projectDir = process.env.CLAUDE_PROJECT_DIR || process.env.GEMINI_PROJECT_DIR || process.env.OC_PROJECT_DIR || process.env.KILO_PROJECT_DIR;
45
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
+
46
72
  const ensureGitignore = () => {
47
73
  if (!projectDir) return;
48
74
  const gitignorePath = path.join(projectDir, '.gitignore');
@@ -77,6 +103,10 @@ try {
77
103
  }
78
104
  } catch (e) {}
79
105
  }
106
+
107
+ const langCtx = getLangPluginContext(projectDir);
108
+ if (langCtx) outputs.push(langCtx);
109
+
80
110
  const additionalContext = outputs.join('\n\n');
81
111
 
82
112
  const isGemini = process.env.GEMINI_PROJECT_DIR !== undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-gc",
3
- "version": "2.0.203",
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",