@aemonge-dev/pi-openspec-gate
Declared-work gate for Pi: accounting at the tool-call boundary — every mutation is exempt-recorded, attributed to a focused change, declared by the human, or blocked with remedies. Never permission; never a renderer; never a prompt. Flow vocabulary is sc
Package details
Install @aemonge-dev/pi-openspec-gate from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@aemonge-dev/pi-openspec-gate- Package
@aemonge-dev/pi-openspec-gate- Version
0.7.0- Published
- Oct 27, 2026
- Downloads
- 346/mo · 283/wk
- Author
- aemonge
- License
- MIT
- Types
- extension
- Size
- 180.2 KB
- Dependencies
- 0 dependencies · 1 peer
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-openspec-gate
Declared-work gate for Pi: accounting at the tool-call boundary. Every mutating tool call is exactly one of:
- exempt — path-scoped (
openspec/**, docs, skills), always recorded - attributed — the session's openspec focus names an active change; a
receipt (tool · paths · UTC) lands in that change's
5-execution.md - declared — the human says a phrase a schema declares (
x-declares, e.g. the shippedmissionschema declares "action the mission" / "execute the mission"); one visible debt line inopenspec/bare-work.md, then it proceeds (one-shot latch) - blocked — visible, byte-stable, state-aware: a stop directive, then only remedies that are real in this state
⛔ unaccounted mutation — no active change
stop: this refusal is final — do not retry, do not investigate; act on one remedy or relay them to the human
why: segment 'python3 --version' is not classified read-only — read-only commands still pass
focus: openspec_focus → action: set, change: <slug> (a change that exists in openspec/changes/)
create: openspec new change <slug> --schema spec
declare: relay to the human — their reply says one of "action the spec" · "execute the spec" (an assistant's own message never declares); recorded as declared work, proceeds
Never permission, never a renderer, never a prompt: another layer owns
trust; this gate owns declaredness. unknown = mutating is a fail-safe,
not a judgment. Change entry (openspec new / init) is never gated.
Subagents inherit the parent's focus — federated accounting. Memory tools
are instruments: ungated, recorded, never blocked. Blocks are state-aware:
the stop directive opens every one, each remedy names its exact instrument,
the focus line disappears when no change exists to focus, the create line
names the resolvable schemas instead of a placeholder, a refused bash
compound names its offending segment (read-only commands still pass), the
declare line names the SPEAKER — relay to the human, whose reply says one
of the exact x-declares phrases (an assistant's own message never
declares; a phrase in assistant text is noticed and the next block carries
the correction — the built-in "do it" alias is retired) — and subagent
sessions get a relay-upward line.
The shipped mission schema
One human-validatable value slice as a five-step flow — idea →
proposition → proposal → plan → execution. Approvals flow down step by
step; evidence flows up inside each; the claim born at step 2 (Claim:
one testable, falsifiable sentence) is judged by step 5's final
Human-validation checkbox.
The schema declares the gates; the gate enforces them:
name: mission
version: 1
x-declares: [action the mission, execute the mission, action the job, execute the job]
artifacts:
- id: proposition
generates: 2-proposition.md
x-grant-verb: deal
x-requires-approval: [idea]
x-lines: [Claim]
- id: execution
generates: 5-execution.md
x-grant-verb: execute
x-requires-approval: [plan]
x-verdict-line: Human validation
x-receives-receipts: true
x-completes: approved
x-unlocks-source: true
openspec strips unknown x- fields silently (non-strict schema); the gate
is their only consumer and validates them loudly. Schemas without tags get
no phase gates — legacy single-approve behavior. The vocabulary in full —
x-declares (the human's declaration phrases), x-grant / x-grant-verb
(the same tag, short and long form; both on one artifact fails loudly),
x-requires-approval (grants), x-requires-written (evidence: the listed
artifacts' files must be on disk before this one drafts — existence, not
approval), x-unlocks (capability classes this grant opens: research,
writes; x-unlocks-source: true is the legacy alias for [writes]),
x-sections (bare or rich, gating an artifact’s own sections
with x-requires-written / x-requires-approval), x-verdict-line,
x-receives-receipts, x-completes — is data; the
engine hardcodes none of it. Schemas resolve project-local first
(<repo>/openspec/schemas/), then openspec's global home
(~/.local/share/openspec/schemas/), then ~/.config/openspec/schemas/.
Dependencies point to earlier artifacts only (declaration sequence is
grant sequence); self- or forward-references fail loudly at parse. Verbs,
filenames, verdicts, unlock points, and declaration phrases are schema
data.
Phase gates
Creating artifact N+1 blocks until artifact N bears a human grant, and
declared sections/lines must exist at birth. Evidence dependencies
(x-requires-written) gate creation on file existence — no grant needed,
only the listed artifacts on disk — and a grant refuses to stamp if
declared evidence vanished after its own grant. The first artifact
drafts free; existing files stay editable (receipts flow). status: approved is written only when every artifact is granted — attribution
is still not approval.
pios-gate grant <slug> <artifact-id> # canonical: id or artifact/section path
pios-gate <verb> <slug> # an x-grant-verb the schema declares
pios-gate approve <slug> [artifact-id] # canonical stamp (gated schemas: per-step only)
pios-gate confirm <slug> # permits the declared verdict-line flip
pios-gate pending # the cockpit — every change, next acts
pios-gate inspect <slug> # one change, verbose (artifacts, stamps, structure)
pios-gate focus # teaching: focus is a session instrument
pios-gate <anything else> # exec openspec verbatim, exit code intact
Grants are strictly next-artifact-only, you approve what you read
(undrafted artifacts refuse), and the grant itself refuses to stamp
artifacts missing declared structure or carrying ungranted gated
sections. Any tool-call attempt on the grant surface — approve, confirm,
grant, or any verb the schemas declare — is itself blocked: approval is a
human act. The research window: when a focused change's schema declares
x-unlocks: [research] on an artifact, research instruments block until
that artifact is granted — the schema declares when, the config declares
what (tools + bash command verbs; web/docs tools by default, graphify
here). Research without a focused change is also gated inside any repo
with a local openspec/ — void research is unaccounted work, and it
blocks with the same state-aware remedies (focus → create → declare);
outside an openspec repo research stays free (the gate is inert there).
"approval": { "enforce": true, "humanGateSteps": true,
"archiveGuard": true, "stampCreations": true,
"phaseGates": true },
"unlocks": {
"research": {
"tools": ["web_explore", "web_search", "web_fetch", "resolve-library-id", "query-docs"],
"commands": ["graphify"]
}
}
No prompts
The extension injects nothing into LLM context: no context handler, no
protocol block, no shipped skills. Every remedy the gate needs rides its
own block reasons; every flow word it acts on comes from schema x- tags
in data. Enforce first, explain in the README — the teaching can never
drift from the teeth because the teeth are all there is.
Try it — the shipped example
./example/try.sh # or: just try
A self-verifying walkthrough in a scratch repo: the negatives first
(grant sequence, approve-what-you-read, structure refusal), then the
four-grant lifecycle of the v4 spec schema (open → dig → commit →
build) to status: approved, confirm, and ready to archive? —
asserting every step. Copy example/openspec/schemas/spec into your
repo as the starting point. See example/README.md.
Install
pi install npm:@aemonge-dev/pi-openspec-gate # personal
pi install -l npm:@aemonge-dev/pi-openspec-gate # project-scoped
npm install -g @aemonge-dev/pi-openspec-gate # pios-gate + alias
The extension (session gate) and the terminal CLI are one package, two installs — run both and you're fully installed.
Config (config.json beside index.ts)
{
"enabled": true,
"exempt": [
{ "prefix": "**/openspec/**", "category": "openspec-artifacts" },
{ "prefix": "**/docs/**", "category": "documentation" },
{ "prefix": "**/skills/**", "category": "declared-skills" }
],
"accountingTools": ["work_focus", "openspec_focus", "stage", "subject",
"mem_save", "mem_update", "mem_delete",
"mem_save_prompt", "mem_session_summary",
"mem_session_end", "mem_capture_passive"],
"readOnlyTools": []
}
Exemptions are path-scoped only, never fuzzy. accountingTools names
instruments that pass ungated — the gate never gates its own layer, and
memory writes are instruments: bookkeeping about the work, never the work.
readOnlyTools merges over the built-in research set — the escape hatch
for new research tools without a package release.
Facts (session entries; any reader may consume, the gate never renders)
| Fact | Meaning |
|---|---|
pi-openspec-gate.blocked.v1 |
blocked: tool, paths, basis, UTC |
pi-openspec-gate.declared.v1 |
a human declaration consumed one mutation |
pi-openspec-gate.attributed.v1 |
mutation attributed to the focused change |
pi-openspec-gate.exempt.v1 |
path-exempt pass with category + paths |
pi-openspec-gate.inherited.v1 |
subagent inherited the parent's focus |
pi-openspec-gate.instrument.v1 |
allowlisted instrument passed |
pi-openspec-gate.unapproved.v1 |
unapproved change blocked |
pi-openspec-gate.humangate.v1 |
human-validation flip checked |
pi-openspec-gate.creation-stamped.v1 |
witnessed creation stamped proposed |
pi-openspec-gate.phasegate.v1 |
artifact gate: missing-approval / missing-sections |
Development
just lint # vocabulary law: permission + flow words stay out of the gate
just test # full suite (node:test, no deps)
just build # loader smoke + packed-artifact gates
just publish <version> # gates → bump+commit+tag → npm publish → global install
The shipped mission example in one sentence: a mission is born free (idea), commits to a
falsifiable claim (proposition), is signed as a deal (proposal), exposes
its method and knowledge (plan), and earns its debrief (execution) — where
the human's verdict upholds or refutes the claim it opened with. Write your own schema the same way: declare it. The full
ledger — every change ever built, with receipts — lives in
openspec/changes/archive/, and CHANGELOG.md is its
human-readable front door.
License
MIT