gm-skill 2.0.1221 → 2.0.1222

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.1221` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` (or any cascading sibling crate) republishes this package.
38
+ `2.0.1222` — 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.448
1
+ 0.1.449
package/bin/plugkit.wasm CHANGED
Binary file
@@ -1 +1 @@
1
- 9bafe43d985cebd33c63433b532a7f396599905fa1d90c9f411ea292f10fd7c1 plugkit.wasm
1
+ 0cac0a48e7346d2f75779c9972efe47c3f4ab38063a077660906a53028d2e70a plugkit.wasm
@@ -78,6 +78,17 @@ function isBrowserRunningFileLocal(rel) {
78
78
  return false;
79
79
  }
80
80
 
81
+ function hashFileShortLocal(cwd, rel) {
82
+ try {
83
+ const fs = require('fs');
84
+ const path = require('path');
85
+ const crypto = require('crypto');
86
+ const abs = path.isAbsolute(rel) ? rel : path.join(cwd, rel);
87
+ const buf = fs.readFileSync(abs);
88
+ return crypto.createHash('sha256').update(buf).digest('hex').slice(0, 12);
89
+ } catch (_) { return ''; }
90
+ }
91
+
81
92
  function recordBrowserEditLocal(cwd, filePath) {
82
93
  try {
83
94
  const fs = require('fs');
@@ -89,8 +100,11 @@ function recordBrowserEditLocal(cwd, filePath) {
89
100
  fs.mkdirSync(path.dirname(editsFile), { recursive: true });
90
101
  let list = [];
91
102
  try { list = JSON.parse(fs.readFileSync(editsFile, 'utf8')); if (!Array.isArray(list)) list = []; } catch (_) {}
92
- const entry = { file: rel.replace(/\\\\/g, '/'), ts: Date.now() };
93
- if (!list.some(e => e && e.file === entry.file)) list.push(entry);
103
+ const relPath = rel.replace(/\\\\/g, '/');
104
+ const hash = hashFileShortLocal(cwd, relPath);
105
+ const idx = list.findIndex(e => e && e.file === relPath);
106
+ const entry = { file: relPath, ts: Date.now(), hash };
107
+ if (idx === -1) list.push(entry); else list[idx] = entry;
94
108
  fs.writeFileSync(editsFile, JSON.stringify(list));
95
109
  return true;
96
110
  } catch (_) { return false; }
@@ -322,9 +336,24 @@ function dispatchAutoRecall(instance, queryPrompt) {
322
336
  function tryAutoRecallForTurnEntry(instance, sess, cwd) {
323
337
  try {
324
338
  const prompt = readUserPromptForRecall(cwd);
325
- if (!prompt) return null;
326
- const primary = dispatchAutoRecall(instance, prompt);
327
- const fallbackQuery = deriveFallbackQuery(prompt);
339
+ let emptyPromptFallback = false;
340
+ let effectivePrompt = prompt;
341
+ if (!prompt || !String(prompt).trim()) {
342
+ emptyPromptFallback = true;
343
+ const key = sess || '(no-session)';
344
+ const t = _turns.get(key);
345
+ const phase = (t && t.lastPhase) || 'PLAN';
346
+ const phaseQueryMap = {
347
+ PLAN: 'PLAN orient',
348
+ EXECUTE: 'EXECUTE work',
349
+ EMIT: 'EMIT closure',
350
+ VERIFY: 'VERIFY trajectory',
351
+ COMPLETE: 'COMPLETE residual',
352
+ };
353
+ effectivePrompt = phaseQueryMap[phase] || 'PLAN orient';
354
+ }
355
+ const primary = dispatchAutoRecall(instance, effectivePrompt);
356
+ const fallbackQuery = deriveFallbackQuery(effectivePrompt);
328
357
  let fallback = null;
329
358
  if (fallbackQuery && fallbackQuery !== (primary && primary.query)) {
330
359
  fallback = dispatchAutoRecall(instance, fallbackQuery);
@@ -344,12 +373,16 @@ function tryAutoRecallForTurnEntry(instance, sess, cwd) {
344
373
  if (primary && primary.query) queries.push(primary.query);
345
374
  if (fallback && fallback.query && !queries.includes(fallback.query)) queries.push(fallback.query);
346
375
  const payload = {
347
- query: (primary && primary.query) || '',
376
+ query: (primary && primary.query) || effectivePrompt || '',
348
377
  queries,
349
378
  hits: merged.slice(0, 20),
350
379
  fired_at: new Date().toISOString(),
351
380
  turn_entry: true,
352
381
  };
382
+ if (emptyPromptFallback) {
383
+ payload.fallback_reason = 'empty-prompt';
384
+ if (!payload.query) payload.query = effectivePrompt;
385
+ }
353
386
  logEvent('plugkit', 'auto_recall.turn-entry', { sess, queries, count: merged.length });
354
387
  return payload;
355
388
  } catch (e) {
@@ -1276,6 +1309,7 @@ function makeHostFunctions(instanceRef) {
1276
1309
  const lang = opts.lang || 'nodejs';
1277
1310
  const cwd = opts.cwd || process.cwd();
1278
1311
  const rawTimeout = opts.timeoutMs;
1312
+ const MIN_TIMEOUT_MS = 100;
1279
1313
  if (rawTimeout === undefined || rawTimeout === null || typeof rawTimeout !== 'number' || !Number.isFinite(rawTimeout) || rawTimeout <= 0 || !Number.isInteger(rawTimeout)) {
1280
1314
  return writeWasmJson(instanceRef.value, {
1281
1315
  ok: false,
@@ -1285,6 +1319,15 @@ function makeHostFunctions(instanceRef) {
1285
1319
  received: rawTimeout === undefined ? null : rawTimeout,
1286
1320
  });
1287
1321
  }
1322
+ if (rawTimeout < MIN_TIMEOUT_MS) {
1323
+ return writeWasmJson(instanceRef.value, {
1324
+ ok: false,
1325
+ error: 'timeoutMs below floor',
1326
+ min: MIN_TIMEOUT_MS,
1327
+ received: rawTimeout,
1328
+ paper_ref: '§20',
1329
+ });
1330
+ }
1288
1331
  const timeoutMs = rawTimeout;
1289
1332
  let cmd, args;
1290
1333
  if (lang === 'nodejs' || lang === 'js') { cmd = process.execPath; args = ['-e', code]; }
@@ -1852,10 +1895,23 @@ async function runSpoolWatcher(instance, spoolDir) {
1852
1895
 
1853
1896
  if (verb === 'browser') {
1854
1897
  try {
1855
- const witnessFile = path.join(process.cwd(), '.gm', 'exec-spool', '.turn-browser-witnessed');
1898
+ const cwd_ = process.cwd();
1899
+ const editsFile = path.join(cwd_, '.gm', 'exec-spool', '.turn-browser-edits.json');
1900
+ const witnessFile = path.join(cwd_, '.gm', 'exec-spool', '.turn-browser-witnessed');
1856
1901
  fs.mkdirSync(path.dirname(witnessFile), { recursive: true });
1857
- fs.writeFileSync(witnessFile, JSON.stringify({ ts: Date.now(), task: taskBase, dur_ms }));
1858
- logEvent('plugkit', 'browser.witness-marked', { task: taskBase });
1902
+ let edits = [];
1903
+ try { edits = JSON.parse(fs.readFileSync(editsFile, 'utf8')); if (!Array.isArray(edits)) edits = []; } catch (_) {}
1904
+ const witnessed_hashes = {};
1905
+ for (const e of edits) {
1906
+ if (!e || !e.file) continue;
1907
+ try {
1908
+ const abs = path.isAbsolute(e.file) ? e.file : path.join(cwd_, e.file);
1909
+ const buf = fs.readFileSync(abs);
1910
+ witnessed_hashes[e.file] = crypto.createHash('sha256').update(buf).digest('hex').slice(0, 12);
1911
+ } catch (_) { witnessed_hashes[e.file] = ''; }
1912
+ }
1913
+ fs.writeFileSync(witnessFile, JSON.stringify({ ts: Date.now(), task: taskBase, dur_ms, witnessed_hashes }));
1914
+ logEvent('plugkit', 'browser.witness-marked', { task: taskBase, files: Object.keys(witnessed_hashes) });
1859
1915
  } catch (_) {}
1860
1916
  }
1861
1917
 
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1221",
3
+ "version": "2.0.1222",
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.448"
20
+ "plugkitVersion": "0.1.449"
21
21
  }
@@ -77,6 +77,14 @@ function browserWitnessFile(cwd) {
77
77
  return path.join(cwd || process.cwd(), '.gm', 'exec-spool', '.turn-browser-witnessed');
78
78
  }
79
79
 
80
+ function hashFileShort(root, rel) {
81
+ try {
82
+ const abs = path.isAbsolute(rel) ? rel : path.join(root, rel);
83
+ const buf = fs.readFileSync(abs);
84
+ return require('crypto').createHash('sha256').update(buf).digest('hex').slice(0, 12);
85
+ } catch (_) { return ''; }
86
+ }
87
+
80
88
  function recordBrowserEdit(cwd, filePath) {
81
89
  try {
82
90
  const root = cwd || process.cwd();
@@ -87,8 +95,11 @@ function recordBrowserEdit(cwd, filePath) {
87
95
  fs.mkdirSync(path.dirname(f), { recursive: true });
88
96
  let list = [];
89
97
  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);
98
+ const relPath = rel.replace(/\\/g, '/');
99
+ const hash = hashFileShort(root, relPath);
100
+ const idx = list.findIndex(e => e && e.file === relPath);
101
+ const entry = { file: relPath, ts: Date.now(), hash };
102
+ if (idx === -1) list.push(entry); else list[idx] = entry;
92
103
  fs.writeFileSync(f, JSON.stringify(list));
93
104
  return true;
94
105
  } catch (_) { return false; }
@@ -103,12 +114,27 @@ function clearBrowserTurnMarkers(cwd) {
103
114
 
104
115
  function markBrowserWitnessed(cwd, meta) {
105
116
  try {
106
- const f = browserWitnessFile(cwd);
117
+ const root = cwd || process.cwd();
118
+ const f = browserWitnessFile(root);
107
119
  fs.mkdirSync(path.dirname(f), { recursive: true });
108
- fs.writeFileSync(f, JSON.stringify({ ts: Date.now(), ...(meta || {}) }));
120
+ const edits = readBrowserEdits(root);
121
+ const witnessed_hashes = {};
122
+ for (const e of edits) {
123
+ if (!e || !e.file) continue;
124
+ witnessed_hashes[e.file] = hashFileShort(root, e.file);
125
+ }
126
+ fs.writeFileSync(f, JSON.stringify({ ts: Date.now(), witnessed_hashes, ...(meta || {}) }));
109
127
  } catch (_) {}
110
128
  }
111
129
 
130
+ function readBrowserWitness(cwd) {
131
+ try {
132
+ const f = browserWitnessFile(cwd);
133
+ if (!fs.existsSync(f)) return null;
134
+ return JSON.parse(fs.readFileSync(f, 'utf8'));
135
+ } catch (_) { return null; }
136
+ }
137
+
112
138
  function readBrowserEdits(cwd) {
113
139
  try {
114
140
  const f = browserEditsFile(cwd);
@@ -354,6 +380,29 @@ function checkDispatchGates(sessionId, operation, extra) {
354
380
  const shown = files.slice(0, 5).join(', ') + (files.length > 5 ? `, +${files.length - 5} more` : '');
355
381
  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
382
  logDeviation('deviation.browser-witness-missing', { files, operation });
383
+ } else if (browserEdits.length > 0 && isBrowserWitnessed(cwd)) {
384
+ const witness = readBrowserWitness(cwd) || {};
385
+ const wh = witness.witnessed_hashes || {};
386
+ const mismatches = [];
387
+ for (const e of browserEdits) {
388
+ if (!e || !e.file) continue;
389
+ const witnessed = wh[e.file];
390
+ if (!witnessed) {
391
+ mismatches.push({ file: e.file, reason: 'no witnessed hash recorded (file edited after witness, or witness predates edit)' });
392
+ continue;
393
+ }
394
+ const current = hashFileShort(cwd || process.cwd(), e.file);
395
+ if (current !== witnessed) {
396
+ mismatches.push({ file: e.file, witnessed_hash: witnessed, current_hash: current || '(unreadable)' });
397
+ }
398
+ }
399
+ if (mismatches.length > 0) {
400
+ const summary = mismatches.slice(0, 3).map(m =>
401
+ `${m.file} (witnessed=${m.witnessed_hash || 'none'}, current=${m.current_hash || '(none)'}${m.reason ? '; ' + m.reason : ''})`
402
+ ).join('; ');
403
+ residuals.push(`Browser Witness hash mismatch: you witnessed file(s) at one state, but their current content differs. Either the witness was on a different state or the file was reverted/re-edited without re-witnessing. Re-run the browser verb against the current state. Mismatches: ${summary}${mismatches.length > 3 ? `, +${mismatches.length - 3} more` : ''}`);
404
+ logDeviation('deviation.browser-witness-hash-mismatch', { mismatches, operation });
405
+ }
357
406
  }
358
407
  if (residuals.length > 0) {
359
408
  logDeviation('deviation.gate-deny', { operation, reason: 'stop-gate residuals', residuals });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1221",
3
+ "version": "2.0.1222",
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.1221"
42
+ "gm-plugkit": "^2.0.1222"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=16.0.0"