gm-skill 2.0.1161 → 2.0.1163

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
@@ -35,7 +35,7 @@ An earlier generation fanned out fifteen per-platform downstream repos (gm-cc, g
35
35
 
36
36
  ## Version
37
37
 
38
- `2.0.1161` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` (or any cascading sibling crate) republishes this package.
38
+ `2.0.1163` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` (or any cascading sibling crate) republishes this package.
39
39
 
40
40
  ## Source of truth
41
41
 
package/bin/bootstrap.js CHANGED
@@ -7,7 +7,7 @@ const os = require('os');
7
7
  const crypto = require('crypto');
8
8
  const { spawnSync } = require('child_process');
9
9
 
10
- const NPM_PACKAGE = '@anentrypoint/plugkit-wasm';
10
+ const NPM_PACKAGE = 'plugkit-wasm';
11
11
  const ATTEMPT_TIMEOUT_MS = 10 * 60 * 1000;
12
12
  const MAX_ATTEMPTS = 3;
13
13
  const BACKOFF_MS = [5000, 15000];
@@ -7,7 +7,7 @@ const os = require('os');
7
7
  const crypto = require('crypto');
8
8
  const { spawn, spawnSync } = require('child_process');
9
9
 
10
- const NPM_PACKAGE = '@anentrypoint/plugkit-wasm';
10
+ const NPM_PACKAGE = 'plugkit-wasm';
11
11
  const ATTEMPT_TIMEOUT_MS = 10 * 60 * 1000;
12
12
  const MAX_ATTEMPTS = 3;
13
13
  const BACKOFF_MS = [5000, 15000];
@@ -811,6 +811,37 @@ function getBinaryPath() {
811
811
  return getWasmPath();
812
812
  }
813
813
 
814
+ function probeUnsupervisedWatcher(spoolDir) {
815
+ try {
816
+ const statusPath = path.join(spoolDir, '.status.json');
817
+ const supervisorPath = path.join(spoolDir, '.supervisor.json');
818
+ const markerPath = path.join(spoolDir, '.pre-supervised-watcher.json');
819
+ if (!fs.existsSync(statusPath)) {
820
+ try { fs.unlinkSync(markerPath); } catch (_) {}
821
+ return;
822
+ }
823
+ const status = JSON.parse(fs.readFileSync(statusPath, 'utf-8'));
824
+ const age = Date.now() - (status && status.ts || 0);
825
+ if (age > 30_000) {
826
+ try { fs.unlinkSync(markerPath); } catch (_) {}
827
+ return;
828
+ }
829
+ if (fs.existsSync(supervisorPath)) {
830
+ try { fs.unlinkSync(markerPath); } catch (_) {}
831
+ return;
832
+ }
833
+ const marker = {
834
+ ts: Date.now(),
835
+ reason: 'running-watcher-has-no-supervisor',
836
+ watcher_pid: status.pid,
837
+ watcher_version: status.version,
838
+ severity: 'warn',
839
+ instruction: 'A running watcher was started under an older bootstrap that did not spawn a supervisor. Unplanned-restart recovery and idle-teardown coordination are dormant. To migrate, stop the current watcher (taskkill /F /T /PID <watcher_pid> on Windows or kill <watcher_pid> on POSIX) and let the next bootstrap re-spawn it under supervisor.js.',
840
+ };
841
+ fs.writeFileSync(markerPath, JSON.stringify(marker, null, 2));
842
+ } catch (_) {}
843
+ }
844
+
814
845
  function startSpoolDaemon() {
815
846
  try {
816
847
  const wrapper = path.join(gmToolsDir(), 'plugkit-wasm-wrapper.js');
@@ -821,6 +852,7 @@ function startSpoolDaemon() {
821
852
  const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
822
853
  const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
823
854
  fs.mkdirSync(spoolDir, { recursive: true });
855
+ probeUnsupervisedWatcher(spoolDir);
824
856
  const logPath = path.join(spoolDir, '.watcher.log');
825
857
  try {
826
858
  const stat = fs.statSync(logPath);
@@ -216,6 +216,7 @@ process.on('SIGTERM', () => {
216
216
 
217
217
  writeSupervisorStatus('starting', {});
218
218
  logEvent('supervisor.starting', { spool_dir: spoolDir });
219
+ try { fs.unlinkSync(path.join(spoolDir, '.pre-supervised-watcher.json')); } catch (_) {}
219
220
  spawnWatcher('initial');
220
221
  setInterval(checkWatcherHealth, POLL_INTERVAL_MS);
221
222
  setInterval(() => writeSupervisorStatus('watching', { watcher_pid: currentChildPid, boot_reason: currentBootReason }), 10_000);
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1161",
3
+ "version": "2.0.1163",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
@@ -170,6 +170,25 @@ function markInstructionSeen(sessionId) {
170
170
  } catch (_) {}
171
171
  }
172
172
 
173
+ const DEFER_MARKERS = [
174
+ 'next pass', 'next session', 'next turn',
175
+ 'defer to later', 'deferred to later', 'deferred for later',
176
+ 'future pass', 'future session', 'future turn',
177
+ 'address it next', 'address this next', 'leave for next',
178
+ 'documented for next', 'documented for future',
179
+ 'below criticality', 'skip for now', 'punt for now',
180
+ 'do later', 'fix later', 'later pass',
181
+ ];
182
+
183
+ function deferMarkerIn(text) {
184
+ if (!text) return null;
185
+ const lower = String(text).toLowerCase();
186
+ for (const m of DEFER_MARKERS) {
187
+ if (lower.includes(m)) return m;
188
+ }
189
+ return null;
190
+ }
191
+
173
192
  function checkDispatchGates(sessionId, operation, extra) {
174
193
  const cwd = process.cwd();
175
194
  const gm = path.join(cwd, '.gm');
@@ -226,6 +245,17 @@ function checkDispatchGates(sessionId, operation, extra) {
226
245
  logDeviation('deviation.mutable-without-evidence', { mutable_id: extra.id || null });
227
246
  }
228
247
 
248
+ if (operation === 'git' && extra && extra.commit_message) {
249
+ const marker = deferMarkerIn(extra.commit_message);
250
+ if (marker) {
251
+ logDeviation('deviation.commit-message-defer', { marker, operation });
252
+ return {
253
+ allowed: false,
254
+ reason: `commit message rejected: deferral phrase '${marker}' detected. Per paper §22 Fix on Sight, defer markers are forced closure. Either inline-fix and re-witness, or split the deferred work as a separate PRD item with blockedBy: [external] before committing. Rewrite the commit message and retry.`,
255
+ };
256
+ }
257
+ }
258
+
229
259
  if (!['write', 'edit', 'git'].includes(operation)) return { allowed: true };
230
260
 
231
261
  if (fs.existsSync(prdPath) && fs.existsSync(needsGmPath) && !fs.existsSync(gmFiredPath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1161",
3
+ "version": "2.0.1163",
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.1161"
42
+ "gm-plugkit": "^2.0.1163"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=16.0.0"
@@ -20,6 +20,10 @@ If the `instruction` response carries a non-null `update_available`, plugkit dri
20
20
 
21
21
  If `running_tasks` is non-empty, you own them — every entry is a subprocess you started that's still consuming CPU/memory. Stop ones that have outlived their purpose with `task-stop` (write `.gm/exec-spool/in/task-stop/<N>.txt` with `{id: "t<n>"}`). The 15-min idle reaper is the last resort, not a substitute for hygiene. If `stuck_spool` is non-empty, dispatches are wedged — the watcher's host_exec_js is synchronous and a stuck body blocks every other verb until it returns. Diagnose via `.watcher.log` and consider rebootstrapping if it doesn't clear; spool bodies are Turing-complete and can loop forever. Long-running work goes through `task-spawn` (returns a `task_id` immediately, body runs detached), not through the sync `nodejs`/`bash`/`python` language verbs.
22
22
 
23
+ If `should_residual_scan` is true, dispatch `residual-scan` before any text response that reads like a stop. The marker is one-shot per stopping window — re-checking after you've fired it is fine; firing it for the first time in a session that has pending work is non-negotiable. If `unsupervised_watcher` is non-null, the running watcher predates supervisor.js and has no unplanned-restart recovery; stop the named PID and let the next bootstrap re-spawn it under supervision.
24
+
25
+ No deferral phrasing. "Next pass", "next session", "future work", "defer to later", "address it next", "below criticality", "do later", "fix later" are all forced closure dressed up. `prd-add` rejects items whose description/subject/notes contain these markers unless the item carries `blockedBy: [external]` or `blockedBy: [out-of-reach]`. The dispatch gate also rejects git commits whose message contains them. Per §22 Fix on Sight: in-spirit reachable work is executed this turn, not deferred. If the work is genuinely unreachable, declare it with blockedBy; that's an auditable refusal, not a defer.
26
+
23
27
  The wasm artifact lives at `~/.claude/gm-tools/plugkit.wasm`; the spool watcher runs it. The watcher's own stdout/stderr is appended to `.gm/exec-spool/.watcher.log` — Read it to see plugkit's internal trace, dispatch timings, sweep actions, errors.
24
28
 
25
29
  The watcher self-shuts-down after 15 minutes idle (no spool I/O, no live browser session) and is restarted on next agent activity by a detached supervisor. `.gm/exec-spool/.unplanned-restart.json` is a critical-failure marker — present means a prior watcher died without a planned shutdown. Treat as a PRD-worthy incident on sight: diagnose via `.watcher.log` and `gm-log/<day>/plugkit.jsonl` events `supervisor.watcher-exited-unexpectedly` and `supervisor.heartbeat-stale` around the prior_status.ts timestamp, then delete the marker once root cause is named.