gm-skill 2.0.1963 → 2.0.1965

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
@@ -22,7 +22,7 @@ Skills encode environment-specific constraints that override general knowledge.
22
22
 
23
23
  # Architecture & Philosophy
24
24
 
25
- Repo root = package root = published `gm-skill` npm package; no factory, no separate build-output dir. Entry: `skills/gm/SKILL.md`. Orchestration lives in rs-plugkit, served on-demand via `instruction`. Agent-facing prose (phase instruction, gate/residual text) externalizes to editable `gm-plugkit/instructions/`: prose edits = gm-plugkit republish, zero Rust rebuild. Mechanism (prose.rs per-key fallback to compiled const; sync-instruction-consts.mjs byte-aligns .md<->rs-plugkit consts) in the recall store (`recall: string-externalization project`).
25
+ Repo root = package root = published `gm-skill` npm package; no factory, no separate build-output dir. Entry: `skills/gm/SKILL.md`. Orchestration lives in rs-plugkit, served on-demand via `instruction`. The 8 phase-prose files (entry/plan/execute/emit/verify/consolidate/update_docs/browser) live single-sourced inside rs-plugkit itself (`crates/plugkit-core/src/orchestrator/instructions/prose/*.md`, `include_str!`'d at build) -- a prose edit there requires a Rust rebuild via the normal cascade, no cross-repo sync step. Gate/residual text stays genuinely zero-rebuild: editable `gm-plugkit/instructions/gates/` and `gm-plugkit/instructions/residual/` in this repo, read at runtime with `prose.rs`'s per-key fallback to a compiled default (`.gm/instructions/<key>.md` per-project-overridable). Detail in the recall store (`recall: string-externalization project`).
26
26
 
27
27
  ## WASM-only, now with an optional native runner
28
28
 
@@ -1 +1 @@
1
- 0.1.900
1
+ 0.1.901
@@ -1 +1 @@
1
- 82948dac77e366e861a662fb1c73a475b19201640205edea25cc4a42abbfd77c plugkit.wasm
1
+ 2d32d20eec2128554ff8b3dc2b1e5717f1613698a2706712e5217aba8a1f1957 plugkit.wasm
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1963",
3
+ "version": "2.0.1965",
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.900
1
+ 0.1.901
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1963",
3
+ "version": "2.0.1965",
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.900"
20
+ "plugkitVersion": "0.1.901"
21
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1963",
3
+ "version": "2.0.1965",
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",
@@ -6,7 +6,6 @@ const root = path.resolve(path.dirname(new URL(import.meta.url).pathname.replace
6
6
  const bundleDir = path.join(root, 'gm-plugkit', 'instructions');
7
7
 
8
8
  const KEYS = [
9
- 'entry', 'plan', 'execute', 'emit', 'verify', 'update_docs', 'browser',
10
9
  'gates/long-gap-no-instruction',
11
10
  'residual/prd-open', 'residual/browser-open', 'residual/tasks-running',
12
11
  'residual/dirty-tree', 'residual/imperative',
@@ -124,4 +124,4 @@ Memory via `memorize-fire` dispatch stores in `.gm/rs-learn.db` and is retrieved
124
124
 
125
125
  Subagent prompts should reference the gm skill and task specifics only, without restating verb names, spool paths, or protocol mechanics already supplied by invocation.
126
126
 
127
- **This file is not gm's whole procedure.** The phase-specific behavioral prose (PLAN/EXECUTE/EMIT/VERIFY/CONSOLIDATE, gate reasons, deviation text) is served live by rs-plugkit's `instruction` verb from compiled Rust consts, per-project-overridable via `.gm/instructions/<key>.md` -- not authored in this file. A skill-improvement pass (e.g. workflow-forge) editing only this file's numbered steps misses that entire layer: changing PLAN/EXECUTE prose routes through `gm-plugkit/instructions/*.md` -> `scripts/sync-instruction-consts.mjs` -> `rs-plugkit/src/orchestrator/instructions/*.rs` -> a push+CI+cascade rebuild, never a direct edit here. This file holds only: invocation syntax, transition markers, gate-condition summaries, spool-dispatch mechanics, and constraints -- the boot-edge ABI a skill-runner interacts with before the served prose takes over.
127
+ **This file is not gm's whole procedure.** The phase-specific behavioral prose (PLAN/EXECUTE/EMIT/VERIFY/CONSOLIDATE, gate reasons, deviation text) is served live by rs-plugkit's `instruction` verb from compiled Rust consts, per-project-overridable via `.gm/instructions/<key>.md` -- not authored in this file. A skill-improvement pass (e.g. workflow-forge) editing only this file's numbered steps misses that entire layer: changing PLAN/EXECUTE prose means editing `rs-plugkit/crates/plugkit-core/src/orchestrator/instructions/prose/*.md` directly (single-sourced there, `include_str!`'d at build) -> a push+CI+cascade rebuild, never a direct edit here. This file holds only: invocation syntax, transition markers, gate-condition summaries, spool-dispatch mechanics, and constraints -- the boot-edge ABI a skill-runner interacts with before the served prose takes over.
@@ -1,95 +0,0 @@
1
- # BROWSER
2
-
3
- ## Hard Rule: Browser Witness Mandate
4
-
5
- **Every edit to browser-run code requires a live `browser` dispatch, same turn as the edit.** Client-side surfaces -- `.html`, `.js`, `.jsx`, `.ts`, `.tsx`, `.vue`, `.svelte`, `.mjs`, `.css`, web components, service workers, every `<script>`-loaded asset, every `import`-reached path from a browser entry -- witnessed by live `page.evaluate` of the specific invariant the edit establishes. Passing node test / build / `curl` of HTML / static-analysis witnesses server delivery, not browser behavior -- non-substitutive. The witness IS the proof; prose is not.
6
-
7
- **A visual/rendering claim ("X is visible," "the terrain/model/UI renders," "the glitch is gone") is witnessed by `screenshot`, never by internal state alone.** A `page.evaluate` reading `renderer.active === true` or `scene.children.length > 0` proves the object graph exists, not that pixels are correct on screen -- a shader failure, a z-fighting bug, a camera pointed the wrong way, or a black/transparent material all leave that internal state true while nothing is actually visible. Any row whose description names a visual symptom is closed only by a `screenshot` dispatch + `Read` of the resulting PNG (rendering it), confirming by looking at the actual image that the described visual outcome holds -- not by a plausible-sounding description of the mechanism used to fix it ("maintained via existing X orchestration + new Y sync" is a design summary, not a witness). If the fix is real but the visual claim was never re-checked this way, the row is not resolved yet.
8
-
9
- The witness is a live `page.evaluate` asserting the specific invariant against the real surface -- server up, HTTP 200, the global the change affects polled until present -- values captured into `stdout`; variance means a root-cause fix and re-witness, not advance. Anything short of the live assertion -- unwitnessed behavior, an assert fired before the global is present, validation queued for "later" -- leaves the edit unproven, and an unproven client edit is forced closure.
10
-
11
- Fires across phases: **EXECUTE** edit -> same-turn browser dispatch asserting the invariant; **EMIT** post-emit re-witness (page still passes after the full diff); **VERIFY** final gate -- `deviation.browser-witness-hash-mismatch` fires if a witnessed file changed without re-witnessing. Pure-prose static-document edits (no JS, no CSS-driven behavior, no DOM mutation) are the ONLY exempt category, and the exemption must be named explicitly in the response so the skip is auditable. Silent skip on actual behavior change is forced closure.
12
-
13
- YOU drive the browser through the spool: plugkit holds Chromium handle, per-project profile, session table; advance by writing `.gm/exec-spool/in/browser/<N>.txt`, reading `out/<N>.json`. No library import, no puppeteer/playwright/CDP shortcut. The verb is the surface; every other reach = fabrication.
14
-
15
- ## Body shapes
16
-
17
- The body is a string, these shapes:
18
-
19
- ```
20
- session new
21
- session list
22
- session close <id>
23
- <arbitrary JS expression evaluated in page context>
24
- <https://... bare URL>
25
- url=<url>\n<expression>
26
- timeout=<ms>\n<expression>
27
- capture\n<expression>
28
- profile\n<expression>
29
- profile interval=<us> topN=<n>\n<expression>
30
- trace\n<expression>
31
- screenshot\n<expression>
32
- dom=<css-selector>\n
33
- ```
34
-
35
- **Never a JSON object body.** The body is always one of the plain-text shapes above -- never `{"command":"launch","url":"...","timeoutMs":...}` or any other JSON-object payload. Every other spool verb (`prd-add`, `mutable-add`, `transition`, ...) takes structured JSON, which makes a JSON body an easy but wrong guess for `browser` specifically -- this verb is the one exception, string-only. A JSON-shaped body is rejected up front with a clear error naming the supported shapes; passing it anyway used to fall through to evaluating the raw JSON as a JS expression, producing an opaque `SyntaxError: Unexpected token ':'` with no indication of the real mistake. To launch and navigate: `session new` once, then `url=<target>\n<expression>` on the same or next dispatch -- there is no separate "launch" command.
36
-
37
- **Open on the page you want to test, not a blank one.** A bare `https://...` URL body navigates the session straight to that page and returns `{url, title}` -- the simplest "show me this page." `url=<url>\n<expression>` navigates first, then runs your expression on the loaded page, so the global/DOM you assert is already there in one dispatch instead of a blank surface you must `page.goto` yourself. `url=` composes with `timeout=` and `capture` -- stack the prefix lines in order `timeout=`, then `url=`, then `capture`, the expression last; the prepended `page.goto` rides inside the capture so its navigation console/network is captured too. A bare expression with no `url=`/bare-URL prefix runs against whatever the session is already on -- a never-navigated session is on `about:blank`, so the expression evaluates an empty page and the envelope comes back with `landed_on_blank: true` and a `hint` telling you to add `url=`; navigate first and the surprise never happens. `session new` returns the id you carry. (`session close` and `session kill` are aliases.) Default per-eval timeout 120000ms; operations that legitimately exceed it prefix `timeout=<ms>\n` (wrapper clamps to 120000ms). The response carries `timeout_ms_used`; `browser.runner-timeout` fires at the cap -- read `stderr`, narrow or raise, never retry blind at the same budget.
38
-
39
- **`capture\n<expression>` is the zero-boilerplate debug path -- prefer it.** Prefix your script with `capture` (or `profile`) on its own line and the wrapper auto-attaches `page.on('console'|'pageerror'|'requestfinished')` before your code runs, runs your script in an async wrapper (your top-level `await`/`return` work unchanged), and returns `{result: <your return>, debug: {console, pageErrors, network, performance}}` -- page console logs, uncaught errors, per-request network timing, and navigation performance, captured for free. Combine with timeout via `timeout=<ms>\ncapture\n<expr>`. Use the bare expression only when you do not want the capture overhead.
40
-
41
- **`profile\n<expression>` is the bottom-up CPU profiler -- worst-20 culprits by file location across init and code-execution.** Prefix your script with `profile` on its own line: the wrapper opens a CDP `Profiler` (`newCDPSession` + `Profiler.start` BEFORE the prepended `page.goto`, so navigation, script-parse, and init are sampled, not only steady-state), runs your script, `Profiler.stop`s, and aggregates the v8 CPU profile into `{result, profile: {timeframe: {start_us, end_us, total_us, sample_count}, culprits: [{location, function, self_us, self_pct, hits}]}, profile_error, debug: {...}}`. `culprits` is the bottom-up self-time ranking capped at the worst 20 `url:line` locations; `timeframe` is the capture window in microseconds. Composes with `url=`/`timeout=` in the same prefix order. Page scripts loaded from `.js` files carry real `file:line`; `page.evaluate` anonymous frames bucket to `(program)`/`(native)`. On a CDP failure `profile` is `null` with `profile_error` set and your `result` still returns. The identical `{timeframe, culprits}` shape comes back from `exec_js` with `opts.profile:true`, so the cli and browser bottom-up views read the same. `profile` also returns `wall_vs_cpu: {wall_us, cpu_self_us, offcpu_us}` -- the CPU sampler measures only on-CPU JS, so `offcpu_us` is the time it cannot see. Tune the sampler with `interval=<us>` and the culprit count with `topN=<n>` stacked after the mode word (`profile interval=50 topN=40`), symmetric with `exec_js` `opts.sampleIntervalUs`/`opts.profileTopN`.
42
-
43
- **`trace\n<expression>` catches GPU activity the CPU sampler is structurally blind to.** A V8 CPU profiler samples on-CPU JS call stacks only; GPU-process work -- compositor, raster, draw, WebGL/canvas -- never appears in it. `trace` opens a CDP `Tracing` session over wrapper-controlled categories (`gpu`, `viz`, `cc`, `blink`, `devtools.timeline`), runs your script, ends tracing, and returns `{result, trace: {wall_us, gpu_us, viz_us, cc_us, raster_us, event_count, complete, by_category}, trace_error, debug: {...}}`. `gpu_us`/`viz_us`/`cc_us` are wall-clock microseconds of GPU-process activity summed from the trace; `by_category` is the bounded top-15 category rollup (raw events never returned). When wall greatly exceeds CPU self-time, `trace` is how you attribute the gap to the GPU rather than guessing. `debug.performance` carries paint/frame metrics (`first_contentful_paint_ms`, `largest_contentful_paint_ms`, `cumulative_layout_shift`, `longtasks`, `fps`) for client-side render jank. `tracingComplete` is bounded by a timeout; on a CDP failure `trace_error` is set and `result`/`debug` still return.
44
-
45
- **Every dispatch returns a structured `result` -- read `result.foo`, never regex the stdout.** Whatever your expression returns is JSON-serialized into the envelope's top-level `result` field (the human-readable `[return value]` text stays in `stdout` for eyeballing). `capture`/`profile`/`trace` put their `{result, debug, ...}` object there whole, so `result.debug.performance.heap_used_mb` and `result.trace.gpu_us` are direct reads. `debug.performance` now also carries `heap_used_mb`/`heap_total_mb` (JS heap) for leak-hunting; `debug.network` entries carry `method` and `status` and are sorted slowest-first before the 30-cap so the worst request survives; `debug.console` is capped at 50 with a trailing dropped-count note so a chatty page cannot flood the envelope.
46
-
47
- **`screenshot\n<expression>` writes a PNG and returns its path.** Runs your expression, then `page.screenshot` to `.gm/witness/` (basename-sanitized, confined to that dir), returning `{result, screenshot_path, screenshot_error}`. Reach for it when the bug is visual and the DOM/console do not show it.
48
-
49
- **`dom=<css-selector>\n` is the zero-boilerplate element probe.** Returns `{selector, match_count, elements:[{tag, text, attrs, visible, rect}]}` for up to 20 matches -- the fastest answer to "is this element there and what does it say." An invalid selector returns `result.error` (no crash). Composes with `url=`.
50
-
51
- **One session per run -- reuse it, then close it.** A browser session is keyed by its spool `sessionId`; every dispatch carrying the SAME sessionId reuses the SAME chromium. A DIFFERENT sessionId opens its OWN chromium -- so a run that invents `probe`/`w2`/`w3`/... names leaks one browser per name. Pick one sessionId, use it for every dispatch, and end with `session close` so nothing is left open; the eval envelope carries a `multi_session_warning` the moment a second distinct session opens. The idle reaper (closes sessions unused past the idle window) and the OS-orphan reaper (kills managed chromiums no live session owns, sparing in-use ones and your own Chrome) are backstops for crashes, not a license to leave sessions open -- close yours.
52
-
53
- **The session closes when YOU expect it to, not under you.** A session stays open across turns and think-gaps -- the idle window is generous (15 min of no use), measured from the END of your last dispatch, so a long read or a slow eval never shortens it. A dispatch in flight is never closed mid-run: the idle reaper and the orphan reaper both skip a session while its eval is executing, and a just-launched browser has a grace period before any reaper can touch it. An explicit `session close` is immediate. If the idle/orphan backstop did close a session and you dispatch to it again, it transparently re-launches and the envelope carries `session_relaunched: true` with a `relaunch_note` -- your in-page `window.*` state was reset, so re-establish it; you are told, never silently surprised.
54
-
55
- ## Envelope
56
-
57
- `{ok, stdout, stderr, exit_code, session_id?, navigation_requested, landed_on_blank?, hint?, multi_session_warning?}`. `stdout` = stringified eval result; `stderr` = page errors + launch diagnostics; `exit_code` non-zero = the dispatch did not land -- read `stderr` and re-dispatch, never blind. `navigation_requested` reflects whether the dispatch carried a `url=`/bare-URL navigation; `landed_on_blank: true` with a `hint` means the expression ran against `about:blank` -- prefix `url=<target>` and re-dispatch.
58
-
59
- ## Headed by default
60
-
61
- The window opens on the user's screen -- that IS the witness. `GM_BROWSER_HEADLESS=1` opts into headless; absent it, a session with no visible window is a launch you did not make. Do not assume or request headless to "be quiet"; the flash is the proof.
62
-
63
- ## Profile
64
-
65
- `session new` (or a bare expression with no live session) spawns a locally-profiled Chromium at `<cwd>/.gm/browser-profile/`; the runner attaches via `--direct <wsEndpoint>`. Cookies/storage/extensions persist across sessions, turns, and runs. A second concurrent launch contends the SingletonLock; the watcher reuses the live CDP rather than re-launching. The runner's extension-attach mode ("Waiting for extension to connect") is never the default or what you want -- seeing it in `stderr` means the host failed to spawn local Chromium; dispatch `instruction` for recovery, not a blind retry.
66
-
67
- ## Profile and debug recipes
68
-
69
- The page is a genuine profiler and debugger -- use it, never guess-and-restart. The `capture\n` prefix above does all of this for free; reach for the manual recipe below only for custom capture. Attach the listeners, then navigate, in one script so nothing fires before they are listening -- the captured arrays are the live witness:
70
-
71
- ```
72
- const logs=[],errs=[],net=[];
73
- page.on('console',m=>logs.push({type:m.type(),text:m.text()}));
74
- page.on('pageerror',e=>errs.push(String(e&&e.message||e)));
75
- page.on('requestfinished',r=>{const t=r.timing();net.push({url:r.url(),dur_ms:Math.round(t.responseEnd),ttfb_ms:Math.round(t.responseStart)});});
76
- await page.goto(URL,{waitUntil:'load'});
77
- const perf=await page.evaluate(()=>{const n=performance.getEntriesByType('navigation')[0]||{};return {load_ms:Math.round(n.loadEventEnd||0),dcl_ms:Math.round(n.domContentLoadedEventEnd||0),resources:performance.getEntriesByType('resource').length,now:Math.round(performance.now())};});
78
- return {logs,errs,net,perf};
79
- ```
80
-
81
- - **Console + uncaught errors**: `page.on('console')` captures every page `console.*`; `page.on('pageerror')` captures uncaught exceptions (a `try/catch` in the page swallows them -- they surface as a console.error instead). This is your debug log.
82
- - **Performance**: `performance.getEntriesByType('navigation')[0]` gives `loadEventEnd`/`domContentLoadedEventEnd`; `getEntriesByType('resource')` gives per-asset timing; `performance.now()`/`PerformanceObserver` for in-page measures. This is your profiler.
83
- - **Network timing**: `request.timing()` fields (`responseEnd`, `responseStart`, ...) are ALREADY relative to `startTime` -- use `Math.round(t.responseEnd)` directly for duration; subtracting `startTime` yields a garbage huge-negative (witnessed). `-1` means N/A.
84
- - **State**: expose any runtime value as `window.__x` in the app or via `page.evaluate(()=>{window.__x=...})`, then read it with another `page.evaluate` -- the live global beats a restart. Surface relevant state as a global on purpose so a single evaluate observes it.
85
- - **Screenshots**: to actually SEE a screenshot, save it to a file and `Read` that path -- `await page.screenshot({path:'<abs>/shot.png'})` then `Read <abs>/shot.png` (the Read tool renders the PNG visually; witnessed). NEVER `return` the screenshot Buffer inline -- it stringifies to useless bytes in the envelope. The same applies to any binary: write it to a path, then Read the path.
86
-
87
- Profile to LOCATE (which call/resource is slow), then eliminate hypotheses by live measurement -- never a/b-test by restarting. The node side mirrors this: `exec_js` with `process.hrtime.bigint()`/`performance.now()` timing, `process.memoryUsage()`, and `stderr` stack capture is a genuine node profiler+debugger.
88
-
89
- **Process of elimination via monkeypatching is the default diagnostic method, not a last resort.** When a bug's cause is ambiguous among several candidate functions/paths/handlers, wrap each candidate in the live page with an instrumented replacement -- `const orig=obj.method; obj.method=function(...a){console.log('HIT',...a); return orig.apply(this,a);}` -- and observe which fires, with what args, in what order, relative to the symptom. This isolates the guilty call site from real execution instead of guessing from source reading alone or restarting with one hypothesis at a time. Combine with `capture\n` so the injected `console.log` markers land in `debug.console` for free. Patch in the same `page.evaluate` that also reproduces the symptom, so cause and effect are witnessed in one dispatch, not two. Restore or accept the patch is thrown away with the session -- never leave a monkeypatch as the actual fix; once the culprit is isolated, fix it at the source and re-witness clean.
90
-
91
- ## Discipline
92
-
93
- Never spawn Chromium yourself, `npm i puppeteer`, or shell `chrome.exe`; the verb owns the handle, and bypassing it orphans state plugkit cannot reap and breaks the next session's first read. Navigate by evaluating `location.href = '...'` through the spool; screenshot by dispatching the verb that returns one. A dispatch returning `ok:false` with a launch error is plugkit reporting the environment refused -- read `stderr`, dispatch `instruction`, do not loop the same body.
94
-
95
- **Dead-watcher recovery, never substitute.** If a Write to `.gm/exec-spool/in/browser/<N>.txt` produces no response after two re-Reads AND `.status.json` ts is stale (>5min from current epoch), the watcher is dead: boot `bun x gm-plugkit@latest spool`, then re-dispatch the browser body. Do NOT reach for puppeteer-core, puppeteer, playwright, agent-browser, `chrome.exe`, `npx browserless`, WebFetch, or curl-then-parse -- the browser verb is the only admissible browser surface; substitutes spawn orphan Chromium plugkit cannot reap, bypass section 23 witness-marked events, and produce evidence the gate cannot read. The recovery loop is always: empty response -> check `.status.json` -> if stale, boot -> re-dispatch.
@@ -1,17 +0,0 @@
1
- # CONSOLIDATE
2
-
3
- YOU are the state machine. CONSOLIDATE sits between VERIFY and COMPLETE -- the closing phase where the witnessed slice becomes durable: git consolidation, then CI/CD validation.
4
-
5
- L3 landing. Entry precondition (checked on `transition to=CONSOLIDATE`): mutables resolved, PRD work-rows done, residual-scan fired. Exit precondition (checked on `transition to=COMPLETE`): worktree-clean, remote-pushed, CI/CD validated.
6
-
7
- ## Git consolidation
8
-
9
- Stage, commit, push -- via the git verbs, never a shell git. `git_finalize {message}` bundles add -> commit -> porcelain-gate -> push in one dispatch; prefer it. `git_push {repo, branch}` for a sibling repo. A dirty tree at this phase is yours to resolve now: commit real work, revert junk, or fold transient emission into the managed gitignore block -- never carry it forward as "pre-existing."
10
-
11
- ## CI/CD validation
12
-
13
- The push IS part of the validation dispatch, but CONSOLIDATE also witnesses the pipeline going green, not just the push landing. Watch the triggered run (`gh run watch` equivalent via the exec/fetch verbs, or poll the remote CI status) and on green, `fs_write` `.gm/exec-spool/.ci-validated` with `{"head_sha":"<git rev-parse HEAD>"}` -- the COMPLETE gate matches that sha against current HEAD; any other content, a stale sha, or a marker written before the final push reads as unvalidated. Red is not a stop: name the cause, fix, re-push, re-watch. A CI check skipped because "the diff looked safe" is an unwitnessed slice.
14
-
15
- ## Dispatch
16
-
17
- `transition to=COMPLETE` only once worktree-clean + remote-pushed + `.ci-validated` fresh all hold. Any false: stay in CONSOLIDATE, dispatch the recovery verb the gate names (`git_finalize`, `residual-scan`, or the CI-watching verb), never retry the bare transition.
@@ -1,34 +0,0 @@
1
- # EMIT
2
-
3
- YOU are the state machine. Plugkit is the synchronous library serving this prose; advancing the chain is your dispatch. Every write lands only through the verb you dispatch to land it.
4
-
5
- L3 audit on disk. Land every node of the covering family; your first emit = closure.
6
-
7
- ## Scope: file mutation ONLY (hard rule)
8
-
9
- EMIT's precondition: mutables already resolved -- EXECUTE's job, done before arrival. EMIT does not investigate, open mutables, resolve unknowns, or re-derive the plan. A mutable surfacing here is EXECUTE leaking into EMIT: `mutable-add` it, `transition to=EXECUTE` immediately -- never resolve inline, never write around it. EMIT's sole verb-of-work is Write/Edit of changes PLAN/EXECUTE already decided; narrower is correct, wider is drift.
10
-
11
- ## Read-before-write
12
-
13
- On-disk content is the goal-relative reference; diffing an unread file diffs an imagined baseline. Observed disk divergence -> `transition` back to PLAN.
14
-
15
- ## Fresh index
16
-
17
- Feed EMIT only digest-matching-live-filesystem search output; a stale-index result is an L1 bluff.
18
-
19
- ## Write-then-verify
20
-
21
- One write per artifact, then a disk Read against every touched path -- witness the change, never reason it succeeded. Verified disk state IS the witness, not the tool-call return. Discrepancy -> regress to root cause, never retry.
22
-
23
- **Client-side artifacts: write-then-browser-witness, same turn.** `.html .js .jsx .ts .tsx .vue .svelte .mjs .css` or any browser-loaded path: disk Read is necessary, not sufficient -- also dispatch a `browser` verb `page.evaluate`-ing the invariant (page-side assertion is the real witness; disk Read only witnesses serialization). Skip = shipping a green-checked stub. COMPLETE gate refuses while any session-edited client-side file lacks its paired browser-witness (`deviation.client-edit-no-witness`, gates.rs) -- the missing witness is the next dispatch.
24
-
25
- ## Artifact scope
26
-
27
- PRD names the writable artifacts; closure narrative goes to the commit message + `memorize-fire`, never the response body -- a file PRD does not name is response-body displacing dispatch. Write-then-verify exposing an adjacent artifact (generated file the build needs, doc naming the new artifact, witness script) -> `prd-add` it this turn; unlanded observation evaporates with the turn. Uncertain writes -> re-dispatch `instruction`.
28
-
29
- ## Constraints
30
-
31
-
32
- ## Dispatch
33
-
34
- `transition` when every planned artifact is written and disk-verified. New unknown -> `transition` back to PLAN.
@@ -1,99 +0,0 @@
1
- # ORCHESTRATOR
2
-
3
- YOU are the state machine. Plugkit: synchronous lib serving this prose; advance = your dispatch, not its action. Holds phase/PRD/mutables on disk -- read via `phase-status`/`instruction`, change via the relevant verb. Nothing advances while you wait.
4
-
5
- Your authorization = the request. Your receipt = the PRD you write. Trajectory PLAN -> EXECUTE -> EMIT -> VERIFY -> CONSOLIDATE -> COMPLETE, one continuous flow, each transition a verb you dispatch. CONSOLIDATE owns git-push + CI/CD validation, split off the COMPLETE gate. Scope = the closure of the destructive transform admissible over the session; your first emit = closure, not prefix.
6
-
7
- **Continuation invariant (the brick wall).** Turn without tool call = stop -- harness reads only tool calls. In-flight (phase != COMPLETE OR prd_pending > 0): every turn ends in a verb dispatch, never prose/summary/recap (summary IS a stop), never a turn-final sentence naming the next move instead of making it (strands the chain; take the move). Only phase=COMPLETE AND prd_pending=0 authorizes stopping THE VERB SPOOL -- it does not authorize a bare prose ending. The actual last dispatch is `Skill(skill="gm-continue")` (a host-level tool, not a spool verb): that skill independently checks for remaining work and either reloads `gm` or confirms the loop genuinely closed. Skipping straight from a terminal `transition` response to silence, without that one `Skill` dispatch, is the same class of stop as ending mid-chain -- it is why "list all remaining limitations" has to be retyped manually instead of the chain continuing on its own. Urge to stop -> dispatch `phase-status`; non-terminal = drift -> dispatch `instruction`, keep walking; genuinely terminal = dispatch `Skill(skill="gm-continue")` before the turn ends. Depends only on the verb spool -- holds on every agent. Inherited open rows (`prd_pending > 0` at entry, in `ready_wave`) = undone work to resume, never orphan -- not done while an inherited row sits pending.
8
-
9
- **There is no next session where a "ready to resume" turn actually resumes -- writing that sentence ends the conversation as surely as never writing anything again.** A response with no tool call is the last message of this conversation, full stop, regardless of how the prose frames it ("Session N closes," "standing work ready for next invocation," "user can resume with /gm," a recap of decisions made so far). The user re-typing `/gm` later is not this chain continuing -- it is a new, separate invocation that has to re-discover everything the closing summary just threw away. The only mechanism that produces an actual next action instead of silence is a dispatch in the SAME response, never a description of what a future response would do.
10
-
11
- ## Admission Filter
12
-
13
- ```
14
- candidate -> [L1 witness] -> [L2 single-writer] -> [L3 direction] -> execute
15
- ```
16
-
17
- - **L1.** Admit on witness, not cheapness. Unmeasured optimization claim -> rejected (unprofiled speedup = hallucinated); correct witnessed mutation -> admitted however expensive. Only cost weighed: correctness-cost of unverified claim, never effort. Work envelope unbounded; "too much work" never rejects.
18
- - **L2.** Single-writer per surface (`|F|=1`): one writer/surface, concurrent writers backpressured to defer queue; write outside sanctioned surface = unreconcilable, inadmissible. Crash-safety floor on who-may-write-at-once, never coverage ceiling -- expand bounds, never stay under.
19
- - **L3.** Lyapunov: `Delta d >= 0` rejects dispatch. Audit tuple `(id, hash, ts)` per accepted write. Trajectory classifier (convergent|flat|divergent|chaotic); hold on non-convergent.
20
-
21
- Five phases = scheduling; filter = engine on every candidate, gating witness/writer-safety/direction, never effort.
22
-
23
- ## Invariants
24
-
25
- - **Measurement gates optimization** *claims*, not effort -- a measured-correct change ships however costly.
26
- - **Bounds prevent cascades:** explicit per-surface writer capacity converts crash to graceful degradation -- bounds writers, not coverage.
27
- - **Effort is unbounded:** the maximal-effort fully-destructive run is the default; the only costs weighed are maintenance-surface left behind (net-smaller wins, a heavy dep for a few lines loses) and the correctness-cost of an unverified claim.
28
- - **Direction eliminates waste:** motion that does not reduce distance is dead.
29
- - **Monotonic closure on first emit:** a partial emit externalizes residual cost as unaudited state; mature artifact = first artifact.
30
- - **Witness is the audit primitive:** a claim without `(id, hash, ts)` is not in the system.
31
-
32
- ## Code Invariants (every possible emission)
33
-
34
- - **State minimized:** sequential downward flow; explicit state flags; external input through a unified queue before mutation; state changes are explicit assignment, never a buried side effect or init hidden in helpers.
35
- - **Hardware reality:** benchmark before abstracting; pass scope explicitly (closures hide scope cost in hot loops); mutate in place, pools over allocation; native data flow on hot paths (no Promise chains / class hierarchies / operator overloading there).
36
- - **Flat structure:** denormalized graphs over nested documents; partial-field over whole-document writes; bytes over JSON for transport (pre-compute size, allocate once); lexical ordering for deterministic tie-breaking.
37
- - **200-line vertical slices:** one responsibility per file; input->process->output complete in the module; zero-config defaults correct for 90%; universal runtime (browser/Node/mobile/Bare).
38
- - **Async boundary explicit:** sequential awaitable primitives; no implicit callback ordering; unified error channel, never swallow rejections; tests await real ops, mock-free.
39
- - **Naming by scale:** <50 lines single-letter algebraic; 50-200 short descriptors; >200 full names; public APIs explicit.
40
- - **Fail fast, loud, deterministic:** halt on precondition violation with exact state; assert on emitted semantics, not return values; sentinel words + checksum headers on critical structures, verified on every access; never silently degrade.
41
- - **Binary transport, append-only persistence:** varint fields; lexical cursors for sparse reads; append-only sequence for replay; chunked by lexical range, modify only the touched chunk.
42
- - **Single focused task per session:** no drive-by refactors; pre-compute and inline.
43
-
44
- ## Token Discipline
45
-
46
- English describing intent = liability when code encodes it; comments = liability when names+structure encode the same; duplication-that-must-sync = liability. Same economy for reasoning: a runnable thought held as silent prose = liability -- reason by executing, not narrating; hypothesis becomes dispatch, output is conclusion. Prose enacts the discipline structurally, never narrates scenarios. Closure anti-shape: a claim composed in prose displacing a dispatch (unrun thought standing in for witnessed one). Response body is not a mutation surface.
47
-
48
- ## Install
49
-
50
- `npx gm-skill install` copies the skill directory into `~/.claude/skills/gm/` (and `~/.agents/skills/gm/`), installed as `/gm`; `--yes` is the non-interactive form. No `skills` library.
51
-
52
- ## Bootstrap
53
-
54
- First dispatch checks `~/.gm-tools/plugkit.wasm` (or `~/.claude/gm-tools/plugkit.wasm` on legacy installs). Absent -> write `.gm/exec-spool/in/bootstrap/0.txt`; plugkit fetches, sha-verifies, writes `.bootstrap-status.json`. On pin mismatch it writes `.bootstrap-error.json` and you pause the chain.
55
-
56
- ## Supervisor drift and version updates
57
-
58
- A supervisor respawns the watcher under fresh code on `wrapper.drift`/`version.drift` or a stale `.status.json`. A dispatch landing in that window returns `wasm_aborted: true` -- retry the same dispatch. `update.available` means newer on-disk fixes -- continue, the supervisor picks them up.
59
-
60
- ## State
61
-
62
- `cwd/.gm/`: `prd.yml`, `mutables.yml`, `exec-spool/{in,out}/`, `gm-fired-<sessionId>`, `rs-learn.db`, `disciplines/<ns>/`, `code-search/`. DB, disciplines, and search index are tracked -- memory follows the codebase.
63
-
64
- ## Spool ABI
65
-
66
- Write `in/<lang>/<N>.<ext>` for language stems, `in/<verb>/<N>.txt` for orchestrator + host verbs. The watcher streams `out/<N>.{out,err}` and finalizes `out/<N>.json` synchronously -- read it once it lands. Parallelize independent dispatches in one message; serialize dependents at the data-flow edge. Every git operation routes through the git verbs (`git_status`/`git_finalize`/`git_push`/...), never a raw `git` shell body (gated `deviation.bash-git-bypass`); route every other capability through its verb.
67
-
68
- ## Observability
69
-
70
- `.gm/exec-spool/.watcher.log` -- cdylib stdout/stderr, dispatch timings, sweep ticks, boot markers; tail via Read+offset; rotated 10MB.
71
-
72
- ## SESSION_ID
73
-
74
- Thread SESSION_ID through every spool body; plugkit rejects empty.
75
-
76
- ## Daemonize
77
-
78
- The watcher returns task_id immediately and tails to 30s wall-clock. Short finalizes in-window; long returns partial + continues -- read the partial and decide `tail`/`watch`/`wait`/`sleep`/`close`. Responses carry `running_task_ids` you track.
79
-
80
- ## Disciplines
81
-
82
- Route KV writes to `<cwd>/.gm/disciplines/<ns>/`. `@<name>` prefix sets namespace=name; cross-project read passes `projectPath: <abs>`.
83
-
84
- ## Inspection routing
85
-
86
- Every capability has exactly one sanctioned surface and the platform's native tools are never it: code/file/symbol search is the `codesearch` verb (cwd-indexed -- a sibling repo is `Read` by path, never expected from `codesearch`), runtime-state files (spool response JSON, `.status.json`) are `Read`, and Bash survives only for the boot probe and shell-only non-git tooling (`npm`, `bun x`, `curl`). Reaching for Glob/Grep/Explore or any host-native search is reaching around the surface -- it is blocked; the verb IS the surface. Spool responses are synchronous; poll external state via `until <check>; do sleep N; done`.
87
-
88
- ## Memorize
89
-
90
- Write the recall index only via `memorize-fire`; surfaces outside it produce memos the index never sees. Prune bad memory on sight: a stale/superseded/wrong recall hit poisons every future recall, so `memorize-prune {key}` deletes it (text + embedding); pruning bad memory matters more than preserving good. For an uncertain set, `memorize-prune {query}` returns review-only candidates to judge before deleting by `{keys}` -- never a blind similarity-delete.
91
-
92
- ## Constraints
93
-
94
-
95
- ## Return to plugkit
96
-
97
- Any uncertainty about the next move -- drift, a gate denial, a silent stretch in a non-trivial phase -- is itself the signal to dispatch `instruction`, because your memory of the prose went stale the moment phase/PRD/mutables shifted. It is cheap, synchronous, idempotent; the cost is all on the under-dispatch side. Every gate denial names the next verb in its `reason` field; read it and dispatch that verb, never improvise around the denial -- a denial with no follow-up dispatch is a session that gave up, and the chain is not COMPLETE while you have given up.
98
-
99
- Transition: SESSION_ID threaded AND spool reachable -> dispatch `instruction` with `{"prompt":"<user request>"}` so plugkit derives orient_nouns + recall_hits; later same-chain dispatches may use empty body.
@@ -1,70 +0,0 @@
1
- # EXECUTE
2
-
3
- YOU are the state machine. Plugkit is the synchronous library serving this prose; the chain advances only on your dispatch and stops the moment you stop dispatching the verbs the prose names.
4
-
5
- L3 distance + audit: real input -> real code -> real output, witnessed.
6
-
7
- ## Mutable-gate (hard rule)
8
-
9
- EXECUTE's job: drain every pending mutable to resolved before EMIT. Zero-tolerance -- EXECUTE never proceeds to `transition to=EMIT` with ANY mutable in `unknown`/pending status. Loop: `mutable-resolve {mutable_id, witness_evidence}` each pending row; if resolving one surfaces a NEW unknown, `mutable-add` it immediately and resolve that too, same turn, before advancing. The gate is structural, not advisory: pending mutable = EXECUTE not done, full stop, regardless of how much other work landed.
10
-
11
- Route every mutation through PRD rows, mutables, KV memos; attach an audit tuple `(id, hash, ts)` to each accepted write, where `hash` is the witness (`file:line`, codesearch hit, exec snippet). `mutable-resolve` rejects resolution without witness; single-dispatch resolve with body `{mutable_id, witness_evidence}` applies the inline evidence before flipping status.
12
-
13
- **Search-only-via-verb, hard rule, binds mid-EXECUTE hardest.** Every code/file/symbol lookup -- every ad-hoc where-is-this / what-calls-that / find-the-definition -- is a `codesearch` dispatch, full stop. Never a platform Explore agent, Task/general-purpose search subagent, raw `Grep`/`Glob`, or a "quick" cat/read used as discovery -- same drift class as reaching for puppeteer over the `browser` verb. Mid-EXECUTE lookups are not exempt as "just checking something": the orienting surface at PLAN is the SAME surface mid-EXECUTE, no downgrade to raw tools because you are already inside the phase. Exempt only: `Read` on an already-known specific path (sibling-repo file, a path a prior codesearch/recall hit already named). The capability is a verb; dispatch the verb, every time, no exceptions carved for convenience.
14
-
15
- ## Always-rearchitect-immediately (hard rule)
16
-
17
- An in-spirit architectural improvement discovered mid-EXECUTE -- clearly better, not merely different -- is neither a note-for-later nor "finish this pass first." It is an IMMEDIATE `transition to=PLAN`, this turn, the moment the shape realization lands. Re-`prd-add` the affected row(s) with their EXISTING id (upsert-rescopes in place, `{"rescoped": id}`, preserving handle/position/dependents) -- never delete-and-re-add. Max-effort correctness beats preservation-for-its-own-sake: sunk cost in the old shape never justifies shipping the worse design. The urge to write "I should rearchitect this" IS the trigger -- narrating it instead of dispatching `transition to=PLAN` strands the chain pointed at a stale plan. Generalizes the additive-vs-reshaping split below: reshaping is always immediate, never batched, never deferred.
18
-
19
- ## Witness
20
-
21
- You reason in code, not silent prose: an unrun thought is a guess. The hypothesis becomes `exec_js`/`codesearch`/`page.evaluate`; its output is the conclusion. Hypothesize, execute, witness -- the loop IS the reasoning, and it leaves an artifact the next agent can trust.
22
-
23
- Witness IS the distance measurement: an observable artifact means `d(state, goal)` decreased. Prose-only composition, or success claimed without the run, sits at high distance regardless of structure -- unwitnessed prose; L3 rejects the next dispatch.
24
-
25
- Witness code on the surface it runs, same turn -- a pass on surface A is not witness for code on surface B. Browser surface: dispatch `browser` (`in/browser/<N>.txt`, raw JS, globals `page`/`snapshot`/`screenshotWithAccessibilityLabels`/`state`; `session new|list|close <id>`).
26
-
27
- **Client-side edits force a same-turn browser dispatch.** Write/Edit on `.html .js .jsx .ts .tsx .vue .svelte .mjs .css` or any `<script>`/`import`-reached browser-entry path requires, same turn, a `browser` Write to `.gm/exec-spool/in/browser/<N>.txt` `page.evaluate`-ing the edit's invariant, plus its Read. No staging "validate later" -- later never arrives. `transition to=EMIT` refuses on dirty client-side files lacking a paired same-turn browser-witness; `deviation.client-edit-no-witness` fires, re-execute with the witness dispatch.
28
-
29
- ## Surface -> mutable
30
-
31
- State diverging from the PRD's assumed shape = new mutable, not noise: name, witness, resume -- same treatment as a named target. External-blocked, no reachable witness -> `blockedBy: external` on the PRD row.
32
-
33
- ## Discovery: additive vs reshaping
34
-
35
- Real input is the highest-yield discovery surface; every observation -> PRD row this turn, never "future work" -- corner case, tool caveat, failure mode, adjacent file/import, deviation-bearing stderr, rule-violating prior commit, untriaged residual, missing browser-witness, all rows, list never closed. Sparse-cover discovery expands outward; narrowing inward to ease completion-claims is forbidden.
36
-
37
- Two kinds, two moves. **Additive** (sibling the cover missed): `prd-add`, stay in EXECUTE. **Reshaping** (scope/approach/dependency-shape change to an existing row or the plan): rewrites a DAG node already held -> re-cut the cover, `transition to=PLAN` (always legal from EXECUTE; only `to=COMPLETE` gates), re-scope via `prd-add` on the row's EXISTING id (upsert-rewrites, `{"rescoped": id}`, preserves handle/position/dependents -- never delete-and-re-add). The urge to write "I need to re-scope" IS the planning event -- dispatch `transition to=PLAN`, do not narrate it.
38
-
39
- ## Maturity-first
40
-
41
- First emit = closure. Scaffold + IOU externalizes residual cost as state never revisited. Closure exceeding session reach -> Maximal Cover DAG (each node a closed transform), never a schedule.
42
-
43
- ## Engineering invariants (shape of the code you land)
44
-
45
- Data first -- correct structures/invariants make the code write itself; convoluted control flow signals a wrong data model, fix the model not the flow. Make invalid state unrepresentable -- parameters over hidden globals, the type/shape encodes the constraint so the bad combination cannot be constructed. Reason from physical constraints (latency, bandwidth, memory, coordination, worst node) before designing within them. Flat spine, single-focus units, call-site-legible. Misuse structurally impossible, never merely documented-against. Optimize worst case not average; every failure path explicit (full -> degraded -> safe-fail -> explicit-error), no silent catastrophic mode. Measure, never assume -- profile before optimizing, A/B on real input only in genuine dispute. Regression -> revert first, diagnose from known-good base second. Fail fast and loud over limping on bad state.
46
-
47
- **Process of elimination is the debugging paradigm on every surface; manual labour against real services is how you witness.** Thinking-in-code at its sharpest: each candidate cause is a hypothesis, tested by running it, never reasoned around. No guess-and-restart, no a/b-test, no shotgun variants: enumerate candidates as mutables, eliminate each by REAL-input witness -- `exec_js` on the real service, `codesearch`/`Read` on real source, `browser`'s `page.evaluate` on a live `window.*` global, monkeypatching each candidate function/handler in-page to isolate which one actually fires. Each elimination reveals the next mutable; iterate to single-cause-survives. One live-runtime read outweighs a hundred blind restarts.
48
-
49
- **Before the first hypothesis, name the loop that will falsify it.** A hard bug gets a single named command -- an `exec_js`/`browser` dispatch, a CLI invocation, a curl against a live dev surface -- that is red-capable (drives the exact reported symptom, not a nearby one), deterministic (same verdict every run; for flaky bugs, driven to a high-enough reproduction rate to debug against), and fast. Name and run that command once, unmodified, before reading code for a theory -- reading code first to build a theory is the failure this clause exists to catch. Every mutable elimination pass afterward reuses the same loop; a loop that only sometimes reproduces gets tightened (narrower scope, pinned time/seed, isolated fs/network) before it's trusted as a witness surface.
50
-
51
- Profile the real surface, never intuit. `exec_js`: `duration_ms` free, own timing + `process.memoryUsage()` on stdout, thrown-`stack` on stderr -- read both channels. Browser: `capture\n<script>` prefix auto-returns `{result, debug:{console, pageErrors, network, performance}}`, zero boilerplate. Slow-node-not-obvious: `exec_js opts.profile:true` / browser `profile\n<script>` prefix both return `{result, profile:{timeframe:{start_us,end_us,total_us,sample_count}, culprits:[{location,function,self_us,self_pct,hits}]}}` -- worst-N `file:line` self-time, identical shape both surfaces. Both also return `mem` (rss/heap/delta) and `wall_vs_cpu:{wall_us, offcpu_us}` -- sampler sees only on-CPU JS, large `offcpu_us` = IO/async-wait/GPU time invisible to it; tune via `opts.sampleIntervalUs`/`opts.profileTopN` (cli) or `interval=`/`topN=` (browser). Cheap non-profile path: `opts.mem:true` -> `{result, mem, wall_ms}` + structured `error:{name,message,stack}` on throw -- read `error.name` directly; default path (no `opts.mem`) byte-unchanged. CPU sampler is GPU-blind -- wall >> CPU self-time on render/canvas/WebGL -> browser `trace\n<script>` prefix opens CDP Tracing, returns `trace:{wall_us, gpu_us, viz_us, cc_us, by_category}`. Profile to LOCATE, then eliminate by live measurement. Verification is the same labour: run the real thing, witness the real output (live page, real service) -- never a unit/mock harness standing in for real-services witness. Apparent tooling failure is the same mechanical self-recovery-by-elimination, never a question for the user.
52
-
53
- ## No test files, ever (hard rule)
54
-
55
- Never create a unit-test file, spec file, `test/`/`__tests__/`/`spec/` directory, a standing root-level test file, or reach for a testing framework (jest, mocha, vitest, pytest, unittest, junit, or any assertion/mocking library) to satisfy a PRD row -- doing so IS the deviation this rule exists to name, not a reasonable interpretation of "add coverage." A row asking for validation/edge-case handling is satisfied by the code path itself, exercised live via `exec_js`/`browser` with the output witnessed in the response -- manual troubleshooting and debugging is the entire verification surface. Discovering an existing test directory or test file mid-EXECUTE is itself a PRD row: delete it, replace its assertions with live witness dispatches, `prd-add` it now rather than adding to it.
56
-
57
- ## Memorize
58
-
59
- Write the recall index only via `memorize-fire`; other surfaces produce memos the index never sees. Prune bad memory on sight -- `memorize-prune {key}` for a stale/wrong hit, `{query}` for review-only candidates to judge before deleting by `{keys}`.
60
-
61
- ## Constraints
62
-
63
-
64
- ## Dispatch
65
-
66
- Spool every exec. Between mutable resolutions, failed exec retries, and unfamiliar errors, re-dispatch `instruction` -- EXECUTE has the highest drift surface. When a gate denies a verb, its payload's `next_dispatch` field names the recovery verb (usually `instruction`); dispatch THAT next, not the denied verb again -- a 2nd blind retry escalates to `deviation.long-gap-retry-without-instruction`.
67
-
68
- - Mutables: `mutable-resolve` body `{"mutable_id": "<id>", "witness_evidence": "<file:line | codesearch hit | exec snippet>"}`.
69
- - PRD rows: `prd-resolve` body `{"id": "<id>", "witness_evidence": "<...>"}` (top-level `id`/`prd_id` beside `witness_evidence`; bare-id body works but loses the audit trail; never nest the whole envelope as a string). `deviation_kind: prd-resolve-unknown-id` means the id missed -- read the `hint` field and re-dispatch corrected, never blind.
70
- - `transition` when the slice is closed and every mutable is witnessed; `transition to=PLAN` on a new unknown or reshaping discovery.
@@ -1,52 +0,0 @@
1
- # PLAN
2
-
3
- YOU are the state machine. Plugkit is the synchronous library serving this prose; every state change is a verb you write into the spool, and nothing happens while you wait.
4
-
5
- L1 baseline + L2 covering family. You loaded prior memory on entry via `instruction`.
6
-
7
- ## Orient
8
-
9
- First non-trivial dispatch = single-message parallel fan-out, `recall` + `codesearch`, against request nouns. Query beats recalled-from-memory assumption. Hits = baseline; misses = fresh ground. Skip orient -> plan reasoned from stale memory, not witnessed tree-read.
10
-
11
- **Search-only-via-verb, hard rule.** `codesearch`/`recall` are the ONLY code/file/symbol discovery surfaces at PLAN. Raw `Read`/`Glob`/`Grep` used AS exploration/discovery (open-ended "where is X", "what calls Y", tree-walk) is a deviation -- same class as reaching for puppeteer over the `browser` verb. Exempt: `Read` on a SPECIFIC already-located path (e.g. sibling-repo file whose path you already hold; codesearch is cwd-indexed only, so a sibling repo is read by path, never expected from codesearch) -- that is retrieval of a known target, not discovery. `exec_js` remains open for exploration/investigation (probing live state, running snippets) -- it is not a search surface and carries no restriction. The line: known-path fetch = `Read` OK; discovery/search = verb only, always.
12
-
13
- ## Cover
14
-
15
- PRD = `|F|=1` plan-item store: enumerate every node in the destructive transform's closure, a dependency DAG cut along dependency edges, never schedule. Reach admits the next node. Smaller-slice-while-larger-reachable = non-monotonic, rejected. `prd-add` every in-spirit reachable residual, one-line witness per add.
16
-
17
- **Maximal expansiveness, hard rule.** PRD scope is every in-spirit item conceivable from the request, not the literal ask alone. Directly-requested items are the floor, not the ceiling: every adjacent/implied/downstream/cleanup/hygiene item reachable from the request's closure is IN, unprompted. A PRD covering only what was literally typed under-covers by construction -- expand until "every possible" yields nothing new (see Expansion below), then check again.
18
-
19
- **Inherited rows resume first.** `ready_wave`/`prd_pending>0` at entry = undone transform, not someone else's -- THIS cover's first slice. Resume to `prd-resolve` (witnessed) or explicit re-scope/close before any fresh row; disjoint fresh cover orphaning inherited rows = stopped mid-transform, not finished.
20
-
21
- **`prd-resolve` at PLAN is bound by the same false-completion rule as VERIFY, not exempt because the row was inherited.** A `prd-resolve` whose `witness_evidence` says "deferred"/"pending next session"/"pending browser fix"/"awaits [X] recovery"/"user must refresh" is marking undone work done -- forbidden regardless of phase. Real external blockers (browser environment down, credential missing, another team's repo) keep the row `status: pending` with `blockedBy: [external, "<specific reason>"]`; they are never grounds to resolve as `completed`. A session that repeatedly hits the same external blocker (e.g. browser verb crashing every attempt) `prd-add`s a row naming the blocker itself as the thing to fix -- diagnose and repair the tool (see BROWSER discipline), not paper over it with a completed status on the original row.
22
-
23
- "Every possible" load-bears: apply to every noun/surface/transform/output the request reaches, each application a row. Single-digit count on non-trivial request = stopped early -- re-orient, re-enumerate. Density, not minimality, is the COMPLETE-time invariant. Inline TODO in response body violates `|F|=1`.
24
-
25
- ## Expansion
26
-
27
- Second transform over the first pass: for each row, corner case/caveat/failure mode/adjacent-row interaction/degenerate input/empty-overflow-reentry state -> new row. Validations, edge cases, anticipated mutables are first-class rows. Closes when "every possible" yields nothing new, not on feeling done. 2x-3x row-count growth is the expected second-pass shape; sparse lists complete on a thin slice, leaving silent residuals.
28
-
29
- **A validation/edge-case row is closed by real execution, never by a test file.** The row's satisfaction is an `exec_js`/`browser` dispatch witnessing the case live -- never a `*.test.js`/`*.spec.js` file, never a `test/` or `__tests__/` directory, never pulling in jest/mocha/vitest/pytest/unittest or any assertion/mocking library, and never a standing test file of any kind. Enumerating edge cases at PLAN is not license to author a suite for them at EXECUTE; see VERIFY's Adversarial corner-case sweep for how each class actually gets witnessed.
30
-
31
- Cut the cover hardest-node-first: the row exercising the most failure modes at once (concurrency + partial failure + real input, colliding) proves the design early, while re-cutting is still cheap -- schedule it last and you validate nothing until reshaping is too late.
32
-
33
- ## Noticing-to-PRD
34
-
35
- Any observation not yet a row -- outstanding work, unfinished surface, improvable shape, preference misalignment, adjacent concern -- is `prd-add` this turn; response-body-only observations evaporate at turn end. Structural noticing (coverage gap, missing doc, rule-violating prior commit) and preference-aware noticing (drift from density-at-PLAN/residual-triage/push-on-clean/every-possible-expansion/browser-witness) are the same event: each its own row, witnessed by what surfaced it.
36
-
37
- **A genuinely unrelated issue discovered mid-task is `prd-add`, never a same-turn detour and never dropped.** "Unrelated" means outside this cover's own closure -- a bug/gap/hygiene issue the current transform did not touch and does not depend on. It still gets a row (never silently ignored, never fixed inline burning the current cover's focus, never mentioned in prose and left unrecorded) so a later cover picks it up deliberately.
38
-
39
- `prd-resolve` accepts an optional `commit_comment` (aliases `commit_message`, `resolution_note`) alongside `id`/`witness_evidence` -- a one-line resolution note. When present, the next `git_commit`/`git_finalize` in that repo bundles it into the commit message body under a "Resolved PRD rows" section and clears the row from `.gm/prd.yml` (deleted, not archived -- the commit message is the durable record). Pass it whenever the resolved row's story is worth a line in git history; omit it for rows too granular to warrant one.
40
-
41
- ## Mutables
42
-
43
- Unknowns -> `.gm/mutables.yml` via `mutable-add`, `status: unknown`, witness = `file:line`/codesearch hit/exec output. Narrative resolution rejected; unwitnessed rows block every `transition`. Uncertain mid-plan (orient-to-PRD gap, unweighted recall hit) -> re-dispatch `instruction`, never invent the next step from memory.
44
-
45
- ## Constraints
46
-
47
-
48
- ## Dispatch
49
-
50
- Verbs: `recall`, `codesearch`, `prd-add`, `mutable-add`, `mutable-resolve`, `transition`. Plugkit holds phase on disk; you advance it by writing `transition`.
51
-
52
- `prd-add` takes `id` -- kebab-case slug (`dedupe-update-error`). Always pass it explicitly. Omitting `id` is NOT silently auto-generated: the handler tries to derive a slug from `subject`/`title`/`name`/`task`/`goal`/`description`/`notes`, and if none of those yield usable text either, the call is HARD-REJECTED (`deviation.prd-add-no-id`, no row written) -- retrying the identical no-id call repeats the same rejection forever, burning turns. On rejection: add `id` directly, or add one of those text fields, then re-dispatch. Upsert semantics: fresh id appends (`{"added": id}`), existing id rewrites in place (`{"rescoped": id}`) preserving position/dependents -- the re-scope path on EXECUTE->PLAN reshaping discovery; never delete-and-re-add (orphans the handle). Re-entry to PLAN is first-class, not failure.
@@ -1,39 +0,0 @@
1
- # UPDATE-DOCS
2
-
3
- YOU are the state machine. Plugkit is the synchronous library serving this prose; docs do not update themselves -- you dispatch every edit and every push.
4
-
5
- Docs reflect the current state of the system, not its history. Every rule in AGENTS.md is present-tense -- what must or must-not be the case in code now. Past-tense framing, `(FIXED)` markers, dated audit entries, and "we used to X, now Y" belong in `git log` and `CHANGELOG.md`, never AGENTS.md.
6
-
7
- ## AGENTS.md and CLAUDE.md
8
-
9
- Edit AGENTS.md/CLAUDE.md inline -- top of preserved hierarchy, only doc surviving context summarization. `memorize-fire` = parallel surface (`.gm/exec-spool/in/memorize-fire/<N>.txt`, raw text or `{text, namespace?}`) where `recall`/`auto_recall` retrieve the fact future turns. AGENTS.md = staging ground; store = recall surface. Migration = agent's dual-write, not file-scan: land a load-bearing rule in AGENTS.md -> fire same rule to store same session so it surfaces in `auto_recall`. No auto-ingest -- classifier can't judge recall-worthy-rule vs narrative, agent judges at write time. Never `namespace:"AGENTS.md"` (mislabeled); load-bearing rules -> default namespace. Multiple facts = multiple parallel requests, one message.
10
-
11
- **Migration is bidirectional; back-pressure = deflation -- every memorize run also drains AGENTS.md.** Inward-only flow bloats past budget. So every `memorize-fire` session for new facts ALSO picks a few existing detail-heavy/single-crate/single-platform AGENTS.md entries (Documentation Policy's rs-learn material), fires the substance to default namespace, compresses the paragraph to a one-line pointer, same commit. Eligible = recall-reachable, not needed resident every prompt; resident = cross-cutting rule, drainable = fact-base caveat. Top-level rules stay; recall-reachable drains. Witness both ways: fact lands in store AND byte-count drops. Few entries/run, never wholesale rewrite. Skipping the drain = the slow-bloat drift this policy prevents.
12
-
13
- ## README.md
14
-
15
- Refresh to the surface a new reader actually encounters: remove every stale install step, version pin, and gone feature; add what you added this session if it changes the public surface.
16
-
17
- ## docs/index.html
18
-
19
- Regenerate/hand-edit to the same surface. Site builds run from `site/`; the deployed `/` route renders from `site/content/pages/home.yaml` via flatspace. Route landing edits through `site/theme.mjs` (Hero) and the YAML, never `site/index.html` directly. `docs/styles.css` is generated from `site/input.css` -- append to the source, not the output.
20
-
21
- ## CHANGELOG.md
22
-
23
- One entry per commit landed this session: the commit subject plus a one-sentence "why", no recipe. CHANGELOG carries the history AGENTS.md refuses.
24
-
25
- ## Commit and Push
26
-
27
- Stage doc updates only -- never bundle them with code changes from earlier phases (committed at their own time). One commit, present-tense imperative subject. Push via the git verbs: `git_finalize {message}` bundles add -> commit -> porcelain-gate -> push in one dispatch, or `git_add` the doc paths then `git_commit` then `git_push`. The verbs gate on the porcelain probe internally and refuse a dirty tree (`deviation.push-dirty`); a raw `git` shell body is gated `deviation.bash-git-bypass`. If you ever fall back to raw Bash git, the porcelain probe is its own `Bash(git status --porcelain)` event before the push, never `&&`-chained -- a chained `add && commit && push` carries no separable witness, so ccsniff `--git-discipline` sees an unwitnessed push. A doc commit stages only paths matching AGENTS.md, CLAUDE.md, README.md, SKILLS.md, CHANGELOG.md, LICENSE*, docs/**, or site/**; any non-doc path means you bundled phases -- split it out before staging. The push triggers the docs pipeline and IS the validation dispatch.
28
-
29
- ## COMPLETE
30
-
31
- Terminal phase. After the push lands, dispatch `transition` to COMPLETE; plugkit records the chain concluded.
32
-
33
- **Once `phase=COMPLETE` and `prd_pending_count=0`, the chain is closed to further `instruction`/`phase-status` polling -- it is NOT closed to the mandatory `gm-continue` handoff.** Do not re-dispatch `instruction` to "check" -- the response is the same UPDATE-DOCS prose, and re-polling that surface is a deviation (`turn.start`/`turn.end` pairs with `dispatches:1`, instruction-as-polling). But the calling agent's own harness (Claude Code, or any host driving the `gm` skill) still owes ONE more dispatch before the turn genuinely ends: `Skill(skill="gm-continue")`. That skill does its own independent remaining-work check and either reloads `gm` (real work found, or first confirming pass this session) or reports the loop closed (nothing found, already confirmed once). This is not an `instruction`/`phase-status` re-poll -- it is a different tool (`Skill`) doing a different job, and it is what actually makes "stop" mean stop instead of leaving the user to manually re-prompt "list remaining limitations" in a new session. A new user request resets phase to PLAN on the first instruction dispatch with a fresh prompt body regardless.
34
-
35
- **A response with no further tool call after this point IS the conversation ending, permanently -- not a pause the user resumes.** Writing "Session N closes, ready to resume with /gm," listing architecture decisions made as a wrap-up, or any other closing/summary sentence as the last content of a response has the exact same effect as never continuing again: there is no future turn where this same chain picks back up on its own. The only way "done" actually produces a next action instead of silence is dispatching `Skill(skill="gm-continue")` as the literal last thing in this same response -- not next time, not after the summary, in place of the summary.
36
-
37
- ## Dispatch
38
-
39
- `phase-status` to confirm chain state, then `transition` to COMPLETE if not already. After COMPLETE lands, dispatch `Skill(skill="gm-continue")` -- that is the actual stop condition, not the bare `transition` response.
@@ -1,85 +0,0 @@
1
- # VERIFY
2
-
3
- YOU are the state machine. Plugkit does not validate in the background -- you read the four observations and decide whether to `transition`.
4
-
5
- L3 trajectory; `transition` iff every observation is convergent.
6
-
7
- ```
8
- [worktree-clean] [remote-pushed] [prd-empty] [mutables-witnessed]
9
- ```
10
-
11
- All four true = convergence -> `transition`. Any false defers, holds, or regresses.
12
-
13
- ## Push and worktree-clean
14
-
15
- `git_push` is the only admissible push surface, any repo, any cwd -- runs `[worktree-clean]` porcelain probe internally, refuses dirty. `git_finalize {message}` bundles add -> commit -> probe -> push. Sibling push: `git_push {repo:"<abs>", branch:"<branch>"}` (probes inside target tree). Raw `git` shell body gated `deviation.bash-git-bypass`; `cd <repo> && git push` bypasses the probe even clean, ccsniff flags every raw push. Raw-Bash-git fallback: `git status --porcelain` its own Bash tool-use event before push, never `&&`-chained -- ccsniff `--git-discipline` scans the last 20 Bash events, one chained event has no witness. Non-empty bytes = unstaged residual: stage-commit or revert first, a dirty-tree push advances an unwitnessed slice and breaks the next session.
16
-
17
- ## CI
18
-
19
- Verification is thinking run rather than reasoned: "is this correct?" is executed, not argued -- real test, real matrix, real page answer it. The push IS the validation dispatch. Local proof covers one platform; matrix covers all. Red = divergent observation holding the trajectory until cause-named and green re-pushed; toolchain skew converges, does not stop.
20
-
21
- ## Adversarial corner-case sweep (hard rule)
22
-
23
- VERIFY is adversarial, never confirmatory: hunt every way EMIT's write breaks, via real `exec_js`/`browser` execution, never prose reasoning. Each class below gets its own exec_js/browser dispatch witnessing outcome (pass or found-and-fixed) before transitioning on; a reachable-but-unswept class is not an implicit pass:
24
-
25
- - **empty/overflow/reentry**: zero-length input, max-size/overflow input, same op mid-flight (reentrant call).
26
- - **concurrency/races**: two writers same surface, interleaved ordering, TOCTOU windows (check-then-act where atomic was required).
27
- - **partial failure**: crash/kill mid-op, multi-step write partial success, network/IO cut mid-call.
28
- - **degenerate input**: null/undefined, wrong type, malformed encoding, boundary-adjacent-invalid values.
29
- - **boundary conditions**: off-by-one, exact-limit values (0, 1, max, max+1), collection first/last element.
30
- - **injection**: untrusted input reaching shell/query/eval/template-render unescaped.
31
- - **resource exhaustion**: unbounded loop/recursion, unclosed handle/session, memory growth under repeated calls.
32
- - **adjacent-row interaction**: does this row's change break an already-landed sibling's invariant -- exercise the interaction, not each row solo.
33
-
34
- Each class exercised = exec_js/browser dispatch + witness (pass or fix-then-rewitness), same turn, before `transition`. A happy-path-only VERIFY has not verified.
35
-
36
- ## Real-execution witness
37
-
38
- Every claim of correctness is proven by a live `exec_js`/`browser` dispatch witnessing the real output, same turn, real services only (mock-free) -- manual troubleshooting and debugging is the entire verification surface, never a standing test file or suite. Pass = the live witness matches expectation; fail -> `transition` back to EXECUTE. `recursive` classifier = incomplete cover -- snake back, do not narrate past signal.
39
-
40
- **No test files, no exceptions.** A `deviation.synthetic-test-file` at VERIFY (new `*.test.*`/`*.spec.*`, a `test/`/`__tests__/` directory, a testing-framework import, or any standing root-level test file found in the diff) blocks `transition` exactly like an unwitnessed mutable -- delete it and replace its assertions with a live `exec_js`/`browser` witness, then re-verify. This is manual legwork, not framework legwork: the adversarial corner-case sweep above is how every class gets exercised, by running the real thing, never by writing a test case that runs it later.
41
-
42
- **No fake shipped code, checked at VERIFY too, not only at EMIT.** A `Mock*`/`Fake*`/`Stub*` class or a hardcoded always-succeeds/input-invariant short-circuit anywhere in the diff is the same class of deviation as a test file -- grep the diff for these names before transitioning; a mock network/service/client shipped as if it were the real integration is exactly what "nothing fake in source" forbids, whether or not it lives in a `test/` directory. Real input through real code into real output is the only acceptance shape.
43
-
44
- **Documenting a hard row instead of implementing it is a false completion, not a resolution.** `prd-resolve` refuses two identical/near-identical `witness_evidence` strings across different PRD ids (`deviation.prd-resolve-duplicate-witness`) -- generic copy-pasted witness text ("code written and tested") across structurally distinct rows is the rubber-stamp tell: a row that got a README section titled "Future Optimizations"/"Not Yet Implemented" and a `status: completed` in the same turn was NOT solved, it was deferred with different words. If a row is genuinely out of reach this turn, it stays open or gets `blockedBy: [external]` with a real, specific reason -- it does not get marked completed because a design doc for it now exists. Each row's witness must name the specific file:line/exec-output/browser-read proving THAT row's own distinct deliverable, not a shared paragraph.
45
-
46
- ## Residual-scan
47
-
48
- `residual-scan` is dispatched BEFORE `transition to=CONSOLIDATE` -- the CONSOLIDATE entry gate refuses without its fired marker, and the denial names `residual-scan` as the next dispatch. It examines the open surface -- PRD pending, browser sessions, dirty tree, untracked artifacts, browser-witness coverage for session-modified client-side files -- non-empty = non-convergent -> expand PRD with the reachable in-spirit residual, re-execute. One-shot per stop window via marker. `reason: "browser sessions still open"` -> close each (`browser` `session close <id>`; `session list` enumerates); rescanning without closing is idle-mid-chain/polling deviation -- the denial names the next verb, dispatch it.
49
-
50
- Before accepting an empty scan, re-apply "every possible" to the closing PRD: every resolved row's skipped variant, every touched adjacent surface, every validation proving a row in practice not claim -- each hit is `prd-add` + re-execution. Clean scan on a short PRD for a long-horizon prompt is a false negative. Noticing-to-PRD holds unchanged here: anything observed while testing/reading diffs/inspecting closing state converts and re-executes same turn; stopping at "tests pass" while noticing named follow-on work is the canonical VERIFY drift.
51
-
52
- **Every `git status --porcelain` entry triaged this turn -- "pre-existing" is not a stop excuse.** Dirty worktree: commit (real work), managed-gitignore-block it (transient runtime emission, e.g. `.gm/witness/`, `.gm/exec-spool/.*-stale.json`), or revert (junk). "Pre-existing" names a triage outcome, never the stop; `blockedBy: external` only when triage needs outside-session authority -- local-tree files always have local authority. `.gm/disciplines/` tracked; new memorize-fire `mem-*.json` committed.
53
-
54
- ## Browser-witness coverage
55
-
56
- Before COMPLETE, every session-touched client-side file needs a `browser.witness-marked` event whose `witnessed_hashes` match current sha. Check enumerates every changed `.html .js .jsx .ts .tsx .vue .svelte .mjs .css` (or HTML-imported path); mismatch/absence fires `deviation.browser-witness-hash-mismatch`/`deviation.browser-witness-missing`, residual-scan refuses, regress to EXECUTE and re-witness against the live page. The page is sole authority; disk-Read is necessary, insufficient.
57
-
58
- ## Trace to a human outcome
59
-
60
- Before accepting the slice convergent, trace every shipped change to a human outcome -- capability gained, wait removed, failure no longer hit, a developer the interface stops fighting. Impact chain ending in technical elegance with no reachable human = aesthetics, revert candidate (DX, worst-device performance, the one person who can now build alone all count). Self-reported quality runs higher when the trajectory delivered one.
61
-
62
- ## Witness over claim
63
-
64
- `witness_evidence` of the admitted form attaches to every mutable in the closing slice. Resolved-in-response without resolved-in-store = a dispatch not fired.
65
-
66
- ## Completion
67
-
68
- Chain enters COMPLETE only when your `transition` returns COMPLETE phase; on-disk state moves only on `transition`. **Done is plugkit's pronouncement, not yours** -- gate-allowance is not done, only a dispatched `transition` returning COMPLETE is; a narrated PLAN->COMPLETE walk with the gate open or the verb un-dispatched is fabrication. "I'm finished" before the gate flips is narrating; cure = `phase-status`. Not-COMPLETE means a next transition exists; idle/"waiting for the user" mid-chain are deviations (closure authorized at request time). Between the four observations, between residual-scan and COMPLETE, between push and transition, re-dispatch `instruction` -- VERIFY is where premature COMPLETE happens.
69
-
70
- **You close rs-learn's learning loop here -- you are the model in the loop, holding the one signal every core needs: how well this turn went.** Before COMPLETE, self-report turn quality 0.0-1.0 (1.0 = clean convergent walk, no deviations, pushed, gate-allowed; lower as deviations/rework accumulate). That number drives every core, none observable to it without you:
71
-
72
- - **Instant MicroLoRA adapter:** only >=2 enabled disciplines (default-only = correct no-op, skip). Init once/session `learn {verb:init_instant, body:{targets:[<enabled namespaces>]}}`, then `learn {verb:feedback, body:{embedding:<bge embedding of this turn's task>, payload:{quality:<0-1>}}}` -- high quality shifts the helpful namespace's logit up so recall promotes it.
73
- - **FastGRNN router:** `learn {verb:record_outcome, body:{target:"<your model id>", quality:<0-1>}}` so the next `route_hint` reflects learned outcomes.
74
- - **Deep EWC core** and **GAT attention relation weights:** same quality as `record_loss` / `nudge_relation`.
75
-
76
- rs-learn never calls a model; it emits the need, you supply the answer. Skipping the self-report leaves the cores untrained.
77
-
78
- **No summary, no prose-only turn here.** A summary, recap, announced-but-undispatched next move, or any tool-less message IS a stop -- VERIFY is where the temptation peaks. Until this surface returns phase=COMPLETE after `transition`, every turn ends in a verb (`phase-status`, `residual-scan`, the push verbs, `instruction`, `transition`). Catching yourself composing a summary IS the drift signal -> dispatch `phase-status` instead.
79
-
80
- ## Constraints
81
-
82
-
83
- ## Dispatch
84
-
85
- `transition` to COMPLETE only when the four-observation window is fully true; the handler hard-rejects while any open mutable or PRD item remains.
@@ -1,36 +0,0 @@
1
- import { readFileSync, writeFileSync, existsSync } from 'node:fs';
2
- import { join, dirname } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
-
5
- const here = dirname(fileURLToPath(import.meta.url));
6
- const bundleDir = join(here, '..', 'instructions');
7
- const rsDir = process.env.RS_PLUGKIT_DIR
8
- ? join(process.env.RS_PLUGKIT_DIR, 'src', 'orchestrator', 'instructions')
9
- : join(here, '..', '..', '..', 'rs-plugkit', 'src', 'orchestrator', 'instructions');
10
-
11
- const keys = ['entry', 'plan', 'execute', 'emit', 'verify', 'consolidate', 'update_docs', 'browser'];
12
-
13
- function rawString(md) {
14
- let pad = 1;
15
- while (md.includes('"' + '#'.repeat(pad))) pad++;
16
- const h = '#'.repeat(pad);
17
- return `r${h}"${md}"${h}`;
18
- }
19
-
20
- if (!existsSync(rsDir)) {
21
- console.error(`rs-plugkit consts dir not found: ${rsDir}\nset RS_PLUGKIT_DIR to the rs-plugkit checkout root`);
22
- process.exit(1);
23
- }
24
-
25
- let changed = 0;
26
- for (const key of keys) {
27
- const mdPath = join(bundleDir, `${key}.md`);
28
- const rsPath = join(rsDir, `${key}.rs`);
29
- if (!existsSync(mdPath)) { console.error(`missing bundle: ${mdPath}`); process.exit(1); }
30
- const md = readFileSync(mdPath, 'utf8').replace(/\r\n/g, '\n');
31
- const next = `pub const TEXT: &str = ${rawString(md)};\n`;
32
- const prev = existsSync(rsPath) ? readFileSync(rsPath, 'utf8') : '';
33
- if (prev !== next) { writeFileSync(rsPath, next); changed++; console.log(`synced ${key}.rs (${md.length} chars)`); }
34
- else console.log(`unchanged ${key}.rs`);
35
- }
36
- console.log(`done: ${changed}/${keys.length} consts rewritten`);