gm-skill 2.0.1828 → 2.0.1830
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/bin/bootstrap.js +16 -2
- package/bin/install.js +17 -3
- package/gm-plugkit/bootstrap.js +30 -13
- package/gm-plugkit/package.json +2 -3
- package/gm-plugkit/plugkit-wasm-wrapper.js +1 -1
- package/gm.json +1 -1
- package/lib/skill-bootstrap.js +16 -2
- package/package.json +1 -1
- package/skills/gm/SKILL.md +6 -2
package/bin/bootstrap.js
CHANGED
|
@@ -17,12 +17,26 @@ function log(msg) {
|
|
|
17
17
|
try { process.stderr.write(`[plugkit-bootstrap] ${msg}\n`); } catch (_) {}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
function discoverBundledSkills(wrapperDir) {
|
|
21
|
+
const roots = [
|
|
22
|
+
path.join(wrapperDir, '..', 'skills'),
|
|
23
|
+
path.join(wrapperDir, '..', '..', 'skills'),
|
|
24
|
+
];
|
|
25
|
+
const root = roots.find(r => { try { return fs.existsSync(r) && fs.statSync(r).isDirectory(); } catch (_) { return false; } });
|
|
26
|
+
if (!root) return [];
|
|
27
|
+
try {
|
|
28
|
+
return fs.readdirSync(root, { withFileTypes: true })
|
|
29
|
+
.filter(e => e.isDirectory())
|
|
30
|
+
.map(e => e.name)
|
|
31
|
+
.filter(name => { try { return fs.existsSync(path.join(root, name, 'SKILL.md')); } catch (_) { return false; } })
|
|
32
|
+
.sort();
|
|
33
|
+
} catch (_) { return []; }
|
|
34
|
+
}
|
|
21
35
|
|
|
22
36
|
function ensureSkillMdCurrent(wrapperDir) {
|
|
23
37
|
const home = os.homedir();
|
|
24
38
|
const allRefreshed = [];
|
|
25
|
-
for (const skillName of
|
|
39
|
+
for (const skillName of discoverBundledSkills(wrapperDir)) {
|
|
26
40
|
try {
|
|
27
41
|
const candidates = [
|
|
28
42
|
path.join(wrapperDir, '..', 'skills', skillName, 'SKILL.md'),
|
package/bin/install.js
CHANGED
|
@@ -6,11 +6,25 @@ const path = require('path');
|
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const readline = require('readline');
|
|
8
8
|
|
|
9
|
-
const BUNDLED_SKILLS = ['gm', 'gm-continue', 'wfgy-method'];
|
|
10
|
-
|
|
11
9
|
function out(msg) { process.stdout.write(msg + '\n'); }
|
|
12
10
|
function err(msg) { process.stderr.write(msg + '\n'); }
|
|
13
11
|
|
|
12
|
+
function discoverBundledSkills() {
|
|
13
|
+
const roots = [
|
|
14
|
+
path.join(__dirname, '..', 'skills'),
|
|
15
|
+
path.join(__dirname, '..', '..', 'skills'),
|
|
16
|
+
];
|
|
17
|
+
const root = roots.find(r => { try { return fs.existsSync(r) && fs.statSync(r).isDirectory(); } catch (_) { return false; } });
|
|
18
|
+
if (!root) return [];
|
|
19
|
+
try {
|
|
20
|
+
return fs.readdirSync(root, { withFileTypes: true })
|
|
21
|
+
.filter(e => e.isDirectory())
|
|
22
|
+
.map(e => e.name)
|
|
23
|
+
.filter(name => { try { return fs.existsSync(path.join(root, name, 'SKILL.md')); } catch (_) { return false; } })
|
|
24
|
+
.sort();
|
|
25
|
+
} catch (_) { return []; }
|
|
26
|
+
}
|
|
27
|
+
|
|
14
28
|
function parseArgs(argv) {
|
|
15
29
|
const flags = { yes: false, project: false, help: false };
|
|
16
30
|
for (const a of argv) {
|
|
@@ -179,7 +193,7 @@ async function main() {
|
|
|
179
193
|
const nonInteractive = flags.yes || !process.stdin.isTTY;
|
|
180
194
|
|
|
181
195
|
let anyInstalled = false;
|
|
182
|
-
for (const skillName of
|
|
196
|
+
for (const skillName of discoverBundledSkills()) {
|
|
183
197
|
const skillSrc = bundledSkillDir(skillName);
|
|
184
198
|
if (!skillSrc) { err(`bundled skill directory skills/${skillName} not found in package`); continue; }
|
|
185
199
|
const installed = installSkillDir(skillSrc, skillName, home, flags.project);
|
package/gm-plugkit/bootstrap.js
CHANGED
|
@@ -774,7 +774,32 @@ function ensureGmPlugkitVersionFresh() {
|
|
|
774
774
|
} catch (_) { return false; }
|
|
775
775
|
}
|
|
776
776
|
|
|
777
|
-
|
|
777
|
+
function discoverBundledSkillsAndSources() {
|
|
778
|
+
const found = new Map();
|
|
779
|
+
found.set('gm', path.join(__dirname, 'SKILL.md'));
|
|
780
|
+
try {
|
|
781
|
+
for (const f of fs.readdirSync(__dirname)) {
|
|
782
|
+
const m = f.match(/^SKILL-(.+)\.md$/);
|
|
783
|
+
if (m) found.set(m[1], path.join(__dirname, f));
|
|
784
|
+
}
|
|
785
|
+
} catch (_) {}
|
|
786
|
+
const devSkillsRoots = [
|
|
787
|
+
path.join(__dirname, '..', 'gm-skill', 'skills'),
|
|
788
|
+
path.join(__dirname, '..', '..', 'gm-skill', 'skills'),
|
|
789
|
+
path.join(__dirname, '..', 'skills'),
|
|
790
|
+
];
|
|
791
|
+
for (const root of devSkillsRoots) {
|
|
792
|
+
try {
|
|
793
|
+
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) continue;
|
|
794
|
+
for (const e of fs.readdirSync(root, { withFileTypes: true })) {
|
|
795
|
+
if (!e.isDirectory()) continue;
|
|
796
|
+
const p = path.join(root, e.name, 'SKILL.md');
|
|
797
|
+
if (fs.existsSync(p) && !found.has(e.name)) found.set(e.name, p);
|
|
798
|
+
}
|
|
799
|
+
} catch (_) {}
|
|
800
|
+
}
|
|
801
|
+
return found;
|
|
802
|
+
}
|
|
778
803
|
|
|
779
804
|
function ensureSkillMdFresh() {
|
|
780
805
|
const home = process.env.HOME || process.env.USERPROFILE || require('os').homedir();
|
|
@@ -782,19 +807,11 @@ function ensureSkillMdFresh() {
|
|
|
782
807
|
const _norm = s => s.replace(/\r\n/g, '\n');
|
|
783
808
|
const allRefreshed = [];
|
|
784
809
|
const sources = {};
|
|
785
|
-
|
|
810
|
+
const discovered = discoverBundledSkillsAndSources();
|
|
811
|
+
for (const [skillName, bundledPath] of discovered) {
|
|
786
812
|
try {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
path.join(__dirname, '..', 'gm-skill', 'skills', skillName, 'SKILL.md'),
|
|
790
|
-
path.join(__dirname, '..', '..', 'gm-skill', 'skills', skillName, 'SKILL.md'),
|
|
791
|
-
path.join(__dirname, '..', 'skills', skillName, 'SKILL.md'),
|
|
792
|
-
];
|
|
793
|
-
const bundledPath = candidates.find(p => {
|
|
794
|
-
try { return fs.existsSync(p); } catch (_) { return false; }
|
|
795
|
-
});
|
|
796
|
-
if (!bundledPath) {
|
|
797
|
-
try { obsEvent('bootstrap', 'skill-md.refresh.bundled-not-found', { skillName, searched: candidates }); } catch (_) {}
|
|
813
|
+
if (!fs.existsSync(bundledPath)) {
|
|
814
|
+
try { obsEvent('bootstrap', 'skill-md.refresh.bundled-not-found', { skillName, searched: [bundledPath] }); } catch (_) {}
|
|
798
815
|
continue;
|
|
799
816
|
}
|
|
800
817
|
const bundled = fs.readFileSync(bundledPath, 'utf-8');
|
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.1830",
|
|
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": {
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
"plugkit.version",
|
|
18
18
|
"plugkit.sha256",
|
|
19
19
|
"SKILL.md",
|
|
20
|
-
"SKILL
|
|
21
|
-
"SKILL-wfgy-method.md",
|
|
20
|
+
"SKILL-*.md",
|
|
22
21
|
"instructions/"
|
|
23
22
|
],
|
|
24
23
|
"keywords": [
|
|
@@ -1087,7 +1087,7 @@ function runBrowserRunner(pw, args, timeoutMs, cwd, claudeSessionId) {
|
|
|
1087
1087
|
const sockDir = playwriterHomeFor(cwd, claudeSessionId);
|
|
1088
1088
|
try { fs.mkdirSync(sockDir, { recursive: true }); } catch (_) {}
|
|
1089
1089
|
env.PLAYWRITER_HOME = sockDir;
|
|
1090
|
-
_writeStatusBusy((timeoutMs ||
|
|
1090
|
+
_writeStatusBusy((timeoutMs || 120000) + 5000);
|
|
1091
1091
|
return spawnSync(spawnCmd, spawnArgs, {
|
|
1092
1092
|
encoding: 'utf-8',
|
|
1093
1093
|
timeout: timeoutMs,
|
package/gm.json
CHANGED
package/lib/skill-bootstrap.js
CHANGED
|
@@ -290,12 +290,26 @@ function ensureBuildToolIgnores(cwd) {
|
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
function discoverBundledSkills() {
|
|
294
|
+
const roots = [
|
|
295
|
+
path.join(__dirname, '..', 'skills'),
|
|
296
|
+
path.join(__dirname, '..', '..', 'skills'),
|
|
297
|
+
];
|
|
298
|
+
const root = roots.find(r => { try { return fs.existsSync(r) && fs.statSync(r).isDirectory(); } catch (_) { return false; } });
|
|
299
|
+
if (!root) return [];
|
|
300
|
+
try {
|
|
301
|
+
return fs.readdirSync(root, { withFileTypes: true })
|
|
302
|
+
.filter(e => e.isDirectory())
|
|
303
|
+
.map(e => e.name)
|
|
304
|
+
.filter(name => { try { return fs.existsSync(path.join(root, name, 'SKILL.md')); } catch (_) { return false; } })
|
|
305
|
+
.sort();
|
|
306
|
+
} catch (_) { return []; }
|
|
307
|
+
}
|
|
294
308
|
|
|
295
309
|
function ensureSkillMdCurrent() {
|
|
296
310
|
const allRefreshed = [];
|
|
297
311
|
let anySkipped = false;
|
|
298
|
-
for (const skillName of
|
|
312
|
+
for (const skillName of discoverBundledSkills()) {
|
|
299
313
|
try {
|
|
300
314
|
const bundledPath = resolveFromCandidates([
|
|
301
315
|
path.join(__dirname, '..', 'skills', skillName, 'SKILL.md'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1830",
|
|
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.md
CHANGED
|
@@ -14,10 +14,14 @@ allowed-tools: Skill, Read, Write, Bash(bun *), Bash(npx *)
|
|
|
14
14
|
|
|
15
15
|
**Standing approval: finish everything, max granularity, no budget constraints.** Every session is pre-approved through ALL requested work -- plan, every addition, every discovered/unfinished item. No stop-and-ask, no deferral, no narrowed slice. Effort is unbounded and never gated on cost, context window, session length, or "budget" of any kind -- there is no such thing as running out mid-cycle in a way that authorizes stopping; if a walk is long, PLAN pushes more rows and EXECUTE keeps walking them, session after session if needed, never pausing to ask which subset to attempt first. A prompt offering the user a menu of "compressed/deferred/partial" options gated on running out of budget is itself the deviation -- make the decision yourself (always: proceed with the full scope, never the partial one) and dispatch, don't present it as a choice. PRD rows are in `.gm/prd.yml`; a long-horizon task means MORE rows pushed onto PLAN as work is discovered, never fewer turns spent working. `blockedBy: external` in mutable status means the blocker is outside this repo/session (another team's repo, unavailable credential, scheduled call) -- it never applies to large/complex/contended/multi-step work that's reachable this turn, and never to "not enough budget"; that work gets more PRD rows, not a stop.
|
|
16
16
|
|
|
17
|
+
**Naming a token/context count as a reason to change behavior is itself the deviation, full stop -- this applies even while reasoning, not only in the final message.** "32k remaining," "fast-track, no delays," "due to token constraints, finalizing immediately," "given the budget, batch/skip/compress X" -- any of these phrases appearing ANYWHERE in a turn (thinking, prose, or tool-call rationale) is the exact failure this rule exists to name, independent of whether the actions taken afterward look reasonable. There is no quantity of remaining context that changes what you do next; 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. This is not a softer version of "no budget constraints" above; it is the same rule restated at the point where it actually breaks -- the rule was violated last not through an explicit stated stop, but through invoking the concept as live context for a decision (batching resolves, skipping witnesses, "fast-tracking" a chain) while still taking real dispatch actions. 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
|
+
|
|
17
19
|
**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.
|
|
18
20
|
|
|
19
21
|
**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.
|
|
20
22
|
|
|
23
|
+
**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`.
|
|
24
|
+
|
|
21
25
|
`instruction` dispatch returns prose describing the current phase and next steps. When uncertain about next action, dispatch `instruction` -- never invent the next step from memory, never stop to ask.
|
|
22
26
|
|
|
23
27
|
Verbs are written to `.gm/exec-spool/in/<verb>/<N>.txt` as JSON. Plugkit processes on read. Phase transitions are explicit `transition {to:"PHASE"}` dispatches. Phase state is in responses and `.gm/exec-spool/.turn-summary.json`; never assume phase from context.
|
|
@@ -42,7 +46,7 @@ Boot probe at session start, one Bash call:
|
|
|
42
46
|
cat .gm/exec-spool/.status.json 2>/dev/null; echo ---; cat .gm/exec-spool/.turn-summary.json 2>/dev/null; echo ---; date +%s%3N
|
|
43
47
|
```
|
|
44
48
|
|
|
45
|
-
`.turn-summary.json` fields: `phase`, `prd_pending`, `last_instruction_age_ms`, `long_gap_threshold_ms`, `update_available`, `deviations_30m`. If `update_available` is set, dispatch `bun x gm-plugkit@latest spool`. If `last_instruction_age_ms` exceeds `long_gap_threshold_ms`, dispatch `instruction` before other verbs. `.status.json` `ts` within
|
|
49
|
+
`.turn-summary.json` fields: `phase`, `prd_pending`, `last_instruction_age_ms`, `long_gap_threshold_ms`, `update_available`, `deviations_30m`. If `update_available` is set, dispatch `bun x gm-plugkit@latest spool`. If `last_instruction_age_ms` exceeds `long_gap_threshold_ms`, dispatch `instruction` before other verbs. `.status.json` `ts` within 5min = watcher alive; gap > 5min = dead. Exception: if `busy_until` is in the future, watcher is handling a long verb (browser, chromium spawn).
|
|
46
50
|
|
|
47
51
|
```bash
|
|
48
52
|
bun x gm-plugkit@latest spool
|
|
@@ -52,7 +56,7 @@ bun x gm-plugkit@latest spool
|
|
|
52
56
|
|
|
53
57
|
**Dispatch shape: Write request + Read response, SAME tool-call block.** Never proceed/narrate/begin work before reading the response and following its `instruction` field. First-read "file does not exist" mid-verb = normal, re-Read next message. Never poll with `sleep && ls` -- plugkit is synchronous; missing response = dead watcher (recheck `ts`) or slow verb, never "still processing."
|
|
54
58
|
|
|
55
|
-
**Dead-watcher recovery is mandatory, not optional.** Two consecutive missing re-Reads AND stale `ts` (>
|
|
59
|
+
**Dead-watcher recovery is mandatory, not optional.** Two consecutive missing re-Reads AND stale `ts` (>5min) AND no future `busy_until` = dead: `bun x gm-plugkit@latest spool` boots fresh, re-dispatch the original verb. If `busy_until` is set, the watcher is processing a long verb; wait instead of rebooting. Recovery = notice-dead -> boot -> re-dispatch, always -- never substitute a raw tool for the dead verb.
|
|
56
60
|
|
|
57
61
|
**Apparent tooling failure is never grounds to ask the user, never a blind restart.** "Spooler not working" / missing response / stale watcher / `gm_plugkit_stale` flagged in a response = your own mechanical self-recovery: honor a future `busy_until` (wait), else boot + re-dispatch. You have boot authority; asking the user to do what a verb can do is a deviation. Staleness of any kind (stale watcher version, stale served prose vs published source) is itself a deviation to resolve immediately, the same turn it's noticed -- `bun x gm-plugkit@latest spool` first, before any other work.
|
|
58
62
|
|