gm-skill 2.0.1628 → 2.0.1630

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
@@ -130,6 +130,8 @@ Every skill's `allowed-tools:` is reduced to `Skill, Read, Write` (plus the SKIL
130
130
 
131
131
  **The agent IS the LLM rs-learn calls**: no separate judge model; all decisions are inline via spool. Internals in rs-learn (`recall: rs-learn self-report core internals`).
132
132
 
133
+ **Idempotency contract (f∘f≡f)**: the spool dispatch layer is at-least-once by design (the in-memory processed-Map guards only concurrent double-pickup, not cross-time replay or restart), so correctness rests on every state-mutating verb being individually convergent: `memorize`/`memorize-fire` content-hash key + dedup, `git_finalize`/`git_commit` nothing-to-commit/already-pushed, `insert_edge` kv-overwrite-by-id + dedup-guarded index, `invalidate_edge` early-return, `ensure_managed_gitignore` strip-rebuild-changed-gate, codeinsight digest-gate, publish.yml already-published-skip + porcelain-gated version-commit-back. Read-only verbs (recall/codesearch/git_status/instruction/health/filter) recompute every dispatch, never cache. `exec_js`/`browser` re-run on replay (at-most-once-by-nature); a persistent dedup ledger was rejected as net-additive. Detail in rs-learn (`recall: idempotency contract per-verb convergence`).
134
+
133
135
  **host_exec_js is synchronous**: pass a real per-call `timeoutMs` (zero/missing is a hard error). Detail in rs-learn (`recall: host_exec_js synchronous`).
134
136
 
135
137
  **Sync-before-emit (codeinsight + search)**: output must come from a freshly-synced index this invocation (cache serves only on digest match). Mechanics in rs-learn (`recall: sync-before-emit codeinsight search`).
@@ -160,6 +162,8 @@ Orchestration state is tracked via `.gm/` marker files, not hook events; the CLI
160
162
 
161
163
  **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
164
 
165
+ **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.
166
+
163
167
  **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
168
 
165
169
  **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.1630",
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.1630",
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.1630",
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