gm-skill 2.0.1911 → 2.0.1913
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 +6 -2
- package/bin/install.js +116 -0
- package/gm-plugkit/package.json +1 -1
- package/gm-plugkit/plugkit-wasm-wrapper.js +36 -1
- package/gm.json +1 -1
- package/package.json +1 -1
- package/skills/gm/SKILL.md +2 -0
package/AGENTS.md
CHANGED
|
@@ -24,9 +24,11 @@ Skills encode environment-specific constraints that override general knowledge.
|
|
|
24
24
|
|
|
25
25
|
Repo root = package root = published `gm-skill` npm package; no factory, no separate build-output dir. Entry: `skills/gm/SKILL.md`. Orchestration lives in rs-plugkit, served on-demand via `instruction`. Agent-facing prose (phase instruction, gate/residual text) externalizes to editable `gm-plugkit/instructions/`: prose edits = gm-plugkit republish, zero Rust rebuild. Mechanism (prose.rs per-key fallback to compiled const; sync-instruction-consts.mjs byte-aligns .md<->rs-plugkit consts) in the recall store (`recall: string-externalization project`).
|
|
26
26
|
|
|
27
|
-
## WASM-only
|
|
27
|
+
## WASM-only, now with an optional native runner
|
|
28
28
|
|
|
29
|
-
Plugkit = wasm cdylib
|
|
29
|
+
Plugkit = wasm cdylib. Two host loaders exist and coexist: `plugkit-wasm-wrapper.js` under Node/bun (original, still the default) and `gm-runner` (rs-plugkit `crates/gm-runner/`, a real native cross-platform binary built by CI and published to `AnEntrypoint/gm-runner-bin` releases) -- gm-runner links `wasmtime` and hosts the same wasm module directly, no Node/bun dependency for the boot step. Both loaders serve the byte-identical spool ABI (`in/`/`out/` layout, verb names), so a gm-driven session works the same regardless of which loader booted it. `bin/install.js` best-effort-downloads gm-runner (sha256-verified against its release's `.sha256` sidecar) but never hard-requires it -- a platform with no published gm-runner binary, or a failed download, silently falls back to bun/npx exactly as before. `plugkit.wasm` itself is fetched at bootstrap from `plugkit-wasm` npm / `plugkit-bin` gh-releases, sha256-pinned, by whichever loader is running (gm-runner's own `download.rs::bootstrap_plugkit_wasm` mirrors the JS wrapper's fetch+verify). Size + embedded-model (offline in-wasm embeddings) mechanics: the recall store (`recall: WASM-only plugkit size mechanics`).
|
|
30
|
+
|
|
31
|
+
**gm-runner auto-updates the wasm it serves, not itself.** `spool.rs::run_spool_watcher` polls `plugkit-bin`'s GitHub Releases API every 600s for a newer tag (`download.rs::fetch_latest_plugkit_version`); on a mismatch it downloads+verifies the new wasm and the existing 30s local-version-skew check picks up the on-disk change and triggers a clean in-process reload (`StopReason::Reload`). The gm-runner *executable* itself has no such remote self-update loop yet -- a stale installed gm-runner binary keeps running until the next `bin/install.js` re-run re-checks `gm-runner-bin`'s latest release tag.
|
|
30
32
|
|
|
31
33
|
Wasm host-import link-module rule (`#[link(wasm_import_module="env")]` on every host-import extern block, every dep crate): the recall store (`recall: wasm host-import link-module trap`).
|
|
32
34
|
|
|
@@ -152,6 +154,8 @@ A task that reduces to read/investigate/report, or a change confined to files th
|
|
|
152
154
|
|
|
153
155
|
Push to any rs-* sibling -> `cascade.yml` -> rs-plugkit `release.yml` -> single `plugkit.wasm` (npm `plugkit-wasm` + `plugkit-bin` Releases) -> auto-bump `gm.json::plugkitVersion` -> `publish.yml` ships gm-skill+gm-plugkit+SKILL.md mirror. Step sequence + PUBLISHER_TOKEN: the recall store (`recall: cascade pipeline`).
|
|
154
156
|
|
|
157
|
+
A push touching `rs-plugkit/crates/gm-runner/**` additionally triggers its own `gm-runner.yml` workflow (separate from `release.yml`, matrix-builds all 6 platform targets, publishes to `AnEntrypoint/gm-runner-bin` Releases) -- a stuck/unschedulable single matrix leg (observed: `macos-13` queued 18h+ with zero GH-assigned runner) must never block the `release` job from shipping the other 5 platforms; that job runs `if: always()` and tolerates partial artifacts rather than a blanket `needs: build` hard-gate.
|
|
158
|
+
|
|
155
159
|
**Repos involved (push to any triggers cascade):** `AnEntrypoint/{rs-codeinsight, rs-search, rs-plugkit, gm}`. rs-learn and rs-exec are retired (crates removed from / never depended on by rs-plugkit; their spool-dispatch and memory surfaces reimplemented natively in rs-plugkit wasm_dispatch; repos archived as tombstones, README points at rs-plugkit). Roles, npm package names, legacy-retirement detail: the recall store (`recall: cascade repos involved roles`, `recall: legacy gm-skill variants retired`).
|
|
156
160
|
|
|
157
161
|
**To update every possible thing**: push to the relevant repo. No manual version bumps, no local `cargo update`/`cargo build` -- push, let CI build.
|
package/bin/install.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const os = require('os');
|
|
7
|
+
const crypto = require('crypto');
|
|
8
|
+
const https = require('https');
|
|
7
9
|
const readline = require('readline');
|
|
8
10
|
|
|
9
11
|
function out(msg) { process.stdout.write(msg + '\n'); }
|
|
@@ -170,6 +172,119 @@ function runPlugkitBootstrap() {
|
|
|
170
172
|
return Promise.resolve(false);
|
|
171
173
|
}
|
|
172
174
|
|
|
175
|
+
// Maps process.platform/process.arch to the exact release-asset basename
|
|
176
|
+
// gm-runner's CI (rs-plugkit/.github/workflows/gm-runner.yml) publishes to
|
|
177
|
+
// AnEntrypoint/gm-runner-bin -- must stay byte-identical to that workflow's
|
|
178
|
+
// `artifact:` matrix values, this is the only other place that name is
|
|
179
|
+
// spelled. Returns null for a host combination CI does not build (no crash,
|
|
180
|
+
// caller falls back to the existing bun/npx path).
|
|
181
|
+
function gmRunnerAssetName() {
|
|
182
|
+
const plat = process.platform;
|
|
183
|
+
const arch = process.arch;
|
|
184
|
+
if (plat === 'win32') {
|
|
185
|
+
if (arch === 'x64') return 'gm-runner-windows-x64.exe';
|
|
186
|
+
if (arch === 'arm64') return 'gm-runner-windows-arm64.exe';
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
if (plat === 'darwin') {
|
|
190
|
+
if (arch === 'x64') return 'gm-runner-macos-x64';
|
|
191
|
+
if (arch === 'arm64') return 'gm-runner-macos-arm64';
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
if (plat === 'linux') {
|
|
195
|
+
if (arch === 'x64') return 'gm-runner-linux-x64';
|
|
196
|
+
if (arch === 'arm64') return 'gm-runner-linux-arm64';
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function httpsGetBuffer(url, redirectsLeft) {
|
|
203
|
+
if (redirectsLeft === undefined) redirectsLeft = 5;
|
|
204
|
+
return new Promise((resolve, reject) => {
|
|
205
|
+
https.get(url, { headers: { 'User-Agent': 'gm-skill-installer' } }, res => {
|
|
206
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
207
|
+
res.resume();
|
|
208
|
+
if (redirectsLeft <= 0) { reject(new Error('too many redirects fetching ' + url)); return; }
|
|
209
|
+
httpsGetBuffer(res.headers.location, redirectsLeft - 1).then(resolve, reject);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (res.statusCode !== 200) {
|
|
213
|
+
res.resume();
|
|
214
|
+
reject(new Error(`GET ${url} -> HTTP ${res.statusCode}`));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const chunks = [];
|
|
218
|
+
res.on('data', c => chunks.push(c));
|
|
219
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
220
|
+
res.on('error', reject);
|
|
221
|
+
}).on('error', reject);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function sha256Hex(buf) {
|
|
226
|
+
return crypto.createHash('sha256').update(buf).digest('hex');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function gmToolsDir() {
|
|
230
|
+
const home = homeDir();
|
|
231
|
+
return path.join(home, '.gm-tools');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Downloads the platform-matched gm-runner binary from gm-runner-bin's
|
|
235
|
+
// GitHub Releases (native runner replacing the bun/node spool-boot path),
|
|
236
|
+
// verified against the release's own .sha256 sidecar before the atomic
|
|
237
|
+
// rename lands -- same trust model as gm-runner's own
|
|
238
|
+
// download::download_and_verify for plugkit.wasm, so a corrupt/partial
|
|
239
|
+
// download is never silently accepted. Best-effort: any failure (offline,
|
|
240
|
+
// asset missing for this host, network error) resolves false rather than
|
|
241
|
+
// throwing, so install never hard-fails over a runner-binary fetch -- the
|
|
242
|
+
// bun/npx spool path remains the fallback until remove-node-bun-from-native-path
|
|
243
|
+
// lands.
|
|
244
|
+
async function downloadGmRunner({ silent } = {}) {
|
|
245
|
+
const assetName = gmRunnerAssetName();
|
|
246
|
+
if (!assetName) {
|
|
247
|
+
if (!silent) err(`gm-runner: no published binary for platform=${process.platform} arch=${process.arch}, skipping native runner install`);
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
const destDir = gmToolsDir();
|
|
251
|
+
const destPath = path.join(destDir, assetName.endsWith('.exe') ? 'gm-runner.exe' : 'gm-runner');
|
|
252
|
+
try {
|
|
253
|
+
const releaseInfo = JSON.parse((await httpsGetBuffer('https://api.github.com/repos/AnEntrypoint/gm-runner-bin/releases/latest')).toString('utf8'));
|
|
254
|
+
const tag = releaseInfo && releaseInfo.tag_name;
|
|
255
|
+
if (!tag) { if (!silent) err('gm-runner: no releases published yet at AnEntrypoint/gm-runner-bin, skipping'); return false; }
|
|
256
|
+
const base = `https://github.com/AnEntrypoint/gm-runner-bin/releases/download/${tag}`;
|
|
257
|
+
const binUrl = `${base}/${assetName}`;
|
|
258
|
+
const shaUrl = `${binUrl}.sha256`;
|
|
259
|
+
|
|
260
|
+
const versionFile = path.join(destDir, 'gm-runner.version');
|
|
261
|
+
if (fs.existsSync(destPath) && fs.existsSync(versionFile)) {
|
|
262
|
+
const installed = fs.readFileSync(versionFile, 'utf8').trim();
|
|
263
|
+
if (installed === tag) { if (!silent) out(`gm-runner ${tag} already installed at ${destPath}`); return true; }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const [binBuf, shaBuf] = await Promise.all([httpsGetBuffer(binUrl), httpsGetBuffer(shaUrl)]);
|
|
267
|
+
const expectedSha = shaBuf.toString('utf8').trim().split(/\s+/)[0];
|
|
268
|
+
const actualSha = sha256Hex(binBuf);
|
|
269
|
+
if (!expectedSha || actualSha.toLowerCase() !== expectedSha.toLowerCase()) {
|
|
270
|
+
if (!silent) err(`gm-runner: sha256 mismatch downloading ${binUrl} (expected ${expectedSha}, got ${actualSha}), not installing`);
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
275
|
+
const tmp = destPath + '.tmp' + process.pid;
|
|
276
|
+
fs.writeFileSync(tmp, binBuf);
|
|
277
|
+
if (process.platform !== 'win32') { try { fs.chmodSync(tmp, 0o755); } catch (_) {} }
|
|
278
|
+
fs.renameSync(tmp, destPath);
|
|
279
|
+
fs.writeFileSync(versionFile, tag);
|
|
280
|
+
if (!silent) out(`Installed gm-runner ${tag} to ${destPath}`);
|
|
281
|
+
return true;
|
|
282
|
+
} catch (e) {
|
|
283
|
+
if (!silent) err(`gm-runner download skipped: ${e && e.message || e}`);
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
173
288
|
function printHelp() {
|
|
174
289
|
out('gm installer');
|
|
175
290
|
out('');
|
|
@@ -220,6 +335,7 @@ async function main() {
|
|
|
220
335
|
}
|
|
221
336
|
|
|
222
337
|
await runPlugkitBootstrap();
|
|
338
|
+
await downloadGmRunner({ silent: false });
|
|
223
339
|
|
|
224
340
|
out('');
|
|
225
341
|
out('Done. Open Claude Code and run /gm. New top-level skill dirs may need one restart to register.');
|
package/gm-plugkit/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1913",
|
|
4
4
|
"description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform binary, verifies SHA256, and starts the spool watcher daemon. Includes plugkit-wasm-wrapper for WASM-based spool watching.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -758,6 +758,29 @@ function findCachedBunRunnerBin() {
|
|
|
758
758
|
return null;
|
|
759
759
|
}
|
|
760
760
|
|
|
761
|
+
// Cache dir entries are named `<pkg>@<version>[@@@N]` (the `@@@N` suffix is bun's own
|
|
762
|
+
// disambiguator for multiple cache slots of the same resolved version, not part of the semver).
|
|
763
|
+
// Picks the highest semver-looking version present so a stale older cache entry left over from
|
|
764
|
+
// a prior gm-plugkit run doesn't win over a newer one that's also cached.
|
|
765
|
+
function findCachedBunRunnerVersion() {
|
|
766
|
+
try {
|
|
767
|
+
const cacheDir = path.join(os.homedir(), '.bun', 'install', 'cache');
|
|
768
|
+
const entries = fs.readdirSync(cacheDir).filter(n => n.startsWith(`${BROWSER_RUNNER_BIN}@`));
|
|
769
|
+
const versions = entries
|
|
770
|
+
.map(n => n.slice(BROWSER_RUNNER_BIN.length + 1).split('@@@')[0])
|
|
771
|
+
.filter(v => /^\d+\.\d+\.\d+/.test(v));
|
|
772
|
+
if (!versions.length) return null;
|
|
773
|
+
versions.sort((a, b) => {
|
|
774
|
+
const pa = a.split('.').map(Number), pb = b.split('.').map(Number);
|
|
775
|
+
for (let i = 0; i < 3; i++) { if (pa[i] !== pb[i]) return pb[i] - pa[i]; }
|
|
776
|
+
return 0;
|
|
777
|
+
});
|
|
778
|
+
return versions[0];
|
|
779
|
+
} catch (_) {
|
|
780
|
+
return null;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
761
784
|
// playwriter's CLI `--timeout` option is parsed as a raw string (its own arg parser does not
|
|
762
785
|
// coerce it despite the zod schema declaring z.number()), and that string is forwarded verbatim
|
|
763
786
|
// into `vm.runInContext(..., { timeout })`, which throws
|
|
@@ -826,7 +849,19 @@ function findBrowserRunner() {
|
|
|
826
849
|
const whichCmd = process.platform === 'win32' ? 'where' : 'which';
|
|
827
850
|
const bunR = spawnSync(whichCmd, ['bun'], { encoding: 'utf-8', shell: true });
|
|
828
851
|
if (bunR.status === 0 && bunR.stdout.trim()) {
|
|
829
|
-
|
|
852
|
+
// `bun x <pkg>@latest` re-resolves the `@latest` tag against the npm registry on every
|
|
853
|
+
// single invocation, even when a resolved copy already sits in bun's own content-addressed
|
|
854
|
+
// cache (~/.bun/install/cache/<pkg>@<version>) from a prior run -- a redundant network
|
|
855
|
+
// round-trip per browser-verb dispatch. Observed on Windows to occasionally never return
|
|
856
|
+
// (hangs past "Resolved, downloaded and extracted" with no further output or error), which
|
|
857
|
+
// wedges every subsequent browser dispatch behind a 30s+ spawnSync timeout for no benefit,
|
|
858
|
+
// since the pinned exact version was already available locally. Prefer `bun x <pkg>@<exact
|
|
859
|
+
// cached version>` when a cached copy exists -- this still goes through `bun x`'s real
|
|
860
|
+
// dependency-tree resolution (the reason `bun x` is preferred over the raw cached bin.js
|
|
861
|
+
// above), it just skips the registry round-trip for the tag lookup itself.
|
|
862
|
+
const cachedVersion = findCachedBunRunnerVersion();
|
|
863
|
+
const pkgSpec = cachedVersion ? `${BROWSER_RUNNER_BIN}@${cachedVersion}` : `${BROWSER_RUNNER_BIN}@latest`;
|
|
864
|
+
return { cmd: 'bun', baseArgs: ['x', pkgSpec], shell: true };
|
|
830
865
|
}
|
|
831
866
|
const cachedBin = findCachedBunRunnerBin();
|
|
832
867
|
if (cachedBin) return { cmd: process.execPath, baseArgs: [cachedBin], shell: false };
|
package/gm.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1913",
|
|
4
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
5
|
"author": "AnEntrypoint",
|
|
6
6
|
"license": "MIT",
|
package/skills/gm/SKILL.md
CHANGED
|
@@ -60,6 +60,8 @@ bun x gm-plugkit@latest spool
|
|
|
60
60
|
|
|
61
61
|
(`npx -y gm-plugkit@latest spool` if no `bun`.) Fire-and-forget: spawns the detached daemon and returns immediately (already-alive watcher also returns at once, unchanged) -- it does NOT wait for the watcher to confirm serving. No `&`, no `sleep`, no re-`cat`; write your first verb to `in/` right after it returns. A first-read "file does not exist" on that verb is normal (the just-spawned watcher hasn't noticed the file yet) -- re-Read next message, same as any dead-watcher-adjacent recheck. If you need to actively confirm serving before dispatching (rare), read `.gm/exec-spool/.status.json` yourself and check `ts` freshness.
|
|
62
62
|
|
|
63
|
+
**A native `gm-runner` binary at `~/.gm-tools/gm-runner` (`gm-runner.exe` on Windows) is a drop-in replacement for the bun/npx boot line above, when present** -- `gm-runner spool` serves the identical spool ABI (same `in/`/`out/` layout, same verb names) directly from a compiled binary, no bun/npx/node_modules dependency for the boot step. It is optional and additive right now, not a required cutover: `bin/install.js`'s installer best-effort-downloads it (sha256-verified, from `AnEntrypoint/gm-runner-bin` releases) but silently no-ops if the platform has no published binary yet or the download fails, and gm-runner itself only auto-updates its own served `plugkit.wasm` (a background 600s remote-version poll), not its own executable. If `~/.gm-tools/gm-runner` (or `.exe`) exists, prefer it for the boot line; otherwise fall back to `bun x gm-plugkit@latest spool` exactly as before -- both are equally valid, check-for-binary-then-fallback, never a hard requirement on one or the other.
|
|
64
|
+
|
|
63
65
|
The `Resolving dependencies` / `Saved lockfile` chatter before the JSON payload is `bunx` re-resolving the `@latest` tag against the registry, not gm-plugkit hanging -- the daemon already spawns detached+unref'd and the CLI itself exits the instant that happens; the visible delay is entirely bunx's own network round-trip, unavoidable on `@latest` (a pinned exact version, once bunx-cached, skips it). `GM_PLUGKIT_SKIP_SELF_STALE_CHECK=1` skips the CLI's own redundant npm-registry version probe (already covered by `@latest`'s resolution) for a faster boot on repeat same-session invocations. In PowerShell 5.1, never `2>&1`-redirect this command into another cmdlet (e.g. `| Select-Object`) -- PowerShell wraps every stderr line from a native exe in a `NativeCommandError` record and reports failure even on exit 0, turning bun's routine stderr progress output into a misleading red error block; run it bare or capture stdout only.
|
|
64
66
|
|
|
65
67
|
**Dispatch shape: Write request + Read response, SAME tool-call block.** Never proceed/narrate/begin work before reading the response and following its `instruction` field. First-read "file does not exist" mid-verb = normal, re-Read next message. Never poll with `sleep && ls` -- plugkit is synchronous; missing response = dead watcher (recheck `ts`) or slow verb, never "still processing."
|