gm-skill 2.0.1971 → 2.0.1973

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.
@@ -46,7 +46,7 @@ function obsEvent(subsystem, event, fields) {
46
46
 
47
47
  function writeBootstrapError(spec) {
48
48
  try {
49
- const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
49
+ const projectDir = resolveProjectRoot(process.env.CLAUDE_PROJECT_DIR || process.cwd());
50
50
  const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
51
51
  fs.mkdirSync(spoolDir, { recursive: true });
52
52
  fs.writeFileSync(path.join(spoolDir, '.bootstrap-error.json'), JSON.stringify({ ts: new Date().toISOString(), ...spec }, null, 2));
@@ -55,7 +55,7 @@ function writeBootstrapError(spec) {
55
55
 
56
56
  function clearBootstrapError() {
57
57
  try {
58
- const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
58
+ const projectDir = resolveProjectRoot(process.env.CLAUDE_PROJECT_DIR || process.cwd());
59
59
  fs.unlinkSync(path.join(projectDir, '.gm', 'exec-spool', '.bootstrap-error.json'));
60
60
  } catch (_) {}
61
61
  }
@@ -223,6 +223,39 @@ function gmToolsDir() {
223
223
  return primary;
224
224
  }
225
225
 
226
+ // Root a project-dir resolution at the git COMMON dir, not the raw cwd/
227
+ // CLAUDE_PROJECT_DIR -- a worktree (e.g. Workflow's isolation:'worktree'
228
+ // agents, each `git worktree add`-ing a fresh physical directory) shares the
229
+ // SAME underlying repo as its main checkout but has its own separate
230
+ // directory tree. Every cwd-derived project-dir computation in this file and
231
+ // in supervisor.js/cli.js must funnel through this so a worktree-spawned
232
+ // process resolves to the SAME .gm/exec-spool/ as its main-repo sibling --
233
+ // otherwise the single-instance supervisor lock can never see a sibling
234
+ // worktree's already-running watcher, and every worktree cold-boots its own
235
+ // independent watcher (each loading the full embed model + running its own
236
+ // cold reindex of what is, conceptually, the same project). Live-measured
237
+ // this session under real concurrent multi-agent load: this was the actual
238
+ // root cause of a user-flagged "memory grows to ~2GB then clears" churn
239
+ // pattern -- N worktrees of one repo each independently paying full
240
+ // cold-embed cost instead of sharing one already-warm watcher, and the
241
+ // resulting CPU contention pushed genuinely-busy processes past their
242
+ // heartbeat deadline into a restart cycle that produces the sawtooth memory
243
+ // pattern. Mirrors the existing browserRootDir() resolution already used for
244
+ // browser-session state in plugkit-wasm-wrapper.js (same fix, same reason,
245
+ // applied one layer up at the process-boot-dedup level).
246
+ function resolveProjectRoot(start) {
247
+ const resolved = path.resolve(start);
248
+ try {
249
+ const r = spawnSync('git', ['rev-parse', '--git-common-dir'], { cwd: resolved, encoding: 'utf-8', windowsHide: true, timeout: 1500 });
250
+ if (r.status === 0 && r.stdout && r.stdout.trim()) {
251
+ let commonDir = r.stdout.trim();
252
+ if (!path.isAbsolute(commonDir)) commonDir = path.resolve(resolved, commonDir);
253
+ if (/(^|[\\/])\.git$/.test(commonDir)) return path.dirname(commonDir);
254
+ }
255
+ } catch (_) {}
256
+ return resolved;
257
+ }
258
+
226
259
  function readVersionFile() {
227
260
  const p = path.join(wrapperDir, 'plugkit.version');
228
261
  if (!fs.existsSync(p)) throw new Error(`plugkit.version not found at ${p}`);
@@ -1254,6 +1287,7 @@ module.exports = {
1254
1287
  ensureNextStepWiring,
1255
1288
  ensureInstructionsBundle,
1256
1289
  gmToolsDir,
1290
+ resolveProjectRoot,
1257
1291
  getWasmPath,
1258
1292
  getBinaryPath,
1259
1293
  startSpoolDaemon,
package/gm-plugkit/cli.js CHANGED
@@ -5,7 +5,7 @@ const fs = require('fs');
5
5
  const os = require('os');
6
6
  const path = require('path');
7
7
  const cp = require('child_process');
8
- const { ensureReady, startSpoolDaemon, gmToolsDir, readVersionFile, ensureGmPlugkitVersionFresh, ensureSkillMdFresh, ensureWrapperFresh, isReady, getWasmPath, readPinnedGmPlugkitVersion, spawnPinnedBoot } = require('./bootstrap');
8
+ const { ensureReady, startSpoolDaemon, gmToolsDir, readVersionFile, ensureGmPlugkitVersionFresh, ensureSkillMdFresh, ensureWrapperFresh, isReady, getWasmPath, readPinnedGmPlugkitVersion, spawnPinnedBoot, resolveProjectRoot } = require('./bootstrap');
9
9
 
10
10
  function getWasmPathSafe() {
11
11
  try { return getWasmPath(); } catch (_) { return null; }
@@ -201,7 +201,7 @@ function killStaleWatchers() {
201
201
  }
202
202
 
203
203
  function spoolDir() {
204
- const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
204
+ const projectDir = resolveProjectRoot(process.env.CLAUDE_PROJECT_DIR || process.cwd());
205
205
  return path.join(projectDir, '.gm', 'exec-spool');
206
206
  }
207
207
 
@@ -242,6 +242,29 @@ function writeCliError(phase, err) {
242
242
  } catch (_) {}
243
243
  }
244
244
 
245
+ // gm-runner is a genuine, sha256-verified drop-in replacement for this
246
+ // entire bun/node boot path -- when it's installed, delegate to it
247
+ // immediately and exit, before any bun/node-specific bootstrap logic runs.
248
+ // This is the actual code-level enforcement of what was previously only a
249
+ // documented convention (SKILL.md telling an LLM agent to manually prefer
250
+ // gm-runner) -- with this in place bun/node are only ever exercised on a
251
+ // platform gm-runner-bin has no published binary for, or during the
252
+ // one-time install before gm-runner itself lands on disk.
253
+ function tryDelegateToGmRunner(args) {
254
+ if (process.env.GM_PLUGKIT_NO_RUNNER_DELEGATE === '1') return false;
255
+ const exeName = process.platform === 'win32' ? 'gm-runner.exe' : 'gm-runner';
256
+ const runnerPath = path.join(gmToolsDir(), exeName);
257
+ if (!fs.existsSync(runnerPath)) return false;
258
+ try {
259
+ const result = cp.spawnSync(runnerPath, args, { stdio: 'inherit', windowsHide: true });
260
+ if (result.error) return false; // exec genuinely failed to start -- fall through to bun/node path
261
+ process.exit(typeof result.status === 'number' ? result.status : 0);
262
+ } catch (_) {
263
+ return false;
264
+ }
265
+ return true;
266
+ }
267
+
245
268
  (async () => {
246
269
  const args = process.argv.slice(2);
247
270
 
@@ -250,6 +273,8 @@ function writeCliError(phase, err) {
250
273
  process.exit(0);
251
274
  }
252
275
 
276
+ tryDelegateToGmRunner(args);
277
+
253
278
  if (args.includes('--kill-stale-watchers')) {
254
279
  process.exit(killStaleWatchers());
255
280
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1971",
3
+ "version": "2.0.1973",
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": {
@@ -4732,9 +4732,22 @@ async function runSpoolWatcher(instance, spoolDir) {
4732
4732
  } catch (_) {}
4733
4733
  }
4734
4734
  _writeStatusBusy = (ms) => { try { writeStatus(ms); } catch (_) {} };
4735
- setInterval(() => writeStatus(), 5000);
4735
+ // Every regular heartbeat tick also carries a modest rolling busy_until
4736
+ // (45s, comfortably above the supervisor's 30s STATUS_STALE_MS) -- not
4737
+ // because this tick itself is doing long work, but because under heavy
4738
+ // real multi-process contention (many concurrent gm-driven watchers on
4739
+ // one machine, live-observed: 13 concurrent agent watchers each competing
4740
+ // for CPU, all doing real embed/codesearch work) the OS scheduler can
4741
+ // starve THIS process of any time slice for well over 30s between ticks
4742
+ // even though the process is genuinely alive and will resume once
4743
+ // scheduled -- no in-process yield/setImmediate can fix a starvation that
4744
+ // happens entirely outside this process's own event loop. A missed tick
4745
+ // or two under real contention should not itself be a kill signal; a
4746
+ // GENUINELY dead process still gets caught because nothing here ever
4747
+ // refreshes busy_until once the process actually exits.
4748
+ setInterval(() => writeStatus(45000), 5000);
4736
4749
  setInterval(() => { try { scanStalledTurns(); } catch (_) {} }, 30000);
4737
- writeStatus();
4750
+ writeStatus(45000);
4738
4751
 
4739
4752
  setTimeout(async () => {
4740
4753
  try {
@@ -6,7 +6,7 @@ const path = require('path');
6
6
  const os = require('os');
7
7
  const crypto = require('crypto');
8
8
  const { spawn, spawnSync } = require('child_process');
9
- const { gmToolsDir } = require('./bootstrap');
9
+ const { gmToolsDir, resolveProjectRoot } = require('./bootstrap');
10
10
  const { logEvent: _sharedLogEvent, GM_LOG_ROOT: _sharedGmLogRoot } = require('./gm-log');
11
11
  const { pidCommandLineForKillGuard: _sharedPidCommandLine } = require('./gm-process');
12
12
 
@@ -17,7 +17,11 @@ function wrapperSha12OnDisk() {
17
17
  } catch (_) { return null; }
18
18
  }
19
19
 
20
- const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
20
+ // Root the spool/lock state at the git common dir (see resolveProjectRoot's
21
+ // own doc comment in bootstrap.js for the full worktree-dedup rationale) so
22
+ // a worktree-spawned supervisor shares its lock/spoolDir with the main-repo
23
+ // checkout instead of always cold-booting its own independent watcher.
24
+ const projectDir = resolveProjectRoot(process.env.CLAUDE_PROJECT_DIR || process.cwd());
21
25
  const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
22
26
  fs.mkdirSync(spoolDir, { recursive: true });
23
27
 
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1971",
3
+ "version": "2.0.1973",
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.1971",
3
+ "version": "2.0.1973",
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",