gm-skill 2.0.1256 → 2.0.1258

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.1256` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` (or any cascading sibling crate) republishes this package.
38
+ `2.0.1258` — 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.464
1
+ 0.1.465
package/bin/plugkit.wasm CHANGED
Binary file
@@ -1 +1 @@
1
- 5f964bb1da8059f4d6969dc179e407cc14fa91860135389a611d1896085b59be plugkit.wasm
1
+ 09af78c6bc70c9933aad2ca7fb53b739a8a4f19fb08b19ba6fe33997d15de8e7 plugkit.wasm
@@ -27,213 +27,6 @@ const ORCHESTRATOR_VERBS = new Set(['instruction', 'transition', 'phase-status',
27
27
  const TURN_IDLE_MS = 30_000;
28
28
  const _turns = new Map();
29
29
 
30
- const SPOOL_POLL_GATE_MARK = '__gm_spool_poll_gate__';
31
-
32
- function spoolPollGateScript() {
33
- return `#!/usr/bin/env node
34
- // ${SPOOL_POLL_GATE_MARK}
35
- // PreToolUse hook that blocks bash polling of .gm/exec-spool.
36
- // Plugkit is synchronous from the agent's view; the Read tool is the canonical
37
- // way to inspect response files. This hook denies Bash commands that try to
38
- // poll or shell-read the spool directory.
39
-
40
- const SPOOL_POLL_PATTERNS = [
41
- /\\bsleep\\s+\\d+(?:\\.\\d+)?\\s*[;&]+\\s*(?:cat|ls|tail|head|find|test|grep)\\b[^|]*\\.gm[\\\\/](?:exec-spool|spool)/i,
42
- /\\bStart-Sleep\\b[^;|]*?[;|]\\s*(?:Get-Content|Test-Path|Get-ChildItem|cat|ls|gci|gc|tp)\\b[^|]*\\.gm[\\\\/](?:exec-spool|spool)/i,
43
- /\\b(?:cat|ls|tail|head|Get-Content|Test-Path|Get-ChildItem)\\b[^|]*\\.gm[\\\\/](?:exec-spool|spool)[^|]*?[;&|]+\\s*(?:sleep|Start-Sleep)\\b/i,
44
- /\\bwhile\\b[^;]*?(?:!|-not)\\s*(?:-(?:f|e)\\s+|Test-Path\\s+)[^;]*?\\.gm[\\\\/](?:exec-spool|spool)/i,
45
- /\\buntil\\b[^;]*?(?:-f|-e|Test-Path)\\s+[^;]*?\\.gm[\\\\/](?:exec-spool|spool)/i,
46
- /\\bfor\\s+i\\s+in\\b[^;]*?;\\s*do\\b[^;]*?(?:sleep|Start-Sleep)[^;]*?\\.gm[\\\\/](?:exec-spool|spool)/i,
47
- /\\b(?:cat|head|tail|less|more|type|Get-Content|gc)\\s+(?:-[A-Za-z]+\\s+)*['"]?[^'"|;&]*\\.gm[\\\\/](?:exec-spool|spool)[\\\\/]\\.status\\.json\\b/i,
48
- /\\b(?:cat|head|tail|less|more|type|Get-Content|gc)\\s+(?:-[A-Za-z]+\\s+)*['"]?[^'"|;&]*\\.gm[\\\\/](?:exec-spool|spool)[\\\\/]\\.watcher\\.log\\b/i,
49
- /\\b(?:ls|dir|Get-ChildItem|gci)\\s+(?:-[A-Za-z]+\\s+)*['"]?[^'"|;&]*\\.gm[\\\\/](?:exec-spool|spool)(?:[\\\\/](?:in|out)?)?[\\\\/]?['"]?\\s*(?:$|[|;&])/i,
50
- /\\b(?:test|Test-Path|tp)\\s+(?:-[A-Za-z]+\\s+)?['"]?[^'"|;&]*\\.gm[\\\\/](?:exec-spool|spool)[\\\\/](?:out|in)[\\\\/]/i,
51
- /\\bfind\\b[^|]*['"]?[^'"|;&]*\\.gm[\\\\/](?:exec-spool|spool)\\b/i,
52
- /\\b(?:xargs|parallel|fzf)\\b[^|]*\\.gm[\\\\/](?:exec-spool|spool)/i,
53
- ];
54
-
55
- 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.';
56
-
57
- function stripHeredocsAndStringLiterals(command) {
58
- let s = String(command);
59
- s = s.replace(/<<-?\\s*'([A-Z_]+)'[\\s\\S]*?\\n\\1/g, '');
60
- s = s.replace(/<<-?\\s*"?([A-Z_]+)"?[\\s\\S]*?\\n\\1/g, '');
61
- s = s.replace(/\\$\\(cat\\s+<<-?\\s*'?([A-Z_]+)'?[\\s\\S]*?\\n\\1\\s*\\)/g, '');
62
- s = s.replace(/-m\\s+(['"])(?:\\\\.|(?!\\1)[^\\\\])*\\1/g, '-m STR');
63
- s = s.replace(/--message[= ]+(['"])(?:\\\\.|(?!\\1)[^\\\\])*\\1/g, '--message STR');
64
- return s;
65
- }
66
-
67
- function isSpoolPollCommand(command) {
68
- if (!command) return null;
69
- const stripped = stripHeredocsAndStringLiterals(command);
70
- for (const re of SPOOL_POLL_PATTERNS) {
71
- if (re.test(stripped)) return re.source;
72
- }
73
- return null;
74
- }
75
-
76
- function isBrowserRunningFileLocal(rel) {
77
- if (!rel) return false;
78
- const norm = String(rel).replace(/\\\\/g, '/');
79
- if (/\\.(html?|tsx|jsx|vue|svelte)$/i.test(norm)) return true;
80
- if (/\\.(mjs|cjs|js|ts|css|scss|sass)$/i.test(norm) && /^(src|public|site|app|pages|components|client|web)\\//i.test(norm)) return true;
81
- return false;
82
- }
83
-
84
- function hashFileShortLocal(cwd, rel) {
85
- try {
86
- const fs = require('fs');
87
- const path = require('path');
88
- const crypto = require('crypto');
89
- const abs = path.isAbsolute(rel) ? rel : path.join(cwd, rel);
90
- const buf = fs.readFileSync(abs);
91
- return crypto.createHash('sha256').update(buf).digest('hex').slice(0, 12);
92
- } catch (_) { return ''; }
93
- }
94
-
95
- function recordBrowserEditLocal(cwd, filePath) {
96
- try {
97
- const fs = require('fs');
98
- const path = require('path');
99
- let rel = filePath;
100
- try { rel = path.relative(cwd, filePath); } catch (_) {}
101
- if (!isBrowserRunningFileLocal(rel)) return false;
102
- const editsFile = path.join(cwd, '.gm', 'exec-spool', '.turn-browser-edits.json');
103
- fs.mkdirSync(path.dirname(editsFile), { recursive: true });
104
- let list = [];
105
- try { list = JSON.parse(fs.readFileSync(editsFile, 'utf8')); if (!Array.isArray(list)) list = []; } catch (_) {}
106
- const relPath = rel.replace(/\\\\/g, '/');
107
- const hash = hashFileShortLocal(cwd, relPath);
108
- const idx = list.findIndex(e => e && e.file === relPath);
109
- const entry = { file: relPath, ts: Date.now(), hash };
110
- if (idx === -1) list.push(entry); else list[idx] = entry;
111
- fs.writeFileSync(editsFile, JSON.stringify(list));
112
- return true;
113
- } catch (_) { return false; }
114
- }
115
-
116
- let raw = '';
117
- process.stdin.setEncoding('utf8');
118
- process.stdin.on('data', (chunk) => { raw += chunk; });
119
- process.stdin.on('end', () => {
120
- let event = {};
121
- try { event = JSON.parse(raw || '{}'); } catch (_) { event = {}; }
122
- const tool = event.tool_name || event.tool || '';
123
- const input = event.tool_input || event.input || {};
124
- const cwd = event.cwd || process.env.CLAUDE_PROJECT_DIR || process.cwd();
125
-
126
- if (tool === 'Write' || tool === 'Edit' || tool === 'MultiEdit') {
127
- const fp = input.file_path || input.filePath || input.path || '';
128
- if (fp) {
129
- try { recordBrowserEditLocal(cwd, fp); } catch (_) {}
130
- }
131
- process.stdout.write(JSON.stringify({ continue: true }));
132
- process.exit(0);
133
- }
134
-
135
- if (tool !== 'Bash') {
136
- process.stdout.write(JSON.stringify({ continue: true }));
137
- process.exit(0);
138
- }
139
- const command = input.command || input.cmd || '';
140
- const pattern = isSpoolPollCommand(command);
141
- if (!pattern) {
142
- process.stdout.write(JSON.stringify({ continue: true }));
143
- process.exit(0);
144
- }
145
- try {
146
- const fs = require('fs');
147
- const path = require('path');
148
- const os = require('os');
149
- const day = new Date().toISOString().slice(0, 10);
150
- const dir = path.join(process.env.GM_LOG_DIR || path.join(os.homedir(), '.claude', 'gm-log'), day);
151
- fs.mkdirSync(dir, { recursive: true });
152
- fs.appendFileSync(path.join(dir, 'hook.jsonl'), JSON.stringify({
153
- ts: new Date().toISOString(),
154
- sub: 'hook',
155
- event: 'deviation.spool-poll',
156
- pid: process.pid,
157
- sess: event.session_id || process.env.CLAUDE_SESSION_ID || process.env.GM_SESSION_ID || '',
158
- cwd: process.cwd(),
159
- operation: 'bash',
160
- pattern,
161
- command_excerpt: String(command).slice(0, 200),
162
- via: 'pre-tool-use-hook',
163
- }) + '\\n');
164
- } catch (_) {}
165
- process.stdout.write(JSON.stringify({
166
- decision: 'block',
167
- reason: SPOOL_POLL_REASON,
168
- }));
169
- process.exit(2);
170
- });
171
- `;
172
- }
173
-
174
- function ensureSpoolPollGate(cwd) {
175
- try {
176
- const gmHooks = path.join(cwd, '.gm', 'hooks');
177
- fs.mkdirSync(gmHooks, { recursive: true });
178
- const gateScript = path.join(gmHooks, 'spool-poll-gate.js');
179
- const want = spoolPollGateScript();
180
- let need = true;
181
- let oldSha = '';
182
- let existed = false;
183
- try {
184
- const existing = fs.readFileSync(gateScript, 'utf8');
185
- existed = true;
186
- if (existing === want) need = false;
187
- else {
188
- try {
189
- const _crypto = require('crypto');
190
- oldSha = _crypto.createHash('sha256').update(existing).digest('hex').slice(0, 12);
191
- } catch (_) {}
192
- }
193
- } catch (_) {}
194
- if (need) {
195
- fs.writeFileSync(gateScript, want);
196
- try {
197
- const _crypto = require('crypto');
198
- const newSha = _crypto.createHash('sha256').update(want).digest('hex').slice(0, 12);
199
- try {
200
- logEvent('bootstrap', existed ? 'gate.refreshed' : 'gate.installed', {
201
- cwd,
202
- path: gateScript,
203
- old_sha: oldSha || null,
204
- new_sha: newSha,
205
- bytes: Buffer.byteLength(want, 'utf8'),
206
- });
207
- } catch (_) {}
208
- } catch (_) {}
209
- }
210
-
211
- const claudeDir = path.join(cwd, '.claude');
212
- fs.mkdirSync(claudeDir, { recursive: true });
213
- const settingsPath = path.join(claudeDir, 'settings.json');
214
- let settings = {};
215
- try {
216
- const rawSettings = fs.readFileSync(settingsPath, 'utf8');
217
- settings = JSON.parse(rawSettings || '{}');
218
- } catch (_) { settings = {}; }
219
- if (!settings.hooks || typeof settings.hooks !== 'object') settings.hooks = {};
220
- if (!Array.isArray(settings.hooks.PreToolUse)) settings.hooks.PreToolUse = [];
221
- const wantCommand = `node "\${CLAUDE_PROJECT_DIR}/.gm/hooks/spool-poll-gate.js"`;
222
- for (const matcher of ['Bash', 'Write', 'Edit', 'MultiEdit']) {
223
- let entry = settings.hooks.PreToolUse.find(e => e && e.matcher === matcher);
224
- if (!entry) {
225
- entry = { matcher, hooks: [] };
226
- settings.hooks.PreToolUse.push(entry);
227
- }
228
- if (!Array.isArray(entry.hooks)) entry.hooks = [];
229
- const already = entry.hooks.some(h => h && typeof h.command === 'string' && h.command.includes('spool-poll-gate.js'));
230
- if (!already) {
231
- entry.hooks.push({ type: 'command', command: wantCommand });
232
- }
233
- }
234
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
235
- } catch (_) {}
236
- }
237
30
 
238
31
  function applyDisciplineSigil(rawBody) {
239
32
  let parsed;
@@ -1602,7 +1395,6 @@ async function runSpoolWatcher(instance, spoolDir) {
1602
1395
  fs.mkdirSync(inDir, { recursive: true });
1603
1396
  fs.mkdirSync(outDir, { recursive: true });
1604
1397
 
1605
- try { ensureSpoolPollGate(process.env.CLAUDE_PROJECT_DIR || process.cwd()); } catch (_) {}
1606
1398
 
1607
1399
  const LOCK_PATH = path.join(spoolDir, '.watcher.lock');
1608
1400
  let _ownWrapperSha12 = '';
@@ -2388,7 +2180,6 @@ async function tryInstantiate(wasmPath) {
2388
2180
 
2389
2181
  if (args[0] === 'spool') {
2390
2182
  const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
2391
- ensureSpoolPollGate(projectDir);
2392
2183
  const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
2393
2184
  await runSpoolWatcher(instance, spoolDir);
2394
2185
  } else if (args[0] === 'dispatch') {
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1256",
3
+ "version": "2.0.1258",
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.464"
20
+ "plugkitVersion": "0.1.465"
21
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1256",
3
+ "version": "2.0.1258",
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.1256"
42
+ "gm-plugkit": "^2.0.1258"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=16.0.0"
@@ -1,54 +0,0 @@
1
- #!/usr/bin/env node
2
- const { isSpoolPollCommand, SPOOL_POLL_REASON, logDeviation, recordBrowserEdit, isBrowserRunningFile } = require('./spool-dispatch.js');
3
-
4
- let raw = '';
5
- process.stdin.setEncoding('utf8');
6
- process.stdin.on('data', (chunk) => { raw += chunk; });
7
- process.stdin.on('end', () => {
8
- let event = {};
9
- try { event = JSON.parse(raw || '{}'); } catch (_) { event = {}; }
10
- const tool = event.tool_name || event.tool || '';
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
-
30
- if (tool !== 'Bash') {
31
- process.stdout.write(JSON.stringify({ continue: true }));
32
- process.exit(0);
33
- }
34
- const command = input.command || input.cmd || '';
35
- const pattern = isSpoolPollCommand(command);
36
- if (!pattern) {
37
- process.stdout.write(JSON.stringify({ continue: true }));
38
- process.exit(0);
39
- }
40
- try {
41
- logDeviation('deviation.spool-poll', {
42
- operation: 'bash',
43
- pattern,
44
- command_excerpt: String(command).slice(0, 200),
45
- via: 'pre-tool-use-hook',
46
- sess: event.session_id || process.env.CLAUDE_SESSION_ID || process.env.GM_SESSION_ID || '',
47
- });
48
- } catch (_) {}
49
- process.stdout.write(JSON.stringify({
50
- decision: 'block',
51
- reason: SPOOL_POLL_REASON,
52
- }));
53
- process.exit(2);
54
- });