gm-skill 2.0.1215 → 2.0.1217
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/gm-plugkit/plugkit-wasm-wrapper.js +167 -8
- package/gm.json +1 -1
- package/lib/skill-bootstrap.js +1 -1
- package/lib/spool-dispatch.js +9 -2
- package/lib/spool-poll-gate.js +1 -0
- 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.1217` — 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
|
|
|
@@ -100,7 +100,7 @@ process.stdin.on('end', () => {
|
|
|
100
100
|
sub: 'hook',
|
|
101
101
|
event: 'deviation.spool-poll',
|
|
102
102
|
pid: process.pid,
|
|
103
|
-
sess: process.env.CLAUDE_SESSION_ID || process.env.GM_SESSION_ID || '',
|
|
103
|
+
sess: event.session_id || process.env.CLAUDE_SESSION_ID || process.env.GM_SESSION_ID || '',
|
|
104
104
|
cwd: process.cwd(),
|
|
105
105
|
operation: 'bash',
|
|
106
106
|
pattern,
|
|
@@ -124,11 +124,35 @@ function ensureSpoolPollGate(cwd) {
|
|
|
124
124
|
const gateScript = path.join(gmHooks, 'spool-poll-gate.js');
|
|
125
125
|
const want = spoolPollGateScript();
|
|
126
126
|
let need = true;
|
|
127
|
+
let oldSha = '';
|
|
128
|
+
let existed = false;
|
|
127
129
|
try {
|
|
128
130
|
const existing = fs.readFileSync(gateScript, 'utf8');
|
|
131
|
+
existed = true;
|
|
129
132
|
if (existing === want) need = false;
|
|
133
|
+
else {
|
|
134
|
+
try {
|
|
135
|
+
const _crypto = require('crypto');
|
|
136
|
+
oldSha = _crypto.createHash('sha256').update(existing).digest('hex').slice(0, 12);
|
|
137
|
+
} catch (_) {}
|
|
138
|
+
}
|
|
130
139
|
} catch (_) {}
|
|
131
|
-
if (need)
|
|
140
|
+
if (need) {
|
|
141
|
+
fs.writeFileSync(gateScript, want);
|
|
142
|
+
try {
|
|
143
|
+
const _crypto = require('crypto');
|
|
144
|
+
const newSha = _crypto.createHash('sha256').update(want).digest('hex').slice(0, 12);
|
|
145
|
+
try {
|
|
146
|
+
logEvent('bootstrap', existed ? 'gate.refreshed' : 'gate.installed', {
|
|
147
|
+
cwd,
|
|
148
|
+
path: gateScript,
|
|
149
|
+
old_sha: oldSha || null,
|
|
150
|
+
new_sha: newSha,
|
|
151
|
+
bytes: Buffer.byteLength(want, 'utf8'),
|
|
152
|
+
});
|
|
153
|
+
} catch (_) {}
|
|
154
|
+
} catch (_) {}
|
|
155
|
+
}
|
|
132
156
|
|
|
133
157
|
const claudeDir = path.join(cwd, '.claude');
|
|
134
158
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
@@ -172,6 +196,99 @@ function applyDisciplineSigil(rawBody) {
|
|
|
172
196
|
return JSON.stringify(parsed);
|
|
173
197
|
}
|
|
174
198
|
|
|
199
|
+
function isInstructionTurnStart(sess) {
|
|
200
|
+
const key = sess || '(no-session)';
|
|
201
|
+
const now = Date.now();
|
|
202
|
+
const t = _turns.get(key);
|
|
203
|
+
if (!t) return true;
|
|
204
|
+
if ((now - t.lastTs) > TURN_IDLE_MS) return true;
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function readUserPromptForRecall(cwd) {
|
|
209
|
+
const root = cwd || process.cwd();
|
|
210
|
+
try {
|
|
211
|
+
const p = path.join(root, '.gm', 'last-prompt.txt');
|
|
212
|
+
const txt = fs.readFileSync(p, 'utf8').trim();
|
|
213
|
+
if (txt) return txt;
|
|
214
|
+
} catch (_) {}
|
|
215
|
+
try {
|
|
216
|
+
const p = path.join(root, '.gm', 'turn-state.json');
|
|
217
|
+
const obj = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
218
|
+
if (obj && typeof obj.last_prompt === 'string' && obj.last_prompt.trim()) return obj.last_prompt.trim();
|
|
219
|
+
if (obj && typeof obj.prompt === 'string' && obj.prompt.trim()) return obj.prompt.trim();
|
|
220
|
+
} catch (_) {}
|
|
221
|
+
return '';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function dispatchVerbToWasmInternal(instance, verb, body) {
|
|
225
|
+
const dispatch = instance.exports.dispatch_verb;
|
|
226
|
+
if (!dispatch) return null;
|
|
227
|
+
const verbBytes = new TextEncoder().encode(verb);
|
|
228
|
+
const bodyBytes = new TextEncoder().encode(body || '');
|
|
229
|
+
const verbPtr = instance.exports.plugkit_alloc(verbBytes.length);
|
|
230
|
+
const bodyPtr = instance.exports.plugkit_alloc(bodyBytes.length);
|
|
231
|
+
try {
|
|
232
|
+
new Uint8Array(instance.exports.memory.buffer, verbPtr, verbBytes.length).set(verbBytes);
|
|
233
|
+
new Uint8Array(instance.exports.memory.buffer, bodyPtr, bodyBytes.length).set(bodyBytes);
|
|
234
|
+
const result = dispatch(verbPtr, verbBytes.length, bodyPtr, bodyBytes.length);
|
|
235
|
+
const ptr = Number(result & 0xffffffffn);
|
|
236
|
+
const len = Number(result >> 32n);
|
|
237
|
+
const out = new TextDecoder().decode(new Uint8Array(instance.exports.memory.buffer, ptr, len));
|
|
238
|
+
try { instance.exports.plugkit_free(ptr, len); } catch (_) {}
|
|
239
|
+
return out;
|
|
240
|
+
} finally {
|
|
241
|
+
try { instance.exports.plugkit_free(verbPtr, verbBytes.length); } catch (_) {}
|
|
242
|
+
try { instance.exports.plugkit_free(bodyPtr, bodyBytes.length); } catch (_) {}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function tryAutoRecallForTurnEntry(instance, sess, cwd) {
|
|
247
|
+
try {
|
|
248
|
+
const prompt = readUserPromptForRecall(cwd);
|
|
249
|
+
if (!prompt) return null;
|
|
250
|
+
const out = dispatchVerbToWasmInternal(instance, 'auto-recall', prompt);
|
|
251
|
+
if (!out) return null;
|
|
252
|
+
let parsed;
|
|
253
|
+
try { parsed = JSON.parse(out); } catch (_) { return null; }
|
|
254
|
+
if (!parsed || parsed.ok !== true) return null;
|
|
255
|
+
let inner = parsed.data;
|
|
256
|
+
if (typeof parsed.stdout === 'string' && parsed.stdout.length > 0) {
|
|
257
|
+
try { inner = JSON.parse(parsed.stdout); } catch (_) {}
|
|
258
|
+
}
|
|
259
|
+
if (!inner || typeof inner !== 'object') return null;
|
|
260
|
+
const hits = Array.isArray(inner.results) ? inner.results : (Array.isArray(inner.hits) ? inner.hits : []);
|
|
261
|
+
const payload = { query: inner.query || '', hits, fired_at: new Date().toISOString(), turn_entry: true };
|
|
262
|
+
logEvent('plugkit', 'auto_recall.turn-entry', { sess, query: payload.query, count: hits.length });
|
|
263
|
+
return payload;
|
|
264
|
+
} catch (e) {
|
|
265
|
+
logEvent('plugkit', 'auto_recall.error', { sess, error: String(e && e.message || e) });
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function mergeAutoRecallIntoInstructionResponse(resultStr, autoRecall) {
|
|
271
|
+
if (!autoRecall) return resultStr;
|
|
272
|
+
let parsed;
|
|
273
|
+
try { parsed = JSON.parse(resultStr); } catch (_) { return resultStr; }
|
|
274
|
+
if (!parsed || typeof parsed !== 'object') return resultStr;
|
|
275
|
+
if (parsed.data && typeof parsed.data === 'object') {
|
|
276
|
+
parsed.data.auto_recall = autoRecall;
|
|
277
|
+
} else {
|
|
278
|
+
parsed.auto_recall = autoRecall;
|
|
279
|
+
}
|
|
280
|
+
if (typeof parsed.stdout === 'string' && parsed.stdout.length > 0) {
|
|
281
|
+
try {
|
|
282
|
+
const inner = JSON.parse(parsed.stdout);
|
|
283
|
+
if (inner && typeof inner === 'object') {
|
|
284
|
+
inner.auto_recall = autoRecall;
|
|
285
|
+
parsed.stdout = JSON.stringify(inner);
|
|
286
|
+
}
|
|
287
|
+
} catch (_) {}
|
|
288
|
+
}
|
|
289
|
+
return JSON.stringify(parsed);
|
|
290
|
+
}
|
|
291
|
+
|
|
175
292
|
function turnTick(sess, verb, taskBase, phase) {
|
|
176
293
|
const key = sess || '(no-session)';
|
|
177
294
|
const now = Date.now();
|
|
@@ -256,6 +373,13 @@ function emitOrchestratorEvents(verb, taskBase, resultStr) {
|
|
|
256
373
|
let parsed;
|
|
257
374
|
try { parsed = JSON.parse(resultStr); } catch (_) { return; }
|
|
258
375
|
if (!parsed || parsed.ok !== true) {
|
|
376
|
+
let errData = null;
|
|
377
|
+
if (parsed && typeof parsed.stdout === 'string' && parsed.stdout.length > 0) {
|
|
378
|
+
try { errData = JSON.parse(parsed.stdout); } catch (_) {}
|
|
379
|
+
}
|
|
380
|
+
if (verb === 'prd-resolve' && errData && errData.deviation_kind === 'prd-resolve-unknown-id') {
|
|
381
|
+
logEvent('hook', 'deviation.prd-resolve-unknown-id', { task: taskBase, prd_id: errData.prd_id, reason: errData.error });
|
|
382
|
+
}
|
|
259
383
|
logEvent('plugkit', 'orchestrator.error', { verb, task: taskBase, error: parsed && parsed.error ? String(parsed.error) : 'unknown' });
|
|
260
384
|
return;
|
|
261
385
|
}
|
|
@@ -276,7 +400,11 @@ function emitOrchestratorEvents(verb, taskBase, resultStr) {
|
|
|
276
400
|
logEvent('plugkit', 'prd.added', { task: taskBase, id: data.added });
|
|
277
401
|
break;
|
|
278
402
|
case 'prd-resolve':
|
|
279
|
-
|
|
403
|
+
if (data && data.deviation_kind === 'prd-resolve-unknown-id') {
|
|
404
|
+
logEvent('hook', 'deviation.prd-resolve-unknown-id', { task: taskBase, prd_id: data.prd_id, reason: data.error });
|
|
405
|
+
} else {
|
|
406
|
+
logEvent('plugkit', 'prd.resolved', { task: taskBase, id: data.resolved });
|
|
407
|
+
}
|
|
280
408
|
break;
|
|
281
409
|
case 'mutable-add':
|
|
282
410
|
logEvent('plugkit', 'mutable.added', { task: taskBase, id: data.added });
|
|
@@ -1192,6 +1320,8 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1192
1320
|
fs.mkdirSync(inDir, { recursive: true });
|
|
1193
1321
|
fs.mkdirSync(outDir, { recursive: true });
|
|
1194
1322
|
|
|
1323
|
+
try { ensureSpoolPollGate(process.env.CLAUDE_PROJECT_DIR || process.cwd()); } catch (_) {}
|
|
1324
|
+
|
|
1195
1325
|
const LOCK_PATH = path.join(spoolDir, '.watcher.lock');
|
|
1196
1326
|
let _ownWrapperSha12 = '';
|
|
1197
1327
|
try {
|
|
@@ -1495,18 +1625,35 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1495
1625
|
prior_status: _priorStatus,
|
|
1496
1626
|
prior_status_age_ms: _priorStatus && Number.isFinite(_priorStatus.ts) ? Date.now() - _priorStatus.ts : null,
|
|
1497
1627
|
};
|
|
1498
|
-
const
|
|
1628
|
+
const _PLANNED_REASONS = new Set(['idle', 'sigterm', 'version-change', 'wrapper-change', 'peer-stale-takeover', 'version-drift', 'external-planned']);
|
|
1629
|
+
const _isPlannedBoot = _priorShutdown && _PLANNED_REASONS.has(_priorShutdown.reason);
|
|
1499
1630
|
const _isFirstBoot = !_priorShutdown && !_priorStatus;
|
|
1500
1631
|
const UNPLANNED_RESTART_MARKER = path.join(spoolDir, '.unplanned-restart.json');
|
|
1501
|
-
|
|
1632
|
+
const HEARTBEAT_RECENT_MS = 60_000;
|
|
1633
|
+
const HEARTBEAT_DEAD_MS = 5 * 60_000;
|
|
1634
|
+
let _severity = 'critical';
|
|
1635
|
+
if (_isPlannedBoot) {
|
|
1636
|
+
_severity = 'info';
|
|
1637
|
+
} else if (!_priorShutdown && _priorStatus && Number.isFinite(_priorStatus.ts)) {
|
|
1638
|
+
const _statusAge = Date.now() - _priorStatus.ts;
|
|
1639
|
+
if (_statusAge <= HEARTBEAT_RECENT_MS) _severity = 'warn';
|
|
1640
|
+
else if (_statusAge < HEARTBEAT_DEAD_MS) _severity = 'warn';
|
|
1641
|
+
else _severity = 'critical';
|
|
1642
|
+
}
|
|
1643
|
+
if (!_isFirstBoot) {
|
|
1502
1644
|
const incidentPayload = {
|
|
1503
1645
|
ts: Date.now(),
|
|
1504
1646
|
version: _bootVersion,
|
|
1505
|
-
severity:
|
|
1647
|
+
severity: _severity,
|
|
1648
|
+
planned: _isPlannedBoot,
|
|
1506
1649
|
...restartContext,
|
|
1507
1650
|
log_tail_path: path.join(spoolDir, '.watcher.log'),
|
|
1508
1651
|
gm_log_dir: GM_LOG_ROOT,
|
|
1509
|
-
instruction:
|
|
1652
|
+
instruction: _isPlannedBoot
|
|
1653
|
+
? `Planned restart: prior watcher exited with reason="${_priorShutdown.reason}". No action required.`
|
|
1654
|
+
: (_severity === 'warn'
|
|
1655
|
+
? 'Prior watcher disappeared with a recent heartbeat — likely a clean shutdown that did not write .shutdown-reason.json. Inspect .watcher.log if recurrent.'
|
|
1656
|
+
: 'Prior watcher died without a planned shutdown and without a recent heartbeat. This is treated as a critical failure. Inspect .watcher.log and gm-log/<day>/plugkit.jsonl events supervisor.watcher-exited-unexpectedly + supervisor.heartbeat-stale around the prior_status.ts timestamp to diagnose root cause.'),
|
|
1510
1657
|
};
|
|
1511
1658
|
logEvent('plugkit', 'watcher.unplanned-restart', incidentPayload);
|
|
1512
1659
|
try {
|
|
@@ -1564,6 +1711,14 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1564
1711
|
console.log(`[dispatch] → verb=${verb} task=${taskBase} body=${bodyBytes.length}b`);
|
|
1565
1712
|
logEvent('plugkit', 'dispatch.start', { verb, task: taskBase, body_bytes: bodyBytes.length, cwd: process.cwd() });
|
|
1566
1713
|
|
|
1714
|
+
let autoRecallPayload = null;
|
|
1715
|
+
if (verb === 'instruction') {
|
|
1716
|
+
const sessForRecall = readCurrentSess();
|
|
1717
|
+
if (isInstructionTurnStart(sessForRecall)) {
|
|
1718
|
+
autoRecallPayload = tryAutoRecallForTurnEntry(instance, sessForRecall, process.cwd());
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1567
1722
|
const verbPtr = instance.exports.plugkit_alloc(verbBytes.length);
|
|
1568
1723
|
const bodyPtr = instance.exports.plugkit_alloc(bodyBytes.length);
|
|
1569
1724
|
new Uint8Array(instance.exports.memory.buffer, verbPtr, verbBytes.length).set(verbBytes);
|
|
@@ -1574,7 +1729,11 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1574
1729
|
const ptr = Number(result & 0xffffffffn);
|
|
1575
1730
|
const len = Number(result >> 32n);
|
|
1576
1731
|
const resultBytes = new Uint8Array(instance.exports.memory.buffer, ptr, len);
|
|
1577
|
-
|
|
1732
|
+
let resultStr = new TextDecoder().decode(resultBytes);
|
|
1733
|
+
|
|
1734
|
+
if (autoRecallPayload) {
|
|
1735
|
+
resultStr = mergeAutoRecallIntoInstructionResponse(resultStr, autoRecallPayload);
|
|
1736
|
+
}
|
|
1578
1737
|
|
|
1579
1738
|
const outName = dir === '.' ? `${taskBase}.json` : `${verb}-${taskBase}.json`;
|
|
1580
1739
|
fs.writeFileSync(path.join(outDir, outName), resultStr);
|
package/gm.json
CHANGED
package/lib/skill-bootstrap.js
CHANGED
|
@@ -393,7 +393,7 @@ process.stdin.on('end', () => {
|
|
|
393
393
|
sub: 'hook',
|
|
394
394
|
event: 'deviation.spool-poll',
|
|
395
395
|
pid: process.pid,
|
|
396
|
-
sess: process.env.CLAUDE_SESSION_ID || process.env.GM_SESSION_ID || '',
|
|
396
|
+
sess: event.session_id || process.env.CLAUDE_SESSION_ID || process.env.GM_SESSION_ID || '',
|
|
397
397
|
cwd: process.cwd(),
|
|
398
398
|
operation: 'bash',
|
|
399
399
|
pattern,
|
package/lib/spool-dispatch.js
CHANGED
|
@@ -11,14 +11,21 @@ function logDeviation(event, fields) {
|
|
|
11
11
|
const day = new Date().toISOString().slice(0, 10);
|
|
12
12
|
const dir = path.join(GM_LOG_ROOT, day);
|
|
13
13
|
fs.mkdirSync(dir, { recursive: true });
|
|
14
|
+
const f = fields || {};
|
|
15
|
+
const sessOverride = (f.sess !== undefined) ? f.sess : null;
|
|
16
|
+
const rest = { ...f };
|
|
17
|
+
delete rest.sess;
|
|
18
|
+
const sess = (sessOverride && String(sessOverride).length > 0)
|
|
19
|
+
? String(sessOverride)
|
|
20
|
+
: (process.env.CLAUDE_SESSION_ID || process.env.GM_SESSION_ID || '');
|
|
14
21
|
const line = JSON.stringify({
|
|
15
22
|
ts: new Date().toISOString(),
|
|
16
23
|
sub: 'hook',
|
|
17
24
|
event,
|
|
18
25
|
pid: process.pid,
|
|
19
|
-
sess
|
|
26
|
+
sess,
|
|
20
27
|
cwd: process.cwd(),
|
|
21
|
-
...
|
|
28
|
+
...rest,
|
|
22
29
|
});
|
|
23
30
|
fs.appendFileSync(path.join(dir, 'hook.jsonl'), line + '\n');
|
|
24
31
|
} catch (_) {}
|
package/lib/spool-poll-gate.js
CHANGED
|
@@ -25,6 +25,7 @@ process.stdin.on('end', () => {
|
|
|
25
25
|
pattern,
|
|
26
26
|
command_excerpt: String(command).slice(0, 200),
|
|
27
27
|
via: 'pre-tool-use-hook',
|
|
28
|
+
sess: event.session_id || process.env.CLAUDE_SESSION_ID || process.env.GM_SESSION_ID || '',
|
|
28
29
|
});
|
|
29
30
|
} catch (_) {}
|
|
30
31
|
process.stdout.write(JSON.stringify({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1217",
|
|
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.1217"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=16.0.0"
|