gm-skill 2.0.1588 → 2.0.1590
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/AGENTS.md +2 -2
- package/bin/bootstrap.js +0 -23
- package/bin/gm-validate.js +3 -3
- package/bin/plugkit.js +0 -4
- package/bin/plugkit.version +1 -1
- package/bin/plugkit.wasm.sha256 +1 -1
- package/gm-plugkit/instructions/browser.md +1 -1
- package/gm-plugkit/package.json +1 -1
- package/gm-plugkit/plugkit-wasm-wrapper.js +0 -2
- package/gm.json +2 -2
- package/lang/ssh.js +6 -1
- package/lib/browser-spool-handler.js +1 -1
- package/lib/skill-bootstrap.js +9 -5
- package/lib/spool-dispatch.js +19 -14
- package/lib/spool.js +8 -3
- package/package.json +1 -1
- package/skills/gm-skill/index.js +1 -12
package/AGENTS.md
CHANGED
|
@@ -56,9 +56,9 @@ Record only non-obvious technical caveats that cost multiple runs to discover; r
|
|
|
56
56
|
|
|
57
57
|
## Coding Style
|
|
58
58
|
|
|
59
|
-
**No comments in code** -- no inline, block, or JSDoc comments anywhere (source, generated output, hooks, scripts).
|
|
59
|
+
**No comments in code** -- no inline, block, or JSDoc comments anywhere (source, generated output, hooks, scripts). `test.js checkNoComments()` is the structural guard (fails on any leading `//` over tracked `.js/.mjs/.cjs`); one sighting spawns the full-tree sweep.
|
|
60
60
|
|
|
61
|
-
**No UTF-8 BOM in any tracked source file.** A leading `efbbbf` breaks `node`
|
|
61
|
+
**No UTF-8 BOM in any tracked source file.** A leading `efbbbf` breaks `node` and strict `JSON.parse`; cause is PowerShell's default UTF-16/UTF-8-BOM encoding -- always `-Encoding utf8` (no BOM) or the `Write` tool. `test.js checkNoBom()` is the structural guard; one sighting spawns the full-tree sweep. Incident + mechanics in rs-learn (`recall: BOM regression incident`).
|
|
62
62
|
|
|
63
63
|
**No graphical symbols; convert to industry-standard text on sight.** Decorative glyphs are forbidden in all output and source: arrows, box/geometric glyphs, stars, filled/hollow dots and bullets, checks/crosses, emojis, any non-ASCII decorative symbol. Convert on sight in the same turn (arrow -> `->`, bullet -> `-`/`*`, check/cross -> `[x]`/`[ ]` or done/todo/pass/fail, status dot -> the word). Tell-tale-AI class: one sighting spawns the full-codebase sweep, never a one-off edit. Exempt: functional code operators (`=>`, `??`, `?.`, comparison/math), frozen changelog/git-log entries, binary stores, intentional icon-font/CSS-content product glyphs. `ccsniff --glyph-discipline` flags decorative glyphs post-hoc (run each audit, like `--git-discipline`/`--search-discipline`).
|
|
64
64
|
|
package/bin/bootstrap.js
CHANGED
|
@@ -114,11 +114,6 @@ function ensureNextStepWiring(cwd) {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
// Resolve a bare command name to its actual .exe on Windows. cmd.exe + .cmd
|
|
118
|
-
// shim chains re-enter conhost (visible window flash) even with
|
|
119
|
-
// windowsHide:true on the parent. Spawning the real .exe directly lets
|
|
120
|
-
// CREATE_NO_WINDOW propagate. Falls back to .cmd or the original name when
|
|
121
|
-
// no .exe is found. See [[windows-spawn-cmd-shim-flash]].
|
|
122
117
|
function resolveWindowsExe(cmd) {
|
|
123
118
|
if (process.platform !== 'win32') return cmd;
|
|
124
119
|
try {
|
|
@@ -213,13 +208,6 @@ function gmToolsDir() {
|
|
|
213
208
|
return primary;
|
|
214
209
|
}
|
|
215
210
|
|
|
216
|
-
// Copy the freshly-resolved plugkit binary + its version+sha manifests to
|
|
217
|
-
// ~/.gm-tools (or ~/.claude/gm-tools for legacy installs) so hooks.json can
|
|
218
|
-
// invoke plugkit directly without going through node. Self-update inside the
|
|
219
|
-
// Rust binary keeps gm-tools fresh from
|
|
220
|
-
// here on. Skipped silently on any error -- the next session-start hook will
|
|
221
|
-
// retry via ensure_tools_current.
|
|
222
|
-
|
|
223
211
|
function copyWasmToGmTools(wasmPath, wrapperDir, version) {
|
|
224
212
|
const dst = gmToolsDir();
|
|
225
213
|
fs.mkdirSync(dst, { recursive: true });
|
|
@@ -584,17 +572,6 @@ function getWasmPath(opts) {
|
|
|
584
572
|
return null;
|
|
585
573
|
}
|
|
586
574
|
|
|
587
|
-
// ---------------------------------------------------------------------------
|
|
588
|
-
// Daemon kill on version change.
|
|
589
|
-
//
|
|
590
|
-
// The plugin tarball pins `plugkit.version`. When that pin advances and we
|
|
591
|
-
// install a newer cached binary, any long-running daemon (the runner) holds
|
|
592
|
-
// stale code and serves stale RPCs until killed. We track which version the
|
|
593
|
-
// daemon was last started under via `.daemon-version`; on every wrapper
|
|
594
|
-
// invocation, if the wrapper-pinned version differs, we kill the daemon so
|
|
595
|
-
// the next exec spawns it fresh under the new binary.
|
|
596
|
-
// ---------------------------------------------------------------------------
|
|
597
|
-
|
|
598
575
|
function daemonVersionSentinel() {
|
|
599
576
|
const root = (() => {
|
|
600
577
|
try { const r = cacheRoot(); ensureDir(r); return r; }
|
package/bin/gm-validate.js
CHANGED
|
@@ -226,8 +226,8 @@ async function validateBrowserEmbed() {
|
|
|
226
226
|
const pw = which('playwriter') || which('playwriter.cmd');
|
|
227
227
|
if (!pw) { v.skipped = true; v.errors.push('playwriter not found'); return v; }
|
|
228
228
|
|
|
229
|
-
const tbDir = '
|
|
230
|
-
if (!fs.existsSync(path.join(tbDir, 'docs'))) { v.skipped = true; v.errors.push('
|
|
229
|
+
const tbDir = path.resolve(__dirname, '..');
|
|
230
|
+
if (!fs.existsSync(path.join(tbDir, 'docs'))) { v.skipped = true; v.errors.push('repo docs/ missing -- cannot serve a fixture page'); return v; }
|
|
231
231
|
|
|
232
232
|
let serveProc = null;
|
|
233
233
|
const port = 3088;
|
|
@@ -250,7 +250,7 @@ async function validateBrowserEmbed() {
|
|
|
250
250
|
ready = true; break;
|
|
251
251
|
} catch (_) { await sleep(500); }
|
|
252
252
|
}
|
|
253
|
-
if (!ready) { v.errors.push('
|
|
253
|
+
if (!ready) { v.errors.push('docs serve never came up on ' + port); return v; }
|
|
254
254
|
|
|
255
255
|
let sessionId = '';
|
|
256
256
|
try {
|
package/bin/plugkit.js
CHANGED
|
@@ -38,7 +38,6 @@ function readExpectedSha() {
|
|
|
38
38
|
return null;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
// Returns true if gm-tools WASM matches pinned version by sha. Fast: no network.
|
|
42
41
|
function isReady() {
|
|
43
42
|
const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
|
|
44
43
|
const primaryWasm = path.join(home, '.gm-tools', 'plugkit.wasm');
|
|
@@ -51,9 +50,6 @@ function isReady() {
|
|
|
51
50
|
return actual && actual.toLowerCase() === expected;
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
// Synchronously run bootstrap.js in a child node. Blocks until install finishes
|
|
55
|
-
// (or fails). Bootstrap itself is cache-aware: re-download only when sha differs
|
|
56
|
-
// from manifest. Wraps stdio:inherit so the user sees progress.
|
|
57
53
|
function ensureReady(silent) {
|
|
58
54
|
if (isReady()) return true;
|
|
59
55
|
const bootstrap = path.join(wrapperDir, 'bootstrap.js');
|
package/bin/plugkit.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.673
|
package/bin/plugkit.wasm.sha256
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
d98bd5e7040372a6d0965337ebd831597675b66e30bca581456923e483f83a3d plugkit.wasm
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# BROWSER
|
|
2
2
|
|
|
3
|
-
## Hard Rule: Browser Witness Mandate
|
|
3
|
+
## Hard Rule: Browser Witness Mandate
|
|
4
4
|
|
|
5
5
|
**Every edit to code that runs in a browser requires a live `browser` dispatch in the same turn as the edit.** Client-side surfaces -- `.html`, `.js`, `.jsx`, `.ts`, `.tsx`, `.vue`, `.svelte`, `.mjs`, `.css`, web components, service workers, every asset loaded by `<script>`, every path reached by `import` from a browser-side entry -- must be witnessed by a live `page.evaluate` of the specific invariant the edit establishes. A passing node test, build, `curl` of the HTML, or static-analysis pass witnesses server delivery, not browser behavior, and is non-substitutive. The witness IS the proof; prose is not.
|
|
6
6
|
|
package/gm-plugkit/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1590",
|
|
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": {
|
|
@@ -2342,8 +2342,6 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2342
2342
|
lastActivityMs = Date.now();
|
|
2343
2343
|
}
|
|
2344
2344
|
|
|
2345
|
-
/* killPidQuiet, purgeProfileLockFiles, gracefulCloseBrowser are module-scope (defined above spool()). */
|
|
2346
|
-
|
|
2347
2345
|
function teardownAll(reason) {
|
|
2348
2346
|
try {
|
|
2349
2347
|
logEvent('plugkit', 'watcher.teardown', { reason, idle_ms: Date.now() - lastActivityMs });
|
package/gm.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1590",
|
|
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.673"
|
|
21
21
|
}
|
package/lang/ssh.js
CHANGED
|
@@ -83,7 +83,12 @@ function runSsh(target, cmd, onData) {
|
|
|
83
83
|
};
|
|
84
84
|
|
|
85
85
|
const timeout = setTimeout(() => {
|
|
86
|
-
if (!done) {
|
|
86
|
+
if (!done) {
|
|
87
|
+
done = true;
|
|
88
|
+
try { ssh.end(); } catch (_) {}
|
|
89
|
+
const partial = out.trimEnd();
|
|
90
|
+
resolve(partial ? `${partial}\n[ssh timed out after 55s; output above is partial]` : '[ssh timed out after 55s; no output]');
|
|
91
|
+
}
|
|
87
92
|
}, 55000);
|
|
88
93
|
|
|
89
94
|
ssh.on('ready', () => {
|
|
@@ -46,7 +46,7 @@ async function handleBrowserVerb(body, sessionId) {
|
|
|
46
46
|
const available = await isBrowserAvailable();
|
|
47
47
|
if (!available) {
|
|
48
48
|
throw new Error(
|
|
49
|
-
'Browser
|
|
49
|
+
'Browser unavailable: the plugkit health probe did not respond. Check the watcher is alive (.gm/exec-spool/.status.json ts within 15s); if dead, boot it with `bun x gm-plugkit@latest spool`.'
|
|
50
50
|
);
|
|
51
51
|
}
|
|
52
52
|
|
package/lib/skill-bootstrap.js
CHANGED
|
@@ -373,13 +373,13 @@ function findPlugkitWasmPids() {
|
|
|
373
373
|
try {
|
|
374
374
|
if (process.platform === 'win32') {
|
|
375
375
|
const ps = "Get-CimInstance Win32_Process -Filter \"Name='bun.exe' OR Name='node.exe'\" | Where-Object { $_.CommandLine -match 'plugkit-wasm-wrapper' } | Select-Object -ExpandProperty ProcessId";
|
|
376
|
-
const output = execFileSync('powershell', ['-NoProfile', '-Command', ps], { encoding: 'utf8', windowsHide: true });
|
|
376
|
+
const output = execFileSync('powershell', ['-NoProfile', '-Command', ps], { encoding: 'utf8', windowsHide: true, timeout: 3000 });
|
|
377
377
|
for (const line of output.split(/\r?\n/)) {
|
|
378
378
|
const trimmed = line.trim();
|
|
379
379
|
if (/^\d+$/.test(trimmed)) pids.push(trimmed);
|
|
380
380
|
}
|
|
381
381
|
} else {
|
|
382
|
-
const output = execSync("ps -eo pid,args", { encoding: 'utf8' });
|
|
382
|
+
const output = execSync("ps -eo pid,args", { encoding: 'utf8', timeout: 3000 });
|
|
383
383
|
const lines = output.split('\n').filter(Boolean);
|
|
384
384
|
for (const line of lines) {
|
|
385
385
|
if (!line.includes('plugkit-wasm-wrapper')) continue;
|
|
@@ -722,10 +722,14 @@ async function bootstrapPlugkit(sessionId, options) {
|
|
|
722
722
|
if (forceLatest) {
|
|
723
723
|
const latest = await getLatestRemoteVersion();
|
|
724
724
|
if (latest && latest.version) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
if (latest.
|
|
725
|
+
if (latest.version === manifest.version) {
|
|
726
|
+
expectedHash = latest.sha || expectedHash;
|
|
727
|
+
} else if (latest.sha) {
|
|
728
|
+
targetVersion = latest.version;
|
|
729
|
+
expectedHash = latest.sha;
|
|
728
730
|
emitBootstrapEvent('info', 'forceLatest: using newer remote version', { latest: latest.version, manifest: manifest.version });
|
|
731
|
+
} else {
|
|
732
|
+
emitBootstrapEvent('warn', 'forceLatest: no sha for newer version; staying on pinned manifest version', { latest: latest.version, manifest: manifest.version });
|
|
729
733
|
}
|
|
730
734
|
}
|
|
731
735
|
}
|
package/lib/spool-dispatch.js
CHANGED
|
@@ -173,19 +173,22 @@ function unsolicitedDocs(cwd) {
|
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
function yamlStatusValues(content) {
|
|
177
|
+
const values = [];
|
|
178
|
+
for (const raw of String(content).split(/\r?\n/)) {
|
|
179
|
+
const line = raw.replace(/#.*$/, '');
|
|
180
|
+
const m = line.match(/^\s*(?:-\s*)?status\s*:\s*([A-Za-z0-9_-]+)\s*$/);
|
|
181
|
+
if (m) values.push(m[1]);
|
|
182
|
+
}
|
|
183
|
+
return values;
|
|
184
|
+
}
|
|
185
|
+
|
|
176
186
|
function sessionMarkerPath(sessionId, kind) {
|
|
177
187
|
const cwd = process.cwd();
|
|
178
188
|
return path.join(cwd, '.gm', 'exec-spool', `.session-${kind}-${sessionId || 'anon'}`);
|
|
179
189
|
}
|
|
180
190
|
|
|
181
191
|
function hasDispatchedInstruction(sessionId) {
|
|
182
|
-
try {
|
|
183
|
-
const outDir = path.join(process.cwd(), '.gm', 'exec-spool', 'out');
|
|
184
|
-
if (!fs.existsSync(outDir)) return false;
|
|
185
|
-
for (const f of fs.readdirSync(outDir)) {
|
|
186
|
-
if (f.startsWith('instruction-')) return true;
|
|
187
|
-
}
|
|
188
|
-
} catch (_) {}
|
|
189
192
|
return fs.existsSync(sessionMarkerPath(sessionId, 'instruction-seen'));
|
|
190
193
|
}
|
|
191
194
|
|
|
@@ -318,16 +321,18 @@ function checkDispatchGates(sessionId, operation, extra) {
|
|
|
318
321
|
const residuals = [];
|
|
319
322
|
if (fs.existsSync(prdPath)) {
|
|
320
323
|
try {
|
|
321
|
-
const
|
|
322
|
-
if (
|
|
324
|
+
const statuses = yamlStatusValues(fs.readFileSync(prdPath, 'utf8'));
|
|
325
|
+
if (statuses.includes('pending') || statuses.includes('in_progress')) {
|
|
323
326
|
residuals.push('PRD has open items -- resolve or name-and-stop before declaring done');
|
|
324
327
|
}
|
|
325
|
-
} catch (
|
|
328
|
+
} catch (e) {
|
|
329
|
+
residuals.push(`prd.yml unreadable (${e.message}) -- cannot verify PRD state`);
|
|
330
|
+
}
|
|
326
331
|
}
|
|
327
332
|
if (fs.existsSync(mutsPath)) {
|
|
328
333
|
try {
|
|
329
|
-
const
|
|
330
|
-
if (
|
|
334
|
+
const statuses = yamlStatusValues(fs.readFileSync(mutsPath, 'utf8'));
|
|
335
|
+
if (statuses.includes('unknown')) {
|
|
331
336
|
residuals.push('unresolved mutables present -- resolve with witness_evidence before declaring done');
|
|
332
337
|
}
|
|
333
338
|
} catch (e) {
|
|
@@ -353,7 +358,7 @@ function checkDispatchGates(sessionId, operation, extra) {
|
|
|
353
358
|
if (browserEdits.length > 0 && !isBrowserWitnessed(cwd)) {
|
|
354
359
|
const files = browserEdits.map(e => e.file);
|
|
355
360
|
const shown = files.slice(0, 5).join(', ') + (files.length > 5 ? `, +${files.length - 5} more` : '');
|
|
356
|
-
residuals.push(`Browser Witness required: you edited ${shown} without dispatching the browser verb to witness the change in a live page.
|
|
361
|
+
residuals.push(`Browser Witness required: you edited ${shown} without dispatching the browser verb to witness the change in a live page. This is non-negotiable. Either dispatch browser to verify the edit works in-browser, or revert the changes.`);
|
|
357
362
|
logDeviation('deviation.browser-witness-missing', { files, operation });
|
|
358
363
|
} else if (browserEdits.length > 0 && isBrowserWitnessed(cwd)) {
|
|
359
364
|
const witness = readBrowserWitness(cwd) || {};
|
|
@@ -413,7 +418,7 @@ function checkDispatchGates(sessionId, operation, extra) {
|
|
|
413
418
|
logDeviation('deviation.commit-message-defer', { marker, operation });
|
|
414
419
|
return {
|
|
415
420
|
allowed: false,
|
|
416
|
-
reason: `commit message rejected: deferral phrase '${marker}' detected.
|
|
421
|
+
reason: `commit message rejected: deferral phrase '${marker}' detected. Defer markers force closure: either inline-fix and re-witness, or split the deferred work as a separate PRD item with blockedBy: [external] before committing. Rewrite the commit message and retry.`,
|
|
417
422
|
};
|
|
418
423
|
}
|
|
419
424
|
}
|
package/lib/spool.js
CHANGED
|
@@ -13,7 +13,10 @@ function generateTaskId() {
|
|
|
13
13
|
|
|
14
14
|
function validateLang(lang) {
|
|
15
15
|
const valid = ['nodejs', 'python', 'bash', 'typescript', 'go', 'rust', 'c', 'cpp', 'java', 'deno'];
|
|
16
|
-
|
|
16
|
+
if (!valid.includes(lang)) {
|
|
17
|
+
throw new Error(`unknown lang '${lang}'; valid: ${valid.join(', ')}`);
|
|
18
|
+
}
|
|
19
|
+
return lang;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
function getExtForLang(lang) {
|
|
@@ -33,8 +36,10 @@ function getExtForLang(lang) {
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
function validateVerb(verb) {
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
if (typeof verb !== 'string' || !/^[a-z][a-z0-9_-]*$/.test(verb)) {
|
|
40
|
+
throw new Error(`invalid verb '${verb}'; expected a kebab/snake-case identifier`);
|
|
41
|
+
}
|
|
42
|
+
return verb;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
function writeSpool(body, lang = 'nodejs', options = {}) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1590",
|
|
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",
|
package/skills/gm-skill/index.js
CHANGED
|
@@ -7,15 +7,4 @@ function loadCanonicalSkill() {
|
|
|
7
7
|
return fs.readFileSync(SKILL_MD_PATH, 'utf-8');
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
return `---
|
|
12
|
-
name: gm-${platformName}
|
|
13
|
-
description: AI-native software engineering via skill-driven orchestration on ${platformName}; bootstraps plugkit for task execution and session isolation
|
|
14
|
-
allowed-tools: Skill
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
See [gm-skill](../gm-skill/SKILL.md). All platforms share the same plugkit dispatch surface.
|
|
18
|
-
`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
module.exports = { loadCanonicalSkill, renderPlatformSkill, SKILL_MD_PATH };
|
|
10
|
+
module.exports = { loadCanonicalSkill, SKILL_MD_PATH };
|