pi-claude-supervisor
A policy-gated Pi supervisor for observing and verifying Claude Code workers.
Package details
Install pi-claude-supervisor from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-claude-supervisor- Package
pi-claude-supervisor- Version
0.8.0- Published
- Sep 20, 2026
- Downloads
- 1,782/mo · 1,782/wk
- Author
- btnalit
- License
- MIT
- Types
- extension
- Size
- 894.7 KB
- Dependencies
- 0 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-claude-supervisor
English · 简体中文
What it is
pi-claude-supervisor is a standard Pi agent extension package that
supervises Claude Code for unattended local development. Pi owns the task's
lifecycle, state machine, policy decisions, acceptance checks, and an
independent Review step; Claude Code is the Worker that does the editing. The
extension is a single pi install: Pi discovers and loads it itself, there is
no separate build or binary, and there is nothing to configure inside Pi
beyond environment variables.
There is a hard boundary the Worker can never cross, regardless of its own
permission settings: it may not push to a remote, merge into main or an
integration branch, open a pull request, or run a remote CLI mutation;
.git metadata writes and destructive rewrites of protected branches are
denied outright. Everything else — edits, tests, shell commands, local
commits — follows the policy you configure. The extension itself never
merges, deploys, releases, or publishes anything at runtime.
How the loop works, once a task starts:
- The Worker works a turn; when it stops (
turn_completed), Pi's Decision Worker — a persistent Pi session with read-only tools — chooses to continue, redirect, answer a question, verify, stop, or park the task. verifyruns the acceptance checks (the defaultgit diff --check, or the checks from a--specfile).- An independent Reviewer — a fresh, read-only Pi session — returns pass, revise, or human.
revisesends the Worker a bounded repair turn (up tomaxRepairRounds); a pass promotes the work to acompletedcandidate.- Unresolvable work is parked as
blocked(a non-publishable candidate, not a crash); crashes and timeouts becomefailed. - Every terminal state emits a candidate notice, in the Pi UI and optionally to a webhook (WeCom or generic JSON, with retries).
Quick start
Requirements: Pi 0.85+, Node.js 22.19+, Claude Code 2.1.270+ (interactive hooks verified on 2.1.273), Linux for the cgroup and tmux features.
Install it like any other Pi extension:
pi install npm:pi-claude-supervisor
That's the whole installation — Pi loads the package's ./src/index.ts
extension directly and registers the /supervise command. Configuration is
environment variables only, set before starting Pi (or in
~/.config/pi-claude-supervisor/env):
export PI_CLAUDE_SUPERVISOR_MODE=auto
export PI_CLAUDE_SUPERVISOR_TRANSPORT=tmux
# Optional: candidate/failure notifications
export PI_CLAUDE_SUPERVISOR_HUMAN_WEBHOOK_URL='https://example.invalid/webhook'
export PI_CLAUDE_SUPERVISOR_HUMAN_WEBHOOK_FORMAT=generic
Then, inside any Pi session:
/supervise start implement the requested change
/supervise adopt-tmux my-tmux-session implement the requested change
/supervise start --deadline 8h a large multi-worktree change
Both accept --spec <file> and --deadline <duration> (8h, 90m, 0 for
no deadline) ahead of the task text.
Watch a task with /supervise status <task-id> or /supervise sessions; for
a tmux transport, attach directly with the tmux -S <socket> attach -t <session> command each of these prints. /supervise stop <task-id> closes
the task; on the interactive tmux transport a completed task instead leaves
the session open for you by default (see below).
Modes and transports
PI_CLAUDE_SUPERVISOR_MODE=auto (or PI_CLAUDE_SUPERVISOR_AUTOMATION=1)
enables automatic supervision — the Decision Worker/Reviewer loop above.
Without it, /supervise still exposes its commands, but a Worker runs
without that loop.
| Transport | TRANSPORT |
TMUX_MODE |
What the Worker runs as | Use it when |
|---|---|---|---|---|
| Interactive tmux | tmux |
interactive (default) |
The real, unmodified Claude Code TUI in a tmux pane, driven by Claude Code hooks | You want to watch or occasionally type into the exact session Claude uses |
| Headless JSONL | jsonl (default in auto mode) |
– | claude -p --input-format stream-json, no terminal |
Every permission-relevant command must be visible to the Supervisor |
| tmux bridge | tmux |
bridge |
Claude's stream-json protocol, rendered into a tmux pane | A visible pane with the older structured (pre-hook) transport |
| Manual (process-pipe) | process-pipe (default when MODE is unset) |
– | The worker's stdin/stdout as plain text | Exposing the commands without automatic supervision |
Interactive tmux mode (hooks)
PI_CLAUDE_SUPERVISOR_TRANSPORT=tmux defaults to running the real,
unmodified Claude Code TUI in the tmux pane — the same interface you would
see running claude yourself — instead of the structured stream-json
bridge. You can attach to the printed attach=... command at any time and
watch, or type into the session yourself; Pi reports its events through
Claude Code's own hooks rather than scraping the screen.
When the extension loads with PI_CLAUDE_SUPERVISOR_TRANSPORT=tmux in
interactive mode (the default TMUX_MODE), it automatically installs a small
relay command for the eight Claude Code hook events it uses into
~/.claude/settings.json (or $CLAUDE_CONFIG_DIR/settings.json) — idempotent,
and announced once in the Pi UI. Set
PI_CLAUDE_SUPERVISOR_AUTO_INSTALL_HOOKS=0 to opt out; /supervise uninstall-hooks removes the entry, and /supervise install-hooks remains
available to install it by hand. The relay is a ~1ms no-op in any Claude
session no Supervisor is listening for. An owned /supervise start does not
depend on this at all — it passes its own --settings file — but
/supervise adopt-tmux runs inside your normal Claude Code configuration and
needs the relay installed there.
/supervise start <task>
/supervise adopt-tmux <tmux-session> <task>
Pi intervenes when Claude stops a turn (Stop; an API/model failure mid-turn
arrives as StopFailure and is treated as an errored turn the Decision
Worker can retry, and a minute of idle prompt with no stop signal closes the
turn as a safety net), when Claude is about to show you a real permission
prompt (only then — every ordinary tool call is otherwise left to your own
Claude Code permission mode), when Claude asks AskUserQuestion (the
Decision Worker picks an answer and Claude continues with it as ordinary
text, exactly as in headless mode), and when the session exits. Before that
prompt, a PreToolUse veto point only ever denies a known direct remote
push/merge/PR or other destructive/protected-branch operation (or forwards an
AskUserQuestion); it never second-guesses a normal edit, read, or local
command — those reach your own permission mode with no decision from Pi at
all.
What this means for the boundary. Interactive mode deliberately skips the
headless-mode check that refuses inherited Bash pre-authorization or an
auto/bypassPermissions mode in your Claude settings: your own
configuration governs what Claude may do without asking, exactly as when you
run Claude yourself. Anything your settings already allow never reaches the
Decision Worker; its judgment applies only where Claude would have asked
you. The hard boundary (remote push/merge/PR, remote CLI mutation, .git
writes, destructive protected-branch rewrites) is enforced by PreToolUse
regardless of permission mode — verified against auto mode on Claude Code
2.1.273 — and is the only guarantee this mode makes beyond your own
settings. Use headless (bridge) mode when every Bash call must be visible
to the Supervisor.
Adopting an idle session. adopt-tmux types the task into the session
only when Claude is idle at its prompt; a session caught mid-turn keeps its
current work and is judged on its next Stop instead.
Human coexistence. If you type into the attached session, automation
pauses (human_takeover, visible as a warning) until you run /supervise resume-auto <task-id>; the turn that completed while you were driving is
replayed to the Decision Worker at that point, so nothing already finished is
lost.
Completion hands the session back. Unlike other transports, a completed
task by default disconnects Pi from the session instead of closing it, so you
can keep working in the same window or review what Claude did; /supervise stop <task-id> closes it explicitly, and a blocked or failed candidate still
stops the Worker as usual. Set
PI_CLAUDE_SUPERVISOR_CLOSE_WORKER_ON_COMPLETION=1 to restore the old
close-on-completion behavior. The hand-back is clean, not a bare disconnect:
before reporting the candidate ready, Pi moves every process out of its
private cgroup into its parent (instead of killing them) and stops the
guardian process, and the tmux server and pane are left alone — the session
survives a Pi restart with nothing left owing it. Afterward it is an ordinary
tmux session with no Supervisor attached; tmux -S <socket> attach -t <session> reaches it directly, and /supervise adopt-tmux can babysit it
again exactly as it would any other externally created session.
Cost accounting limits. The TUI's Stop hook has no total_cost_usd or
token usage (that only comes from Claude's own result stream-json record,
which the TUI does not emit), so cost tracking in interactive mode only
counts turns, not dollars; --max-budget-usd is also unavailable (Claude
Code only enforces it under -p) and is not passed to an interactive launch.
Set autonomy.maxWorkerCostUsd expecting it to have no effect in interactive
mode, or use bridge/jsonl mode when a hard cost cap matters.
Trust dialog. The very first time Claude Code runs in a given directory
it shows its own one-time "do you trust this folder" dialog before any hook
fires. For a session that /supervise start launched, the launcher accepts
it automatically — only when the pane's directory is the task directory. An
adopted session was started by you, so you already answered it.
Headless mode (JSONL)
PI_CLAUDE_SUPERVISOR_TRANSPORT=jsonl runs Claude as claude -p --input-format stream-json, with no terminal at all; it is the default
transport once PI_CLAUDE_SUPERVISOR_MODE=auto is set. Every permission
request Claude makes is answered by the Supervisor — the deny list, routine
in-cwd edits, and read-only/local-dev shell commands are answered by policy
alone, and everything else goes to the Decision Worker. worker_usage
events carry full token and cost accounting from Claude's own result
records, so --max-budget-usd and the token/cost reporting below both work
as expected. Use this transport when nothing should ever run without the
Supervisor being able to see it, or when you don't need to attach.
Safety boundary
- Always denied, regardless of policy or permission mode: remote push,
merge/PR into
mainor an integration branch, other remote CLI mutations,.gitmetadata writes, and destructive rewrites of protected branches (reset,update-ref,symbolic-ref, or a delete/move/forcebranch). A shell argument the policy cannot see through ($VAR,$(…), a glob) is vetoed, best-effort, only on the commands where it could reach that boundary — git, gh, npm/pnpm/yarn, curl/wget/ssh, a nestedclaude, or an interpreter/runner such aseval,sh -c,xargs,find -exec. A quoted heredoc body is judged by its consumer: a shell runs it,cat > fileorgit commit -mstores it. Everything else (for f in …; do echo "$f",rm -rf ./dist, a Write to Claude's own scratchpad) follows the configured policy — Claude's own permission mode governs it, as when you run Claude. autonomy.permissionAuthority(policy|hybriddefault |decision-worker) controls who answers a permission request — every request in headless mode, and in interactive tmux mode only those Claude would otherwise have shown you as a prompt:hybridanswers routine in-cwd edits and local read-only/dev shell commands from policy alone, and sends every ambiguous request to the Decision Worker (a policy denial is always applied directly).- Baseline, not branch. Any branch, including
main, may be supervised; the candidate only has to descend from the recorded baseline commit (merge-base --is-ancestor). A branch change mid-task is recorded (worker_branch_changed), not rejected, and a candidate on a protected branch is reported in its notice (branch,protectedBranch), not parked.checkout/switchontomainis allowed; only a destructive rewrite of a protected branch name is denied. Claude Code's own "branch first if you're on the default branch" guidance is advisory, not enforced. - Worker commands launch without a shell. Automatic mode admits only the bare
claudecommand name and pins an operator-owned, non-writable executable path (PI_CLAUDE_SUPERVISOR_TRUSTED_CLAUDEto pin one explicitly). - On Linux, a cgroup v2 boundary cleans up every descendant, including
setsid()descendants;requiredmode fails closed instead of falling back. - A wall-clock deadline (4 hours by default,
--deadline 8hper task orDEADLINE_MS) bounds a task. Reaching it does not kill the work: the Decision Worker is warned ahead of time (DEADLINE_WARNING_MS, 15 min) and told to steer the Worker to a wrap-up, and once the deadline passes a close-out window opens (DEADLINE_GRACE_MS, 30 min) in which an idle Worker is verified and reviewed instead of stopped, awaitdecision is no longer honored, and a repair round tells the Worker how long it has left. Only when the close-out window has also elapsed is the Worker stopped outright (worker_watchdog_timeout), and on an adopted interactive session that stop is a release: Claude keeps running, unsupervised. The close-out belongs to automatic tasks; a manual task is stopped at the deadline as before, andDEADLINE_GRACE_MS=0restores that for automatic ones too. A 20-minute no-output watchdog (NO_OUTPUT_TIMEOUT_MS) still stops a silent Worker at any time. - Acceptance checks, evidence collection, and the Reviewer share an abort signal, so a stop or shutdown does not wait for a full command or model timeout.
- Only one cwd lease is held per task; concurrent tasks need separate worktrees.
Task specs
--spec file.json accepts:
{
"goal": "Implement the requested change",
"scope": ["src/"],
"constraints": ["Keep the public API compatible"],
"forbidden": ["Do not publish artifacts"],
"acceptance": [
{ "id": "tests", "name": "tests", "command": "npm", "args": ["test"], "required": true, "timeoutMs": 120000 }
],
"maxRepairRounds": 3,
"autonomy": {
"unattended": true,
"requireLocalCommit": true,
"maxDecisionRetries": 2,
"permissionAuthority": "hybrid",
"maxWorkerCostUsd": 20
}
}
Checks always run with argv, never through a shell. A plain-text task (no
--spec) becomes a goal with the default git diff --check acceptance
check (120s timeout) and the env autonomy defaults below.
Configuration reference
Environment variables (or ~/.config/pi-claude-supervisor/env), all prefixed
PI_CLAUDE_SUPERVISOR_; see .env.example for a template.
| Variable | Default | Meaning |
|---|---|---|
MODE |
unset (manual) | auto enables automatic supervision (Decision Worker + Reviewer loop) |
AUTOMATION |
unset | 1 is equivalent to MODE=auto |
TRANSPORT |
jsonl in auto mode, process-pipe otherwise |
jsonl | tmux | process-pipe (manual only) |
TMUX_MODE |
interactive |
interactive (real TUI via hooks) | bridge (stream-json in a pane) |
AUTO_INSTALL_HOOKS |
true |
Interactive tmux mode only: automatically install the hook relay into the user's Claude settings on load; 0 opts out |
CLOSE_WORKER_ON_COMPLETION |
false |
Interactive tmux only: close the Worker/session on completion instead of leaving it open |
CGROUP_MODE |
auto |
off | auto | required; automatic mode always uses required on Linux; required is rejected for a manual (non-automatic) tmux Worker |
TMUX_SOCKET |
unset (default tmux server) | Socket path for adopting a non-default tmux server |
WORKER |
claude |
Worker command; may include arguments |
NODE |
unset (resolved from PATH) |
Explicit node executable path, for a Bun-compiled Pi |
TRUSTED_CLAUDE |
unset | Pins the expected resolved Claude executable identity explicitly |
STATE_DIR |
~/.pi/agent/claude-supervisor |
Supervisor state directory |
CWD_LEASE_DIR |
<state>/cwd-leases |
Shared cwd-lease registry directory |
WORKER_ENV |
unset | Comma-separated list of env vars to pass through to manual workers |
HUMAN_WEBHOOK_URL |
unset | Outbound candidate/failure notification endpoint |
HUMAN_WEBHOOK_FORMAT |
generic |
wecom | generic |
HUMAN_WEBHOOK_SECRET |
unset | HMAC signing secret; sent as the x-pi-supervisor-signature header |
UNATTENDED |
true |
Task runs without a synchronous human callback |
REQUIRE_LOCAL_COMMIT |
true |
Require a local commit on the candidate's branch before completion |
MAX_DECISION_RETRIES |
2 (0–10) |
Retries of a Decision Worker call that times out or fails (429/529, network, auth) |
PERMISSION_AUTHORITY |
hybrid |
policy | hybrid | decision-worker |
WORKER_MAX_BUDGET_USD |
unset | Hard cap passed as --max-budget-usd; unavailable to interactive tmux |
WORKER_MODEL |
unset (Claude's own default) | --model for the Claude Worker |
WORKER_AUTOCOMPACT_TOKENS |
200000 in automatic mode |
Per-turn context bound; 0 keeps Claude's own default |
WORKER_MCP_CONFIG |
unset | Path passed as --strict-mcp-config --mcp-config, restricting the Worker's MCP servers |
DECISION_MODEL |
unset (Pi's default) | provider/model-id for the Pi Decision Worker, as listed by Pi |
REVIEWER_MODEL |
unset (Pi's default) | provider/model-id for the independent Reviewer |
DECISION_COMPACT_TOKENS |
60000 |
Proactively compacts the persistent Decision Worker session past this size; 0 disables it |
PROGRESS_HEARTBEAT_MS |
60000 |
Minimum interval between repeated progress notifications for the same phase |
DECISION_SESSION_RETENTION_DAYS |
30 |
Prunes closed Decision Worker session records older than this; 0 keeps forever |
EVIDENCE_MAX_BYTES |
1048576 (1 MiB) |
Maximum repository evidence bytes collected per task |
EVIDENCE_MAX_UNTRACKED_FILES |
512 |
Maximum untracked files collected as evidence per task |
REVIEW_TIMEOUT_MS |
600000 (10 min) |
Total independent Reviewer budget per round, including one retry on a provider error |
DEADLINE_MS |
4h |
Cumulative wall-clock budget per task (8h, 90m, 2h30m or ms; 5m–7d); 0 (or 0m) disables it; --deadline overrides it per task |
DEADLINE_GRACE_MS |
30m |
Close-out window after the deadline for automatic tasks: an idle Worker is verified instead of stopped; 0 restores the immediate stop |
DEADLINE_WARNING_MS |
15m |
How long before the deadline the Decision Worker is warned and re-asked; 0 disables the warning |
NO_OUTPUT_TIMEOUT_MS |
20m |
Stop a Worker that has produced no output for this long; 0 disables the check |
EVENT_LOG_MAX_BYTES |
67108864 (64 MiB) |
Rotates events.jsonl at this size; 5 rotated files are kept |
Recovery, leases and state
After an unclean Pi restart, /supervise sessions lists recoverable tasks;
/supervise recover [--takeover] <task-id> restores the Decision Worker
context and starts a new Claude Worker. It never silently resumes or
duplicates work. Add --takeover only once the lease proves the old
Worker's process group is gone and its cgroup is a real, readable empty
boundary (and, for tmux, that the private tmux session is also gone);
missing or unverifiable evidence is refused rather than reclaimed.
/supervise recover does not persist whether the original task was
interactive — it derives that from the current TRANSPORT/TMUX_MODE
configuration at recovery time, so do not change either between starting a
task and recovering it.
A task that stopped at its wall-clock deadline is listed with deadline=expired … ago. Plain recover refuses it; recover --takeover --extend <duration> <task-id> grants that much budget from now (the recovered Supervisor persists
the new deadline), and --extend 0 opens the close-out at once, so the fresh
Worker's first watchdog tick verifies and reviews the repository as it stands
and any repair round tells it how long it has. A record nobody will recover is
dropped with /supervise discard <task-id> (its session file is kept until
retention pruning).
Every task holds one cwd lease under CWD_LEASE_DIR; concurrent tasks need
separate worktrees. A lease record that cannot be read (corrupt JSON,
unexpected shape) is quarantined instead of blocking other lookups, and
/supervise sessions lists the current quarantined records so an operator
can inspect and clean them up.
Events are append-only JSONL in <STATE_DIR>/events.jsonl, with
worker_output size-capped and the log rotated past EVENT_LOG_MAX_BYTES.
The Decision Worker session for each task is persisted as its own JSONL file
under the state directory, pruned by DECISION_SESSION_RETENTION_DAYS.
Notifications
Every terminal state (completed, blocked, failed) emits a candidate
notice in the Pi UI and, if HUMAN_WEBHOOK_URL is set, to a webhook in
either wecom or generic JSON form, signed with HUMAN_WEBHOOK_SECRET
when set. A parked candidate that asked a question sends a separate "needs
you" notice; a human takeover (you typed into the session) only shows an
in-UI hint, since you are already there. Either notice includes an
attach field with the literal tmux -S <socket> attach -t <session>
command when it concerns a tmux session, and a usage summary
(CandidateNotice.usage: cost, worker turns/tokens, Pi tokens, decision and
reviewer call counts). Webhook delivery retries transient errors (network,
429/5xx). Notifications are outbound-only: receiving one does not grant any
approval, and a webhook cannot push commands back into Pi — use /supervise send/approve/takeover for that.
Token usage and cost controls
Measured on one real unattended review task (29 minutes wall clock):
| Component | Turns/calls | Tokens | Cost |
|---|---|---|---|
| Claude Code Worker | 70 turns | 15.5M cache-read + 370k cache-write + 100k output | $18.46 |
| Pi Decision Worker | 30 model calls | ~1.0M (91k uncached + 914k cache-read) | $0.04 |
Almost all of the money goes to the Worker, not the Supervisor's own Decision
Worker or Reviewer calls. In this run the Worker averaged ~220k tokens of
context per turn because it ran as a single long -p session under a
1M-token window that never compacted; a trivial Claude Code turn costs
roughly 24k prompt tokens for its system prompt alone, regardless of which
MCP servers are configured. Of the 30 Decision Worker calls, 28 were
permission requests, and the Decision Worker overrode the deterministic
policy 4 times (denying downloads and writes outside the task directory) —
this is why hybrid is the default permissionAuthority, not policy.
Replaying those 28 requests through the shipped isRoutinePermission
classifier answers 4 of them locally; that task was dominated by inline
node -e scripts and $(...) substitutions, which are never routine. An
ordinary implementation task is mostly in-cwd Edit/Write, npm test and
git status/diff/add/commit, all of which are routine, so its Decision
Worker call count drops much further.
Knobs, with their defaults and trade-offs:
PERMISSION_AUTHORITY(policy|hybriddefault |decision-worker):hybridanswers routine in-cwd file edits and local read-only/dev shell commands from the deterministic policy alone (isRoutinePermissioninsrc/policy.ts) and still sends every ambiguous request, and every policy denial, to the Decision Worker. This mainly buys latency and a smaller Decision Worker context, not dollars: the 30 calls above already cost $0.04.WORKER_MODEL/--model: roughly a 5x price difference between Opus- and Sonnet-class models. This is the single largest lever on the actual bill, and it is the operator's choice; the Supervisor does not pick it for you.WORKER_AUTOCOMPACT_TOKENS(default 200000 in automatic mode;0keeps Claude's own default): bounds context per Worker turn so a long session does not keep accumulating ~220k-token turns. Worth tens of percent, at the cost of some context quality.WORKER_MAX_BUDGET_USD/autonomy.maxWorkerCostUsd: a hard cap passed to Claude as--max-budget-usdand re-checked by the Supervisor against the cumulative Workerresultcost. It is a cap, not a saving; a task that hits it is parked with its evidence.WORKER_MCP_CONFIG(--strict-mcp-config --mcp-config): restricts the Worker to only the listed MCP servers. It bounds what the Worker can reach, not the ~24k-token fixed overhead of an ordinary turn.DECISION_MODEL/REVIEWER_MODEL(provider/model-id, for exampleanthropic/claude-haiku-4-5-20251001): the Pi Decision Worker and Reviewer models. Pi-side usage was already a few cents in this run, so a cheaper model here mostly buys latency, not headline savings.DECISION_COMPACT_TOKENS(default 60000;0disables): proactively compacts the persistent Decision Worker session once its estimated context passes this threshold, and re-sends the startup instructions once on the next prompt after compaction.
The Supervisor records what it spends rather than estimating it after the
fact: every Worker result record becomes a worker_usage event, every
Decision Worker/Reviewer model call becomes a pi_usage event, and both
accumulate into session.usage (SupervisorTokenUsage). /supervise status <task-id> prints a cost=… workerTurns=… workerTokens=… piTokens=… decisionCalls=… reviewerCalls=… summary; progress notifications carry
SupervisorProgress.costUsd/.piTokens, and a candidate notification
carries the same summary through CandidateNotice.usage, which the generic
webhook serialises as a numeric usage object and the WeCom format renders
as two extra lines.
None of this changes what a task actually costs beyond the Worker model and
budget choice; the Supervisor-side changes here mainly cut Decision Worker
tokens and latency, which were cents to begin with. For a cost-sensitive
unattended run, a reasonable starting point is a Sonnet-class WORKER_MODEL,
an explicit WORKER_MAX_BUDGET_USD per task, the default hybrid permission
authority, and a Haiku-class DECISION_MODEL.
Development
Contributing to the extension itself (not required to use it):
npm ci --ignore-scripts
npm run check
npm run build
npm run test:pi
npm run test:install
Some tests validate the trusted-executable and protected-branch boundaries against the real checkout, so they must run from a non-protected branch and from a path that is not group/world-writable.
See architecture, testing, and
releasing for more detail; docs/autonomy-target.md
records the confirmed unattended-development target this project is built
around.
License
MIT. See LICENSE.