gm-codex 2.0.202 → 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 +2 -2
- package/hooks/pre-tool-use-hook.js +41 -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
|
|
|
@@ -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