gm-codex 2.0.967 → 2.0.969
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/.codex-plugin/plugin.json +1 -1
- package/bin/bootstrap.js +48 -17
- package/bin/plugkit.js +25 -1
- package/gm.json +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/gm/SKILL.md +8 -4
- package/skills/gm-complete/SKILL.md +1 -1
- package/skills/gm-execute/SKILL.md +3 -3
- package/skills/planning/SKILL.md +1 -1
package/bin/bootstrap.js
CHANGED
|
@@ -27,7 +27,7 @@ function log(msg) {
|
|
|
27
27
|
function probeBinaryVersion(binPath) {
|
|
28
28
|
try {
|
|
29
29
|
const { spawnSync } = require('child_process');
|
|
30
|
-
const r = spawnSync(binPath, ['--version'], { timeout: 3000, encoding: 'utf8' });
|
|
30
|
+
const r = spawnSync(binPath, ['--version'], { timeout: 3000, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
31
31
|
if (r.error) return null;
|
|
32
32
|
const text = `${r.stdout || ''} ${r.stderr || ''}`.trim();
|
|
33
33
|
const m = text.match(/(\d+\.\d+\.\d+)/);
|
|
@@ -183,9 +183,7 @@ function acquireLock(lockPath) {
|
|
|
183
183
|
continue;
|
|
184
184
|
}
|
|
185
185
|
if (Date.now() - start > ATTEMPT_TIMEOUT_MS) throw new Error(`lock wait timeout: ${lockPath}`);
|
|
186
|
-
const
|
|
187
|
-
const deadline = Date.now() + waitMs;
|
|
188
|
-
while (Date.now() < deadline) {}
|
|
186
|
+
try { const { spawnSync } = require('child_process'); spawnSync(process.execPath, ['-e', 'setTimeout(()=>{}, 2000)'], { timeout: 2500, killSignal: 'SIGKILL', stdio: 'ignore', windowsHide: true }); } catch (_) {}
|
|
189
187
|
}
|
|
190
188
|
}
|
|
191
189
|
}
|
|
@@ -382,8 +380,7 @@ async function bootstrap(opts) {
|
|
|
382
380
|
if (!opts.silent) log(`cache heal (sha match): ${finalPath}${actualVersion ? ` (matches pin v${version})` : ''}`);
|
|
383
381
|
proactiveKillForNewInstall(version, finalPath);
|
|
384
382
|
pruneOldVersions(root, version, readRtkVersion(wrapperDir));
|
|
385
|
-
|
|
386
|
-
catch (err) { log(`rtk fetch skipped: ${err.message}`); }
|
|
383
|
+
spawnDetachedRtkFetch(wrapperDir);
|
|
387
384
|
return finalPath;
|
|
388
385
|
}
|
|
389
386
|
}
|
|
@@ -400,8 +397,7 @@ async function bootstrap(opts) {
|
|
|
400
397
|
log(`cache heal (sha match) under lock: ${finalPath}`);
|
|
401
398
|
proactiveKillForNewInstall(version, finalPath);
|
|
402
399
|
pruneOldVersions(root, version, readRtkVersion(wrapperDir));
|
|
403
|
-
|
|
404
|
-
catch (err) { log(`rtk fetch skipped: ${err.message}`); }
|
|
400
|
+
spawnDetachedRtkFetch(wrapperDir);
|
|
405
401
|
return finalPath;
|
|
406
402
|
}
|
|
407
403
|
|
|
@@ -461,14 +457,28 @@ async function bootstrap(opts) {
|
|
|
461
457
|
obsEvent('bootstrap', 'install.done', { path: finalPath, version, kind: 'plugkit' });
|
|
462
458
|
proactiveKillForNewInstall(version, finalPath);
|
|
463
459
|
pruneOldVersions(root, version, readRtkVersion(wrapperDir));
|
|
464
|
-
|
|
465
|
-
catch (err) { log(`rtk fetch skipped: ${err.message}`); }
|
|
460
|
+
spawnDetachedRtkFetch(wrapperDir);
|
|
466
461
|
return finalPath;
|
|
467
462
|
} finally {
|
|
468
463
|
releaseLock(lockPath);
|
|
469
464
|
}
|
|
470
465
|
}
|
|
471
466
|
|
|
467
|
+
function spawnDetachedRtkFetch(wrapperDir) {
|
|
468
|
+
try {
|
|
469
|
+
const { spawn } = require('child_process');
|
|
470
|
+
const child = spawn(process.execPath, [__filename, '--rtk-only', '--wrapper-dir', wrapperDir], {
|
|
471
|
+
detached: true,
|
|
472
|
+
stdio: 'ignore',
|
|
473
|
+
windowsHide: true,
|
|
474
|
+
});
|
|
475
|
+
child.unref();
|
|
476
|
+
obsEvent('bootstrap', 'rtk.detached.spawned', { pid: child.pid, wrapperDir });
|
|
477
|
+
} catch (err) {
|
|
478
|
+
log(`rtk detach spawn failed: ${err.message}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
472
482
|
function rtkCacheDir(root, wrapperDir, plugkitVerDir) {
|
|
473
483
|
const rtkVer = readRtkVersion(wrapperDir);
|
|
474
484
|
if (!rtkVer) return plugkitVerDir;
|
|
@@ -613,7 +623,7 @@ function listRunningPlugkitImagePaths() {
|
|
|
613
623
|
if (os.platform() === 'win32') {
|
|
614
624
|
let parsed = null;
|
|
615
625
|
try {
|
|
616
|
-
const p = spawnSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', "Get-Process plugkit* -ErrorAction SilentlyContinue | Select-Object Id,Path | ConvertTo-Json -Compress"], { windowsHide: true, encoding: 'utf8', timeout: 5000, killSignal: 'SIGKILL' });
|
|
626
|
+
const p = spawnSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', "Get-Process plugkit* -ErrorAction SilentlyContinue | Select-Object Id,Path | ConvertTo-Json -Compress"], { windowsHide: true, encoding: 'utf8', timeout: 5000, killSignal: 'SIGKILL', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
617
627
|
const text = ((p && p.stdout) || '').trim();
|
|
618
628
|
if (text) {
|
|
619
629
|
const j = JSON.parse(text);
|
|
@@ -628,7 +638,7 @@ function listRunningPlugkitImagePaths() {
|
|
|
628
638
|
out.push({ pid, path: (item.Path || '').trim() });
|
|
629
639
|
}
|
|
630
640
|
} else {
|
|
631
|
-
const r = spawnSync('tasklist', ['/FI', 'IMAGENAME eq plugkit*', '/FO', 'CSV', '/NH'], { windowsHide: true, encoding: 'utf8', timeout: 5000, killSignal: 'SIGKILL' });
|
|
641
|
+
const r = spawnSync('tasklist', ['/FI', 'IMAGENAME eq plugkit*', '/FO', 'CSV', '/NH'], { windowsHide: true, encoding: 'utf8', timeout: 5000, killSignal: 'SIGKILL', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
632
642
|
const text = (r && r.stdout) || '';
|
|
633
643
|
const seen = new Set();
|
|
634
644
|
for (const line of text.split(/\r?\n/)) {
|
|
@@ -654,7 +664,7 @@ function listRunningPlugkitImagePaths() {
|
|
|
654
664
|
out.push({ pid, path: imagePath });
|
|
655
665
|
}
|
|
656
666
|
} else {
|
|
657
|
-
const r = spawnSync('ps', ['-axo', 'pid=,comm='], { encoding: 'utf8' });
|
|
667
|
+
const r = spawnSync('ps', ['-axo', 'pid=,comm='], { encoding: 'utf8', timeout: 5000, killSignal: 'SIGKILL', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
658
668
|
const text = (r && r.stdout) || '';
|
|
659
669
|
for (const line of text.split(/\r?\n/)) {
|
|
660
670
|
const m = line.match(/^\s*(\d+)\s+(.+?)\s*$/);
|
|
@@ -663,7 +673,7 @@ function listRunningPlugkitImagePaths() {
|
|
|
663
673
|
const pid = parseInt(m[1], 10);
|
|
664
674
|
let imagePath = '';
|
|
665
675
|
try {
|
|
666
|
-
const p = spawnSync('ps', ['-p', String(pid), '-o', 'command='], { encoding: 'utf8' });
|
|
676
|
+
const p = spawnSync('ps', ['-p', String(pid), '-o', 'command='], { encoding: 'utf8', timeout: 3000, killSignal: 'SIGKILL', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
667
677
|
imagePath = ((p && p.stdout) || '').trim().split(/\s+/)[0] || '';
|
|
668
678
|
} catch (_) {}
|
|
669
679
|
out.push({ pid, path: imagePath });
|
|
@@ -732,7 +742,28 @@ function killStaleDaemonIfVersionChanged(wrapperDir) {
|
|
|
732
742
|
module.exports = { bootstrap, resolveCachedBinary, resolveCachedRtk, platformKey, binaryName, rtkBinaryName, cacheRoot, obsEvent, killRunningDaemons, killStaleDaemonIfVersionChanged, killSpoolWatcherInCwd, proactiveKillForNewInstall };
|
|
733
743
|
|
|
734
744
|
if (require.main === module) {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
745
|
+
const argv = process.argv.slice(2);
|
|
746
|
+
if (argv.includes('--rtk-only')) {
|
|
747
|
+
const wIdx = argv.indexOf('--wrapper-dir');
|
|
748
|
+
const wrapperDir = wIdx >= 0 ? argv[wIdx + 1] : __dirname;
|
|
749
|
+
(async () => {
|
|
750
|
+
try {
|
|
751
|
+
const version = readVersionFile(wrapperDir);
|
|
752
|
+
let root = cacheRoot();
|
|
753
|
+
try { ensureDir(root); }
|
|
754
|
+
catch (_) { root = fallbackCacheRoot(); ensureDir(root); }
|
|
755
|
+
const verDir = path.join(root, `v${version}`);
|
|
756
|
+
ensureDir(verDir);
|
|
757
|
+
await bootstrapRtk(verDir, version, wrapperDir, true, root);
|
|
758
|
+
process.exit(0);
|
|
759
|
+
} catch (err) {
|
|
760
|
+
obsEvent('bootstrap', 'rtk.detached.failed', { err: String(err.message || err) });
|
|
761
|
+
process.exit(1);
|
|
762
|
+
}
|
|
763
|
+
})();
|
|
764
|
+
} else {
|
|
765
|
+
bootstrap({ silent: false })
|
|
766
|
+
.then(p => { process.stdout.write(p + '\n'); process.exit(0); })
|
|
767
|
+
.catch(err => { log(`FATAL: ${err.message}`); obsEvent('bootstrap', 'fatal', { err: String(err.message || err) }); process.exit(1); });
|
|
768
|
+
}
|
|
738
769
|
}
|
package/bin/plugkit.js
CHANGED
|
@@ -7,6 +7,30 @@ const { bootstrap, resolveCachedBinary, resolveCachedRtk, obsEvent, killStaleDae
|
|
|
7
7
|
|
|
8
8
|
const dir = __dirname;
|
|
9
9
|
|
|
10
|
+
function readPinnedVersion() {
|
|
11
|
+
try { return fs.readFileSync(path.join(dir, 'plugkit.version'), 'utf8').trim(); } catch (_) { return null; }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function probeCachedVersion(binPath) {
|
|
15
|
+
try {
|
|
16
|
+
const r = spawnSync(binPath, ['--version'], { timeout: 3000, encoding: 'utf8', windowsHide: true });
|
|
17
|
+
if (r.error) return null;
|
|
18
|
+
const text = `${r.stdout || ''} ${r.stderr || ''}`.trim();
|
|
19
|
+
const m = text.match(/(\d+\.\d+\.\d+)/);
|
|
20
|
+
return m ? m[1] : null;
|
|
21
|
+
} catch (_) { return null; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function resolveBinaryWithPinCheck() {
|
|
25
|
+
const cached = resolveCachedBinary({ wrapperDir: dir });
|
|
26
|
+
if (!cached) return null;
|
|
27
|
+
const pin = readPinnedVersion();
|
|
28
|
+
if (!pin) return cached;
|
|
29
|
+
const got = probeCachedVersion(cached);
|
|
30
|
+
if (got && got === pin) return cached;
|
|
31
|
+
try { return await bootstrap({ wrapperDir: dir, silent: true }); } catch (_) { return cached; }
|
|
32
|
+
}
|
|
33
|
+
|
|
10
34
|
function envWithRtkOnPath() {
|
|
11
35
|
const rtkPath = resolveCachedRtk({ wrapperDir: dir });
|
|
12
36
|
if (!rtkPath) return process.env;
|
|
@@ -49,7 +73,7 @@ async function main() {
|
|
|
49
73
|
// below — fall through to the spawn path so the actual handler runs.
|
|
50
74
|
if (!bin) process.exit(0);
|
|
51
75
|
} else if (isHook) {
|
|
52
|
-
bin =
|
|
76
|
+
bin = (await resolveBinaryWithPinCheck()) || legacyFallback();
|
|
53
77
|
if (!bin) {
|
|
54
78
|
process.stderr.write(`[plugkit] hook ${hookSubcmd} skipped: binary not yet installed. Bootstrap will run on session-start.\n`);
|
|
55
79
|
obsEvent('plugkit_wrapper', 'hook_skip_uncached', { argv: args.slice(0, 4), dur_ms: Date.now() - startedAt });
|
package/gm.json
CHANGED
package/package.json
CHANGED
package/plugin.json
CHANGED
package/skills/gm/SKILL.md
CHANGED
|
@@ -50,13 +50,17 @@ One subagent per fact, fan out in parallel — batching dilutes the signal. The
|
|
|
50
50
|
|
|
51
51
|
## Execution order
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
The spool is the universal dispatch surface. Write a file to `.gm/exec-spool/in/<lang-or-verb>/<N>.<ext>`; the watcher executes and streams `out/<N>.out` + `out/<N>.err` + `out/<N>.json`. Languages: nodejs, python, bash, typescript, go, rust, c, cpp, java, deno. Verbs: codesearch, recall, memorize, wait, sleep, status, close, browser, runner, type, kill-port, forget, feedback, learn-status, learn-debug, learn-build, discipline, pause, health.
|
|
54
|
+
|
|
55
|
+
Order of cheapness:
|
|
56
|
+
|
|
57
|
+
1. Recall — `in/recall/<N>.txt` with the query
|
|
58
|
+
2. Codebase search — `in/codesearch/<N>.txt` with two-word query, 90% of lookups
|
|
59
|
+
3. Code execution — `in/<lang>/<N>.<ext>`
|
|
56
60
|
4. Web (`WebFetch`, `WebSearch`) — env facts not in codebase
|
|
57
61
|
5. User — last resort
|
|
58
62
|
|
|
59
|
-
Bash accepts ONLY git commands
|
|
63
|
+
Bash accepts ONLY git commands directly (`git status`, `git commit`, `git push`, `git log`, `gh ...`). Everything else — code AND every utility verb — dispatches via the spool. Never `Bash(node/npm/npx/bun)`, never `Bash(exec:<anything>)`. `git push` triggers auto CI watch via Stop hook.
|
|
60
64
|
|
|
61
65
|
Skill chain: `planning` → `gm-execute` → `gm-emit` → `gm-complete` → `update-docs`.
|
|
62
66
|
|
|
@@ -54,7 +54,7 @@ Long-running probes split into navigate-call → `exec:wait N` → probe-call to
|
|
|
54
54
|
|
|
55
55
|
Exempt only when: change is server-only with zero browser-facing surface, OR the repository has no browser surface at all (pure CLI / library). Exemption requires explicit tag in the response: `BROWSER EXEMPT: <reason — must reference diff paths showing zero browser-facing surface>`. Default posture is NOT exempt — burden is on the agent to prove exemption with diff evidence.
|
|
56
56
|
|
|
57
|
-
Pre-flight: run `git diff --name-only origin/main..HEAD` and
|
|
57
|
+
Pre-flight: run `git diff --name-only origin/main..HEAD` directly via Bash, then dispatch a nodejs spool file that reads the diff list and filters lines matching `client/|docs/|\.html$|\.glsl$|\.frag$|\.vert$`. Any hit AND no `exec:browser` block in this session → mandatory regression to `gm-execute`.
|
|
58
58
|
|
|
59
59
|
## Integration test gate
|
|
60
60
|
|
|
@@ -40,13 +40,13 @@ Spend on `.prd` items in descending order of consequence-if-wrong × distance-fr
|
|
|
40
40
|
|
|
41
41
|
## Code execution
|
|
42
42
|
|
|
43
|
-
Code
|
|
43
|
+
Code AND utility verbs both run through the file-spool. Write a file to `.gm/exec-spool/in/<lang-or-verb>/<N>.<ext>` — language stems (`in/nodejs/42.js`, `in/python/43.py`, `in/bash/44.sh`, plus typescript, go, rust, c, cpp, java, deno) or verb stems (`in/codesearch/45.txt`, `in/recall/46.txt`, `in/memorize/47.md`, plus wait, sleep, status, close, browser, runner, type, kill-port, forget, feedback, learn-status, learn-debug, learn-build, discipline, pause, health). The spool watcher executes and streams stdout to `out/<N>.out`, stderr to `out/<N>.err`, then writes `out/<N>.json` metadata sidecar at completion (taskId, lang, ok, exitCode, durationMs, timedOut, startedAt, endedAt). Both streams return as systemMessage with `--- stdout ---` / `--- stderr ---` separators. File I/O via a nodejs spool file + `require('fs')`. Only `git` and `gh` run directly in Bash. Never `Bash(node/npm/npx/bun)`, never `Bash(exec:<anything>)`.
|
|
44
44
|
|
|
45
|
-
Pack runs: `Promise.allSettled`, each idea own try/catch, under 12s per call. Runner: `
|
|
45
|
+
Pack runs: `Promise.allSettled`, each idea own try/catch, under 12s per call. Runner: write `in/runner/<N>.txt` with body `start` | `stop` | `status`.
|
|
46
46
|
|
|
47
47
|
Every exec daemonizes. The hook tails the task logfile up to 30s wall-clock and returns whatever is there — short tasks complete inside the window and look synchronous; long tasks return a task_id with partial output. Continue with `exec:tail` (drain, bounded), `exec:watch` (resume blocking until match or timeout), or `exec:close` (terminate). Never re-spawn a long task to check on it — that orphans the first one. `exec:wait` is a pure timer; `exec:sleep` blocks on a specific task's output; `exec:watch` is the match-or-timeout primitive. Every execution-platform RPC returns the live list of running tasks for this session — close stragglers via `exec:close\n<id>` so the list stays scannable. Session-end (clear/logout/prompt_input_exit) kills the session's tasks; compaction/handoff preserves them.
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Every utility verb dispatches via `in/<verb>/<N>.txt`; the body of the file is the verb's argument. There is no inline form and no Bash-prefix form — both are denied by the hook.
|
|
50
50
|
|
|
51
51
|
## Codebase search
|
|
52
52
|
|
package/skills/planning/SKILL.md
CHANGED
|
@@ -103,7 +103,7 @@ The 200 lines are a *budget* for maximum surface coverage, not a target. Subsyst
|
|
|
103
103
|
|
|
104
104
|
## Execution norms encoded in the plan
|
|
105
105
|
|
|
106
|
-
Code execution
|
|
106
|
+
Code execution AND utility verbs both write to `.gm/exec-spool/in/<lang-or-verb>/<N>.<ext>`. Languages live under `in/<lang>/` (nodejs, python, bash, typescript, go, rust, c, cpp, java, deno); verbs live under `in/<verb>/` (codesearch, recall, memorize, wait, sleep, status, close, browser, runner, type, kill-port, forget, feedback, learn-status, learn-debug, learn-build, discipline, pause, health). The spool watcher runs the file and streams to `out/<N>.out` (stdout) + `out/<N>.err` (stderr) line-by-line, then writes `out/<N>.json` metadata (exitCode, durationMs, timedOut, startedAt, endedAt) at completion. Both streams return as systemMessage with `--- stdout ---` / `--- stderr ---` separators. `in/` and `out/` are wiped at session start and at real-exit session end. Only `git` (and `gh`) run directly via Bash; never `Bash(node/npm/npx/bun)`, never `Bash(exec:<anything>)`. Spool paths in nodejs files are platform-literal — use `os.tmpdir()` and `path.join`. The spool enforces per-task timeouts; on timeout, partial output is preserved and the watcher emits `[exec timed out after Nms; partial output above]`.
|
|
107
107
|
|
|
108
108
|
`exec:codesearch` only — Grep/Glob/Find/Explore are hook-blocked. Start two words, change/add one per pass, minimum four attempts before concluding absent.
|
|
109
109
|
|