gm-skill 2.0.1218 → 2.0.1219

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.1218` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` (or any cascading sibling crate) republishes this package.
38
+ `2.0.1219` — 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
 
@@ -1 +1 @@
1
- 0.1.446
1
+ 0.1.447
package/bin/plugkit.wasm CHANGED
Binary file
@@ -1 +1 @@
1
- f47f68684b3860078e10698ca188c302968eb99fbc6608a2851eb3ded4b55df8 plugkit.wasm
1
+ 0ce920f00138b9524a0a1ad062dab691e4959497853644a7d06403c2b77ed480 plugkit.wasm
@@ -1223,7 +1223,17 @@ function makeHostFunctions(instanceRef) {
1223
1223
  const opts = optsStr ? JSON.parse(optsStr) : {};
1224
1224
  const lang = opts.lang || 'nodejs';
1225
1225
  const cwd = opts.cwd || process.cwd();
1226
- const timeoutMs = opts.timeoutMs || 30000;
1226
+ const rawTimeout = opts.timeoutMs;
1227
+ if (rawTimeout === undefined || rawTimeout === null || typeof rawTimeout !== 'number' || !Number.isFinite(rawTimeout) || rawTimeout <= 0 || !Number.isInteger(rawTimeout)) {
1228
+ return writeWasmJson(instanceRef.value, {
1229
+ ok: false,
1230
+ error: 'missing timeoutMs',
1231
+ required: 'positive integer milliseconds',
1232
+ paper_ref: '§20',
1233
+ received: rawTimeout === undefined ? null : rawTimeout,
1234
+ });
1235
+ }
1236
+ const timeoutMs = rawTimeout;
1227
1237
  let cmd, args;
1228
1238
  if (lang === 'nodejs' || lang === 'js') { cmd = process.execPath; args = ['-e', code]; }
1229
1239
  else if (lang === 'python') { cmd = 'python'; args = ['-c', code]; }
@@ -1755,6 +1765,13 @@ async function runSpoolWatcher(instance, spoolDir) {
1755
1765
  const sessForRecall = readCurrentSess();
1756
1766
  if (isInstructionTurnStart(sessForRecall)) {
1757
1767
  autoRecallPayload = tryAutoRecallForTurnEntry(instance, sessForRecall, process.cwd());
1768
+ try {
1769
+ const _spoolDir = path.join(process.cwd(), '.gm', 'exec-spool');
1770
+ for (const _f of ['.turn-browser-edits.json', '.turn-browser-witnessed']) {
1771
+ const _p = path.join(_spoolDir, _f);
1772
+ if (fs.existsSync(_p)) fs.unlinkSync(_p);
1773
+ }
1774
+ } catch (_) {}
1758
1775
  }
1759
1776
  }
1760
1777
 
@@ -1781,6 +1798,15 @@ async function runSpoolWatcher(instance, spoolDir) {
1781
1798
  logEvent('plugkit', 'dispatch.end', { verb, task: taskBase, dur_ms, out_bytes: resultStr.length });
1782
1799
  emitOrchestratorEvents(verb, taskBase, resultStr);
1783
1800
 
1801
+ if (verb === 'browser') {
1802
+ try {
1803
+ const witnessFile = path.join(process.cwd(), '.gm', 'exec-spool', '.turn-browser-witnessed');
1804
+ fs.mkdirSync(path.dirname(witnessFile), { recursive: true });
1805
+ fs.writeFileSync(witnessFile, JSON.stringify({ ts: Date.now(), task: taskBase, dur_ms }));
1806
+ logEvent('plugkit', 'browser.witness-marked', { task: taskBase });
1807
+ } catch (_) {}
1808
+ }
1809
+
1784
1810
  try { instance.exports.plugkit_free(verbPtr, verbBytes.length); } catch (_) {}
1785
1811
  try { instance.exports.plugkit_free(bodyPtr, bodyBytes.length); } catch (_) {}
1786
1812
  try { instance.exports.plugkit_free(ptr, len); } catch (_) {}
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1218",
3
+ "version": "2.0.1219",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
@@ -17,5 +17,5 @@
17
17
  "publishConfig": {
18
18
  "access": "public"
19
19
  },
20
- "plugkitVersion": "0.1.446"
20
+ "plugkitVersion": "0.1.447"
21
21
  }
@@ -59,6 +59,69 @@ function hasUnpushedCommits(cwd) {
59
59
 
60
60
  const TOPLEVEL_DOC_ALLOWLIST = new Set(['AGENTS.md', 'CLAUDE.md', 'README.md', 'SKILLS.md', 'CHANGELOG.md', 'LICENSE', 'LICENSE.md']);
61
61
 
62
+ const BROWSER_FILE_EXT_RE = /\.(html?|tsx|jsx|vue|svelte|mjs|cjs|js|ts|css|scss|sass)$/i;
63
+ const BROWSER_FILE_DIR_RE = /^(src|public|site|app|pages|components|client|web)[\\/]/i;
64
+
65
+ function isBrowserRunningFile(rel) {
66
+ if (!rel) return false;
67
+ const norm = String(rel).replace(/\\/g, '/');
68
+ if (/\.(html?|tsx|jsx|vue|svelte)$/i.test(norm)) return true;
69
+ if (/\.(mjs|cjs|js|ts|css|scss|sass)$/i.test(norm) && BROWSER_FILE_DIR_RE.test(norm)) return true;
70
+ return false;
71
+ }
72
+
73
+ function browserEditsFile(cwd) {
74
+ return path.join(cwd || process.cwd(), '.gm', 'exec-spool', '.turn-browser-edits.json');
75
+ }
76
+ function browserWitnessFile(cwd) {
77
+ return path.join(cwd || process.cwd(), '.gm', 'exec-spool', '.turn-browser-witnessed');
78
+ }
79
+
80
+ function recordBrowserEdit(cwd, filePath) {
81
+ try {
82
+ const root = cwd || process.cwd();
83
+ let rel = filePath;
84
+ try { rel = path.relative(root, filePath); } catch (_) {}
85
+ if (!isBrowserRunningFile(rel)) return false;
86
+ const f = browserEditsFile(root);
87
+ fs.mkdirSync(path.dirname(f), { recursive: true });
88
+ let list = [];
89
+ try { list = JSON.parse(fs.readFileSync(f, 'utf8')); if (!Array.isArray(list)) list = []; } catch (_) {}
90
+ const entry = { file: rel.replace(/\\/g, '/'), ts: Date.now() };
91
+ if (!list.some(e => e && e.file === entry.file)) list.push(entry);
92
+ fs.writeFileSync(f, JSON.stringify(list));
93
+ return true;
94
+ } catch (_) { return false; }
95
+ }
96
+
97
+ function clearBrowserTurnMarkers(cwd) {
98
+ const root = cwd || process.cwd();
99
+ for (const p of [browserEditsFile(root), browserWitnessFile(root)]) {
100
+ try { if (fs.existsSync(p)) fs.unlinkSync(p); } catch (_) {}
101
+ }
102
+ }
103
+
104
+ function markBrowserWitnessed(cwd, meta) {
105
+ try {
106
+ const f = browserWitnessFile(cwd);
107
+ fs.mkdirSync(path.dirname(f), { recursive: true });
108
+ fs.writeFileSync(f, JSON.stringify({ ts: Date.now(), ...(meta || {}) }));
109
+ } catch (_) {}
110
+ }
111
+
112
+ function readBrowserEdits(cwd) {
113
+ try {
114
+ const f = browserEditsFile(cwd);
115
+ if (!fs.existsSync(f)) return [];
116
+ const list = JSON.parse(fs.readFileSync(f, 'utf8'));
117
+ return Array.isArray(list) ? list : [];
118
+ } catch (_) { return []; }
119
+ }
120
+
121
+ function isBrowserWitnessed(cwd) {
122
+ try { return fs.existsSync(browserWitnessFile(cwd)); } catch (_) { return false; }
123
+ }
124
+
62
125
  function unsolicitedDocs(cwd) {
63
126
  try {
64
127
  const r = spawnSync('git', ['status', '--porcelain'], {
@@ -285,6 +348,13 @@ function checkDispatchGates(sessionId, operation, extra) {
285
348
  logDeviation('deviation.unsolicited-doc-created', { file: f, operation });
286
349
  }
287
350
  }
351
+ const browserEdits = readBrowserEdits(cwd);
352
+ if (browserEdits.length > 0 && !isBrowserWitnessed(cwd)) {
353
+ const files = browserEdits.map(e => e.file);
354
+ const shown = files.slice(0, 5).join(', ') + (files.length > 5 ? `, +${files.length - 5} more` : '');
355
+ residuals.push(`Browser Witness required: you edited ${shown} without dispatching the browser verb to witness the change in a live page. Per paper §23 this is non-negotiable. Either dispatch browser to verify the edit works in-browser, or revert the changes.`);
356
+ logDeviation('deviation.browser-witness-missing', { files, operation });
357
+ }
288
358
  if (residuals.length > 0) {
289
359
  logDeviation('deviation.gate-deny', { operation, reason: 'stop-gate residuals', residuals });
290
360
  return { allowed: false, reason: `stop-gate residuals: ${residuals.join('; ')}`, residuals };
@@ -339,4 +409,4 @@ function checkDispatchGates(sessionId, operation, extra) {
339
409
  return { allowed: true };
340
410
  }
341
411
 
342
- module.exports = { dispatchSpool, checkDispatchGates, isWorktreeDirty, hasUnpushedCommits, unsolicitedDocs, logDeviation, markInstructionSeen, hasDispatchedInstruction, isSpoolPollCommand, SPOOL_POLL_REASON };
412
+ module.exports = { dispatchSpool, checkDispatchGates, isWorktreeDirty, hasUnpushedCommits, unsolicitedDocs, logDeviation, markInstructionSeen, hasDispatchedInstruction, isSpoolPollCommand, SPOOL_POLL_REASON, recordBrowserEdit, markBrowserWitnessed, clearBrowserTurnMarkers, isBrowserRunningFile, readBrowserEdits, isBrowserWitnessed };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const { isSpoolPollCommand, SPOOL_POLL_REASON, logDeviation } = require('./spool-dispatch.js');
2
+ const { isSpoolPollCommand, SPOOL_POLL_REASON, logDeviation, recordBrowserEdit, isBrowserRunningFile } = require('./spool-dispatch.js');
3
3
 
4
4
  let raw = '';
5
5
  process.stdin.setEncoding('utf8');
@@ -9,6 +9,24 @@ process.stdin.on('end', () => {
9
9
  try { event = JSON.parse(raw || '{}'); } catch (_) { event = {}; }
10
10
  const tool = event.tool_name || event.tool || '';
11
11
  const input = event.tool_input || event.input || {};
12
+ const cwd = event.cwd || process.env.CLAUDE_PROJECT_DIR || process.cwd();
13
+
14
+ if (tool === 'Write' || tool === 'Edit' || tool === 'MultiEdit') {
15
+ const fp = input.file_path || input.filePath || input.path || '';
16
+ if (fp && isBrowserRunningFile(require('path').relative(cwd, fp))) {
17
+ try { recordBrowserEdit(cwd, fp); } catch (_) {}
18
+ try {
19
+ logDeviation('browser-edit.recorded', {
20
+ operation: tool.toLowerCase(),
21
+ file: fp,
22
+ sess: event.session_id || process.env.CLAUDE_SESSION_ID || process.env.GM_SESSION_ID || '',
23
+ });
24
+ } catch (_) {}
25
+ }
26
+ process.stdout.write(JSON.stringify({ continue: true }));
27
+ process.exit(0);
28
+ }
29
+
12
30
  if (tool !== 'Bash') {
13
31
  process.stdout.write(JSON.stringify({ continue: true }));
14
32
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1218",
3
+ "version": "2.0.1219",
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.1218"
42
+ "gm-plugkit": "^2.0.1219"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=16.0.0"