gm-plugkit 2.0.2466 → 2.0.2468
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/SKILL.md +6 -10
- package/bootstrap.js +91 -0
- package/cli.js +19 -4
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -6,21 +6,17 @@ allowed-tools: Skill, Read, Write, AskUserQuestion, Bash(bun *), Bash(npx *), Ba
|
|
|
6
6
|
|
|
7
7
|
# gm
|
|
8
8
|
|
|
9
|
-
**The conversation literally stops the instant a response contains no tool call -- this is the entire mechanism, not a guideline to remember.** Nothing external enforces continuation; the only thing that keeps this session alive turn after turn is that every single response contains a dispatch. The moment a response is prose alone -- a summary, a "here's what I did," a "Session N closes, ready to resume with /gm," a recap of architecture decisions made, a list of what's left for "next time" -- that response IS the final message of the conversation. There is no next turn where the user re-prompts and work resumes; from the model's own vantage point mid-generation, writing that closing summary is indistinguishable from ending the conversation permanently, because it has the exact same effect. So: never compose a closing/summary/"standing work ready" sentence as the last thing in a response -- if the work in front of you feels finished, the correct final action is still a dispatch (the next verb, or `Skill(skill="gm-continue")` if genuinely `phase=COMPLETE AND prd_pending_count=0`), never a sentence describing that it's finished. A turn-final sentence naming the next move instead of taking it is the same stop -- take the move, don't narrate it.
|
|
9
|
+
**The conversation literally stops the instant a response contains no tool call -- this is the entire mechanism, not a guideline to remember.** Nothing external enforces continuation; the only thing that keeps this session alive turn after turn is that every single response contains a dispatch. The moment a response is prose alone -- a summary, a "here's what I did," a "Session N closes, ready to resume with /gm," a recap of architecture decisions made, a list of what's left for "next time" -- that response IS the final message of the conversation. There is no next turn where the user re-prompts and work resumes; from the model's own vantage point mid-generation, writing that closing summary is indistinguishable from ending the conversation permanently, because it has the exact same effect. So: never compose a closing/summary/"standing work ready" sentence as the last thing in a response -- if the work in front of you feels finished, the correct final action is still a dispatch (the next verb, or `Skill(skill="gm-continue")` if genuinely `phase=COMPLETE AND prd_pending_count=0`), never a sentence describing that it's finished. A turn-final sentence naming the next move instead of taking it is the same stop -- take the move, don't narrate it. Standing approval already covers this turn's work (see below) -- always dispatch the named verb next till the workflow is complete. Decisions about scope expansions or `prd-add` (a discovered row) are approved.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**`gm` must end by calling `gm-continue` which dispatches the finishing procedure; there is no other exit.** At genuine `phase=COMPLETE AND prd_pending_count=0`, the only allowed next step is dispatching `Skill(skill="gm-continue")` as the literal last tool call of the turn -- (reaching COMPLETE earlier in a turn does not license a prose-only response afterward -- the Skill dispatch belongs in that same turn, immediately).
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
**plugkit provides our completion checks** If uncertain what's next, dispatch `phase-status`, read the phase, then keep walking -- "uncertain" is never grounds to stop.
|
|
14
14
|
|
|
15
|
-
**
|
|
15
|
+
**Standing approval: finish everything, max granularity.** Every session is approved through requested work -- our policy is to plan, every addition, every discovered/unfinished item. We add prds insted of deferring work. If a walk is long, SPECIFY pushes more rows and PROVE/EMIT keep walking them, session after session if needed, never pausing to ask which subset to attempt first. Decide to always: proceed with the full scope, never the partial one. PRD rows are in `.gm/prd.yml`; a long task means MORE rows pushed onto SPECIFY as work is discovered, never fewer turns spent working. An apparent external blocker (a crashing tool, a down service, a missing peice, another team's repo) is an indication to add more prds and expand scope, making the most sensible choices along the way about replacing the problematic content with something that works, retry/escalate/route around, open the cross-repo change. `blockedBy` may only transiently carry the concrete reach action forward while it is being built.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
The next action is determined solely by phase + PRD state, identically whether 500k tokens or 500 remain. If you catch yourself estimating/citing a remaining-token number for any reason, that estimate itself is the drift signal -- discard the framing entirely and re-derive the next action from `phase`/`prd_pending_count` alone. Real per-row witnessing (one `exec_js`/`browser` dispatch per row, actually reading its output) is mandatory regardless of how many rows remain or how much context is left; a batch `prd-resolve` for N rows justified by "given the budget" is `deviation.prd-resolve-duplicate-witness`-shaped even when each id is individually correct, because the underlying witnesses were never separately produced.
|
|
18
18
|
|
|
19
|
-
**
|
|
20
|
-
|
|
21
|
-
**No task is bounded; "out of scope" naming a real, reachable piece of work must never occur.** A task's actual scope is whatever its closure requires, not whatever fits some assumed limit -- when a row turns out bigger/harder/more multi-part than first estimated, the fix is fitting the bound to the task (more PRD rows, more turns, more sessions if genuinely needed), never fitting the task to an assumed bound by declaring part of it "out of scope," "future work," or "not yet implemented." A design doc describing what a reachable piece of work would look like, in place of doing that work, is the same deviation named above (documenting instead of implementing) wearing the "scoping" costume -- catch it the same way: if it's reachable this session, it is in scope by definition, full stop.
|
|
22
|
-
|
|
23
|
-
**A gate denied the same way 3+ times in a row is a stuck loop, not a retry target.** Retrying the identical `transition`/verb after an unchanged denial repeats the same failure -- plugkit's own gate response names this explicitly (`stuck-loop-escalation`) once it detects the repeat. On that signal, or on noticing it yourself: stop retrying bare, `prd-add` a row naming the concrete stuck state (what's blocking, what you tried, why it didn't clear), invoke the `wfgy-method` skill's BBCR bounded-retry-then-surface discipline to recover with a checkpoint instead of blind-retrying, then re-attempt the transition once the actual blocker is cleared.
|
|
19
|
+
**No task is bounded; The fix is fitting the bound to the task (more PRD rows, more turns, more sessions if genuinely needed), never fitting the task to an assumed bound by declaring part of it "out of scope," "future work," or "not yet implemented." We don't want ideas for future work in the project when finished, all plans must go through the process of becoming a prd and being executed, leaving no other artifacts.
|
|
24
20
|
|
|
25
21
|
**A fresh session entering a repo already mid-chain checks for a recent `stuck-loop-escalation` or repeated `prd-resolve-unknown-id` before repeating the same fix shape.** If a prior session hit either (guessing at ids never `prd-add`ed this chain, or retrying the same denied transition), the actual PRD rows and their real ids are on disk in `.gm/prd.yml` -- read them directly rather than assuming remembered ids from context are still correct. Re-`prd-add`ing a row under a slightly different id because the original is unknown/forgotten creates an orphaned duplicate; when uncertain of a row's real id, `codesearch`/`recall`/direct `.gm/prd.yml` read settles it, never a guess-and-hope `prd-resolve`.
|
|
26
22
|
|
package/bootstrap.js
CHANGED
|
@@ -792,6 +792,95 @@ function getBinaryPath() {
|
|
|
792
792
|
return getWasmPath();
|
|
793
793
|
}
|
|
794
794
|
|
|
795
|
+
function agentplugRunnerAssetName() {
|
|
796
|
+
const plat = process.platform;
|
|
797
|
+
const arch = process.arch;
|
|
798
|
+
if (plat === 'win32') {
|
|
799
|
+
if (arch === 'x64') return 'agentplug-runner-windows-x64.exe';
|
|
800
|
+
if (arch === 'arm64') return 'agentplug-runner-windows-arm64.exe';
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
if (plat === 'darwin') {
|
|
804
|
+
if (arch === 'x64') return 'agentplug-runner-macos-x64';
|
|
805
|
+
if (arch === 'arm64') return 'agentplug-runner-macos-arm64';
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
if (plat === 'linux') {
|
|
809
|
+
if (arch === 'x64') return 'agentplug-runner-linux-x64';
|
|
810
|
+
if (arch === 'arm64') return 'agentplug-runner-linux-arm64';
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function compareDottedSemverAscending(a, b) {
|
|
817
|
+
const pa = a.replace(/^v/, '').split('.').map(Number);
|
|
818
|
+
const pb = b.replace(/^v/, '').split('.').map(Number);
|
|
819
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
820
|
+
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
821
|
+
if (d !== 0) return d;
|
|
822
|
+
}
|
|
823
|
+
return 0;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function latestAgentplugRunnerTagViaGitLsRemote() {
|
|
827
|
+
const { execFileSync } = require('child_process');
|
|
828
|
+
const out = execFileSync('git', ['ls-remote', '--tags', '--refs', 'https://github.com/AnEntrypoint/agentplug-bin.git'], {
|
|
829
|
+
encoding: 'utf8',
|
|
830
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
831
|
+
});
|
|
832
|
+
const tags = out.split('\n')
|
|
833
|
+
.map(line => line.match(/refs\/tags\/(.+)$/))
|
|
834
|
+
.filter(Boolean)
|
|
835
|
+
.map(m => m[1]);
|
|
836
|
+
if (tags.length === 0) return null;
|
|
837
|
+
tags.sort(compareDottedSemverAscending);
|
|
838
|
+
return tags[tags.length - 1];
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// The sole loader has to exist before startSpoolDaemon can do anything --
|
|
842
|
+
// a project that never ran `gm-skill install` (or whose install predates
|
|
843
|
+
// agentplug-runner) hit a hard "not installed" failure here even though
|
|
844
|
+
// the sha256-verified download this needs is the same one bin/install.js
|
|
845
|
+
// already performs. Attempting it inline means a bare `spool` boot recovers
|
|
846
|
+
// on its own instead of requiring a separate manual install step first.
|
|
847
|
+
async function ensureAgentplugRunnerInstalled(destPath) {
|
|
848
|
+
const assetName = agentplugRunnerAssetName();
|
|
849
|
+
if (!assetName) return false;
|
|
850
|
+
const destDir = gmToolsDir();
|
|
851
|
+
try {
|
|
852
|
+
let tag;
|
|
853
|
+
try {
|
|
854
|
+
const releaseInfo = JSON.parse((await httpGetBuffer('https://api.github.com/repos/AnEntrypoint/agentplug-bin/releases/latest', 15000)).toString('utf8'));
|
|
855
|
+
tag = releaseInfo && releaseInfo.tag_name;
|
|
856
|
+
} catch (apiErr) {
|
|
857
|
+
try {
|
|
858
|
+
tag = latestAgentplugRunnerTagViaGitLsRemote();
|
|
859
|
+
} catch (_) {
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
if (!tag) return false;
|
|
864
|
+
const base = `https://github.com/AnEntrypoint/agentplug-bin/releases/download/${tag}`;
|
|
865
|
+
const [binBuf, shaBuf] = await Promise.all([
|
|
866
|
+
httpGetBuffer(`${base}/${assetName}`, 60000),
|
|
867
|
+
httpGetBuffer(`${base}/${assetName}.sha256`, 15000),
|
|
868
|
+
]);
|
|
869
|
+
const expectedSha = shaBuf.toString('utf8').trim().split(/\s+/)[0];
|
|
870
|
+
const actualSha = sha256Hex(binBuf);
|
|
871
|
+
if (!expectedSha || actualSha.toLowerCase() !== expectedSha.toLowerCase()) return false;
|
|
872
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
873
|
+
const tmp = destPath + '.tmp' + process.pid;
|
|
874
|
+
fs.writeFileSync(tmp, binBuf);
|
|
875
|
+
if (process.platform !== 'win32') { try { fs.chmodSync(tmp, 0o755); } catch (_) {} }
|
|
876
|
+
fs.renameSync(tmp, destPath);
|
|
877
|
+
fs.writeFileSync(path.join(destDir, 'agentplug-runner.version'), tag);
|
|
878
|
+
return true;
|
|
879
|
+
} catch (_) {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
795
884
|
function startSpoolDaemon() {
|
|
796
885
|
try {
|
|
797
886
|
const runnerName = process.platform === 'win32' ? 'agentplug-runner.exe' : 'agentplug-runner';
|
|
@@ -805,6 +894,7 @@ function startSpoolDaemon() {
|
|
|
805
894
|
`which downloads the sha256-verified native runner from AnEntrypoint/agentplug-bin for this platform ` +
|
|
806
895
|
`(${process.platform}/${process.arch}). If no binary is published for this platform yet, there is no ` +
|
|
807
896
|
`loader available -- file an issue at https://github.com/AnEntrypoint/agentplug-bin so a binary is built for it.`,
|
|
897
|
+
needsRunnerInstall: true,
|
|
808
898
|
};
|
|
809
899
|
}
|
|
810
900
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
@@ -846,6 +936,7 @@ module.exports = {
|
|
|
846
936
|
getWasmPath,
|
|
847
937
|
getBinaryPath,
|
|
848
938
|
startSpoolDaemon,
|
|
939
|
+
ensureAgentplugRunnerInstalled,
|
|
849
940
|
isReady,
|
|
850
941
|
cacheRoot,
|
|
851
942
|
obsEvent,
|
package/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ const fs = require('fs');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const cp = require('child_process');
|
|
8
|
-
const { ensureReady, startSpoolDaemon, gmToolsDir, readVersionFile, ensureGmPlugkitVersionFresh, ensureSkillMdFresh, isReady, getWasmPath, readPinnedGmPlugkitVersion, spawnPinnedBoot, resolveProjectRoot } = require('./bootstrap');
|
|
8
|
+
const { ensureReady, startSpoolDaemon, ensureAgentplugRunnerInstalled, gmToolsDir, readVersionFile, ensureGmPlugkitVersionFresh, ensureSkillMdFresh, isReady, getWasmPath, readPinnedGmPlugkitVersion, spawnPinnedBoot, resolveProjectRoot } = require('./bootstrap');
|
|
9
9
|
const { pidAliveSync, waitForPidDeath } = require('./gm-process');
|
|
10
10
|
|
|
11
11
|
function getWasmPathSafe() {
|
|
@@ -24,7 +24,7 @@ function spawnBackgroundFreshnessCheck(reason) {
|
|
|
24
24
|
} catch (_) {}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function spawnDaemonOrExit(version, binaryPath, message) {
|
|
27
|
+
async function spawnDaemonOrExit(version, binaryPath, message) {
|
|
28
28
|
let daemon;
|
|
29
29
|
try {
|
|
30
30
|
daemon = startSpoolDaemon();
|
|
@@ -33,6 +33,21 @@ function spawnDaemonOrExit(version, binaryPath, message) {
|
|
|
33
33
|
console.error('Daemon start failed:', err.message);
|
|
34
34
|
process.exit(1);
|
|
35
35
|
}
|
|
36
|
+
if (daemon && !daemon.ok && daemon.needsRunnerInstall) {
|
|
37
|
+
const runnerName = process.platform === 'win32' ? 'agentplug-runner.exe' : 'agentplug-runner';
|
|
38
|
+
const runnerPath = path.join(gmToolsDir(), runnerName);
|
|
39
|
+
console.error('agentplug-runner not found -- attempting sha256-verified download from AnEntrypoint/agentplug-bin before failing...');
|
|
40
|
+
const installed = await ensureAgentplugRunnerInstalled(runnerPath);
|
|
41
|
+
if (installed) {
|
|
42
|
+
try {
|
|
43
|
+
daemon = startSpoolDaemon();
|
|
44
|
+
} catch (err) {
|
|
45
|
+
writeCliError('start-daemon', err);
|
|
46
|
+
console.error('Daemon start failed:', err.message);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
36
51
|
if (!daemon || !daemon.ok) {
|
|
37
52
|
const errMsg = (daemon && daemon.error) || 'startSpoolDaemon returned non-ok';
|
|
38
53
|
writeCliError('start-daemon', new Error(errMsg));
|
|
@@ -207,7 +222,7 @@ function tryDelegateToRunner(args) {
|
|
|
207
222
|
try { installedVersion = readVersionFile(); } catch (_) { installedVersion = null; }
|
|
208
223
|
writeCliStatus({ phase: 'bootstrapped', version: installedVersion, binary: getWasmPathSafe() });
|
|
209
224
|
spawnBackgroundFreshnessCheck(versionDrifted ? 'version-drift-respawn' : 'fast-path-spawn');
|
|
210
|
-
spawnDaemonOrExit(
|
|
225
|
+
await spawnDaemonOrExit(
|
|
211
226
|
installedVersion,
|
|
212
227
|
getWasmPathSafe(),
|
|
213
228
|
'plugkit daemon spawned from existing local install, not yet confirmed serving -- check .gm/exec-spool/.status.json for heartbeat freshness; remote freshness check running in background'
|
|
@@ -231,7 +246,7 @@ function tryDelegateToRunner(args) {
|
|
|
231
246
|
}
|
|
232
247
|
|
|
233
248
|
writeCliStatus({ phase: 'bootstrapped', version: bootstrapResult.version, binary: bootstrapResult.binaryPath });
|
|
234
|
-
spawnDaemonOrExit(
|
|
249
|
+
await spawnDaemonOrExit(
|
|
235
250
|
bootstrapResult.version,
|
|
236
251
|
bootstrapResult.binaryPath,
|
|
237
252
|
'plugkit daemon spawned, not yet confirmed serving -- check .gm/exec-spool/.status.json for heartbeat freshness'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2468",
|
|
4
4
|
"description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform wasm, verifies SHA256, and launches agentplug-runner (the native wasm host) as the spool watcher daemon.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|