gm-skill 2.0.1349 → 2.0.1352
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 -0
- package/bin/gm-shell-validate.js +119 -0
- package/bin/plugkit.version +1 -1
- package/bin/plugkit.wasm.sha256 +1 -1
- package/gm-plugkit/package.json +1 -1
- package/gm.json +2 -2
- package/package.json +1 -1
- package/skills/gm-skill/SKILL.md +2 -0
package/AGENTS.md
CHANGED
|
@@ -90,6 +90,8 @@ Every possible skill's `allowed-tools:` frontmatter is reduced to `Skill, Read,
|
|
|
90
90
|
|
|
91
91
|
**Done is plugkit's pronouncement, never the agent's claim**: the chain is COMPLETE only when `transition to=COMPLETE` returns COMPLETE phase and plugkit's on-disk state file reflects it. "I think we're done" is not done. "The user seems satisfied" is not done. The COMPLETE gate (gates.rs) is the single arbiter — it refuses on PRD-open, mutables-unresolved, dirty worktree, or missing residual-scan marker. The agent's job is to drive the chain into a state where the gate allows, then dispatch the verb, then read the response. Every possible alternative is narration.
|
|
92
92
|
|
|
93
|
+
**Every residual is triaged this turn, never named-and-deferred**: every entry in `git status --porcelain` at residual-scan or COMPLETE-attempt is the agent's responsibility to triage now. Triage = decide one of (a) commit (real session work or real upstream artifact landed in this tree), (b) add to the managed gitignore block (transient runtime emission, e.g., `.gm/witness/`, `.gm/exec-spool/.*-stale.json`), (c) revert (accidental or stale junk). The label "pre-existing residual" is NOT a stop excuse — it is the *outcome* of triage decision (a) when work is real, or (c) when junk. Name-and-stop with `blockedBy: external` is admissible *only* when triage requires external authority that this session cannot reach (another team's repo, hardware credential, owner-only decision visible to no one in-process). For local-tree residuals the agent always has authority and triages this turn; declaring "pre-existing, can't touch" on local files is the canonical drift mechanism that wedges the chain at VERIFY. Disciplines (`.gm/disciplines/`) are tracked, never ignored — new memorize-fire mem-*.json files are committed, not gitignored. The managed gitignore block (between `# >>> plugkit managed` markers) carries only runtime artifacts that have no future read value (witness JSON, transient staleness markers, lock files).
|
|
94
|
+
|
|
93
95
|
**Always seek the next state transition**: if the chain is not COMPLETE, there is a next move. Idle mid-chain is a deviation. The agent who finishes a verb and stops without dispatching the next instruction has stopped walking the chain. `phase-status` tells you where you are; `instruction` tells you what's next. There is no "I'll wait for the user" mid-chain — the user authorized closure at request time, not phase-by-phase.
|
|
94
96
|
|
|
95
97
|
**Return to plugkit on every possible drift**: `instruction` is the recovery primitive. Against every possible stall, gate-denial, unexpected error, or moment of uncertainty about the next step, the response is always to dispatch `instruction` and read the prose — never to improvise. The verb is synchronous, cheap, idempotent; over-dispatching it has no cost, under-dispatching it is the canonical drift mechanism. A session that goes >N tool calls without an instruction dispatch in a non-trivial phase is hallucinating its own chain. Every possible gate denial names the next verb in its `reason` field — the agent reads the field and dispatches the named verb, never argues around the denial.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const cp = require('child_process');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
const ROOT = process.cwd();
|
|
10
|
+
const WITNESS_DIR = path.join(ROOT, '.gm', 'witness');
|
|
11
|
+
|
|
12
|
+
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
13
|
+
function rmrf(p) { try { fs.rmSync(p, { recursive: true, force: true }); } catch (_) {} }
|
|
14
|
+
function write(file, text) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, text); }
|
|
15
|
+
function which(cmds) {
|
|
16
|
+
for (const cmd of cmds) {
|
|
17
|
+
try {
|
|
18
|
+
const out = cp.execFileSync('where.exe', [cmd], { encoding: 'utf8', windowsHide: true }).split(/\r?\n/).filter(Boolean);
|
|
19
|
+
if (out[0]) return out[0];
|
|
20
|
+
} catch (_) {}
|
|
21
|
+
}
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function renderPreview() {
|
|
26
|
+
const preview = fs.mkdtempSync(path.join(os.tmpdir(), 'gm-shell-preview-'));
|
|
27
|
+
fs.mkdirSync(path.join(preview, 'vendor'), { recursive: true });
|
|
28
|
+
cp.execSync(`powershell.exe -NoProfile -NonInteractive -Command "Copy-Item -Recurse -Force '${path.join(ROOT, 'site', 'vendor', '*')}' '${path.join(preview, 'vendor')}'"`, { stdio: 'ignore', windowsHide: true });
|
|
29
|
+
|
|
30
|
+
const renderScript = `
|
|
31
|
+
import { writeFileSync } from 'fs';
|
|
32
|
+
import { resolve } from 'path';
|
|
33
|
+
const mod = await import('file:///C:/dev/gm/site/theme.mjs');
|
|
34
|
+
const ctx = {
|
|
35
|
+
readGlobal: (k) => {
|
|
36
|
+
if (k === 'site') return { title: 'gm', tagline: "more coushin' for the pushin'", description: 'local browser OS surface', glyph: '◆', accent_from: '#7ee787', accent_to: '#56d364' };
|
|
37
|
+
if (k === 'navigation') return { links: [{ label: 'Home', href: '/' }, { label: 'Paper', href: '/paper/' }, { label: 'Stats', href: '/stats/' }, { label: 'Crates', href: '/crates/' }, { label: 'Skills', href: '/skills/' }] };
|
|
38
|
+
return null;
|
|
39
|
+
},
|
|
40
|
+
read: (k) => {
|
|
41
|
+
if (k === 'pages') return { docs: [{ id: 'home', title: 'gm', layout: 'landing', hero: { heading: 'gm', body: 'local browser OS surface', subheading: 'predictable panes and local vendors', ctas: [{ label: 'Open docs', href: '/paper/', primary: true }], badges: [{ label: 'local' }, { label: 'xstate' }] }, features: { heading: 'features', items: [{ name: 'A', desc: 'a' }, { name: 'B', desc: 'b' }, { name: 'C', desc: 'c' }, { name: 'D', desc: 'd' }] }, examples: { heading: 'docs', items: [{ name: 'Doc', desc: 'd', href: '/paper/', cta: 'open' }] }, quickstart: { lines: [{ kind: 'cmd', text: 'npm run build' }, { kind: 'cmd', text: 'npm start' }] } }] };
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const out = await mod.default.render(ctx);
|
|
46
|
+
writeFileSync(resolve('${preview.replace(/\\/g, '\\\\')}', 'index.html'), out[0].html);
|
|
47
|
+
`;
|
|
48
|
+
const tmp = path.join(os.tmpdir(), `gm-shell-render-${Date.now()}.mjs`);
|
|
49
|
+
fs.writeFileSync(tmp, renderScript);
|
|
50
|
+
cp.execFileSync('node', [tmp], { stdio: 'inherit', windowsHide: true });
|
|
51
|
+
try { fs.unlinkSync(tmp); } catch (_) {}
|
|
52
|
+
|
|
53
|
+
const server = cp.spawn('python', ['-m', 'http.server', '4210', '--directory', preview], { cwd: ROOT, detached: true, stdio: 'ignore', windowsHide: true });
|
|
54
|
+
server.unref();
|
|
55
|
+
await sleep(1500);
|
|
56
|
+
return { preview, port: 4210 };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function main() {
|
|
60
|
+
const { preview, port } = await renderPreview();
|
|
61
|
+
const witness = path.join(os.tmpdir(), `gm-shell-witness-${Date.now()}.js`);
|
|
62
|
+
const witnessOut = path.join(WITNESS_DIR, `gm-shell-${Date.now()}.json`);
|
|
63
|
+
write(witness, `
|
|
64
|
+
await page.goto('http://127.0.0.1:${port}/');
|
|
65
|
+
await page.waitForLoadState('domcontentloaded');
|
|
66
|
+
await page.waitForTimeout(500);
|
|
67
|
+
const before = await page.evaluate(() => ({
|
|
68
|
+
app: window.__debug.gm.app(),
|
|
69
|
+
activeSurface: document.querySelector('[data-app-surface]:not([hidden])')?.dataset.appSurface || null,
|
|
70
|
+
activeAppCopy: document.getElementById('active-app-copy')?.textContent || ''
|
|
71
|
+
}));
|
|
72
|
+
await page.evaluate(() => { const btn = document.querySelectorAll('[data-app]')[1]; if (btn) btn.click(); });
|
|
73
|
+
await page.waitForTimeout(300);
|
|
74
|
+
const after = await page.evaluate(() => ({
|
|
75
|
+
app: window.__debug.gm.app(),
|
|
76
|
+
activeSurface: document.querySelector('[data-app-surface]:not([hidden])')?.dataset.appSurface || null,
|
|
77
|
+
activeAppCopy: document.getElementById('active-app-copy')?.textContent || ''
|
|
78
|
+
}));
|
|
79
|
+
const result = { before, after };
|
|
80
|
+
return JSON.stringify(result);
|
|
81
|
+
`);
|
|
82
|
+
|
|
83
|
+
const script = fs.readFileSync(witness, 'utf8');
|
|
84
|
+
const relayUrl = 'http://127.0.0.1:19988/cli/execute';
|
|
85
|
+
const response = await fetch(relayUrl, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: { 'Content-Type': 'application/json' },
|
|
88
|
+
body: JSON.stringify({ sessionId: '9', code: script, timeout: 60000, cwd: ROOT }),
|
|
89
|
+
});
|
|
90
|
+
const result = await response.json();
|
|
91
|
+
const out = result.text || '';
|
|
92
|
+
console.log(out.trim());
|
|
93
|
+
if (!response.ok || result.isError) {
|
|
94
|
+
throw new Error(out || `browser witness failed (${response.status})`);
|
|
95
|
+
}
|
|
96
|
+
fs.mkdirSync(WITNESS_DIR, { recursive: true });
|
|
97
|
+
let parsed = null;
|
|
98
|
+
const m = out.match(/\[return value\]\s*(\{[\s\S]*\})\s*$/);
|
|
99
|
+
if (m) {
|
|
100
|
+
try { parsed = JSON.parse(m[0]); } catch (_) {}
|
|
101
|
+
if (!parsed) {
|
|
102
|
+
try { parsed = JSON.parse(m[1]); } catch (_) {}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!parsed) {
|
|
106
|
+
try {
|
|
107
|
+
const raw = fs.readFileSync(witnessOut, 'utf8');
|
|
108
|
+
parsed = JSON.parse(raw);
|
|
109
|
+
} catch (_) {}
|
|
110
|
+
}
|
|
111
|
+
fs.writeFileSync(witnessOut, JSON.stringify({ preview, port, output: out.trim(), parsed }, null, 2));
|
|
112
|
+
try { fs.unlinkSync(witness); } catch (_) {}
|
|
113
|
+
rmrf(preview);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
main().catch((e) => {
|
|
117
|
+
console.error(e && e.stack || String(e));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
});
|
package/bin/plugkit.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.510
|
package/bin/plugkit.wasm.sha256
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
b94cb1a78578e3293b1f674a7ebd6861890fd4ffa765a4aa582d5b20d4b9bc95 plugkit.wasm
|
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.1352",
|
|
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.
|
|
3
|
+
"version": "2.0.1352",
|
|
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.510"
|
|
21
21
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1352",
|
|
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/SKILL.md
CHANGED
|
@@ -48,6 +48,8 @@ First turn body must be `{"prompt":"<user request>"}` so orient_nouns and recall
|
|
|
48
48
|
|
|
49
49
|
The chain is not COMPLETE until your changes are on origin. Commit and push at the end of every possible session that touched tracked files — you do not ask the user whether to push, you dispatch it. Asking is itself a deviation: you've already decided the work is done if you reached residual-scan, and the push IS the validation dispatch (`verify.rs`). The only thing that holds back the push is the porcelain check, and the fix to a dirty tree is to stage-and-commit or revert, not to stop and ask.
|
|
50
50
|
|
|
51
|
+
**Every residual is triaged this turn — "pre-existing" is not a stop excuse.** When `git status --porcelain` is non-empty, every entry is your responsibility right now: commit (real work), ignore via the managed block (transient runtime emission), or revert (stale junk). Labeling a residual "pre-existing" only NAMES the triage outcome — it does not authorize stopping. `blockedBy: external` is admissible only when triage genuinely requires authority outside this session (another team's repo, hardware credential). For files visible in your local tree, you decide this turn. Disciplines (`.gm/disciplines/`) are tracked, not ignored; new memorize-fire JSON files get committed. Witness emissions (`.gm/witness/`) and transient staleness markers belong in the managed gitignore block.
|
|
52
|
+
|
|
51
53
|
`git push` is admissible only when `git status --porcelain` reads empty, and the porcelain probe must be its own Bash **tool-use event** before the push — a separate `Bash(...)` invocation, not a `&&`-chained command within one Bash call. ccsniff `--git-discipline` scans the last 20 Bash tool-use events (not shell commands within those events) for an explicit `git status --porcelain` (or `-s`); putting `add && commit && push` into one Bash call counts as one event with no porcelain witness, even though `git push` is technically the third shell command. The witness lives in the tool-call stream, not the shell stream. The discipline is **three Bash tool-use events** visible in the transcript: `Bash(git status --porcelain)` → read empty → `Bash(git push)`. You dispatch the `git_push` verb (not raw Bash) when possible — it gates on the porcelain probe internally, refuses dirty, and emits `deviation.push-dirty`. A raw `git push` Bash event without a preceding porcelain-probe Bash event is itself a deviation, regardless of how clean the worktree is by construction. Witness clean via `git_status`; witness pushed-to-remote via `branch_status` (ahead==0). The residual-scan and COMPLETE gate both refuse a dirty tree or a missing residual-check marker.
|
|
52
54
|
|
|
53
55
|
Response body is not a mutation surface. Memory writes route through `memorize-fire` only — another verb YOU dispatch. **Never** write persistent memory to platform-specific paths (`~/.claude/projects/*/memory/`, `~/.codex/memory/`, `~/.cursor/*`, etc.) — those don't transport between agent platforms and break the moment a session runs under a different harness. The only two portable surfaces are (a) dispatched `memorize-fire` (which writes through plugkit to the rs-learn store that travels with the project) and (b) `AGENTS.md` for project-tracked rules. If you reach for a `Write` tool on a memory directory under `~/`, stop — that's the lock-in anti-pattern.
|