gm-skill 2.0.1120 → 2.0.1121

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/README.md CHANGED
@@ -28,7 +28,7 @@ npx gm-skill-bootstrap
28
28
 
29
29
  ## Version
30
30
 
31
- `2.0.1120` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` republishes this package alongside all 15 platform packages.
31
+ `2.0.1121` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` republishes this package alongside all 15 platform packages.
32
32
 
33
33
  ## Source of truth
34
34
 
@@ -597,22 +597,31 @@ function copyWasmToGmTools(wasmPath, version) {
597
597
  const dst = gmToolsDir();
598
598
  fs.mkdirSync(dst, { recursive: true });
599
599
  const target = path.join(dst, 'plugkit.wasm');
600
+ const wrapperSrc = path.join(__dirname, 'plugkit-wasm-wrapper.js');
601
+ const wrapperDst = path.join(dst, 'plugkit-wasm-wrapper.js');
600
602
 
603
+ let wasmFresh = false;
601
604
  if (fs.existsSync(target)) {
602
- let needsRefresh = true;
603
605
  try {
604
606
  const cur = sha256OfFileSync(target);
605
607
  const src = sha256OfFileSync(wasmPath);
606
- if (cur === src) needsRefresh = false;
608
+ if (cur === src) wasmFresh = true;
607
609
  } catch (_) {}
608
- if (!needsRefresh) {
609
- try { fs.writeFileSync(path.join(dst, 'plugkit.version'), version); } catch (_) {}
610
- return;
611
- }
612
610
  }
613
-
614
- fs.copyFileSync(wasmPath, target);
611
+ if (!wasmFresh) fs.copyFileSync(wasmPath, target);
615
612
  fs.writeFileSync(path.join(dst, 'plugkit.version'), version);
613
+
614
+ if (fs.existsSync(wrapperSrc)) {
615
+ let wrapperFresh = false;
616
+ if (fs.existsSync(wrapperDst)) {
617
+ try {
618
+ const cur = sha256OfFileSync(wrapperDst);
619
+ const src = sha256OfFileSync(wrapperSrc);
620
+ if (cur === src) wrapperFresh = true;
621
+ } catch (_) {}
622
+ }
623
+ if (!wrapperFresh) fs.copyFileSync(wrapperSrc, wrapperDst);
624
+ }
616
625
  }
617
626
 
618
627
  function getWasmPath() {
@@ -6,6 +6,10 @@ import https from 'https';
6
6
  import { watch } from 'fs';
7
7
  import { spawn, spawnSync } from 'child_process';
8
8
  import net from 'net';
9
+ import { fileURLToPath } from 'url';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
9
13
 
10
14
  const KV_DIR = path.join(os.homedir(), '.claude', 'gm-tools', 'kv');
11
15
  fs.mkdirSync(KV_DIR, { recursive: true });
@@ -632,6 +636,24 @@ async function runSpoolWatcher(instance, spoolDir) {
632
636
  process.on('SIGTERM', () => { releaseLock(); process.exit(0); });
633
637
  process.on('exit', releaseLock);
634
638
 
639
+ try {
640
+ const wrapperDst = path.join(os.homedir(), '.claude', 'gm-tools', 'plugkit-wasm-wrapper.js');
641
+ if (path.resolve(__filename) !== path.resolve(wrapperDst)) {
642
+ let same = false;
643
+ if (fs.existsSync(wrapperDst)) {
644
+ try {
645
+ const a = fs.readFileSync(__filename);
646
+ const b = fs.readFileSync(wrapperDst);
647
+ if (a.length === b.length && crypto.createHash('sha256').update(a).digest('hex') === crypto.createHash('sha256').update(b).digest('hex')) same = true;
648
+ } catch (_) {}
649
+ }
650
+ if (!same) {
651
+ fs.copyFileSync(__filename, wrapperDst);
652
+ console.log(`[plugkit-wasm] installed wrapper at ${wrapperDst}`);
653
+ }
654
+ }
655
+ } catch (e) { console.error(`[plugkit-wasm] wrapper self-install failed: ${e.message}`); }
656
+
635
657
  console.log(`[plugkit-wasm] plugkit v${resolveVersion(instance)} (wasm)`);
636
658
  console.log(`[plugkit-wasm] watching ${inDir}`);
637
659
 
@@ -853,22 +875,116 @@ async function runSpoolWatcher(instance, spoolDir) {
853
875
  await new Promise(() => {});
854
876
  }
855
877
 
856
- (async () => {
878
+ async function selfHealFromGithubReleases() {
879
+ return new Promise((resolve, reject) => {
880
+ const fetchJson = (url) => new Promise((res, rej) => {
881
+ const req = https.get(url, { timeout: 5000, headers: { 'user-agent': 'plugkit-wasm-wrapper', 'accept': 'application/json' } }, (r) => {
882
+ if (r.statusCode >= 300 && r.statusCode < 400 && r.headers.location) {
883
+ r.resume(); fetchJson(r.headers.location).then(res, rej); return;
884
+ }
885
+ if (r.statusCode !== 200) { r.resume(); rej(new Error(`HTTP ${r.statusCode} ${url}`)); return; }
886
+ const chunks = []; r.on('data', c => chunks.push(c));
887
+ r.on('end', () => { try { res(JSON.parse(Buffer.concat(chunks).toString('utf-8'))); } catch (e) { rej(e); } });
888
+ r.on('error', rej);
889
+ });
890
+ req.on('timeout', () => req.destroy(new Error('timeout')));
891
+ req.on('error', rej);
892
+ });
893
+ const fetchBuf = (url) => new Promise((res, rej) => {
894
+ const req = https.get(url, { timeout: 30000, headers: { 'user-agent': 'plugkit-wasm-wrapper' } }, (r) => {
895
+ if (r.statusCode >= 300 && r.statusCode < 400 && r.headers.location) {
896
+ r.resume(); fetchBuf(r.headers.location).then(res, rej); return;
897
+ }
898
+ if (r.statusCode !== 200) { r.resume(); rej(new Error(`HTTP ${r.statusCode} ${url}`)); return; }
899
+ const chunks = []; r.on('data', c => chunks.push(c));
900
+ r.on('end', () => res(Buffer.concat(chunks)));
901
+ r.on('error', rej);
902
+ });
903
+ req.on('timeout', () => req.destroy(new Error('timeout')));
904
+ req.on('error', rej);
905
+ });
906
+ (async () => {
907
+ try {
908
+ const rel = await fetchJson('https://api.github.com/repos/AnEntrypoint/plugkit-bin/releases/latest');
909
+ const tag = rel.tag_name;
910
+ if (!tag) throw new Error('no tag_name from GH Releases');
911
+ const version = tag.replace(/^v/, '');
912
+ const base = `https://github.com/AnEntrypoint/plugkit-bin/releases/download/${tag}`;
913
+ const [wasm, sha] = await Promise.all([
914
+ fetchBuf(`${base}/plugkit.wasm`),
915
+ fetchBuf(`${base}/plugkit.wasm.sha256`).then(b => b.toString('utf-8').trim().split(/\s+/)[0]).catch(() => ''),
916
+ ]);
917
+ if (sha) {
918
+ const got = crypto.createHash('sha256').update(wasm).digest('hex');
919
+ if (got !== sha) throw new Error(`sha mismatch: got ${got}, expected ${sha}`);
920
+ }
921
+ const toolsDir = path.join(os.homedir(), '.claude', 'gm-tools');
922
+ fs.mkdirSync(toolsDir, { recursive: true });
923
+ fs.writeFileSync(path.join(toolsDir, 'plugkit.wasm'), wasm);
924
+ fs.writeFileSync(path.join(toolsDir, 'plugkit.version'), version);
925
+ const wrapperSrc = __filename;
926
+ const wrapperDst = path.join(toolsDir, 'plugkit-wasm-wrapper.js');
927
+ if (path.resolve(wrapperSrc) !== path.resolve(wrapperDst) && fs.existsSync(wrapperSrc)) {
928
+ try { fs.copyFileSync(wrapperSrc, wrapperDst); } catch (_) {}
929
+ }
930
+ resolve({ ok: true, version, sha });
931
+ } catch (e) { reject(e); }
932
+ })();
933
+ });
934
+ }
935
+
936
+ async function selfHeal(reason) {
937
+ console.error(`[plugkit-wasm] self-heal: ${reason}`);
857
938
  try {
858
- const wasmPath = path.join(os.homedir(), '.claude', 'gm-tools', 'plugkit.wasm');
859
- const wasmBuffer = fs.readFileSync(wasmPath);
860
- const wasmModule = new WebAssembly.Module(wasmBuffer);
939
+ const r = await selfHealFromGithubReleases();
940
+ console.error(`[plugkit-wasm] self-heal: installed v${r.version} from GH Releases`);
941
+ return true;
942
+ } catch (e) {
943
+ console.error(`[plugkit-wasm] self-heal GH fetch failed: ${e.message}`);
944
+ }
945
+ console.error('[plugkit-wasm] self-heal: run `bun x gm-plugkit@latest spool` to recover manually');
946
+ return false;
947
+ }
861
948
 
862
- const instanceRef = { value: null };
863
- const hostFunctions = makeHostFunctions(instanceRef);
949
+ async function tryInstantiate(wasmPath) {
950
+ const wasmBuffer = fs.readFileSync(wasmPath);
951
+ const wasmModule = new WebAssembly.Module(wasmBuffer);
952
+ const instanceRef = { value: null };
953
+ const hostFunctions = makeHostFunctions(instanceRef);
954
+ const importObject = {
955
+ env: hostFunctions,
956
+ wasi_snapshot_preview1: createWasiShim(instanceRef),
957
+ };
958
+ const instance = new WebAssembly.Instance(wasmModule, importObject);
959
+ instanceRef.value = instance;
960
+ return { instance, instanceRef };
961
+ }
864
962
 
865
- const importObject = {
866
- env: hostFunctions,
867
- wasi_snapshot_preview1: createWasiShim(instanceRef),
868
- };
963
+ (async () => {
964
+ try {
965
+ const wasmPath = path.join(os.homedir(), '.claude', 'gm-tools', 'plugkit.wasm');
869
966
 
870
- const instance = new WebAssembly.Instance(wasmModule, importObject);
871
- instanceRef.value = instance;
967
+ let instance, instanceRef;
968
+ if (!fs.existsSync(wasmPath)) {
969
+ const healed = await selfHeal('wasm not installed');
970
+ if (!healed) process.exit(1);
971
+ }
972
+ try {
973
+ ({ instance, instanceRef } = await tryInstantiate(wasmPath));
974
+ } catch (e) {
975
+ const isLink = e && (e.name === 'LinkError' || /Import/i.test(e.message || ''));
976
+ const isCompile = e && (e.name === 'CompileError' || /WebAssembly/i.test(e.message || ''));
977
+ if (isLink || isCompile) {
978
+ const healed = await selfHeal(`${e.name || 'instantiate'}: ${e.message}`);
979
+ if (!healed) {
980
+ console.error('[plugkit-wasm] wrapper/wasm version skew — run: bun x gm-plugkit@latest spool');
981
+ process.exit(1);
982
+ }
983
+ ({ instance, instanceRef } = await tryInstantiate(wasmPath));
984
+ } else {
985
+ throw e;
986
+ }
987
+ }
872
988
 
873
989
  const args = process.argv.slice(2);
874
990
  if (args.includes('--version')) {
@@ -876,6 +992,25 @@ async function runSpoolWatcher(instance, spoolDir) {
876
992
  process.exit(0);
877
993
  }
878
994
 
995
+ if (args[0] === 'bootstrap' || args.includes('--ensure-latest')) {
996
+ try {
997
+ const bootstrapPath = path.join(__dirname, 'bootstrap.js');
998
+ if (fs.existsSync(bootstrapPath)) {
999
+ const bootstrap = await import('file://' + bootstrapPath.replace(/\\/g, '/'));
1000
+ if (bootstrap && typeof bootstrap.ensureReady === 'function') {
1001
+ const r = await bootstrap.ensureReady({ forceLatest: true });
1002
+ console.log(JSON.stringify(r || { ok: true }));
1003
+ process.exit(0);
1004
+ }
1005
+ }
1006
+ console.error('bootstrap.js not callable');
1007
+ process.exit(1);
1008
+ } catch (e) {
1009
+ console.error('bootstrap error:', e.message);
1010
+ process.exit(1);
1011
+ }
1012
+ }
1013
+
879
1014
  if (args[0] === 'spool') {
880
1015
  const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
881
1016
  const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1120",
3
+ "version": "2.0.1121",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1120",
3
+ "version": "2.0.1121",
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",
@@ -39,7 +39,7 @@
39
39
  "gm.json"
40
40
  ],
41
41
  "dependencies": {
42
- "gm-plugkit": "^2.0.1120"
42
+ "gm-plugkit": "^2.0.1121"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=16.0.0"
@@ -10,9 +10,13 @@ The wasm artifact lives at `~/.claude/gm-tools/plugkit.wasm`; the spool watcher
10
10
 
11
11
  ## Boot the spool watcher (first turn only)
12
12
 
13
- Check `.gm/exec-spool/.status.json`. If absent or `ts` > 15s old:
13
+ Check `.gm/exec-spool/.status.json`. If absent or `ts` > 15s old, boot via the npm package — `bun x gm-plugkit@latest spool` fetches the freshest plugkit (wasm + wrapper), copies them into `~/.claude/gm-tools/`, then enters spool mode:
14
14
 
15
- `node ~/.claude/gm-tools/plugkit-wasm-wrapper.js spool > /dev/null 2>&1 &`
15
+ ```
16
+ bun x gm-plugkit@latest spool > /dev/null 2>&1 &
17
+ ```
18
+
19
+ If `bun` is not available, fall back to `npx -y gm-plugkit@latest spool > /dev/null 2>&1 &` or to the local wrapper if it's already installed: `node ~/.claude/gm-tools/plugkit-wasm-wrapper.js spool > /dev/null 2>&1 &`. The wrapper has a self-heal: if it detects a `LinkError` or missing wasm at instantiation, it re-runs bootstrap automatically and retries.
16
20
 
17
21
  Wait 2 seconds, verify `.status.json` is fresh. Then proceed.
18
22