gm-skill 2.0.1220 → 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 +1 -1
- package/bin/plugkit.version +1 -1
- package/bin/plugkit.wasm +0 -0
- package/bin/plugkit.wasm.sha256 +1 -1
- package/gm-plugkit/plugkit-wasm-wrapper.js +120 -12
- package/gm.json +2 -2
- package/lib/skill-bootstrap.js +1 -1
- package/lib/spool-dispatch.js +54 -5
- package/package.json +2 -2
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.
|
|
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
|
|
package/bin/plugkit.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.449
|
package/bin/plugkit.wasm
CHANGED
|
Binary file
|
package/bin/plugkit.wasm.sha256
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
0cac0a48e7346d2f75779c9972efe47c3f4ab38063a077660906a53028d2e70a plugkit.wasm
|
|
@@ -49,7 +49,7 @@ const SPOOL_POLL_PATTERNS = [
|
|
|
49
49
|
/\\b(?:test|Test-Path|tp)\\s+(?:-[A-Za-z]+\\s+)?['"]?[^'"|;&]*\\.gm[\\\\/](?:exec-spool|spool)[\\\\/]/i,
|
|
50
50
|
];
|
|
51
51
|
|
|
52
|
-
const SPOOL_POLL_REASON = 'spool polling and bash-reads of .gm/exec-spool/ are forbidden — plugkit is synchronous from your view, and the canonical way to inspect spool files
|
|
52
|
+
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.';
|
|
53
53
|
|
|
54
54
|
function stripHeredocsAndStringLiterals(command) {
|
|
55
55
|
let s = String(command);
|
|
@@ -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
|
|
93
|
-
|
|
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; }
|
|
@@ -282,11 +296,29 @@ function dispatchVerbToWasmInternal(instance, verb, body) {
|
|
|
282
296
|
}
|
|
283
297
|
}
|
|
284
298
|
|
|
285
|
-
|
|
299
|
+
const AUTO_RECALL_STOPWORDS = new Set([
|
|
300
|
+
'the','a','an','and','or','but','if','then','else','for','of','to','in','on','at','by','with','from','as','is','are','was','were','be','been','being','have','has','had','do','does','did','will','would','should','could','can','may','might','must','shall',
|
|
301
|
+
'look','check','see','use','make','run','get','set','put','take','give','find','show','tell','let','keep','try','add','new','old','this','that','these','those','it','its','their','there','here','about','into','over','under','also','just','some','any','all','more','less','most','past','minutes','minute','hours','hour','seconds','second','days','day',
|
|
302
|
+
]);
|
|
303
|
+
|
|
304
|
+
function deriveFallbackQuery(prompt) {
|
|
286
305
|
try {
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
const
|
|
306
|
+
const tokens = String(prompt).toLowerCase().split(/[^a-z0-9_]+/).filter(Boolean);
|
|
307
|
+
const freq = new Map();
|
|
308
|
+
for (const t of tokens) {
|
|
309
|
+
if (t.length < 4) continue;
|
|
310
|
+
if (AUTO_RECALL_STOPWORDS.has(t)) continue;
|
|
311
|
+
freq.set(t, (freq.get(t) || 0) + 1);
|
|
312
|
+
}
|
|
313
|
+
const ranked = Array.from(freq.entries()).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
314
|
+
const top = ranked.slice(0, 3).map(([w]) => w);
|
|
315
|
+
return top.join(' ');
|
|
316
|
+
} catch (_) { return ''; }
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function dispatchAutoRecall(instance, queryPrompt) {
|
|
320
|
+
try {
|
|
321
|
+
const out = dispatchVerbToWasmInternal(instance, 'auto-recall', queryPrompt);
|
|
290
322
|
if (!out) return null;
|
|
291
323
|
let parsed;
|
|
292
324
|
try { parsed = JSON.parse(out); } catch (_) { return null; }
|
|
@@ -297,8 +329,61 @@ function tryAutoRecallForTurnEntry(instance, sess, cwd) {
|
|
|
297
329
|
}
|
|
298
330
|
if (!inner || typeof inner !== 'object') return null;
|
|
299
331
|
const hits = Array.isArray(inner.results) ? inner.results : (Array.isArray(inner.hits) ? inner.hits : []);
|
|
300
|
-
|
|
301
|
-
|
|
332
|
+
return { query: inner.query || '', hits };
|
|
333
|
+
} catch (_) { return null; }
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function tryAutoRecallForTurnEntry(instance, sess, cwd) {
|
|
337
|
+
try {
|
|
338
|
+
const prompt = readUserPromptForRecall(cwd);
|
|
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);
|
|
357
|
+
let fallback = null;
|
|
358
|
+
if (fallbackQuery && fallbackQuery !== (primary && primary.query)) {
|
|
359
|
+
fallback = dispatchAutoRecall(instance, fallbackQuery);
|
|
360
|
+
}
|
|
361
|
+
const seen = new Set();
|
|
362
|
+
const merged = [];
|
|
363
|
+
for (const src of [primary, fallback]) {
|
|
364
|
+
if (!src || !Array.isArray(src.hits)) continue;
|
|
365
|
+
for (const h of src.hits) {
|
|
366
|
+
const id = h && (h.id || h.hash || h.key || JSON.stringify(h));
|
|
367
|
+
if (id && seen.has(id)) continue;
|
|
368
|
+
if (id) seen.add(id);
|
|
369
|
+
merged.push(h);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const queries = [];
|
|
373
|
+
if (primary && primary.query) queries.push(primary.query);
|
|
374
|
+
if (fallback && fallback.query && !queries.includes(fallback.query)) queries.push(fallback.query);
|
|
375
|
+
const payload = {
|
|
376
|
+
query: (primary && primary.query) || effectivePrompt || '',
|
|
377
|
+
queries,
|
|
378
|
+
hits: merged.slice(0, 20),
|
|
379
|
+
fired_at: new Date().toISOString(),
|
|
380
|
+
turn_entry: true,
|
|
381
|
+
};
|
|
382
|
+
if (emptyPromptFallback) {
|
|
383
|
+
payload.fallback_reason = 'empty-prompt';
|
|
384
|
+
if (!payload.query) payload.query = effectivePrompt;
|
|
385
|
+
}
|
|
386
|
+
logEvent('plugkit', 'auto_recall.turn-entry', { sess, queries, count: merged.length });
|
|
302
387
|
return payload;
|
|
303
388
|
} catch (e) {
|
|
304
389
|
logEvent('plugkit', 'auto_recall.error', { sess, error: String(e && e.message || e) });
|
|
@@ -1224,6 +1309,7 @@ function makeHostFunctions(instanceRef) {
|
|
|
1224
1309
|
const lang = opts.lang || 'nodejs';
|
|
1225
1310
|
const cwd = opts.cwd || process.cwd();
|
|
1226
1311
|
const rawTimeout = opts.timeoutMs;
|
|
1312
|
+
const MIN_TIMEOUT_MS = 100;
|
|
1227
1313
|
if (rawTimeout === undefined || rawTimeout === null || typeof rawTimeout !== 'number' || !Number.isFinite(rawTimeout) || rawTimeout <= 0 || !Number.isInteger(rawTimeout)) {
|
|
1228
1314
|
return writeWasmJson(instanceRef.value, {
|
|
1229
1315
|
ok: false,
|
|
@@ -1233,6 +1319,15 @@ function makeHostFunctions(instanceRef) {
|
|
|
1233
1319
|
received: rawTimeout === undefined ? null : rawTimeout,
|
|
1234
1320
|
});
|
|
1235
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
|
+
}
|
|
1236
1331
|
const timeoutMs = rawTimeout;
|
|
1237
1332
|
let cmd, args;
|
|
1238
1333
|
if (lang === 'nodejs' || lang === 'js') { cmd = process.execPath; args = ['-e', code]; }
|
|
@@ -1800,10 +1895,23 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1800
1895
|
|
|
1801
1896
|
if (verb === 'browser') {
|
|
1802
1897
|
try {
|
|
1803
|
-
const
|
|
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');
|
|
1804
1901
|
fs.mkdirSync(path.dirname(witnessFile), { recursive: true });
|
|
1805
|
-
|
|
1806
|
-
|
|
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) });
|
|
1807
1915
|
} catch (_) {}
|
|
1808
1916
|
}
|
|
1809
1917
|
|
package/gm.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm",
|
|
3
|
-
"version": "2.0.
|
|
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.
|
|
20
|
+
"plugkitVersion": "0.1.449"
|
|
21
21
|
}
|
package/lib/skill-bootstrap.js
CHANGED
|
@@ -342,7 +342,7 @@ const SPOOL_POLL_PATTERNS = [
|
|
|
342
342
|
/\\b(?:test|Test-Path|tp)\\s+(?:-[A-Za-z]+\\s+)?['"]?[^'"|;&]*\\.gm[\\\\/](?:exec-spool|spool)[\\\\/]/i,
|
|
343
343
|
];
|
|
344
344
|
|
|
345
|
-
const SPOOL_POLL_REASON = 'spool polling and bash-reads of .gm/exec-spool/ are forbidden — plugkit is synchronous from your view, and the canonical way to inspect spool files
|
|
345
|
+
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.';
|
|
346
346
|
|
|
347
347
|
function stripHeredocsAndStringLiterals(command) {
|
|
348
348
|
let s = String(command);
|
package/lib/spool-dispatch.js
CHANGED
|
@@ -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
|
|
91
|
-
|
|
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
|
|
117
|
+
const root = cwd || process.cwd();
|
|
118
|
+
const f = browserWitnessFile(root);
|
|
107
119
|
fs.mkdirSync(path.dirname(f), { recursive: true });
|
|
108
|
-
|
|
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);
|
|
@@ -267,7 +293,7 @@ const SPOOL_POLL_PATTERNS = [
|
|
|
267
293
|
/\b(?:test|Test-Path|tp)\s+(?:-[A-Za-z]+\s+)?['"]?[^'"|;&]*\.gm[\\/](?:exec-spool|spool)[\\/]/i,
|
|
268
294
|
];
|
|
269
295
|
|
|
270
|
-
const SPOOL_POLL_REASON = 'spool polling and bash-reads of .gm/exec-spool/ are forbidden — plugkit is synchronous from your view, and the canonical way to inspect spool files
|
|
296
|
+
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.';
|
|
271
297
|
|
|
272
298
|
function stripHeredocsAndStringLiterals(command) {
|
|
273
299
|
let s = String(command);
|
|
@@ -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.
|
|
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.
|
|
42
|
+
"gm-plugkit": "^2.0.1222"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=16.0.0"
|