gm-skill 2.0.2013 → 2.0.2015

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
@@ -106,7 +106,7 @@ A task that reduces to read/investigate/report, or a change confined to files th
106
106
 
107
107
  **Done is plugkit's pronouncement, never the agent's claim**: COMPLETE only when `transition to=COMPLETE` returns COMPLETE phase, on-disk state reflects it. COMPLETE gate (gates.rs) = sole arbiter -- refuses on PRD-open, mutables-unresolved, dirty worktree, missing residual-scan marker. Agent drives into gate-allowing state, dispatches, reads response; every alternative is narration.
108
108
 
109
- **Every residual is triaged this turn, never named-and-deferred**: every `git status --porcelain` entry at residual-scan/COMPLETE-attempt triaged now -- (a) commit (real work), (b) managed-gitignore-block (transient runtime emission, e.g. `.gm/witness/`, `.gm/exec-spool/.*-stale.json`), (c) revert (junk). "Pre-existing" = the (a)/(c) outcome, never a stop excuse. `blockedBy: external` admissible only when triage needs unreachable authority; local-tree files always agent-authoritative. Disciplines + new memorize-fire `mem-*.json` committed. Managed block (`# >>> plugkit managed` markers) carries only zero-future-read-value runtime artifacts.
109
+ **Every residual is triaged this turn, never named-and-deferred**: every `git status --porcelain` entry at residual-scan/COMPLETE-attempt triaged now -- (a) commit (real work), (b) managed-gitignore-block (transient runtime emission, e.g. `.gm/witness/`, `.gm/exec-spool/.*-stale.json`), (c) revert (junk). "Pre-existing" = the (a)/(c) outcome, never a stop excuse. Everything is fixable: when triage points outside the local tree, the row's job is the concrete reach action that builds past it (open the PR, script the credential path, replace/drive-directly the broken tool), never a `blockedBy: external` resting state -- `blockedBy` only transiently carries that reach path forward while it is built, and local-tree files are always agent-authoritative to fix now. Disciplines + new memorize-fire `mem-*.json` committed. Managed block (`# >>> plugkit managed` markers) carries only zero-future-read-value runtime artifacts.
110
110
 
111
111
  **"Every possible" is the load-bearing test, applied to every noun the request names** -- PRD density at PLAN, second-pass corner-case transform: the recall store (`recall: PLAN-phase PRD density rule`).
112
112
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.2013",
3
+ "version": "2.0.2015",
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": {
@@ -2564,6 +2564,9 @@ function makeHostFunctions(instanceRef) {
2564
2564
  const __topNM = modeOpts.match(/topN=(\d+)/);
2565
2565
  const sampleIntervalUs = __intervalM && parseInt(__intervalM[1], 10) > 0 ? parseInt(__intervalM[1], 10) : 100;
2566
2566
  const profileTopNBrowser = __topNM && parseInt(__topNM[1], 10) > 0 ? parseInt(__topNM[1], 10) : 20;
2567
+ const rawUserScript = (screenshotPath || domSelector)
2568
+ ? null
2569
+ : (modeMatch ? modeMatch[3] : evalBody);
2567
2570
  // Real, pre-existing bug fixed here: this block previously called `page.evaluateOnNewDocument`,
2568
2571
  // which is a PUPPETEER method name -- playwriter's `page` object is a real Playwright Page,
2569
2572
  // whose equivalent is `page.addInitScript`. evaluateOnNewDocument does not exist on a
@@ -2772,13 +2775,51 @@ function makeHostFunctions(instanceRef) {
2772
2775
  // A temp .js file has no such length limit -- this sidesteps the Bun bug class entirely rather
2773
2776
  // than working around it script-by-script.
2774
2777
  const scriptFile = path.join(os.tmpdir(), `gm-browser-eval-${process.pid}-${execProfileSeq++}.js`);
2775
- try {
2776
- fs.writeFileSync(scriptFile, evalBody, 'utf-8');
2777
- r = runBrowserRunner(pw, ['-s', pwSessionId, '--timeout', String(timeoutMs), '-f', scriptFile], outerTimeoutMs, cwd, sessionId);
2778
- } finally {
2779
- try { fs.unlinkSync(scriptFile); } catch (_) {}
2780
- clearInflight(sessionId);
2781
- stampBrowserLastUse(cwd, sessionId);
2778
+ // The playwriter relay's attach+eval hop crashes with a UV_HANDLE_CLOSING native
2779
+ // assertion on Windows. When the session already has a live Chrome CDP endpoint
2780
+ // (chromium launched with --remote-debugging-port, recorded in browser-ports.json),
2781
+ // drive that endpoint directly via the proven wrapper/cdp-eval.js helper instead --
2782
+ // it connects over the DevTools websocket and runs the raw user script through
2783
+ // Runtime.evaluate, never spawning the crashing relay. This is gated strictly on a
2784
+ // live CDP port AND a self-contained raw user script (screenshot=/dom= bodies build
2785
+ // page.screenshot/page.evaluate calls that need the Playwright page object cdp-eval.js
2786
+ // does not provide, so rawUserScript is null for those and the playwriter path runs).
2787
+ let usedCdp = false;
2788
+ if (rawUserScript != null) {
2789
+ let cdpPort = null;
2790
+ let cdpWs = null;
2791
+ try {
2792
+ const ent = readJsonFile(browserPortsFile(cwd), {})[sessionId];
2793
+ if (ent && Number.isFinite(ent.port)) { cdpPort = ent.port; cdpWs = ent.wsEndpoint || null; }
2794
+ } catch (_) {}
2795
+ const cdpLive = cdpPort != null && !!fetchJsonSync(`http://127.0.0.1:${cdpPort}/json/version`, 1000);
2796
+ if (cdpLive) {
2797
+ const cdpEvalScript = path.join(os.tmpdir(), `gm-cdp-eval-${process.pid}-${execProfileSeq++}.js`);
2798
+ try {
2799
+ fs.writeFileSync(cdpEvalScript, rawUserScript, 'utf-8');
2800
+ const cfg = JSON.stringify({ port: cdpPort, wsEndpoint: cdpWs, startUrl, scriptFile: cdpEvalScript, resultFile, timeoutMs });
2801
+ r = spawnSync(process.execPath, [path.join(__dirname, 'wrapper', 'cdp-eval.js'), cfg], {
2802
+ encoding: 'utf-8',
2803
+ timeout: outerTimeoutMs,
2804
+ windowsHide: true,
2805
+ });
2806
+ usedCdp = true;
2807
+ } finally {
2808
+ try { fs.unlinkSync(cdpEvalScript); } catch (_) {}
2809
+ clearInflight(sessionId);
2810
+ stampBrowserLastUse(cwd, sessionId);
2811
+ }
2812
+ }
2813
+ }
2814
+ if (!usedCdp) {
2815
+ try {
2816
+ fs.writeFileSync(scriptFile, evalBody, 'utf-8');
2817
+ r = runBrowserRunner(pw, ['-s', pwSessionId, '--timeout', String(timeoutMs), '-f', scriptFile], outerTimeoutMs, cwd, sessionId);
2818
+ } finally {
2819
+ try { fs.unlinkSync(scriptFile); } catch (_) {}
2820
+ clearInflight(sessionId);
2821
+ stampBrowserLastUse(cwd, sessionId);
2822
+ }
2782
2823
  }
2783
2824
  const ok = r.status === 0;
2784
2825
  if (!ok && r.status === null) {
@@ -0,0 +1,123 @@
1
+ import fs from 'fs';
2
+ import http from 'http';
3
+
4
+ function httpJson(url, timeoutMs) {
5
+ return new Promise((resolve) => {
6
+ const req = http.get(url, { timeout: timeoutMs }, (res) => {
7
+ let body = '';
8
+ res.on('data', (c) => { body += c; });
9
+ res.on('end', () => { try { resolve(JSON.parse(body)); } catch (_) { resolve(null); } });
10
+ });
11
+ req.on('error', () => resolve(null));
12
+ req.on('timeout', () => { req.destroy(); resolve(null); });
13
+ });
14
+ }
15
+
16
+ async function pickPageTarget(port, startUrl, timeoutMs) {
17
+ const deadline = Date.now() + timeoutMs;
18
+ while (Date.now() < deadline) {
19
+ const list = await httpJson(`http://127.0.0.1:${port}/json/list`, 2000);
20
+ if (Array.isArray(list)) {
21
+ const page = list.find((t) => t.type === 'page' && t.webSocketDebuggerUrl);
22
+ if (page) return page;
23
+ }
24
+ if (startUrl) {
25
+ const created = await httpJson(`http://127.0.0.1:${port}/json/new?${encodeURIComponent(startUrl)}`, 3000);
26
+ if (created && created.webSocketDebuggerUrl) return created;
27
+ }
28
+ await new Promise((r) => setTimeout(r, 250));
29
+ }
30
+ return null;
31
+ }
32
+
33
+ function cdpSession(wsUrl, timeoutMs) {
34
+ return new Promise((resolve, reject) => {
35
+ const ws = new WebSocket(wsUrl);
36
+ let nextId = 1;
37
+ const pending = new Map();
38
+ const timer = setTimeout(() => { try { ws.close(); } catch (_) {} reject(new Error('cdp timeout')); }, timeoutMs);
39
+ ws.addEventListener('open', () => {
40
+ resolve({
41
+ send(method, params) {
42
+ const id = nextId++;
43
+ return new Promise((res, rej) => {
44
+ pending.set(id, { res, rej });
45
+ ws.send(JSON.stringify({ id, method, params: params || {} }));
46
+ });
47
+ },
48
+ close() { clearTimeout(timer); try { ws.close(); } catch (_) {} },
49
+ });
50
+ });
51
+ ws.addEventListener('message', (ev) => {
52
+ let msg;
53
+ try { msg = JSON.parse(ev.data); } catch (_) { return; }
54
+ if (msg.id && pending.has(msg.id)) {
55
+ const { res, rej } = pending.get(msg.id);
56
+ pending.delete(msg.id);
57
+ if (msg.error) rej(new Error(msg.error.message || 'cdp error'));
58
+ else res(msg.result);
59
+ }
60
+ });
61
+ ws.addEventListener('error', () => { clearTimeout(timer); reject(new Error('cdp websocket error')); });
62
+ });
63
+ }
64
+
65
+ // Direct CDP evaluation, replacing the playwriter relay attach+eval that crashes
66
+ // with UV_HANDLE_CLOSING on Windows. Everything up to obtaining Chrome's CDP
67
+ // endpoint is already done by the wrapper (it launches Chrome with
68
+ // --remote-debugging-port and polls /json/version); this drives that endpoint
69
+ // directly over the DevTools websocket, so the crashing relay process is never
70
+ // spawned. Reads {port, startUrl, scriptFile, resultFile, timeoutMs} from argv[2]
71
+ // as JSON, runs the script via Runtime.evaluate (awaitPromise, returnByValue),
72
+ // and writes the returned value to resultFile -- the same result channel the
73
+ // playwriter path used.
74
+ async function main() {
75
+ const cfg = JSON.parse(process.argv[2]);
76
+ const { port, startUrl, scriptFile, resultFile, timeoutMs } = cfg;
77
+ const script = fs.readFileSync(scriptFile, 'utf-8');
78
+ const target = await pickPageTarget(port, startUrl, Math.min(timeoutMs, 30000));
79
+ if (!target) {
80
+ fs.writeFileSync(resultFile, JSON.stringify({ __cdpError: 'no page target on CDP endpoint' }));
81
+ process.stderr.write('cdp-eval: no page target\n');
82
+ process.exit(1);
83
+ }
84
+ const sess = await cdpSession(target.webSocketDebuggerUrl, timeoutMs);
85
+ try {
86
+ await sess.send('Runtime.enable', {});
87
+ if (startUrl) {
88
+ await sess.send('Page.enable', {});
89
+ await sess.send('Page.navigate', { url: startUrl });
90
+ await new Promise((r) => setTimeout(r, 1200));
91
+ }
92
+ // The wrapper's eval body is a function body ending in `return __RET;`.
93
+ // Wrap it in an async IIFE so Runtime.evaluate resolves the returned value.
94
+ const wrapped = `(async () => { ${script} })()`;
95
+ const res = await sess.send('Runtime.evaluate', {
96
+ expression: wrapped,
97
+ awaitPromise: true,
98
+ returnByValue: true,
99
+ userGesture: true,
100
+ timeout: timeoutMs,
101
+ });
102
+ if (res.exceptionDetails) {
103
+ const msg = res.exceptionDetails.exception && res.exceptionDetails.exception.description
104
+ ? res.exceptionDetails.exception.description
105
+ : (res.exceptionDetails.text || 'evaluate exception');
106
+ fs.writeFileSync(resultFile, JSON.stringify({ __cdpError: msg }));
107
+ process.stderr.write(`cdp-eval: exception ${msg}\n`);
108
+ sess.close();
109
+ process.exit(1);
110
+ }
111
+ const value = res.result && ('value' in res.result) ? res.result.value : null;
112
+ fs.writeFileSync(resultFile, JSON.stringify(value === undefined ? null : value));
113
+ sess.close();
114
+ process.exit(0);
115
+ } catch (e) {
116
+ fs.writeFileSync(resultFile, JSON.stringify({ __cdpError: String(e && e.message || e) }));
117
+ process.stderr.write(`cdp-eval: ${e && e.message || e}\n`);
118
+ try { sess.close(); } catch (_) {}
119
+ process.exit(1);
120
+ }
121
+ }
122
+
123
+ main();
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.2013",
3
+ "version": "2.0.2015",
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.2013",
3
+ "version": "2.0.2015",
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",
@@ -94,7 +94,7 @@ If similar symptoms recur (stub-like responses, silent truncation, debug fields
94
94
 
95
95
  **Windows-specific transient flakiness that is NOT a plugkit/wrapper bug, just retry it:** `bun x <pkg>@<version>` occasionally hangs indefinitely at "Resolving dependencies" with zero further output even with a pinned exact cached version (a known intermittent Bun/Windows dependency-resolution stall, reproducible with plain `bun x playwriter@<version> session list` outside any wrapper). A bare re-dispatch of the exact same body, or a fresh direct CLI retry, has resolved it every time observed. Do not chase this as a code bug -- 2-3 retries is the correct response, same bound as the raw-CLI fragility note below.
96
96
 
97
- **Raw-playwriter-CLI fallback, only if the `browser` verb itself is genuinely down (last resort, rare):** a headed `playwriter session new` (no `--browser headless`) requires a real Chrome + the Playwriter browser extension already paired and connected -- it does NOT auto-launch a visible window in this environment, it times out with "Extension did not connect" / "No connected browsers detected" (use `--direct` with an explicit `--remote-debugging-port`-launched Chrome instead if a visible/headed session is genuinely required, not bare `session new`). Sessions (both plugkit-relayed and raw-CLI) have been observed torn down between consecutive CLI invocations even a few seconds apart ("Session N not found"), and a `UV_HANDLE_CLOSING` native assertion crash in playwriter's own relay process on Windows has recurred multiple times ("Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\\win\\async.c, line 76"), most often right after a `resume()`/`reload()`/session-teardown call -- recover via `playwriter session reset <id>`, or if that also fails, `playwriter session delete <id>` + relaunch. This is upstream Windows-native fragility in playwriter itself, not fixable from inside a `gm` session. Do not burn more than 2-3 retries chasing a single flaky browser witness on Windows -- if the real `browser` verb and raw CLI both fail repeatedly for the same script, record it as a genuine `blockedBy: external` tooling gap on the relevant PRD/mutable row (with what was tried) rather than looping indefinitely.
97
+ **The browser verb drives Chrome directly over CDP -- the playwriter relay is no longer on the eval path.** The `UV_HANDLE_CLOSING` native crash in playwriter's own relay process on Windows ("Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\\win\\async.c, line 76") was resolved by removing the dependency on that relay: the wrapper already launches Chrome with `--remote-debugging-port` and obtains a live DevTools `webSocketDebuggerUrl` (playwriter-free), and `gm-plugkit/wrapper/cdp-eval.js` now runs the script in-page via `Runtime.evaluate` over that websocket directly, so the crashing relay attach+eval is never spawned. This is the standing browser eval path. A flaky browser witness is therefore a real bug to root-cause in the CDP path or the Chrome launch, never a `blockedBy: external` tooling gap -- everything is fixable: if a browser dispatch fails, read the CDP endpoint (`http://127.0.0.1:<port>/json/version`), check the Chrome launch args, and fix the actual failure rather than parking it. There is no accepted "external tooling gap" resolution for the browser verb.
98
98
 
99
99
  Spool input from PowerShell must be UTF-8 no-BOM (`-Encoding utf8` or `[System.IO.File]::WriteAllText`); UTF-16+BOM causes `spool.body-encoding-recoded`. First-turn body is `{"prompt":"<user request>"}` (derives orient_nouns + recall_hits); later turns may use `{}`. Batch independent dispatches: multiple `prd-add`, `prd-resolve`, `mutable-add`, `recall`+`codesearch`, or inspection `Read` calls in a single tool block. Avoid editing the same file twice in one block; collapse changes into a single Edit.
100
100