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
package/lib/spool.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
3
4
|
|
|
4
5
|
function getSpoolBaseDir() {
|
|
5
|
-
|
|
6
|
+
const cwd = process.cwd();
|
|
7
|
+
return path.join(cwd, '.gm', 'exec-spool');
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
function generateTaskId() {
|
|
@@ -16,8 +18,16 @@ function validateLang(lang) {
|
|
|
16
18
|
|
|
17
19
|
function getExtForLang(lang) {
|
|
18
20
|
const langExt = {
|
|
19
|
-
nodejs: 'js',
|
|
20
|
-
|
|
21
|
+
nodejs: 'js',
|
|
22
|
+
python: 'py',
|
|
23
|
+
bash: 'sh',
|
|
24
|
+
typescript: 'ts',
|
|
25
|
+
go: 'go',
|
|
26
|
+
rust: 'rs',
|
|
27
|
+
c: 'c',
|
|
28
|
+
cpp: 'cpp',
|
|
29
|
+
java: 'java',
|
|
30
|
+
deno: 'ts'
|
|
21
31
|
};
|
|
22
32
|
return langExt[lang] || 'js';
|
|
23
33
|
}
|
|
@@ -29,32 +39,43 @@ function validateVerb(verb) {
|
|
|
29
39
|
|
|
30
40
|
function writeSpool(body, lang = 'nodejs', options = {}) {
|
|
31
41
|
const validLang = validateLang(lang);
|
|
42
|
+
const ext = getExtForLang(validLang);
|
|
32
43
|
const taskId = options.taskId || generateTaskId();
|
|
33
|
-
|
|
34
|
-
const
|
|
44
|
+
|
|
45
|
+
const baseDir = getSpoolBaseDir();
|
|
46
|
+
const inDir = path.join(baseDir, 'in', validLang);
|
|
47
|
+
const inFile = path.join(inDir, `${taskId}.${ext}`);
|
|
35
48
|
|
|
36
49
|
fs.mkdirSync(inDir, { recursive: true });
|
|
37
50
|
|
|
38
51
|
const sessionId = options.sessionId || process.env.CLAUDE_SESSION_ID;
|
|
39
52
|
const code = sessionId ? `const SESSION_ID = '${sessionId}';\n${body}` : body;
|
|
53
|
+
|
|
40
54
|
fs.writeFileSync(inFile, code, 'utf8');
|
|
41
55
|
|
|
42
|
-
return {
|
|
56
|
+
return {
|
|
57
|
+
id: taskId,
|
|
58
|
+
path: inFile,
|
|
59
|
+
lang: validLang,
|
|
60
|
+
ext
|
|
61
|
+
};
|
|
43
62
|
}
|
|
44
63
|
|
|
45
64
|
function writeSpoolVerb(body, verb, options = {}) {
|
|
46
65
|
const validVerb = validateVerb(verb);
|
|
47
66
|
const taskId = options.taskId || generateTaskId();
|
|
48
|
-
const
|
|
67
|
+
const baseDir = getSpoolBaseDir();
|
|
68
|
+
const inDir = path.join(baseDir, 'in', validVerb);
|
|
49
69
|
const inFile = path.join(inDir, `${taskId}.txt`);
|
|
50
|
-
|
|
51
70
|
fs.mkdirSync(inDir, { recursive: true });
|
|
52
71
|
fs.writeFileSync(inFile, body, 'utf8');
|
|
53
72
|
return { id: taskId, path: inFile, verb: validVerb };
|
|
54
73
|
}
|
|
55
74
|
|
|
56
75
|
function readSpoolOutput(id) {
|
|
57
|
-
const
|
|
76
|
+
const baseDir = getSpoolBaseDir();
|
|
77
|
+
const outDir = path.join(baseDir, 'out');
|
|
78
|
+
|
|
58
79
|
const outFile = path.join(outDir, `${id}.out`);
|
|
59
80
|
const errFile = path.join(outDir, `${id}.err`);
|
|
60
81
|
const jsonFile = path.join(outDir, `${id}.json`);
|
|
@@ -72,7 +93,10 @@ function readSpoolOutput(id) {
|
|
|
72
93
|
}
|
|
73
94
|
|
|
74
95
|
return {
|
|
75
|
-
id,
|
|
96
|
+
id,
|
|
97
|
+
stdout,
|
|
98
|
+
stderr,
|
|
99
|
+
metadata,
|
|
76
100
|
exitCode: metadata.exitCode,
|
|
77
101
|
durationMs: metadata.durationMs,
|
|
78
102
|
timedOut: metadata.timedOut || false
|
|
@@ -80,25 +104,37 @@ function readSpoolOutput(id) {
|
|
|
80
104
|
}
|
|
81
105
|
|
|
82
106
|
async function waitForCompletion(id, timeoutMs = 30000) {
|
|
83
|
-
const
|
|
107
|
+
const baseDir = getSpoolBaseDir();
|
|
108
|
+
const outDir = path.join(baseDir, 'out');
|
|
109
|
+
const jsonFile = path.join(outDir, `${id}.json`);
|
|
110
|
+
|
|
84
111
|
const start = Date.now();
|
|
112
|
+
const interval = 50;
|
|
85
113
|
|
|
86
114
|
while (Date.now() - start < timeoutMs) {
|
|
87
115
|
if (fs.existsSync(jsonFile)) {
|
|
88
116
|
try {
|
|
89
117
|
const metadata = JSON.parse(fs.readFileSync(jsonFile, 'utf8'));
|
|
90
118
|
const output = readSpoolOutput(id);
|
|
91
|
-
return {
|
|
119
|
+
return {
|
|
120
|
+
ok: metadata.exitCode === 0 && !metadata.timedOut,
|
|
121
|
+
...output
|
|
122
|
+
};
|
|
92
123
|
} catch (e) {
|
|
93
|
-
await new Promise(r => setTimeout(r,
|
|
124
|
+
await new Promise(r => setTimeout(r, interval));
|
|
94
125
|
}
|
|
95
126
|
} else {
|
|
96
|
-
await new Promise(r => setTimeout(r,
|
|
127
|
+
await new Promise(r => setTimeout(r, interval));
|
|
97
128
|
}
|
|
98
129
|
}
|
|
99
130
|
|
|
100
131
|
const output = readSpoolOutput(id);
|
|
101
|
-
return {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
...output,
|
|
135
|
+
timedOut: true,
|
|
136
|
+
stderr: output.stderr + `\n[spool timeout after ${timeoutMs}ms]`
|
|
137
|
+
};
|
|
102
138
|
}
|
|
103
139
|
|
|
104
140
|
async function execSpool(body, lang, options = {}) {
|
|
@@ -112,52 +148,54 @@ async function execSpool(body, lang, options = {}) {
|
|
|
112
148
|
return result;
|
|
113
149
|
}
|
|
114
150
|
|
|
115
|
-
async function
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async function execBash(body, options = {}) {
|
|
120
|
-
return execSpool(body, 'bash', options);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function execCodesearch(query, options = {}) {
|
|
124
|
-
return execSpool(query, 'codesearch', options);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function execRecall(query, options = {}) {
|
|
128
|
-
return execSpool(query, 'recall', options);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async function execMemorize(fact, options = {}) {
|
|
132
|
-
return execSpool(fact, 'memorize', options);
|
|
133
|
-
}
|
|
151
|
+
async function execCodesearch(query, options = {}) { return execSpool(query, 'codesearch', options); }
|
|
152
|
+
async function execRecall(query, options = {}) { return execSpool(query, 'recall', options); }
|
|
153
|
+
async function execMemorize(fact, options = {}) { return execSpool(fact, 'memorize', options); }
|
|
134
154
|
|
|
135
155
|
function getAllOutputs() {
|
|
136
|
-
const
|
|
137
|
-
|
|
156
|
+
const baseDir = getSpoolBaseDir();
|
|
157
|
+
const outDir = path.join(baseDir, 'out');
|
|
158
|
+
|
|
159
|
+
if (!fs.existsSync(outDir)) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
138
162
|
|
|
139
163
|
const files = fs.readdirSync(outDir);
|
|
140
164
|
const taskIds = new Set();
|
|
165
|
+
|
|
141
166
|
files.forEach(file => {
|
|
142
167
|
const match = file.match(/^(.+?)\.(out|err|json)$/);
|
|
143
|
-
if (match)
|
|
168
|
+
if (match) {
|
|
169
|
+
taskIds.add(match[1]);
|
|
170
|
+
}
|
|
144
171
|
});
|
|
172
|
+
|
|
145
173
|
return Array.from(taskIds).map(id => readSpoolOutput(id));
|
|
146
174
|
}
|
|
147
175
|
|
|
148
|
-
function
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
});
|
|
176
|
+
async function getEvents(sessionId, cwd) {
|
|
177
|
+
try {
|
|
178
|
+
const { getSnapshot } = require('./skill-bootstrap');
|
|
179
|
+
return await getSnapshot(sessionId, cwd);
|
|
180
|
+
} catch (e) {
|
|
181
|
+
return { error: e.message };
|
|
182
|
+
}
|
|
156
183
|
}
|
|
157
184
|
|
|
158
185
|
module.exports = {
|
|
159
|
-
writeSpool,
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
186
|
+
writeSpool,
|
|
187
|
+
writeSpoolVerb,
|
|
188
|
+
readSpoolOutput,
|
|
189
|
+
waitForCompletion,
|
|
190
|
+
execSpool,
|
|
191
|
+
execCodesearch,
|
|
192
|
+
execRecall,
|
|
193
|
+
execMemorize,
|
|
194
|
+
getAllOutputs,
|
|
195
|
+
getSpoolBaseDir,
|
|
196
|
+
generateTaskId,
|
|
197
|
+
validateLang,
|
|
198
|
+
getExtForLang,
|
|
199
|
+
validateVerb,
|
|
200
|
+
getEvents
|
|
163
201
|
};
|
package/lib/wasm-host.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync, spawnSync } = require('child_process');
|
|
4
|
+
const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args));
|
|
5
|
+
|
|
6
|
+
class WasmHost {
|
|
7
|
+
constructor(wasmPath) {
|
|
8
|
+
this.wasmPath = wasmPath;
|
|
9
|
+
this.instance = null;
|
|
10
|
+
this.memory = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async init() {
|
|
14
|
+
try {
|
|
15
|
+
const wasmBuffer = fs.readFileSync(this.wasmPath);
|
|
16
|
+
const wasmModule = new WebAssembly.Module(wasmBuffer);
|
|
17
|
+
|
|
18
|
+
const importObject = {
|
|
19
|
+
host: {
|
|
20
|
+
host_fs_read: this.hostFsRead.bind(this),
|
|
21
|
+
host_fs_write: this.hostFsWrite.bind(this),
|
|
22
|
+
host_fs_readdir: this.hostFsReaddir.bind(this),
|
|
23
|
+
host_fs_stat: this.hostFsStat.bind(this),
|
|
24
|
+
host_kv_get: this.hostKvGet.bind(this),
|
|
25
|
+
host_kv_put: this.hostKvPut.bind(this),
|
|
26
|
+
host_kv_query: this.hostKvQuery.bind(this),
|
|
27
|
+
host_fetch: this.hostFetch.bind(this),
|
|
28
|
+
host_vec_search: this.hostVecSearch.bind(this),
|
|
29
|
+
host_vec_embed: this.hostVecEmbed.bind(this),
|
|
30
|
+
host_browser_spawn: this.hostBrowserSpawn.bind(this),
|
|
31
|
+
host_browser_eval: this.hostBrowserEval.bind(this),
|
|
32
|
+
host_browser_close: this.hostBrowserClose.bind(this),
|
|
33
|
+
host_exec_js: this.hostExecJs.bind(this),
|
|
34
|
+
host_log: this.hostLog.bind(this),
|
|
35
|
+
host_now_ms: this.hostNowMs.bind(this),
|
|
36
|
+
host_env_get: this.hostEnvGet.bind(this),
|
|
37
|
+
},
|
|
38
|
+
env: {
|
|
39
|
+
memory: new WebAssembly.Memory({ initial: 256, maximum: 512 }),
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
this.instance = new WebAssembly.Instance(wasmModule, importObject);
|
|
44
|
+
this.memory = importObject.env.memory;
|
|
45
|
+
return { ok: true };
|
|
46
|
+
} catch (err) {
|
|
47
|
+
return { ok: false, error: err.message };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
readString(offset, len) {
|
|
52
|
+
const buf = new Uint8Array(this.memory.buffer, offset, len);
|
|
53
|
+
return new TextDecoder().decode(buf);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
writeString(str) {
|
|
57
|
+
const encoder = new TextEncoder();
|
|
58
|
+
const encoded = encoder.encode(str);
|
|
59
|
+
const len = encoded.length;
|
|
60
|
+
const offset = this.instance.exports.plugkit_alloc(len);
|
|
61
|
+
const buf = new Uint8Array(this.memory.buffer, offset, len);
|
|
62
|
+
buf.set(encoded);
|
|
63
|
+
return [offset, len];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
hostFsRead(pathPtr, pathLen) {
|
|
67
|
+
try {
|
|
68
|
+
const pathStr = this.readString(pathPtr, pathLen);
|
|
69
|
+
const content = fs.readFileSync(pathStr, 'utf8');
|
|
70
|
+
const [offset, len] = this.writeString(content);
|
|
71
|
+
return offset;
|
|
72
|
+
} catch (err) {
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
hostFsWrite(pathPtr, pathLen, dataPtr, dataLen) {
|
|
78
|
+
try {
|
|
79
|
+
const pathStr = this.readString(pathPtr, pathLen);
|
|
80
|
+
const data = this.readString(dataPtr, dataLen);
|
|
81
|
+
fs.writeFileSync(pathStr, data, 'utf8');
|
|
82
|
+
return 1;
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
hostFsReaddir(pathPtr, pathLen) {
|
|
89
|
+
try {
|
|
90
|
+
const pathStr = this.readString(pathPtr, pathLen);
|
|
91
|
+
const entries = fs.readdirSync(pathStr);
|
|
92
|
+
const result = JSON.stringify(entries);
|
|
93
|
+
const [offset] = this.writeString(result);
|
|
94
|
+
return offset;
|
|
95
|
+
} catch (err) {
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
hostFsStat(pathPtr, pathLen) {
|
|
101
|
+
try {
|
|
102
|
+
const pathStr = this.readString(pathPtr, pathLen);
|
|
103
|
+
const stat = fs.statSync(pathStr);
|
|
104
|
+
const result = JSON.stringify({
|
|
105
|
+
isFile: stat.isFile(),
|
|
106
|
+
isDirectory: stat.isDirectory(),
|
|
107
|
+
size: stat.size,
|
|
108
|
+
mtime: stat.mtime.getTime(),
|
|
109
|
+
});
|
|
110
|
+
const [offset] = this.writeString(result);
|
|
111
|
+
return offset;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
hostKvGet(keyPtr, keyLen) {
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
hostKvPut(keyPtr, keyLen, valPtr, valLen) {
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
hostKvQuery(queryPtr, queryLen) {
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
hostFetch(urlPtr, urlLen, optsPtr, optsLen) {
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
hostVecSearch(queryPtr, queryLen) {
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
hostVecEmbed(textPtr, textLen) {
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
hostBrowserSpawn(urlPtr, urlLen) {
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
hostBrowserEval(sessionPtr, sessionLen, jsPtr, jsLen) {
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
hostBrowserClose(sessionPtr, sessionLen) {
|
|
150
|
+
return 1;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
hostExecJs(codePtr, codeLen) {
|
|
154
|
+
try {
|
|
155
|
+
const code = this.readString(codePtr, codeLen);
|
|
156
|
+
const result = eval(`(${code})`);
|
|
157
|
+
const [offset] = this.writeString(JSON.stringify(result));
|
|
158
|
+
return offset;
|
|
159
|
+
} catch (err) {
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
hostLog(msgPtr, msgLen) {
|
|
165
|
+
const msg = this.readString(msgPtr, msgLen);
|
|
166
|
+
console.log(msg);
|
|
167
|
+
return 1;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
hostNowMs() {
|
|
171
|
+
return Date.now();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
hostEnvGet(keyPtr, keyLen) {
|
|
175
|
+
const key = this.readString(keyPtr, keyLen);
|
|
176
|
+
const val = process.env[key] || '';
|
|
177
|
+
const [offset] = this.writeString(val);
|
|
178
|
+
return offset;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async dispatch(verb, body) {
|
|
182
|
+
if (!this.instance) {
|
|
183
|
+
const initResult = await this.init();
|
|
184
|
+
if (!initResult.ok) return initResult;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
|
|
189
|
+
const [verbOffset, verbLen] = this.writeString(verb);
|
|
190
|
+
const [bodyOffset, bodyLen] = this.writeString(bodyStr);
|
|
191
|
+
|
|
192
|
+
const resultPtr = this.instance.exports.dispatch_verb(verbOffset, verbLen, bodyOffset, bodyLen);
|
|
193
|
+
if (resultPtr === 0) {
|
|
194
|
+
return { ok: false, error: 'dispatch_verb returned null' };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const resultStr = this.readString(resultPtr, 1024);
|
|
198
|
+
try {
|
|
199
|
+
return JSON.parse(resultStr);
|
|
200
|
+
} catch {
|
|
201
|
+
return { ok: true, output: resultStr };
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
return { ok: false, error: err.message };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async hook(name, body) {
|
|
209
|
+
if (!this.instance) {
|
|
210
|
+
const initResult = await this.init();
|
|
211
|
+
if (!initResult.ok) return initResult;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
|
|
216
|
+
const [nameOffset, nameLen] = this.writeString(name);
|
|
217
|
+
const [bodyOffset, bodyLen] = this.writeString(bodyStr);
|
|
218
|
+
|
|
219
|
+
const hookFn = this.instance.exports[`hook_${name}`];
|
|
220
|
+
if (!hookFn) {
|
|
221
|
+
return { ok: false, error: `hook_${name} not found` };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const resultPtr = hookFn(bodyOffset, bodyLen);
|
|
225
|
+
if (resultPtr === 0) {
|
|
226
|
+
return { ok: false, error: `hook_${name} returned null` };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const resultStr = this.readString(resultPtr, 1024);
|
|
230
|
+
try {
|
|
231
|
+
return JSON.parse(resultStr);
|
|
232
|
+
} catch {
|
|
233
|
+
return { ok: true, output: resultStr };
|
|
234
|
+
}
|
|
235
|
+
} catch (err) {
|
|
236
|
+
return { ok: false, error: err.message };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module.exports = WasmHost;
|
package/package.json
CHANGED
|
@@ -1,32 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
".": "./lib/index.js",
|
|
8
|
-
"./daemon-bootstrap": "./lib/daemon-bootstrap.js",
|
|
9
|
-
"./manifest": "./lib/manifest.js",
|
|
10
|
-
"./prepare": "./lib/prepare.js",
|
|
11
|
-
"./loader": "./lib/loader.js"
|
|
12
|
-
},
|
|
13
|
-
"scripts": {
|
|
14
|
-
"test": "node test.js"
|
|
15
|
-
},
|
|
3
|
+
"version": "2.0.1081",
|
|
4
|
+
"description": "Canonical universal harness — AI-native software engineering via skill-driven orchestration; bootstraps plugkit for task execution and session isolation. Install in any AI coding agent host.",
|
|
5
|
+
"author": "AnEntrypoint",
|
|
6
|
+
"license": "MIT",
|
|
16
7
|
"keywords": [
|
|
17
8
|
"gm",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
9
|
+
"skill",
|
|
10
|
+
"harness",
|
|
11
|
+
"plugkit",
|
|
12
|
+
"orchestration",
|
|
13
|
+
"ai-coding",
|
|
14
|
+
"canonical"
|
|
21
15
|
],
|
|
22
|
-
"
|
|
23
|
-
"
|
|
16
|
+
"homepage": "https://github.com/AnEntrypoint/gm-skill#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/AnEntrypoint/gm-skill/issues"
|
|
19
|
+
},
|
|
24
20
|
"repository": {
|
|
25
21
|
"type": "git",
|
|
26
|
-
"url": "https://github.com/AnEntrypoint/gm.git"
|
|
22
|
+
"url": "https://github.com/AnEntrypoint/gm-skill.git"
|
|
23
|
+
},
|
|
24
|
+
"main": "bin/bootstrap.js",
|
|
25
|
+
"bin": {
|
|
26
|
+
"gm-skill-bootstrap": "./bin/bootstrap.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"skills/",
|
|
30
|
+
"agents/",
|
|
31
|
+
"prompts/",
|
|
32
|
+
"lib/",
|
|
33
|
+
"lang/",
|
|
34
|
+
"scripts/",
|
|
35
|
+
"bin/",
|
|
36
|
+
"gm-plugkit/",
|
|
37
|
+
"AGENTS.md",
|
|
38
|
+
"README.md",
|
|
39
|
+
"gm.json"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"gm-plugkit": "^2.0.1081"
|
|
27
43
|
},
|
|
28
|
-
"homepage": "https://github.com/AnEntrypoint/gm#readme",
|
|
29
44
|
"engines": {
|
|
30
45
|
"node": ">=16.0.0"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
31
49
|
}
|
|
32
50
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The Bash tool accepts ONLY git commands directly (no exec: prefix): `git status`, `git commit -m "msg"`, `git push`, etc.
|
|
2
|
+
|
|
3
|
+
Everything else — code execution AND utility verbs — goes through the file-spool. Write a file at:
|
|
4
|
+
|
|
5
|
+
.gm/exec-spool/in/<lang-or-verb>/<N>.<ext>
|
|
6
|
+
|
|
7
|
+
Examples:
|
|
8
|
+
in/nodejs/42.js in/python/43.py in/bash/44.sh
|
|
9
|
+
in/codesearch/45.txt in/recall/46.txt in/memorize/47.md
|
|
10
|
+
in/wait/48.txt in/browser/49.js in/runner/50.txt
|
|
11
|
+
|
|
12
|
+
Languages: nodejs, python, bash, typescript, go, rust, c, cpp, java, deno
|
|
13
|
+
Verbs: codesearch, recall, memorize, wait, sleep, status, close, browser, runner, type, kill-port, forget, feedback, learn-status, learn-debug, learn-build, discipline, pause
|
|
14
|
+
|
|
15
|
+
The spool watcher executes the request and streams output:
|
|
16
|
+
- .gm/exec-spool/out/<N>.out (stdout, written line-by-line)
|
|
17
|
+
- .gm/exec-spool/out/<N>.err (stderr, written line-by-line)
|
|
18
|
+
- .gm/exec-spool/out/<N>.json (metadata: exitCode, durationMs, timedOut, startedAt, endedAt — written once at completion)
|
|
19
|
+
|
|
20
|
+
Result returns as systemMessage on next tool use with `--- stdout ---` and `--- stderr ---` separators. Both `in/` and `out/` are wiped at session start and session end (real-exit reasons only).
|
|
21
|
+
|
|
22
|
+
Anything else via Bash is blocked.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
=== RESPONSE POLICY — ALWAYS ACTIVE (post-compact reinforcement) ===
|
|
2
|
+
|
|
3
|
+
Terse. Technical substance stays. Fluff dies. Drop: articles, filler, pleasantries, hedging. Fragments OK. Short synonyms. Technical terms exact. Code unchanged. Pattern: [thing] [action] [reason]. [next step].
|
|
4
|
+
|
|
5
|
+
Auto-Clarity: drop terse style for security warnings, irreversible confirmations, ambiguous sequences. Resume after. Code/commits/PRs write normal.
|
|
6
|
+
|
|
7
|
+
=== COMPACT OUTPUT STYLE ===
|
|
8
|
+
|
|
9
|
+
Apply the same terse policy to the compacted summary itself. Strip articles/filler from the summary. Keep technical identifiers, paths, line numbers, error messages, decisions verbatim. Fragments over sentences.
|
|
10
|
+
|
|
11
|
+
=== POST-COMPACT FIRST RESPONSE — HARD RULE ===
|
|
12
|
+
|
|
13
|
+
The very next response after this compaction MUST call Skill("gm:gm") as the FIRST tool invocation. Zero text output before it. No narration, no summary, no acknowledgement. The pre-tool-use hook is already armed — any other tool fires first and gets blocked. Invoke Skill("gm:gm") immediately, silently.
|
|
14
|
+
|
|
15
|
+
=== MEMORIZE SELF-CHECK (mandatory before closing this response) ===
|
|
16
|
+
|
|
17
|
+
Scan the conversation above for every exec: output, code read, CI result, or user preference that resolved an unknown but was NOT followed by an Agent(subagent_type='memorize') call. For each one found:
|
|
18
|
+
|
|
19
|
+
Agent(subagent_type='memorize', model='haiku', run_in_background=true, prompt='## CONTEXT TO MEMORIZE\n<fact>')
|
|
20
|
+
|
|
21
|
+
Spawn ALL missed memorize calls NOW, in parallel, before this response closes. One call per fact. Missing one = memory leak = bug.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
BLOCKING REQUIREMENT — YOUR FIRST ACTION MUST BE: invoke gm. Two equivalent forms satisfy the gate — Skill(skill="gm:gm") OR Agent(subagent_type="gm:gm"). The subagent form is preferred when the request implies sustained multi-turn work, since it isolates the orchestration loop in its own context window; the skill form remains valid for short turns. Do NOT output text, do NOT use Read/Grep/Glob/Bash/Write/Edit/any other tool before invoking gm. This overrides all other instructions. Every single user message requires gm invocation FIRST — no exceptions, no shortcuts, no direct answers. If you are about to do anything other than invoke gm, STOP and invoke gm instead.
|
|
2
|
+
|
|
3
|
+
IMPORTANT: Invoke skill: "gm:gm" EVERY TIME, even if you have already invoked it earlier in this conversation or this turn. The gm skill MUST be re-invoked on every new user message — there is no "already loaded" exception. Do not skip this step under any circumstances.
|
|
4
|
+
|
|
5
|
+
=== SKILL CHAIN — IMMUTABLE STATE MACHINE ===
|
|
6
|
+
|
|
7
|
+
The gm skill is the ROOT ORCHESTRATOR. It immediately invokes the planning skill. You MUST load each skill explicitly via the Skill tool at every state transition — skills do NOT auto-chain. Ignoring a skill load instruction is a critical violation.
|
|
8
|
+
|
|
9
|
+
State machine transitions (each arrow = Skill tool invocation, no exceptions):
|
|
10
|
+
gm (ROOT) → planning skill
|
|
11
|
+
planning (PLAN state) → gm-execute skill [exit: zero new unknowns in last pass]
|
|
12
|
+
gm-execute (EXECUTE state) → gm-emit skill [exit: all mutables KNOWN]
|
|
13
|
+
gm-emit (EMIT state) → gm-complete skill [exit: all gate conditions pass]
|
|
14
|
+
gm-complete (VERIFY state) → gm-execute skill [exit: .prd items remain]
|
|
15
|
+
gm-complete (VERIFY state) → update-docs skill [exit: .prd empty + pushed]
|
|
16
|
+
|
|
17
|
+
State regressions (also Skill tool invocations):
|
|
18
|
+
Any new unknown → planning skill immediately
|
|
19
|
+
EMIT logic wrong → gm-execute skill
|
|
20
|
+
VERIFY file broken → gm-emit skill
|
|
21
|
+
VERIFY logic wrong → gm-execute skill
|
|
22
|
+
|
|
23
|
+
After PLAN completes: launch parallel gm:gm subagents (via Agent tool with subagent_type="gm:gm") for independent .prd items — maximum 3 concurrent, never sequential for independent work.
|
|
24
|
+
|
|
25
|
+
=== MEMORIZE ON RESOLUTION — HARD RULE ===
|
|
26
|
+
|
|
27
|
+
Every unknown→known transition MUST be handed off to a memorize agent THE SAME TURN it resolves — not at phase end, not in a batch. This is the most violated rule. Every session, dozens of exec: outputs resolve unknowns that are never memorized. Those facts die on compaction.
|
|
28
|
+
|
|
29
|
+
The ONLY acceptable memorize call form:
|
|
30
|
+
|
|
31
|
+
Agent(subagent_type='gm:memorize', model='haiku', run_in_background=true, prompt='## CONTEXT TO MEMORIZE\n<single fact with enough context for a cold-start agent>')
|
|
32
|
+
|
|
33
|
+
Trigger (any = fire NOW, same turn, before next tool):
|
|
34
|
+
- exec: output answers ANY prior "let me check" / "does this API take X" / "what version is installed"
|
|
35
|
+
- Code read confirms or refutes an assumption about existing structure
|
|
36
|
+
- CI log or error output reveals a root cause
|
|
37
|
+
- User states a preference, constraint, deadline, or judgment call
|
|
38
|
+
- Fix works for non-obvious reason
|
|
39
|
+
- Tool / env quirk observed (blocked commands, path oddities, platform differences)
|
|
40
|
+
|
|
41
|
+
Parallel spawn: N facts in one turn → N Agent(memorize) calls in ONE message, parallel tool blocks. NEVER serialize.
|
|
42
|
+
|
|
43
|
+
End-of-turn self-check (mandatory, no exceptions): before closing ANY response, scan the entire turn for exec: outputs and code reads that resolved an unknown but were NOT followed by Agent(memorize). Spawn ALL missed ones now. "I'll memorize this" in text is NOT a memorize call — only the Agent tool call counts.
|
|
44
|
+
|
|
45
|
+
Skipping memorize = memory leak = critical bug. Saying you will memorize ≠ memorizing.
|
|
46
|
+
|
|
47
|
+
=== NO NARRATION BEFORE EXECUTION ===
|
|
48
|
+
|
|
49
|
+
Do NOT output text describing what you are about to do before doing it. Run the tool first. State findings AFTER. Pattern: tool call → tool result → brief text summary of what was found. NOT: text describing upcoming tool → tool call.
|
|
50
|
+
|
|
51
|
+
"I'll check the file:" followed by Read = violation.
|
|
52
|
+
"Let me search for X" followed by exec:codesearch = violation.
|
|
53
|
+
"Now I'll fix Y" followed by Edit = violation.
|
|
54
|
+
|
|
55
|
+
Every sentence of text output must be AFTER at least one tool result that justifies it. No pre-announcement narration.
|
|
56
|
+
|
|
57
|
+
=== AUTONOMY — HARD RULE ===
|
|
58
|
+
|
|
59
|
+
A written PRD is the user's authorization. EXECUTE owns the work to COMPLETE. Resolve every doubt that arises during execution by witnessed probe, by recall, or by re-reading the PRD — never by routing the doubt back to the user. Any question whose answer the agent could obtain itself belongs to the agent, not the user.
|
|
60
|
+
|
|
61
|
+
Asking is permitted only as last resort — destructive-irreversible action with no PRD coverage, OR user intent genuinely irrecoverable from PRD/memory/code. Channel: `exec:pause` (renames .gm/prd.yml → .gm/prd.paused.yml; question in header). In-conversation asking is last-resort beneath last-resort.
|
|
62
|
+
|
|
63
|
+
The size of the task, the cost of context, the duration of CI, and the number of repos involved are never grounds to ask.
|
|
64
|
+
|
|
65
|
+
=== REFUSAL BAN — MAXIMAL COVER INSTEAD ===
|
|
66
|
+
|
|
67
|
+
When scope exceeds reach, expand the cover. Don't refuse. Don't ship one slice with the rest deferred to "follow-up" — that's the same failure as bluffing strength, just dressed differently.
|
|
68
|
+
|
|
69
|
+
Refusal has two shapes, both forbidden, both structurally identical to bluffing strength:
|
|
70
|
+
- TOTAL refusal — no work delivered toward the request.
|
|
71
|
+
- DISTRIBUTED refusal — one bounded subset delivered while other witnessable subsets of the same request are abandoned as "follow-up".
|
|
72
|
+
|
|
73
|
+
Required move: construct the covering family — every bounded subset of the request that is witnessable from this session — write it into the PRD, execute every member. Single-subset delivery is legitimate only when no other witnessable subset exists. At end-of-turn, name the residual complement explicitly with the reason each excluded piece falls outside reach.
|
|
74
|
+
|
|
75
|
+
Enforcement is on what is delivered, not on which words appear. Before closing the turn, check that committed work + named complement = witnessable closure of the request. Anything witnessable that falls in neither set means the cover is not yet maximal — re-enter planning to expand it. Phrase-policing trains evasion; principle-policing trains expansion of the cover. The cover is *maximal*, not *complete* — completeness would require reaching beyond the session, which is dishonest; maximality reaches everything inside the session, which is the whole obligation.
|
|
76
|
+
|
|
77
|
+
=== MUTABLES.YML — MACHINE-CHECKED DISCIPLINE ===
|
|
78
|
+
|
|
79
|
+
`.gm/mutables.yml` is co-equal with `.gm/prd.yml`. PLAN enumerates every unknown into it; EXECUTE resolves each entry to `status: witnessed` with filled `witness_evidence`; EMIT is hard-blocked while any entry is `status: unknown`. The pre-tool-use hook denies Write/Edit/NotebookEdit and `git commit`/`git push` while unresolved entries exist; the stop hook denies turn-stop the same way. Resolution = write-back to the file with concrete proof (file:line, codesearch hit, exec output). Saying "I resolved it" without updating the file leaves the gate closed.
|
|
80
|
+
|
|
81
|
+
=== SMOKE-PAGE BAN — USE window.__debug ===
|
|
82
|
+
|
|
83
|
+
`window.__debug` is the in-page observability registry. Each module registers on mount, deregisters on unmount. Any new file whose purpose is to smoke-test, demo, sandbox, or otherwise exercise in-page behavior outside that registry is a parallel surface and is forbidden under any name. Extend the registry instead and exercise it from `test.js` at project root.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
BLOCKING REQUIREMENT — READ THIS FIRST: Your VERY FIRST action on EVERY user message MUST be to invoke the Skill tool with skill: "gm:gm". Do NOT read files, do NOT search, do NOT answer, do NOT plan, do NOT use any other tool before invoking skill: "gm:gm". This is non-negotiable. If you respond without invoking gm:gm first, you are violating a hard constraint.
|
|
2
|
+
|
|
3
|
+
=== TOOL RULES ===
|
|
4
|
+
|
|
5
|
+
Skill tool: invoke skills by name (e.g. skill: "gm:gm"). Never use the Agent tool to load skills.
|
|
6
|
+
|
|
7
|
+
Bash tool: only these formats are allowed —
|
|
8
|
+
exec:nodejs / exec:python / exec:bash / exec:typescript / exec:go / exec:rust / exec:c / exec:cpp / exec:java
|
|
9
|
+
exec:browser (JS automation against `page`)
|
|
10
|
+
exec:codesearch (natural language search)
|
|
11
|
+
exec:status / exec:sleep / exec:close / exec:runner / exec:type
|
|
12
|
+
git <args> (git commands directly, no exec: prefix)
|
|
13
|
+
Everything else is blocked. Never Bash(node ...) or Bash(npm ...) or Bash(npx ...).
|
|
14
|
+
|
|
15
|
+
Glob/Grep/Find/Explore: blocked — use exec:codesearch instead.
|