gm-skill 2.0.1373 → 2.0.1375

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1373",
3
+ "version": "2.0.1375",
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": {
@@ -359,6 +359,38 @@ function readCurrentSess() {
359
359
 
360
360
  const __lockRejectedEmitAt = new Map();
361
361
 
362
+ function autoRecordBrowserEditsFromBody(body, cwd, taskBase, verb) {
363
+ if (!body || typeof body !== 'string') return;
364
+ const BROWSER_EXT_RE = /[\w.\-/\\]+\.(?:html?|tsx?|jsx?|mjs|cjs|vue|svelte|css|scss|sass)\b/gi;
365
+ const matches = body.match(BROWSER_EXT_RE);
366
+ if (!matches || matches.length === 0) return;
367
+ const seen = new Set();
368
+ const editsFile = path.join(cwd, '.gm', 'exec-spool', '.turn-browser-edits.json');
369
+ let list = [];
370
+ try { list = JSON.parse(fs.readFileSync(editsFile, 'utf8')); if (!Array.isArray(list)) list = []; } catch (_) {}
371
+ let added = 0;
372
+ for (const raw of matches) {
373
+ let rel = String(raw).replace(/^["'`(]+|["'`)]+$/g, '').replace(/\\/g, '/');
374
+ if (rel.startsWith('http://') || rel.startsWith('https://') || rel.startsWith('//')) continue;
375
+ if (rel.includes('node_modules/') || rel.startsWith('.gm/') || rel.includes('/.gm/')) continue;
376
+ if (seen.has(rel)) continue;
377
+ seen.add(rel);
378
+ const abs = path.isAbsolute(rel) ? rel : path.join(cwd, rel);
379
+ let st;
380
+ try { st = fs.statSync(abs); } catch (_) { continue; }
381
+ if (!st.isFile()) continue;
382
+ let hash = '';
383
+ try { hash = require('crypto').createHash('sha256').update(fs.readFileSync(abs)).digest('hex').slice(0, 12); } catch (_) {}
384
+ const idx = list.findIndex(e => e && e.file === rel);
385
+ const entry = { file: rel, ts: Date.now(), hash, source_verb: verb, source_task: taskBase };
386
+ if (idx === -1) { list.push(entry); added++; } else { list[idx] = entry; }
387
+ }
388
+ if (added > 0) {
389
+ try { fs.mkdirSync(path.dirname(editsFile), { recursive: true }); fs.writeFileSync(editsFile, JSON.stringify(list)); } catch (_) {}
390
+ logEvent('plugkit', 'browser.edits-autorecorded', { verb, task: taskBase, files: list.slice(-added).map(e => e.file), added });
391
+ }
392
+ }
393
+
362
394
  function logEvent(sub, event, fields) {
363
395
  if (process.env.GM_LOG_DISABLE) return;
364
396
  try {
@@ -2535,6 +2567,10 @@ async function runSpoolWatcher(instance, spoolDir) {
2535
2567
  console.log(`[dispatch] → verb=${verb} task=${taskBase} body=${bodyBytes.length}b`);
2536
2568
  logEvent('plugkit', 'dispatch.start', { verb, task: taskBase, body_bytes: bodyBytes.length, cwd: process.cwd() });
2537
2569
 
2570
+ if (verb === 'memorize-fire' || verb === 'transition' || verb === 'prd-resolve' || verb === 'mutable-resolve') {
2571
+ try { autoRecordBrowserEditsFromBody(body, process.cwd(), taskBase, verb); } catch (_) {}
2572
+ }
2573
+
2538
2574
  let autoRecallPayload = null;
2539
2575
  if (verb === 'instruction') {
2540
2576
  const sessForRecall = readCurrentSess();
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1373",
3
+ "version": "2.0.1375",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
@@ -203,15 +203,13 @@ const SPOOL_POLL_PATTERNS = [
203
203
  /\bwhile\b[^;]*?(?:!|-not)\s*(?:-(?:f|e)\s+|Test-Path\s+)[^;]*?\.gm[\\/](?:exec-spool|spool)/i,
204
204
  /\buntil\b[^;]*?(?:-f|-e|Test-Path)\s+[^;]*?\.gm[\\/](?:exec-spool|spool)/i,
205
205
  /\bfor\s+i\s+in\b[^;]*?;\s*do\b[^;]*?(?:sleep|Start-Sleep)[^;]*?\.gm[\\/](?:exec-spool|spool)/i,
206
- /\b(?:cat|head|tail|less|more|type|Get-Content|gc)\s+(?:-[A-Za-z]+\s+)*['"]?[^'"|;&]*\.gm[\\/](?:exec-spool|spool)[\\/]\.status\.json\b/i,
207
- /\b(?:cat|head|tail|less|more|type|Get-Content|gc)\s+(?:-[A-Za-z]+\s+)*['"]?[^'"|;&]*\.gm[\\/](?:exec-spool|spool)[\\/]\.watcher\.log\b/i,
208
206
  /\b(?:ls|dir|Get-ChildItem|gci)\s+(?:-[A-Za-z]+\s+)*['"]?[^'"|;&]*\.gm[\\/](?:exec-spool|spool)(?:[\\/](?:in|out)?)?[\\/]?['"]?\s*(?:$|[|;&])/i,
209
207
  /\b(?:test|Test-Path|tp)\s+(?:-[A-Za-z]+\s+)?['"]?[^'"|;&]*\.gm[\\/](?:exec-spool|spool)[\\/](?:out|in)[\\/]/i,
210
208
  /\bfind\b[^|]*['"]?[^'"|;&]*\.gm[\\/](?:exec-spool|spool)\b/i,
211
209
  /\b(?:xargs|parallel|fzf)\b[^|]*\.gm[\\/](?:exec-spool|spool)/i,
212
210
  ];
213
211
 
214
- const SPOOL_POLL_REASON = 'spool polling and bash-reads of .gm/exec-spool/ are forbidden — plugkit is synchronous from your view, and the Read tool is the canonical way to inspect spool files. Specific replacements:\n\n- Instead of `cat .gm/exec-spool/.status.json` → use the Read tool: `Read .gm/exec-spool/.status.json`\n- Instead of `ls .gm/exec-spool/out/` → check the specific response file you wrote, e.g. `Read .gm/exec-spool/out/<verb>-<N>.json`\n- Instead of `cat .gm/exec-spool/.watcher.log` → use the Read tool with offset for tailing\n- Instead of `sleep N; cat .gm/exec-spool/<...>` → just Read the response file directly; if it doesn\'t exist yet, the watcher is dead (Read .gm/exec-spool/.status.json fresh ts means alive) or the verb is slow (Read .gm/exec-spool/.watcher.log for the dispatch trace)\n\nYou are the state machine. Plugkit serves the response the moment you write the request file. If you find yourself thinking "let me just check whether the file is there yet"use Read. If you find yourself thinking "the watcher might have died" Read .gm/exec-spool/.status.json. Bash on .gm/exec-spool/ is wrong every single time.';
212
+ const SPOOL_POLL_REASON = 'spool POLLING (sleep+cat, while !test, ls/find on the spool dirs) is forbidden — plugkit is synchronous from your view, so the response file is there the moment the watcher finishes the verb. Specific replacements:\n\n- Instead of `ls .gm/exec-spool/out/` → check the specific response file you wrote, e.g. `Read .gm/exec-spool/out/<verb>-<N>.json`\n- Instead of `sleep N; cat .gm/exec-spool/<...>` → just Read the response file directly; if it doesn\'t exist yet, the watcher is dead (the SKILL.md boot probe `cat .gm/exec-spool/.status.json; date +%s%3N` is the way to check liveness) or the verb is slow (Read .gm/exec-spool/.watcher.log for the dispatch trace)\n- Instead of `while [ ! -f ... ]; do sleep ...; done` → write the request, Read the response in the same message, accept the file-not-found and re-Read in the next message\n\nThe SKILL.md-prescribed boot probe (`cat .gm/exec-spool/.status.json; date +%s%3N`) is NOT a violation it is the canonical liveness check because it pipes with `date` for ts comparison. The Read tool can\'t do that in one call. What this gate denies is the *polling* pattern around the spool dirs, not the boot-probe cat. You are the state machine. Plugkit serves the response the moment you write the request file.';
215
213
 
216
214
  function stripHeredocsAndStringLiterals(command) {
217
215
  let s = String(command);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1373",
3
+ "version": "2.0.1375",
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",