gm-skill 2.0.1115 → 2.0.1117
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 +16 -7
- package/gm.json +2 -2
- package/lib/skill-bootstrap.js +26 -3
- package/package.json +2 -2
- package/skills/gm-skill/SKILL.md +62 -10
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ npx gm-skill-bootstrap
|
|
|
28
28
|
|
|
29
29
|
## Version
|
|
30
30
|
|
|
31
|
-
`2.0.
|
|
31
|
+
`2.0.1117` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` republishes this package alongside all 15 platform packages.
|
|
32
32
|
|
|
33
33
|
## Source of truth
|
|
34
34
|
|
package/bin/plugkit.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.394
|
package/bin/plugkit.wasm
CHANGED
|
Binary file
|
package/bin/plugkit.wasm.sha256
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
c682ec59373cc9cc7962d438c6f189844dc8b1998d53c7da14cddc49662b37f4 plugkit.wasm
|
|
@@ -660,10 +660,14 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
660
660
|
const dir = path.dirname(relPath);
|
|
661
661
|
const verb = dir === '.' ? path.basename(filePath, path.extname(filePath)) : dir;
|
|
662
662
|
const body = content.trim() || '{}';
|
|
663
|
+
const taskBase = path.basename(filePath, path.extname(filePath));
|
|
663
664
|
|
|
664
665
|
const verbBytes = new TextEncoder().encode(verb);
|
|
665
666
|
const bodyBytes = new TextEncoder().encode(body);
|
|
666
667
|
|
|
668
|
+
const t0 = Date.now();
|
|
669
|
+
console.log(`[dispatch] → verb=${verb} task=${taskBase} body=${bodyBytes.length}b`);
|
|
670
|
+
|
|
667
671
|
const verbPtr = instance.exports.plugkit_alloc(verbBytes.length);
|
|
668
672
|
const bodyPtr = instance.exports.plugkit_alloc(bodyBytes.length);
|
|
669
673
|
new Uint8Array(instance.exports.memory.buffer, verbPtr, verbBytes.length).set(verbBytes);
|
|
@@ -676,9 +680,9 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
676
680
|
const resultBytes = new Uint8Array(instance.exports.memory.buffer, ptr, len);
|
|
677
681
|
const resultStr = new TextDecoder().decode(resultBytes);
|
|
678
682
|
|
|
679
|
-
const taskBase = path.basename(filePath, path.extname(filePath));
|
|
680
683
|
const outName = dir === '.' ? `${taskBase}.json` : `${verb}-${taskBase}.json`;
|
|
681
684
|
fs.writeFileSync(path.join(outDir, outName), resultStr);
|
|
685
|
+
console.log(`[dispatch] ← verb=${verb} task=${taskBase} ms=${Date.now() - t0} out=${resultStr.length}b`);
|
|
682
686
|
|
|
683
687
|
try { instance.exports.plugkit_free(verbPtr, verbBytes.length); } catch (_) {}
|
|
684
688
|
try { instance.exports.plugkit_free(bodyPtr, bodyBytes.length); } catch (_) {}
|
|
@@ -742,19 +746,22 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
742
746
|
setInterval(() => {
|
|
743
747
|
try {
|
|
744
748
|
const cutoff = Date.now() - 3600_000;
|
|
749
|
+
let swept = 0;
|
|
745
750
|
for (const entry of fs.readdirSync(outDir)) {
|
|
746
751
|
try {
|
|
747
752
|
const fp = path.join(outDir, entry);
|
|
748
753
|
const s = fs.statSync(fp);
|
|
749
|
-
if (s.mtimeMs < cutoff) fs.unlinkSync(fp);
|
|
750
|
-
} catch (
|
|
754
|
+
if (s.mtimeMs < cutoff) { fs.unlinkSync(fp); swept++; }
|
|
755
|
+
} catch (e) { console.error(`[retention] failed to sweep ${entry}: ${e.message}`); }
|
|
751
756
|
}
|
|
752
|
-
|
|
757
|
+
if (swept > 0) console.log(`[retention] swept ${swept} out/ files older than 1h`);
|
|
758
|
+
} catch (e) { console.error(`[retention] sweep error: ${e.message}`); }
|
|
753
759
|
}, 60_000);
|
|
754
760
|
|
|
755
761
|
setInterval(() => {
|
|
756
762
|
try {
|
|
757
763
|
const cutoff = Date.now() - 600_000;
|
|
764
|
+
let stale = 0;
|
|
758
765
|
const walk = (dir) => {
|
|
759
766
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
760
767
|
const fp = path.join(dir, entry.name);
|
|
@@ -768,14 +775,16 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
768
775
|
const outName = verbDir === '.' ? `${base}.json` : `${verbDir}-${base}.json`;
|
|
769
776
|
try {
|
|
770
777
|
fs.writeFileSync(path.join(outDir, outName), JSON.stringify({ ok: false, error: 'stale input — never dispatched or watcher crash mid-flight' }));
|
|
771
|
-
} catch (
|
|
772
|
-
try { fs.unlinkSync(fp); } catch (
|
|
778
|
+
} catch (e) { console.error(`[stale-sweep] failed to write error for ${rel}: ${e.message}`); }
|
|
779
|
+
try { fs.unlinkSync(fp); stale++; } catch (e) { console.error(`[stale-sweep] failed to unlink ${rel}: ${e.message}`); }
|
|
780
|
+
console.error(`[stale-sweep] auto-failed ${rel} (age >${600}s)`);
|
|
773
781
|
}
|
|
774
782
|
}
|
|
775
783
|
}
|
|
776
784
|
};
|
|
777
785
|
walk(inDir);
|
|
778
|
-
|
|
786
|
+
if (stale > 0) console.log(`[stale-sweep] failed ${stale} orphaned inputs`);
|
|
787
|
+
} catch (e) { console.error(`[stale-sweep] sweep error: ${e.message}`); }
|
|
779
788
|
}, 300_000);
|
|
780
789
|
|
|
781
790
|
const existing = walkDir(inDir);
|
package/gm.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1117",
|
|
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.394"
|
|
21
21
|
}
|
package/lib/skill-bootstrap.js
CHANGED
|
@@ -232,6 +232,24 @@ async function verifyBinaryHealth(filePath) {
|
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
function openWatcherLog(projectDir) {
|
|
236
|
+
const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
|
|
237
|
+
fs.mkdirSync(spoolDir, { recursive: true });
|
|
238
|
+
const logPath = path.join(spoolDir, '.watcher.log');
|
|
239
|
+
try {
|
|
240
|
+
const stat = fs.statSync(logPath);
|
|
241
|
+
if (stat.size > 10 * 1024 * 1024) {
|
|
242
|
+
const rotated = path.join(spoolDir, '.watcher.log.1');
|
|
243
|
+
try { fs.unlinkSync(rotated); } catch (_) {}
|
|
244
|
+
fs.renameSync(logPath, rotated);
|
|
245
|
+
}
|
|
246
|
+
} catch (_) {}
|
|
247
|
+
const fd = fs.openSync(logPath, 'a');
|
|
248
|
+
const header = `\n--- watcher boot ${new Date().toISOString()} pid=${process.pid} ---\n`;
|
|
249
|
+
try { fs.writeSync(fd, header); } catch (_) {}
|
|
250
|
+
return fd;
|
|
251
|
+
}
|
|
252
|
+
|
|
235
253
|
async function spawnPlugkitWatcher(wasmPath) {
|
|
236
254
|
try {
|
|
237
255
|
emitBootstrapEvent('info', 'Spawning plugkit WASM watcher daemon');
|
|
@@ -249,18 +267,23 @@ async function spawnPlugkitWatcher(wasmPath) {
|
|
|
249
267
|
throw new Error(`WASM wrapper not found at ${wrapperPath}`);
|
|
250
268
|
}
|
|
251
269
|
|
|
270
|
+
const projectDir = process.cwd();
|
|
271
|
+
const logFd = openWatcherLog(projectDir);
|
|
272
|
+
|
|
252
273
|
const runtime = process.platform === 'win32' ? 'bun.exe' : 'bun';
|
|
253
274
|
const proc = spawn(runtime, [wrapperPath, 'spool'], {
|
|
254
275
|
detached: true,
|
|
255
|
-
stdio: 'ignore',
|
|
276
|
+
stdio: ['ignore', logFd, logFd],
|
|
256
277
|
windowsHide: true,
|
|
257
|
-
env: { ...process.env, CLAUDE_PROJECT_DIR:
|
|
278
|
+
env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir },
|
|
258
279
|
});
|
|
259
280
|
|
|
281
|
+
try { fs.closeSync(logFd); } catch (_) {}
|
|
282
|
+
|
|
260
283
|
const pid = proc.pid;
|
|
261
284
|
proc.unref();
|
|
262
285
|
|
|
263
|
-
emitBootstrapEvent('info', 'Plugkit WASM watcher spawned', { pid });
|
|
286
|
+
emitBootstrapEvent('info', 'Plugkit WASM watcher spawned', { pid, logPath: path.join(projectDir, '.gm', 'exec-spool', '.watcher.log') });
|
|
264
287
|
return pid;
|
|
265
288
|
} catch (e) {
|
|
266
289
|
emitBootstrapEvent('error', 'Failed to spawn plugkit WASM watcher', { error: e.message });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1117",
|
|
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.1117"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=16.0.0"
|
package/skills/gm-skill/SKILL.md
CHANGED
|
@@ -1,32 +1,86 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gm-skill
|
|
3
|
-
description: AI-native software engineering harness. plugkit
|
|
3
|
+
description: AI-native software engineering harness. plugkit owns all state and serves every instruction via the spool. The agent dispatches verbs; plugkit tracks phase, mutables, PRD, and recall.
|
|
4
4
|
allowed-tools: Skill, Read, Write, Bash(node *), Bash(bun *)
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# gm — single entry point
|
|
8
8
|
|
|
9
|
-
The wasm artifact lives at `~/.claude/gm-tools/plugkit.wasm`; the spool watcher runs it.
|
|
9
|
+
The wasm artifact lives at `~/.claude/gm-tools/plugkit.wasm`; the spool watcher runs it. The watcher's own stdout/stderr is appended to `.gm/exec-spool/.watcher.log` — Read it to see plugkit's internal trace, dispatch timings, sweep actions, errors.
|
|
10
10
|
|
|
11
11
|
## Boot the spool watcher (first turn only)
|
|
12
12
|
|
|
13
|
-
Check `.gm/exec-spool/.status.json`. If absent or `ts` > 15s old
|
|
13
|
+
Check `.gm/exec-spool/.status.json`. If absent or `ts` > 15s old:
|
|
14
14
|
|
|
15
15
|
`node ~/.claude/gm-tools/plugkit-wasm-wrapper.js spool > /dev/null 2>&1 &`
|
|
16
16
|
|
|
17
|
-
Wait 2 seconds, verify `.status.json` is fresh. Then proceed
|
|
17
|
+
Wait 2 seconds, verify `.status.json` is fresh. Then proceed.
|
|
18
18
|
|
|
19
19
|
## Dispatch ABI
|
|
20
20
|
|
|
21
|
-
Write request body to `.gm/exec-spool/in/<verb>/<N>.txt`. Read response from `.gm/exec-spool/out/<verb>-<N>.json`
|
|
21
|
+
Write request body to `.gm/exec-spool/in/<verb>/<N>.txt`. Read response from `.gm/exec-spool/out/<verb>-<N>.json` (nested verbs) or `out/<N>.json` (root verbs). Bodies are JSON, raw code, or a single phase name depending on the verb.
|
|
22
|
+
|
|
23
|
+
## Batch dispatch — never serial round-trips for independent verbs
|
|
24
|
+
|
|
25
|
+
The watcher processes verbs sequentially internally, but the agent's bottleneck is round-trip latency, not the watcher. **Write N inputs in one message via parallel Write tool calls, then read N outputs in one message via parallel Read calls.** A 5-verb batch is one agent turn, not five.
|
|
26
|
+
|
|
27
|
+
Example PLAN orient pack — 3 recalls + 3 codesearches in ONE message:
|
|
28
|
+
```
|
|
29
|
+
Write .gm/exec-spool/in/recall/1.txt body: {"query":"<noun A>"}
|
|
30
|
+
Write .gm/exec-spool/in/recall/2.txt body: {"query":"<noun B>"}
|
|
31
|
+
Write .gm/exec-spool/in/recall/3.txt body: {"query":"<noun C>"}
|
|
32
|
+
Write .gm/exec-spool/in/codesearch/1.txt body: {"query":"<phrase X>"}
|
|
33
|
+
Write .gm/exec-spool/in/codesearch/2.txt body: {"query":"<phrase Y>"}
|
|
34
|
+
Write .gm/exec-spool/in/codesearch/3.txt body: {"query":"<phrase Z>"}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then in the NEXT message, all 6 Reads in parallel.
|
|
38
|
+
|
|
39
|
+
For dependent verbs (transition after instruction, prd-resolve after work), the agent must serialize — but only at the dependency boundary, not across independent dispatches.
|
|
40
|
+
|
|
41
|
+
## State lives in plugkit, not in conversation context
|
|
42
|
+
|
|
43
|
+
Never Read `.gm/prd.yml` or `.gm/mutables.yml` directly. Every `instruction` response carries the data you need:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
{
|
|
47
|
+
phase, // current phase
|
|
48
|
+
instruction, // phase prose (the active discipline)
|
|
49
|
+
prd_items: [...], // full PRD items with id, subject, status, fields
|
|
50
|
+
prd_pending_count,
|
|
51
|
+
mutables_pending: [{id, claim, witness_method, witness_evidence, status}, ...],
|
|
52
|
+
recall_hits: [...], // auto-fired against phase + first pending PRD subject
|
|
53
|
+
next_phase_hint
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Plugkit observability — read .watcher.log
|
|
58
|
+
|
|
59
|
+
The watcher writes its own stdout + stderr (plus the wasm cdylib's `println!`/`eprintln!`) to `.gm/exec-spool/.watcher.log`. Useful when:
|
|
60
|
+
|
|
61
|
+
- A dispatch returned an error you don't understand → tail the log for the stack
|
|
62
|
+
- A verb seems slow → log shows `[dispatch] ← verb=X ms=N`
|
|
63
|
+
- Sweep cleaned up something → log shows `[retention]` or `[stale-sweep]` lines
|
|
64
|
+
- Watcher boot issues → `--- watcher boot ... ---` markers
|
|
65
|
+
|
|
66
|
+
Read with `offset` to tail:
|
|
67
|
+
```
|
|
68
|
+
Read .gm/exec-spool/.watcher.log offset=<last-known-line>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The log is rotated at 10MB (older content moves to `.watcher.log.1`).
|
|
22
72
|
|
|
23
73
|
## The loop
|
|
24
74
|
|
|
25
|
-
Dispatch `instruction`
|
|
75
|
+
Dispatch `instruction` with empty body to get current-phase guidance + full state snapshot. Follow the `instruction` prose imperatively. Add PRD items via `prd-add` (JSON body), resolve via `prd-resolve` (id as body). Add mutables via `mutable-add`, resolve via `mutable-resolve` once `witness_evidence` is filled. Every resolve auto-fires `memorize-fire` so the evidence becomes recall-able.
|
|
76
|
+
|
|
77
|
+
Resolve every entry in `mutables_pending` before transitioning. When the phase's exit condition is met, dispatch `transition` with the next phase name (or empty for auto-advance). Each transition response embeds `recall_hits` automatically — relevant prior memos surface without you asking.
|
|
78
|
+
|
|
79
|
+
Stop when `next_phase_hint` is null or phase is `COMPLETE`.
|
|
26
80
|
|
|
27
81
|
## Orchestrator verbs
|
|
28
82
|
|
|
29
|
-
`instruction`, `transition`, `phase-status`, `mutable-resolve`, `memorize-fire`, `residual-scan`, `auto-recall`.
|
|
83
|
+
`instruction`, `transition`, `phase-status`, `prd-add`, `prd-resolve`, `prd-list`, `mutable-add`, `mutable-resolve`, `mutable-list`, `memorize-fire`, `residual-scan`, `auto-recall`.
|
|
30
84
|
|
|
31
85
|
## Host verbs
|
|
32
86
|
|
|
@@ -42,6 +96,4 @@ Dispatch `.gm/exec-spool/in/browser/<N>.txt` with raw JavaScript as the body. Th
|
|
|
42
96
|
|
|
43
97
|
Special commands (body starts with `session `): `session new`, `session list`, `session close <id>` pass through to playwriter directly.
|
|
44
98
|
|
|
45
|
-
Chrome is detected from system install paths; profile dir is project-scoped so cookies/login persist per project.
|
|
46
|
-
|
|
47
|
-
Plugkit serves what prior skills (`gm:planning`, `gm:gm-execute`) used to serve, on demand, per phase. There is no other skill.
|
|
99
|
+
Chrome is detected from system install paths; profile dir is project-scoped so cookies/login persist per project. The wrapper auto-adds `.plugkit-browser-profile/` to `.gitignore`.
|