gm-skill 2.0.2087 → 2.0.2089

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 CHANGED
@@ -87,7 +87,7 @@ Every tool the agent uses is a dispatch verb. No direct shell, no direct file wr
87
87
  - **`recall`**: vector + KV recall against `rs-learn`, scored by cosine x recency, namespace-aware
88
88
  - **`codesearch`**: semantic vector search across the project
89
89
  - **`memorize`**: write to the recall index (with the BGE query/passage prefix asymmetry)
90
- - **`browser`**: headful-by-default Chrome session driven natively by `agentplug` (CDP direct, no JS wrapper) -- a process-wide session registry keeps the launched Chrome child + CDP port alive across dispatches instead of relaunching per-call, profile persisted at `.gm/browser-chrome-profile-<session_id>/`; `session new|list|close|reset <id>` manages sessions explicitly
90
+ - **`browser`**: headful-by-default Chrome session driven natively by `agentplug` (CDP direct, no JS wrapper) -- a process-wide session registry keeps the launched Chrome child + CDP port alive across dispatches instead of relaunching per-call, profile persisted at `.gm/browser-chrome-profile-<session_id>/`; `session new|list|close|reset <id>` manages sessions explicitly; `screenshot[=name]`/`dom=<selector>`/`timeout=<ms>` body prefixes stack with a plain eval body for capture and DOM-scoped queries
91
91
  - **`git_status` / `branch_status` / `git_push`**: git verbs that gate on porcelain
92
92
  - **`filter`**: in-wasm stdout-compaction (grep/ls/tree/json/diff)
93
93
 
@@ -99,6 +99,10 @@ Orchestration state is tracked via `.gm/` marker files, not hook events. The gat
99
99
  - **turn entry**: the `instruction` verb reminds the agent to dispatch first and attaches the per-prompt auto-recall pack
100
100
  - **pre-tool-use**: blocks Write/Edit/git before the gm skill fires for the turn
101
101
  - **stop**: blocks session end while `.gm/prd.yml` has open items, mutables are unresolved, residual-scan hasn't fired, or the worktree is dirty or unpushed
102
+ - **VERIFY -> CONSOLIDATE**: `residual-scan-fired`, `prd-all-closed`, `mutables-all-resolved`, `claim-audit-clean` (every AGENTS.md/recall claim naming a commit hash resolves against real git log), `submodules-clean` (every tracked submodule gitlink matches that submodule's own live HEAD)
103
+ - **CONSOLIDATE -> COMPLETE**: `prd-all-closed`, `mutables-all-resolved`, `worktree-clean`, `residual-scan-fired`, `ci-validated-fresh` (`.gm/exec-spool/.ci-validated` matches current HEAD sha), `browser-witness-coverage`, `submodules-clean`
104
+
105
+ The gate graph itself is data, not hardcoded Rust: a project's `.gm/instructions/fsm/graph.json` (written by the `fsm-vendor` verb) can add states, rewire edges, or swap which gates guard which transition, including a `policy` block that externalizes previously-hardcoded behavior (status vocabularies, witness-requirement toggles, CAS retry attempts) as project-overridable JSON.
102
106
 
103
107
  ### ground truth
104
108
 
@@ -143,3 +147,7 @@ Empty submodule directories after a normal `git clone` are expected, not a bug -
143
147
  ## license
144
148
 
145
149
  MIT
150
+
151
+ ## donations
152
+
153
+ BTC: `15FLMay4of9rk4jK2davzzL4HDdGQtscGX`
@@ -1 +1 @@
1
- 45b48b0ab0d6e87742499e779eb662bd2b4620070760e397d77a15f99fd22a65 plugkit-slim.wasm
1
+ 679ca6886f25de4843570a38e52d153afae973ce0fa0837cdfdae85b60230fb9 plugkit-slim.wasm
@@ -1 +1 @@
1
- 0.1.961
1
+ 0.1.962
@@ -1 +1 @@
1
- e60d087a568e33418ad9f934c4c12c87357e94f987853248f36d9dea0c8a807f plugkit.wasm
1
+ 12bf61f61d47fddffc6ad0d7d1ced8e6d2616959a2813547da00ee913abc50cd plugkit.wasm
@@ -659,7 +659,6 @@ function spawnPinnedBoot(extraArgs) {
659
659
 
660
660
  function discoverBundledSkillsAndSources() {
661
661
  const found = new Map();
662
- found.set('gm', path.join(__dirname, 'SKILL.md'));
663
662
  try {
664
663
  for (const f of fs.readdirSync(__dirname)) {
665
664
  const m = f.match(/^SKILL-(.+)\.md$/);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.2087",
3
+ "version": "2.0.2089",
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": {
@@ -1 +1 @@
1
- 45b48b0ab0d6e87742499e779eb662bd2b4620070760e397d77a15f99fd22a65 plugkit-slim.wasm
1
+ 679ca6886f25de4843570a38e52d153afae973ce0fa0837cdfdae85b60230fb9 plugkit-slim.wasm
@@ -1 +1 @@
1
- 0.1.961
1
+ 0.1.962
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.2087",
3
+ "version": "2.0.2089",
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.961"
20
+ "plugkitVersion": "0.1.962"
21
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.2087",
3
+ "version": "2.0.2089",
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",
@@ -5,142 +5,139 @@ import process from 'node:process';
5
5
  const root = path.resolve(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1')), '..');
6
6
  const bundleDir = path.join(root, 'gm-plugkit', 'instructions');
7
7
 
8
- const KEYS = [
8
+ const GATE_AND_RESIDUAL_KEYS = [
9
9
  'gates/long-gap-no-instruction',
10
10
  'residual/prd-open', 'residual/browser-open', 'residual/tasks-running',
11
11
  'residual/dirty-tree', 'residual/imperative',
12
12
  ];
13
13
 
14
- const missing = [];
15
- const empty = [];
16
- for (const key of KEYS) {
17
- const fp = path.join(bundleDir, `${key}.md`);
18
- if (!fs.existsSync(fp)) { missing.push(key); continue; }
19
- if (fs.readFileSync(fp, 'utf8').trim() === '') empty.push(key);
14
+ function validateBundleCompleteness() {
15
+ const missing = [];
16
+ const empty = [];
17
+ for (const key of GATE_AND_RESIDUAL_KEYS) {
18
+ const fp = path.join(bundleDir, `${key}.md`);
19
+ if (!fs.existsSync(fp)) { missing.push(key); continue; }
20
+ if (fs.readFileSync(fp, 'utf8').trim() === '') empty.push(key);
21
+ }
22
+ if (missing.length || empty.length) {
23
+ if (missing.length) console.error(`prose bundle missing entries: ${missing.join(', ')}`);
24
+ if (empty.length) console.error(`prose bundle empty entries: ${empty.join(', ')}`);
25
+ return false;
26
+ }
27
+ console.log(`prose bundle complete: ${GATE_AND_RESIDUAL_KEYS.length} keys present and non-empty`);
28
+ return true;
29
+ }
30
+
31
+ function resolveConformancePaths() {
32
+ const proseSourceDir = path.join(root, 'rs-plugkit', 'crates', 'plugkit-core', 'src', 'orchestrator', 'instructions', 'prose');
33
+ return {
34
+ proseSourceDir,
35
+ browserMdPath: path.join(proseSourceDir, 'browser.md'),
36
+ executeMdPath: path.join(proseSourceDir, 'execute.md'),
37
+ browserRsPath: path.join(root, 'agentplug', 'crates', 'agentplug-host', 'src', 'browser.rs'),
38
+ cdpEvalJsPath: path.join(root, 'agentplug', 'crates', 'agentplug-host', 'src', 'cdp_eval.js'),
39
+ execJsRsPath: path.join(root, 'agentplug', 'crates', 'agentplug-host', 'src', 'exec_js.rs'),
40
+ };
20
41
  }
21
42
 
22
- let bundleFailed = false;
23
- if (missing.length || empty.length) {
24
- bundleFailed = true;
25
- if (missing.length) console.error(`prose bundle missing entries: ${missing.join(', ')}`);
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`);
43
+ function checkEveryRequiredFileExists(requiredFiles) {
44
+ return requiredFiles.filter((p) => !fs.existsSync(p));
29
45
  }
30
46
 
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.
47
+ function extractBrowserModePrefixesFromProse(browserMd) {
76
48
  const bodyShapesMatch = browserMd.match(/## Body shapes[\s\S]*?```\r?\n([\s\S]*?)```/);
77
49
  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
- }
50
+ if (!bodyShapesMatch) return promisedPrefixes;
51
+
52
+ for (const line of bodyShapesMatch[1].split(/\r?\n/)) {
53
+ const t = line.trim();
54
+ if (!t) continue;
55
+
56
+ const sessionMatch = t.match(/^session (\w+)/);
57
+ if (sessionMatch) { promisedPrefixes.push({ kind: 'session', name: sessionMatch[1] }); continue; }
58
+
59
+ const modeMatch = t.match(/^(\w+)(?:\s+\w+=<[^>]+>)*\\n/);
60
+ if (modeMatch && !['url', 'timeout', 'dom'].includes(modeMatch[1])) { promisedPrefixes.push({ kind: 'mode', name: modeMatch[1] }); continue; }
61
+
62
+ const kvMatch = t.match(/^(\w+)=</);
63
+ if (kvMatch) { promisedPrefixes.push({ kind: 'kv', name: kvMatch[1] }); continue; }
92
64
  }
65
+ return promisedPrefixes;
66
+ }
67
+
68
+ function checkPrefixInImplementingCode(kind, name, browserRs, cdpEvalJs) {
69
+ if (kind === 'session') return browserRs.includes(`"session ${name}`);
70
+ if (kind === 'kv') return browserRs.includes(`"${name}="`) || cdpEvalJs.includes(`${name}=`);
71
+ return browserRs.includes(`"${name}\\n"`) || browserRs.includes(`"${name}"`) || cdpEvalJs.includes(`'${name}'`) || cdpEvalJs.includes(`"${name}"`);
72
+ }
93
73
 
74
+ function crossReferenceBrowserPrefixes(browserMd, browserRs, cdpEvalJs) {
75
+ const promisedPrefixes = extractBrowserModePrefixesFromProse(browserMd);
94
76
  const conformanceFindings = [];
77
+
95
78
  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) {
79
+ if (!checkPrefixInImplementingCode(kind, name, browserRs, cdpEvalJs)) {
116
80
  conformanceFindings.push(`browser.md promises ${kind} prefix "${name}" with zero implementing-code reference in browser.rs or cdp_eval.js`);
117
81
  }
118
82
  }
83
+ return { conformanceFindings, promisedPrefixCount: promisedPrefixes.length };
84
+ }
119
85
 
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>").
86
+ function extractExecJsOptsFieldsFromProse(executeMd) {
123
87
  const optsFieldRe = /opts\.([a-zA-Z][a-zA-Z0-9]*)/g;
124
88
  const promisedOptsFields = new Set();
125
89
  let m;
126
- while ((m = optsFieldRe.exec(executeMd)) !== null) promisedOptsFields.add(m[1]);
90
+ while ((m = optsFieldRe.exec(executeMd)) !== null) {
91
+ if (m[1] !== 'true' && m[1] !== 'false') promisedOptsFields.add(m[1]);
92
+ }
93
+ return promisedOptsFields;
94
+ }
95
+
96
+ function crossReferenceExecJsOptsFields(executeMd, execJsRs) {
97
+ const promisedOptsFields = extractExecJsOptsFieldsFromProse(executeMd);
98
+ const conformanceFindings = [];
99
+
127
100
  for (const field of promisedOptsFields) {
128
- if (field === 'true' || field === 'false') continue;
129
- const needle = `opts.get("${field}")`;
130
- if (!execJsRs.includes(needle)) {
101
+ if (!execJsRs.includes(`opts.get("${field}")`)) {
131
102
  conformanceFindings.push(`execute.md promises exec_js opts.${field} with no matching opts.get("${field}") in exec_js.rs`);
132
103
  }
133
104
  }
105
+ return { conformanceFindings, promisedOptsFieldCount: promisedOptsFields.size };
106
+ }
107
+
108
+ function runConformanceCheck() {
109
+ const paths = resolveConformancePaths();
110
+ const requiredFiles = [paths.browserMdPath, paths.executeMdPath, paths.browserRsPath, paths.cdpEvalJsPath, paths.execJsRsPath];
111
+ const missingFiles = checkEveryRequiredFileExists(requiredFiles);
112
+
113
+ if (missingFiles.length > 0) {
114
+ console.log(`prose-conformance: skipping -- missing file(s) (submodules not populated, or a partial/shallow checkout): ${missingFiles.map((p) => path.relative(root, p)).join(', ')}`);
115
+ return false;
116
+ }
117
+
118
+ const browserMd = fs.readFileSync(paths.browserMdPath, 'utf8');
119
+ const browserRs = fs.readFileSync(paths.browserRsPath, 'utf8');
120
+ const cdpEvalJs = fs.readFileSync(paths.cdpEvalJsPath, 'utf8');
121
+ const executeMd = fs.readFileSync(paths.executeMdPath, 'utf8');
122
+ const execJsRs = fs.readFileSync(paths.execJsRsPath, 'utf8');
123
+
124
+ const browserResult = crossReferenceBrowserPrefixes(browserMd, browserRs, cdpEvalJs);
125
+ const execJsResult = crossReferenceExecJsOptsFields(executeMd, execJsRs);
126
+ const allFindings = [...browserResult.conformanceFindings, ...execJsResult.conformanceFindings];
134
127
 
135
- if (conformanceFindings.length) {
136
- conformanceFailed = true;
128
+ if (allFindings.length) {
137
129
  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`);
130
+ for (const f of allFindings) console.error(` - ${f}`);
131
+ return true;
141
132
  }
133
+
134
+ console.log(`prose-conformance: ${browserResult.promisedPrefixCount} browser mode-prefixes + ${execJsResult.promisedOptsFieldCount} exec_js opts fields all have a matching implementing-code reference`);
135
+ return false;
142
136
  }
143
137
 
138
+ const bundleFailed = !validateBundleCompleteness();
139
+ const conformanceFailed = runConformanceCheck();
140
+
144
141
  if (bundleFailed || conformanceFailed) {
145
142
  process.exit(1);
146
- }
143
+ }