gm-skill 2.0.1322 → 2.0.1323

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
@@ -33,7 +33,7 @@ This repo IS the published `gm-skill` npm package. No build step, no factory. Th
33
33
  ```
34
34
  gm/
35
35
  ├── skills/gm-skill/ ← the skill (SKILL.md + index.js, ~12 lines of prose)
36
- ├── bin/ ← bootstrap, plugkit launcher, gmsniff, ccsniff
36
+ ├── bin/ ← bootstrap + plugkit launcher (gmsniff / ccsniff are separate npm packages — `bun x gmsniff`, `bun x ccsniff`)
37
37
  ├── lib/ ← runtime: spool dispatch, skill bootstrap, daemon mgmt
38
38
  ├── agents/ ← subagent prompts (gm, memorize, research-worker, textprocessing)
39
39
  ├── prompts/ ← bash-deny, session-start, prompt-submit, pre-compact
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1322",
3
+ "version": "2.0.1323",
4
4
  "description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform binary, verifies SHA256, and starts the spool watcher daemon. Includes plugkit-wasm-wrapper for WASM-based spool watching.",
5
5
  "main": "index.js",
6
6
  "bin": {
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1322",
3
+ "version": "2.0.1323",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1322",
3
+ "version": "2.0.1323",
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",
@@ -23,9 +23,7 @@
23
23
  },
24
24
  "main": "bin/bootstrap.js",
25
25
  "bin": {
26
- "gm-skill-bootstrap": "./bin/bootstrap.js",
27
- "gmsniff": "./bin/gmsniff.js",
28
- "ccsniff": "./bin/ccsniff.js"
26
+ "gm-skill-bootstrap": "./bin/bootstrap.js"
29
27
  },
30
28
  "files": [
31
29
  "skills/",
package/bin/ccsniff.js DELETED
@@ -1,189 +0,0 @@
1
- #!/usr/bin/env node
2
- const fs = require('fs');
3
- const path = require('path');
4
- const os = require('os');
5
-
6
- const C = { r:'\x1b[31m', g:'\x1b[32m', y:'\x1b[33m', b:'\x1b[34m', m:'\x1b[35m', c:'\x1b[36m', d:'\x1b[2m', x:'\x1b[0m', bold:'\x1b[1m' };
7
- const useColor = process.stdout.isTTY;
8
- const col = (k, s) => useColor ? C[k] + s + C.x : s;
9
-
10
- function parseDuration(s) {
11
- if (!s) return null;
12
- const m = String(s).match(/^(\d+)([smhd])$/);
13
- if (!m) return null;
14
- return parseInt(m[1],10) * ({s:1000,m:60000,h:3600000,d:86400000}[m[2]]);
15
- }
16
-
17
- function parseArgs(argv) {
18
- const flags = new Set();
19
- let since = null, project = null;
20
- for (let i = 2; i < argv.length; i++) {
21
- const a = argv[i];
22
- if (a === '--since') { since = parseDuration(argv[++i]); continue; }
23
- if (a === '--project') { project = argv[++i]; continue; }
24
- if (a.startsWith('--')) flags.add(a.slice(2));
25
- }
26
- return { flags, since, project };
27
- }
28
-
29
- function encodeCwd(cwd) {
30
- return cwd.replace(/[\\/:]+/g, '-').replace(/^-+/, '');
31
- }
32
-
33
- function findTranscripts(project) {
34
- const root = path.join(os.homedir(), '.claude', 'projects');
35
- if (!fs.existsSync(root)) { console.log(col('d', `no log at ${root}`)); return []; }
36
- const out = [];
37
- const targetEnc = project ? encodeCwd(project) : null;
38
- for (const dir of fs.readdirSync(root)) {
39
- if (targetEnc && !dir.includes(targetEnc)) continue;
40
- const full = path.join(root, dir);
41
- if (!fs.statSync(full).isDirectory()) continue;
42
- for (const f of fs.readdirSync(full)) {
43
- if (f.endsWith('.jsonl')) out.push({ project: dir, file: path.join(full, f) });
44
- }
45
- }
46
- return out;
47
- }
48
-
49
- function* iterTurns(file) {
50
- let lines;
51
- try { lines = fs.readFileSync(file, 'utf8').split(/\r?\n/); }
52
- catch { return; }
53
- for (const line of lines) {
54
- if (!line) continue;
55
- try { yield JSON.parse(line); } catch {}
56
- }
57
- }
58
-
59
- function extractBashCommands(turn) {
60
- const out = [];
61
- const msg = turn.message;
62
- if (!msg || !Array.isArray(msg.content)) return out;
63
- for (const c of msg.content) {
64
- if (c.type === 'tool_use' && (c.name === 'Bash' || c.name === 'PowerShell')) {
65
- out.push({ tool: c.name, cmd: c.input?.command || '', id: c.id });
66
- }
67
- }
68
- return out;
69
- }
70
-
71
- function turnTs(turn) {
72
- return Date.parse(turn.timestamp || '') || 0;
73
- }
74
-
75
- function gitDisciplineScan(transcripts, cutoff) {
76
- const findings = [];
77
- for (const { project, file } of transcripts) {
78
- let lastCommitTs = null, lastCommitSess = null, sawPushAfterCommit = false;
79
- for (const turn of iterTurns(file)) {
80
- const ts = turnTs(turn);
81
- if (cutoff && ts && ts < cutoff) continue;
82
- const cmds = extractBashCommands(turn);
83
- for (const { tool, cmd } of cmds) {
84
- if (!cmd) continue;
85
- if (/\bgit\s+push\b/.test(cmd) && !/exec-spool|spool-dispatch|git_push/.test(cmd)) {
86
- findings.push({ kind: 'raw-push', project, file: path.basename(file), ts, tool, cmd: cmd.slice(0, 120), sess: turn.sessionId });
87
- sawPushAfterCommit = true;
88
- }
89
- if (/\bgit\s+commit\b/.test(cmd)) {
90
- if (lastCommitTs && !sawPushAfterCommit) {
91
- findings.push({ kind: 'commit-without-push', project, file: path.basename(file), ts: lastCommitTs, sess: lastCommitSess, cmd: '(prior commit had no push)' });
92
- }
93
- lastCommitTs = ts; lastCommitSess = turn.sessionId; sawPushAfterCommit = false;
94
- }
95
- if (/git\s+push.*--force\b/.test(cmd)) {
96
- findings.push({ kind: 'force-push', project, file: path.basename(file), ts, tool, cmd: cmd.slice(0,120), sess: turn.sessionId });
97
- }
98
- }
99
- }
100
- }
101
- return findings;
102
- }
103
-
104
- function collectPlugkitEvents(cutoff) {
105
- const roots = [
106
- path.join(os.homedir(), '.gm-log'),
107
- path.join(os.homedir(), '.claude', 'gm-log'),
108
- ].filter(r => fs.existsSync(r));
109
- const events = [];
110
- if (roots.length === 0) { console.log(col('d', `no gm-log at ~/.gm-log or ~/.claude/gm-log`)); return events; }
111
- for (const root of roots) {
112
- for (const date of fs.readdirSync(root)) {
113
- const pj = path.join(root, date, 'plugkit.jsonl');
114
- if (!fs.existsSync(pj)) continue;
115
- let lines; try { lines = fs.readFileSync(pj, 'utf8').split(/\r?\n/); } catch { continue; }
116
- for (const line of lines) {
117
- if (!line) continue;
118
- try {
119
- const ev = JSON.parse(line);
120
- const ts = Date.parse(ev.ts || '') || 0;
121
- if (cutoff && ts && ts < cutoff) continue;
122
- ev._ts = ts;
123
- events.push(ev);
124
- } catch {}
125
- }
126
- }
127
- }
128
- return events;
129
- }
130
-
131
- function learningXref(transcripts, cutoff) {
132
- const events = collectPlugkitEvents(cutoff);
133
- const bySess = new Map();
134
- for (const ev of events) {
135
- const s = ev.sess || '';
136
- if (!s) continue;
137
- if (!bySess.has(s)) bySess.set(s, []);
138
- bySess.get(s).push(ev);
139
- }
140
- const xrefs = [];
141
- for (const { project, file } of transcripts) {
142
- for (const turn of iterTurns(file)) {
143
- const sess = turn.sessionId;
144
- const ts = turnTs(turn);
145
- if (cutoff && ts && ts < cutoff) continue;
146
- if (!sess || !bySess.has(sess)) continue;
147
- const candidates = bySess.get(sess).filter(ev => Math.abs((ev._ts||0) - ts) < 60000);
148
- for (const ev of candidates) {
149
- xrefs.push({ project, sess, turnTs: ts, evTs: ev._ts, event: ev.event, verb: ev.verb, dur: ev.dur_ms });
150
- }
151
- }
152
- }
153
- return xrefs;
154
- }
155
-
156
- function fmtTs(ts) { return ts ? new Date(ts).toISOString().slice(0,19).replace('T',' ') : '----------'; }
157
-
158
- function main() {
159
- const { flags, since, project } = parseArgs(process.argv);
160
- const cutoff = since ? Date.now() - since : 0;
161
- const transcripts = findTranscripts(project);
162
- console.log(col('bold', `ccsniff — ${transcripts.length} transcript(s)${since ? `, since ${since/1000}s` : ''}${project ? `, project=${project}` : ''}`));
163
- console.log(col('d', '-'.repeat(80)));
164
-
165
- if (flags.has('git-discipline') || flags.size === 0) {
166
- const findings = gitDisciplineScan(transcripts, cutoff);
167
- console.log(col('bold', `git-discipline: ${findings.length} finding(s)`));
168
- const byKind = {};
169
- for (const f of findings) byKind[f.kind] = (byKind[f.kind]||0)+1;
170
- console.log(col('d', ' by kind: ') + Object.entries(byKind).map(([k,v]) => `${col('y',k)}=${v}`).join(' '));
171
- for (const f of findings.slice(-200)) {
172
- const tag = f.kind === 'raw-push' ? col('r','RAW-PUSH ')
173
- : f.kind === 'commit-without-push' ? col('y','COMMIT-NO-PUSH ')
174
- : col('r','FORCE-PUSH ');
175
- console.log(`${col('d', fmtTs(f.ts))} ${tag} ${col('m','['+String(f.sess||'').slice(0,8)+']')} ${col('c', f.project.slice(0,40))} ${f.cmd||''}`);
176
- }
177
- console.log();
178
- }
179
-
180
- if (flags.has('learning-xref')) {
181
- const xrefs = learningXref(transcripts, cutoff);
182
- console.log(col('bold', `learning-xref: ${xrefs.length} joined turn/event pair(s)`));
183
- for (const x of xrefs.slice(-200)) {
184
- console.log(`${col('d', fmtTs(x.turnTs))} ${col('m','['+x.sess.slice(0,8)+']')} ${col('c', x.event||'').padEnd(24)} verb=${x.verb||''} dur=${x.dur||''}ms proj=${x.project.slice(0,30)}`);
185
- }
186
- }
187
- }
188
-
189
- main();
package/bin/gmsniff.js DELETED
@@ -1,160 +0,0 @@
1
- #!/usr/bin/env node
2
- const fs = require('fs');
3
- const path = require('path');
4
- const os = require('os');
5
-
6
- const C = { r:'\x1b[31m', g:'\x1b[32m', y:'\x1b[33m', b:'\x1b[34m', m:'\x1b[35m', c:'\x1b[36m', d:'\x1b[2m', x:'\x1b[0m', bold:'\x1b[1m' };
7
- const useColor = process.stdout.isTTY;
8
- const col = (k, s) => useColor ? C[k] + s + C.x : s;
9
-
10
- function parseDuration(s) {
11
- if (!s) return null;
12
- const m = String(s).match(/^(\d+)([smhd])$/);
13
- if (!m) return null;
14
- const n = parseInt(m[1], 10);
15
- const mult = { s:1000, m:60000, h:3600000, d:86400000 }[m[2]];
16
- return n * mult;
17
- }
18
-
19
- function parseArgs(argv) {
20
- const flags = new Set();
21
- let since = null;
22
- let project = null;
23
- for (let i = 2; i < argv.length; i++) {
24
- const a = argv[i];
25
- if (a === '--since') { since = parseDuration(argv[++i]); continue; }
26
- if (a === '--project') { project = argv[++i]; continue; }
27
- if (a.startsWith('--')) flags.add(a.slice(2));
28
- }
29
- return { flags, since, project };
30
- }
31
-
32
- function findProjectRoot(start) {
33
- let dir = start;
34
- for (let i = 0; i < 12; i++) {
35
- if (fs.existsSync(path.join(dir, '.gm', 'exec-spool'))) return dir;
36
- const parent = path.dirname(dir);
37
- if (parent === dir) break;
38
- dir = parent;
39
- }
40
- return start;
41
- }
42
-
43
- function readLines(p) {
44
- if (!fs.existsSync(p)) { console.log(col('d', `no log at ${p}`)); return []; }
45
- try { return fs.readFileSync(p, 'utf8').split(/\r?\n/).filter(Boolean); }
46
- catch (e) { console.log(col('r', `read err ${p}: ${e.message}`)); return []; }
47
- }
48
-
49
- function collectEvents(sinceMs, projectOverride) {
50
- const events = [];
51
- const cutoff = sinceMs ? Date.now() - sinceMs : 0;
52
- const projectRoot = projectOverride || findProjectRoot(process.cwd());
53
- const wlog = path.join(projectRoot, '.gm', 'exec-spool', '.watcher.log');
54
- for (const line of readLines(wlog)) {
55
- const m = line.match(/evt:\s*(\{.*\})\s*$/);
56
- if (!m) continue;
57
- try {
58
- const ev = JSON.parse(m[1]);
59
- const ts = typeof ev.ts === 'number' ? ev.ts : Date.parse(ev.ts || '');
60
- if (ts && ts < cutoff) continue;
61
- ev._ts = ts || 0; ev._src = 'watcher.log';
62
- events.push(ev);
63
- } catch {}
64
- }
65
- const gmLogRoots = [
66
- path.join(os.homedir(), '.gm-log'),
67
- path.join(os.homedir(), '.claude', 'gm-log'),
68
- ];
69
- for (const gmLogRoot of gmLogRoots) {
70
- if (!fs.existsSync(gmLogRoot)) continue;
71
- for (const date of fs.readdirSync(gmLogRoot)) {
72
- const pj = path.join(gmLogRoot, date, 'plugkit.jsonl');
73
- if (!fs.existsSync(pj)) continue;
74
- for (const line of readLines(pj)) {
75
- try {
76
- const ev = JSON.parse(line);
77
- const ts = Date.parse(ev.ts || '') || ev.ts || 0;
78
- if (ts && ts < cutoff) continue;
79
- ev._ts = ts; ev._src = `gm-log/${date}/plugkit.jsonl`;
80
- events.push(ev);
81
- } catch {}
82
- }
83
- }
84
- }
85
- events.sort((a,b) => (a._ts||0) - (b._ts||0));
86
- return events;
87
- }
88
-
89
- function fmtTs(ts) {
90
- if (!ts) return '----------';
91
- return new Date(ts).toISOString().slice(11, 19);
92
- }
93
-
94
- function evName(ev) { return ev.event || ''; }
95
-
96
- const FILTERS = {
97
- 'embed-failures': ev => /^embed_(fail|init_fail|cached_fail|init_cached_fail)$/.test(evName(ev)),
98
- 'recall-misses': ev => evName(ev) === 'recall' && (ev.hits === 0 || (Array.isArray(ev.results) && ev.results.length === 0)),
99
- 'recall-scores': ev => evName(ev) === 'recall' || evName(ev) === 'recall_score_unavailable',
100
- 'classifier-rejects': ev => evName(ev) === 'memorize_reject',
101
- 'memory-leverage': ev => evName(ev) === 'recall' && (ev.hits > 0 || (Array.isArray(ev.results) && ev.results.length > 0)),
102
- 'recall-modes': ev => evName(ev) === 'recall' && ev.mode,
103
- 'table-drops': ev => evName(ev) === 'table_dropped',
104
- 'discipline-sigil-ignored': ev => evName(ev) === 'discipline_sigil_ignored',
105
- };
106
-
107
- function renderEvent(ev, kinds) {
108
- const t = col('d', fmtTs(ev._ts));
109
- const name = col('c', evName(ev).padEnd(28));
110
- const sess = ev.sess ? col('m', `[${String(ev.sess).slice(0,16)}]`) : '';
111
- const extras = [];
112
- if (kinds.has('embed-failures') && ev.step) extras.push(`step=${ev.step}`);
113
- if (ev.error) extras.push(col('r', `err=${String(ev.error).slice(0,80)}`));
114
- if (ev.reason) extras.push(`reason=${ev.reason}`);
115
- if (ev.text_prefix) extras.push(col('y', `text="${String(ev.text_prefix).slice(0,40)}"`));
116
- if (ev.namespace) extras.push(`ns=${ev.namespace}`);
117
- if (ev.mode) extras.push(col('g', `mode=${ev.mode}`));
118
- if (ev.derived_query) extras.push(`q="${String(ev.derived_query).slice(0,40)}"`);
119
- if (typeof ev.hits === 'number') extras.push(`hits=${ev.hits}`);
120
- if (typeof ev.score === 'number') extras.push(`score=${ev.score.toFixed(3)}`);
121
- if (ev.sigil) extras.push(col('y', `sigil=@${ev.sigil}`));
122
- if (ev.key && evName(ev) === 'table_dropped') extras.push(`key=${ev.key}`);
123
- return `${t} ${name} ${sess} ${extras.join(' ')}`;
124
- }
125
-
126
- function summarize(events) {
127
- const counts = {};
128
- for (const ev of events) {
129
- const n = evName(ev) || '(unknown)';
130
- counts[n] = (counts[n] || 0) + 1;
131
- }
132
- return counts;
133
- }
134
-
135
- function main() {
136
- const { flags, since, project } = parseArgs(process.argv);
137
- const allEvents = collectEvents(since, project);
138
- const activeFilters = [...flags].filter(f => FILTERS[f]);
139
- const useAll = activeFilters.length === 0;
140
- const kinds = new Set(activeFilters);
141
- const matched = useAll ? allEvents : allEvents.filter(ev => activeFilters.some(f => FILTERS[f](ev)));
142
-
143
- console.log(col('bold', `gmsniff — ${matched.length} event(s)${since ? ` in last ${since/1000}s` : ''}${activeFilters.length ? `, filters: ${activeFilters.join(',')}` : ' (all)'}`));
144
- const counts = summarize(matched);
145
- const summary = Object.entries(counts).sort((a,b) => b[1]-a[1]).map(([k,v]) => `${col('c',k)}=${v}`).join(' ');
146
- if (summary) console.log(col('d', 'by event: ') + summary);
147
- console.log(col('d', '-'.repeat(80)));
148
-
149
- if (kinds.has('recall-modes')) {
150
- const modeCounts = {};
151
- for (const ev of matched) if (ev.mode) modeCounts[ev.mode] = (modeCounts[ev.mode] || 0) + 1;
152
- console.log(col('bold','recall mode distribution:'));
153
- for (const [m,n] of Object.entries(modeCounts)) console.log(` ${col('g',m.padEnd(20))} ${n}`);
154
- console.log();
155
- }
156
-
157
- for (const ev of matched) console.log(renderEvent(ev, kinds));
158
- }
159
-
160
- main();