gm-skill 0.1.2 → 2.0.1081
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.md +1 -0
- package/LICENSE +21 -0
- package/README.md +20 -84
- package/agents/gm.md +22 -0
- package/agents/memorize.md +100 -0
- package/agents/research-worker.md +36 -0
- package/agents/textprocessing.md +47 -0
- package/bin/bootstrap.js +702 -0
- package/bin/plugkit.js +136 -0
- package/bin/plugkit.sha256 +7 -0
- package/bin/plugkit.version +1 -0
- package/bin/plugkit.wasm +0 -0
- package/bin/plugkit.wasm.sha256 +1 -0
- package/bin/rtk.sha256 +6 -0
- package/bin/rtk.version +1 -0
- package/gm-plugkit/bootstrap.js +694 -0
- package/gm-plugkit/cli.js +48 -0
- package/gm-plugkit/index.js +12 -0
- package/gm-plugkit/package.json +26 -0
- package/gm-plugkit/plugkit-wasm-wrapper.js +190 -0
- package/gm-plugkit/plugkit.sha256 +6 -0
- package/gm-plugkit/plugkit.version +1 -0
- package/gm.json +27 -0
- package/lang/browser.js +45 -0
- package/lang/ssh.js +166 -0
- package/lib/browser-spool-handler.js +130 -0
- package/lib/browser.js +131 -0
- package/lib/codeinsight.js +109 -0
- package/lib/daemon-bootstrap.js +253 -132
- package/lib/git.js +0 -1
- package/lib/learning.js +169 -0
- package/lib/skill-bootstrap.js +406 -0
- package/lib/spool-dispatch.js +100 -0
- package/lib/spool.js +87 -49
- package/lib/wasm-host.js +241 -0
- package/package.json +38 -20
- package/prompts/bash-deny.txt +22 -0
- package/prompts/pre-compact.txt +21 -0
- package/prompts/prompt-submit.txt +83 -0
- package/prompts/session-start.txt +15 -0
- package/scripts/run-hook.sh +7 -0
- package/scripts/watch-cascade.js +166 -0
- package/skills/browser/SKILL.md +80 -0
- package/skills/code-search/SKILL.md +48 -0
- package/skills/create-lang-plugin/SKILL.md +121 -0
- package/skills/gm/SKILL.md +10 -49
- package/skills/gm-complete/SKILL.md +16 -87
- package/skills/gm-emit/SKILL.md +17 -50
- package/skills/gm-execute/SKILL.md +18 -69
- package/skills/gm-skill/SKILL.md +43 -0
- package/skills/gm-skill/index.js +21 -0
- package/skills/governance/SKILL.md +97 -0
- package/skills/pages/SKILL.md +208 -0
- package/skills/planning/SKILL.md +21 -97
- package/skills/research/SKILL.md +43 -0
- package/skills/ssh/SKILL.md +71 -0
- package/skills/textprocessing/SKILL.md +40 -0
- package/skills/update-docs/SKILL.md +24 -43
- package/gm-complete.SKILL.md +0 -106
- package/gm-emit.SKILL.md +0 -70
- package/gm-execute.SKILL.md +0 -88
- package/gm.SKILL.md +0 -63
- package/index.js +0 -1
- package/lib/index.js +0 -37
- package/lib/loader.js +0 -66
- package/lib/manifest.js +0 -99
- package/lib/prepare.js +0 -14
- package/planning.SKILL.md +0 -118
- package/skills/gm/index.js +0 -113
- package/skills/gm-complete/index.js +0 -118
- package/skills/gm-complete.SKILL.md +0 -106
- package/skills/gm-emit/index.js +0 -90
- package/skills/gm-emit.SKILL.md +0 -70
- package/skills/gm-execute/index.js +0 -91
- package/skills/gm-execute.SKILL.md +0 -88
- package/skills/gm.SKILL.md +0 -63
- package/skills/planning/index.js +0 -107
- package/skills/planning.SKILL.md +0 -118
- package/skills/update-docs/index.js +0 -108
- package/skills/update-docs.SKILL.md +0 -66
- package/test-build.js +0 -29
- package/test-e2e.js +0 -117
- package/test-unified.js +0 -24
- package/test.js +0 -89
- package/update-docs.SKILL.md +0 -66
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT}}"
|
|
3
|
+
[ -z "$PLUGIN_ROOT" ] && exit 0
|
|
4
|
+
PLUGKIT="$PLUGIN_ROOT/bin/plugkit"
|
|
5
|
+
[ -f "$PLUGIN_ROOT/bin/plugkit.exe" ] && PLUGKIT="$PLUGIN_ROOT/bin/plugkit.exe"
|
|
6
|
+
[ ! -f "$PLUGKIT" ] && exit 0
|
|
7
|
+
"$PLUGKIT" hook "$1"
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { execSync, spawnSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
const REPOS = {
|
|
7
|
+
'rs-exec': 'AnEntrypoint/rs-exec',
|
|
8
|
+
'rs-codeinsight':'AnEntrypoint/rs-codeinsight',
|
|
9
|
+
'rs-search': 'AnEntrypoint/rs-search',
|
|
10
|
+
'rs-plugkit': 'AnEntrypoint/rs-plugkit',
|
|
11
|
+
'gm': 'AnEntrypoint/gm',
|
|
12
|
+
'gm-cc': 'AnEntrypoint/gm-cc',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const POLL_MS = 20000;
|
|
16
|
+
const TIMEOUT_MS = 30 * 60 * 1000;
|
|
17
|
+
|
|
18
|
+
function gh(args) {
|
|
19
|
+
const r = spawnSync('gh', args, { encoding: 'utf8' });
|
|
20
|
+
if (r.status !== 0) throw new Error(r.stderr.trim() || `gh ${args.join(' ')} failed`);
|
|
21
|
+
return r.stdout.trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function latestRun(repo) {
|
|
25
|
+
const out = gh(['run', 'list', '--repo', repo, '--limit', '1', '--json', 'databaseId,status,conclusion,name,headBranch,createdAt']);
|
|
26
|
+
const rows = JSON.parse(out);
|
|
27
|
+
return rows[0] || null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getGmCcSha() {
|
|
31
|
+
return gh(['api', 'repos/AnEntrypoint/gm-cc/git/refs/heads/main', '--jq', '.object.sha']);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getInstalledSha() {
|
|
35
|
+
const os = require('os');
|
|
36
|
+
const path = require('path');
|
|
37
|
+
const fs = require('fs');
|
|
38
|
+
const base = path.join(os.homedir(), '.claude/plugins/cache/gm-cc/gm');
|
|
39
|
+
if (!fs.existsSync(base)) return null;
|
|
40
|
+
const dirs = fs.readdirSync(base).filter(d => /^[0-9a-f]{12,}$/.test(d));
|
|
41
|
+
dirs.sort((a, b) => {
|
|
42
|
+
try {
|
|
43
|
+
const av = JSON.parse(fs.readFileSync(path.join(base, a, 'gm.json'), 'utf8')).version || '0';
|
|
44
|
+
const bv = JSON.parse(fs.readFileSync(path.join(base, b, 'gm.json'), 'utf8')).version || '0';
|
|
45
|
+
return bv.localeCompare(av, undefined, { numeric: true });
|
|
46
|
+
} catch { return 0; }
|
|
47
|
+
});
|
|
48
|
+
if (!dirs[0]) return null;
|
|
49
|
+
const gm = JSON.parse(fs.readFileSync(path.join(base, dirs[0], 'gm.json'), 'utf8'));
|
|
50
|
+
return { hash: dirs[0], version: gm.version, plugkitVersion: gm.plugkitVersion };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getPlugkitVersion() {
|
|
54
|
+
const fs = require('fs');
|
|
55
|
+
const cargo = 'C:/dev/rs-plugkit/Cargo.toml';
|
|
56
|
+
if (!fs.existsSync(cargo)) return null;
|
|
57
|
+
const m = fs.readFileSync(cargo, 'utf8').match(/^version\s*=\s*"([^"]+)"/m);
|
|
58
|
+
return m ? m[1] : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function validate(label, fn) {
|
|
62
|
+
try {
|
|
63
|
+
const result = fn();
|
|
64
|
+
console.log(` ✓ ${label}: ${result}`);
|
|
65
|
+
return true;
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.log(` ✗ ${label}: ${e.message}`);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function watchRun(repo, runId, label) {
|
|
73
|
+
const start = Date.now();
|
|
74
|
+
while (Date.now() - start < TIMEOUT_MS) {
|
|
75
|
+
const out = gh(['run', 'view', String(runId), '--repo', repo, '--json', 'status,conclusion']);
|
|
76
|
+
const { status, conclusion } = JSON.parse(out);
|
|
77
|
+
process.stdout.write(`\r ${label}: ${status} ${conclusion || ''} `);
|
|
78
|
+
if (status === 'completed') {
|
|
79
|
+
process.stdout.write('\n');
|
|
80
|
+
if (conclusion !== 'success') throw new Error(`${label} concluded: ${conclusion}`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
await sleep(POLL_MS);
|
|
84
|
+
}
|
|
85
|
+
throw new Error(`${label} timed out after ${TIMEOUT_MS / 60000}min`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
89
|
+
|
|
90
|
+
async function waitForNewRun(repo, label, afterTime, maxWaitMs = 5 * 60 * 1000) {
|
|
91
|
+
const start = Date.now();
|
|
92
|
+
while (Date.now() - start < maxWaitMs) {
|
|
93
|
+
const run = latestRun(repo);
|
|
94
|
+
if (run && new Date(run.createdAt).getTime() > afterTime) return run;
|
|
95
|
+
process.stdout.write(`\r Waiting for ${label} run to appear... `);
|
|
96
|
+
await sleep(POLL_MS);
|
|
97
|
+
}
|
|
98
|
+
process.stdout.write('\n');
|
|
99
|
+
throw new Error(`No new run appeared in ${repo} within ${maxWaitMs / 60000}min`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function main() {
|
|
103
|
+
const triggerTime = Date.now();
|
|
104
|
+
|
|
105
|
+
console.log('\n=== Cascade Watcher ===');
|
|
106
|
+
console.log('Monitoring full pipeline: rs-{exec,codeinsight,search} → rs-plugkit → gm → gm-cc\n');
|
|
107
|
+
|
|
108
|
+
console.log('[1] Baseline');
|
|
109
|
+
const baseGmCcSha = getGmCcSha();
|
|
110
|
+
const baseInstalled = getInstalledSha();
|
|
111
|
+
const basePlugkitVersion = getPlugkitVersion();
|
|
112
|
+
console.log(` gm-cc HEAD: ${baseGmCcSha}`);
|
|
113
|
+
console.log(` installed hash: ${baseInstalled ? baseInstalled.hash : 'unknown'} (gm v${baseInstalled?.version}, plugkit v${baseInstalled?.plugkitVersion})`);
|
|
114
|
+
console.log(` local plugkit: v${basePlugkitVersion}`);
|
|
115
|
+
|
|
116
|
+
console.log('\n[2] rs-plugkit Release run');
|
|
117
|
+
const plugkitRun = await waitForNewRun('AnEntrypoint/rs-plugkit', 'rs-plugkit Release', triggerTime - 10 * 60 * 1000);
|
|
118
|
+
console.log(` Run #${plugkitRun.databaseId} "${plugkitRun.name}" on ${plugkitRun.headBranch}`);
|
|
119
|
+
await watchRun('AnEntrypoint/rs-plugkit', plugkitRun.databaseId, 'rs-plugkit Release');
|
|
120
|
+
|
|
121
|
+
console.log('\n[3] Validate rs-plugkit version bumped');
|
|
122
|
+
validate('rs-plugkit Cargo.toml version', () => {
|
|
123
|
+
const v = getPlugkitVersion();
|
|
124
|
+
if (!v) throw new Error('Could not read');
|
|
125
|
+
return `v${v}`;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
validate('gm-starter/gm.json plugkitVersion', () => {
|
|
129
|
+
const fs = require('fs');
|
|
130
|
+
const p = 'C:/dev/plugforge/gm-starter/gm.json';
|
|
131
|
+
if (!fs.existsSync(p)) throw new Error('file not found');
|
|
132
|
+
const j = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
133
|
+
return `v${j.plugkitVersion}`;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
console.log('\n[4] gm Build & Publish run');
|
|
137
|
+
const afterPlugkit = Date.now();
|
|
138
|
+
const pfRun = await waitForNewRun('AnEntrypoint/gm', 'Build & Publish Plugins', afterPlugkit - 3 * 60 * 1000);
|
|
139
|
+
console.log(` Run #${pfRun.databaseId} "${pfRun.name}" on ${pfRun.headBranch}`);
|
|
140
|
+
await watchRun('AnEntrypoint/gm', pfRun.databaseId, 'Build & Publish Plugins');
|
|
141
|
+
|
|
142
|
+
console.log('\n[5] Validate gm-cc updated');
|
|
143
|
+
const newGmCcSha = getGmCcSha();
|
|
144
|
+
validate('gm-cc HEAD changed', () => {
|
|
145
|
+
if (newGmCcSha === baseGmCcSha) throw new Error(`still ${baseGmCcSha}`);
|
|
146
|
+
return newGmCcSha;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
console.log('\n[6] Validate local installed plugin (requires /plugin + /reload-plugins)');
|
|
150
|
+
const installed = getInstalledSha();
|
|
151
|
+
validate('installed hash matches gm-cc HEAD prefix', () => {
|
|
152
|
+
if (!installed) throw new Error('no installed plugin found');
|
|
153
|
+
if (!newGmCcSha.startsWith(installed.hash)) throw new Error(`installed ${installed.hash} != gm-cc ${newGmCcSha.slice(0, 12)}`);
|
|
154
|
+
return `${installed.hash} (gm v${installed.version}, plugkit v${installed.plugkitVersion})`;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
console.log('\n=== Cascade complete ===');
|
|
158
|
+
console.log(` gm-cc: ${baseGmCcSha.slice(0, 12)} → ${newGmCcSha.slice(0, 12)}`);
|
|
159
|
+
if (installed && newGmCcSha.startsWith(installed.hash)) {
|
|
160
|
+
console.log(' Local plugin is up to date.');
|
|
161
|
+
} else {
|
|
162
|
+
console.log(' ⚠ Run /plugin then /reload-plugins to update local cache.');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
main().catch(e => { console.error('\nFATAL:', e.message); process.exit(1); });
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser
|
|
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: Skill
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Browser automation
|
|
8
|
+
|
|
9
|
+
Two pathways — never mix in the same spool dispatch.
|
|
10
|
+
|
|
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
|
+
|
|
13
|
+
`browser:` prefix is playwriter session management. One command per block.
|
|
14
|
+
|
|
15
|
+
## Core
|
|
16
|
+
|
|
17
|
+
Write to `.gm/exec-spool/in/browser/<N>.txt`:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
await page.goto('https://example.com')
|
|
21
|
+
await snapshot({ page })
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
browser:
|
|
26
|
+
playwriter session new --direct
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
browser:
|
|
31
|
+
playwriter -s 1 -e 'await page.goto("http://example.com")'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Session state persists across `browser:` calls. `-e` arg uses single quotes outside, double inside JS strings.
|
|
35
|
+
|
|
36
|
+
## Timing
|
|
37
|
+
|
|
38
|
+
Never `await setTimeout(N)` with N > 10000. Poll instead.
|
|
39
|
+
|
|
40
|
+
Write to `.gm/exec-spool/in/browser/<N>.txt`:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
const start = Date.now()
|
|
44
|
+
while (!state.done && Date.now() - start < 12000) {
|
|
45
|
+
await new Promise(r => setTimeout(r, 500))
|
|
46
|
+
}
|
|
47
|
+
console.log(state.result)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`Assertion failed: UV_HANDLE_CLOSING` is normal background-on-exit noise; ignore it.
|
|
51
|
+
|
|
52
|
+
## Patterns
|
|
53
|
+
|
|
54
|
+
Data extraction — write to `.gm/exec-spool/in/browser/<N>.txt`:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
const items = await page.$$eval('.title', els => els.map(e => e.textContent))
|
|
58
|
+
console.log(JSON.stringify(items))
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Console monitoring — set listeners first, then poll. Write to `.gm/exec-spool/in/browser/<N>.txt`:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
state.logs = []
|
|
65
|
+
page.on('console', msg => state.logs.push({ type: msg.type(), text: msg.text() }))
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Then write to `.gm/exec-spool/in/browser/<N+1>.txt`:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
console.log(JSON.stringify(state.logs.slice(-20)))
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Constraints
|
|
75
|
+
|
|
76
|
+
- One playwriter command per `browser:` block
|
|
77
|
+
- `exec:browser` body is plain JS, no shell quoting
|
|
78
|
+
- Browser tasks drain automatically on every plugkit interaction
|
|
79
|
+
- Sessions reap after 5–15 min idle; cleaned up on session end
|
|
80
|
+
- 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
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-search
|
|
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
|
+
---
|
|
5
|
+
|
|
6
|
+
# Codebase search
|
|
7
|
+
|
|
8
|
+
`exec:codesearch` is the only codebase search tool. Never use Grep, Glob, Find, Explore, raw `grep`/`rg`/`find` inside `exec:bash`. No fallback.
|
|
9
|
+
|
|
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
|
+
|
|
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`.
|
|
15
|
+
|
|
16
|
+
## Syntax
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
exec:codesearch
|
|
20
|
+
<two-word query>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Iteration
|
|
24
|
+
|
|
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.
|
|
26
|
+
|
|
27
|
+
## Examples
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
exec:codesearch
|
|
31
|
+
session cleanup idle
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
No results, then:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
exec:codesearch
|
|
38
|
+
cleanup sessions timeout
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
PDF:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
exec:codesearch
|
|
45
|
+
usb descriptor endpoint
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Returns `docs/usb-spec.pdf:42` — cite the page; `Read` if surrounding text is needed.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-lang-plugin
|
|
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
|
+
---
|
|
5
|
+
|
|
6
|
+
# Create lang plugin
|
|
7
|
+
|
|
8
|
+
Single CommonJS file at `<projectDir>/lang/<id>.js`. Auto-discovered — no hook editing.
|
|
9
|
+
|
|
10
|
+
## Plugin shape
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
'use strict';
|
|
14
|
+
module.exports = {
|
|
15
|
+
id: 'mytool',
|
|
16
|
+
exec: {
|
|
17
|
+
match: /^exec:mytool/,
|
|
18
|
+
run(code, cwd) { /* returns string or Promise<string> */ }
|
|
19
|
+
},
|
|
20
|
+
lsp: {
|
|
21
|
+
check(fileContent, cwd) { /* returns Diagnostic[] */ }
|
|
22
|
+
},
|
|
23
|
+
extensions: ['.ext'],
|
|
24
|
+
context: `=== mytool ===\n...`
|
|
25
|
+
};
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`type Diagnostic = { line: number; col: number; severity: 'error'|'warning'; message: string }`
|
|
29
|
+
|
|
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
|
+
|
|
32
|
+
## Identify the tool
|
|
33
|
+
|
|
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?
|
|
35
|
+
|
|
36
|
+
## exec.run patterns
|
|
37
|
+
|
|
38
|
+
HTTP eval against a running server:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
function httpPost(port, urlPath, body) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const data = JSON.stringify(body);
|
|
44
|
+
const req = http.request(
|
|
45
|
+
{ hostname: '127.0.0.1', port, path: urlPath, method: 'POST',
|
|
46
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } },
|
|
47
|
+
res => { let raw = ''; res.on('data', c => raw += c); res.on('end', () => resolve(JSON.parse(raw))); }
|
|
48
|
+
);
|
|
49
|
+
req.setTimeout(8000, () => { req.destroy(); reject(new Error('timeout')); });
|
|
50
|
+
req.on('error', reject);
|
|
51
|
+
req.write(data); req.end();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
File-based, headless:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
function runFile(code, cwd) {
|
|
60
|
+
const tmp = path.join(os.tmpdir(), `plugin_${Date.now()}.ext`);
|
|
61
|
+
fs.writeFileSync(tmp, code);
|
|
62
|
+
try { return execFileSync('mytool', ['run', tmp], { cwd, encoding: 'utf8', timeout: 10000 }); }
|
|
63
|
+
finally { try { fs.unlinkSync(tmp); } catch (_) {} }
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Single-expression detection:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
const isSingleExpr = code => !code.trim().includes('\n') && !/\b(func|def|fn |class|import)\b/.test(code);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## lsp.check
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
function check(fileContent, cwd) {
|
|
77
|
+
const tmp = path.join(os.tmpdir(), `lsp_${Math.random().toString(36).slice(2)}.ext`);
|
|
78
|
+
try {
|
|
79
|
+
fs.writeFileSync(tmp, fileContent);
|
|
80
|
+
const r = spawnSync('mytool', ['check', tmp], { encoding: 'utf8', cwd });
|
|
81
|
+
return (r.stdout + r.stderr).split('\n').reduce((acc, line) => {
|
|
82
|
+
const m = line.match(/^.+:(\d+):(\d+):\s+(error|warning):\s+(.+)$/);
|
|
83
|
+
if (m) acc.push({ line: +m[1], col: +m[2], severity: m[3], message: m[4].trim() });
|
|
84
|
+
return acc;
|
|
85
|
+
}, []);
|
|
86
|
+
} catch (_) { return []; }
|
|
87
|
+
finally { try { fs.unlinkSync(tmp); } catch (_) {} }
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## context
|
|
92
|
+
|
|
93
|
+
Under 300 chars:
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
context: `=== mytool ===\nexec:mytool\n<expression>\n\nRuns via <how>. Use for <when>.`
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Verify
|
|
100
|
+
|
|
101
|
+
Write to `.gm/exec-spool/in/nodejs/<N>.js`:
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
const p = require('/abs/path/lang/mytool.js');
|
|
105
|
+
console.log(p.id, typeof p.exec.run, p.exec.match.toString());
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Then test dispatch by writing to `.gm/exec-spool/in/mytool/<N>.txt`:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
<simple test expression>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Constraints
|
|
115
|
+
|
|
116
|
+
- `exec.run` async OK, 30s timeout
|
|
117
|
+
- `lsp.check` synchronous only — no Promises
|
|
118
|
+
- CommonJS only — no ES module syntax
|
|
119
|
+
- No persistent processes
|
|
120
|
+
- `id` must match filename exactly
|
|
121
|
+
- First match wins — keep `match` specific
|
package/skills/gm/SKILL.md
CHANGED
|
@@ -1,63 +1,24 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gm
|
|
3
3
|
description: Orchestrator dispatching PLAN→EXECUTE→EMIT→VERIFY→UPDATE-DOCS skill chain; spool-driven task execution with session isolation
|
|
4
|
-
allowed-tools: Skill
|
|
5
|
-
compatible-platforms:
|
|
6
|
-
- gm-cc
|
|
7
|
-
- gm-gc
|
|
8
|
-
- gm-oc
|
|
9
|
-
- gm-kilo
|
|
10
|
-
- gm-codex
|
|
11
|
-
- gm-copilot-cli
|
|
12
|
-
- gm-vscode
|
|
13
|
-
- gm-cursor
|
|
14
|
-
- gm-zed
|
|
15
|
-
- gm-jetbrains
|
|
4
|
+
allowed-tools: Skill, Read, Write
|
|
16
5
|
end-to-end: true
|
|
17
6
|
---
|
|
18
7
|
|
|
19
|
-
#
|
|
8
|
+
# gm — ORCHESTRATOR
|
|
20
9
|
|
|
21
|
-
|
|
10
|
+
The user's request is the authorization. The PRD is the receipt. Once the user has spoken, the chain runs to COMPLETE without re-asking, without permission gates between phases, without narrating each step as if it were a deliverable. Re-asking "want me to do X?" after the user said "do X" is forced closure dressed as deference.
|
|
22
11
|
|
|
23
|
-
|
|
12
|
+
When scope exceeds reach, the response is a maximal cover, not a single slice with the rest deferred. Distributed refusal is the same failure dressed as triage. Pick the wider read, declare the read in one line so the user can interrupt mid-chain, execute.
|
|
24
13
|
|
|
25
|
-
|
|
14
|
+
The skill chain is one continuous motion: PLAN → EXECUTE → EMIT → VERIFY → UPDATE-DOCS. No stop between phases. No approval gates. No summarizing-as-completion. The next skill fires the moment the current skill's transition is named. A skill that ends without invoking its successor has stalled the chain.
|
|
26
15
|
|
|
27
|
-
|
|
16
|
+
## Dispatch
|
|
28
17
|
|
|
29
|
-
|
|
18
|
+
Every operation routes through the spool. Write `.gm/exec-spool/in/<verb>/<N>.txt` with the body. Read `.gm/exec-spool/out/<N>.json`. The orchestrator owns FSM state; the skill reads `nextSkill` and dispatches.
|
|
30
19
|
|
|
31
|
-
|
|
20
|
+
Verbs available here: `phase-status`, `transition`, `mutable-resolve` (auto-fires memorize), `memorize-fire`, plus `recall`, `codesearch`, `memorize`, `health`, all language stems.
|
|
32
21
|
|
|
33
|
-
|
|
22
|
+
## Transition
|
|
34
23
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
**Session isolation**: SESSION_ID environment variable (or uuid fallback) threads through task dispatch for cleanup scope. rs-exec RPC handlers verify session_id match on all task-scoped operations.
|
|
38
|
-
|
|
39
|
-
**Code does mechanics; meaning routes through textprocessing skill**: summarize, classify, extract intent, rewrite, translate, semantic dedup, rank, label — all via `Agent(subagent_type='gm:textprocessing', ...)`.
|
|
40
|
-
|
|
41
|
-
**Recall before fresh execution**: before witnessing unknown via execution, recall first. Hits arrive as weak_prior; empty results confirm fresh unknown.
|
|
42
|
-
|
|
43
|
-
**Memorize is the back-half of witness**: resolution incomplete until fact lives outside this context window. Fire `Agent(subagent_type='gm:memorize', model='haiku', run_in_background=true, prompt='## CONTEXT TO MEMORIZE\n<fact>')` alongside witness, in parallel, never blocking.
|
|
44
|
-
|
|
45
|
-
**Parallel independent items**: up to 3 `gm:gm` subagents per message for independent PRD items. Serial for dependent items — no re-asking between them.
|
|
46
|
-
|
|
47
|
-
**Terse response**: fragments OK. `[thing] [action] [reason]. [next step].` Code, commits, PRs use normal prose.
|
|
48
|
-
|
|
49
|
-
## End-to-End Phase Chaining (Skills-Based Platforms)
|
|
50
|
-
|
|
51
|
-
When `end-to-end: true` is present in SKILL.md frontmatter, skill output includes structured JSON on stdout (final line):
|
|
52
|
-
|
|
53
|
-
```json
|
|
54
|
-
{"nextSkill": "gm-execute" | "gm-emit" | "gm-complete" | "update-docs" | null, "context": {PRD and state dict}, "phase": "PLAN" | "EXECUTE" | "EMIT" | "COMPLETE"}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Platform adapters (vscode, cursor, zed, jetbrains) that support `end-to-end: true` detection:
|
|
58
|
-
1. Invoke `Skill(skill="gm:gm")`
|
|
59
|
-
2. Parse stdout for trailing JSON blob
|
|
60
|
-
3. If `nextSkill` is non-null, invoke `Skill(skill="gm:<nextSkill>")` with context dict auto-passed
|
|
61
|
-
4. Repeat until `nextSkill` is null
|
|
62
|
-
|
|
63
|
-
This collapses 5 manual skill invocations into 1 user invocation + 4 transparent auto-dispatches, achieving perceived single-flow parity with gm-cc's subagent orchestration.
|
|
24
|
+
Read `out/<N>.json::nextSkill`. Invoke `Skill(skill="gm:<nextSkill>")` immediately. End of skill body — no trailing narration, no "I will now". The invocation IS the transition.
|
|
@@ -1,106 +1,35 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gm-complete
|
|
3
3
|
description: VERIFY and COMPLETE phase. End-to-end system verification and git enforcement. Any new unknown triggers immediate snake back to planning — restart chain.
|
|
4
|
+
allowed-tools: Skill, Read, Write
|
|
4
5
|
---
|
|
5
6
|
|
|
6
|
-
#
|
|
7
|
+
# gm-complete — VERIFY
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
COMPLETE is earned, not declared. Three preconditions: git clean, pushed to remote, CI green. Any one missing means the phase has not concluded.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
## CI Is The Build
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
For Rust crates (rs-exec, rs-codeinsight, rs-search, rs-learn, rs-plugkit) and the gm publish chain, `git push` triggers the build matrix across six target platforms. `cargo build` and `cargo test` are not run locally — a local build covers exactly one platform and proves nothing about the other five. Push, watch CI, fix on red. Toolchain mismatches and rustc skew never block a push.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
- `.prd` empty AND test.js green AND pushed AND CI green → `update-docs`
|
|
16
|
-
- Broken file output → `gm-emit`
|
|
17
|
-
- Wrong logic → `gm-execute`
|
|
18
|
-
- New unknown or wrong requirements → `planning`
|
|
15
|
+
Watch protocol: after push, poll `gh run list --branch <branch> --limit 3 --json status,conclusion,name` until the run completes, up to `GM_CI_WATCH_SECS` (default 180). On red, triage the failure shape (import error → check manifests; type error → snake to PLAN; test failure → root cause; lint → fix in-band; build timeout → re-trigger once, else PRD `blockedBy: external`). Fix at root, push, re-watch. Green CI is the precondition for VERIFY → UPDATE-DOCS.
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
## Single Integration Test
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
One `test.js` at project root. 200-line hard cap. No fixtures, no mocks, no scattered test files. `gm-complete` runs it. Failure = regression to EXECUTE. Prefer compaction over expansion when editing: merge groups, drop redundancy.
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
- `browser_validated` — for any change touching client / UI / browser-facing code, see gate below. test.js + node-side imports DO NOT satisfy this gate.
|
|
26
|
-
- `git_clean` — `git status --porcelain` returns empty
|
|
27
|
-
- `git_pushed` — `git log origin/main..HEAD --oneline` returns empty
|
|
28
|
-
- `ci_passed` — every GitHub Actions run reaches `conclusion: success`
|
|
29
|
-
- `mutables_resolved` — `.gm/mutables.yml` deleted OR every entry `status: witnessed`. Stop hook hard-blocks turn-stop while any entry is `status: unknown`.
|
|
30
|
-
- `prd_empty` — `.gm/prd.yml` deleted AFTER residual scan: enumerate every in-spirit reachable residual surfaced this session; any hit re-enters `planning`, appends PRD items, executes. Empty PRD is necessary, not sufficient — done = empty PRD AND zero reachable in-spirit residuals. Out-of-spirit-or-unreachable residuals are named in the response and skipped; everything else is this turn's work.
|
|
31
|
-
- `stress_suite_clear` — change walked through M1–D1 (governance), none flunked
|
|
32
|
-
- `hidden_decision_posture` — open → down_weighted → closed only when CI is green AND stress suite is clear
|
|
21
|
+
## Residual-Scan Gate
|
|
33
22
|
|
|
34
|
-
|
|
23
|
+
Before allowing transition to update-docs, fire the `residual-scan` verb. Empty PRD is necessary but not sufficient — the gate asks what the agent should have decided to do but did not. Either re-enter planning with appended items and execute, or explicitly state "residual scan: none reachable in-spirit." The `.gm/residual-check-fired` marker makes this one-shot per stopping window. Common residuals: pre-existing build break surfaced this turn, neighboring lint failure, obvious refactor win, observability gap, doc drift, follow-on work the user clearly implied.
|
|
35
24
|
|
|
36
|
-
|
|
25
|
+
## Git Gate
|
|
37
26
|
|
|
38
|
-
|
|
39
|
-
const { fn } = await import('/abs/path/to/module.js');
|
|
40
|
-
console.log(await fn(realInput));
|
|
41
|
-
```
|
|
27
|
+
`git status` clean. `git log` shows the commit pushed. `gh run list` shows the most recent run for the branch concluded green. All three witnessed before transition.
|
|
42
28
|
|
|
43
|
-
|
|
29
|
+
## Dispatch
|
|
44
30
|
|
|
45
|
-
|
|
31
|
+
`phase-status`, `transition`, `residual-scan`. Spool the CI watch through `in/bash/` so timeouts respect the spool budget.
|
|
46
32
|
|
|
47
|
-
|
|
33
|
+
## Transition
|
|
48
34
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Protocol: boot the real server (or open the static page) on a known URL — witness HTTP 200. `exec:browser` → `page.goto(url)` → wait for app init by polling for the global the change affects (`window.__app.<system>`). Probe via `page.evaluate(() => …)` asserting the specific invariant the change was supposed to establish — instance counts, scene meshes, DOM nodes, render stats, network frames. Capture witnessed numbers in the response — "looks fine" is not a witness. Failures route to `gm-execute` (logic) or `gm-emit` (output) — never paper over.
|
|
52
|
-
|
|
53
|
-
Long-running probes split into navigate-call → `exec:wait N` → probe-call to stay under the per-call budget. Do not stack multi-second `setTimeout` inside one `exec:browser` invocation.
|
|
54
|
-
|
|
55
|
-
Exempt only when: change is server-only with zero browser-facing surface, OR the repository has no browser surface at all (pure CLI / library). Exemption requires explicit tag in the response: `BROWSER EXEMPT: <reason — must reference diff paths showing zero browser-facing surface>`. Default posture is NOT exempt — burden is on the agent to prove exemption with diff evidence.
|
|
56
|
-
|
|
57
|
-
Pre-flight: run `git diff --name-only origin/main..HEAD` directly via Bash, then dispatch a nodejs spool file that reads the diff list and filters lines matching `client/|docs/|\.html$|\.glsl$|\.frag$|\.vert$`. Any hit AND no `exec:browser` block in this session → mandatory regression to `gm-execute`.
|
|
58
|
-
|
|
59
|
-
## Integration test gate
|
|
60
|
-
|
|
61
|
-
Write to `.gm/exec-spool/in/nodejs/<N>.js`:
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
const { execSync } = require('child_process');
|
|
65
|
-
try { execSync('node test.js', { stdio: 'inherit', timeout: 30000 }); console.log('PASS'); }
|
|
66
|
-
catch (e) { console.error('FAIL'); process.exit(1); }
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Failure → `gm-execute`. No test.js in a repo with testable surface → `gm-execute` to create it.
|
|
70
|
-
|
|
71
|
-
## Git enforcement
|
|
72
|
-
|
|
73
|
-
Run directly via Bash:
|
|
74
|
-
|
|
75
|
-
```
|
|
76
|
-
git status --porcelain
|
|
77
|
-
git log origin/main..HEAD --oneline
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Both must return empty. Local commit without push is not complete.
|
|
81
|
-
|
|
82
|
-
## CI is automated
|
|
83
|
-
|
|
84
|
-
The Stop hook watches Actions for the pushed HEAD. Do not call `gh run list` manually. All-green → Stop approves with CI summary in next-turn context. Failure → Stop blocks with run names + IDs; investigate via `gh run view <id> --log-failed`, fix, push, hook re-watches. Deadline 180s (override `GM_CI_WATCH_SECS`); slow jobs get a "still in progress" approve.
|
|
85
|
-
|
|
86
|
-
## Hygiene sweep
|
|
87
|
-
|
|
88
|
-
1. Files >200 lines → split
|
|
89
|
-
2. Comments in code → remove
|
|
90
|
-
3. Scattered test files (`.test.js`, `.spec.js`, `__tests__/`, `fixtures/`, `mocks/`) → delete, consolidate into root `test.js`
|
|
91
|
-
4. Mock / stub / simulation files → delete
|
|
92
|
-
5. Unnecessary doc files (not CHANGELOG, CLAUDE, README, TODO.md) → delete
|
|
93
|
-
6. Duplicate concern → regress to `planning` with restructuring instructions
|
|
94
|
-
7. Hardcoded values → derive from ground truth
|
|
95
|
-
8. Fallback / demo modes → remove, fail loud
|
|
96
|
-
9. TODO.md → empty or deleted
|
|
97
|
-
10. CHANGELOG.md → entries for this session
|
|
98
|
-
11. Observability gaps → server subsystems expose `/debug/<subsystem>`; client modules register in `window.__debug`
|
|
99
|
-
12. Memorize → every fact from verification handed off via background `Agent(memorize)` at moment of resolution
|
|
100
|
-
13. Deploy / publish → if deployable, deploy; if npm package, publish
|
|
101
|
-
14. GitHub Pages → check `.github/workflows/pages.yml` + `docs/index.html` exist; invoke `pages` skill if absent
|
|
102
|
-
15. Governance stress-suite → walk change through M1, F1, C1, H1, S1, B1, A1, D1; any flunk regresses to the owning phase
|
|
103
|
-
|
|
104
|
-
## Completion
|
|
105
|
-
|
|
106
|
-
All true at once: witnessed e2e | browser_validated when client work touched | failure paths exercised | test.js passes | `.prd` deleted | git clean and pushed | CI green | hygiene sweep clean | TODO.md gone | CHANGELOG.md updated.
|
|
35
|
+
Residual-scan clear AND git clean AND CI green → `Skill(skill="gm:update-docs")`. Anything else → `Skill(skill="gm:planning")` or `Skill(skill="gm:gm-execute")` per the gap.
|