gm-cc 2.0.727 → 2.0.1064

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.
Files changed (44) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/agents/gm.md +1 -3
  3. package/agents/memorize.md +22 -2
  4. package/agents/research-worker.md +36 -0
  5. package/agents/textprocessing.md +47 -0
  6. package/bin/bootstrap.js +624 -34
  7. package/bin/plugkit.js +95 -53
  8. package/bin/plugkit.sha256 +6 -6
  9. package/bin/plugkit.version +1 -1
  10. package/bin/rtk.sha256 +6 -0
  11. package/bin/rtk.version +1 -0
  12. package/hooks/hooks.json +2 -46
  13. package/hooks/hooks.spec.json +48 -0
  14. package/package.json +2 -2
  15. package/plugin.json +1 -1
  16. package/skills/browser/SKILL.md +18 -16
  17. package/skills/code-search/SKILL.md +15 -15
  18. package/skills/create-lang-plugin/SKILL.md +22 -26
  19. package/skills/gm/SKILL.md +31 -66
  20. package/skills/gm-cc/SKILL.md +19 -0
  21. package/skills/gm-codex/SKILL.md +19 -0
  22. package/skills/gm-complete/SKILL.md +52 -69
  23. package/skills/gm-copilot-cli/SKILL.md +19 -0
  24. package/skills/gm-cursor/SKILL.md +19 -0
  25. package/skills/gm-emit/SKILL.md +44 -61
  26. package/skills/gm-execute/SKILL.md +42 -84
  27. package/skills/gm-gc/SKILL.md +19 -0
  28. package/skills/gm-jetbrains/SKILL.md +19 -0
  29. package/skills/gm-kilo/SKILL.md +19 -0
  30. package/skills/gm-oc/SKILL.md +19 -0
  31. package/skills/gm-vscode/SKILL.md +19 -0
  32. package/skills/gm-zed/SKILL.md +19 -0
  33. package/skills/governance/SKILL.md +24 -23
  34. package/skills/pages/SKILL.md +42 -92
  35. package/skills/planning/SKILL.md +83 -80
  36. package/skills/research/SKILL.md +43 -0
  37. package/skills/ssh/SKILL.md +15 -9
  38. package/skills/textprocessing/SKILL.md +40 -0
  39. package/skills/update-docs/SKILL.md +27 -21
  40. package/.github/workflows/publish-npm.yml +0 -44
  41. package/hooks/post-tool-use-hook.js +0 -34
  42. package/hooks/pre-tool-use-hook.js +0 -45
  43. package/hooks/prompt-submit-hook.js +0 -19
  44. package/hooks/session-start-hook.js +0 -23
package/bin/plugkit.js CHANGED
@@ -1,68 +1,110 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
- const { spawn, spawnSync } = require('child_process');
3
+ // Hot path: spawnSync to ~/.claude/gm-tools/plugkit.exe with inherited stdio.
4
+ // Cold path (session-start / prompt-submit OR missing binary): synchronously
5
+ // ensure gm-tools/plugkit{.exe} matches the pinned version, then run hook.
6
+ // Cache-aware: when local matches the pin (sha-checked), zero network calls.
7
+
8
+ const { spawnSync } = require('child_process');
4
9
  const path = require('path');
5
10
  const fs = require('fs');
6
- const { bootstrap, resolveCachedBinary } = require('./bootstrap');
11
+ const os = require('os');
7
12
 
8
- const dir = __dirname;
13
+ const wrapperDir = __dirname;
9
14
 
10
- async function resolveBinary() {
11
- const cached = resolveCachedBinary({ wrapperDir: dir });
12
- if (cached) return cached;
13
- return await bootstrap({ wrapperDir: dir });
15
+ function toolsBin() {
16
+ const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
17
+ const exe = process.platform === 'win32' ? 'plugkit.exe' : 'plugkit';
18
+ return path.join(home, '.claude', 'gm-tools', exe);
14
19
  }
15
20
 
16
- async function main() {
17
- const args = process.argv.slice(2);
18
- const isHook = args[0] === 'hook';
19
- let bin;
21
+ function sha256OfFileSync(filePath) {
20
22
  try {
21
- bin = await resolveBinary();
22
- } catch (err) {
23
- process.stderr.write(`[plugkit] bootstrap failed: ${err.message}\n`);
24
- const legacy = legacyFallback();
25
- if (legacy) { bin = legacy; }
26
- else if (isHook) { process.exit(0); }
27
- else process.exit(1);
28
- }
23
+ const crypto = require('crypto');
24
+ const h = crypto.createHash('sha256');
25
+ const fd = fs.openSync(filePath, 'r');
26
+ try {
27
+ const buf = Buffer.alloc(1 << 20);
28
+ let n;
29
+ while ((n = fs.readSync(fd, buf, 0, buf.length, null)) > 0) h.update(buf.subarray(0, n));
30
+ } finally { fs.closeSync(fd); }
31
+ return h.digest('hex');
32
+ } catch (_) { return null; }
33
+ }
29
34
 
30
- if (isHook && !process.stdin.isTTY) {
31
- const chunks = [];
32
- process.stdin.on('data', c => chunks.push(c));
33
- process.stdin.on('end', () => {
34
- const child = spawn(bin, args, { stdio: ['pipe', 'inherit', 'inherit'], windowsHide: true });
35
- child.stdin.end(Buffer.concat(chunks));
36
- child.on('close', code => process.exit(code ?? 1));
37
- child.on('error', () => process.exit(1));
38
- });
39
- process.stdin.on('error', () => process.exit(1));
40
- } else {
41
- const result = spawnSync(bin, args, { stdio: 'inherit', windowsHide: true });
42
- process.exit(result.status ?? 1);
43
- }
35
+ function platformAsset() {
36
+ const p = process.platform;
37
+ const a = process.arch;
38
+ if (p === 'win32') return a === 'arm64' ? 'plugkit-win32-arm64.exe' : 'plugkit-win32-x64.exe';
39
+ if (p === 'darwin') return a === 'arm64' ? 'plugkit-darwin-arm64' : 'plugkit-darwin-x64';
40
+ return (a === 'arm64' || a === 'aarch64') ? 'plugkit-linux-arm64' : 'plugkit-linux-x64';
44
41
  }
45
42
 
46
- function legacyFallback() {
47
- const os = require('os');
48
- const p = os.platform();
49
- const a = os.arch();
50
- let candidates = [];
51
- if (p === 'win32') {
52
- candidates = [
53
- path.join(dir, a === 'arm64' ? 'plugkit-win32-arm64.exe' : 'plugkit-win32-x64.exe'),
54
- path.join(dir, 'plugkit.exe'),
55
- ];
56
- } else if (p === 'darwin') {
57
- candidates = [path.join(dir, a === 'arm64' ? 'plugkit-darwin-arm64' : 'plugkit-darwin-x64')];
58
- } else {
59
- candidates = [path.join(dir, (a === 'arm64' || a === 'aarch64') ? 'plugkit-linux-arm64' : 'plugkit-linux-x64')];
60
- }
61
- for (const c of candidates) if (fs.existsSync(c)) return c;
43
+ function readPinnedVersion() {
44
+ try { return fs.readFileSync(path.join(wrapperDir, 'plugkit.version'), 'utf8').trim(); } catch (_) { return null; }
45
+ }
46
+
47
+ function readExpectedSha() {
48
+ try {
49
+ const manifest = fs.readFileSync(path.join(wrapperDir, 'plugkit.sha256'), 'utf8');
50
+ const asset = platformAsset();
51
+ for (const line of manifest.split(/\r?\n/)) {
52
+ const parts = line.trim().split(/\s+/);
53
+ if (parts.length >= 2 && parts[parts.length - 1].replace(/^\*/, '') === asset) {
54
+ return parts[0].toLowerCase();
55
+ }
56
+ }
57
+ } catch (_) {}
62
58
  return null;
63
59
  }
64
60
 
65
- main().catch(err => {
66
- process.stderr.write(`[plugkit] fatal: ${err.message}\n`);
67
- process.exit(1);
68
- });
61
+ // Returns true if gm-tools binary matches pinned version by sha. Fast: no network.
62
+ function isReady() {
63
+ const bin = toolsBin();
64
+ if (!fs.existsSync(bin)) return false;
65
+ const expected = readExpectedSha();
66
+ if (!expected) return true; // no manifest to compare against — trust existence
67
+ const actual = sha256OfFileSync(bin);
68
+ return actual && actual.toLowerCase() === expected;
69
+ }
70
+
71
+ // Synchronously run bootstrap.js in a child node. Blocks until install finishes
72
+ // (or fails). Bootstrap itself is cache-aware: re-download only when sha differs
73
+ // from manifest. Wraps stdio:inherit so the user sees progress.
74
+ function ensureReady(silent) {
75
+ if (isReady()) return true;
76
+ const bootstrap = path.join(wrapperDir, 'bootstrap.js');
77
+ const r = spawnSync(process.execPath, [bootstrap], {
78
+ stdio: silent ? ['ignore', 'pipe', 'pipe'] : ['ignore', 'inherit', 'inherit'],
79
+ windowsHide: true,
80
+ });
81
+ return r.status === 0 && isReady();
82
+ }
83
+
84
+ function main() {
85
+ const args = process.argv.slice(2);
86
+ const isHook = args[0] === 'hook';
87
+ const hookSubcmd = isHook ? (args[1] || '') : '';
88
+
89
+ // Synchronous readiness check on these hooks. Hot path: isReady() is sha-match
90
+ // against pinned manifest, returns true in <50ms with no network.
91
+ const blocksUntilReady = hookSubcmd === 'session-start' || hookSubcmd === 'prompt-submit';
92
+
93
+ if (blocksUntilReady) {
94
+ if (!ensureReady(false)) {
95
+ process.stderr.write('[plugkit] bootstrap failed; aborting hook\n');
96
+ process.exit(1);
97
+ }
98
+ } else if (!fs.existsSync(toolsBin())) {
99
+ // For non-blocking hooks (pre-tool-use, post-tool-use, stop, etc.): if the
100
+ // binary doesn't exist yet, exit cleanly — session-start will populate it.
101
+ if (isHook) process.exit(0);
102
+ process.exit(1);
103
+ }
104
+
105
+ const bin = toolsBin();
106
+ const r = spawnSync(bin, args, { stdio: 'inherit', windowsHide: true });
107
+ process.exit(r.status ?? 1);
108
+ }
109
+
110
+ main();
@@ -1,6 +1,6 @@
1
- 9dbd63a59e435a3a8cb8a1b89344ecfa4b4865bdf81ec5a3aa53202645f39b01 plugkit-win32-x64.exe
2
- 18f37964c80c20fe7adaeef4c6168b331d8d23a94ff369d9807f0847cc16c757 plugkit-win32-arm64.exe
3
- 2cd123dd7704bd823d0c655ecb815afb155c8de2b5dafaadd475864af676a72c plugkit-darwin-x64
4
- df98f857def381b366b6346b613bbd4753b62557c374b5768aee9183f52a3dd0 plugkit-darwin-arm64
5
- fcfdbba8a28458da1a8937670d5173f774ec6fe6bcd5b820c0bce5ae8d785660 plugkit-linux-x64
6
- 2b6f8a8c9316f7bb1378752d61928575d4f0fcc739daab7376af66ed1b65d82f plugkit-linux-arm64
1
+ e5569efe81e4ef06c8349678253ca2845571495e619babb0a79be9268ea83c2a plugkit-win32-x64.exe
2
+ ce2f09f8ea0dd522345a9d9c3e5b04ba44bc37b2046d311d7ff1737f1b3fbf1a plugkit-win32-arm64.exe
3
+ a1a1d376986551828e5a39e4ae931accf66f00663aceac1439b2778cb4fffd27 plugkit-darwin-x64
4
+ 7c36d730edab5cddf678211146ca670c9ce1def17d8b454234ce4bc04a4d7e85 plugkit-darwin-arm64
5
+ c9db60a399caf53c490dc08705713c7d83a1f62db057585a3950d64ab8fa449a plugkit-linux-x64
6
+ b9ebabaace995b1768d1d96ae13ca18a6dc5e2ca65b774fcdd457f069a7d115c plugkit-linux-arm64
@@ -1 +1 @@
1
- 0.1.241
1
+ 0.1.366
package/bin/rtk.sha256 ADDED
@@ -0,0 +1,6 @@
1
+ 53224d66572e937507a8d2877c768e4e6cc3da66aa6f8d0f132afaab2edc2a10 rtk-win32-x64.exe
2
+ dedabc1d89641c60c91f09570353b6270dba4f5d53f8597018a708e515265d53 rtk-win32-arm64.exe
3
+ cf3190554b82c7395948b7a478c78bbe2241549b00777e660deff4cbb9e0c4b6 rtk-darwin-x64
4
+ c815bad459b4eaccc8be4a5d74dba397fdfe7d3716e0b6023b188d2351128b82 rtk-darwin-arm64
5
+ 7d60dd5abc15f6d46ffd89b5de7253a067e2a3ef6f1cd8ae5a236eda05a504f4 rtk-linux-x64
6
+ cd5dd78035845eef4b362927c61f61e23925af3c12779131024d8334bad87a6b rtk-linux-arm64
@@ -0,0 +1 @@
1
+ 0.40.0
package/hooks/hooks.json CHANGED
@@ -9,11 +9,6 @@
9
9
  "type": "command",
10
10
  "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/plugkit.js hook pre-tool-use",
11
11
  "timeout": 3600
12
- },
13
- {
14
- "type": "command",
15
- "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pre-tool-use-hook.js",
16
- "timeout": 2000
17
12
  }
18
13
  ]
19
14
  }
@@ -24,8 +19,8 @@
24
19
  "hooks": [
25
20
  {
26
21
  "type": "command",
27
- "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-use-hook.js",
28
- "timeout": 3000
22
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/plugkit.js hook post-tool-use",
23
+ "timeout": 35000
29
24
  }
30
25
  ]
31
26
  }
@@ -38,11 +33,6 @@
38
33
  "type": "command",
39
34
  "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/plugkit.js hook session-start",
40
35
  "timeout": 180000
41
- },
42
- {
43
- "type": "command",
44
- "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/session-start-hook.js",
45
- "timeout": 3000
46
36
  }
47
37
  ]
48
38
  }
@@ -55,40 +45,6 @@
55
45
  "type": "command",
56
46
  "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/plugkit.js hook prompt-submit",
57
47
  "timeout": 60000
58
- },
59
- {
60
- "type": "command",
61
- "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/prompt-submit-hook.js",
62
- "timeout": 3000
63
- }
64
- ]
65
- }
66
- ],
67
- "PreCompact": [
68
- {
69
- "matcher": "*",
70
- "hooks": [
71
- {
72
- "type": "command",
73
- "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/plugkit.js hook pre-compact",
74
- "timeout": 30000
75
- }
76
- ]
77
- }
78
- ],
79
- "Stop": [
80
- {
81
- "matcher": "*",
82
- "hooks": [
83
- {
84
- "type": "command",
85
- "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/plugkit.js hook stop",
86
- "timeout": 15000
87
- },
88
- {
89
- "type": "command",
90
- "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/plugkit.js hook stop-git",
91
- "timeout": 210000
92
48
  }
93
49
  ]
94
50
  }
@@ -0,0 +1,48 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "description": "Hook spec for gm Claude Code extension",
4
+ "envVar": "CLAUDE_PLUGIN_ROOT",
5
+ "plugkitInvoker": "node",
6
+ "events": [
7
+ {
8
+ "eventKey": "PreToolUse",
9
+ "commands": [
10
+ {
11
+ "kind": "plugkit",
12
+ "subcommand": "pre-tool-use",
13
+ "timeout": 3600
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "eventKey": "PostToolUse",
19
+ "commands": [
20
+ {
21
+ "kind": "plugkit",
22
+ "subcommand": "post-tool-use",
23
+ "timeout": 35000
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ "eventKey": "SessionStart",
29
+ "commands": [
30
+ {
31
+ "kind": "plugkit",
32
+ "subcommand": "session-start",
33
+ "timeout": 180000
34
+ }
35
+ ]
36
+ },
37
+ {
38
+ "eventKey": "UserPromptSubmit",
39
+ "commands": [
40
+ {
41
+ "kind": "plugkit",
42
+ "subcommand": "prompt-submit",
43
+ "timeout": 60000
44
+ }
45
+ ]
46
+ }
47
+ ]
48
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-cc",
3
- "version": "2.0.727",
3
+ "version": "2.0.1064",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
@@ -58,4 +58,4 @@
58
58
  "optional": true
59
59
  }
60
60
  }
61
- }
61
+ }
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.727",
3
+ "version": "2.0.1064",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": {
6
6
  "name": "AnEntrypoint",
@@ -1,18 +1,18 @@
1
1
  ---
2
2
  name: browser
3
3
  description: Browser automation via playwriter. Use when user needs to interact with websites, navigate pages, fill forms, click buttons, take screenshots, extract data, test web apps, or automate any browser task.
4
- allowed-tools: Bash(browser:*), Bash(exec:browser*)
4
+ allowed-tools: Skill
5
5
  ---
6
6
 
7
- # Browser Automation
7
+ # Browser automation
8
8
 
9
- Two pathways — never mix:
9
+ Two pathways — never mix in the same Bash call.
10
10
 
11
- **`exec:browser`** JS against `page`. `page`, `snapshot`, `screenshotWithAccessibilityLabels`, `state` globals available. 15s live window then backgrounds; drains auto on every subsequent plugkit call.
11
+ `exec:browser` runs JS against `page`. Globals available: `page`, `snapshot`, `screenshotWithAccessibilityLabels`, `state`. 15s live window, then backgrounds; output drains automatically on every subsequent plugkit call.
12
12
 
13
- **`browser:` prefix** playwriter session management. One command per block.
13
+ `browser:` prefix is playwriter session management. One command per block.
14
14
 
15
- ## Core Usage
15
+ ## Core
16
16
 
17
17
  ```
18
18
  exec:browser
@@ -30,11 +30,11 @@ browser:
30
30
  playwriter -s 1 -e 'await page.goto("http://example.com")'
31
31
  ```
32
32
 
33
- Session state persists across `browser:` calls. `-e` arg: single quotes outside, double quotes inside JS strings.
33
+ Session state persists across `browser:` calls. `-e` arg uses single quotes outside, double inside JS strings.
34
34
 
35
35
  ## Timing
36
36
 
37
- Never `await setTimeout(N)` with N > 10000. Use poll loops:
37
+ Never `await setTimeout(N)` with N > 10000. Poll instead.
38
38
 
39
39
  ```
40
40
  exec:browser
@@ -45,11 +45,12 @@ while (!state.done && Date.now() - start < 12000) {
45
45
  console.log(state.result)
46
46
  ```
47
47
 
48
- "Assertion failed: UV_HANDLE_CLOSING" = backgrounded normally, ignore noise.
48
+ `Assertion failed: UV_HANDLE_CLOSING` is normal background-on-exit noise; ignore it.
49
49
 
50
- ## Common Patterns
50
+ ## Patterns
51
51
 
52
52
  Data extraction:
53
+
53
54
  ```
54
55
  exec:browser
55
56
  const items = await page.$$eval('.title', els => els.map(e => e.textContent))
@@ -57,6 +58,7 @@ console.log(JSON.stringify(items))
57
58
  ```
58
59
 
59
60
  Console monitoring — set listeners first, then poll:
61
+
60
62
  ```
61
63
  exec:browser
62
64
  state.logs = []
@@ -68,10 +70,10 @@ exec:browser
68
70
  console.log(JSON.stringify(state.logs.slice(-20)))
69
71
  ```
70
72
 
71
- ## Rules
73
+ ## Constraints
72
74
 
73
- - One `playwriter` command per `browser:` block
74
- - Never mix pathways in same Bash call
75
- - `exec:browser` = plain JS, no shell quoting
76
- - All browser tasks drain automatically on every plugkit interaction
77
- - Sessions reap after 5-15min idle; browser cleaned up on session end
75
+ - One playwriter command per `browser:` block
76
+ - `exec:browser` is plain JS, no shell quoting
77
+ - Browser tasks drain automatically on every plugkit interaction
78
+ - Sessions reap after 5–15 min idle; cleaned up on session end
79
+ - Never write standalone `.mjs`/`.js` Playwright scripts as a fallback — `exec:browser` errors must be debugged through `exec:browser` retries, not by creating test files on disk
@@ -3,13 +3,15 @@ name: code-search
3
3
  description: Mandatory codebase search workflow. Use whenever you need to find anything in the codebase. Start with two words, iterate by changing or adding words until found.
4
4
  ---
5
5
 
6
- # CODEBASE SEARCH
6
+ # Codebase search
7
7
 
8
- `exec:codesearch` is the only codebase search tool. `Grep`, `Glob`, `Find`, `Explore`, `grep`/`rg`/`find` inside `exec:bash` = ALL hook-blocked. No fallback path.
8
+ `exec:codesearch` is the only codebase search tool. Grep, Glob, Find, Explore, raw `grep`/`rg`/`find` inside `exec:bash` are all hook-blocked. No fallback.
9
9
 
10
- Handles: exact symbols, exact strings, file-name fragments, regex-ish patterns, natural-language queries, PDF pages (cite `path/doc.pdf:<page>`).
10
+ A `@<discipline>` first-token after the verb scopes the search to that discipline's index; absent the sigil, results fan across default plus enabled disciplines, prefixed by source.
11
11
 
12
- Direct-read exceptions: known absolute path `Read`. Known dir listing `exec:nodejs` + `fs.readdirSync`.
12
+ Handles exact symbols, exact strings, file-name fragments, regex-ish patterns, natural-language queries, and PDF pages (cite `path/doc.pdf:<page>`).
13
+
14
+ Direct-read exceptions: known absolute path → `Read`. Known directory listing → `exec:nodejs` + `fs.readdirSync`.
13
15
 
14
16
  ## Syntax
15
17
 
@@ -18,15 +20,9 @@ exec:codesearch
18
20
  <two-word query>
19
21
  ```
20
22
 
21
- ## Protocol
22
-
23
- 1. Start: exactly two words
24
- 2. No results → change one word
25
- 3. Still no → add third word
26
- 4. Still no → swap changed word again
27
- 5. Minimum 4 attempts before concluding absent
23
+ ## Iteration
28
24
 
29
- Never: one word | full sentence | give up under 4 attempts | switch tools.
25
+ Start at exactly two words. No results → change one word. Still none add a third. Still none → swap the changed word again. Minimum four attempts before concluding absent. Never one word, never a full sentence, never switch tools.
30
26
 
31
27
  ## Examples
32
28
 
@@ -34,15 +30,19 @@ Never: one word | full sentence | give up under 4 attempts | switch tools.
34
30
  exec:codesearch
35
31
  session cleanup idle
36
32
  ```
37
- → no results →
33
+
34
+ No results, then:
35
+
38
36
  ```
39
37
  exec:codesearch
40
38
  cleanup sessions timeout
41
39
  ```
42
40
 
43
- PDF search:
41
+ PDF:
42
+
44
43
  ```
45
44
  exec:codesearch
46
45
  usb descriptor endpoint
47
46
  ```
48
- → returns `docs/usb-spec.pdf:42` — cite page, Read if you need surrounding text.
47
+
48
+ Returns `docs/usb-spec.pdf:42` — cite the page; `Read` if surrounding text is needed.
@@ -3,48 +3,40 @@ name: create-lang-plugin
3
3
  description: Create a lang/ plugin that wires any CLI tool or language runtime into gm-cc — adds exec:<id> dispatch, optional LSP diagnostics, and optional prompt context injection. Zero hook configuration required.
4
4
  ---
5
5
 
6
- # CREATE LANG PLUGIN
6
+ # Create lang plugin
7
7
 
8
8
  Single CommonJS file at `<projectDir>/lang/<id>.js`. Auto-discovered — no hook editing.
9
9
 
10
- ## Plugin Shape
10
+ ## Plugin shape
11
11
 
12
12
  ```js
13
13
  'use strict';
14
14
  module.exports = {
15
- id: 'mytool', // must match filename
15
+ id: 'mytool',
16
16
  exec: {
17
17
  match: /^exec:mytool/,
18
18
  run(code, cwd) { /* returns string or Promise<string> */ }
19
19
  },
20
- lsp: { // optional — synchronous only
20
+ lsp: {
21
21
  check(fileContent, cwd) { /* returns Diagnostic[] */ }
22
22
  },
23
- extensions: ['.ext'], // optional — for lsp.check
24
- context: `=== mytool ===\n...` // optional — string or () => string
23
+ extensions: ['.ext'],
24
+ context: `=== mytool ===\n...`
25
25
  };
26
26
  ```
27
27
 
28
28
  `type Diagnostic = { line: number; col: number; severity: 'error'|'warning'; message: string }`
29
29
 
30
- ## How It Works
30
+ `exec.run` runs in a child process, 30s timeout, async OK. Called when Claude writes `exec:mytool\n<code>`. `lsp.check` is synchronous-only, called per prompt-submit. `context` is injected into every prompt, truncated to 2000 chars.
31
31
 
32
- - `exec.run` child process, 30s timeout, async OK. Called when Claude writes `exec:mytool\n<code>`.
33
- - `lsp.check` — synchronous, called per prompt submit. Use `spawnSync`/`execFileSync`. No async.
34
- - `context` — injected into every prompt (truncated 2000 chars).
32
+ ## Identify the tool
35
33
 
36
- ## Step 1 Identify Tool
34
+ What is the CLI name or npm package? Does it run a single expression (`tool eval`, `tool -e`, HTTP POST) or a file (`tool run <file>`)? What is its lint/check mode and output format? File extensions? Does it require a running server, or does it run headless?
37
35
 
38
- 1. CLI name or npm package?
39
- 2. Run single expression? (`tool eval <expr>`, `tool -e <code>`, HTTP POST...)
40
- 3. Run file? (`tool run <file>`)
41
- 4. Lint/check mode + output format?
42
- 5. File extensions?
43
- 6. Requires running server or headless?
36
+ ## exec.run patterns
44
37
 
45
- ## Step 2 exec.run Patterns
38
+ HTTP eval against a running server:
46
39
 
47
- HTTP eval (running server):
48
40
  ```js
49
41
  function httpPost(port, urlPath, body) {
50
42
  return new Promise((resolve, reject) => {
@@ -61,7 +53,8 @@ function httpPost(port, urlPath, body) {
61
53
  }
62
54
  ```
63
55
 
64
- File-based (headless):
56
+ File-based, headless:
57
+
65
58
  ```js
66
59
  function runFile(code, cwd) {
67
60
  const tmp = path.join(os.tmpdir(), `plugin_${Date.now()}.ext`);
@@ -71,12 +64,13 @@ function runFile(code, cwd) {
71
64
  }
72
65
  ```
73
66
 
74
- Single expr detection:
67
+ Single-expression detection:
68
+
75
69
  ```js
76
70
  const isSingleExpr = code => !code.trim().includes('\n') && !/\b(func|def|fn |class|import)\b/.test(code);
77
71
  ```
78
72
 
79
- ## Step 3 — lsp.check
73
+ ## lsp.check
80
74
 
81
75
  ```js
82
76
  function check(fileContent, cwd) {
@@ -94,14 +88,15 @@ function check(fileContent, cwd) {
94
88
  }
95
89
  ```
96
90
 
97
- ## Step 4 — context String
91
+ ## context
98
92
 
99
93
  Under 300 chars:
94
+
100
95
  ```js
101
96
  context: `=== mytool ===\nexec:mytool\n<expression>\n\nRuns via <how>. Use for <when>.`
102
97
  ```
103
98
 
104
- ## Step 5 — Write + Verify
99
+ ## Verify
105
100
 
106
101
  ```
107
102
  exec:nodejs
@@ -110,6 +105,7 @@ console.log(p.id, typeof p.exec.run, p.exec.match.toString());
110
105
  ```
111
106
 
112
107
  Then test dispatch:
108
+
113
109
  ```
114
110
  exec:mytool
115
111
  <simple test expression>
@@ -117,9 +113,9 @@ exec:mytool
117
113
 
118
114
  ## Constraints
119
115
 
120
- - `exec.run` async OK (30s timeout)
116
+ - `exec.run` async OK, 30s timeout
121
117
  - `lsp.check` synchronous only — no Promises
122
118
  - CommonJS only — no ES module syntax
123
119
  - No persistent processes
124
120
  - `id` must match filename exactly
125
- - First match wins — make `match` specific
121
+ - First match wins — keep `match` specific