@cad0p/pi-steering-github
GitHub workflow rules for pi-steering — every PR closes at least one issue; PR/issue bodies come from napkin vault body files.
Package details
Install @cad0p/pi-steering-github from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@cad0p/pi-steering-github- Package
@cad0p/pi-steering-github- Version
0.1.0- Published
- Aug 14, 2026
- Downloads
- 2,650/mo · 227/wk
- Author
- cad0p
- License
- MIT
- Types
- package
- Size
- 105.5 KB
- Dependencies
- 1 dependency · 1 peer
Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-steering-github
GitHub workflow rules for pi-steering: every PR closes at least one issue; PR/issue bodies come from napkin vault body files.
Ported from the live prototype that ran in the global pi-steering config (first live validation 2026-08-14, pi-steering PR #46 session: create gate fired, agent complied in 7s). Reason strings are byte-identical to the prototype — agents in the wild rely on them.
What it ships
One Plugin (name: "github") with four rules and one predicate:
| Rule | Fires on | Blocks when |
|---|---|---|
pr-body-from-vault-file |
gh pr create | new | edit |
the body doesn't come from --body-file pointing at a <repo>/prs/ napkin-vault file (inline --body is blocked) |
pr-create-needs-issue-link |
gh pr create | new |
the --title value or the body lacks a closing keyword + #N |
pr-merge-needs-closing-keywords |
gh pr merge |
--subject or --body lacks a closing keyword + #N |
issue-body-from-vault-file |
gh issue create | edit |
the body doesn't come from --body-file pointing at a <repo>/issues/ napkin-vault file |
| Predicate | Purpose |
|---|---|
missingVaultBodyFile |
true when --body-file is absent, unreadable, outside a napkin vault, or not under <repo>/<section>/ inside the vault (fail-closed) |
All rules are strict — no noOverride: false, so there is no agent-side override escape hatch. The policy is unconditional.
Install
pnpm add @cad0p/pi-steering-github
@cad0p/pi-steering-github declares @cad0p/pi-steering as a peerDependency and pulls @cad0p/pi-napkin as a runtime dependency (the vault rules use its ./steering subpath for napkin-vault detection — compiled JS since 0.7.0-20260814.0). Non-napkin users can disable the two vault body-file rules (see Disabling) — the @cad0p/pi-napkin dep stays but is inert.
Usage
// .pi/steering/index.ts
import { defineConfig } from "@cad0p/pi-steering";
import githubPlugin from "@cad0p/pi-steering-github";
export default defineConfig({
plugins: [githubPlugin],
});
Listing the plugin feeds its rule/predicate names into defineConfig's type unions, so disabledRules typos fail at compile time.
Rules
pr-body-from-vault-file
gh pr create|new|edit must take the body from --body-file pointing at a file inside a napkin vault under a <repo>/prs/ directory. Inline --body is blocked entirely.
Placement only — no content check. The closing-keyword content check belongs to pr-create-needs-issue-link (responsibility separation). Why vault body files: they are reviewable, persistent, and kb-discoverable — the body is written and reviewed before the command runs, so the PR description is a deliberate artifact rather than an inline afterthought.
pr-create-needs-issue-link
gh pr create|new must carry a closing keyword (close/closes/closed, fix/fixes/fixed, resolve/resolves/resolved) + #N in both the inline --title value and the body. The body normally comes from the vault body file, so the check reads the file content (falling back to inline --body text).
- Title keyword — the squash-merge subject inherits the PR title, so even a web-UI merge with a Title-Only commit policy auto-closes the issue from the commit subject.
- Body keyword — drives the GitHub "linked issues" sidebar link and the description-channel auto-close on merge.
- Multiple issues — repeat the keyword per issue (
"Closes #A, closes #B");"Closes #A #B"honors only the first number. A bare#Nmention never counts; colons and case variants are accepted. - Draft PRs are gated like any other PR (a tracking issue is the allowed pattern while a draft is open).
pr-merge-needs-closing-keywords
gh pr merge must carry a closing keyword + #N in both the --subject value (commit subject) and the --body value (commit body) — either flag order, short -t/-b forms, --flag=value forms. Passing both explicitly protects against PR title/body edits between creation and merge; GitHub parses both channels even with a Title-Only commit policy.
issue-body-from-vault-file
gh issue create|edit must take the body from --body-file inside a napkin vault under a <repo>/issues/ directory. No keyword requirement — issues close nothing.
Predicate
when.missingVaultBodyFile takes { section: "prs" | "issues" } and returns true (rule blocks) when the command's --body-file value is missing, unreadable, outside a napkin vault, or not under <repo>/<section>/ inside the vault. <repo> is the origin URL basename of the git repo the command runs in (falling back to the cwd folder name when the remote is unresolvable). Fail-closed: anything unverifiable counts as missing — including a walker-unknown cwd (cd "$X" && gh pr create ...).
Disabling
Strict rules are still individually disableable at the config level:
export default defineConfig({
plugins: [githubPlugin],
// Keep the issue-link policy but allow inline --body anywhere.
disabledRules: ["pr-body-from-vault-file", "issue-body-from-vault-file"],
});
Non-napkin users (no obsidian vault) should disable the two vault body-file rules and keep the keyword rules — pr-create-needs-issue-link then falls back to inline --body text, and pr-merge-needs-closing-keywords is self-contained:
disabledRules: ["pr-body-from-vault-file", "issue-body-from-vault-file"],
Helpers (escape-hatch)
When the built-in predicate isn't enough, reach for the exported helpers inside when.condition:
findFlagValue(ctx, flags)— value of the first occurrence of a flag (space or=form), unquoted.resolveAgainstCwd(ctx, path)— resolve a path against the command's effective cwd (nullon walker-unknown cwd).bodyHasClosingKeyword(ctx)— does the body (vault body-file content, or inline--body) carry a closing-keyword ref?repoName(ctx, cwd)— origin URL basename, cwd-basename fallback.unquote(text)/argText(ctx)— low-level walker-word utilities.
The pattern constants (CLOSING_KEYWORD, ISSUE_REF, TITLE_WITH_REF, SUBJECT_WITH_REF, BODY_WITH_REF, PR_BODY_ANCHOR, PR_CREATE_ANCHOR, PR_MERGE_PATTERN, ISSUE_BODY_ANCHOR) are exported too — they are what the rules ship, pinned by the unit tests.
Known limitations
- Inline
--bodyis blocked by the vault body-file rules by design — the file is the source of truth. If you need inline bodies, disable those rules. --body-filecontent is checked at eval time bypr-create-needs-issue-link/pr-merge-needs-closing-keywords: the file must already contain the closing keyword when the command runs.pr-merge-needs-closing-keywordsadditionally has no--body-filesupport — merge commit messages must be passed explicitly via--subject/--body.- Value-region truncation: pattern matching runs on the walker-normalized command, and a flag's value region ends at the next
\s-pair. A value containing a literal-(space-dash-space) truncates the region, so a closing-keyword ref after such a sequence may be missed (rule fires; add the keyword earlier in the value).
License
MIT