gm-skill 2.0.1149 → 2.0.1150

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.1149` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` (or any cascading sibling crate) republishes this package.
38
+ `2.0.1150` — 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.414
1
+ 0.1.415
package/bin/plugkit.wasm CHANGED
Binary file
@@ -1 +1 @@
1
- 74a7d080de71336c5d7c56a0edd4936c853259a21323c0ab797ce9a3782f5dac plugkit.wasm
1
+ 3af3d80f571bec77e28471184811226f865a150cfcbecef00b6d90e2ed90c140 plugkit.wasm
@@ -14,6 +14,73 @@ const __dirname = path.dirname(__filename);
14
14
  const KV_DIR = path.join(os.homedir(), '.claude', 'gm-tools', 'kv');
15
15
  fs.mkdirSync(KV_DIR, { recursive: true });
16
16
 
17
+ const GM_LOG_ROOT = process.env.GM_LOG_DIR || path.join(os.homedir(), '.claude', 'gm-log');
18
+ const ORCHESTRATOR_VERBS = new Set(['instruction', 'transition', 'phase-status', 'prd-add', 'prd-resolve', 'prd-list', 'mutable-add', 'mutable-resolve', 'mutable-list', 'memorize-fire', 'residual-scan', 'auto-recall']);
19
+
20
+ function logEvent(sub, event, fields) {
21
+ if (process.env.GM_LOG_DISABLE) return;
22
+ try {
23
+ const day = new Date().toISOString().slice(0, 10);
24
+ const dir = path.join(GM_LOG_ROOT, day);
25
+ fs.mkdirSync(dir, { recursive: true });
26
+ const line = JSON.stringify({
27
+ ts: new Date().toISOString(),
28
+ sub,
29
+ event,
30
+ pid: process.pid,
31
+ sess: process.env.CLAUDE_SESSION_ID || process.env.GM_SESSION_ID || '',
32
+ ...fields,
33
+ });
34
+ fs.appendFileSync(path.join(dir, `${sub}.jsonl`), line + '\n');
35
+ } catch (_) {}
36
+ }
37
+
38
+ function emitOrchestratorEvents(verb, taskBase, resultStr) {
39
+ if (!ORCHESTRATOR_VERBS.has(verb)) return;
40
+ let parsed;
41
+ try { parsed = JSON.parse(resultStr); } catch (_) { return; }
42
+ if (!parsed || parsed.ok !== true) {
43
+ logEvent('plugkit', 'orchestrator.error', { verb, task: taskBase, error: parsed && parsed.error ? String(parsed.error) : 'unknown' });
44
+ return;
45
+ }
46
+ const data = parsed.data || {};
47
+ switch (verb) {
48
+ case 'transition':
49
+ logEvent('plugkit', 'phase.transitioned', { task: taskBase, phase: data.phase, next_skill: data.nextSkill, recall_count: Array.isArray(data.recall_hits) ? data.recall_hits.length : 0 });
50
+ break;
51
+ case 'instruction':
52
+ logEvent('plugkit', 'instruction.served', { task: taskBase, phase: data.phase, prd_pending: data.prd_pending_count, mutables_pending: Array.isArray(data.mutables_pending) ? data.mutables_pending.length : 0, next_phase_hint: data.next_phase_hint });
53
+ break;
54
+ case 'phase-status':
55
+ logEvent('plugkit', 'phase.status', { task: taskBase, phase: data.phase, last_skill: data.last_skill });
56
+ break;
57
+ case 'prd-add':
58
+ logEvent('plugkit', 'prd.added', { task: taskBase, id: data.added });
59
+ break;
60
+ case 'prd-resolve':
61
+ logEvent('plugkit', 'prd.resolved', { task: taskBase, id: data.resolved });
62
+ break;
63
+ case 'mutable-add':
64
+ logEvent('plugkit', 'mutable.added', { task: taskBase, id: data.added });
65
+ break;
66
+ case 'mutable-resolve':
67
+ logEvent('plugkit', 'mutable.resolved', { task: taskBase, id: data.resolved, memorize_spool: data.memorize_spool });
68
+ break;
69
+ case 'memorize-fire':
70
+ logEvent('plugkit', 'memorize.fired', { task: taskBase, key: data.key, namespace: data.namespace, bytes: data.bytes });
71
+ break;
72
+ case 'residual-scan':
73
+ if (data.scan === 'fired') logEvent('plugkit', 'residual.fired', { task: taskBase, marker: data.marker });
74
+ else logEvent('plugkit', 'residual.skipped', { task: taskBase, reason: data.reason });
75
+ break;
76
+ case 'auto-recall':
77
+ logEvent('plugkit', 'auto_recall.hits', { task: taskBase, count: Array.isArray(data.hits) ? data.hits.length : 0 });
78
+ break;
79
+ default:
80
+ break;
81
+ }
82
+ }
83
+
17
84
  const TMP_DIR = os.tmpdir();
18
85
  const BROWSER_PORTS_FILE = path.join(TMP_DIR, 'plugkit-browser-ports.json');
19
86
  const BROWSER_SESSIONS_FILE = path.join(TMP_DIR, 'plugkit-browser-sessions.json');
@@ -656,8 +723,10 @@ async function runSpoolWatcher(instance, spoolDir) {
656
723
  }
657
724
  } catch (e) { console.error(`[plugkit-wasm] wrapper self-install failed: ${e.message}`); }
658
725
 
659
- console.log(`[plugkit-wasm] plugkit v${resolveVersion(instance)} (wasm)`);
726
+ const _bootVersion = resolveVersion(instance);
727
+ console.log(`[plugkit-wasm] plugkit v${_bootVersion} (wasm)`);
660
728
  console.log(`[plugkit-wasm] watching ${inDir}`);
729
+ logEvent('plugkit', 'watcher.boot', { version: _bootVersion, in_dir: inDir, out_dir: outDir, spool_dir: spoolDir });
661
730
 
662
731
  const PROCESSED_MAX = 10000;
663
732
  const processed = new Map();
@@ -692,6 +761,7 @@ async function runSpoolWatcher(instance, spoolDir) {
692
761
 
693
762
  const t0 = Date.now();
694
763
  console.log(`[dispatch] → verb=${verb} task=${taskBase} body=${bodyBytes.length}b`);
764
+ logEvent('plugkit', 'dispatch.start', { verb, task: taskBase, body_bytes: bodyBytes.length, cwd: process.cwd() });
695
765
 
696
766
  const verbPtr = instance.exports.plugkit_alloc(verbBytes.length);
697
767
  const bodyPtr = instance.exports.plugkit_alloc(bodyBytes.length);
@@ -707,7 +777,10 @@ async function runSpoolWatcher(instance, spoolDir) {
707
777
 
708
778
  const outName = dir === '.' ? `${taskBase}.json` : `${verb}-${taskBase}.json`;
709
779
  fs.writeFileSync(path.join(outDir, outName), resultStr);
710
- console.log(`[dispatch] verb=${verb} task=${taskBase} ms=${Date.now() - t0} out=${resultStr.length}b`);
780
+ const dur_ms = Date.now() - t0;
781
+ console.log(`[dispatch] ← verb=${verb} task=${taskBase} ms=${dur_ms} out=${resultStr.length}b`);
782
+ logEvent('plugkit', 'dispatch.end', { verb, task: taskBase, dur_ms, out_bytes: resultStr.length });
783
+ emitOrchestratorEvents(verb, taskBase, resultStr);
711
784
 
712
785
  try { instance.exports.plugkit_free(verbPtr, verbBytes.length); } catch (_) {}
713
786
  try { instance.exports.plugkit_free(bodyPtr, bodyBytes.length); } catch (_) {}
@@ -727,6 +800,7 @@ async function runSpoolWatcher(instance, spoolDir) {
727
800
  } catch (_) {}
728
801
  try { fs.unlinkSync(filePath); } catch (_) {}
729
802
  unmarkProcessed(key);
803
+ logEvent('plugkit', 'dispatch.error', { verb, task: taskBase, error: String(e && e.message || e) });
730
804
  }
731
805
  };
732
806
 
@@ -819,8 +893,14 @@ async function runSpoolWatcher(instance, spoolDir) {
819
893
  if (s.mtimeMs < cutoff) { fs.unlinkSync(fp); swept++; }
820
894
  } catch (e) { console.error(`[retention] failed to sweep ${entry}: ${e.message}`); }
821
895
  }
822
- if (swept > 0) console.log(`[retention] swept ${swept} out/ files older than 1h`);
823
- } catch (e) { console.error(`[retention] sweep error: ${e.message}`); }
896
+ if (swept > 0) {
897
+ console.log(`[retention] swept ${swept} out/ files older than 1h`);
898
+ logEvent('plugkit', 'sweep.retention', { swept });
899
+ }
900
+ } catch (e) {
901
+ console.error(`[retention] sweep error: ${e.message}`);
902
+ logEvent('plugkit', 'sweep.retention.error', { error: String(e.message || e) });
903
+ }
824
904
  }, 60_000);
825
905
 
826
906
  setInterval(() => {
@@ -848,8 +928,14 @@ async function runSpoolWatcher(instance, spoolDir) {
848
928
  }
849
929
  };
850
930
  walk(inDir);
851
- if (stale > 0) console.log(`[stale-sweep] failed ${stale} orphaned inputs`);
852
- } catch (e) { console.error(`[stale-sweep] sweep error: ${e.message}`); }
931
+ if (stale > 0) {
932
+ console.log(`[stale-sweep] failed ${stale} orphaned inputs`);
933
+ logEvent('plugkit', 'sweep.stale', { stale });
934
+ }
935
+ } catch (e) {
936
+ console.error(`[stale-sweep] sweep error: ${e.message}`);
937
+ logEvent('plugkit', 'sweep.stale.error', { error: String(e.message || e) });
938
+ }
853
939
  }, 300_000);
854
940
 
855
941
  const existing = walkDir(inDir);
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1149",
3
+ "version": "2.0.1150",
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.414"
20
+ "plugkitVersion": "0.1.415"
21
21
  }
@@ -3,6 +3,27 @@ const path = require('path');
3
3
  const os = require('os');
4
4
  const { spawnSync } = require('child_process');
5
5
 
6
+ const GM_LOG_ROOT = process.env.GM_LOG_DIR || path.join(os.homedir(), '.claude', 'gm-log');
7
+
8
+ function logDeviation(event, fields) {
9
+ if (process.env.GM_LOG_DISABLE) return;
10
+ try {
11
+ const day = new Date().toISOString().slice(0, 10);
12
+ const dir = path.join(GM_LOG_ROOT, day);
13
+ fs.mkdirSync(dir, { recursive: true });
14
+ const line = JSON.stringify({
15
+ ts: new Date().toISOString(),
16
+ sub: 'hook',
17
+ event,
18
+ pid: process.pid,
19
+ sess: process.env.CLAUDE_SESSION_ID || process.env.GM_SESSION_ID || '',
20
+ cwd: process.cwd(),
21
+ ...fields,
22
+ });
23
+ fs.appendFileSync(path.join(dir, 'hook.jsonl'), line + '\n');
24
+ } catch (_) {}
25
+ }
26
+
6
27
  function isWorktreeDirty(cwd) {
7
28
  try {
8
29
  const r = spawnSync('git', ['status', '--porcelain'], {
@@ -99,7 +120,30 @@ async function pollForCompletion(jsonFile, timeoutMs, taskId) {
99
120
  };
100
121
  }
101
122
 
102
- function checkDispatchGates(sessionId, operation) {
123
+ function sessionMarkerPath(sessionId, kind) {
124
+ const cwd = process.cwd();
125
+ return path.join(cwd, '.gm', 'exec-spool', `.session-${kind}-${sessionId || 'anon'}`);
126
+ }
127
+
128
+ function hasDispatchedInstruction(sessionId) {
129
+ try {
130
+ const outDir = path.join(process.cwd(), '.gm', 'exec-spool', 'out');
131
+ if (!fs.existsSync(outDir)) return false;
132
+ for (const f of fs.readdirSync(outDir)) {
133
+ if (f.startsWith('instruction-')) return true;
134
+ }
135
+ } catch (_) {}
136
+ return fs.existsSync(sessionMarkerPath(sessionId, 'instruction-seen'));
137
+ }
138
+
139
+ function markInstructionSeen(sessionId) {
140
+ try {
141
+ fs.mkdirSync(path.dirname(sessionMarkerPath(sessionId, 'instruction-seen')), { recursive: true });
142
+ fs.writeFileSync(sessionMarkerPath(sessionId, 'instruction-seen'), String(Date.now()));
143
+ } catch (_) {}
144
+ }
145
+
146
+ function checkDispatchGates(sessionId, operation, extra) {
103
147
  const cwd = process.cwd();
104
148
  const gm = path.join(cwd, '.gm');
105
149
  const prdPath = path.join(gm, 'prd.yml');
@@ -134,14 +178,24 @@ function checkDispatchGates(sessionId, operation) {
134
178
  residuals.push(`${unpushed.count} unpushed commit${unpushed.count === 1 ? '' : 's'} — push to remote before declaring done`);
135
179
  }
136
180
  if (residuals.length > 0) {
181
+ logDeviation('deviation.gate-deny', { operation, reason: 'stop-gate residuals', residuals });
137
182
  return { allowed: false, reason: `stop-gate residuals: ${residuals.join('; ')}`, residuals };
138
183
  }
139
184
  return { allowed: true };
140
185
  }
141
186
 
187
+ if (['write', 'edit'].includes(operation) && !hasDispatchedInstruction(sessionId)) {
188
+ logDeviation('deviation.write-before-instruction', { operation, sessionId });
189
+ }
190
+
191
+ if (operation === 'mutable-resolve' && extra && (!extra.witness_evidence || String(extra.witness_evidence).trim() === '')) {
192
+ logDeviation('deviation.mutable-without-evidence', { mutable_id: extra.id || null });
193
+ }
194
+
142
195
  if (!['write', 'edit', 'git'].includes(operation)) return { allowed: true };
143
196
 
144
197
  if (fs.existsSync(prdPath) && fs.existsSync(needsGmPath) && !fs.existsSync(gmFiredPath)) {
198
+ logDeviation('deviation.gate-deny', { operation, reason: 'gm orchestration in progress' });
145
199
  return { allowed: false, reason: 'gm orchestration in progress; skills must complete work before tools execute' };
146
200
  }
147
201
 
@@ -149,6 +203,7 @@ function checkDispatchGates(sessionId, operation) {
149
203
  try {
150
204
  const content = fs.readFileSync(mutsPath, 'utf8');
151
205
  if (content.includes('status: unknown')) {
206
+ logDeviation('deviation.gate-deny', { operation, reason: 'unresolved mutables' });
152
207
  return { allowed: false, reason: 'unresolved mutables block tool execution; resolve all mutables before proceeding' };
153
208
  }
154
209
  } catch (_) {}
@@ -157,4 +212,4 @@ function checkDispatchGates(sessionId, operation) {
157
212
  return { allowed: true };
158
213
  }
159
214
 
160
- module.exports = { dispatchSpool, checkDispatchGates, isWorktreeDirty, hasUnpushedCommits };
215
+ module.exports = { dispatchSpool, checkDispatchGates, isWorktreeDirty, hasUnpushedCommits, logDeviation, markInstructionSeen, hasDispatchedInstruction };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1149",
3
+ "version": "2.0.1150",
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.1149"
42
+ "gm-plugkit": "^2.0.1150"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=16.0.0"