gm-skill 2.0.2073 → 2.0.2075

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 CHANGED
@@ -67,7 +67,9 @@ Record only non-obvious multi-run-cost caveats; prune stale; never document the
67
67
 
68
68
  **No test files or test suites of any kind, ever -- manual troubleshooting and debugging through real code execution is the only verification surface.** No `*.test.*`/`*.spec.*` files, no `test/`/`__tests__/`/`spec/` directories, no jest/mocha/vitest/pytest/unittest/junit or any assertion/mocking library, in this repo or any repo gm drives work in -- and no standing root-level test file either. Verification is running the real thing and reading the real output -- `exec_js`/`browser` witnessing a live invariant, same turn as the code it checks. A PRD row for "add validation"/"handle edge case X" is closed by exercising that case live, never by authoring a test case that exercises it later. Rationale + measured impact on coder throughput: the recall store (`recall: synthetic-test-file coder-performance regression`). Full phase-level enforcement (PLAN's edge-case rows, EXECUTE's hard rule, VERIFY's adversarial corner-case sweep) lives in rs-plugkit's served `instruction` prose, not duplicated here.
69
69
 
70
- **No comments in code** -- no inline, block, or JSDoc comments anywhere (source, generated output, hooks, scripts). Checked manually on sight (leading `//` over tracked `.js/.mjs/.cjs`); one sighting spawns the full-tree sweep.
70
+ **Self-explanatory code replaces the comment; the comment is never written in the first place.** A name, a function boundary, an extracted variable, or a small type IS the explanation -- prefer renaming/restructuring over annotating every single time a comment urge appears. No inline, block, doc (`///`, `/**`, `#`, JSDoc), or rationale comments anywhere (source, generated output, hooks, scripts, Rust, JS/TS, shell, YAML). A multi-line or paragraph-long comment is the same violation at higher volume, not a lesser one -- it is not exempted by explaining a "why"; that urge to explain is itself the signal that a name or structure is doing too little work, so restructure instead of narrating around the gap. If a rule/tradeoff genuinely needs recording for future sessions (not this code), it goes in AGENTS.md or the recall store, never inline next to the code.
71
+
72
+ **A comment encountered anywhere -- pre-existing, another session's, a dependency's vendored copy inside this repo's own tracked tree -- is converted to self-explanatory code the moment it's seen, same turn, not left for a later cleanup pass.** Read the comment, understand what it was compensating for (an unclear name, an un-extracted step, an unstated invariant), fix that root cause so the comment's content becomes redundant, then delete the comment. "Already there, not part of this task" is not an exemption -- the same one-sighting-spawns-a-sweep discipline that governs every other tell-tale-AI class in this section applies here: a comment survives only until the next file touching it is opened. Checked manually on sight across every tracked source extension (`.rs`, `.js`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.sh`, `.ps1`); one sighting spawns the full-tree sweep.
71
73
 
72
74
  **No UTF-8 BOM in any tracked source file** -- always `-Encoding utf8` (no BOM) or the `Write` tool; PowerShell defaults betray this. Checked manually on sight; one sighting spawns the full-tree sweep. Cause + breakage mechanics in the recall store (`recall: BOM regression incident`).
73
75
 
@@ -105,7 +107,7 @@ A task that reduces to read/investigate/report, or a change confined to files th
105
107
 
106
108
  **Disciplines are isolated knowledge stores**, tracked, `@<name>` sigil-scoped, enabled list at `.gm/disciplines/enabled.txt` (one name per line; `discipline_note.rs::active_policies()` auto-surfaces each enabled discipline's `.gm/disciplines/<name>/policy.md` on every `instruction`). Detail: the recall store (`recall: gm disciplines mechanics`).
107
109
 
108
- **Every runtime config surface, indexed**: `.gm/instructions/<key>.md` + `.gm/instructions/source.json` (prose vendoring, see above); `.gm/instructions/fsm/graph.json` (phase-graph override, write path is the `fsm-vendor` verb, see SKILL.md); `.gm/instructions/hooks/<name>.js` (jit-hook: a real `exec_js` script the orchestrator runs automatically at a gate's evaluation, wired in via a `GateDef`'s `hook` field + `hook_mode` -- `"hook-only"` replaces the compiled predicate, `"both"` requires both, default `"predicate-only"` ignores the hook; an explicit `return true` passes, anything else -- `false`, a thrown error, non-boolean, missing `return`, missing/unreadable file -- fails CLOSED); `.gm/browser-config.json` (`BrowserConfig::load`, per-project -- `cdp_poll_timeout_ms` default 1000, `cdp_poll_interval_ms` default 250, `chrome_ready_deadline_ms` default 30000, `eval_timeout_grace_ms` default 6000, `headless` default false, `session_idle_timeout_ms` default 1800000); `.gm/daemon-project-config.json` (`gm_concurrency_limit`, per-project fairness cap on the shared pool); `~/.agentplug/daemon-config.json` (`DaemonConfig::load`, machine-wide daemon lifecycle timing, including the `side_plugin_concurrency` pool-size knob for the shared bert/treesitter/libsql instances); `.gm/disciplines/enabled.txt` + `.gm/disciplines/<name>/policy.md` (above); `AGENTPLUG_NO_DAEMON`/`CLAUDE_PROJECT_DIR`/`GM_PLUGKIT_SKIP_SELF_STALE_CHECK` (env toggles, SKILL.md documents the third). Daemon-scoped fields (`daemon-project-config.json`, `~/.agentplug/daemon-config.json`) fully detailed in `.gm/daemon-config-reference.md`; this line is the top-level index into that doc and the others.
110
+ **Every runtime config surface, indexed**: `.gm/instructions/<key>.md`+`source.json` (prose vendoring, see above), `.gm/instructions/fsm/graph.json` (phase-graph override via `fsm-vendor`), `.gm/instructions/hooks/<name>.js` (jit-hook, fails closed), `.gm/browser-config.json`, `.gm/daemon-project-config.json`, `~/.agentplug/daemon-config.json`, `.gm/disciplines/enabled.txt`, env toggles (`AGENTPLUG_NO_DAEMON`/`CLAUDE_PROJECT_DIR`/`GM_PLUGKIT_SKIP_SELF_STALE_CHECK`). Field-level detail: `.gm/daemon-config-reference.md` and the recall store (`recall: gm runtime config surface field defaults`).
109
111
 
110
112
  **Nothing fake in source the user runs**: stub/mock/placeholder-return/fixture-only-path/demo-mode-short-circuit/"TODO: implement" forbidden in shipped code. Scaffolds/shims permitted only delegating to real behavior (upstream API, subprocess, disk); check for an existing library before adding a shim. Detection = behavioral: always-succeeds, input-invariant, or type-signature-satisfying short-circuit = stub. Acceptance = real input through real code into real output, witnessed. `Mock*`/`Fake*`/`Stub*` names checked at VERIFY explicitly, not left prose-only (`recall: nevil false-completion mock-network incident`). A row marked `completed` by documenting it (a README "Future Optimizations" section, a design note) instead of implementing it is the same violation -- `prd-resolve` structurally refuses two PRD rows sharing byte-identical `witness_evidence` text (`deviation.prd-resolve-duplicate-witness`), the copy-paste tell of rubber-stamped completion.
111
113
 
@@ -28,13 +28,15 @@ const {
28
28
  killPid,
29
29
  killSpoolWatcherInCwd,
30
30
  proactiveKillForNewInstall,
31
+ ensureNextStepWiring: ensureNextStepWiringShared,
31
32
  } = shared;
32
33
 
33
- // ensureNextStepWiring here is intentionally NOT imported from
34
- // bootstrap-shared -- this file's version does strictly more (CLAUDE.md
35
- // prepend, AGENTS.md append, managed .npmignore block) than bin/bootstrap.js's
36
- // leaner one, so it is genuinely-different logic, not accidental drift, and
37
- // stays local to this file.
34
+ // This file's own ensureNextStepWiring below delegates the shared prefix
35
+ // (seed next-step.md, prepend CLAUDE.md, append AGENTS.md) to
36
+ // bootstrap-shared's ensureNextStepWiringShared, then adds strictly more
37
+ // (a managed .npmignore block) that bootstrap-shared's leaner version does
38
+ // not need -- genuinely-different logic on top of a shared base, not two
39
+ // independent copies of the same first 40 lines.
38
40
 
39
41
  function resolveWindowsExe(cmd) {
40
42
  if (process.platform !== 'win32') return cmd;
@@ -177,48 +179,7 @@ function ensureInstructionsBundle(cwd) {
177
179
  }
178
180
 
179
181
  function ensureNextStepWiring(cwd) {
180
- const changes = [];
181
- const gmDir = path.join(cwd, '.gm');
182
- try { fs.mkdirSync(gmDir, { recursive: true }); }
183
- catch (e) { obsEvent('bootstrap', 'next-step.wiring.target-failed', { target: gmDir, error: e.message }); }
184
-
185
- const nextStepPath = path.join(gmDir, 'next-step.md');
186
- const nextStepBody = '# Next step\n\nDispatch the `instruction` verb to plugkit by writing `.gm/exec-spool/in/instruction/<N>.txt` (any unique N) with body `{}` (or `{"prompt":"<user request>"}` on the first dispatch of the turn). Read the response from `.gm/exec-spool/out/<N>.json` and follow the imperative in the `instruction` field.\n\nThis file is auto-rewritten by plugkit on every instruction dispatch.\n';
187
- try {
188
- if (!fs.existsSync(nextStepPath)) {
189
- fs.writeFileSync(nextStepPath, nextStepBody);
190
- changes.push('seeded .gm/next-step.md');
191
- }
192
- } catch (e) { obsEvent('bootstrap', 'next-step.wiring.target-failed', { target: nextStepPath, error: e.message }); }
193
-
194
-
195
- const claudeMdPath = path.join(cwd, 'CLAUDE.md');
196
- try {
197
- if (!fs.existsSync(claudeMdPath)) {
198
- fs.writeFileSync(claudeMdPath, '@AGENTS.md\n');
199
- changes.push('created CLAUDE.md');
200
- } else {
201
- const cur = fs.readFileSync(claudeMdPath, 'utf8');
202
- const hasLine = cur.split(/\r?\n/).some(l => l.trim() === '@AGENTS.md');
203
- if (!hasLine) {
204
- fs.writeFileSync(claudeMdPath, '@AGENTS.md\n' + cur);
205
- changes.push('prepended @AGENTS.md to CLAUDE.md');
206
- }
207
- }
208
- } catch (e) { obsEvent('bootstrap', 'next-step.wiring.target-failed', { target: claudeMdPath, error: e.message }); }
209
-
210
- const agentsMdPath = path.join(cwd, 'AGENTS.md');
211
- try {
212
- if (fs.existsSync(agentsMdPath)) {
213
- const cur = fs.readFileSync(agentsMdPath, 'utf8');
214
- const hasLine = cur.split(/\r?\n/).some(l => l.trim() === '@.gm/next-step.md');
215
- if (!hasLine) {
216
- const sep = cur.endsWith('\n') ? '' : '\n';
217
- fs.writeFileSync(agentsMdPath, cur + sep + '\n@.gm/next-step.md\n');
218
- changes.push('appended @.gm/next-step.md to AGENTS.md');
219
- }
220
- }
221
- } catch (e) { obsEvent('bootstrap', 'next-step.wiring.target-failed', { target: agentsMdPath, error: e.message }); }
182
+ const changes = ensureNextStepWiringShared(cwd);
222
183
 
223
184
  // gm writes its own runtime data into .gm/ in every project it drives; if that
224
185
  // project is an npm package, that data must never be published. Maintain a
@@ -42,7 +42,7 @@ function sha256OfFileSync(filePath) {
42
42
  function pidCommandLineForKillGuard(pid) {
43
43
  try {
44
44
  if (process.platform === 'win32') {
45
- const r = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', `(Get-WmiObject Win32_Process -Filter "ProcessId=${Number(pid)}").CommandLine`], { encoding: 'utf8', windowsHide: true, timeout: 5000 });
45
+ const r = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', `(Get-CimInstance Win32_Process -Filter "ProcessId=${Number(pid)}").CommandLine`], { encoding: 'utf8', windowsHide: true, timeout: 5000 });
46
46
  return String((r && r.stdout) || '');
47
47
  }
48
48
  const r = spawnSync('ps', ['-p', String(pid), '-o', 'args='], { encoding: 'utf8', timeout: 5000 });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.2073",
3
+ "version": "2.0.2075",
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": {
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.2073",
3
+ "version": "2.0.2075",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.2073",
3
+ "version": "2.0.2075",
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",
@@ -19,9 +19,128 @@ for (const key of KEYS) {
19
19
  if (fs.readFileSync(fp, 'utf8').trim() === '') empty.push(key);
20
20
  }
21
21
 
22
+ let bundleFailed = false;
22
23
  if (missing.length || empty.length) {
24
+ bundleFailed = true;
23
25
  if (missing.length) console.error(`prose bundle missing entries: ${missing.join(', ')}`);
24
26
  if (empty.length) console.error(`prose bundle empty entries: ${empty.join(', ')}`);
27
+ } else {
28
+ console.log(`prose bundle complete: ${KEYS.length} keys present and non-empty`);
29
+ }
30
+
31
+ // Real conformance run: extract every `browser` verb mode-prefix and
32
+ // exec_js `opts.*` field the served prose promises, and check that the
33
+ // implementing native source (agentplug-host, the sole loader per this
34
+ // repo's own architecture) actually references it. Static, not a live
35
+ // dispatch -- this is a "did anyone even write the code" check, not a
36
+ // behavioral one, but that is exactly what catches a "documented, zero
37
+ // implementing code" gap: this check is what found the browser.md
38
+ // screenshot/dom= gap live (agentplug-host/src/browser.rs had zero
39
+ // BrowserMode variant for either, confirmed by grep before this script
40
+ // was written). Runs against the checked-out submodules, so it only fires
41
+ // in CI/local contexts where they're actually populated (a plain `git
42
+ // clone` without --recurse-submodules leaves them empty per README.md's
43
+ // own documented note -- absence there is skipped, not failed, matching
44
+ // this repo's existing plain-clone tolerance).
45
+ const proseSourceDir = path.join(root, 'rs-plugkit', 'crates', 'plugkit-core', 'src', 'orchestrator', 'instructions', 'prose');
46
+ const browserMdPath = path.join(proseSourceDir, 'browser.md');
47
+ const executeMdPath = path.join(proseSourceDir, 'execute.md');
48
+ const browserRsPath = path.join(root, 'agentplug', 'crates', 'agentplug-host', 'src', 'browser.rs');
49
+ const cdpEvalJsPath = path.join(root, 'agentplug', 'crates', 'agentplug-host', 'src', 'cdp_eval.js');
50
+ const execJsRsPath = path.join(root, 'agentplug', 'crates', 'agentplug-host', 'src', 'exec_js.rs');
51
+
52
+ // Every INDIVIDUAL file this check reads, not just the containing
53
+ // directories -- a submodule can be present but shallow/partial (a
54
+ // degenerate state distinct from "not cloned at all"), and the directory-
55
+ // only check previously crashed with an unhandled ENOENT the moment any one
56
+ // file was individually missing (live-reproduced this session: temporarily
57
+ // removing browser.md alone, with every directory still present, crashed
58
+ // the script instead of skipping gracefully).
59
+ const requiredFiles = [browserMdPath, executeMdPath, browserRsPath, cdpEvalJsPath, execJsRsPath];
60
+ const missingFiles = requiredFiles.filter((p) => !fs.existsSync(p));
61
+ const submodulesPresent = missingFiles.length === 0;
62
+
63
+ let conformanceFailed = false;
64
+ if (!submodulesPresent) {
65
+ console.log(`prose-conformance: skipping -- missing file(s) (submodules not populated, or a partial/shallow checkout): ${missingFiles.map((p) => path.relative(root, p)).join(', ')}`);
66
+ } else {
67
+ const browserMd = fs.readFileSync(path.join(proseSourceDir, 'browser.md'), 'utf8');
68
+ const browserRs = fs.readFileSync(browserRsPath, 'utf8');
69
+ const cdpEvalJs = fs.readFileSync(cdpEvalJsPath, 'utf8');
70
+ const executeMd = fs.readFileSync(path.join(proseSourceDir, 'execute.md'), 'utf8');
71
+ const execJsRs = fs.readFileSync(execJsRsPath, 'utf8');
72
+
73
+ // Extract every backtick-quoted `<word>\n<expression>` / `<word>=` mode
74
+ // prefix from browser.md's "Body shapes" code block -- the exact syntax
75
+ // the served prose promises as a real body-shape prefix.
76
+ const bodyShapesMatch = browserMd.match(/## Body shapes[\s\S]*?```\r?\n([\s\S]*?)```/);
77
+ const promisedPrefixes = [];
78
+ if (bodyShapesMatch) {
79
+ for (const line of bodyShapesMatch[1].split(/\r?\n/)) {
80
+ const t = line.trim();
81
+ if (!t) continue;
82
+ // "session new" / "session list" / "session close <id>" -> session subcommands
83
+ const sessionMatch = t.match(/^session (\w+)/);
84
+ if (sessionMatch) { promisedPrefixes.push({ kind: 'session', name: sessionMatch[1] }); continue; }
85
+ // "<word>\n<expression>" or "<word> interval=...\n<expression>" -> bare mode word
86
+ const modeMatch = t.match(/^(\w+)(?:\s+\w+=<[^>]+>)*\\n/);
87
+ if (modeMatch && !['url', 'timeout', 'dom'].includes(modeMatch[1])) { promisedPrefixes.push({ kind: 'mode', name: modeMatch[1] }); continue; }
88
+ // "<word>=<...>" -> key= prefix (url=, timeout=, dom=)
89
+ const kvMatch = t.match(/^(\w+)=</);
90
+ if (kvMatch) { promisedPrefixes.push({ kind: 'kv', name: kvMatch[1] }); continue; }
91
+ }
92
+ }
93
+
94
+ const conformanceFindings = [];
95
+ for (const { kind, name } of promisedPrefixes) {
96
+ // Every promised prefix must appear as a real string literal in either
97
+ // the Rust prefix-stripping logic or the JS mode-dispatch logic -- one
98
+ // hit in either is sufficient (some prefixes are parsed Rust-side,
99
+ // others are only checked JS-side against the `mode` string agentplug
100
+ // passes through). `session <word>` prefixes are matched as the real
101
+ // "session <word>" literal parse_session_command uses, not the bare
102
+ // word alone (which would false-positive-pass on any incidental
103
+ // substring match).
104
+ let inRust, inJs;
105
+ if (kind === 'session') {
106
+ inRust = browserRs.includes(`"session ${name}`);
107
+ inJs = false;
108
+ } else if (kind === 'kv') {
109
+ inRust = browserRs.includes(`"${name}="`);
110
+ inJs = cdpEvalJs.includes(`${name}=`);
111
+ } else {
112
+ inRust = browserRs.includes(`"${name}\\n"`) || browserRs.includes(`"${name}"`);
113
+ inJs = cdpEvalJs.includes(`'${name}'`) || cdpEvalJs.includes(`"${name}"`);
114
+ }
115
+ if (!inRust && !inJs) {
116
+ conformanceFindings.push(`browser.md promises ${kind} prefix "${name}" with zero implementing-code reference in browser.rs or cdp_eval.js`);
117
+ }
118
+ }
119
+
120
+ // exec_js opts.* fields: extract every `opts.<fieldName>` token referenced
121
+ // in execute.md's prose and check exec_js.rs actually reads it via
122
+ // opts.get("<fieldName>").
123
+ const optsFieldRe = /opts\.([a-zA-Z][a-zA-Z0-9]*)/g;
124
+ const promisedOptsFields = new Set();
125
+ let m;
126
+ while ((m = optsFieldRe.exec(executeMd)) !== null) promisedOptsFields.add(m[1]);
127
+ for (const field of promisedOptsFields) {
128
+ if (field === 'true' || field === 'false') continue;
129
+ const needle = `opts.get("${field}")`;
130
+ if (!execJsRs.includes(needle)) {
131
+ conformanceFindings.push(`execute.md promises exec_js opts.${field} with no matching opts.get("${field}") in exec_js.rs`);
132
+ }
133
+ }
134
+
135
+ if (conformanceFindings.length) {
136
+ conformanceFailed = true;
137
+ console.error('prose-conformance FAILED -- prose promises capabilities with no confirmed implementing-code reference:');
138
+ for (const f of conformanceFindings) console.error(` - ${f}`);
139
+ } else {
140
+ console.log(`prose-conformance: ${promisedPrefixes.length} browser mode-prefixes + ${promisedOptsFields.size} exec_js opts fields all have a matching implementing-code reference`);
141
+ }
142
+ }
143
+
144
+ if (bundleFailed || conformanceFailed) {
25
145
  process.exit(1);
26
146
  }
27
- console.log(`prose bundle complete: ${KEYS.length} keys present and non-empty`);