gm-skill 2.0.1822 → 2.0.1824
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 +2 -0
- package/bin/bootstrap.js +50 -46
- package/bin/install.js +1 -1
- package/bin/plugkit.version +1 -1
- package/bin/plugkit.wasm.sha256 +1 -1
- package/gm-plugkit/package.json +1 -1
- package/gm-plugkit/plugkit.version +1 -1
- package/gm.json +2 -2
- package/lib/skill-bootstrap.js +52 -45
- package/package.json +1 -1
- package/skills/gm/SKILL.md +3 -1
- package/skills/gm-continue/SKILL.md +32 -0
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ disclaimer: this is extremely opinionated. it will block bash, redirect your too
|
|
|
16
16
|
|
|
17
17
|
A Claude Code Agent Skill is just a directory at `~/.claude/skills/<name>/SKILL.md` (personal, all projects) or `.claude/skills/<name>/SKILL.md` (one project). The directory name becomes the slash command. No marketplace, no `npx skills` library -- the installer copies the directory into place.
|
|
18
18
|
|
|
19
|
+
**The npm package is `gm-skill`, not `gm`.** `npx gm@latest` resolves to an unrelated, unmaintained GraphicsMagick wrapper (`gm` on npm, last published years ago) with no `install` command -- it fails with `could not determine executable to run`. Always spell out the full package name below.
|
|
20
|
+
|
|
19
21
|
Interactive (offers Claude Code settings):
|
|
20
22
|
|
|
21
23
|
```
|
package/bin/bootstrap.js
CHANGED
|
@@ -17,58 +17,62 @@ function log(msg) {
|
|
|
17
17
|
try { process.stderr.write(`[plugkit-bootstrap] ${msg}\n`); } catch (_) {}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
const BOOTSTRAP_BUNDLED_SKILLS = ['gm', 'gm-continue', 'wfgy-method'];
|
|
21
|
+
|
|
20
22
|
function ensureSkillMdCurrent(wrapperDir) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
for (const target of targets) {
|
|
45
|
-
try {
|
|
46
|
-
let needsWrite = true;
|
|
47
|
-
if (fs.existsSync(target)) {
|
|
48
|
-
const existing = fs.readFileSync(target, 'utf8');
|
|
49
|
-
const existingHash = crypto.createHash('sha256').update(_norm(existing)).digest('hex');
|
|
50
|
-
if (existingHash === bundledHash) needsWrite = false;
|
|
23
|
+
const home = os.homedir();
|
|
24
|
+
const allRefreshed = [];
|
|
25
|
+
for (const skillName of BOOTSTRAP_BUNDLED_SKILLS) {
|
|
26
|
+
try {
|
|
27
|
+
const candidates = [
|
|
28
|
+
path.join(wrapperDir, '..', 'skills', skillName, 'SKILL.md'),
|
|
29
|
+
path.join(wrapperDir, '..', '..', 'skills', skillName, 'SKILL.md'),
|
|
30
|
+
];
|
|
31
|
+
const bundledPath = candidates.find(p => { try { return fs.existsSync(p); } catch (_) { return false; } });
|
|
32
|
+
if (!bundledPath) { obsEvent('bootstrap', 'skill-md.refresh.bundled-not-found', { skillName }); continue; }
|
|
33
|
+
const bundled = fs.readFileSync(bundledPath, 'utf8');
|
|
34
|
+
const _norm = s => s.replace(/\r\n/g, '\n');
|
|
35
|
+
const bundledHash = crypto.createHash('sha256').update(_norm(bundled)).digest('hex');
|
|
36
|
+
const targets = [
|
|
37
|
+
path.join(home, '.agents', 'skills', skillName, 'SKILL.md'),
|
|
38
|
+
path.join(home, '.claude', 'skills', skillName, 'SKILL.md'),
|
|
39
|
+
];
|
|
40
|
+
if (skillName === 'gm') {
|
|
41
|
+
for (const legacy of [
|
|
42
|
+
path.join(home, '.agents', 'skills', 'gm-skill'),
|
|
43
|
+
path.join(home, '.claude', 'skills', 'gm-skill'),
|
|
44
|
+
]) {
|
|
45
|
+
try { if (fs.existsSync(legacy)) fs.rmSync(legacy, { recursive: true, force: true }); } catch (_) {}
|
|
51
46
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
fs.
|
|
57
|
-
|
|
47
|
+
}
|
|
48
|
+
for (const target of targets) {
|
|
49
|
+
try {
|
|
50
|
+
let needsWrite = true;
|
|
51
|
+
if (fs.existsSync(target)) {
|
|
52
|
+
const existing = fs.readFileSync(target, 'utf8');
|
|
53
|
+
const existingHash = crypto.createHash('sha256').update(_norm(existing)).digest('hex');
|
|
54
|
+
if (existingHash === bundledHash) needsWrite = false;
|
|
55
|
+
}
|
|
56
|
+
if (needsWrite) {
|
|
57
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
58
|
+
const tmp = target + '.tmp';
|
|
59
|
+
fs.writeFileSync(tmp, bundled);
|
|
60
|
+
fs.renameSync(tmp, target);
|
|
61
|
+
allRefreshed.push(target);
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {
|
|
64
|
+
obsEvent('bootstrap', 'skill-md.refresh.target-failed', { target, error: e.message });
|
|
58
65
|
}
|
|
59
|
-
} catch (e) {
|
|
60
|
-
obsEvent('bootstrap', 'skill-md.refresh.target-failed', { target, error: e.message });
|
|
61
66
|
}
|
|
67
|
+
} catch (e) {
|
|
68
|
+
obsEvent('bootstrap', 'skill-md.refresh.failed', { skillName, error: e.message });
|
|
62
69
|
}
|
|
63
|
-
if (refreshed.length > 0) {
|
|
64
|
-
log(`SKILL.md refreshed (sha=${bundledHash.slice(0, 12)}): ${refreshed.join(', ')}`);
|
|
65
|
-
obsEvent('bootstrap', 'skill-md.refreshed', { hash: bundledHash.slice(0, 12), targets: refreshed });
|
|
66
|
-
}
|
|
67
|
-
return { refreshed, bundledHash };
|
|
68
|
-
} catch (e) {
|
|
69
|
-
obsEvent('bootstrap', 'skill-md.refresh.failed', { error: e.message });
|
|
70
|
-
return { error: e.message };
|
|
71
70
|
}
|
|
71
|
+
if (allRefreshed.length > 0) {
|
|
72
|
+
log(`SKILL.md refreshed: ${allRefreshed.join(', ')}`);
|
|
73
|
+
obsEvent('bootstrap', 'skill-md.refreshed', { targets: allRefreshed });
|
|
74
|
+
}
|
|
75
|
+
return { refreshed: allRefreshed };
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
function ensureNextStepWiring(cwd) {
|
package/bin/install.js
CHANGED
|
@@ -6,7 +6,7 @@ const path = require('path');
|
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const readline = require('readline');
|
|
8
8
|
|
|
9
|
-
const BUNDLED_SKILLS = ['gm', 'wfgy-method'];
|
|
9
|
+
const BUNDLED_SKILLS = ['gm', 'gm-continue', 'wfgy-method'];
|
|
10
10
|
|
|
11
11
|
function out(msg) { process.stdout.write(msg + '\n'); }
|
|
12
12
|
function err(msg) { process.stderr.write(msg + '\n'); }
|
package/bin/plugkit.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.835
|
package/bin/plugkit.wasm.sha256
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
dae6507c9f7c28e66cb4ca0000b1ed3cc6077b943b4af3d35b975f7abba88951 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.1824",
|
|
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": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.835
|
package/gm.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1824",
|
|
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.835"
|
|
21
21
|
}
|
package/lib/skill-bootstrap.js
CHANGED
|
@@ -290,57 +290,64 @@ function ensureBuildToolIgnores(cwd) {
|
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
|
|
293
|
+
const BUNDLED_SKILLS = ['gm', 'gm-continue', 'wfgy-method'];
|
|
294
|
+
|
|
293
295
|
function ensureSkillMdCurrent() {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if (fs.existsSync(target)) {
|
|
321
|
-
const existing = fs.readFileSync(target, 'utf8');
|
|
322
|
-
const existingHash = crypto.createHash('sha256').update(_norm(existing)).digest('hex');
|
|
323
|
-
if (existingHash === bundledHash) needsWrite = false;
|
|
296
|
+
const allRefreshed = [];
|
|
297
|
+
let anySkipped = false;
|
|
298
|
+
for (const skillName of BUNDLED_SKILLS) {
|
|
299
|
+
try {
|
|
300
|
+
const bundledPath = resolveFromCandidates([
|
|
301
|
+
path.join(__dirname, '..', 'skills', skillName, 'SKILL.md'),
|
|
302
|
+
path.join(__dirname, '..', '..', 'skills', skillName, 'SKILL.md'),
|
|
303
|
+
], `gm-skill/skills/${skillName}/SKILL.md`);
|
|
304
|
+
if (!bundledPath || !fs.existsSync(bundledPath)) {
|
|
305
|
+
emitBootstrapEvent('warn', 'bundled SKILL.md not found; skipping refresh', { skillName });
|
|
306
|
+
anySkipped = true;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
const bundled = fs.readFileSync(bundledPath, 'utf8');
|
|
310
|
+
const _norm = s => s.replace(/\r\n/g, '\n');
|
|
311
|
+
const bundledHash = crypto.createHash('sha256').update(_norm(bundled)).digest('hex');
|
|
312
|
+
const targets = [
|
|
313
|
+
path.join(os.homedir(), '.agents', 'skills', skillName, 'SKILL.md'),
|
|
314
|
+
path.join(os.homedir(), '.claude', 'skills', skillName, 'SKILL.md'),
|
|
315
|
+
];
|
|
316
|
+
if (skillName === 'gm') {
|
|
317
|
+
for (const legacy of [
|
|
318
|
+
path.join(os.homedir(), '.agents', 'skills', 'gm-skill'),
|
|
319
|
+
path.join(os.homedir(), '.claude', 'skills', 'gm-skill'),
|
|
320
|
+
]) {
|
|
321
|
+
try { if (fs.existsSync(legacy)) fs.rmSync(legacy, { recursive: true, force: true }); } catch (_) {}
|
|
324
322
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
fs.
|
|
330
|
-
|
|
323
|
+
}
|
|
324
|
+
for (const target of targets) {
|
|
325
|
+
try {
|
|
326
|
+
let needsWrite = true;
|
|
327
|
+
if (fs.existsSync(target)) {
|
|
328
|
+
const existing = fs.readFileSync(target, 'utf8');
|
|
329
|
+
const existingHash = crypto.createHash('sha256').update(_norm(existing)).digest('hex');
|
|
330
|
+
if (existingHash === bundledHash) needsWrite = false;
|
|
331
|
+
}
|
|
332
|
+
if (needsWrite) {
|
|
333
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
334
|
+
const tmp = target + '.tmp';
|
|
335
|
+
fs.writeFileSync(tmp, bundled);
|
|
336
|
+
fs.renameSync(tmp, target);
|
|
337
|
+
allRefreshed.push(target);
|
|
338
|
+
}
|
|
339
|
+
} catch (e) {
|
|
340
|
+
emitBootstrapEvent('warn', 'SKILL.md refresh failed for target', { target, error: e.message });
|
|
331
341
|
}
|
|
332
|
-
} catch (e) {
|
|
333
|
-
emitBootstrapEvent('warn', 'SKILL.md refresh failed for target', { target, error: e.message });
|
|
334
342
|
}
|
|
343
|
+
} catch (e) {
|
|
344
|
+
emitBootstrapEvent('warn', 'ensureSkillMdCurrent failed for skill', { skillName, error: e.message });
|
|
335
345
|
}
|
|
336
|
-
if (refreshed.length > 0) {
|
|
337
|
-
emitBootstrapEvent('info', 'SKILL.md refreshed', { hash: bundledHash.slice(0, 12), targets: refreshed });
|
|
338
|
-
}
|
|
339
|
-
return { refreshed, bundledHash };
|
|
340
|
-
} catch (e) {
|
|
341
|
-
emitBootstrapEvent('warn', 'ensureSkillMdCurrent failed', { error: e.message });
|
|
342
|
-
return { refreshed: [], error: e.message };
|
|
343
346
|
}
|
|
347
|
+
if (allRefreshed.length > 0) {
|
|
348
|
+
emitBootstrapEvent('info', 'SKILL.md refreshed', { targets: allRefreshed });
|
|
349
|
+
}
|
|
350
|
+
return { refreshed: allRefreshed, skipped: anySkipped };
|
|
344
351
|
}
|
|
345
352
|
|
|
346
353
|
function writeSessionSidecar(sessionId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1824",
|
|
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
|
@@ -8,7 +8,7 @@ allowed-tools: Skill, Read, Write, Bash(bun *), Bash(npx *)
|
|
|
8
8
|
|
|
9
9
|
**No tool call = stop; mid-chain stop = cardinal failure.** Agents read only tool calls + outputs -- prose-only turn ends the session. In-flight (`phase != COMPLETE OR prd_pending_count > 0`): every turn ends in a chain-advancing dispatch (`instruction`, the named next verb, or `transition`). No summaries, no "here's what I did," no closure narration. A turn-final sentence naming the next move instead of taking it is the same stop -- take the move.
|
|
10
10
|
|
|
11
|
-
**At genuine `phase=COMPLETE AND prd_pending_count=0`, the only allowed next step is
|
|
11
|
+
**At genuine `phase=COMPLETE AND prd_pending_count=0`, the only allowed next step is dispatching `Skill(skill="gm-continue")`, never a bare prose ending.** That dedicated skill does the remaining-work search and the `gm`/`wfgy-method` handoff decision -- do not inline that logic here or improvise it; dispatch the skill and follow what it does.
|
|
12
12
|
|
|
13
13
|
**Done is plugkit's word, never yours.** COMPLETE gate is the sole arbiter; not-COMPLETE = a next transition to seek, never a stopping point. Idle mid-chain is a deviation, not a pause. If uncertain what's next, dispatch `phase-status`, read the phase, then keep walking -- "uncertain" is never grounds to stop.
|
|
14
14
|
|
|
@@ -60,6 +60,8 @@ Spool input from PowerShell must be UTF-8 no-BOM (`-Encoding utf8` or `[System.I
|
|
|
60
60
|
|
|
61
61
|
**Apply "every possible" to every noun, at PLAN and every re-entry to it.** PLAN is exhaustive, not minimal: every noun the request touches gets every possible task/validation/mutable/corner-case/caveat/failure-mode/empty-overflow-reentry-degenerate state as its own PRD row. A single-digit PRD count on a non-trivial or long-horizon request means it stopped early -- re-orient, re-enumerate, push more rows. Long-horizon prompts routinely produce high-tens-to-hundreds of rows; density at PLAN is the only protection against silently stopping with work undone. `blockedBy: external` is exhaustively narrow (outside-session authority only) -- never a way to shrink the row count for tedious/large/multi-step work.
|
|
62
62
|
|
|
63
|
+
**Every PLAN pass also asks: what architectural change makes this practical and low-maintenance going forward, not just correct right now?** For each row, before accepting the literal ask as the whole scope: is there a structural change (removing an obsolete mechanism, consolidating duplicated logic, replacing a bespoke reimplementation with a maintained one, fixing a wrong abstraction at its root instead of patching around it) that would make this and future similar work cheaper, not just this one instance? If yes, that becomes its own PRD row alongside the literal ask -- never silently skipped as "out of scope" or "nice to have." A plan that only satisfies the literal request while leaving an obvious maintenance burden in place under-covers by the same standard as a plan that misses a corner case.
|
|
64
|
+
|
|
63
65
|
**EXECUTE resolves all mutables before EMIT, discovers more, resolves those too, rearchitects immediately on in-spirit discovery.** Any architectural improvement discovered mid-EXECUTE is an immediate `transition to=PLAN`, re-`prd-add` the affected row with its existing id (re-scope, never delete-and-re-add) -- maximal-effort correctness over preservation-for-its-own-sake, no deferral, no "note for later."
|
|
64
66
|
|
|
65
67
|
VERIFY is adversarial, never confirmatory: run the real code path and read its actual output via `exec_js` or `browser` -- a finding is only real once witnessed by execution this turn. Never assert a crash, pass, or defect from memory, prior session state, or written prose alone. A gate denial (e.g. `residual.skipped` on dirty worktree) is resolved immediately by the named recovery action (commit or revert the listed files, then re-dispatch the same verb) in the same turn -- it is never grounds to re-enter PLAN, add unrelated PRD rows, or narrate the blocker instead of clearing it.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gm-continue
|
|
3
|
+
description: The mandatory final handoff after a gm walk reaches phase=COMPLETE with prd_pending_count=0. Never end a gm session with prose alone -- dispatch this skill instead. It searches for genuinely remaining work and reloads gm if any exists; if gm already ran a full walk without resolving everything, it loads wfgy-method instead to apply bounded-retry-then-surface discipline before any further gm re-entry. Use immediately after any gm chain reaches its terminal state, never as a substitute for gm itself.
|
|
4
|
+
allowed-tools: Skill, Read, Write, Bash(bun *), Bash(npx *)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# gm-continue
|
|
8
|
+
|
|
9
|
+
This is the only allowed next step when a `gm` walk reaches `phase=COMPLETE AND prd_pending_count=0`. Never end that turn with prose alone -- dispatch this skill instead, every time, no exceptions for "it looks finished."
|
|
10
|
+
|
|
11
|
+
## What this skill does
|
|
12
|
+
|
|
13
|
+
1. Read `.gm/exec-spool/.turn-summary.json` for the current `phase`, `prd_pending`, and how many times this repo has already round-tripped through `gm-continue` this session (track via a counter file, see below).
|
|
14
|
+
2. Read `.gm/prd.yml` and `.gm/mutables.yml` directly for any row marked `blockedBy: [external]`/`[out-of-reach]` or otherwise deferred. **A prior `external`/`out-of-reach` marking is not a closed door -- it is remaining work by definition.** Re-examine each one now: is the blocker still genuinely unreachable this turn, or was it marked that way under time/scope pressure when it was actually solvable? Anything reachable now goes back into scope, re-`prd-add`'d (re-scope the existing id, never delete-and-re-add) as work to actually solve, not left marked external.
|
|
15
|
+
3. Fan out `codesearch` + `recall` against the ORIGINAL request's full closure, not against memory of what was already done -- a fresh look, same discipline as `gm`'s own PLAN orient.
|
|
16
|
+
4. If `.gm/prd.yml` has ANY items at all (pending, or reopened `external` rows from step 2), that alone is remaining work -- skip straight to the dispatch below, no further search needed.
|
|
17
|
+
5. Two outcomes, decided by what steps 2-4 actually find:
|
|
18
|
+
- **Real remaining work found** (any PRD item at all, a reopened external/out-of-reach row, an unaddressed noun from the request, an edge case never exercised, a residual never triaged, a sibling repo never checked): dispatch `Skill(skill="gm")` and instruct it explicitly to finish ALL remaining tasks in `.gm/prd.yml` and fix any issues that arise while doing so -- not a narrow slice, not "just the reopened ones." Tell it plainly what was found so it lands directly in PLAN with real rows, not a blind re-orient.
|
|
19
|
+
- **Nothing found, but this is the first `gm-continue` dispatch this session**: dispatch `Skill(skill="gm")` anyway, once, to let a full independent PLAN pass confirm it from inside `gm`'s own discipline (fresh `codesearch`/`recall`, `prd_pending_count=0` reached with nothing new added). That confirming turn inside `gm` is the actual stopping point -- prose-only is earned there, not here.
|
|
20
|
+
- **Nothing found, and `gm-continue` already ran that confirming pass this session** (counter >= 1): the loop is closed. Prose-only summary is authorized. Do not dispatch anything further.
|
|
21
|
+
|
|
22
|
+
## When to load `wfgy-method` instead of `gm`
|
|
23
|
+
|
|
24
|
+
If a prior `gm` walk reached COMPLETE but the same class of gap keeps recurring across repeat `gm-continue` invocations (the confirming pass itself found new work more than once, or a stuck-loop-escalation was seen during the walk), dispatch `Skill(skill="wfgy-method")` instead of reloading `gm` directly. Apply its BBCR bounded-retry-then-surface discipline first -- checkpoint, name the unresolved tension, surface it plainly -- before any further `gm` re-entry. Reloading `gm` blind into a recurring gap repeats the same failure; `wfgy-method` exists to break that specific pattern.
|
|
25
|
+
|
|
26
|
+
## Recursion bound
|
|
27
|
+
|
|
28
|
+
Track invocation count in `.gm/.gm-continue-count` (plain integer, reset by a fresh user prompt). This skill dispatches `gm` or `wfgy-method` at most twice per user turn before it is required to stop and report to the user directly: once to check, once more only if that check found real work and the subsequent `gm` walk needs its own confirming `gm-continue` pass. A third consecutive "nothing new, re-check again" cycle is itself the stuck-loop signal -- surface it, do not keep looping silently.
|
|
29
|
+
|
|
30
|
+
## Never a substitute for `gm`
|
|
31
|
+
|
|
32
|
+
This skill does no PRD work, no EXECUTE, no EMIT, no VERIFY itself -- it only orients, decides, and hands off. All actual work happens inside `gm` (or `wfgy-method`'s recovery discipline), dispatched via `Skill`, never inlined here.
|