gm-cc 2.0.631 → 2.0.632
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/.claude-plugin/marketplace.json +1 -1
- package/bin/plugkit-darwin-arm64 +0 -0
- package/bin/plugkit-darwin-x64 +0 -0
- package/bin/plugkit-linux-arm64 +0 -0
- package/bin/plugkit-linux-x64 +0 -0
- package/bin/plugkit-win32-arm64.exe +0 -0
- package/bin/plugkit-win32-x64.exe +0 -0
- package/bin/plugkit.exe +0 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/browser/SKILL.md +24 -149
- package/skills/code-search/SKILL.md +14 -47
- package/skills/create-lang-plugin/SKILL.md +47 -105
- package/skills/gm/SKILL.md +20 -48
- package/skills/gm-complete/SKILL.md +54 -139
- package/skills/gm-emit/SKILL.md +49 -133
- package/skills/gm-execute/SKILL.md +52 -140
- package/skills/governance/SKILL.md +73 -98
- package/skills/planning/SKILL.md +54 -127
- package/skills/ssh/SKILL.md +10 -37
- package/skills/update-docs/SKILL.md +21 -74
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"name": "AnEntrypoint"
|
|
5
5
|
},
|
|
6
6
|
"description": "State machine agent with hooks, skills, and automated git enforcement",
|
|
7
|
-
"version": "2.0.
|
|
7
|
+
"version": "2.0.632",
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "State machine agent with hooks, skills, and automated git enforcement"
|
|
10
10
|
},
|
package/bin/plugkit-darwin-arm64
CHANGED
|
Binary file
|
package/bin/plugkit-darwin-x64
CHANGED
|
Binary file
|
package/bin/plugkit-linux-arm64
CHANGED
|
Binary file
|
package/bin/plugkit-linux-x64
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/bin/plugkit.exe
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/plugin.json
CHANGED
package/skills/browser/SKILL.md
CHANGED
|
@@ -4,199 +4,74 @@ description: Browser automation via playwriter. Use when user needs to interact
|
|
|
4
4
|
allowed-tools: Bash(browser:*), Bash(exec:browser*)
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Browser Automation
|
|
7
|
+
# Browser Automation
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Two pathways — never mix:
|
|
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
12
|
|
|
12
|
-
|
|
13
|
+
**`browser:` prefix** — playwriter session management. One command per block.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
**JS execution** (`exec:browser`) — run JavaScript directly against `page`. State persists across calls via `state` global.
|
|
17
|
-
|
|
18
|
-
**CRITICAL**: Never mix these two pathways. Each `browser:` block is a separate Bash call. Each `exec:browser` block is a separate Bash call.
|
|
19
|
-
|
|
20
|
-
## 15-Second Ceiling — How It Works
|
|
21
|
-
|
|
22
|
-
Every `exec:browser` call has a 15s live window. During that window, all stdout/stderr is streamed to you in real time. After 15s the task backgrounds and you receive:
|
|
23
|
-
- All output produced so far (live drain)
|
|
24
|
-
- A task ID with `plugkit sleep/status/close` instructions
|
|
25
|
-
|
|
26
|
-
**The task keeps running.** Every subsequent plugkit interaction automatically drains all running browser tasks — you will see new output without asking.
|
|
27
|
-
|
|
28
|
-
**Never use `await new Promise(r => setTimeout(r, N))` with N > 10000.** Use short poll loops instead (see patterns below).
|
|
29
|
-
|
|
30
|
-
**"Assertion failed: UV_HANDLE_CLOSING" in output** means the call exceeded 15s and was cut off — ignore the assertion noise, look at the output before it. The task was backgrounded normally.
|
|
31
|
-
|
|
32
|
-
## Idle Timeout & Session Reaper
|
|
33
|
-
|
|
34
|
-
Playwriter kills idle browser sessions after 5-15 minutes of inactivity. The rs-exec tooling now automatically cleans up the spawned browser process when the Claude Code session ends, preventing zombie tabs.
|
|
35
|
-
|
|
36
|
-
**Historical note**: Earlier versions left the browser running after session end, causing repeated tabs on reconnect. This is now fixed — the browser will be killed when your session idles and closes.
|
|
37
|
-
|
|
38
|
-
## Session Pathway (`browser:`)
|
|
39
|
-
|
|
40
|
-
Create a session first, use `--direct` for CDP mode (requires Chrome with remote debugging):
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
browser:
|
|
44
|
-
playwriter session new --direct
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Returns a numeric session ID (e.g. `1`). Use that ID for all subsequent calls. **Each command must be a separate Bash call:**
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
browser:
|
|
51
|
-
playwriter -s 1 -e 'await page.goto("http://example.com")'
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
browser:
|
|
56
|
-
playwriter -s 1 -e 'await snapshot({ page })'
|
|
57
|
-
```
|
|
15
|
+
## Core Usage
|
|
58
16
|
|
|
59
17
|
```
|
|
60
|
-
browser
|
|
61
|
-
|
|
18
|
+
exec:browser
|
|
19
|
+
await page.goto('https://example.com')
|
|
20
|
+
await snapshot({ page })
|
|
62
21
|
```
|
|
63
22
|
|
|
64
|
-
State persists across session calls:
|
|
65
|
-
|
|
66
23
|
```
|
|
67
24
|
browser:
|
|
68
|
-
playwriter
|
|
25
|
+
playwriter session new --direct
|
|
69
26
|
```
|
|
70
27
|
|
|
71
28
|
```
|
|
72
29
|
browser:
|
|
73
|
-
playwriter -s 1 -e '
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
**RULE**: The `-e` argument must use single quotes. The JS inside must use double quotes for strings.
|
|
78
|
-
|
|
79
|
-
**RULE**: Never chain multiple `playwriter` commands in one `browser:` block — run one command per block.
|
|
80
|
-
|
|
81
|
-
## JS Execution Pathway (`exec:browser`)
|
|
82
|
-
|
|
83
|
-
For direct page access, DOM queries, and data extraction. The runtime provides `page`, `snapshot`, `screenshotWithAccessibilityLabels`, and `state` as globals.
|
|
84
|
-
|
|
85
|
-
```
|
|
86
|
-
exec:browser
|
|
87
|
-
await page.goto('https://example.com')
|
|
88
|
-
await snapshot({ page })
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
```
|
|
92
|
-
exec:browser
|
|
93
|
-
const title = await page.title()
|
|
94
|
-
console.log(title)
|
|
30
|
+
playwriter -s 1 -e 'await page.goto("http://example.com")'
|
|
95
31
|
```
|
|
96
32
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
## Core Workflow
|
|
100
|
-
|
|
101
|
-
1. **Navigate**: `exec:browser\nawait page.goto('url')` — session auto-created on first call
|
|
102
|
-
2. **Snapshot**: `exec:browser\nawait snapshot({ page })`
|
|
103
|
-
3. **Interact**: click, fill, type in subsequent `exec:browser` calls
|
|
104
|
-
4. **Extract data**: `exec:browser\nconsole.log(await page.evaluate(() => document.title))`
|
|
33
|
+
Session state persists across `browser:` calls. `-e` arg: single quotes outside, double quotes inside JS strings.
|
|
105
34
|
|
|
106
|
-
##
|
|
35
|
+
## Timing
|
|
107
36
|
|
|
108
|
-
|
|
37
|
+
Never `await setTimeout(N)` with N > 10000. Use poll loops:
|
|
109
38
|
|
|
110
|
-
**Step 1** — set up listener and kick off the operation:
|
|
111
|
-
```
|
|
112
|
-
exec:browser
|
|
113
|
-
state.done = false
|
|
114
|
-
state.result = null
|
|
115
|
-
page.on('console', msg => {
|
|
116
|
-
const t = msg.text()
|
|
117
|
-
if (t.includes('loaded') || t.includes('ready')) { state.done = true; state.result = t }
|
|
118
|
-
})
|
|
119
|
-
await page.click('#start-button')
|
|
120
|
-
console.log('started, waiting...')
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
**Step 2** — poll in short bursts (this will background after 15s and keep draining):
|
|
124
39
|
```
|
|
125
40
|
exec:browser
|
|
126
41
|
const start = Date.now()
|
|
127
42
|
while (!state.done && Date.now() - start < 12000) {
|
|
128
43
|
await new Promise(r => setTimeout(r, 500))
|
|
129
44
|
}
|
|
130
|
-
console.log(
|
|
45
|
+
console.log(state.result)
|
|
131
46
|
```
|
|
132
47
|
|
|
133
|
-
|
|
134
|
-
```
|
|
135
|
-
exec:close
|
|
136
|
-
task_N
|
|
137
|
-
```
|
|
48
|
+
"Assertion failed: UV_HANDLE_CLOSING" = backgrounded normally, ignore noise.
|
|
138
49
|
|
|
139
50
|
## Common Patterns
|
|
140
51
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
### Data Extraction
|
|
144
|
-
|
|
52
|
+
Data extraction:
|
|
145
53
|
```
|
|
146
54
|
exec:browser
|
|
147
|
-
const items = await page.$$eval('.
|
|
55
|
+
const items = await page.$$eval('.title', els => els.map(e => e.textContent))
|
|
148
56
|
console.log(JSON.stringify(items))
|
|
149
57
|
```
|
|
150
58
|
|
|
151
|
-
|
|
152
|
-
### Console Monitoring — set up listener first, then poll
|
|
153
|
-
|
|
59
|
+
Console monitoring — set listeners first, then poll:
|
|
154
60
|
```
|
|
155
61
|
exec:browser
|
|
156
62
|
state.logs = []
|
|
157
|
-
state.errors = []
|
|
158
63
|
page.on('console', msg => state.logs.push({ type: msg.type(), text: msg.text() }))
|
|
159
|
-
page.on('pageerror', e => state.errors.push(e.message))
|
|
160
|
-
console.log('listeners attached')
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
```
|
|
164
|
-
exec:browser
|
|
165
|
-
console.log('logs so far:', JSON.stringify(state.logs.slice(-20)))
|
|
166
|
-
console.log('errors:', JSON.stringify(state.errors))
|
|
167
64
|
```
|
|
168
65
|
|
|
169
66
|
```
|
|
170
67
|
exec:browser
|
|
171
|
-
|
|
172
|
-
const r = await page.workers()[0].evaluate(() => JSON.stringify({ type: 'worker alive' }))
|
|
173
|
-
console.log(r)
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
exec:browser
|
|
178
|
-
const result = await page.evaluate(() => JSON.stringify({
|
|
179
|
-
entityCount: window.debug?.scene?.children?.length,
|
|
180
|
-
playerId: window.debug?.client?.playerId
|
|
181
|
-
}))
|
|
182
|
-
console.log(result)
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
exec:browser
|
|
186
|
-
const start = Date.now()
|
|
187
|
-
while (Date.now() - start < 12000) {
|
|
188
|
-
const el = await page.$('#status')
|
|
189
|
-
if (el) { console.log('found:', await el.textContent()); break }
|
|
190
|
-
await new Promise(r => setTimeout(r, 300))
|
|
191
|
-
}
|
|
68
|
+
console.log(JSON.stringify(state.logs.slice(-20)))
|
|
192
69
|
```
|
|
193
70
|
|
|
194
|
-
##
|
|
71
|
+
## Rules
|
|
195
72
|
|
|
196
|
-
- `browser:` prefix → playwriter session management (one command per block)
|
|
197
|
-
- `exec:browser` → JS in page context (multi-line JS allowed, 15s live window)
|
|
198
|
-
- Never mix pathways in the same Bash call
|
|
199
|
-
- `-e` argument: single quotes on outside, double quotes inside for JS strings
|
|
200
73
|
- One `playwriter` command per `browser:` block
|
|
201
|
-
- Never
|
|
202
|
-
-
|
|
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
|
|
@@ -3,53 +3,33 @@ 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
|
-
|
|
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.
|
|
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
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
**What codesearch handles** (every codebase-lookup need lands here):
|
|
14
|
-
- Exact identifier / symbol lookup (function names, class names, constants) — symbols are extracted and indexed separately, exact matches rank top.
|
|
15
|
-
- Exact string content — query tokens >1 trigger a literal-substring boost in content scoring.
|
|
16
|
-
- File-name fragments — file paths are tokenized and matched with a score boost.
|
|
17
|
-
- Regex-ish patterns — BM25 tokenization covers snake_case, camelCase, dot/dash splits; matching component words returns the file.
|
|
18
|
-
- Natural-language concept queries — BM25 + vector re-ranking handle "find the hook that blocks grep", "where is PR stats calculated", etc.
|
|
19
|
-
- PDF pages — specs, papers, manuals, RFCs, datasheets, design docs extracted page-by-page into the same index. Cite `path/to/doc.pdf:<page>`.
|
|
20
|
-
|
|
21
|
-
**Direct-read exceptions** (no search required):
|
|
22
|
-
- Known absolute path → `Read` tool.
|
|
23
|
-
- Listing a known directory → `exec:nodejs` + `fs.readdirSync`.
|
|
24
|
-
|
|
25
|
-
Unscanned digital PDFs are a search gap — if you know a doc exists and it isn't returning, check it is not under an ignored dir and that extraction succeeded (encrypted / image-only PDFs yield empty chunks silently).
|
|
12
|
+
Direct-read exceptions: known absolute path → `Read`. Known dir listing → `exec:nodejs` + `fs.readdirSync`.
|
|
26
13
|
|
|
27
14
|
## Syntax
|
|
28
15
|
|
|
29
16
|
```
|
|
30
17
|
exec:codesearch
|
|
31
|
-
<
|
|
18
|
+
<two-word query>
|
|
32
19
|
```
|
|
33
20
|
|
|
34
|
-
##
|
|
35
|
-
|
|
36
|
-
**Start with exactly two words.** Never start broader. Never start with one word.
|
|
21
|
+
## Protocol
|
|
37
22
|
|
|
38
|
-
|
|
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
|
|
39
28
|
|
|
40
|
-
|
|
41
|
-
2. No results → change one word (synonym, related term)
|
|
42
|
-
3. Still no results → add a third word (narrow scope)
|
|
43
|
-
4. Still no results → swap the changed word again
|
|
44
|
-
5. Keep iterating — changing or adding words each pass — until content is found
|
|
45
|
-
|
|
46
|
-
**Never**: start with one word | start with a sentence | give up after one miss | switch to a different tool | declare content missing after fewer than 4 search attempts
|
|
47
|
-
|
|
48
|
-
**Each search is one `exec:codesearch` call.** Run them sequentially — use each result to inform the next query.
|
|
29
|
+
Never: one word | full sentence | give up under 4 attempts | switch tools.
|
|
49
30
|
|
|
50
31
|
## Examples
|
|
51
32
|
|
|
52
|
-
Finding where a function is defined:
|
|
53
33
|
```
|
|
54
34
|
exec:codesearch
|
|
55
35
|
session cleanup idle
|
|
@@ -59,23 +39,10 @@ session cleanup idle
|
|
|
59
39
|
exec:codesearch
|
|
60
40
|
cleanup sessions timeout
|
|
61
41
|
```
|
|
62
|
-
→ found.
|
|
63
|
-
|
|
64
|
-
Finding config format:
|
|
65
|
-
```
|
|
66
|
-
exec:codesearch
|
|
67
|
-
plugin registration format
|
|
68
|
-
```
|
|
69
|
-
→ no results →
|
|
70
|
-
```
|
|
71
|
-
exec:codesearch
|
|
72
|
-
plugin config array
|
|
73
|
-
```
|
|
74
|
-
→ found.
|
|
75
42
|
|
|
76
|
-
|
|
43
|
+
PDF search:
|
|
77
44
|
```
|
|
78
45
|
exec:codesearch
|
|
79
46
|
usb descriptor endpoint
|
|
80
47
|
```
|
|
81
|
-
→ returns `docs/usb-spec.pdf:42` — cite page,
|
|
48
|
+
→ returns `docs/usb-spec.pdf:42` — cite page, Read if you need surrounding text.
|
|
@@ -5,68 +5,54 @@ description: Create a lang/ plugin that wires any CLI tool or language runtime i
|
|
|
5
5
|
|
|
6
6
|
# CREATE LANG PLUGIN
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Single CommonJS file at `<projectDir>/lang/<id>.js`. Auto-discovered — no hook editing.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
A lang plugin is a single CommonJS file at `<projectDir>/lang/<id>.js`. gm-cc's hooks auto-discover it — no hook editing, no settings changes. The plugin gets three integration points: **exec dispatch**, **LSP diagnostics**, and **context injection**.
|
|
12
|
-
|
|
13
|
-
## PLUGIN SHAPE
|
|
10
|
+
## Plugin Shape
|
|
14
11
|
|
|
15
12
|
```js
|
|
16
13
|
'use strict';
|
|
17
14
|
module.exports = {
|
|
18
|
-
id: 'mytool',
|
|
15
|
+
id: 'mytool', // must match filename
|
|
19
16
|
exec: {
|
|
20
|
-
match: /^exec:mytool/,
|
|
21
|
-
run(code, cwd) {
|
|
22
|
-
// ...
|
|
23
|
-
}
|
|
17
|
+
match: /^exec:mytool/,
|
|
18
|
+
run(code, cwd) { /* returns string or Promise<string> */ }
|
|
24
19
|
},
|
|
25
|
-
lsp: {
|
|
26
|
-
check(fileContent, cwd) {
|
|
27
|
-
// ...
|
|
28
|
-
}
|
|
20
|
+
lsp: { // optional — synchronous only
|
|
21
|
+
check(fileContent, cwd) { /* returns Diagnostic[] */ }
|
|
29
22
|
},
|
|
30
|
-
extensions: ['.ext'],
|
|
31
|
-
context: `=== mytool ===\n...`
|
|
23
|
+
extensions: ['.ext'], // optional — for lsp.check
|
|
24
|
+
context: `=== mytool ===\n...` // optional — string or () => string
|
|
32
25
|
};
|
|
33
26
|
```
|
|
34
27
|
|
|
35
|
-
|
|
36
|
-
type Diagnostic = { line: number; col: number; severity: 'error'|'warning'; message: string };
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## HOW IT WORKS
|
|
40
|
-
|
|
41
|
-
- **`exec.run`** is called in a child process (30s timeout) when Claude writes `exec:mytool\n<code>`. Output is returned as `exec:mytool output:\n\n<result>`. Async is fine here.
|
|
42
|
-
- **`lsp.check`** is called synchronously in the hook process on each prompt submit — must NOT be async. Use `execFileSync` or `spawnSync`.
|
|
43
|
-
- **`context`** is injected into every prompt's `additionalContext` (truncated to 2000 chars) and into the session-start context.
|
|
44
|
-
- **`match`** regex is tested against the full command string `exec:mytool\n<code>` — keep it simple: `/^exec:mytool/`.
|
|
28
|
+
`type Diagnostic = { line: number; col: number; severity: 'error'|'warning'; message: string }`
|
|
45
29
|
|
|
46
|
-
##
|
|
30
|
+
## How It Works
|
|
47
31
|
|
|
48
|
-
|
|
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).
|
|
49
35
|
|
|
50
|
-
|
|
51
|
-
2. How do you run a single expression/snippet? (`tool eval <expr>`, `tool -e <code>`, HTTP POST, ...)
|
|
52
|
-
3. How do you run a file? (`tool run <file>`, `tool <file>`, ...)
|
|
53
|
-
4. Does it have a lint/check mode? What does its output format look like?
|
|
54
|
-
5. What file extensions does it apply to?
|
|
55
|
-
6. Is the game/server running required, or does it work headlessly?
|
|
36
|
+
## Step 1 — Identify Tool
|
|
56
37
|
|
|
57
|
-
|
|
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?
|
|
58
44
|
|
|
59
|
-
|
|
45
|
+
## Step 2 — exec.run Patterns
|
|
60
46
|
|
|
47
|
+
HTTP eval (running server):
|
|
61
48
|
```js
|
|
62
|
-
const http = require('http');
|
|
63
49
|
function httpPost(port, urlPath, body) {
|
|
64
50
|
return new Promise((resolve, reject) => {
|
|
65
51
|
const data = JSON.stringify(body);
|
|
66
52
|
const req = http.request(
|
|
67
53
|
{ hostname: '127.0.0.1', port, path: urlPath, method: 'POST',
|
|
68
54
|
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } },
|
|
69
|
-
|
|
55
|
+
res => { let raw = ''; res.on('data', c => raw += c); res.on('end', () => resolve(JSON.parse(raw))); }
|
|
70
56
|
);
|
|
71
57
|
req.setTimeout(8000, () => { req.destroy(); reject(new Error('timeout')); });
|
|
72
58
|
req.on('error', reject);
|
|
@@ -75,109 +61,65 @@ function httpPost(port, urlPath, body) {
|
|
|
75
61
|
}
|
|
76
62
|
```
|
|
77
63
|
|
|
78
|
-
|
|
79
|
-
|
|
64
|
+
File-based (headless):
|
|
80
65
|
```js
|
|
81
|
-
const fs = require('fs');
|
|
82
|
-
const os = require('os');
|
|
83
|
-
const path = require('path');
|
|
84
|
-
const { execFileSync } = require('child_process');
|
|
85
|
-
|
|
86
66
|
function runFile(code, cwd) {
|
|
87
67
|
const tmp = path.join(os.tmpdir(), `plugin_${Date.now()}.ext`);
|
|
88
68
|
fs.writeFileSync(tmp, code);
|
|
89
|
-
try {
|
|
90
|
-
|
|
91
|
-
} finally {
|
|
92
|
-
try { fs.unlinkSync(tmp); } catch (_) {}
|
|
93
|
-
}
|
|
69
|
+
try { return execFileSync('mytool', ['run', tmp], { cwd, encoding: 'utf8', timeout: 10000 }); }
|
|
70
|
+
finally { try { fs.unlinkSync(tmp); } catch (_) {} }
|
|
94
71
|
}
|
|
95
72
|
```
|
|
96
73
|
|
|
97
|
-
|
|
98
|
-
|
|
74
|
+
Single expr detection:
|
|
99
75
|
```js
|
|
100
|
-
|
|
101
|
-
return !code.trim().includes('\n') && !/\b(func|def|fn |class|import)\b/.test(code);
|
|
102
|
-
}
|
|
76
|
+
const isSingleExpr = code => !code.trim().includes('\n') && !/\b(func|def|fn |class|import)\b/.test(code);
|
|
103
77
|
```
|
|
104
78
|
|
|
105
|
-
##
|
|
106
|
-
|
|
107
|
-
Must be **synchronous**. Parse the tool's stderr/stdout for diagnostics:
|
|
79
|
+
## Step 3 — lsp.check
|
|
108
80
|
|
|
109
81
|
```js
|
|
110
|
-
const { spawnSync } = require('child_process');
|
|
111
|
-
const fs = require('fs');
|
|
112
|
-
const os = require('os');
|
|
113
|
-
const path = require('path');
|
|
114
|
-
|
|
115
82
|
function check(fileContent, cwd) {
|
|
116
83
|
const tmp = path.join(os.tmpdir(), `lsp_${Math.random().toString(36).slice(2)}.ext`);
|
|
117
84
|
try {
|
|
118
85
|
fs.writeFileSync(tmp, fileContent);
|
|
119
86
|
const r = spawnSync('mytool', ['check', tmp], { encoding: 'utf8', cwd });
|
|
120
|
-
|
|
121
|
-
return output.split('\n').reduce((acc, line) => {
|
|
87
|
+
return (r.stdout + r.stderr).split('\n').reduce((acc, line) => {
|
|
122
88
|
const m = line.match(/^.+:(\d+):(\d+):\s+(error|warning):\s+(.+)$/);
|
|
123
|
-
if (m) acc.push({ line:
|
|
89
|
+
if (m) acc.push({ line: +m[1], col: +m[2], severity: m[3], message: m[4].trim() });
|
|
124
90
|
return acc;
|
|
125
91
|
}, []);
|
|
126
|
-
} catch (_) {
|
|
127
|
-
|
|
128
|
-
} finally {
|
|
129
|
-
try { fs.unlinkSync(tmp); } catch (_) {}
|
|
130
|
-
}
|
|
92
|
+
} catch (_) { return []; }
|
|
93
|
+
finally { try { fs.unlinkSync(tmp); } catch (_) {} }
|
|
131
94
|
}
|
|
132
95
|
```
|
|
133
96
|
|
|
134
|
-
|
|
135
|
-
- `file:line:col: error: message` → standard
|
|
136
|
-
- `file:line: E001: message` → gdlint style (`E`=error, `W`=warning)
|
|
137
|
-
- JSON output → `JSON.parse(r.stdout).errors.map(...)`
|
|
138
|
-
|
|
139
|
-
## STEP 4 — WRITE context STRING
|
|
140
|
-
|
|
141
|
-
Describe what `exec:<id>` does and when to use it. This appears in every prompt. Keep it under 300 chars:
|
|
97
|
+
## Step 4 — context String
|
|
142
98
|
|
|
99
|
+
Under 300 chars:
|
|
143
100
|
```js
|
|
144
|
-
context: `=== mytool
|
|
145
|
-
exec:mytool
|
|
146
|
-
<expression or code block>
|
|
147
|
-
|
|
148
|
-
Runs via <how>. Use for <when>.`
|
|
101
|
+
context: `=== mytool ===\nexec:mytool\n<expression>\n\nRuns via <how>. Use for <when>.`
|
|
149
102
|
```
|
|
150
103
|
|
|
151
|
-
##
|
|
152
|
-
|
|
153
|
-
File goes at `lang/<id>.js` in the project root. The `id` field must match the filename (without `.js`).
|
|
154
|
-
|
|
155
|
-
Verify after writing:
|
|
104
|
+
## Step 5 — Write + Verify
|
|
156
105
|
|
|
157
106
|
```
|
|
158
107
|
exec:nodejs
|
|
159
|
-
const p = require('/abs/path/
|
|
108
|
+
const p = require('/abs/path/lang/mytool.js');
|
|
160
109
|
console.log(p.id, typeof p.exec.run, p.exec.match.toString());
|
|
161
110
|
```
|
|
162
111
|
|
|
163
112
|
Then test dispatch:
|
|
164
|
-
|
|
165
113
|
```
|
|
166
114
|
exec:mytool
|
|
167
|
-
<
|
|
115
|
+
<simple test expression>
|
|
168
116
|
```
|
|
169
117
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
## CONSTRAINTS
|
|
173
|
-
|
|
174
|
-
- `exec.run` may be async — it runs in a child process with a 30s timeout
|
|
175
|
-
- `lsp.check` must be synchronous — no Promises, no async/await
|
|
176
|
-
- Plugin must be CommonJS (`module.exports = { ... }`) — no ES module syntax
|
|
177
|
-
- No persistent processes — `exec.run` must complete and exit cleanly
|
|
178
|
-
- `id` must match the filename exactly
|
|
179
|
-
- First match wins — if multiple plugins could match, make `match` specific
|
|
180
|
-
|
|
181
|
-
## EXAMPLE — gdscript plugin (reference implementation)
|
|
118
|
+
## Constraints
|
|
182
119
|
|
|
183
|
-
|
|
120
|
+
- `exec.run` async OK (30s timeout)
|
|
121
|
+
- `lsp.check` synchronous only — no Promises
|
|
122
|
+
- CommonJS only — no ES module syntax
|
|
123
|
+
- No persistent processes
|
|
124
|
+
- `id` must match filename exactly
|
|
125
|
+
- First match wins — make `match` specific
|