gm-skill 2.0.1409 → 2.0.1411

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 CHANGED
@@ -34,7 +34,9 @@ Agents dispatch verbs by writing to `.gm/exec-spool/in/<verb>/<N>.txt` (request
34
34
 
35
35
  **Orchestrator verbs**: `instruction`, `transition`, `phase-status`, `mutable-resolve`, `memorize-fire`, `residual-scan`, `auto-recall`.
36
36
 
37
- **Wasm-direct verbs**: `fs_read`, `fs_write`, `fs_stat`, `fs_readdir`, `kv_get`, `kv_put`, `kv_query`, `fetch`, `exec_js`, `env_get`, `recall`, `codesearch`, `memorize`, `health`, `filter`, `git_status`, `branch_status`, `git_push`.
37
+ **Wasm-direct verbs**: `fs_read`, `fs_write`, `fs_stat`, `fs_readdir`, `kv_get`, `kv_put`, `kv_query`, `fetch`, `exec_js`, `env_get`, `recall`, `codesearch`, `memorize`, `memorize-prune`, `health`, `filter`, `git_status`, `branch_status`, `git_push`.
38
+
39
+ **memorize-prune verb**: deletes bad/superseded memories — pruning bad memory matters more than preserving good memory, since a wrong recall hit is worse than a miss. Two modes: explicit `{key}`/`{keys:[...]}` deletes exactly those mem keys (text + `-vec` embedding sibling) via the `host_kv_delete` host import; `{query}` returns review-only candidates (vector_top_k hits with keys) for the agent to judge, then re-dispatch with the stale `{keys:[...]}`. Query mode never auto-deletes by similarity — a blind similarity-delete is itself a bad-memory generator; the destructive step stays under agent judgment. Emits `memory.pruned` per deletion.
38
40
 
39
41
  **git verbs**: `git_status` returns `{dirty, modified, untracked, deleted, staged}` from `git status --porcelain`. `branch_status` returns `{branch, ahead, behind, remote}` — the `remote-pushed` witness. `git_push` is the ONLY admissible push surface — it gates on `git_porcelain()` non-empty (refuses dirty), emits `deviation.push-dirty` on attempt, and shells the push only when clean. A raw `git push` via Bash bypasses the gate and is itself a deviation; ccsniff `--git-discipline` flags it.
40
42
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1409",
3
+ "version": "2.0.1411",
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": {
@@ -1505,6 +1505,26 @@ function makeHostFunctions(instanceRef) {
1505
1505
  }
1506
1506
  },
1507
1507
 
1508
+ host_kv_delete: (nsPtr, nsLen, keyPtr, keyLen) => {
1509
+ try {
1510
+ const ns = readWasmStr(instanceRef.value, nsPtr, nsLen);
1511
+ const key = readWasmStr(instanceRef.value, keyPtr, keyLen);
1512
+ if (!ns || !key) return 0;
1513
+ let removed = 0;
1514
+ // Delete the key from the namespace AND its -vec sibling across every enabled discipline dir,
1515
+ // so a pruned memory leaves no orphan embedding that host_vec_search would still surface.
1516
+ for (const baseNs of [ns, `${ns}-vec`]) {
1517
+ for (const dir of kvNamespaceDirs(baseNs)) {
1518
+ const fp = path.join(dir, safeName(key) + '.json');
1519
+ try { if (fs.existsSync(fp)) { fs.rmSync(fp, { force: true }); removed++; } } catch (_) {}
1520
+ }
1521
+ }
1522
+ return removed > 0 ? 1 : 0;
1523
+ } catch (e) {
1524
+ return 0;
1525
+ }
1526
+ },
1527
+
1508
1528
  host_kv_query: (nsPtr, nsLen, qPtr, qLen) => {
1509
1529
  try {
1510
1530
  const ns = readWasmStr(instanceRef.value, nsPtr, nsLen);
@@ -2862,7 +2882,6 @@ async function runSpoolWatcher(instance, spoolDir) {
2862
2882
  const UPDATE_CHECK_SHARED_CACHE = path.join(GM_TOOLS_ROOT, '.update-check-cache.json');
2863
2883
  const UPDATE_CHECK_CACHE_TTL_MS = 4 * 60 * 1000;
2864
2884
  let _lastKnownDrift = null;
2865
- let _autoUpdateArmedFor = null;
2866
2885
  function readSharedUpdateCache() {
2867
2886
  try {
2868
2887
  const content = fs.readFileSync(UPDATE_CHECK_SHARED_CACHE, 'utf-8');
@@ -2964,18 +2983,9 @@ async function runSpoolWatcher(instance, spoolDir) {
2964
2983
  logEvent('plugkit', 'update.available', { installed, latest });
2965
2984
  _lastKnownDrift = latest;
2966
2985
  }
2967
- // Auto-update from the shared-cache path too, so any watcher (not only the one that hit the
2968
- // network) arms the self-respawn. The cache entry is only written after a 200 from
2969
- // /releases/latest, and the respawn's ensureReady re-verifies the wasm asset before downloading,
2970
- // so a bump here is safe even without per-asset confirmation in this branch.
2971
- if (isDrift && _autoUpdateArmedFor !== latest) {
2972
- try {
2973
- _autoUpdateArmedFor = latest;
2974
- const verFile = path.join(GM_TOOLS_ROOT, 'plugkit.version');
2975
- fs.writeFileSync(verFile, latest + '\n');
2976
- logEvent('plugkit', 'update.auto-armed', { installed, latest, source: 'shared-cache', action: 'bumped-version-file-for-drift-respawn' });
2977
- } catch (_) {}
2978
- }
2986
+ // NOTE: no version-file bump here either see the network-path comment above. Bumping the version
2987
+ // file ahead of a verified binary download poisons installedVersionAtTools() and causes an infinite
2988
+ // drift-respawn thrash. Auto-update is notify-only until a sha-verified force-download path exists.
2979
2989
  }
2980
2990
  function checkForUpdate() {
2981
2991
  const installed = resolveVersion(instance);
@@ -3018,7 +3028,7 @@ async function runSpoolWatcher(instance, spoolDir) {
3018
3028
  installed,
3019
3029
  latest,
3020
3030
  checked_at_ms: Date.now(),
3021
- instruction: 'plugkit is out of date. The watcher auto-updates: on the next drift-check tick it bumps the disk version file and self-respawns into the fresh wasm. No manual action needed.',
3031
+ instruction: 'plugkit is out of date. Update with: bun x gm-plugkit@latest --kill-stale-watchers; bun x gm-plugkit@latest spool. A fresh boot downloads the new wasm and respawns; an in-place running watcher does not self-download.',
3022
3032
  update_url,
3023
3033
  }, null, 2));
3024
3034
  console.log(`[update] available: installed=${installed} latest=${latest} → wrote ${UPDATE_AVAILABLE_PATH}`);
@@ -3026,27 +3036,14 @@ async function runSpoolWatcher(instance, spoolDir) {
3026
3036
  logEvent('plugkit', 'update.available', { installed, latest, update_url });
3027
3037
  _lastKnownDrift = latest;
3028
3038
  }
3029
- // Auto-update: a running healthy watcher otherwise never self-updates — checkForUpdate
3030
- // only NOTIFIED, and the version-drift self-respawn compares instance-vs-disk-file, but
3031
- // the disk file only moved on a fresh boot that never ran while the watcher held the lock.
3032
- // Close the loop: once the release is fully published (wasm asset present, confirmed by
3033
- // this /releases/latest response carrying it), bump the disk version file to `latest`.
3034
- // The existing drift-check tick then sees instance != file and self-respawns; the respawn's
3035
- // ensureReady downloads the wasm matching the bumped version file. Guard against partial
3036
- // releases and respawn thrash: require the wasm asset, and write the file at most once per
3037
- // detected version (dedupe via _autoUpdateArmedFor).
3038
- try {
3039
- const hasWasmAsset = Array.isArray(rel.assets) && rel.assets.some(a => a && a.name === 'plugkit.wasm');
3040
- if (hasWasmAsset && latest !== installed && _autoUpdateArmedFor !== latest) {
3041
- _autoUpdateArmedFor = latest;
3042
- const verFile = path.join(GM_TOOLS_ROOT, 'plugkit.version');
3043
- fs.writeFileSync(verFile, latest + '\n');
3044
- logEvent('plugkit', 'update.auto-armed', { installed, latest, action: 'bumped-version-file-for-drift-respawn' });
3045
- console.log(`[update] auto-armed: bumped ${verFile} ${installed} -> ${latest}; drift-check will self-respawn into fresh wasm`);
3046
- }
3047
- } catch (e) {
3048
- logUpdateCheckError({ error: `auto-arm failed: ${String(e && e.message || e)}` });
3049
- }
3039
+ // NOTE: do NOT bump the disk version file here to "arm" a drift-respawn. installedVersionAtTools()
3040
+ // reads that file as the source of truth for the installed version; bumping it ahead of the actual
3041
+ // wasm download makes ensureReady compute versionDrift=false (file==target) and isReady()=true, so it
3042
+ // returns already-ready WITHOUT downloading the new binary while the running instance is still the
3043
+ // old version. The drift-check then sees instance(old) != file(new) forever and self-respawns in an
3044
+ // infinite loop, each respawn reloading the same old wasm. The version file must only advance AFTER
3045
+ // a verified binary download (bootstrap's job). Auto-update stays notify-only until ensureReady gains
3046
+ // a sha-verified force-download path; see PRD watcher-autoupdate-thrash-fix.
3050
3047
  } catch (e) {
3051
3048
  logUpdateCheckError({ error: String(e && e.message || e) });
3052
3049
  }
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1409",
3
+ "version": "2.0.1411",
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.1409",
3
+ "version": "2.0.1411",
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",