gm-skill 2.0.1628 → 2.0.1629

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
@@ -160,6 +160,8 @@ Orchestration state is tracked via `.gm/` marker files, not hook events; the CLI
160
160
 
161
161
  **Dead-watcher recovery uses `bun x gm-plugkit@latest spool`, never direct-node boot** (mechanism in rs-learn: `recall: dead-watcher recovery bun x not direct-node`).
162
162
 
163
+ **Starting the spool is one atomic blocking call -- `bun x gm-plugkit@latest spool` daemonizes the watcher AND blocks until `.status.json` heartbeats fresh, returning exit 0 only when serving (loud non-zero on timeout).** No `& + sleep + re-cat` boot dance; the agent writes to `instruction/` the moment the call returns. The wait lives in `gm-plugkit/cli.js` (`waitForWatcherHeartbeat`, `Atomics.wait` sync-sleep), cli-side because `startSpoolDaemon` is sync and shared by non-blocking callers. rs-plugkit carries no server-boot logic -- the daemonize lifecycle is entirely gm-plugkit JS, so this change needs no Rust/cascade rebuild.
164
+
163
165
  **Apparent tooling failure is mechanical self-recovery, NEVER a question for the user and never an a/b-test/blind-restart.** A missing spool response / stale watcher is the agent's own job: honor a future `busy_until` else boot the watcher and re-dispatch -- the spooler is sound by construction, so asking the user to do what a verb can do is a paper-spirit violation. Recovery mechanics (atomic `.status.json`, `FailedToOpenSocket` retry, debug-via-`window.*`-globals) in rs-learn (`recall: spooler self-recovery mechanics`).
164
166
 
165
167
  **Process-of-elimination is the debugging paradigm EVERYWHERE, and manual real-services witness is the verification paradigm EVERYWHERE** -- both stated in `instructions/execute.md` (served EXECUTE prose). Detail in rs-learn (`recall: process-of-elimination manual-real-services-witness paradigm`).
package/gm-plugkit/cli.js CHANGED
@@ -132,6 +132,31 @@ function spoolDir() {
132
132
  return path.join(projectDir, '.gm', 'exec-spool');
133
133
  }
134
134
 
135
+ function readStatus(dir) {
136
+ try { return JSON.parse(fs.readFileSync(path.join(dir, '.status.json'), 'utf-8')); } catch (_) { return null; }
137
+ }
138
+
139
+ function statusServing(st, freshMs) {
140
+ if (!st || !st.pid) return false;
141
+ const now = Date.now();
142
+ if (Number.isFinite(st.busy_until) && st.busy_until > now) return true;
143
+ return Number.isFinite(st.ts) && (now - st.ts) < freshMs;
144
+ }
145
+
146
+ function sleepSync(ms) {
147
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
148
+ }
149
+
150
+ function waitForWatcherHeartbeat(dir, deadlineMs, freshMs) {
151
+ const t0 = Date.now();
152
+ while (Date.now() - t0 < deadlineMs) {
153
+ const st = readStatus(dir);
154
+ if (statusServing(st, freshMs)) return st;
155
+ sleepSync(300);
156
+ }
157
+ return null;
158
+ }
159
+
135
160
  function ensureSpoolDir() {
136
161
  try { fs.mkdirSync(spoolDir(), { recursive: true }); } catch (_) {}
137
162
  }
@@ -207,13 +232,22 @@ function writeCliError(phase, err) {
207
232
  process.exit(1);
208
233
  }
209
234
 
210
- writeCliStatus({ phase: 'ready', version: bootstrapResult.version, daemon_pid: daemon.pid, log: daemon.logPath });
235
+ const serving = waitForWatcherHeartbeat(spoolDir(), 30000, 12000);
236
+ if (!serving) {
237
+ const errMsg = `watcher spawned (pid=${daemon.pid}) but .status.json heartbeat not fresh within 30s`;
238
+ writeCliError('watcher-heartbeat-timeout', new Error(errMsg));
239
+ console.error('Daemon start failed:', errMsg);
240
+ process.exit(1);
241
+ }
242
+
243
+ writeCliStatus({ phase: 'ready', version: bootstrapResult.version, daemon_pid: daemon.pid, watcher_pid: serving.pid, log: daemon.logPath });
211
244
 
212
245
  console.log(JSON.stringify({
213
246
  ok: true,
214
247
  binary: bootstrapResult.binaryPath,
215
248
  daemon,
216
- message: 'plugkit ready, spool watcher running'
249
+ watcher_pid: serving.pid,
250
+ message: 'plugkit ready, spool watcher serving'
217
251
  }));
218
252
  process.exit(0);
219
253
  })().catch((err) => {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1628",
3
+ "version": "2.0.1629",
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": {
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1628",
3
+ "version": "2.0.1629",
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.1628",
3
+ "version": "2.0.1629",
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",
@@ -34,15 +34,15 @@ Every turn: dispatch `instruction`, read it, follow the imperative, dispatch the
34
34
  cat .gm/exec-spool/.status.json 2>/dev/null; echo ---; cat .gm/exec-spool/.turn-summary.json 2>/dev/null; echo ---; date +%s%3N
35
35
  ```
36
36
 
37
- `.turn-summary.json` carries `phase`, `last_skill`, `prd_pending`, `last_instruction_ts`, `last_instruction_age_ms`, `long_gap_threshold_ms`, `browser_sessions_alive`, `update_available`, `deviations_30m`, `watcher_uptime_ms`. Age over threshold: your next non-orienting verb is gated, dispatch `instruction` first. `update_available` non-null: eager-upgrade with `bun x gm-plugkit@latest --kill-stale-watchers; bun x gm-plugkit@latest spool > /dev/null 2>&1 &`, then re-read `.status.json` after 8s to confirm `version`. `deviations_30m` non-zero indicates active drift to investigate before continuing.
37
+ `.turn-summary.json` carries `phase`, `last_skill`, `prd_pending`, `last_instruction_ts`, `last_instruction_age_ms`, `long_gap_threshold_ms`, `browser_sessions_alive`, `update_available`, `deviations_30m`, `watcher_uptime_ms`. Age over threshold: your next non-orienting verb is gated, dispatch `instruction` first. `update_available` non-null: eager-upgrade with `bun x gm-plugkit@latest --kill-stale-watchers; bun x gm-plugkit@latest spool` (the spool call blocks until serving), then read `.status.json` to confirm `version`. `deviations_30m` non-zero indicates active drift to investigate before continuing.
38
38
 
39
39
  Compare `.status.json` `ts` to the printed epoch: gap > 15000 = dead, boot it. Exception: a future `busy_until` means a long verb (browser/chromium spawn blocks the heartbeat ~15-18s) -- wait, do not boot a second watcher.
40
40
 
41
41
  ```bash
42
- bun x gm-plugkit@latest spool > /dev/null 2>&1 &
42
+ bun x gm-plugkit@latest spool
43
43
  ```
44
44
 
45
- (`npx -y gm-plugkit@latest spool > /dev/null 2>&1 &` if `bun` missing.) Wait ~8s, re-`cat .status.json` for a fresh `ts`, and only then write to `instruction/`.
45
+ (`npx -y gm-plugkit@latest spool` if `bun` missing.) This call is atomic: it daemonizes the watcher and blocks until `.status.json` reports a fresh heartbeat, returning only once the spool is serving (exit 0) or failing loud on timeout. No `&`, no `sleep`, no re-`cat` -- when it returns you write to `instruction/` directly. (An already-alive watcher makes it return at once.)
46
46
 
47
47
  **Dispatch shape: Write request + Read response in the SAME tool-call block.** The shape is `Write .gm/exec-spool/in/instruction/<N>.txt` AND `Read .gm/exec-spool/out/instruction-<N>.json` (or `out/<N>.json` for nested verbs) in one block. A first-read "file does not exist" while plugkit is mid-verb is normal -- re-Read next message. Do not proceed, narrate readiness, or begin work before reading the response and following its `instruction` field. Never poll with `sleep && ls`: plugkit is synchronous, so a missing response means dead watcher (re-check `ts`) or slow verb (check `.gm/exec-spool/.watcher.log`), not "still processing."
48
48