gm-skill 2.0.1412 → 2.0.1414
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 +44 -44
- package/README.md +15 -15
- package/bin/plugkit.version +1 -1
- package/bin/plugkit.wasm.sha256 +1 -1
- package/gm-plugkit/package.json +1 -1
- package/gm-plugkit/plugkit-wasm-wrapper.js +6 -1
- package/gm.json +2 -2
- package/package.json +1 -1
- package/skills/gm-skill/SKILL.md +22 -20
package/AGENTS.md
CHANGED
|
@@ -11,7 +11,7 @@ This project has task-specific skills available.
|
|
|
11
11
|
> This step is **non-negotiable** and applies to **every possible task** without exception.
|
|
12
12
|
|
|
13
13
|
**Steps to follow before every possible task:**
|
|
14
|
-
1. `view SKILLS.md
|
|
14
|
+
1. `view SKILLS.md`, discover every possible available skill
|
|
15
15
|
2. `view` every possible skill file that is plausibly relevant to the task
|
|
16
16
|
3. Only then proceed with the task
|
|
17
17
|
|
|
@@ -22,11 +22,11 @@ Skills encode environment-specific constraints that override general knowledge.
|
|
|
22
22
|
|
|
23
23
|
# Architecture & Philosophy
|
|
24
24
|
|
|
25
|
-
This repo IS the published `gm-skill` npm package. The repo root is the package root
|
|
25
|
+
This repo IS the published `gm-skill` npm package. The repo root is the package root, no factory, no build step that generates a separate output dir. `skills/gm-skill/SKILL.md` is the ~12-line entry point; every possible phase prose and orchestration logic lives in rs-plugkit and is served on demand via the `instruction` verb.
|
|
26
26
|
|
|
27
27
|
## WASM-only
|
|
28
28
|
|
|
29
|
-
The plugkit stack runs as a wasm cdylib loaded by `plugkit-wasm-wrapper.js` under Node/bun. No native binaries are built, downloaded, or published. The wasm artifact is small (
|
|
29
|
+
The plugkit stack runs as a wasm cdylib loaded by `plugkit-wasm-wrapper.js` under Node/bun. No native binaries are built, downloaded, or published. The plugkit orchestration logic compiles small (hundreds of KB), but the shipped `plugkit.wasm` artifact is ~149MB because it embeds the bge-small-en-v1.5 embedding model in-wasm (133MB safetensors + 711KB tokenizer, 384-dim, host_delegated:false, embeddings run wasm-side, offline, no text leak). The artifact is fetched at bootstrap from the `plugkit-wasm` npm package / `plugkit-bin` gh-releases (sha256-pinned in `bin/plugkit.wasm.sha256`), not bundled inside `gm-skill`. Atomic write to disk (temp + rename) is sufficient, no kill-before-rename mechanics apply.
|
|
30
30
|
|
|
31
31
|
## Spool dispatch ABI
|
|
32
32
|
|
|
@@ -36,11 +36,11 @@ Agents dispatch verbs by writing to `.gm/exec-spool/in/<verb>/<N>.txt` (request
|
|
|
36
36
|
|
|
37
37
|
**Wasm-direct verbs**: `fs_read`, `fs_write`, `fs_stat`, `fs_readdir`, `kv_get`, `kv_put`, `kv_query`, `fetch`, `exec_js`, `env_get`, `recall`, `codesearch`, `memorize`, `memorize-prune`, `health`, `filter`, `git_status`, `branch_status`, `git_push`.
|
|
38
38
|
|
|
39
|
-
**memorize-prune verb**: deletes bad/superseded memories
|
|
39
|
+
**memorize-prune verb**: deletes bad/superseded memories, pruning bad memory matters more than preserving good memory, since a wrong recall hit is worse than a miss. Two modes: explicit `{key}`/`{keys:[...]}` deletes exactly those mem keys (text + `-vec` embedding sibling) via the `host_kv_delete` host import; `{query}` returns review-only candidates (vector_top_k hits with keys) for the agent to judge, then re-dispatch with the stale `{keys:[...]}`. Query mode never auto-deletes by similarity, a blind similarity-delete is itself a bad-memory generator; the destructive step stays under agent judgment. Emits `memory.pruned` per deletion.
|
|
40
40
|
|
|
41
|
-
**git verbs**: `git_status` returns `{dirty, modified, untracked, deleted, staged}` from `git status --porcelain`. `branch_status` returns `{branch, ahead, behind, remote}
|
|
41
|
+
**git verbs**: `git_status` returns `{dirty, modified, untracked, deleted, staged}` from `git status --porcelain`. `branch_status` returns `{branch, ahead, behind, remote}`, the `remote-pushed` witness. `git_push` is the ONLY admissible push surface, it gates on `git_porcelain()` non-empty (refuses dirty), emits `deviation.push-dirty` on attempt, and shells the push only when clean. A raw `git push` via Bash bypasses the gate and is itself a deviation; ccsniff `--git-discipline` flags it.
|
|
42
42
|
|
|
43
|
-
**filter verb**: pure stdout → compact-stdout transformation. Body `{kind, input, ...opts}` where kind is one of `grep`, `ls`, `tree`, `json`, `diff`, `git-status`, `log`. Returns `{output, stats:{bytes_in, bytes_out, saved_pct, ...}}`. Pipe raw command output through filter before letting it enter context
|
|
43
|
+
**filter verb**: pure stdout → compact-stdout transformation. Body `{kind, input, ...opts}` where kind is one of `grep`, `ls`, `tree`, `json`, `diff`, `git-status`, `log`. Returns `{output, stats:{bytes_in, bytes_out, saved_pct, ...}}`. Pipe raw command output through filter before letting it enter context, rtk's role, in-wasm, no subprocess. Replaces the legacy detached rtk binary download in bootstrap.
|
|
44
44
|
|
|
45
45
|
## Documentation Policy
|
|
46
46
|
|
|
@@ -52,11 +52,11 @@ Only record non-obvious technical caveats that cost multiple runs to discover. R
|
|
|
52
52
|
|
|
53
53
|
## Coding Style
|
|
54
54
|
|
|
55
|
-
**No comments in code.** No inline, block, or JSDoc comments in every possible location
|
|
55
|
+
**No comments in code.** No inline, block, or JSDoc comments in every possible location, source, generated output, hooks, scripts.
|
|
56
56
|
|
|
57
57
|
**Skill SKILL.md files:** Strip explanatory prose. Keep ONLY invocation syntax, transition arrows, gate conditions, constraint lists, and code examples showing exact usage.
|
|
58
58
|
|
|
59
|
-
**Implicit, not explicit, in skill prose.** Skill files (and prompt-submit.txt) elicit behavior
|
|
59
|
+
**Implicit, not explicit, in skill prose.** Skill files (and prompt-submit.txt) elicit behavior, they do not describe it. Write terse imperative principles whose phrasing triggers the model's already-learned dispositions, not numbered procedures that spell out what to do. Forbidden: "1. agent runs N parallel calls 2. then writes 3. then…", "see paper IV §2.3", "as documented in docs/skills.html", citations to the site or papers, multi-step recipes. The skill is a prompt, not a manual; if it reads like a manual the behavior gets imitated as a script and breaks at the first edge case. The papers and site are *outputs* of the discipline, not *inputs* to it; never link from a skill into the docs. Cross-cutting rules that need a citation belong in this file (AGENTS.md), not in skills.
|
|
60
60
|
|
|
61
61
|
## Build
|
|
62
62
|
|
|
@@ -66,79 +66,79 @@ There is no build step. The repo root is the published artifact. `npm publish` f
|
|
|
66
66
|
|
|
67
67
|
## the agent is the orchestrator; plugkit is the brain it drives
|
|
68
68
|
|
|
69
|
-
**The agent orchestrates.** Plugkit is the stateful library the agent drives by dispatching verbs. Plugkit does not act autonomously, does not advance phases in the background, does not validate transitions while the agent waits. Every possible state change is a verb the agent writes into `.gm/exec-spool/in/<verb>/<N>.txt`. If a session shows zero dispatches but the agent narrated a full PLAN→COMPLETE walk, the agent fabricated the walk
|
|
69
|
+
**The agent orchestrates.** Plugkit is the stateful library the agent drives by dispatching verbs. Plugkit does not act autonomously, does not advance phases in the background, does not validate transitions while the agent waits. Every possible state change is a verb the agent writes into `.gm/exec-spool/in/<verb>/<N>.txt`. If a session shows zero dispatches but the agent narrated a full PLAN→COMPLETE walk, the agent fabricated the walk, plugkit's dispatch ledger is ground truth.
|
|
70
70
|
|
|
71
|
-
The PLAN → EXECUTE → EMIT → VERIFY → COMPLETE state machine lives natively in rs-plugkit at `rs-plugkit/src/orchestrator/{mod,state,transitions,mutables,memorize}.rs`. Plugkit owns phase tracking, mutables resolution, memorize firing, and transition legality *as data structures and gate checks
|
|
71
|
+
The PLAN → EXECUTE → EMIT → VERIFY → COMPLETE state machine lives natively in rs-plugkit at `rs-plugkit/src/orchestrator/{mod,state,transitions,mutables,memorize}.rs`. Plugkit owns phase tracking, mutables resolution, memorize firing, and transition legality *as data structures and gate checks*, but the agent triggers every possible operation by dispatching one of the orchestrator verbs over the wasm surface (see Spool dispatch ABI above): `transition`, `mutable-resolve`, `memorize-fire`, `phase-status`, `instruction`, `residual-scan`, `auto-recall`. The gm-skill harness routes the agent's verb writes to plugkit; the harness never reimplements the state machine and the agent never expects plugkit to act without a verb. Polling the spool output dir (`sleep && ls`, `Start-Sleep && Test-Path`) instead of reading the response file is the canonical misuse, plugkit is synchronous from the agent's view.
|
|
72
72
|
|
|
73
73
|
## gm-skill is the canonical universal harness
|
|
74
74
|
|
|
75
|
-
`skills/gm-skill/SKILL.md` is the single source of truth for harness behavior. It is the only skill shipped
|
|
75
|
+
`skills/gm-skill/SKILL.md` is the single source of truth for harness behavior. It is the only skill shipped, the legacy 15-platform fanout (gm-cc, gm-gc, gm-oc, gm-codex, gm-kilo, gm-qwen, gm-hermes, gm-thebird, gm-vscode, gm-cursor, gm-zed, gm-jetbrains, gm-copilot-cli, gm-antigravity, gm-windsurf) is retired; those downstream repos are archived. Users install gm-skill directly into whatever harness they use.
|
|
76
76
|
|
|
77
77
|
## Tool surface is plugkit-only
|
|
78
78
|
|
|
79
|
-
Every possible skill's `allowed-tools:` frontmatter is reduced to `Skill, Read, Write`. `Write` is permitted exclusively for spool dispatch (writing into `.gm/exec-spool/in/<lang>/`). Every possible other side effect
|
|
79
|
+
Every possible skill's `allowed-tools:` frontmatter is reduced to `Skill, Read, Write`. `Write` is permitted exclusively for spool dispatch (writing into `.gm/exec-spool/in/<lang>/`). Every possible other side effect, code execution, git, browser, recall, memorize, codesearch, routes through the spool and is serviced by plugkit. The harness never reaches around plugkit; if a capability is missing, add it as a plugkit verb, not as a skill-side tool.
|
|
80
80
|
|
|
81
81
|
## Core Rules
|
|
82
82
|
|
|
83
83
|
**Shared memory & search index are tracked, never ignored**: `.gm/rs-learn.db` and `.gm/code-search/` are committed so memory and index state shares across every possible machine, session, and CI run. Tooling, scripts, and every possible agent editing `.gitignore` must NEVER add `.gm/`, `.gm/rs-learn.db`, `.gm/code-search/`, or legacy `.code-search/` to ignore rules. Per the gitignore parent-re-include caveat (re-including a path past an ignored parent dir is impossible), individual `.gm/*` entries (prd-state.json, lastskill, turn-state.json, trajectory-drafts/, ingest-drafts/, rslearn-counter.json) are listed one-by-one between `# >>> gm managed` markers, leaving `.gm/rs-learn.db` and `.gm/code-search/` un-ignored. The gm-managed gitignore entries (written by `gm-plugkit/plugkit-wasm-wrapper.js::ensureGitignored(cwd, entry)`) must not include any of those paths. Every possible project-local persistent state (chunk index, DB, embeddings) must write under `.gm/<name>/`, never to a top-level dotfile/dotdir.
|
|
84
84
|
|
|
85
|
-
**Disciplines are isolated knowledge stores**: per-project, at `<project>/.gm/disciplines/<name>/{rs-learn.db, code-search/}`. Every possible discipline owns its own rs-learn DB and code-search index. When a `@<name>` sigil is present in the request, isolation is strict
|
|
85
|
+
**Disciplines are isolated knowledge stores**: per-project, at `<project>/.gm/disciplines/<name>/{rs-learn.db, code-search/}`. Every possible discipline owns its own rs-learn DB and code-search index. When a `@<name>` sigil is present in the request, isolation is strict, cross-discipline reads are forbidden. Without a sigil, reads (recall/codesearch) fan across `default` plus every possible enabled discipline (one per line in `<project>/.gm/disciplines/enabled.txt`) and merge-rank results with `[discipline:<name>]` prefixes; writes (memorize/ingest/index) without a sigil go to `default` only. Disciplines are tracked in git, never ignored, `ensureGitignored` and any gm-managed gitignore entry must not list `.gm/disciplines` or any subpath. The gm-skill harness and every possible spool verb propagate the `@<name>` sigil verbatim through their dispatch chain.
|
|
86
86
|
|
|
87
|
-
**Nothing fake in source the user runs**: every possible stub, mock, placeholder return, fixture-only path, demo-mode short-circuit, and "TODO: implement" body is forbidden in shipped code. Scaffolds and shims are permitted only when they delegate to real behavior (real upstream API, real subprocess, real disk). Before adding a shim, check whether a published library or tool already provides that surface
|
|
87
|
+
**Nothing fake in source the user runs**: every possible stub, mock, placeholder return, fixture-only path, demo-mode short-circuit, and "TODO: implement" body is forbidden in shipped code. Scaffolds and shims are permitted only when they delegate to real behavior (real upstream API, real subprocess, real disk). Before adding a shim, check whether a published library or tool already provides that surface, maintaining a local reimplementation of an upstream solution drifts and ages. Detection is behavioral, not by keyword: code that always succeeds, returns the same value regardless of input, or short-circuits a real call to satisfy a type signature is a stub. Acceptance is real input through real code into real output, witnessed; every possible degradation from that leaves the mutable open.
|
|
88
88
|
|
|
89
|
-
**Spool dispatch gates**: `lib/spool-dispatch.js` implements marker-file gate logic that controls tool use, writes, and git operations. `checkDispatchGates(sessionId, operation)` reads marker files (`.gm/prd.yml`, `.gm/mutables.yml`, `.gm/needs-gm`, `.gm/gm-fired-<id>`) and returns `{allowed: bool, reason: string}`. Gates are checked at the CLI/bootstrap layer before tools execute. Tool denials via gate checks report the reason text to the model so it can adjust behavior (e.g., resolve mutables before retrying). Gate denials never mutate command arguments
|
|
89
|
+
**Spool dispatch gates**: `lib/spool-dispatch.js` implements marker-file gate logic that controls tool use, writes, and git operations. `checkDispatchGates(sessionId, operation)` reads marker files (`.gm/prd.yml`, `.gm/mutables.yml`, `.gm/needs-gm`, `.gm/gm-fired-<id>`) and returns `{allowed: bool, reason: string}`. Gates are checked at the CLI/bootstrap layer before tools execute. Tool denials via gate checks report the reason text to the model so it can adjust behavior (e.g., resolve mutables before retrying). Gate denials never mutate command arguments, they surface policy as imperative instruction via reason string.
|
|
90
90
|
|
|
91
|
-
**Done is plugkit's pronouncement, never the agent's claim**: the chain is COMPLETE only when `transition to=COMPLETE` returns COMPLETE phase and plugkit's on-disk state file reflects it. "I think we're done" is not done. "The user seems satisfied" is not done. The COMPLETE gate (gates.rs) is the single arbiter
|
|
91
|
+
**Done is plugkit's pronouncement, never the agent's claim**: the chain is COMPLETE only when `transition to=COMPLETE` returns COMPLETE phase and plugkit's on-disk state file reflects it. "I think we're done" is not done. "The user seems satisfied" is not done. The COMPLETE gate (gates.rs) is the single arbiter, it refuses on PRD-open, mutables-unresolved, dirty worktree, or missing residual-scan marker. The agent's job is to drive the chain into a state where the gate allows, then dispatch the verb, then read the response. Every possible alternative is narration.
|
|
92
92
|
|
|
93
|
-
**Every residual is triaged this turn, never named-and-deferred**: every entry in `git status --porcelain` at residual-scan or COMPLETE-attempt is the agent's responsibility to triage now. Triage = decide one of (a) commit (real session work or real upstream artifact landed in this tree), (b) add to the managed gitignore block (transient runtime emission, e.g., `.gm/witness/`, `.gm/exec-spool/.*-stale.json`), (c) revert (accidental or stale junk). The label "pre-existing residual" is NOT a stop excuse
|
|
93
|
+
**Every residual is triaged this turn, never named-and-deferred**: every entry in `git status --porcelain` at residual-scan or COMPLETE-attempt is the agent's responsibility to triage now. Triage = decide one of (a) commit (real session work or real upstream artifact landed in this tree), (b) add to the managed gitignore block (transient runtime emission, e.g., `.gm/witness/`, `.gm/exec-spool/.*-stale.json`), (c) revert (accidental or stale junk). The label "pre-existing residual" is NOT a stop excuse, it is the *outcome* of triage decision (a) when work is real, or (c) when junk. Name-and-stop with `blockedBy: external` is admissible *only* when triage requires external authority that this session cannot reach (another team's repo, hardware credential, owner-only decision visible to no one in-process). For local-tree residuals the agent always has authority and triages this turn; declaring "pre-existing, can't touch" on local files is the canonical drift mechanism that wedges the chain at VERIFY. Disciplines (`.gm/disciplines/`) are tracked, never ignored, new memorize-fire mem-*.json files are committed, not gitignored. The managed gitignore block (between `# >>> plugkit managed` markers) carries only runtime artifacts that have no future read value (witness JSON, transient staleness markers, lock files).
|
|
94
94
|
|
|
95
|
-
**"Every possible" is the load-bearing test, applied to every noun the request names**: PLAN-phase PRD construction is exhaustive, not minimal. The phrase "every possible" is how the agent thickens the cover
|
|
95
|
+
**"Every possible" is the load-bearing test, applied to every noun the request names**: PLAN-phase PRD construction is exhaustive, not minimal. The phrase "every possible" is how the agent thickens the cover, every possible task, every possible validation, every possible mutable, every possible corner case, every possible caveat, every possible failure mode, every possible interaction, every possible empty/overflow/reentry state, every possible degenerate input, each application of the phrase produces PRD rows. A non-trivial request that yields a single-digit PRD has not finished enumerating; the closure is still in the agent's head, not in plugkit's store, and the chain will converge on a thin slice that leaves silent residuals. After the first PRD pass, the existing list becomes input to a second transform, for each row, the agent asks what every possible corner case looks like, and writes those as additional rows. The expansion is closed when applying "every possible" to the current list yields nothing new, not when the agent feels done. Validations, edge cases, and anticipated mutables are first-class PRD rows, never implicit. Long-horizon requests routinely produce PRDs in the high tens or hundreds; the row count is the resolution of the cover, and resolution is what the user asked for when they handed over a long-horizon prompt. Sparse PRDs under-specify the closure and orphan the work; dense PRDs make completion observable.
|
|
96
96
|
|
|
97
|
-
**Noticing is a planning event
|
|
97
|
+
**Noticing is a planning event, at every phase, in every dispatch window**: any observation the agent makes during the chain, anything that should be done, anything outstanding, anything unfinished, anything improvable, anything misaligned with user preferences, anything the work itself surfaces about what *else* the work touches, is a `prd-add` the agent dispatches this turn. Observations carried in the response body without conversion to a PRD row evaporate when the turn ends; only the PRD store survives. The default response to noticing is to convert. The discovery surface keeps producing new in-scope items as the chain walks PLAN→EXECUTE→EMIT→VERIFY, every phase has its own noticing-to-PRD pressure. Skipping the conversion ("I'll mention it in the summary" / "future work" / "note for later") is the canonical drift mechanism: the observation does not persist, the future turn does not arrive, the residual goes silent. Density grows along the walk, not just at PLAN-time; a chain that exits PLAN with N rows and reaches COMPLETE with N rows has either had no real discoveries (unlikely on a non-trivial task) or has lost them. When the discovery is structural rather than concrete, "the project would benefit from X", "this surface has no test coverage", "the docs do not mention Y", "the agent's preference for Z is being violated here", it is still a PRD row, written with the witness that motivated it. Preference-aware noticing applies the same conversion: when the agent observes that current state diverges from user-stated preferences (dense PRDs, residual-triage, no name-and-defer, every-possible expansion, browser-witness coverage, push-on-clean), each divergence is a `prd-add` describing what the aligned state looks like.
|
|
98
98
|
|
|
99
|
-
**A turn without a tool call is a stop; summary is a stop; both are forbidden until plugkit pronounces COMPLETE**: every programming agent
|
|
99
|
+
**A turn without a tool call is a stop; summary is a stop; both are forbidden until plugkit pronounces COMPLETE**: every programming agent, regardless of vendor, reads only tool calls and their outputs, so an assistant message that ends in prose with no tool call IS the turn ending and the session halts there. This is the mechanical root of the "agent did one small piece and stopped" failure: the model wrote a wrap-up paragraph, emitted no tool call, and the harness read that as done. The rule is therefore absolute and tool-agnostic: while the chain is in-flight (phase ≠ COMPLETE OR prd_pending_count > 0) the agent NEVER ends a turn in prose and NEVER writes a summary/recap/"what I did" closure, every turn terminates in a tool call that advances the chain (`instruction`, the next named verb, `transition`, `phase-status`). The only event that authorizes a prose-only turn is plugkit returning `phase=COMPLETE` AND `prd_pending_count=0`; the agent's own sense that "the work feels done" authorizes nothing. Before any apparent stop or any summary, the agent dispatches `phase-status` and rechecks, a non-terminal phase means the urge to stop was drift, and the recovery is to dispatch `instruction` and continue. This depends on nothing but the verb spool, so it holds on every agent with no hook and no tool-specific feature; any continuation mechanism that relies on a hook or a single tool's behavior is non-portable and must be replaced by this spool-only discipline.
|
|
100
100
|
|
|
101
|
-
**Always seek the next state transition**: if the chain is not COMPLETE, there is a next move. Idle mid-chain is a deviation. The agent who finishes a verb and stops without dispatching the next instruction has stopped walking the chain. `phase-status` tells you where you are; `instruction` tells you what's next. There is no "I'll wait for the user" mid-chain
|
|
101
|
+
**Always seek the next state transition**: if the chain is not COMPLETE, there is a next move. Idle mid-chain is a deviation. The agent who finishes a verb and stops without dispatching the next instruction has stopped walking the chain. `phase-status` tells you where you are; `instruction` tells you what's next. There is no "I'll wait for the user" mid-chain, the user authorized closure at request time, not phase-by-phase.
|
|
102
102
|
|
|
103
|
-
**Return to plugkit on every possible drift**: `instruction` is the recovery primitive. Against every possible stall, gate-denial, unexpected error, or moment of uncertainty about the next step, the response is always to dispatch `instruction` and read the prose
|
|
103
|
+
**Return to plugkit on every possible drift**: `instruction` is the recovery primitive. Against every possible stall, gate-denial, unexpected error, or moment of uncertainty about the next step, the response is always to dispatch `instruction` and read the prose, never to improvise. The verb is synchronous, cheap, idempotent; over-dispatching it has no cost, under-dispatching it is the canonical drift mechanism. A session that goes >N tool calls without an instruction dispatch in a non-trivial phase is hallucinating its own chain. Every possible gate denial names the next verb in its `reason` field, the agent reads the field and dispatches the named verb, never argues around the denial.
|
|
104
104
|
|
|
105
|
-
**Push is part of COMPLETE, never optional, never asked**: every possible session that mutates tracked files ends with commit + push to origin. Asking the user "do you want me to push?" is a deviation
|
|
105
|
+
**Push is part of COMPLETE, never optional, never asked**: every possible session that mutates tracked files ends with commit + push to origin. Asking the user "do you want me to push?" is a deviation, the push IS the validation dispatch (`verify.rs`: "The push you make IS the validation dispatch"). The chain is not COMPLETE until the remote reflects HEAD. ccsniff `--git-discipline` and a pending `deviation.complete-without-push` event flag sessions that close without pushing.
|
|
106
106
|
|
|
107
|
-
**Push requires clean worktree witnessed in its own tool-use event**: `git push` is admissible only when `git status --porcelain` returns empty, and the porcelain probe must be its own Bash tool-use event before the push
|
|
107
|
+
**Push requires clean worktree witnessed in its own tool-use event**: `git push` is admissible only when `git status --porcelain` returns empty, and the porcelain probe must be its own Bash tool-use event before the push, a separate `Bash(...)` call, not a `&&`-chained shell command within the push event. ccsniff `--git-discipline` scans the last 20 Bash **tool-use events** (not shell commands inside those events) for an explicit porcelain probe; `add && commit && push` in one Bash call counts as one event with no porcelain witness even when the worktree is clean by construction. A push from a dirty tree orphans the unstaged delta and breaks the next session's first read. Enforced in `lib/spool-dispatch.js::checkDispatchGates(sessionId, 'git', {...})` which runs the porcelain probe via `spawnSync('git', ['status', '--porcelain'])` and refuses dirty trees; the rs-plugkit `gates.rs` COMPLETE branch enforces the same invariant for the transition-to-COMPLETE path; instruction prose (`verify.rs`, `update_docs.rs`) restates it imperatively; `residual.rs` skips the scan when dirty so the four-observation window cannot be claimed past an unwitnessed delta. ccsniff `--git-discipline` flags the deviation post-hoc, true positives now that ccsniff 1.1.8+ strips quoted commit-message bodies before regex match.
|
|
108
108
|
|
|
109
109
|
**memorize dispatch manages CLAUDE.md / AGENTS.md**: Do not inline-edit. Dispatch via spool: write `.gm/exec-spool/in/memorize/<N>.txt` with the fact text; the wasm orchestrator embeds and persists it. Classifier rejects changelog-shaped facts from AGENTS.md ingestion (rs-learn store still accepts them).
|
|
110
110
|
|
|
111
|
-
**Behavioral discipline lives in plugkit's `instruction` verb
|
|
111
|
+
**Behavioral discipline lives in plugkit's `instruction` verb**: Three-Layer Admission Filter (L1 cost, L2 bounds, L3 direction), maturity-first emit, response-not-mutation-surface, structural recognition of closure anti-shapes, code invariants (state-space minimization, hardware-reality, flat-structure, vertical-slice, async-boundary, naming-by-scale, fail-fast, binary-transport, single-focus). Dispatch `instruction` for the live prose; do not duplicate it here.
|
|
112
112
|
|
|
113
113
|
**host_exec_js is synchronous**: wasm host runs `exec_js` via Node `child_process.spawnSync`; long subprocesses block the watcher. Pass a real per-call timeout; orphaned background work unsupported under wasm.
|
|
114
114
|
|
|
115
115
|
**Sync-before-emit (codeinsight + search)**: outputs must come from freshly-completed indices. Cache serves only on digest match (mtime sum + git HEAD + dirty-tree marker). Default invocation runs fresh. `--read-cache` permitted only when `.codeinsight.digest` matches; mismatch auto-refreshes. rs-search runs scan + embed + sweep before first result; emits `[index fully synced: …]`. Unverified-index emit = stale ground truth.
|
|
116
116
|
|
|
117
|
-
**Auto-recall on turn entry**: the `instruction` verb attaches an `auto_recall` pack `{query, hits, fired_at, turn_entry:true}` to its response on the first dispatch after a >30s idle gap or session-start. Query is derived from `.gm/last-prompt.txt` / `.gm/turn-state.json`; hits are the top recall results plugkit pulled before serving the instruction. Wasm-side `wasm_hooks::prompt_submit` exports exist for legacy hook-host integration but the current spool watcher does not invoke them
|
|
117
|
+
**Auto-recall on turn entry**: the `instruction` verb attaches an `auto_recall` pack `{query, hits, fired_at, turn_entry:true}` to its response on the first dispatch after a >30s idle gap or session-start. Query is derived from `.gm/last-prompt.txt` / `.gm/turn-state.json`; hits are the top recall results plugkit pulled before serving the instruction. Wasm-side `wasm_hooks::prompt_submit` exports exist for legacy hook-host integration but the current spool watcher does not invoke them, orientation comes through the instruction verb's response pack instead.
|
|
118
118
|
|
|
119
119
|
**Skill SKILL.md frontmatter `allowed-tools:` is harness-enforced**: If a skill omits `allowed-tools` or does not list `Skill`, the model loses the ability to invoke downstream skills that turn. The shipped surface is a single skill (`gm-skill`); this rule governs every possible future skill that participates in a chain.
|
|
120
120
|
|
|
121
121
|
**rs-learn observability**: every possible learning-pipeline state change emits a structured `evt: {event, sess, ts, ...}` line via `wasm_dispatch::emit_event` (host_log level 1) into `.gm/exec-spool/.watcher.log` and gm-log/<date>/plugkit.jsonl. Event taxonomy: `embed_fail` (step + error), `embed_init_ok/fail/cached_fail`, `memorize_reject` (reason, text_prefix, namespace), `memorize_embed_rollback` (key, namespace, error), `discipline_sigil_ignored` (sigil in @<name> request routed to default), `table_dropped` (dim-mismatch silent drops), `recall_score_unavailable` (host_vec_search strips score). Recall replies now include `mode` (vector_top_k|fallback_like|kv_query), `namespace`, `derived_query`, and per-hit `score` (null when host elides). gmsniff flags: `--embed-failures`, `--recall-misses`, `--recall-scores`, `--classifier-rejects`, `--memory-leverage`, `--recall-modes`, `--table-drops`, `--discipline-sigil-ignored`. ccsniff `--learning-xref` joins transcript turn windows to rs_learn events by sess (now stamped) + project + time-window. Learning quality is observable, not algorithmic black box.
|
|
122
122
|
|
|
123
|
-
**SKILL.md auto-refresh**: every possible bootstrap call (`bootstrapPlugkit`) compares the sha256 of the bundled `gm-skill/skills/gm-skill/SKILL.md` (shipped inside the npm package) against the installed copies at `~/.agents/skills/gm-skill/SKILL.md` and `~/.claude/skills/gm-skill/SKILL.md`. Hash mismatch triggers atomic write (`.tmp` + rename) of both targets so the agent sees the latest prose on next session
|
|
123
|
+
**SKILL.md auto-refresh**: every possible bootstrap call (`bootstrapPlugkit`) compares the sha256 of the bundled `gm-skill/skills/gm-skill/SKILL.md` (shipped inside the npm package) against the installed copies at `~/.agents/skills/gm-skill/SKILL.md` and `~/.claude/skills/gm-skill/SKILL.md`. Hash mismatch triggers atomic write (`.tmp` + rename) of both targets so the agent sees the latest prose on next session, no manual reinstall needed. Logged to `bootstrap.jsonl` as `SKILL.md refreshed`. The bundled SKILL.md is the source of truth; reinstalling gm-skill only matters when the npm package itself changes, which the cascade pipeline guarantees on every possible plugkit version bump.
|
|
124
124
|
|
|
125
|
-
**Skill-initiated bootstrap contract**: `lib/skill-bootstrap.js` performs wasm initialization for skill-driven dispatch without hook infrastructure. `bootstrapPlugkit(sessionId)` accepts optional SESSION_ID, ensures the wasm artifact and `plugkit-wasm-wrapper.js` are in place, writes status/error to `.gm/exec-spool/.bootstrap-status.json` and `.bootstrap-error.json` for spool awareness, and returns `{ ok: true }` on success or `{ ok: false, error: message }` on failure. Failures are non-fatal
|
|
125
|
+
**Skill-initiated bootstrap contract**: `lib/skill-bootstrap.js` performs wasm initialization for skill-driven dispatch without hook infrastructure. `bootstrapPlugkit(sessionId)` accepts optional SESSION_ID, ensures the wasm artifact and `plugkit-wasm-wrapper.js` are in place, writes status/error to `.gm/exec-spool/.bootstrap-status.json` and `.bootstrap-error.json` for spool awareness, and returns `{ ok: true }` on success or `{ ok: false, error: message }` on failure. Failures are non-fatal, callers fall back to a degraded surface.
|
|
126
126
|
|
|
127
127
|
## Cascade pipeline
|
|
128
128
|
|
|
129
|
-
Push to every possible rs-* sibling repo (rs-exec, rs-search, rs-codeinsight, rs-learn) triggers `cascade.yml` which uses `gh workflow run` to invoke rs-plugkit's `release.yml` via PUBLISHER_TOKEN. rs-plugkit cargo-pulls the latest sibling crate revs at build time and emits a single `plugkit.wasm` artifact (no per-sibling npm wasm packages
|
|
129
|
+
Push to every possible rs-* sibling repo (rs-exec, rs-search, rs-codeinsight, rs-learn) triggers `cascade.yml` which uses `gh workflow run` to invoke rs-plugkit's `release.yml` via PUBLISHER_TOKEN. rs-plugkit cargo-pulls the latest sibling crate revs at build time and emits a single `plugkit.wasm` artifact (no per-sibling npm wasm packages, that pattern was retired). Publishes to `plugkit-bin` Releases + npm `plugkit-wasm`, then auto-bumps `gm.json::plugkitVersion` and `bin/plugkit.wasm.sha256` in this repo. The version bump commit on this repo triggers `publish.yml`, which (a) `npm publish`es `gm-skill` from the repo root, (b) `npm publish`es `gm-plugkit` from `gm-plugkit/`, and (c) force-pushes `skills/gm-skill/SKILL.md` to the `AnEntrypoint/gm-skill` back-compat mirror repo.
|
|
130
130
|
|
|
131
|
-
Three npm packages publish from this repo: `gm-skill` (the skill harness), `gm-plugkit` (bootstrap + watcher), `plugkit-wasm` (wasm binary). publish.yml + the rs-plugkit cascade ships all three on every version-bump commit. The legacy 15 downstream repos (gm-cc, gm-gc, gm-oc, gm-kilo, gm-codex, gm-qwen, gm-copilot-cli, gm-hermes, gm-thebird, gm-vscode, gm-cursor, gm-zed, gm-jetbrains, gm-antigravity, gm-windsurf) are archived on GitHub
|
|
131
|
+
Three npm packages publish from this repo: `gm-skill` (the skill harness), `gm-plugkit` (bootstrap + watcher), `plugkit-wasm` (wasm binary). publish.yml + the rs-plugkit cascade ships all three on every version-bump commit. The legacy 15 downstream repos (gm-cc, gm-gc, gm-oc, gm-kilo, gm-codex, gm-qwen, gm-copilot-cli, gm-hermes, gm-thebird, gm-vscode, gm-cursor, gm-zed, gm-jetbrains, gm-antigravity, gm-windsurf) are archived on GitHub, no further releases, no orphan-commit publish step.
|
|
132
132
|
|
|
133
133
|
**Repos involved (push to every possible one triggers cascade):**
|
|
134
|
-
- `AnEntrypoint/rs-exec
|
|
135
|
-
- `AnEntrypoint/rs-codeinsight
|
|
136
|
-
- `AnEntrypoint/rs-search
|
|
137
|
-
- `AnEntrypoint/rs-plugkit
|
|
138
|
-
- `AnEntrypoint/rs-learn
|
|
139
|
-
- `AnEntrypoint/gm
|
|
134
|
+
- `AnEntrypoint/rs-exec`, exec runner, browser sessions, idle cleanup, session task isolation
|
|
135
|
+
- `AnEntrypoint/rs-codeinsight`, code search backend, symbol indexing
|
|
136
|
+
- `AnEntrypoint/rs-search`, file search backend, embedding and sweep
|
|
137
|
+
- `AnEntrypoint/rs-plugkit`, CLI entry point, spool watcher dispatcher; version source of truth in `Cargo.toml`
|
|
138
|
+
- `AnEntrypoint/rs-learn`, memory backend, recall/ingest via HTTP RPC
|
|
139
|
+
- `AnEntrypoint/gm`, `gm.json` holds `plugkitVersion`; CI publishes the single `gm-skill` npm package
|
|
140
140
|
|
|
141
|
-
**To update every possible thing**: push to the relevant repo. No manual version bumps, no local cargo builds. Never run `cargo update` or `cargo build` locally
|
|
141
|
+
**To update every possible thing**: push to the relevant repo. No manual version bumps, no local cargo builds. Never run `cargo update` or `cargo build` locally, push and let CI build.
|
|
142
142
|
|
|
143
143
|
**PUBLISHER_TOKEN required** in `rs-exec`, `rs-codeinsight`, `rs-search` for cascade.yml to trigger rs-plugkit. Set with: `gh secret set PUBLISHER_TOKEN --repo AnEntrypoint/<repo>`.
|
|
144
144
|
|
|
@@ -150,31 +150,31 @@ Orchestration state is tracked via marker files in `.gm/` instead of hook events
|
|
|
150
150
|
|
|
151
151
|
**Marker files**: `.gm/prd.yml` (existence triggers needs-gm gate), `.gm/mutables.yml` (every possible unresolved entry blocks Write/Edit/git), `.gm/needs-gm` (written by bootstrap, read by dispatcher), `.gm/gm-fired-<sessionId>` (written by gm skill/agent, cleared at turn start), `.gm/residual-check-fired` (ensures one-shot residual-scan per stop window).
|
|
152
152
|
|
|
153
|
-
**Gate enforcement**: CLI layer (plugkit, rs-exec, downstream platforms) calls `checkDispatchGates()` before tool execution. On denial, reason text surfaces to the model. Bootstrap (lib/skill-bootstrap.js) handles daemon initialization and marker setup. Marker-driven dispatch replaces hook event pump entirely
|
|
153
|
+
**Gate enforcement**: CLI layer (plugkit, rs-exec, downstream platforms) calls `checkDispatchGates()` before tool execution. On denial, reason text surfaces to the model. Bootstrap (lib/skill-bootstrap.js) handles daemon initialization and marker setup. Marker-driven dispatch replaces hook event pump entirely, no session event callbacks needed.
|
|
154
154
|
|
|
155
155
|
**gm-skill tool-use sequencing**: Invoking `Skill(skill="gm-skill")` writes `.gm/gm-fired-<sessionId>` to clear the needs-gm gate. The marker is cleared at turn start to reset the gate. There is one shipped skill; no subagent variant exists.
|
|
156
156
|
|
|
157
|
-
**Session lifecycle**: Session-end kills background tasks via `killSessionTasks` RPC on real-exit reasons (clear/logout/prompt_input_exit). Every possible browser session and background task persists across turn-stops
|
|
157
|
+
**Session lifecycle**: Session-end kills background tasks via `killSessionTasks` RPC on real-exit reasons (clear/logout/prompt_input_exit). Every possible browser session and background task persists across turn-stops, cleanup happens exclusively on real-exit reasons. Residual-scan fires when PRD is empty/missing AND no open browser sessions AND no running tasks; agent either expands PRD with in-spirit residuals or explicitly states none.
|
|
158
158
|
|
|
159
159
|
## Spool observability surface
|
|
160
160
|
|
|
161
161
|
Every possible agent has a one-shot system-state probe: dispatch `plugkit health` via the file-spool (write `.gm/exec-spool/in/health/<N>.txt` empty body, read `out/<N>.json`). Returns plugkit version + pin-match, watcher liveness, runner state, rs-learn status, cache dirs, inbox/outbox counts, recent hook fires, recent errors. Use before assuming every possible component is broken.
|
|
162
162
|
|
|
163
|
-
Three persistent diagnostic files at `.gm/exec-spool/` root are updated by the running stack (not the agent): `.status.json` (watcher state each tick; stale mtime = dead watcher), `.last-session-start.json` (most recent session-start spawn result), `.bootstrap-error.json` (pin-mismatch / fetch-fail surface
|
|
163
|
+
Three persistent diagnostic files at `.gm/exec-spool/` root are updated by the running stack (not the agent): `.status.json` (watcher state each tick; stale mtime = dead watcher), `.last-session-start.json` (most recent session-start spawn result), `.bootstrap-error.json` (pin-mismatch / fetch-fail surface, absent = healthy). Reading these directly via Read is allowed (runtime data exception); spool dispatch isn't needed to inspect them.
|
|
164
164
|
|
|
165
165
|
## Site Build & Documentation
|
|
166
166
|
|
|
167
|
-
**Mermaid integration**: `theme.mjs` (articleClient + landingClient) dynamic-imports mermaid from cdn.jsdelivr.net after `applyDiff` and calls `mermaid.run()` on `.mermaid` blocks. `startOnLoad` must be false
|
|
167
|
+
**Mermaid integration**: `theme.mjs` (articleClient + landingClient) dynamic-imports mermaid from cdn.jsdelivr.net after `applyDiff` and calls `mermaid.run()` on `.mermaid` blocks. `startOnLoad` must be false, the parse happens before article injection, so `startOnLoad` would miss injected blocks. Theme auto-detects color scheme via `prefers-color-scheme`.
|
|
168
168
|
|
|
169
|
-
**Navigation**: `site/content/globals/navigation.yaml` uses grouped entry format
|
|
169
|
+
**Navigation**: `site/content/globals/navigation.yaml` uses grouped entry format, each item is either `{label, href}` (single link) or `{label, group: [{label, href}, ...]}` (dropdown menu). Dropdowns render via `<details>/<summary>` through the flatspace `C.Topbar` primitive invoked in `site/theme.mjs`; no JS required. In-page topbars in docs/paper*.html et al. render directly on file open and must be kept in sync with the same markup.
|
|
170
170
|
|
|
171
171
|
**Landing page renderer**: the deployed `/` route on https://anentrypoint.github.io/gm/ is rendered by `site/theme.mjs` from `site/content/pages/home.yaml` via flatspace. `site/index.html` + `site/main.js` build `docs/bundle.js` for non-flatspace standalone preview only. Landing edits go through `site/theme.mjs` (Hero) and `site/content/pages/home.yaml` (content), never `site/index.html`.
|
|
172
172
|
|
|
173
|
-
**docs/styles.css is generated**: regenerated from `site/input.css` by `site/package.json` build script (copies input.css → docs/styles.css after esbuild). Direct edits to docs/styles.css are wiped on next build
|
|
173
|
+
**docs/styles.css is generated**: regenerated from `site/input.css` by `site/package.json` build script (copies input.css → docs/styles.css after esbuild). Direct edits to docs/styles.css are wiped on next build, append to site/input.css instead.
|
|
174
174
|
|
|
175
175
|
## Made with gm Page
|
|
176
176
|
|
|
177
|
-
`docs/made-with.html` is a static showcase of notable AnEntrypoint projects. Update the PROJECTS array when a new notable project is added
|
|
177
|
+
`docs/made-with.html` is a static showcase of notable AnEntrypoint projects. Update the PROJECTS array when a new notable project is added, projects with interesting descriptions, meaningful star counts, or technically unusual scope. Static data, no runtime API calls. Standalone HTML, not bundled.
|
|
178
178
|
|
|
179
179
|
|
|
180
180
|
@.gm/next-step.md
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **more coushin' for the puhin'**
|
|
4
4
|
|
|
5
|
-
gm is a skill that convinces your coding agent it already is a deterministic state machine
|
|
5
|
+
gm is a skill that convinces your coding agent it already is a deterministic state machine, PLAN → EXECUTE → EMIT → VERIFY → COMPLETE, and then enforces that conviction with a wasm-backed orchestrator, witnessed execution, and a covering family of bounded subsets that refuses to let "follow-up" become a synonym for "I gave up."
|
|
6
6
|
|
|
7
7
|
it is named after **glootius maximus**, the muscle that holds you in the chair while you finish the work. the name is the joke and the discipline at once: the agent that sits down through PLAN → EXECUTE → EMIT → VERIFY → COMPLETE actually ships. the agent that stands up early ships a stub with a green check on it.
|
|
8
8
|
|
|
@@ -33,7 +33,7 @@ This repo IS the published `gm-skill` npm package. No build step, no factory. Th
|
|
|
33
33
|
```
|
|
34
34
|
gm/
|
|
35
35
|
├── skills/gm-skill/ ← the skill (SKILL.md + index.js, ~12 lines of prose)
|
|
36
|
-
├── bin/ ← bootstrap + plugkit launcher (gmsniff / ccsniff are separate npm packages
|
|
36
|
+
├── bin/ ← bootstrap + plugkit launcher (gmsniff / ccsniff are separate npm packages, `bun x gmsniff`, `bun x ccsniff`)
|
|
37
37
|
├── lib/ ← runtime: spool dispatch, skill bootstrap, daemon mgmt
|
|
38
38
|
├── agents/ ← subagent prompts (gm, memorize, research-worker, textprocessing)
|
|
39
39
|
├── prompts/ ← bash-deny, session-start, prompt-submit, pre-compact
|
|
@@ -48,8 +48,8 @@ gm/
|
|
|
48
48
|
|
|
49
49
|
The two npm packages this repo publishes:
|
|
50
50
|
|
|
51
|
-
- **`gm-skill
|
|
52
|
-
- **`gm-plugkit
|
|
51
|
+
- **`gm-skill`**: the skill bundle, installed via `bun x skills`
|
|
52
|
+
- **`gm-plugkit`**: the wasm-wrapper daemon, dependency of `gm-skill`
|
|
53
53
|
|
|
54
54
|
## how it works
|
|
55
55
|
|
|
@@ -61,19 +61,19 @@ PLAN → EXECUTE → EMIT → VERIFY → COMPLETE. Every transition is a verb th
|
|
|
61
61
|
|
|
62
62
|
Every tool the agent uses is a dispatch verb. No direct shell, no direct file writes outside the spool. The wasm host owns the side effects.
|
|
63
63
|
|
|
64
|
-
- **`recall
|
|
65
|
-
- **`codesearch
|
|
66
|
-
- **`memorize
|
|
67
|
-
- **`browser
|
|
68
|
-
- **`git_status` / `branch_status` / `git_push
|
|
69
|
-
- **`filter
|
|
64
|
+
- **`recall`**: vector + KV recall against `rs-learn`, scored by cosine × recency, namespace-aware
|
|
65
|
+
- **`codesearch`**: semantic vector search across the project
|
|
66
|
+
- **`memorize`**: write to the recall index (with the BGE query/passage prefix asymmetry)
|
|
67
|
+
- **`browser`**: managed Chrome session with project-scoped profile at `.gm/browser-profile/`
|
|
68
|
+
- **`git_status` / `branch_status` / `git_push`**: git verbs that gate on porcelain
|
|
69
|
+
- **`filter`**: in-wasm stdout-compaction (grep/ls/tree/json/diff)
|
|
70
70
|
|
|
71
71
|
### hooks
|
|
72
72
|
|
|
73
|
-
- **session-start
|
|
74
|
-
- **prompt-submit
|
|
75
|
-
- **pre-tool-use
|
|
76
|
-
- **stop
|
|
73
|
+
- **session-start**: bootstraps plugkit, seeds `.gm/next-step.md`, sets `needs-gm` marker
|
|
74
|
+
- **prompt-submit**: reminds the agent to dispatch instruction first; injects per-prompt auto-recall
|
|
75
|
+
- **pre-tool-use**: blocks tool use before the gm skill fires for the turn
|
|
76
|
+
- **stop**: blocks session end while `.gm/prd.yml` has open items, mutables are unresolved, residual-scan hasn't fired, or the worktree is dirty
|
|
77
77
|
|
|
78
78
|
### ground truth
|
|
79
79
|
|
|
@@ -94,7 +94,7 @@ A push to `main` triggers `.github/workflows/publish.yml`:
|
|
|
94
94
|
|
|
95
95
|
`.github/workflows/gh-pages.yml` builds the `site/` flatspace source to `dist/` and deploys to GitHub Pages.
|
|
96
96
|
|
|
97
|
-
The plugkit wasm itself is built and released by [rs-plugkit](https://github.com/AnEntrypoint/rs-plugkit) on every push, published to npm as `plugkit-wasm` and to GitHub Releases as `plugkit-bin`. Bootstrapping the agent downloads the wasm at install time
|
|
97
|
+
The plugkit wasm itself is built and released by [rs-plugkit](https://github.com/AnEntrypoint/rs-plugkit) on every push, published to npm as `plugkit-wasm` and to GitHub Releases as `plugkit-bin`. Bootstrapping the agent downloads the wasm at install time, it does not ship in this repo.
|
|
98
98
|
|
|
99
99
|
## license
|
|
100
100
|
|
package/bin/plugkit.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.559
|
package/bin/plugkit.wasm.sha256
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
d08baf97ddce9070182d3fa82316628c9469fbbc55b52908d238d75afd218a30 plugkit.wasm
|
package/gm-plugkit/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1414",
|
|
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": {
|
|
@@ -1753,8 +1753,13 @@ function makeHostFunctions(instanceRef) {
|
|
|
1753
1753
|
});
|
|
1754
1754
|
}
|
|
1755
1755
|
|
|
1756
|
-
if (trimmed.startsWith('session
|
|
1756
|
+
if (trimmed.startsWith('session ')) {
|
|
1757
1757
|
const parts = trimmed.slice(8).trim().split(/\s+/);
|
|
1758
|
+
// playwriter's actual session subcommands are list|new|delete|reset.
|
|
1759
|
+
// Map the legacy close/kill aliases (recognized here historically) to delete,
|
|
1760
|
+
// and pass list/new/delete/reset through verbatim. Anything else is rejected
|
|
1761
|
+
// by playwriter with its own usage text, which is the correct surface.
|
|
1762
|
+
if (parts[0] === 'close' || parts[0] === 'kill') parts[0] = 'delete';
|
|
1758
1763
|
const r = runBrowserRunner(pw, ['session', ...parts], 30000);
|
|
1759
1764
|
return writeWasmJson(instanceRef.value, {
|
|
1760
1765
|
ok: r.status === 0,
|
package/gm.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1414",
|
|
4
4
|
"description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
|
|
5
5
|
"author": "AnEntrypoint",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,5 +17,5 @@
|
|
|
17
17
|
"publishConfig": {
|
|
18
18
|
"access": "public"
|
|
19
19
|
},
|
|
20
|
-
"plugkitVersion": "0.1.
|
|
20
|
+
"plugkitVersion": "0.1.559"
|
|
21
21
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1414",
|
|
4
4
|
"description": "Canonical universal harness — AI-native software engineering via skill-driven orchestration; bootstraps plugkit for task execution and session isolation. Install in any AI coding agent host.",
|
|
5
5
|
"author": "AnEntrypoint",
|
|
6
6
|
"license": "MIT",
|
package/skills/gm-skill/SKILL.md
CHANGED
|
@@ -6,33 +6,33 @@ allowed-tools: Skill, Read, Write, Bash(node *), Bash(bun *)
|
|
|
6
6
|
|
|
7
7
|
# gm
|
|
8
8
|
|
|
9
|
-
**A turn that ends without a tool call is a stop
|
|
9
|
+
**A turn that ends without a tool call is a stop, and stopping mid-chain is the cardinal failure.** Every programming agent reads only tool calls and their outputs; an assistant message that ends in prose with no tool call IS the turn ending, and the session halts there. So while the chain is in-flight (phase ≠ COMPLETE OR prd_pending_count > 0) you NEVER end a turn in prose, every turn ends in a tool call that advances the chain (the next `instruction` dispatch, the next verb the prose named, the next `transition`). You do NOT summarize, you do NOT write a "here's what I did" wrap-up, you do NOT narrate closure, summary is a stop, and stopping is only authorized when plugkit itself returns `phase=COMPLETE` AND `prd_pending_count=0`. Before you are ever tempted to stop or summarize, you dispatch `phase-status` and read it: if it is not terminal, that temptation was a drift signal, dispatch `instruction` and keep walking. This is tool-agnostic by construction: it depends on nothing but the verb spool, so it holds identically on every agent, with no hook and no tool-specific feature. The one and only thing that authorizes the prose-only turn is plugkit's COMPLETE pronouncement; until then, the answer to "am I done?" is another tool call.
|
|
10
10
|
|
|
11
|
-
**Done is what plugkit says is done
|
|
11
|
+
**Done is what plugkit says is done, never your claim.** The COMPLETE gate is the single arbiter. If the chain is not at COMPLETE, there is a next transition to seek; idle mid-chain is a deviation.
|
|
12
12
|
|
|
13
13
|
**Every possible action begins and ends with `instruction`.** When in doubt, dispatch instruction. When denied, dispatch instruction. When the next move is unclear, dispatch instruction. There is no other recovery primitive and there is no situation in which improvising beats re-reading the prose.
|
|
14
14
|
|
|
15
|
-
**You are the state machine.** Plugkit is the durable memory and gate-checker you write into; you are the actor that walks PLAN → EXECUTE → EMIT → VERIFY → COMPLETE. Every possible phase transition, every possible PRD resolution, every possible mutable witness, every possible residual scan is a verb YOU dispatch by writing `.gm/exec-spool/in/<verb>/<N>.txt`. Plugkit does not advance. Plugkit does not validate in the background. Plugkit does not "process" while you wait. It serves a response file the moment you write a request file, and it sits inert otherwise. The phase you are in is the phase you last dispatched `transition` to land on
|
|
15
|
+
**You are the state machine.** Plugkit is the durable memory and gate-checker you write into; you are the actor that walks PLAN → EXECUTE → EMIT → VERIFY → COMPLETE. Every possible phase transition, every possible PRD resolution, every possible mutable witness, every possible residual scan is a verb YOU dispatch by writing `.gm/exec-spool/in/<verb>/<N>.txt`. Plugkit does not advance. Plugkit does not validate in the background. Plugkit does not "process" while you wait. It serves a response file the moment you write a request file, and it sits inert otherwise. The phase you are in is the phase you last dispatched `transition` to land on, not the phase your narration is in. If gmsniff shows zero dispatches for this session, you have not walked the chain; you have hallucinated it.
|
|
16
16
|
|
|
17
17
|
This is the only thing that makes the discipline work. Drop this and every possible other rule collapses: mutables get resolved without witness, COMPLETE gets claimed without VERIFY, residuals get narrated away instead of scanned, and the chain becomes a story you tell instead of work you ship.
|
|
18
18
|
|
|
19
|
-
Every turn: dispatch `instruction` (you are the one dispatching it), read the response body, follow the imperative prose, dispatch the next verb the prose names. Re-dispatch `instruction` against every possible drift, stall, gate-denial, or moment of uncertainty about the next move
|
|
19
|
+
Every turn: dispatch `instruction` (you are the one dispatching it), read the response body, follow the imperative prose, dispatch the next verb the prose names. Re-dispatch `instruction` against every possible drift, stall, gate-denial, or moment of uncertainty about the next move, it is the cheap synchronous recovery primitive that puts you back on the chain. While the chain is in-flight (phase ≠ COMPLETE OR prd_pending_count > 0) there is no cost to over-dispatching it and unbounded cost to acting without it. A session that stops dispatching instruction mid-chain has stopped walking the chain. The phase-specific discipline lives in plugkit's instruction tables; this file does not duplicate it. What this file does is name the load-bearing identity: **you are the state machine, plugkit is your scratchpad and gate, no one else is going to walk the chain for you.**
|
|
20
20
|
|
|
21
21
|
**Once `phase=COMPLETE` AND `prd_pending_count=0`, the chain is closed and you stop dispatching.** Polling `instruction` on a terminal chain returns the same UPDATE-DOCS prose every time and produces `turn.end dispatches:1 verbs:{instruction:1}` events in gmsniff that mark the agent as polling rather than walking. The user reactivates the chain by sending a new prompt; that prompt resets phase to PLAN on the next instruction dispatch carrying a fresh `{"prompt": "..."}` body.
|
|
22
22
|
|
|
23
|
-
**Client-side edits are gated by Browser Witness (paper §23, hard rule).** If you Write or Edit any file with a client-side extension
|
|
23
|
+
**Client-side edits are gated by Browser Witness (paper §23, hard rule).** If you Write or Edit any file with a client-side extension, `.html`, `.js`, `.jsx`, `.ts`, `.tsx`, `.vue`, `.svelte`, `.mjs`, `.css`, or anything loaded from an HTML entry, you dispatch the `browser` verb in the **same turn** with a `page.evaluate` body that asserts the invariant the edit establishes. The transition gate refuses `transition to=COMPLETE` until `.turn-browser-witnessed` covers every entry in `.turn-browser-edits.json` with matching sha; absence or mismatch fires `deviation.client-edit-no-witness`. There is no "validate later", later does not arrive in the chain you are walking; the same response that contains the client-side Write/Edit also contains the `browser` Write + Read.
|
|
24
24
|
|
|
25
|
-
**Boot before dispatching. Always check first.** Writing to `.gm/exec-spool/in/instruction/N.txt` while the watcher is dead is the canonical cold-start failure
|
|
25
|
+
**Boot before dispatching. Always check first.** Writing to `.gm/exec-spool/in/instruction/N.txt` while the watcher is dead is the canonical cold-start failure, the request sits forever, you read no response, you fabricate the chain from memory of the prose. The spool directory's existence does NOT mean the watcher is alive; `.status.json` mtime within the last 15s does. The leftover `.status.json` from yesterday's dead watcher is the most common trap.
|
|
26
26
|
|
|
27
|
-
Your first tool call of every session is the boot probe, in one Bash invocation. Read `.status.json` (liveness) and `.turn-summary.json` (orientation) together
|
|
27
|
+
Your first tool call of every session is the boot probe, in one Bash invocation. Read `.status.json` (liveness) and `.turn-summary.json` (orientation) together, the watcher precomputes both every 5s, so the two files give you everything you'd otherwise extract via gmsniff queries, git, yaml-parse, and ports-file scans:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
cat .gm/exec-spool/.status.json 2>/dev/null; echo ---; cat .gm/exec-spool/.turn-summary.json 2>/dev/null; echo ---; date +%s%3N
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
`.turn-summary.json` carries `phase`, `last_skill`, `prd_pending`, `last_instruction_ts`, `last_instruction_age_ms`, `long_gap_threshold_ms`, `browser_sessions_alive`, `update_available`, `deviations_30m`, `watcher_uptime_ms`. When age exceeds the threshold, your next non-orienting verb will be gated
|
|
33
|
+
`.turn-summary.json` carries `phase`, `last_skill`, `prd_pending`, `last_instruction_ts`, `last_instruction_age_ms`, `long_gap_threshold_ms`, `browser_sessions_alive`, `update_available`, `deviations_30m`, `watcher_uptime_ms`. When age exceeds the threshold, your next non-orienting verb will be gated, dispatch `instruction` first. When `update_available` is non-null, the watcher has detected drift: kill the watcher then re-run the boot command below, which calls `ensureReady` to download the latest wasm and refresh the installed wrapper, then starts a fresh watcher loaded with the new code. The one-line sequence: `bun x gm-plugkit@latest --kill-stale-watchers; bun x gm-plugkit@latest spool > /dev/null 2>&1 &`. Then wait 8s and re-read `.status.json` to confirm `version` matches `update_available.latest`. `deviations_30m` is the rolling count of `deviation.*` events from hook+plugkit logs in the past 30 min, read it instead of running gmsniff at session start; non-zero indicates active drift worth investigating before continuing. `watcher_uptime_ms` shows how long the current watcher process has been alive; combined with `watcher_version` and `update_available` it tells you whether the watcher's loaded code is recent, a multi-hour uptime with no `update_available` means you're on latest, an uptime of seconds means a fresh boot just happened.
|
|
34
34
|
|
|
35
|
-
Compare `.status.json` `ts` field to the printed epoch ms. If the gap is >15000, the watcher is dead
|
|
35
|
+
Compare `.status.json` `ts` field to the printed epoch ms. If the gap is >15000, the watcher is dead, boot it:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
bun x gm-plugkit@latest spool > /dev/null 2>&1 &
|
|
@@ -40,30 +40,32 @@ bun x gm-plugkit@latest spool > /dev/null 2>&1 &
|
|
|
40
40
|
|
|
41
41
|
(`npx -y gm-plugkit@latest spool > /dev/null 2>&1 &` if `bun` missing.) Then wait ~8 seconds and re-`cat .gm/exec-spool/.status.json` to confirm a fresh `ts`. Only after a fresh heartbeat may you write to `instruction/`. Never write to `instruction/` while the heartbeat is stale; the request is silently dropped.
|
|
42
42
|
|
|
43
|
-
**Dispatch shape: Write + Read response in the SAME tool-call block.** Issuing the Write alone is not a dispatch
|
|
43
|
+
**Dispatch shape: Write + Read response in the SAME tool-call block.** Issuing the Write alone is not a dispatch, it's a request that goes nowhere visible. The shape is `Write .gm/exec-spool/in/instruction/<N>.txt` AND `Read .gm/exec-spool/out/instruction-<N>.json` (or `out/<N>.json` for nested verbs) in one parallel-tools block. The Read may return "file does not exist" on the first attempt while plugkit is mid-verb, in that case, re-Read in the next message. Do NOT proceed to any other tool, do NOT narrate "I have everything I need," do NOT begin work until you have READ the response and followed the `instruction` field. Declaring readiness without a response in hand is fabrication.
|
|
44
44
|
|
|
45
|
-
Never poll the spool dir with `sleep && ls` or `Start-Sleep && Test-Path
|
|
45
|
+
Never poll the spool dir with `sleep && ls` or `Start-Sleep && Test-Path`, plugkit is synchronous from your view; if the response is not there, the watcher is dead (re-check `.status.json` mtime) or the verb is slow (check `.gm/exec-spool/.watcher.log`), not "still processing."
|
|
46
46
|
|
|
47
|
-
**Dead-watcher recovery is mandatory, never abandon the dispatch.** If two consecutive re-Reads return "file does not exist" AND `.status.json` ts is stale (>15s gap from current epoch), the watcher is dead. Your next call is `bun x gm-plugkit@latest spool` to boot a fresh watcher (the wrapper has self-respawn paths now
|
|
47
|
+
**Dead-watcher recovery is mandatory, never abandon the dispatch.** If two consecutive re-Reads return "file does not exist" AND `.status.json` ts is stale (>15s gap from current epoch), the watcher is dead. Your next call is `bun x gm-plugkit@latest spool` to boot a fresh watcher (the wrapper has self-respawn paths now, one boot deploys every queued fix to disk). Then re-dispatch the original verb. Do NOT reach for an alternative tool, puppeteer-core, agent-browser, WebFetch, raw `chrome.exe`, none of these substitute for the `browser` verb. Reaching outside plugkit when the spool surface is reachable orphans state the next session cannot reap, bypasses paper §23 witness gates, and ages the project's discipline. The recovery is always: notice dead → boot → re-dispatch. The full chain from spool-write to disk-Read-success is the only admissible loop; any short-circuit produces unreconcilable state.
|
|
48
48
|
|
|
49
49
|
When writing the spool input from PowerShell, pass `-Encoding utf8` (or use `[System.IO.File]::WriteAllText($path, $body)` which defaults to UTF-8 no-BOM). PowerShell 5.1's default `Out-File` / `Set-Content` write UTF-16 LE with BOM, which the watcher detects and re-decodes (`spool.body-encoding-recoded` event in gmsniff), but the deviation is a fingerprint of an instruction you missed. Use `bash -c "echo -n '...' > ..."` or `Write` tool instead when the body is structured JSON.
|
|
50
50
|
|
|
51
51
|
First turn body must be `{"prompt":"<user request>"}` so orient_nouns and recall_hits derive from the request; subsequent turns in the same conversation may use empty body `{}`.
|
|
52
52
|
|
|
53
|
-
**Batch writes, waits, and reads together.** Each agent turn costs cycles; the dispatch shape `Write request → wait → Read response` is one logical step, not three. Issue all three in a single message
|
|
53
|
+
**Batch writes, waits, and reads together.** Each agent turn costs cycles; the dispatch shape `Write request → wait → Read response` is one logical step, not three. Issue all three in a single message, the Write tool call and the Read tool call go in the same `<function_calls>` block. The Read may return "file does not exist" if plugkit is mid-verb; that's fine, retry with one more Read in the next message rather than spreading the cycle across three turns. Fan-out is the same shape, dispatching three independent verbs (`prd-add g1`, `prd-add g2`, `prd-add g3`) means three Write tool calls in one block, then three Read tool calls in one block. Serial dispatch when you could be parallel is wasted cycles. The only sequencing constraint is real data dependency: if verb B needs the response of verb A, those go in separate turns; otherwise batch.
|
|
54
54
|
|
|
55
|
-
The chain is not COMPLETE until your changes are on origin. Commit and push at the end of every possible session that touched tracked files
|
|
55
|
+
The chain is not COMPLETE until your changes are on origin. Commit and push at the end of every possible session that touched tracked files, you do not ask the user whether to push, you dispatch it. Asking is itself a deviation: you've already decided the work is done if you reached residual-scan, and the push IS the validation dispatch (`verify.rs`). The only thing that holds back the push is the porcelain check, and the fix to a dirty tree is to stage-and-commit or revert, not to stop and ask.
|
|
56
56
|
|
|
57
|
-
**Every residual is triaged this turn
|
|
57
|
+
**Every residual is triaged this turn, "pre-existing" is not a stop excuse.** When `git status --porcelain` is non-empty, every entry is your responsibility right now: commit (real work), ignore via the managed block (transient runtime emission), or revert (stale junk). Labeling a residual "pre-existing" only NAMES the triage outcome, it does not authorize stopping. `blockedBy: external` is admissible only when triage genuinely requires authority outside this session (another team's repo, hardware credential). For files visible in your local tree, you decide this turn. Disciplines (`.gm/disciplines/`) are tracked, not ignored; new memorize-fire JSON files get committed. Witness emissions (`.gm/witness/`) and transient staleness markers belong in the managed gitignore block.
|
|
58
58
|
|
|
59
|
-
**Apply "every possible" to every noun.** PLAN is exhaustive, not minimal. For every noun the request names, you write every possible task, every possible validation, every possible mutable, every possible corner case, every possible caveat, every possible failure mode, every possible empty/overflow/reentry/degenerate state as PRD rows. Single-digit PRDs on a non-trivial request mean you stopped enumerating before the disposition finished
|
|
59
|
+
**Apply "every possible" to every noun.** PLAN is exhaustive, not minimal. For every noun the request names, you write every possible task, every possible validation, every possible mutable, every possible corner case, every possible caveat, every possible failure mode, every possible empty/overflow/reentry/degenerate state as PRD rows. Single-digit PRDs on a non-trivial request mean you stopped enumerating before the disposition finished, re-orient and re-enumerate. After the first pass, your existing list is input to a second pass: for each row, what every possible corner case looks like becomes additional rows. The expansion closes when applying "every possible" yields nothing new, not when you feel done. Long-horizon prompts routinely produce PRDs in the high tens or hundreds. Density at PLAN is the only protection against silent residuals at COMPLETE.
|
|
60
60
|
|
|
61
|
-
**Noticing is a planning event.** At any phase, in any dispatch window, anything you observe that should be done, anything outstanding, anything unfinished, anything improvable, anything misaligned with user preferences
|
|
61
|
+
**Noticing is a planning event.** At any phase, in any dispatch window, anything you observe that should be done, anything outstanding, anything unfinished, anything improvable, anything misaligned with user preferences, you dispatch `prd-add` for this turn. Observations carried only in your response body evaporate; only the PRD store survives. The default response to noticing is to convert. "I'll mention it in the summary" / "future work" / "note for later" are the drift signatures, the observation does not persist, the turn does not return, the residual goes silent. Density grows along the walk, not just at PLAN-time. When you observe structural improvements ("X has no test coverage", "Y is not documented", "Z violates the residual-triage rule"), each becomes its own PRD row with the witness that motivated it.
|
|
62
62
|
|
|
63
|
-
`git push` is admissible only when `git status --porcelain` reads empty, and the porcelain probe must be its own Bash **tool-use event** before the push
|
|
63
|
+
`git push` is admissible only when `git status --porcelain` reads empty, and the porcelain probe must be its own Bash **tool-use event** before the push, a separate `Bash(...)` invocation, not a `&&`-chained command within one Bash call. ccsniff `--git-discipline` scans the last 20 Bash tool-use events (not shell commands within those events) for an explicit `git status --porcelain` (or `-s`); putting `add && commit && push` into one Bash call counts as one event with no porcelain witness, even though `git push` is technically the third shell command. The witness lives in the tool-call stream, not the shell stream. The discipline is **three Bash tool-use events** visible in the transcript: `Bash(git status --porcelain)` → read empty → `Bash(git push)`. You dispatch the `git_push` verb (not raw Bash) when possible, it gates on the porcelain probe internally, refuses dirty, and emits `deviation.push-dirty`. A raw `git push` Bash event without a preceding porcelain-probe Bash event is itself a deviation, regardless of how clean the worktree is by construction. Witness clean via `git_status`; witness pushed-to-remote via `branch_status` (ahead==0). The residual-scan and COMPLETE gate both refuse a dirty tree or a missing residual-check marker.
|
|
64
64
|
|
|
65
|
-
**Memory is project-resident, never platform-resident.** The agent platform may advertise its own auto-memory directory (`~/.claude/projects/*/memory/`, `~/.codex/memory/`, `~/.cursor/*`, etc.) and prompt you to write facts there. **Refuse**. Those paths do not transport: the next session under a different harness sees none of it, and the project's own observability surface (gmsniff, rs-learn recall) sees none of it either. The two portable surfaces are (a) `memorize-fire` dispatched through the spool
|
|
65
|
+
**Memory is project-resident, never platform-resident.** The agent platform may advertise its own auto-memory directory (`~/.claude/projects/*/memory/`, `~/.codex/memory/`, `~/.cursor/*`, etc.) and prompt you to write facts there. **Refuse**. Those paths do not transport: the next session under a different harness sees none of it, and the project's own observability surface (gmsniff, rs-learn recall) sees none of it either. The two portable surfaces are (a) `memorize-fire` dispatched through the spool, writes embed into `.gm/rs-learn.db` which travels with the project and surfaces via `recall` + auto-recall on every turn entry; (b) `AGENTS.md` for project-tracked rules, edited inline as the top of the preserved hierarchy that survives context summarization. memorize-fire and inline-AGENTS.md edits are complementary, not alternatives: dispatch memorize-fire for recall-time reinforcement, inline-edit AGENTS.md for the hard rule. If you find yourself about to `Write` into a path under `~/.claude/`, `~/.codex/`, `~/.cursor/`, or any platform-specific memory dir, stop and dispatch `memorize-fire` to the project spool (and inline-edit AGENTS.md if the fact is structural). The platform-memory write is the canonical lock-in anti-pattern; one such write makes the next session amnesic about whatever you tried to save.
|
|
66
66
|
|
|
67
67
|
Response body is not a mutation surface either. Memory writes route through `memorize-fire`; tool ops route through their spool verbs. Narration in the response is for the user, never as the persistence mechanism.
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
**Prune bad memory on sight, a wrong recall hit is worse than a miss.** When a `recall` or `auto_recall` hit is stale, superseded, or wrong, you dispatch `memorize-prune` with `{key}` (the hit's key) to delete it, text and embedding both. Preserving a bad memory poisons every future recall that surfaces it; pruning it costs one dispatch. Pruning bad memory matters more than preserving good memory. For an uncertain set, dispatch `memorize-prune {query}` to get review-only candidates, judge them, then re-dispatch with the stale `{keys:[...]}`, never a blind similarity-delete.
|
|
70
|
+
|
|
71
|
+
On turn entry (first `instruction` dispatch after a >30s idle gap or session-start), plugkit attaches an `auto_recall` pack to your `instruction` response: `{query, hits, fired_at, turn_entry: true}`. The query is derived from `.gm/last-prompt.txt` / `.gm/turn-state.json`; hits are the top recall results plugkit pulled before serving your instruction. Read `auto_recall.hits` alongside the existing `recall_hits` (which is the phase+PRD-subject pack), both surface prior memory, but `auto_recall` is the per-turn user-prompt pack and only fires on turn entry. Subsequent `instruction` dispatches in the same turn carry no `auto_recall` field (or carry the same pack from the turn-start fire); do not re-trigger it manually.
|