gm-codex 2.0.202 → 2.0.205
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 +2 -2
- package/hooks/pre-tool-use-hook.js +42 -0
- package/hooks/prompt-submit-hook.js +66 -0
- package/hooks/session-start-hook.js +30 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
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
|
|
|
@@ -7,6 +7,7 @@ const { execSync, spawnSync } = require('child_process');
|
|
|
7
7
|
|
|
8
8
|
const isGemini = process.env.GEMINI_PROJECT_DIR !== undefined;
|
|
9
9
|
const IS_WIN = process.platform === 'win32';
|
|
10
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.env.GEMINI_PROJECT_DIR || process.env.OC_PROJECT_DIR || process.env.KILO_PROJECT_DIR;
|
|
10
11
|
|
|
11
12
|
// ─── Local tool management ────────────────────────────────────────────────────
|
|
12
13
|
const TOOLS_DIR = path.join(os.homedir(), '.claude', 'gm-tools');
|
|
@@ -104,6 +105,24 @@ async function ensureTools() {
|
|
|
104
105
|
// Run tool installation (fire-and-forget, won't block hook)
|
|
105
106
|
ensureTools().catch(() => {});
|
|
106
107
|
|
|
108
|
+
// ─── Lang plugin loader ───────────────────────────────────────────────────────
|
|
109
|
+
function loadLangPlugins(projectDir) {
|
|
110
|
+
if (!projectDir) return [];
|
|
111
|
+
const langDir = path.join(projectDir, 'lang');
|
|
112
|
+
if (!fs.existsSync(langDir)) return [];
|
|
113
|
+
try {
|
|
114
|
+
return fs.readdirSync(langDir)
|
|
115
|
+
.filter(f => f.endsWith('.js') && f !== 'loader.js' && f !== 'SPEC.md')
|
|
116
|
+
.reduce((acc, f) => {
|
|
117
|
+
try {
|
|
118
|
+
const p = require(path.join(langDir, f));
|
|
119
|
+
if (p && typeof p.id === 'string' && p.exec && p.exec.match instanceof RegExp && typeof p.exec.run === 'function') acc.push(p);
|
|
120
|
+
} catch (_) {}
|
|
121
|
+
return acc;
|
|
122
|
+
}, []);
|
|
123
|
+
} catch (_) { return []; }
|
|
124
|
+
}
|
|
125
|
+
|
|
107
126
|
// Helper: run a local binary (falls back to bunx if not installed)
|
|
108
127
|
function runLocal(name, args, opts = {}) {
|
|
109
128
|
const bin = localBin(name);
|
|
@@ -225,6 +244,29 @@ const run = () => {
|
|
|
225
244
|
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
245
|
}
|
|
227
246
|
const cwd = tool_input?.cwd;
|
|
247
|
+
|
|
248
|
+
// ─── Lang plugin dispatch ─────────────────────────────────────────────
|
|
249
|
+
if (rawLang) {
|
|
250
|
+
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']);
|
|
251
|
+
if (!builtins.has(rawLang)) {
|
|
252
|
+
const plugins = loadLangPlugins(projectDir);
|
|
253
|
+
const plugin = plugins.find(p => p.exec.match.test(`exec:${rawLang}\n${code}`));
|
|
254
|
+
if (plugin) {
|
|
255
|
+
const runnerCode = `
|
|
256
|
+
const plugin = require(${JSON.stringify(path.join(projectDir, 'lang', plugin.id + '.js'))});
|
|
257
|
+
Promise.resolve(plugin.exec.run(${JSON.stringify(code)}, ${JSON.stringify(cwd || projectDir || process.cwd())}))
|
|
258
|
+
.then(out => process.stdout.write(String(out || '')))
|
|
259
|
+
.catch(e => { process.stderr.write(e.message || String(e)); process.exit(1); });
|
|
260
|
+
`;
|
|
261
|
+
const r = spawnSync('bun', ['-e', runnerCode], { encoding: 'utf-8', timeout: 30000, windowsHide: true });
|
|
262
|
+
const out = (r.stdout || '').trimEnd();
|
|
263
|
+
const err = (r.stderr || '').trimEnd();
|
|
264
|
+
if (r.status !== 0 || r.error) return allowWithNoop(`exec:${rawLang} error:\n\n${r.error ? r.error.message : (err || 'exec failed')}`);
|
|
265
|
+
return allowWithNoop(`exec:${rawLang} output:\n\n${out || '(no output)'}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
228
270
|
const detectLang = (src) => {
|
|
229
271
|
if (/^\s*(import |from |export |const |let |var |function |class |async |await |console\.|process\.)/.test(src)) return 'nodejs';
|
|
230
272
|
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