diff --git a/.context/LEARNINGS.md b/.context/LEARNINGS.md index a64dcc584..27bfd6ead 100644 --- a/.context/LEARNINGS.md +++ b/.context/LEARNINGS.md @@ -15,6 +15,26 @@ DO NOT UPDATE FOR: --> +## [2026-08-23-204632] Windows git over HTTPS fails with SEC_E_NO_CREDENTIALS unless the openssl backend is pinned + +**Context**: During the pi integration, every `git fetch`/`ls-remote` over an https remote on this Windows machine failed with `schannel: AcquireCredentialsHandle failed: SEC_E_NO_CREDENTIALS (0x8009030e)` - git's default TLS (schannel) has no usable credential state here. `gh` (own TLS stack) worked fine, and SSH keys were rejected, so the failure looked like a credentials/remote problem. + +**Lesson**: git's schannel backend and the Windows credential manager can be broken independently of the remote; the fix is a per-invocation backend override, not key/credential surgery. + +**Application**: On this machine, run every git https remote operation as `git -c http.sslBackend=openssl ` (git 2.51 supports it). Diagnose with `gh api` first to rule out remote-side auth before touching keys. + +--- + +## [2026-08-23-204632] zensical site rebuilds are non-reproducible off the canonical toolchain (entity-escape drift) + +**Context**: Rebuilding site/ locally with the pinned zensical 0.0.51 (fresh venv, Python 3.10) produced 120 files of pure escape drift (`'` vs literal `'` inside highlighted code blocks) against the committed artifacts; pinning pygments older did not help. + +**Lesson**: The zensical pin only pins the generator, not its transitive deps (markdown/pygments/jinja resolution drifts), so committed site/ output is not reproducible on arbitrary machines; the drift is user-invisible (entities render identically) but is pure diff churn that flips back on the next canonical build. + +**Application**: After a local `zensical build`, run `git diff --ignore-cr-at-eol --numstat site/` and inspect representative hunks BEFORE committing; if the delta is escape drift rather than real content, revert site/ (`git checkout -- site/`, remove new untracked pages) and defer the rebuild to the canonical build machine; record the deferral in the task notes. + +--- + ## [2026-07-25-124457] Using the proprietary sibling repo as design evidence leaks its internals into tracked files **Context**: While deciding the pd-m4 add-path shape, I read the sibling repo's convention file to settle the question, then quoted its guide text and attributed the decision to it in a tracked plan file. An unrelated build warning prompted the sweep that caught it. diff --git a/.context/TASKS.md b/.context/TASKS.md index 22b6c4bd6..28bbd3012 100644 --- a/.context/TASKS.md +++ b/.context/TASKS.md @@ -3072,3 +3072,83 @@ E5[T15–20] E6[T21–23] = 23. - [ ] [E5] Tests: health fixtures, heavy root + theme file, both-fire ordering, convention measure, boundary/disable, surface parity (T15–T20). Plan: specs/plans/pd-m5.md #priority:medium #session:951e1535 #branch:design/pd-m5-triggers #added:2026-07-25 - [ ] [E6] Sync + gates: copilot skill sync, measurement gate (T22), milestone gate (T21–T23). Plan: specs/plans/pd-m5.md #priority:medium #session:951e1535 #branch:design/pd-m5-triggers #added:2026-07-25 + +## Phase PI — Pi CLI Integration + +Spec: `specs/pi-cli-integration.md` +Branch: `feat/pi-cli-integration` (based on upstream main ce5a8328, 2026-08-23) + +Pi (earendil-works/pi, pi.dev) is a self-extensible coding-agent CLI with a +TypeScript extension system, Agent Skills support, native AGENTS.md, and — by +design — no MCP. Integration follows the OpenCode blueprint: Go setup package + +embedded TS shim + bundled skills + shared AGENTS.md template. + +- [ ] Read `specs/pi-cli-integration.md` before starting any PI task. + #added:2026-08-23-151000 +- [x] PI.1: Embedded assets — `internal/assets/integrations/pi/extension/ctx.ts` + (thin shim: before_agent_start branch-scan packet injection, + session_start warm-up cache, session_compact cache drop, tool_result + post-commit / check-task-completion with isError gating, agent_settled + check-persistence; hook-JSON envelope on piped stdin) and + `internal/assets/integrations/pi/skills/` (same 10-skill set as + OpenCode; embed.go directives added) #added:2026-08-23-151000 + #completed:2026-08-23 +- [x] PI.2: Accessors + constants — `internal/assets/read/agent/pi.go` + (PiExtension, PiSkills) and `internal/config/hook` pi path constants + (+ asset.go DirIntegrationsPi*) #added:2026-08-23-151000 + #completed:2026-08-23 +- [x] PI.3: `internal/cli/setup/core/pi/` package — pi.go, extension.go, + skill.go, validate.go + deploy_test.go/testmain_test.go modeled on the + opencode suite (all 4 tests green) #added:2026-08-23-151000 + #completed:2026-08-23 +- [x] PI.4: CLI wiring — `case cfgHook.ToolPi` branch in setup root.Run() + (run.go + doc.go); supporting text: hooks.yaml hook.pi + supported-tools line, + write.yaml write.hook-pi-*, config/embed/text DescKeys, write/setup InfoPi* + (TestDescKeyYAMLLinkage green; no new Use* constant, no new subcommand) + #added:2026-08-23-151000 #completed:2026-08-23 +- [x] PI.5: Docs — pi entry in supported-tools reference (integrations.md + + setup.md + multi-tool-setup.md) + pi quickstart guide (docs/home/pi.md + + zensical nav). NOTE: site/ HTML rebuild deferred — this machine's zensical + 0.0.51 venv renders entity-escape drift (120 files, ' vs ') vs the + committed artifacts (unpinned zensical transitive deps); rebuild on the + canonical build machine and stage site/ with a follow-up commit + #added:2026-08-23-151000 #completed:2026-08-23 +- [x] PI.6: Validation — go build ./... + golangci-lint (0 issues) + targeted + go test green; full-suite delta vs clean main verified: zero new failures + (20 failing packages, all pre-existing Windows-environment: CRLF render + drift, .exe exec, audit path exemptions). Scratch project: init + + dry-run + --write + idempotent re-run + tamper-refresh (byte-identical to + embedded) + unknown-tool list all verified with a freshly built binary. + Live pi: extension loads clean (A/B run with/without .pi identical); + full LLM round-trip blocked by local pi provider config (gx10-spark/* + unreachable from this environment) — left for a working-provider machine + #added:2026-08-23-151000 #completed:2026-08-23 +- [x] PI.7: CI type-check for the pi extension — tools/typecheck/pi/ + (tsconfig + package.json + lockfile with @earendil-works/pi-coding-agent + 0.84.2 types, tsc --noEmit PASSING locally) + typecheck-pi-extension job in + .github/workflows/ci.yml (mirrors tools/typecheck/opencode/). Decisions + locked 2026-08-23: display:true, CI typecheck in scope + #added:2026-08-23-151000 #completed:2026-08-23 +- [ ] Bug (review finding, pre-existing): OpenCode plugin lifecycle legs lack + the hook-JSON stdin envelope — `ctx system post-commit` from + internal/assets/integrations/opencode/plugin/index.ts bails silently in + FullPreamble (no envelope fed via BunShell), so the post-commit nudge is + likely dead; check-task-completion/check-persistence run but key per-session + state to IDUnknown. Spec: specs/pi-cli-integration.md "Hook envelope + requirement" section documents the correct envelope shape; fix the opencode + plugin the same way (or add envelope flags on the Go side) #priority:medium + #added:2026-08-23-151000 +- [ ] PI.8 (audit follow-ups, non-blocking): from the fable-5 effort-max + branch audit (2026-08-23, verdict ship-ready after MAJOR 1, which is + fixed on-branch): (a) post-compaction re-warm runs `ctx agent` inline on + the prompt path (up to 15s) — consider re-warming inside the + session_compact handler instead; (b) embedded assets are not EOL-pinned + (no .gitattributes; Windows builds embed CRLF skills vs LF on CI — + cross-binary refresh flapping; add `internal/assets/** text eol=lf` + pin, repo-wide pre-existing hazard); (c) hook nudge stdout is discarded — + Pi's tool_result handlers can return {content} to append the nudge to the + tool result and reach the LLM (OpenCode parity chose .quiet(); design + decision, not a bug); (d) tools/typecheck/pi/tsconfig.json could mirror + the opencode twin's explicit `paths` mapping for @earendil-works/pi-coding-agent + (resolution works today via node_modules probing; hardening only) + #priority:low #added:2026-08-23-221600 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0cfce1a2..817b18d41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,6 +89,28 @@ jobs: working-directory: tools/typecheck/opencode run: npx tsc --noEmit + typecheck-pi-extension: + name: Typecheck Pi extension + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Set up Node + uses: actions/setup-node@v7 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: tools/typecheck/pi/package-lock.json + + - name: Install dependencies + working-directory: tools/typecheck/pi + run: npm ci + + - name: Type-check embedded extension + working-directory: tools/typecheck/pi + run: npx tsc --noEmit + vscode-extension: name: VS Code extension build + typecheck runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 329e0b5e7..633690254 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ editors/vscode/*.vsix # Type-check tooling for embedded plugins tools/typecheck/opencode/node_modules/ +tools/typecheck/pi/node_modules/ # Some ideas are best kept hidden. ideas diff --git a/docs/cli/setup.md b/docs/cli/setup.md index 5f1ce8edd..c754d7794 100644 --- a/docs/cli/setup.md +++ b/docs/cli/setup.md @@ -36,6 +36,7 @@ ctx setup [flags] | `aider` | Aider CLI | | `copilot` | GitHub Copilot | | `opencode` | OpenCode (terminal-first AI coding agent) | +| `pi` | Pi coding-agent CLI (pi.dev) | | `windsurf` | Windsurf IDE | !!! note "Claude Code Uses the Plugin System" @@ -59,4 +60,7 @@ ctx setup cline --write # Generate OpenCode plugin, skills, AGENTS.md, and global MCP config ctx setup opencode --write + +# Generate Pi extension, skills, and AGENTS.md +ctx setup pi --write ``` diff --git a/docs/home/pi.md b/docs/home/pi.md new file mode 100644 index 000000000..6344cf6b6 --- /dev/null +++ b/docs/home/pi.md @@ -0,0 +1,156 @@ +--- +# / ctx: https://ctx.ist +# ,'`./ do you remember? +# `.,'\ +# \ Copyright 2026-present Context contributors. +# SPDX-License-Identifier: Apache-2.0 + +title: "ctx for Pi" +icon: lucide/terminal +--- + +![ctx](../images/ctx-banner.png) + +## The Problem + +Every Pi session starts from zero. You re-explain your architecture, +the agent repeats mistakes it made yesterday, and decisions get +rediscovered instead of remembered. + +**Without `ctx`:** + +``` +> "Add the validation middleware we discussed" + +I don't have context about previous discussions. Could you describe +what validation middleware you're referring to? +``` + +**With `ctx`:** + +``` +> "Add the validation middleware we discussed" + +Yes. From the Jan 15 session. You decided on Zod schemas at the +route level (DECISIONS.md #12), and the pattern is in +CONVENTIONS.md. I'll follow the existing middleware in +src/middleware/auth.ts as a reference. +``` + +That's the whole pitch: **your AI remembers**. + +## Setup (One Command) + +Install the `ctx` binary first ([installation docs](getting-started.md#installation)), +then run from your project root: + +```bash +ctx setup pi --write && ctx init +``` + +This does two things: + +1. **`ctx setup pi --write`**: generates the project-local Pi + extension, skills, and `AGENTS.md`. +2. **`ctx init`**: creates the `.context/` directory with template + files. + +### What Gets Created + +| File | Purpose | +|------|---------| +| `.pi/extensions/ctx.ts` | Lifecycle extension (hooks into `ctx system` commands) | +| `AGENTS.md` | Agent instructions (Pi reads this natively) | +| `.pi/skills/ctx-*/SKILL.md` | `ctx` skills, available as `/skill:ctx-*` commands | + +The extension is a single file with no runtime dependencies; no +`npm install` needed. Pi loads it automatically on launch. + +> **Trust note:** Pi loads project-local `.pi/` files only after the +> project is trusted. The first `pi` launch in a new project prompts +> for trust; answer yes for the integration to activate. + +> **Launch from the project root:** the extension resolves `.context/` +> relative to Pi's working directory, so start `pi` from the project +> root for the agent-side `ctx` commands to resolve. + +## What Happens Automatically + +The extension wires Pi lifecycle events to `ctx`. You don't need to +do anything; it just works. + +| Event | What fires | What it does | +|-------|-----------|--------------| +| Session start | `session_start` | Warms the ctx agent packet off the prompt path | +| First prompt / after compaction | `before_agent_start` | Injects the packet as a persistent message when no ctx-injected message exists after the most recent compaction | +| After `git commit` | `tool_result` (bash) | Runs `ctx system post-commit` to capture context state (failed commits are ignored) | +| After file edit | `tool_result` (edit/write) | Runs `ctx system check-task-completion` to detect silent task completions | +| Agent settled | `agent_settled` | Runs `ctx system check-persistence` | + +The compaction behavior matters most. When Pi compresses your context +window (`/compact` or auto-compaction), the injected packet ages past +the kept window and is folded into the lossy summary. The extension +re-injects a fresh packet on the next prompt so the agent keeps +breadcrumbs back to your `.context/` directory and its file inventory. + +Pi intentionally has no built-in MCP, so there is no MCP server to +register; the extension is the lifecycle channel. + +### What Is *Not* Included + +Dangerous-command blocking is Claude Code-specific and is not part of +the Pi integration. + +## Skills + +The bundled skills are available as `/skill:*` commands: + +| Command | When to use | +|---------|-------------| +| `/skill:ctx-remember` | "Do you remember?"; reads tasks, decisions, learnings, and recent journal entries. Returns a structured readback. | +| `/skill:ctx-status` | Context summary at a glance: file count, token estimate, recent activity. | +| `/skill:ctx-wrap-up` | End-of-session ceremony. Captures learnings, decisions, conventions, and outstanding tasks to `.context/` files. | +| `/skill:ctx-agent` | Load full context packet on demand | + +The KB editorial pipeline ships too: `/skill:ctx-kb-ingest`, +`/skill:ctx-kb-ask`, `/skill:ctx-kb-note`, `/skill:ctx-kb-ground`, +`/skill:ctx-kb-site-review`. + +You don't need to use these often. The extension handles most context +loading automatically. These are for when you want explicit control. + +## Refreshing the Integration + +If you re-run `ctx setup pi --write` (e.g., after updating `ctx`), the +extension and skills are refreshed in place. Pi auto-discovered +extensions can be hot-reloaded with `/reload`; a restart always works. + +## Troubleshooting + +| Symptom | Cause | Fix | +|---------|-------|-----| +| Extension installed but nothing fires | Project not trusted | Trust the project when prompted; check `defaultProjectTrust` in Pi settings | +| `ctx` commands resolve to the wrong project | Pi launched outside the project root | Launch `pi` from the project root | +| Extension not loading at all | Wrong location | Verify the extension is at `.pi/extensions/ctx.ts` (flat top-level file) | + +## Verify It Works + +Start a new Pi session and ask: + +``` +Do you remember? +``` + +The agent should cite specific context: current tasks, recent +decisions, or previous session topics. If it says "I don't have +memory" or "Let me check," something went wrong; check that the +extension installed correctly and `.context/` has files in it. + +## What's Next + +- [Your First Session](first-session.md): step-by-step walkthrough + from `ctx init` to verified recall. +- [Common Workflows](common-workflows.md): day-to-day commands for + tracking context, checking health, and browsing history. +- [Context Files](context-files.md): what lives in `.context/` and + how each file is used. diff --git a/docs/operations/integrations.md b/docs/operations/integrations.md index 309c98b5c..b8aa373f7 100644 --- a/docs/operations/integrations.md +++ b/docs/operations/integrations.md @@ -605,6 +605,55 @@ ctx status --- +## Pi + +Pi (earendil-works/pi, pi.dev) is a self-extensible coding-agent CLI. +By design it has no built-in MCP, so `ctx` integrates via a thin +TypeScript extension, Agent-Skills-standard skills, and `AGENTS.md` +instructions. All logic stays in the `ctx` binary via `ctx system` +subcommands. + +### Setup + +```bash +# Generate Pi extension, skills, and AGENTS.md +ctx setup pi --write + +# Initialize context +ctx init +``` + +### What Gets Created + +| File | Purpose | +|------|---------| +| `.pi/extensions/ctx.ts` | Lifecycle extension (hooks to `ctx system`) | +| `AGENTS.md` | Agent instructions (read natively) | +| `.pi/skills/ctx-*/SKILL.md` | `ctx` skills (invoke as `/skill:ctx-*`) | + +Project-local `.pi/` files load only after the project is trusted; +the first `pi` launch in a new project prompts for trust. + +### How It Works + +The extension wires Pi lifecycle events to `ctx system`: + +- **`session_start`**: warms the ctx agent packet off the prompt path. +- **`before_agent_start`**: injects the packet as a persistent message + when no ctx-injected message exists after the most recent compaction + (fresh sessions inject on the first turn; re-injection after + compaction is a breadcrumb into `.context/`). +- **`tool_result` (bash, on `git commit`, not `isError`)**: runs + `ctx system post-commit`. +- **`tool_result` (edit/write, not `isError`)**: runs + `ctx system check-task-completion`. +- **`agent_settled`**: runs `ctx system check-persistence`. + +The extension is a single file with no runtime dependencies; no +`npm install` needed. Pi loads it automatically on launch. + +--- + ## Windsurf IDE Windsurf supports custom instructions and file-based context. diff --git a/docs/recipes/multi-tool-setup.md b/docs/recipes/multi-tool-setup.md index b633c12eb..ded75fe79 100644 --- a/docs/recipes/multi-tool-setup.md +++ b/docs/recipes/multi-tool-setup.md @@ -33,6 +33,9 @@ claude /plugin install ctx@activememory-ctx # ## OpenCode ## ctx setup opencode --write && ctx init +# ## Pi ## +ctx setup pi --write && ctx init + # ## Cursor / Aider / Copilot / Windsurf ## ctx setup cursor # or: aider, copilot, windsurf @@ -160,6 +163,25 @@ registers the `ctx` MCP server globally. See automatically. Context loads at session start, survives compaction, and persists at session end, with no manual steps needed. +#### Pi + +Run the one-liner from the project root: + +```bash +ctx setup pi --write && ctx init +``` + +This deploys a lifecycle extension (Pi has no built-in MCP by design), +Agent-Skills-standard skills, and `AGENTS.md`. Project-local `.pi/` +files load only after the project is trusted. See +[`ctx` for Pi](../home/pi.md) for full details. + +!!! tip "Pi Is a First-Class Citizen" + With the extension installed, Pi gets lifecycle hooks and skills + automatically. Context loads on the first prompt, survives + compaction, and persists at session end, with no manual steps + needed. + #### VS Code Install the **`ctx`** extension from the diff --git a/internal/assets/commands/text/hooks.yaml b/internal/assets/commands/text/hooks.yaml index 9b790d3a8..eeaabf6b6 100644 --- a/internal/assets/commands/text/hooks.yaml +++ b/internal/assets/commands/text/hooks.yaml @@ -425,6 +425,27 @@ hook.opencode: Run with --write to generate all files: ctx setup opencode --write +hook.pi: + short: | + Pi Integration + ============== + + Generate .pi/extensions/ctx.ts with ctx lifecycle hooks for the + Pi coding-agent CLI (pi.dev). Pi has no built-in MCP by design, + so the extension is a thin shim that wires Pi lifecycle events to + ctx system subcommands. + + This creates: + .pi/extensions/ctx.ts Extension shim + .pi/skills/ctx-*/SKILL.md ctx skills + AGENTS.md Agent instructions + + Note: project-local .pi/ files load only after the project is + trusted; first `pi` launch prompts for trust. + + Run with --write to generate all files: + + ctx setup pi --write hook.supported-tools: short: | Supported tools: @@ -435,6 +456,7 @@ hook.supported-tools: copilot - GitHub Copilot (VS Code extension) copilot-cli - GitHub Copilot CLI (terminal agent) opencode - OpenCode terminal AI agent + pi - Pi coding-agent CLI (pi.dev) windsurf - Windsurf IDE hook.windsurf: short: | diff --git a/internal/assets/commands/text/write.yaml b/internal/assets/commands/text/write.yaml index 00b46503b..92c7f47d7 100644 --- a/internal/assets/commands/text/write.yaml +++ b/internal/assets/commands/text/write.yaml @@ -159,6 +159,22 @@ write.hook-opencode-summary: MCP: ~/.config/opencode/opencode.json (or $OPENCODE_HOME/opencode.json) AGENTS: AGENTS.md Skills: .opencode/skills/ +write.hook-pi-created: + short: ' ✓ %s' +write.hook-pi-skipped: + short: ' ○ %s (already present, skipped)' +write.hook-pi-summary: + short: |- + Pi will now: + 1. Warm up and inject the ctx context packet on the first prompt + 2. Re-inject the packet after compaction (breadcrumb interop) + 3. Track task completion after edits + 4. Run post-commit capture after `git commit` + 5. Nudge persistence when the agent settles + + Extension: .pi/extensions/ctx.ts + AGENTS: AGENTS.md + Skills: .pi/skills/ write.hook-copilot-created: short: ' ✓ %s' write.hook-copilot-force-hint: diff --git a/internal/assets/embed.go b/internal/assets/embed.go index 3de14d421..53a7a252c 100644 --- a/internal/assets/embed.go +++ b/internal/assets/embed.go @@ -20,6 +20,8 @@ import ( //go:embed integrations/copilot-cli/skills/*/SKILL.md //go:embed integrations/opencode/plugin/index.ts //go:embed integrations/opencode/skills/*/SKILL.md +//go:embed integrations/pi/extension/ctx.ts +//go:embed integrations/pi/skills/*/SKILL.md //go:embed hooks/messages/*/*.txt hooks/messages/registry.yaml hooks/trace/*.sh //go:embed schema/*.json why/*.md //go:embed permissions/*.txt commands/*.yaml commands/text/*.yaml journal/*.css diff --git a/internal/assets/integrations/pi/extension/ctx.ts b/internal/assets/integrations/pi/extension/ctx.ts new file mode 100644 index 000000000..247e66c92 --- /dev/null +++ b/internal/assets/integrations/pi/extension/ctx.ts @@ -0,0 +1,203 @@ +// ctx Pi extension — thin shim to ctx system subcommands. +// All real logic lives in the ctx Go binary; this extension just +// wires Pi lifecycle events to ctx system calls. +// +// Hook envelope requirement (load-bearing): +// The `ctx system` hook subcommands (post-commit, check-task-completion, +// check-persistence) parse a hook-JSON envelope from stdin via +// FullPreamble and bail silently without one. Every hook call below is +// spawned with piped stdin: `{"session_id": ..., "tool_input": {command}}` +// is written and stdin closed immediately (closing also avoids the +// 2s StdinReadTimeout stall on open pipes). `ctx agent` is a normal +// flag-driven command and needs no envelope. +// +// node:child_process is mandatory for the envelope calls: Pi's own exec +// helper hardcodes `stdio: ["ignore", "pipe", "pipe"]` (no stdin pipe), +// so the envelope cannot be delivered through it. +// +// Compaction interop is breadcrumb-mediated and stateless across reloads: +// an injected custom_message is a valid compaction cut point, so once it +// ages past keepRecentTokens Pi folds it into the lossy LLM summary. The +// re-injection predicate below scans the LIVE context +// (buildContextEntries: summary + kept tail + post-compaction entries), +// so a packet that survived into the kept tail of a recent compaction +// suppresses re-injection, a folded-away packet triggers it, and +// extension-instance teardown on /new /resume /fork /reload (which resets +// all in-memory state) can only cause an extra injection, never a missed +// one. We deliberately do NOT take ownership of the Pi LLM summary via +// session_before_compact: a custom summary replaces Pi's, and cross- +// extension precedence is undocumented. +// +// The packet is produced once at session_start (warm-up, off the prompt +// path) and cached; the cache is dropped on session_compact so the next +// re-injection is fresh. All subprocess calls pass ctx.signal and a +// timeout so Esc cancels cleanly; non-zero exits are swallowed (nothrow) +// and an absent `ctx` binary makes the extension no-op silently. +// +// Tool name strings target Pi's built-ins: `bash` (shell), `edit`, +// `write`. A failed tool (isError: true) never triggers post-commit — +// that would re-score the previous commit. The agent's own shell tool is +// NOT anchored by the extension; users launch pi from the project root so +// the Go binary resolves $PWD/.context/ (per ctx's cwd-anchored model). +// If Pi renames an event or built-in tool, the corresponding branch +// silently no-ops; verify against the Pi docs when Pi bumps. +import type { + ExtensionAPI, + ExtensionContext, +} from "@earendil-works/pi-coding-agent"; +import { execFile } from "node:child_process"; + +const CTX_CONTEXT_TYPE = "ctx-context"; +const SHELL_TOOL = "bash"; +const EDIT_TOOLS = new Set(["edit", "write"]); +// Match `git commit` but not `git commit-tree` / `git commit-graph`. +// The negative lookahead rejects `-` immediately after the boundary. +const GIT_COMMIT_RE = /\bgit\s+commit\b(?!-)/; +const AGENT_BUDGET = 4000; +const SUBPROCESS_TIMEOUT_MS = 15_000; +const MAX_BUFFER = 1024 * 1024; + +// runCtx spawns the ctx binary and resolves with trimmed stdout, or +// undefined on any failure (nothrow equivalent). Envelope calls pass +// stdin; stdin is always ended immediately. +function runCtx( + args: string[], + cwd: string, + signal: AbortSignal | undefined, + stdin?: string, +): Promise { + return new Promise((resolve) => { + const child = execFile( + "ctx", + args, + { + cwd, + timeout: SUBPROCESS_TIMEOUT_MS, + signal, + maxBuffer: MAX_BUFFER, + }, + (err, stdout) => { + if (err) { + resolve(undefined); + return; + } + const text = String(stdout).trim(); + resolve(text.length > 0 ? text : undefined); + }, + ); + const input = child.stdin; + if (input) { + // Swallow async stream errors (EPIPE when the child exits + // before the write flushes, destroyed-stream writes on a + // failed spawn): spawn/exec failures already surface in + // the execFile callback, and a raw 'error' emission must + // not escape Pi's handler stack as an uncaught exception. + input.on("error", () => {}); + if (stdin !== undefined) { + input.write(stdin); + } + input.end(); + } + }); +} + +// runCtxHook is the envelope variant: writes the ctx system hook-JSON +// envelope (session_id + tool_input.command) before closing stdin. +function runCtxHook( + ctx: ExtensionContext, + args: string[], + command: string, +): Promise { + const envelope = JSON.stringify({ + session_id: ctx.sessionManager.getSessionId(), + tool_input: { command }, + }); + return runCtx(args, ctx.cwd, ctx.signal, envelope); +} + +// fetchPacket runs the normal (non-hook) ctx agent command. +function fetchPacket( + cwd: string, + signal: AbortSignal | undefined, +): Promise { + return runCtx( + ["agent", "--budget", String(AGENT_BUDGET)], + cwd, + signal, + ); +} + +// needsContextInjection: true when no ctx-injected custom_message exists +// in the LIVE context. Pi's live context is compaction-aware +// (summary + kept tail + post-compaction entries), so a packet that +// survived into the kept tail of a recent compaction is present and +// suppresses re-injection, while a packet that aged past keepRecentTokens +// was folded into the summary and is re-injected. Fresh sessions (no +// injection yet) return true, so the first turn always gets the packet. +function needsContextInjection( + sm: ExtensionContext["sessionManager"], +): boolean { + return !sm.buildContextEntries().some( + (entry) => + entry.type === "custom_message" && + entry.customType === CTX_CONTEXT_TYPE, + ); +} + +export default function (pi: ExtensionAPI) { + let packetCache: string | undefined; + + pi.on("session_start", async (_event, ctx) => { + // Warm-up: keep ctx agent off the prompt path. + packetCache = await fetchPacket(ctx.cwd, ctx.signal); + }); + + pi.on("before_agent_start", async (_event, ctx) => { + if (!needsContextInjection(ctx.sessionManager)) { + return; + } + let packet = packetCache; + if (packet === undefined) { + packet = await fetchPacket(ctx.cwd, ctx.signal); + } + if (packet === undefined) { + return; + } + return { + message: { + customType: CTX_CONTEXT_TYPE, + content: packet, + display: true, + }, + }; + }); + + pi.on("session_compact", async (_event, _ctx) => { + // The live-context predicate subsumes the flag; dropping the + // cache makes the next re-injection fresh. + packetCache = undefined; + }); + + pi.on("tool_result", async (event, ctx) => { + if (event.isError) { + return; + } + if (event.toolName === SHELL_TOOL) { + const command = + typeof event.input.command === "string" + ? event.input.command + : ""; + if (GIT_COMMIT_RE.test(command)) { + await runCtxHook(ctx, ["system", "post-commit"], command); + } + return; + } + if (EDIT_TOOLS.has(event.toolName)) { + await runCtxHook(ctx, ["system", "check-task-completion"], ""); + } + }); + + pi.on("agent_settled", async (_event, ctx) => { + await runCtxHook(ctx, ["system", "check-persistence"], ""); + }); +} diff --git a/internal/assets/integrations/pi/skills/ctx-agent/SKILL.md b/internal/assets/integrations/pi/skills/ctx-agent/SKILL.md new file mode 100644 index 000000000..5943f9372 --- /dev/null +++ b/internal/assets/integrations/pi/skills/ctx-agent/SKILL.md @@ -0,0 +1,29 @@ +--- +name: ctx-agent +description: "Load full context packet. Use at session start or when context seems stale or incomplete." +--- + +Load the full context packet for AI consumption. + +## When to Use + +- At the start of a session to load all context +- When context seems stale or incomplete +- When switching between different areas of work + +## When NOT to Use + +- The plugin hook already runs `ctx agent` on session start: + you rarely need to invoke this manually +- Don't run it just to "refresh" if you already have the context loaded in + this session + +## After Loading + +**Read the files listed in "Read These Files (in order)"**: the packet is a +summary, not a substitute. In particular, read CONVENTIONS.md before writing +any code. + +Confirm to the user: "I have read the required context files and I'm +following project conventions." Read and confirm before beginning +implementation. diff --git a/internal/assets/integrations/pi/skills/ctx-handover/SKILL.md b/internal/assets/integrations/pi/skills/ctx-handover/SKILL.md new file mode 100644 index 000000000..f8cca3e51 --- /dev/null +++ b/internal/assets/integrations/pi/skills/ctx-handover/SKILL.md @@ -0,0 +1,86 @@ +--- +name: ctx-handover +description: "Per-session handover artifact writer. Wraps `ctx handover write` with required `--summary` and `--next`. Always invoked as the final step of `/ctx-wrap-up`; not the user-facing trigger. When `.context/kb/` exists, also folds postdated closeouts into the handover and archives them." +--- + +Write the per-session handover under +`.context/handovers/-.md`. The handover is the +former agent's note to the next agent (or human): what +happened, and what should come next. `/ctx-remember` reads +it at the start of the next session. + +## When to Use + +`/ctx-wrap-up` owns the user-facing session-end trigger and +delegates to this skill as its final step. Direct invocation +is reserved for: + +- `--no-fold` mid-session checkpoint when the user wants to + pause without consuming closeouts. +- Recovery, when a prior session aborted before wrap-up. + +Otherwise, the user invokes `/ctx-wrap-up`, not this skill. + +## Input Contract + +Wraps `ctx handover write`. Empty `TBD`, `see chat`, +whitespace-only values for required flags are rejected by +the CLI. + +| Flag | Required | Description | +|------|----------|-------------| +| `--summary` | yes | Past tense; what happened this session. | +| `--next` | yes | Future tense; the specific first action for the next agent. | +| `--highlights` | no | Notable artifacts produced this session. | +| `--open-questions` | no | Things that remain undecided. | +| `--no-fold` | no | Skip closeout consumption (mid-session checkpoint). | +| `--commit` | no | Override resolved git HEAD for the Provenance line (CI replay). | + +Positional argument: handover title (becomes filename slug). + +## Pre-Write Gates + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/handovers/` missing → suggest `ctx init --upgrade` + and stop. + +`.context/kb/` is not required for handover; KB-state folding +is conditional on the directory's existence. + +## Process + +1. Verify pre-write gates. Refuse cleanly on failure. +2. Gather signal: `git status --short`, `git diff --stat`, + `git log --oneline @{upstream}..HEAD || git log --oneline -5`, + and scan the conversation for the session's arc, concrete + artifacts, open questions, and the specific next action. +3. Draft `--summary` (past tense, concrete) and `--next` + (future tense, specific). Surface to the user for + confirmation before running the CLI. +4. Run: + + ```bash + ctx handover write "" \ + --summary "<...>" --next "<...>" \ + [--highlights "<...>"] [--open-questions "<...>"] \ + [--no-fold] [--commit <sha>] + ``` + + The CLI validates flags, resolves git HEAD, reads the + latest-handover cursor, folds postdated closeouts into + the new handover's `## Folded Closeouts` section, and + archives them under `.context/archive/closeouts/`. + +5. Report the handover filename, count of closeouts folded, + count of malformed closeouts skipped (with filenames), + and the `--next` value verbatim so the operator sees what + the next agent will read first. + +## Anti-Patterns + +- Hand-writing a handover file. The CLI is the sole writer. +- Skipping the fold to "keep closeouts available." Use + `--no-fold` explicitly when the user wants the checkpoint + behavior; do not infer it. +- Inventing `--highlights` or `--open-questions` content the + session did not actually produce. diff --git a/internal/assets/integrations/pi/skills/ctx-kb-ask/SKILL.md b/internal/assets/integrations/pi/skills/ctx-kb-ask/SKILL.md new file mode 100644 index 000000000..8aa87ea04 --- /dev/null +++ b/internal/assets/integrations/pi/skills/ctx-kb-ask/SKILL.md @@ -0,0 +1,101 @@ +--- +name: ctx-kb-ask +description: "Q&A grounded in the existing kb. Read-only on prose; refuses to web-jump; if the kb cannot answer, opens a Q-### row in outstanding-questions.md and reports the gap. Writes an ask closeout for the audit trail." +--- + +Answer a question using only what `.context/kb/` already contains. +Cite by `EV-###`. Do not web-jump, do not invent prose, do not +modify topic pages. If the kb cannot answer, open a `Q-###` row +in `.context/kb/outstanding-questions.md` and report the gap. + +This is the read side of the editorial pipeline. The write side +is `/ctx-kb-ingest`. Authority for prose synthesis lives there; +this skill is read-only on prose. + +## When to Use + +- The user asks "does the kb say...", "according to evidence...", + "what do we know about <topic>". +- The user wants a citation-backed answer before deciding + whether to ingest more material. + +## When NOT to Use + +- The user wants new material extracted (`/ctx-kb-ingest`). +- The user wants the kb structurally audited + (`/ctx-kb-site-review`). +- The user wants external re-grounding (`/ctx-kb-ground`). +- The question is about `ctx` itself (answer from + `KB-RULES.md` directly). + +## Input + +A single question, supplied as the slash argument or inline. +No flags, no sources, no URLs. + +## Refuse-on-Empty + +If no question was supplied, return exactly: + +> no question provided; pass a question or describe it inline. + +Stop. The CLI enforces this independently. + +## Pre-Write Gates + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/kb/` missing → suggest `ctx init --upgrade` and stop. +- Kb scope undeclared → refuse with the scope message and stop. + +## Process + +1. Verify pre-write gates. +2. Survey the kb in this order, stopping early when an answer + surfaces with adequate citation coverage: `index.md` for + scope, topic-page indexes and sub-pages for matching slugs, + `evidence-index.md`, `glossary.md`, `contradictions.md`, + `outstanding-questions.md`. +3. Decide answer vs gap: + - **Answerable with citations**: cite every load-bearing + claim by `EV-###`. Name the topic page(s). Note the + confidence floor of cited rows. + - **Partial answer**: answer the covered part; open a + `Q-###` row for the gap. + - **Not answerable**: open a `Q-###` row; report the gap. + Do not invent. Do not web-jump. +4. If a gap exists, append a `Q-###` row to + `outstanding-questions.md`. Do NOT mint `EV-###` rows; + evidence authoring is `/ctx-kb-ingest`'s authority. +5. Write the ask closeout under + `.context/ingest/closeouts/<TS>-ask-closeout.md` with + required frontmatter (`sha`, `branch`, `mode: ask`, + `pass-mode: read-only`, `life-stage`, `generated-at`) and + body sections: Question, Answer (or `none (gap)`), + Citations (`EV-###` + topic-page paths), Gaps (`Q-###` + opened with one-line rationale), Next pass hint. + +## Anti-Patterns + +- Web-jumping when the kb cannot answer. The contract is + read-only on prose AND web-quiet. +- Inventing citations or claims. +- Modifying a topic page to extend an answer mid-pass. +- Minting `EV-###` rows from this skill. +- Skipping the `Q-###` row when the kb cannot answer. +- Skipping the closeout once pre-write gates pass. + +## Output Contract + +For pre-write refusals, return only the refusal text and stop. + +For passes that clear pre-write gates, end with: + +- **Question**: verbatim or faithful paraphrase. +- **Answer**: concise; cites every load-bearing claim by + `EV-###`. +- **Confidence floor**: lowest band among cited rows. +- **Gaps**: `Q-### opened`, or `none`. +- **Closeout**: filename. +- **Next-recommended-action**: explicit invocation if a gap + was opened (`/ctx-kb-ground` or + `/ctx-kb-ingest <sources>`), or `none`. diff --git a/internal/assets/integrations/pi/skills/ctx-kb-ground/SKILL.md b/internal/assets/integrations/pi/skills/ctx-kb-ground/SKILL.md new file mode 100644 index 000000000..07e18a8c6 --- /dev/null +++ b/internal/assets/integrations/pi/skills/ctx-kb-ground/SKILL.md @@ -0,0 +1,85 @@ +--- +name: ctx-kb-ground +description: "Read-only freshness audit over the kb's tracked sources (URLs, in-tree paths, MCP resources) declared in grounding-sources.md. Classifies each source's drift state, annotates the source-coverage ledger, and writes a ground closeout; flags drifted or new-to-kb sources for /ctx-kb-ingest. Never mints evidence, authors prose, or transitions ledger states." +--- + +Walk the sources declared in `.context/ingest/grounding-sources.md` +and report whether the kb's claims are still current. This is the +*"are we still current?"* pass — a read-only freshness audit, not +a re-ingest. + +Each tracked source — URLs, in-tree paths, or MCP resources — +gets resolved and classified as `unchanged`, `drifted`, `gone`, +`freshness opaque`, or `new to kb`. The skill annotates the +source-coverage ledger's `Residue` / `Next action` cells and +writes a ground closeout. It does NOT mint `EV-###` rows, author +topic-page prose, transition ledger states, or modify Confidence +bands; those are `/ctx-kb-ingest`'s authority. + +If a tracked source drifted or is new to the kb, flag it and +recommend a follow-up `/ctx-kb-ingest`. The declarative watch +list in `grounding-sources.md` persists across sessions and +tracks sources from anywhere the kb cites — the web, this repo's +tree, an MCP server. Distance from the repo is irrelevant; what +matters is that the kb depends on them for evidence. + +## When to Use + +- The user says "re-ground the kb", "check upstream", + "refresh sources". +- A grounding cadence is hitting its scheduled boundary. +- A prior pass left a `Q-###` row that names "needs + re-grounding". + +## When NOT to Use + +- The user has new sources to add (`/ctx-kb-ingest`). +- The user asks a question (`/ctx-kb-ask`). +- The user wants a mechanical audit (`/ctx-kb-site-review`). + +## Input + +No positional arg. Sources come from +`.context/ingest/grounding-sources.md` (one source per line; +`NONE` on a line is a per-pass skip). + +## Pre-Write Gates + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/ingest/` missing → suggest `ctx init --upgrade` + and stop. +- `grounding-sources.md` missing or empty → prompt the user + once for sources to add; if they decline, write a ground + closeout with `sources: 0` and stop. + +## Process + +1. Verify pre-write gates. +2. Read `.context/ingest/grounding-sources.md`. For each + non-skipped line, fetch / re-read the source and compare + against `evidence-index.md` rows already citing it. +3. Update the source-coverage ledger row for each source + touched: `partially-ingested` → `partially-ingested` + (touched), `comprehensive` → `comprehensive` (if no drift + detected), or flag drift in the closeout. +4. For each source that surfaces material the kb should + absorb, flag it and recommend a follow-up + `/ctx-kb-ingest` invocation in the closeout's Next pass + hint. +5. Write the ground closeout under + `.context/ingest/closeouts/<TS>-ground-closeout.md` with + required frontmatter (`sha`, `branch`, `mode: ground`, + `pass-mode: n/a`, `life-stage`, `generated-at`) and a + body listing each source touched, its drift verdict, and + any Next pass hint. + +## Anti-Patterns + +- Authoring topic-page prose from refresh output. Authoring + is `/ctx-kb-ingest`'s authority. +- Minting `EV-###` rows. Evidence minting is ingest's + authority. +- Promoting confidence bands without contradicting evidence. + Drift detection alone is not promotion. +- Skipping the closeout. Even a no-op refresh writes one so + the ledger advance is auditable. diff --git a/internal/assets/integrations/pi/skills/ctx-kb-ingest/SKILL.md b/internal/assets/integrations/pi/skills/ctx-kb-ingest/SKILL.md new file mode 100644 index 000000000..396ed9099 --- /dev/null +++ b/internal/assets/integrations/pi/skills/ctx-kb-ingest/SKILL.md @@ -0,0 +1,157 @@ +--- +name: ctx-kb-ingest +description: "Editorial knowledge-ingestion pass. Reads sources the user supplies, declares its pass-mode (topic-page / triage / evidence-only) before extraction, and is held to mode-specific completion semantics. The topic page is the deliverable; the closeout is the audit trail." +--- + +Single editorial pass that adds knowledge to `.context/kb/`. +Reads materials the user supplies, decides which topic page(s) +they belong to, finds-or-creates those pages, writes synthesized +prose section by section, mints `EV-###` rows as it cites them, +cross-links neighbouring topics, updates the source-coverage +ledger, and writes a closeout under `.context/ingest/closeouts/`. + +Authoritative background reading lives at +`.context/ingest/KB-RULES.md`. This skill encodes the workflow +contract; the rules file is the constitution. Hand-edit +`KB-RULES.md` to evolve the contract; do not paraphrase it +here. + +## When to Use + +- The user supplies one or more sources (paths, URLs, MCP + resources, inline natural-language descriptions) and wants + them read into the kb. +- The user says "ingest the transcripts", "pull this into the + kb", "add evidence from <source>", or invokes the slash form + with paths. + +## When NOT to Use + +- The user asked a question about the kb (use `/ctx-kb-ask`). +- The user wants a structural audit (use `/ctx-kb-site-review`). +- The user wants external re-grounding (use `/ctx-kb-ground`). +- The user wants to park a quick finding (use `/ctx-kb-note`). +- No sources were supplied (refuse-on-empty). + +## Input + +Sources, supplied as one or more of: paths (file or folder), +URLs, MCP resources, or inline natural-language gestures. +Optional second argument is the topic name; when omitted the +skill proposes one and confirms before extraction. + +## Refuse-on-Empty + +If the invocation supplied no sources, return exactly: + +> no sources provided; pass a folder, a URL, an MCP resource, or +> describe the materials inline. + +Stop. The CLI enforces this independently. + +## Pre-Write Gates + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/ingest/` missing → suggest `ctx init --upgrade` + and stop. +- Kb scope undeclared (`.context/kb/index.md` missing, or its + `## Scope` H2 holds the `TODO` placeholder, or lacks + substantive non-placeholder prose): + + > kb scope is undeclared. Open `.context/kb/index.md` and + > replace the TODO placeholder with a one-paragraph scope + > statement that names what is in scope and what is out. + +## Pass-Mode Contract + +Every invocation classifies itself as exactly one mode before +extraction begins. Full semantics in +`.context/ingest/KB-RULES.md` §Pass-mode contract. + +| Mode | Mints prose? | Mints `EV-###`? | Touches topic page? | Default? | +|------------------|--------------|------------------|------------------------|----------| +| `topic-page` | yes | yes | yes (create/extend) | yes | +| `triage` | no | no | no | no | +| `evidence-only` | no | yes (tagged) | no | no | + +Default is `topic-page`. `triage` fires when sources are +disparate with no clear single topic, or the user explicitly +asks for triage. `evidence-only` fires only on explicit user +request ("just mint EV rows", "backfill evidence"); never +inferred from source size or operator convenience. + +Before extraction, emit the declaration in the response stream: + +> **Pass-mode:** `<mode>` +> **Reason:** `<one sentence; required when non-default>` +> **Definition of done:** `<mode-specific completion criterion>` + +The declaration is a contract. Mid-pass mode-switching is +forbidden: abort with a partial closeout and recommend +re-invocation under the correct mode. + +## Topic-Page Circuit Breaker + +A pass in `topic-page` mode may not report `topic-page: +produced` or `topic-page: extended` unless: + +1. `.context/kb/topics/<slug>/index.md` exists and was + created or extended in this pass (topic-page file + creation is performed only by `ctx kb topic new`; this + skill MAY invoke it but MUST NOT write the scaffold + directly). +2. The page cites at least one `EV-###` row that resolves + to `evidence-index.md`. +3. `ctx kb site build` ran clean, or its failure is named + in the closeout's `Next pass hint` and the pass reports + `topic-page: deferred`. +4. The cold-reader orientation rubric records `Result: pass`. + +Any failure → `topic-page: deferred`; ledger advances to +`topic-page-drafted` (not `comprehensive`). + +## Process + +1. Verify pre-write gates. Refuse cleanly on failure. +2. Emit the pass-mode declaration. +3. Resolve sources and the topic name; confirm with the user + when topic is not supplied. +4. Run the topic-adjacency pre-flight against + `.context/kb/source-coverage.md`; record the result in + the closeout's Adjacency pre-flight block. +5. Life-stage check (< 5 topic pages = bootstrap; + reconciliation ceremony skipped except for contradictions; + >= 5 = maintenance, full discipline). +6. Scaffold (topic-page mode only): if the topic folder does + not exist, shell out to `ctx kb topic new "<name>"`. +7. Extract atomic claims; mint `EV-###` rows in + `evidence-index.md`. Confidence band per `KB-RULES.md`; + topic page never claims more certainty than its weakest + cited band. +8. Reconcile (maintenance only): net-new claims append; + reinforcing claims promote; contradicting claims demote + per the demotion policy and open a paired + `outstanding-questions.md` row. +9. Advance the source-coverage ledger per the state-machine + transitions in `KB-RULES.md`. Illegal transitions are + refused at write time. +10. Run the topic-page circuit breaker (topic-page mode only). +11. Record the cold-reader orientation rubric in the closeout. +12. Write the closeout under + `.context/ingest/closeouts/<TS>-ingest-closeout.md` with + required frontmatter (`sha`, `branch`, `mode: ingest`, + `pass-mode`, `life-stage`, `generated-at`). +13. Append one line to `.context/ingest/SESSION_LOG.md` at + the closeout phase boundary, in the exact shape from + `KB-RULES.md` §SESSION_LOG line shape. + +## Anti-Patterns + +- Synthesizing the topic-page scaffold by hand. Only `ctx kb + topic new` writes scaffolds. +- Mid-pass mode-switching without aborting. +- Claiming `comprehensive` ledger advance when the topic page + is incomplete. +- Inventing `EV-###` IDs to make a topic page look complete. +- Demoting in `evidence-index.md` (rows are append-only; + retire by demoting the confidence band, not by deletion). diff --git a/internal/assets/integrations/pi/skills/ctx-kb-note/SKILL.md b/internal/assets/integrations/pi/skills/ctx-kb-note/SKILL.md new file mode 100644 index 000000000..bd9ab89e4 --- /dev/null +++ b/internal/assets/integrations/pi/skills/ctx-kb-note/SKILL.md @@ -0,0 +1,72 @@ +--- +name: ctx-kb-note +description: "Lightweight capture into .context/ingest/findings.md. Single argument is the note text. Never writes to a topic page or to evidence-index.md. The pipeline's ad-hoc escape hatch for 'park this for the next ingest'." +--- + +Append a short note to `.context/ingest/findings.md` so a later +`/ctx-kb-ingest` pass can pick it up. The pipeline's escape +hatch for *"I want to remember this, but I'm not running a full +ingest right now."* No closeout, no ledger update, no +topic-page edit, no `EV-###` minting. Just typed memory landing +in one well-known file. + +## When to Use + +- The user says "drop a note", "capture this for the next + ingest", "park this finding". +- A conversation surfaces a fact, link, or observation that + should land in the kb later but does not justify a full + ingest pass right now. + +## When NOT to Use + +- The user has sources to ingest (`/ctx-kb-ingest`). +- The user is asking a content question (`/ctx-kb-ask`). +- The note is actually a task / decision / learning / + convention for the code-dev side; use the matching + canonical-capture path instead. +- The note is empty (refuse-on-empty). + +## Input + +A single argument: the note text. Free-form prose. No flags. + +## Refuse-on-Empty + +If the invocation supplied no note text, return exactly: + +> no note text provided; pass the note inline. + +Stop. The CLI enforces this independently. + +## Pre-Write Gates + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/ingest/` missing → suggest `ctx init --upgrade` + and stop. + +Kb scope declaration is not required; notes land pre-scope. + +## Process + +1. Verify pre-write gates. +2. Append the note to `.context/ingest/findings.md` as a + single bulleted line, prefixed with the UTC timestamp + (RFC-3339), short SHA, and branch: + + ``` + - 2026-05-16T14:32:11Z sha=88d52870 branch=main + | <note text> + ``` + + If `findings.md` does not exist, create it with a brief + header explaining its purpose. +3. No closeout. Notes are intentionally lightweight; the + audit trail is the file itself. + +## Anti-Patterns + +- Writing a topic page or minting `EV-###` from this skill. +- Skipping the timestamp + provenance prefix; the audit + trail depends on it. +- Hand-editing prior entries to "consolidate" them. Append-only. diff --git a/internal/assets/integrations/pi/skills/ctx-kb-site-review/SKILL.md b/internal/assets/integrations/pi/skills/ctx-kb-site-review/SKILL.md new file mode 100644 index 000000000..1e1c0000f --- /dev/null +++ b/internal/assets/integrations/pi/skills/ctx-kb-site-review/SKILL.md @@ -0,0 +1,72 @@ +--- +name: ctx-kb-site-review +description: "Mechanical structural audit of the kb. Coerces malformed capitalization, flags malformed closeout frontmatter, and refuses to make judgment calls that require evidence. Writes a site-review closeout for the audit trail." +--- + +Walk `.context/kb/` and `.context/ingest/closeouts/` +mechanically. Fix what is unambiguous (capitalization drift, +missing frontmatter fields the CLI knows how to coerce). Flag +what is not (claims that read as broken but require evidence +to fix). Never invent prose. Never mint `EV-###` rows. Never +modify a claim's Confidence band. + +This is a janitor pass, not an editorial pass. Editorial +judgment lives in `/ctx-kb-ingest`. + +## When to Use + +- The user says "audit the kb", "check kb for rot", + "run a site-review". +- Before a release or at the end of a long editorial run, + to catch drift. + +## When NOT to Use + +- The user has new sources to ingest (`/ctx-kb-ingest`). +- The user wants Q&A (`/ctx-kb-ask`). +- The user wants external re-grounding (`/ctx-kb-ground`). + +## Authority Boundary + +- Coerces formatting drift the CLI can fix unambiguously. +- Flags structural problems that require evidence to resolve + (open `Q-###` rows; never invent answers). +- Never writes prose, mints `EV-###`, or changes confidence + bands. + +## Pre-Write Gates + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/kb/` missing → suggest `ctx init --upgrade` and + stop. +- Kb scope undeclared → refuse with the scope message and + stop. + +## Process + +1. Verify pre-write gates. +2. Walk `.context/kb/` and `.context/ingest/closeouts/`: + coerce capitalization drift in Confidence bands; fix + missing frontmatter fields the CLI can supply + deterministically; flag malformed closeouts (missing + `generated-at`, malformed YAML). +3. For each structural problem that requires evidence to + resolve, open a `Q-###` row in + `outstanding-questions.md` naming the file and the + shape of evidence that would close it. +4. Write the site-review closeout under + `.context/ingest/closeouts/<TS>-site-review-closeout.md` + with required frontmatter (`sha`, `branch`, + `mode: site-review`, `pass-mode: n/a`, `life-stage`, + `generated-at`) and a body listing what was coerced, + what was flagged, and any `Q-###` rows opened. + +## Anti-Patterns + +- Inventing prose or claims. +- Minting `EV-###` rows. +- Modifying Confidence bands. +- Hand-editing closeouts post-write (closeouts are + append-never-rewrite). +- Skipping the `Q-###` row when a structural flag would + otherwise vanish without trace. diff --git a/internal/assets/integrations/pi/skills/ctx-remember/SKILL.md b/internal/assets/integrations/pi/skills/ctx-remember/SKILL.md new file mode 100644 index 000000000..442f39ff8 --- /dev/null +++ b/internal/assets/integrations/pi/skills/ctx-remember/SKILL.md @@ -0,0 +1,39 @@ +--- +name: ctx-remember +description: "Recall project context and present structured readback. Use when the user asks 'do you remember?', at session start, or when context seems lost." +--- + +Recall project context and present a structured readback. + +## When to Use + +- When the user asks "Do you remember?", "What were we working on?" +- At the start of a session to pick up where you left off +- When context seems lost or stale + +## Process + +**Do this FIRST (silently):** +1. Read TASKS.md, DECISIONS.md, and LEARNINGS.md from the context directory +2. Read the latest handover under `.context/handovers/` if present. + Filenames are timestamped `<TS>-<slug>.md`; the lexicographically + last is the newest. Its `## Summary` and `## Next Session` sections + are the authoritative recall surface — this is the previous + session's note to this one. +3. If `.context/ingest/closeouts/` exists, list closeouts whose + `generated-at` postdates the handover's `generated-at` and read + their `## What Changed` sections. These are per-pass audit notes + the previous wrap-up did not get a chance to fold. Read-only; + `/ctx-remember` does not run any editorial pass. +4. Check recent session history (for example via `.context/sessions/` or `ctx journal source --limit 5` when available) +5. Run `ctx agent` for the full context packet + +**Then respond with a structured readback:** + +1. **Last session**: cite the most recent session topic and date +2. **Active work**: list pending or in-progress tasks +3. **Recent context**: mention 1-2 recent decisions or learnings +4. **Next step**: offer to continue or ask what to focus on + +**Never** say "I don't have memory" or narrate your discovery process. +The context files are your memory. Present what you found as recall. diff --git a/internal/assets/integrations/pi/skills/ctx-status/SKILL.md b/internal/assets/integrations/pi/skills/ctx-status/SKILL.md new file mode 100644 index 000000000..1674de6ad --- /dev/null +++ b/internal/assets/integrations/pi/skills/ctx-status/SKILL.md @@ -0,0 +1,30 @@ +--- +name: ctx-status +description: "Show context summary. Use at session start or when unclear about current project state." +--- + +Show the current context status: files, token budget, tasks, +and recent activity. + +## When to Use + +- At session start to orient before doing work +- When confused about what is being worked on or what context + exists +- To check token usage and context health +- When the user asks "what's the state of the project?" + +## When NOT to Use + +- When you already loaded context via `/ctx-agent` in this + session (status is a subset of what agent provides) +- Repeatedly within the same session without changes in between + +## Usage Examples + +```text +/ctx-status +``` + +The slash command takes no arguments. For verbose or JSON output, ask the +agent to run `ctx status --verbose` or `ctx status --json` directly. diff --git a/internal/assets/integrations/pi/skills/ctx-wrap-up/SKILL.md b/internal/assets/integrations/pi/skills/ctx-wrap-up/SKILL.md new file mode 100644 index 000000000..9684d2e97 --- /dev/null +++ b/internal/assets/integrations/pi/skills/ctx-wrap-up/SKILL.md @@ -0,0 +1,67 @@ +--- +name: ctx-wrap-up +description: "End-of-session context persistence ceremony. Use when wrapping up a session to capture learnings, decisions, conventions, and tasks." +--- + +Run the end-of-session context persistence ceremony. + +## When to Use + +- When ending a work session +- When switching to a different project or task area +- When context window is getting large +- Before any long break from the project + +## Process + +1. Review work done in this session +2. Capture any new decisions to `.context/DECISIONS.md` +3. Capture any new learnings to `.context/LEARNINGS.md` +4. Capture any new conventions to `.context/CONVENTIONS.md` +5. Update task status in `.context/TASKS.md` +6. Save a session summary to `.context/sessions/` +7. Sweep this session — and any others that grew since their + last import — into the journal: `ctx journal import --all + -y`. It is growth-aware and idempotent, so running it + mid-wrap-up is safe. Best-effort and **non-blocking**: if + it errors, note it and continue; never let it block the + handover. +8. If `.context/kb/` exists, list pending closeouts under + `.context/ingest/closeouts/` and count `open` rows in + `.context/kb/outstanding-questions.md`; surface both + counts so the operator sees what editorial residue is + pending. The handover step's fold pass consumes the + closeouts. Skip this step entirely when `.context/kb/` + does not exist. +9. **Mandatory final step:** delegate to `/ctx-handover` so + the next session has something to read. Draft: + - **Title**: short noun phrase naming the session arc + (becomes the slug in `<TS>-<slug>.md`). + - **`--summary`** (past tense, one paragraph): what was + done this session. Concrete, not vague. + - **`--next`** (future tense, one paragraph): the + specific first action the next agent should take. + - **`--highlights`**: bullet list of notable artifacts. + Always draft; pass empty only after the user + explicitly says there is nothing to highlight. + - **`--open-questions`**: bullet list of things that + remain undecided. Always draft; pass empty only after + explicit user confirmation. + + Confirm the draft with the user, then invoke: + + ```text + /ctx-handover "<title>" --summary "<...>" --next "<...>" \ + [--highlights "<...>"] [--open-questions "<...>"] + ``` + + Do not declare the wrap-up complete until + `.context/handovers/<TS>-<slug>.md` has been written. + +## Self-Check + +Ask: "If this session ended right now, would the next session +know what happened?" If no, persist more context before +ending. The handover step is the floor: without it, recall +degenerates to probabilistic reconstruction from canonical +files plus journal. diff --git a/internal/assets/read/agent/pi.go b/internal/assets/read/agent/pi.go new file mode 100644 index 000000000..103defb34 --- /dev/null +++ b/internal/assets/read/agent/pi.go @@ -0,0 +1,80 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package agent + +import ( + "io/fs" + "path" + + "github.com/ActiveMemory/ctx/internal/assets" + "github.com/ActiveMemory/ctx/internal/config/asset" +) + +// pi.go is a deliberate deviation from agent.go's layout: +// the Pi integration embeds two surfaces (an extension +// directory and a skill tree), so both accessors live in +// this file instead of joining the single-payload +// accessors in agent.go. + +// PiExtension reads all embedded Pi extension files. +// Returns a map of filename to content for files in +// integrations/pi/extension/. +// +// Returns: +// - map[string][]byte: Filename -> content for each extension file +// - error: Non-nil if the directory read fails +func PiExtension() (map[string][]byte, error) { + files := make(map[string][]byte) + entries, dirErr := fs.ReadDir( + assets.FS, asset.DirIntegrationsPiExtension) + if dirErr != nil { + return nil, dirErr + } + for _, entry := range entries { + if entry.IsDir() { + continue + } + name := entry.Name() + p := path.Join(asset.DirIntegrationsPiExtension, name) + content, readErr := assets.FS.ReadFile(p) + if readErr != nil { + return nil, readErr + } + files[name] = content + } + return files, nil +} + +// PiSkills reads all embedded Pi skill templates. +// Returns a map of skill directory name to SKILL.md content for skills +// in integrations/pi/skills/. +// +// Returns: +// - map[string][]byte: Skill name -> SKILL.md content +// - error: Non-nil if the directory read fails +func PiSkills() (map[string][]byte, error) { + skills := make(map[string][]byte) + entries, dirErr := fs.ReadDir(assets.FS, asset.DirIntegrationsPiSkill) + if dirErr != nil { + return nil, dirErr + } + for _, entry := range entries { + if !entry.IsDir() { + continue + } + name := entry.Name() + skillPath := path.Join( + asset.DirIntegrationsPiSkill, + name, asset.FileSKILLMd) + content, readErr := assets.FS.ReadFile(skillPath) + if readErr != nil { + return nil, readErr + } + skills[name] = content + } + return skills, nil +} diff --git a/internal/assets/read/skill/frontmatter_test.go b/internal/assets/read/skill/frontmatter_test.go index f6f67b2ba..bcb030268 100644 --- a/internal/assets/read/skill/frontmatter_test.go +++ b/internal/assets/read/skill/frontmatter_test.go @@ -25,6 +25,7 @@ var skillTrees = []string{ asset.DirClaudeSkills, asset.DirIntegrationsOpenCodeSkill, asset.DirIntegrationsCopilotSkill, + asset.DirIntegrationsPiSkill, } // skillFrontmatter is the minimum frontmatter contract every @@ -38,7 +39,7 @@ type skillFrontmatter struct { } // TestSkillFrontmatter walks every embedded SKILL.md across -// the three tool trees and asserts the minimum frontmatter +// the four tool trees and asserts the minimum frontmatter // contract: `name` matches the containing directory's // basename, and `description` is a non-empty string. All // violations are reported in a single pass. diff --git a/internal/cli/setup/cmd/root/doc.go b/internal/cli/setup/cmd/root/doc.go index ac126fc5f..db5f19e8e 100644 --- a/internal/cli/setup/cmd/root/doc.go +++ b/internal/cli/setup/cmd/root/doc.go @@ -12,9 +12,9 @@ // The command accepts exactly one positional argument: // the name of an AI coding tool (e.g., claude, // cursor, copilot, kiro, cline, aider, windsurf, -// copilot-cli, agents). It outputs configuration -// snippets and setup instructions specific to that -// tool. +// copilot-cli, agents, opencode, pi). It outputs +// configuration snippets and setup instructions +// specific to that tool. // // Without --write, the command prints the integration // instructions and configuration content to stdout diff --git a/internal/cli/setup/cmd/root/run.go b/internal/cli/setup/cmd/root/run.go index 66b82d45b..16b89b301 100644 --- a/internal/cli/setup/cmd/root/run.go +++ b/internal/cli/setup/cmd/root/run.go @@ -19,6 +19,7 @@ import ( coreCursor "github.com/ActiveMemory/ctx/internal/cli/setup/core/cursor" coreKiro "github.com/ActiveMemory/ctx/internal/cli/setup/core/kiro" coreOpenCode "github.com/ActiveMemory/ctx/internal/cli/setup/core/opencode" + corePi "github.com/ActiveMemory/ctx/internal/cli/setup/core/pi" "github.com/ActiveMemory/ctx/internal/config/embed/text" cfgHook "github.com/ActiveMemory/ctx/internal/config/hook" "github.com/ActiveMemory/ctx/internal/err/config" @@ -103,6 +104,12 @@ func Run(cmd *cobra.Command, args []string, writeFile bool) error { } writeSetup.InfoTool(cmd, desc.Text(text.DescKeyHookOpenCode)) + case cfgHook.ToolPi: + if writeFile { + return corePi.Deploy(cmd) + } + writeSetup.InfoTool(cmd, desc.Text(text.DescKeyHookPi)) + case cfgHook.ToolWindsurf: writeSetup.InfoTool(cmd, desc.Text(text.DescKeyHookWindsurf)) diff --git a/internal/cli/setup/core/pi/deploy_test.go b/internal/cli/setup/core/pi/deploy_test.go new file mode 100644 index 000000000..b946580cf --- /dev/null +++ b/internal/cli/setup/core/pi/deploy_test.go @@ -0,0 +1,226 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package pi + +import ( + "bytes" + "os" + "path/filepath" + "runtime" + "sort" + "strings" + "testing" + + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/agent" + "github.com/ActiveMemory/ctx/internal/config/marker" +) + +func testCmd(buf *bytes.Buffer) *cobra.Command { + cmd := &cobra.Command{} + cmd.SetOut(buf) + return cmd +} + +func withTempProjectDir(t *testing.T) string { + t.Helper() + tmp := t.TempDir() + origDir, _ := os.Getwd() + if err := os.Chdir(tmp); err != nil { + t.Fatalf("chdir: %v", err) + } + t.Cleanup(func() { _ = os.Chdir(origDir) }) + return tmp +} + +func TestDeployExtension_RefreshesStaleExtension(t *testing.T) { + withTempProjectDir(t) + target := filepath.Join(".pi", "extensions", "ctx.ts") + if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + if err := os.WriteFile(target, []byte("stale extension"), 0o644); err != nil { + t.Fatalf("seed extension: %v", err) + } + + var buf bytes.Buffer + if err := deployExtension(testCmd(&buf)); err != nil { + t.Fatalf("deployExtension: %v", err) + } + + files, err := agent.PiExtension() + if err != nil { + t.Fatalf("PiExtension: %v", err) + } + got, err := os.ReadFile(target) + if err != nil { + t.Fatalf("read extension: %v", err) + } + if !bytes.Equal(got, files["ctx.ts"]) { + t.Fatalf("extension not refreshed") + } + if bytes.Contains(buf.Bytes(), []byte("skipped")) { + t.Fatalf("expected refresh, got skipped output %q", buf.String()) + } +} + +func TestDeploySkills_RefreshesStaleSkill(t *testing.T) { + withTempProjectDir(t) + target := filepath.Join(".pi", "skills", "ctx-agent", "SKILL.md") + if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + if err := os.WriteFile(target, []byte("stale skill"), 0o644); err != nil { + t.Fatalf("seed skill: %v", err) + } + + var buf bytes.Buffer + if err := deploySkills(testCmd(&buf)); err != nil { + t.Fatalf("deploySkills: %v", err) + } + + skills, err := agent.PiSkills() + if err != nil { + t.Fatalf("PiSkills: %v", err) + } + got, err := os.ReadFile(target) + if err != nil { + t.Fatalf("read skill: %v", err) + } + if !bytes.Equal(got, skills["ctx-agent"]) { + t.Fatalf("skill not refreshed") + } + if bytes.Contains(buf.Bytes(), []byte("skipped")) { + t.Fatalf("expected refresh, got skipped output %q", buf.String()) + } +} + +func TestDeploy_FreshProject_CreatesAllFiles(t *testing.T) { + withTempProjectDir(t) + + var buf bytes.Buffer + if err := Deploy(testCmd(&buf)); err != nil { + t.Fatalf("Deploy: %v", err) + } + + ext, err := agent.PiExtension() + if err != nil { + t.Fatalf("PiExtension: %v", err) + } + got, err := os.ReadFile( + filepath.Join(".pi", "extensions", "ctx.ts")) + if err != nil { + t.Fatalf("read extension: %v", err) + } + if !bytes.Equal(got, ext["ctx.ts"]) { + t.Fatal("extension not byte-identical to embedded") + } + + skills, err := agent.PiSkills() + if err != nil { + t.Fatalf("PiSkills: %v", err) + } + if len(skills) == 0 { + t.Fatal("no embedded skills discovered") + } + for name, content := range skills { + p := filepath.Join(".pi", "skills", name, "SKILL.md") + got, readErr := os.ReadFile(p) + if readErr != nil { + t.Fatalf("skill %q not created: %v", name, readErr) + } + if !bytes.Equal(got, content) { + t.Fatalf("skill %q not byte-identical to embedded", name) + } + } + + agents, err := os.ReadFile("AGENTS.md") + if err != nil { + t.Fatalf("AGENTS.md not created: %v", err) + } + if !bytes.Contains(agents, []byte(marker.AgentsStart)) { + t.Fatal("AGENTS.md missing ctx marker block") + } +} + +func TestDeploySkills_DeterministicOrdering(t *testing.T) { + withTempProjectDir(t) + + skills, err := agent.PiSkills() + if err != nil { + t.Fatalf("PiSkills: %v", err) + } + names := make([]string, 0, len(skills)) + for name := range skills { + names = append(names, name) + } + sort.Strings(names) + + var buf bytes.Buffer + if err := deploySkills(testCmd(&buf)); err != nil { + t.Fatalf("deploySkills: %v", err) + } + + // Per-skill output lines are emitted in deploy order, + // so their sequence must be lexicographic. + var got []string + for _, line := range strings.Split(buf.String(), "\n") { + trimmed := strings.TrimSpace(line) + for _, name := range names { + if strings.Contains(trimmed, + filepath.Join(".pi", "skills", name)) { + got = append(got, name) + break + } + } + } + if len(got) != len(names) { + t.Fatalf("expected %d skill lines, got %d: %q", + len(names), len(got), buf.String()) + } + for i, name := range got { + if name != names[i] { + t.Fatalf("skill %d out of order: %q vs %q", + i, name, names[i]) + } + } +} + +func TestDeployExtension_RejectsSymlinkTarget(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("symlink behavior varies on Windows in this environment") + } + withTempProjectDir(t) + target := filepath.Join(".pi", "extensions", "ctx.ts") + if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + realFile := filepath.Join(t.TempDir(), "outside.ts") + if err := os.WriteFile(realFile, []byte("secret"), 0o644); err != nil { + t.Fatalf("seed real file: %v", err) + } + if err := os.Symlink(realFile, target); err != nil { + t.Fatalf("symlink: %v", err) + } + + if err := deployExtension(testCmd(&bytes.Buffer{})); err == nil { + t.Fatal("expected symlink rejection, got nil") + } +} + +func TestDeploySkills_RejectsNonRegularTarget(t *testing.T) { + withTempProjectDir(t) + target := filepath.Join(".pi", "skills", "ctx-agent", "SKILL.md") + if err := os.MkdirAll(target, 0o755); err != nil { + t.Fatalf("mkdir target dir: %v", err) + } + + if err := deploySkills(testCmd(&bytes.Buffer{})); err == nil { + t.Fatal("expected non-regular target rejection, got nil") + } +} diff --git a/internal/cli/setup/core/pi/doc.go b/internal/cli/setup/core/pi/doc.go new file mode 100644 index 000000000..1e8f1cdc3 --- /dev/null +++ b/internal/cli/setup/core/pi/doc.go @@ -0,0 +1,36 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package pi generates Pi CLI integration files during project +// setup. +// +// Pi (earendil-works/pi, pi.dev) is a self-extensible coding-agent +// CLI. By design it has no built-in MCP, so the integration surface +// is a thin TypeScript extension plus Agent-Skills-standard skills; +// all real logic stays in the ctx Go binary via ctx system +// subcommands. +// +// # Deployment Steps +// +// [Deploy] performs three operations in sequence: +// 1. Extension deployment: writes .pi/extensions/ctx.ts as a flat +// top-level file (Pi auto-loads flat files in .pi/extensions/; +// project-local extensions load only after project trust) +// 2. AGENTS.md: deploys the shared agent instructions template +// (Pi loads AGENTS.md natively as a context file) +// 3. Skills: copies the bundled ctx skills to .pi/skills/ +// +// Extension deployment failure is fatal; AGENTS.md and skills +// failures are warnings that do not halt deployment, matching the +// OpenCode deploy semantics. +// +// # Data Flow +// +// The setup orchestrator calls [Deploy] from the `ctx setup pi +// --write` branch of the setup root command. Deployment reads the +// embedded assets via the agent package (PiExtension, PiSkills) and +// reports progress through the write/setup Pi info helpers. +package pi diff --git a/internal/cli/setup/core/pi/extension.go b/internal/cli/setup/core/pi/extension.go new file mode 100644 index 000000000..0c6a1ba2c --- /dev/null +++ b/internal/cli/setup/core/pi/extension.go @@ -0,0 +1,84 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package pi + +import ( + "bytes" + "os" + "path/filepath" + + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/agent" + "github.com/ActiveMemory/ctx/internal/config/fs" + cfgHook "github.com/ActiveMemory/ctx/internal/config/hook" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" + errSetup "github.com/ActiveMemory/ctx/internal/err/setup" + ctxIo "github.com/ActiveMemory/ctx/internal/io" + writeSetup "github.com/ActiveMemory/ctx/internal/write/setup" +) + +// deployExtension writes the embedded extension to +// .pi/extensions/ctx.ts. Pi auto-loads flat top-level files under +// .pi/extensions/ (project-local; loaded only after project trust). +// If an installed ctx extension differs from the embedded one, it +// is refreshed in place. +// +// The extension is a static embedded asset: a thin TypeScript shim +// that wires Pi lifecycle events to ctx system subcommands, with a +// type-only import of @earendil-works/pi-coding-agent erased at +// compile time. No runtime npm dependency tree is needed. +// +// Parameters: +// - cmd: Cobra command for output messages +// +// Returns: +// - error: Non-nil if directory creation or file write fails +func deployExtension(cmd *cobra.Command) error { + extensionDir := filepath.Join( + cfgHook.DirPi, cfgHook.DirPiExtensions, + ) + target := filepath.Join( + extensionDir, cfgHook.FilePiExtensionDeploy, + ) + if _, validateErr := validateManagedTarget(target); validateErr != nil { + return validateErr + } + + files, readErr := agent.PiExtension() + if readErr != nil { + return readErr + } + content, ok := files[cfgHook.FilePiExtensionDeploy] + if !ok { + return errSetup.MissingEmbeddedAsset(cfgHook.FilePiExtensionDeploy) + } + + if existing, statErr := ctxIo.SafeReadUserFile(target); statErr == nil { + if bytes.Equal(existing, content) { + writeSetup.InfoPiSkipped(cmd, target) + return nil + } + } else if !os.IsNotExist(statErr) { + return errFs.FileRead(target, statErr) + } + + if mkErr := ctxIo.SafeMkdirAll( + extensionDir, fs.PermExec, + ); mkErr != nil { + return errFs.Mkdir(extensionDir, mkErr) + } + + if wErr := ctxIo.SafeWriteFile( + target, content, fs.PermFile, + ); wErr != nil { + return errFs.FileWrite(target, wErr) + } + writeSetup.InfoPiCreated(cmd, target) + + return nil +} diff --git a/internal/cli/setup/core/pi/pi.go b/internal/cli/setup/core/pi/pi.go new file mode 100644 index 000000000..b54796ff9 --- /dev/null +++ b/internal/cli/setup/core/pi/pi.go @@ -0,0 +1,54 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package pi + +import ( + "path/filepath" + + "github.com/spf13/cobra" + + coreAgents "github.com/ActiveMemory/ctx/internal/cli/setup/core/agents" + cfgHook "github.com/ActiveMemory/ctx/internal/config/hook" + writeErr "github.com/ActiveMemory/ctx/internal/write/err" + writeSetup "github.com/ActiveMemory/ctx/internal/write/setup" +) + +// Deploy generates all Pi integration files. +// +// Creates the .pi/extensions/ctx.ts extension file, deploys +// AGENTS.md with shared instructions, and copies ctx skills to +// .pi/skills/. +// +// Refreshes stale managed files and skips ones that already match. +// +// Parameters: +// - cmd: Cobra command for output messages +// +// Returns: +// - error: Non-nil if extension deployment fails (other errors are +// warned but do not halt deployment) +func Deploy(cmd *cobra.Command) error { + if extErr := deployExtension(cmd); extErr != nil { + return extErr + } + + if agentsErr := coreAgents.Deploy(cmd); agentsErr != nil { + writeErr.WarnFile( + cmd, cfgHook.FileAgentsMd, agentsErr, + ) + } + + if skillErr := deploySkills(cmd); skillErr != nil { + skillsBase := filepath.Join( + cfgHook.DirPi, cfgHook.DirPiSkills, + ) + writeErr.WarnFile(cmd, skillsBase, skillErr) + } + + writeSetup.InfoPiSummary(cmd) + return nil +} diff --git a/internal/cli/setup/core/pi/skill.go b/internal/cli/setup/core/pi/skill.go new file mode 100644 index 000000000..f1045a391 --- /dev/null +++ b/internal/cli/setup/core/pi/skill.go @@ -0,0 +1,85 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package pi + +import ( + "bytes" + "os" + "path/filepath" + "sort" + + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/agent" + "github.com/ActiveMemory/ctx/internal/config/fs" + cfgHook "github.com/ActiveMemory/ctx/internal/config/hook" + errFs "github.com/ActiveMemory/ctx/internal/err/fs" + ctxIo "github.com/ActiveMemory/ctx/internal/io" + writeSetup "github.com/ActiveMemory/ctx/internal/write/setup" +) + +// deploySkills creates .pi/skills/<name>/SKILL.md for each embedded +// Pi skill. Existing ctx-managed skill files are refreshed when their +// contents differ from the embedded source. +// +// Parameters: +// - cmd: Cobra command for output messages +// +// Returns: +// - error: Non-nil if directory creation or file write fails +func deploySkills(cmd *cobra.Command) error { + skills, readErr := agent.PiSkills() + if readErr != nil { + return readErr + } + + skillsBase := filepath.Join( + cfgHook.DirPi, cfgHook.DirPiSkills, + ) + + // Iterate in sorted order so deploy is deterministic and tests + // that plant blocking files at a specific skill path observe a + // stable filesystem state on partial-failure paths. + names := make([]string, 0, len(skills)) + for name := range skills { + names = append(names, name) + } + sort.Strings(names) + + for _, name := range names { + content := skills[name] + skillDir := filepath.Join(skillsBase, name) + target := filepath.Join(skillDir, cfgHook.FileSKILLMd) + if _, validateErr := validateManagedTarget(target); validateErr != nil { + return validateErr + } + + if existing, statErr := ctxIo.SafeReadUserFile(target); statErr == nil { + if bytes.Equal(existing, content) { + writeSetup.InfoPiSkipped(cmd, target) + continue + } + } else if !os.IsNotExist(statErr) { + return errFs.FileRead(target, statErr) + } + + if mkErr := ctxIo.SafeMkdirAll( + skillDir, fs.PermExec, + ); mkErr != nil { + return errFs.Mkdir(skillDir, mkErr) + } + + if wErr := ctxIo.SafeWriteFile( + target, content, fs.PermFile, + ); wErr != nil { + return errFs.FileWrite(target, wErr) + } + writeSetup.InfoPiCreated(cmd, target) + } + + return nil +} diff --git a/internal/cli/setup/core/pi/testmain_test.go b/internal/cli/setup/core/pi/testmain_test.go new file mode 100644 index 000000000..0492ee6e8 --- /dev/null +++ b/internal/cli/setup/core/pi/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package pi + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/cli/setup/core/pi/validate.go b/internal/cli/setup/core/pi/validate.go new file mode 100644 index 000000000..c75557b3d --- /dev/null +++ b/internal/cli/setup/core/pi/validate.go @@ -0,0 +1,43 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package pi + +import ( + "os" + + errFs "github.com/ActiveMemory/ctx/internal/err/fs" +) + +// validateManagedTarget rejects symlinks and non-regular files before +// Pi-managed files are read or refreshed in place. Missing files are +// allowed and reported as absent. +// +// Parameters: +// - targetFile: file path to validate +// +// Returns: +// - bool: true when the path exists and passed validation. +// - error: non-nil when stat fails or the path is not a regular file. +func validateManagedTarget(targetFile string) (bool, error) { + fi, lstatErr := os.Lstat(targetFile) + if lstatErr != nil { + if os.IsNotExist(lstatErr) { + return false, nil + } + return false, errFs.StatPath(targetFile, lstatErr) + } + + if fi.Mode()&os.ModeSymlink != 0 { + return false, errFs.FileRead(targetFile, os.ErrInvalid) + } + + if !fi.Mode().IsRegular() { + return false, errFs.FileRead(targetFile, os.ErrInvalid) + } + + return true, nil +} diff --git a/internal/config/asset/asset.go b/internal/config/asset/asset.go index 57ad45ce5..4d127cb6a 100644 --- a/internal/config/asset/asset.go +++ b/internal/config/asset/asset.go @@ -24,6 +24,8 @@ const ( DirIntegrationsCopilotSkill = "integrations/copilot-cli/skills" DirIntegrationsOpenCodePlugin = "integrations/opencode/plugin" DirIntegrationsOpenCodeSkill = "integrations/opencode/skills" + DirIntegrationsPiExtension = "integrations/pi/extension" + DirIntegrationsPiSkill = "integrations/pi/skills" DirHooksMessages = "hooks/messages" DirHooksTrace = "hooks/trace" DirI18nPlaceholders = "i18n/placeholders" diff --git a/internal/config/embed/text/hook.go b/internal/config/embed/text/hook.go index 8636fe4e1..3f825f6fb 100644 --- a/internal/config/embed/text/hook.go +++ b/internal/config/embed/text/hook.go @@ -18,6 +18,8 @@ const ( DescKeyHookCopilotCLI = "hook.copilot-cli" // DescKeyHookOpenCode is the text key for hook opencode messages. DescKeyHookOpenCode = "hook.opencode" + // DescKeyHookPi is the text key for hook pi messages. + DescKeyHookPi = "hook.pi" // DescKeyHookSupportedTools is the text key for hook supported tools messages. DescKeyHookSupportedTools = "hook.supported-tools" // DescKeyHookWindsurf is the text key for hook windsurf messages. @@ -74,6 +76,15 @@ const ( // DescKeyWriteHookOpenCodeSummary is the text key for write hook opencode // summary messages. DescKeyWriteHookOpenCodeSummary = "write.hook-opencode-summary" + // DescKeyWriteHookPiCreated is the text key for write hook pi created + // messages. + DescKeyWriteHookPiCreated = "write.hook-pi-created" + // DescKeyWriteHookPiSkipped is the text key for write hook pi skipped + // messages. + DescKeyWriteHookPiSkipped = "write.hook-pi-skipped" + // DescKeyWriteHookPiSummary is the text key for write hook pi summary + // messages. + DescKeyWriteHookPiSummary = "write.hook-pi-summary" // DescKeyWriteHookUnknownTool is the text key for write hook unknown tool // messages. DescKeyWriteHookUnknownTool = "write.hook-unknown-tool" diff --git a/internal/config/hook/hook.go b/internal/config/hook/hook.go index 3af1ef68c..d393dfd08 100644 --- a/internal/config/hook/hook.go +++ b/internal/config/hook/hook.go @@ -72,6 +72,7 @@ const ( ToolCline = "cline" ToolCodex = "codex" ToolOpenCode = "opencode" + ToolPi = "pi" ToolWindsurf = "windsurf" ) @@ -150,6 +151,22 @@ const ( FileOpenCodePluginDeploy = "ctx.ts" ) +// Pi integration paths. +const ( + // DirPi is the Pi project config directory. + DirPi = ".pi" + // DirPiExtensions is the Pi extensions subdirectory. + DirPiExtensions = "extensions" + // DirPiSkills is the Pi skills subdirectory. + DirPiSkills = "skills" + // FilePiExtensionDeploy is the deployment filename for the Pi + // extension under .pi/extensions/. Pi auto-loads flat top-level + // files in .pi/extensions/ (subdirectory form + // .pi/extensions/<name>/index.ts is also supported, but v1 ships + // a flat file for OpenCode symmetry). + FilePiExtensionDeploy = "ctx.ts" +) + // Prefixes const ( // StdinReadTimeout is the maximum time to wait for hook JSON on stdin diff --git a/internal/write/setup/hook.go b/internal/write/setup/hook.go index 8ec4929c1..af7e0c1d1 100644 --- a/internal/write/setup/hook.go +++ b/internal/write/setup/hook.go @@ -224,6 +224,38 @@ func InfoOpenCodeSummary(cmd *cobra.Command) { cmd.Println(desc.Text(text.DescKeyWriteHookOpenCodeSummary)) } +// InfoPiCreated reports that a Pi integration file was created. +// +// Parameters: +// - cmd: Cobra command for output +// - targetFile: Path to the created file +func InfoPiCreated(cmd *cobra.Command, targetFile string) { + cmd.Println(fmt.Sprintf( + desc.Text(text.DescKeyWriteHookPiCreated), + targetFile)) +} + +// InfoPiSkipped reports that a Pi integration file was skipped +// because it already exists. +// +// Parameters: +// - cmd: Cobra command for output +// - targetFile: Path to the existing file +func InfoPiSkipped(cmd *cobra.Command, targetFile string) { + cmd.Println(fmt.Sprintf( + desc.Text(text.DescKeyWriteHookPiSkipped), + targetFile)) +} + +// InfoPiSummary prints the post-write summary for Pi. +// +// Parameters: +// - cmd: Cobra command for output +func InfoPiSummary(cmd *cobra.Command) { + cmd.Println() + cmd.Println(desc.Text(text.DescKeyWriteHookPiSummary)) +} + // InfoCopilotCLISkipped reports that copilot-cli hooks were skipped // because they already exist. // diff --git a/specs/pi-cli-integration.md b/specs/pi-cli-integration.md new file mode 100644 index 000000000..d569223c2 --- /dev/null +++ b/specs/pi-cli-integration.md @@ -0,0 +1,282 @@ +# Spec: Pi CLI Integration for ctx + +## Context + +Pi (`earendil-works/pi`, pi.dev) is a "self-extensible" coding-agent CLI and +agent harness (TypeScript monorepo, Bun-based, MIT, actively developed as of +2026-08-23). Its design principle: keep the core small, and intentionally ship +**no built-in MCP, sub-agents, permission popups, plan mode, or background +bash**. Workflow behavior moves to four surfaces: + +1. **Extensions** — TypeScript modules auto-discovered from + `~/.pi/agent/extensions/` (global) and `.pi/extensions/` (project-local, + loaded only after project trust; hot-reloadable via `/reload`). Flat + `.pi/extensions/*.ts` and subdirectory form `.pi/extensions/<name>/index.ts` + are both supported. Events of interest: + - `session_start` (reason `startup | reload | new | resume | fork`) + - `session_before_compact` / `session_compact` / `session_compact_failed` + - `before_agent_start` — can inject a **persistent message** + (`{message: {customType, content, display}}`, stored in the session and + sent to the LLM) and/or modify the system prompt + - `tool_call` (pre-execution, can block/modify) and `tool_result` + (post-execution, middleware-style; carries `isError`) + - `agent_settled` — fired when Pi will not continue running automatically + - On `/new`, `/resume`, `/fork`, and `/reload` Pi tears down the extension + instance (`session_shutdown` → reload → `session_start`), so in-memory + extension state resets — flag loss can only cause an extra injection, + never a missed one + - `ExtensionContext` exposes `ctx.cwd`, `ctx.hasUI`, + `ctx.isProjectTrusted()`, `ctx.sessionManager`, and `ctx.signal` + - Available imports: `@earendil-works/pi-coding-agent` (types), `typebox`, + `@earendil-works/pi-ai`, `@earendil-works/pi-tui`, Node built-ins + (`node:child_process`, `node:path`, …) + - Built-in tool names: `read`, `bash`, `edit`, `write`, `grep`, `find`, + `ls` +2. **Skills** — the Agent Skills standard (agentskills.io). Project locations: + `.pi/skills/` in the project, and `.agents/skills/` in cwd and ancestor + directories up to the git root (only `.agents` gets the ancestor walk; both + load post-trust). Global: `~/.pi/agent/skills/`, `~/.agents/skills/`. Also + package.json `pi.skills` and a `skills` array in `.pi/settings.json`. + Required frontmatter: `name` (lowercase a-z, 0-9, hyphens; need not match + the parent directory) and `description`. Skills register as + `/skill:<name>` commands. +3. **AGENTS.md** — loaded natively as a context file + (`systemPromptOptions.contextFiles`). +4. **MCP — absent by design.** No MCP registration path exists. + +ctx already ships the OpenCode integration on the established blueprint +(`specs/opencode-integration.md`): a Go setup package +(`internal/cli/setup/core/opencode/`), a thin embedded TypeScript shim +(`internal/assets/integrations/opencode/plugin/index.ts`) that shells out to +`ctx system` subcommands, bundled skills +(`internal/assets/integrations/opencode/skills/`), and the shared +`agent.AgentsMd()` project-root template. + +## Goal + +Add `ctx setup pi [--write]` that deploys, following the OpenCode blueprint: + +1. `.pi/extensions/ctx.ts` — thin shim extension (embedded asset, not an npm + dependency; type-only import of `@earendil-works/pi-coding-agent`, + `node:child_process` for subprocess). Flat single-file deploy for exact + OpenCode symmetry; Pi auto-loads flat `.pi/extensions/*.ts`. v1 ships no + package.json next to the extension (no runtime deps); the subdirectory form + is left for a future deps-carrying extension. +2. `.pi/skills/ctx-*/SKILL.md` — the same bundled skill set as OpenCode + (frontmatter already conforms to the Agent Skills standard) +3. AGENTS.md at the project root (shared `coreAgents.Deploy(cmd)`, as + `internal/cli/setup/core/opencode/opencode.go` does) + +**Why this shape:** every ctx integration is a Go package that deploys config + +assets; all real logic stays in Go via `ctx system`. Pi's lack of MCP means the +extension is the only lifecycle channel — the same "thin shim" discipline +applies, just without an MCP leg. + +## Extension wiring (`.pi/extensions/ctx.ts`) + +### Hook envelope requirement (load-bearing) + +The `ctx system` hook subcommands (`post-commit`, `check-task-completion`, +`check-persistence`) parse a **hook-JSON envelope from stdin** via +`coreCheck.FullPreamble`; with no envelope they bail silently. The extension +must therefore spawn these with piped stdin and write +`{"session_id": "<session id>", "tool_input": {"command": "<command>"}}` +before closing stdin (`child.stdin.end(json)`). Closing stdin immediately also +avoids the `hook.StdinReadTimeout` (2s) stall on open pipes. Consequences: + +- **`node:child_process` is mandatory** for these calls: Pi's own exec helper + hardcodes `stdio: ["ignore", "pipe", "pipe"]` (no stdin pipe), so the + envelope cannot be delivered through it. +- `session_id` comes from `ctx.sessionManager` so per-session counters and + pause markers are keyed correctly instead of collapsing to `IDUnknown`. +- `ctx agent` is a normal flag-driven command and needs **no** envelope. + +### Event map + +| Pi event | Action | +|----------|--------| +| `session_start` | Warm-up: run `ctx agent --budget 4000` (cwd-anchored to `ctx.cwd`), cache the packet; keep out of the prompt path | +| `before_agent_start` | Inject the cached packet as a persistent `message` **only when** no ctx-injected `custom_message` (our `customType`) exists in the **live context** — compaction-aware scan of `sessionManager.buildContextEntries()` (summary + kept tail + post-compaction entries) | +| `session_compact` (success) | No-op for the flag (the live-context predicate subsumes it); optionally drop the cache so the next injection re-warms | +| `tool_result` — tool `bash`, command matches `git commit`, **`!event.isError`** | Envelope call `ctx system post-commit` | +| `tool_result` — tool `edit` or `write`, **`!event.isError`** | Envelope call `ctx system check-task-completion` | +| `agent_settled` | Envelope call `ctx system check-persistence` | + +Design notes: + +- **`tool_result`, not `tool_call`,** for the post-commit trigger: + `tool_call` fires before execution (blocking semantics); `tool_result` + mirrors OpenCode's post-execution `tool.execute.after` trigger. +- **`isError` gating** (Pi-specific; OpenCode has no equivalent signal): a + failed `git commit` must not fire post-commit (it would re-score the + *previous* commit via `ScoreCommitViolations()` and emit a spurious nudge). + Gate both post-commit and edit/write branches on `!event.isError`. +- **Compaction interop is breadcrumb-mediated and stateless across the + reload:** Pi's own compaction mechanics validate the design — an injected + `custom_message` is a valid compaction cut point, so once it ages past + `keepRecentTokens` it is folded into the lossy LLM summary and re-injection + is genuinely needed; scanning the LIVE context for our `customType` + (`buildContextEntries()`: summary + kept tail + post-compaction entries) + decides precisely (no duplicate when the packet is still in + the kept tail, no `/reload` duplicate, no missed injection after + switch/fork/reload — extension state resets are fail-safe). The rejected + alternative: a custom `session_before_compact` summary would *replace* Pi's + LLM summary; cross-extension precedence is undocumented, so we don't take + ownership of the summary. + Known edge: after an overflow compaction with `willRetry`, the retried turn + does not pass through `before_agent_start`, so re-injection waits for the + next user prompt — accepted. +- **Latency:** the packet is produced once at `session_start` (warm-up) and + injected from cache on `before_agent_start`, so the prompt path never waits + on `ctx agent`. All subprocess calls receive `ctx.signal` and a timeout so + Esc cancels cleanly; swallowed non-zero exits (nothrow equivalent), bounded + output. If the `ctx` binary is absent, the extension no-ops silently. +- Unrecognized tool names silently no-op. Tool-name sets are pinned to Pi's + built-ins (`bash`; `edit`, `write`) — do **not** carry OpenCode's + `shell`/`file_edit` names. Verify against Pi's docs when Pi bumps. +- The agent's own shell tool is not anchored by the extension; users launch + `pi` from the project root (documented in the quickstart, same caveat as + OpenCode). + +## Files to create + +``` +internal/assets/integrations/pi/ +├── extension/ +│ └── ctx.ts # Thin shim extension (~150 lines with envelope + +│ # live-context scan + isError gating) +└── skills/ # Same bundled skill set as OpenCode (10 skills) + ├── ctx-agent/SKILL.md + ├── ctx-handover/SKILL.md + ├── ctx-kb-ask/SKILL.md + ├── ctx-kb-ground/SKILL.md + ├── ctx-kb-ingest/SKILL.md + ├── ctx-kb-note/SKILL.md + ├── ctx-kb-site-review/SKILL.md + ├── ctx-remember/SKILL.md + ├── ctx-status/SKILL.md + └── ctx-wrap-up/SKILL.md + +internal/cli/setup/core/pi/ +├── doc.go # package doc (docstring floor: related packages) +├── pi.go # Deploy entry: extension (fatal) → coreAgents.Deploy +│ # (warn) → skills (warn) → InfoPiSummary +├── extension.go # deploy .pi/extensions/ctx.ts (refresh in place) +├── skill.go # deploy .pi/skills/ctx-*/SKILL.md (sorted, deterministic) +├── validate.go # managed-target validation (OpenCode semantics) +└── deploy_test.go # modeled on opencode's suite + +internal/config/asset/asset.go # DirIntegrationsPiExtension, + # DirIntegrationsPiSkill +internal/assets/read/agent/pi.go # PiExtension(), PiSkills() + # accessors (embedded-fs reads; + # new file is a deliberate + # deviation from agent.go's + # single-file pattern — noted, + # not silent) +internal/config/hook/hook.go # ToolPi = "pi" (tool-const block), + # DirPi = ".pi", DirPiExtensions, + # FilePiExtensionDeploy = "ctx.ts", + # DirPiSkills = "skills" +internal/assets/commands/text/hooks.yaml # hook.pi instructions text + + # "pi" line in hook.supported-tools +internal/assets/commands/text/write.yaml # write.hook-pi-created/-skipped/-summary +internal/config/embed/text/hook.go # DescKeyHookPi, DescKeyWriteHookPiCreated/ + # Skipped/Summary +internal/write/setup/hook.go # InfoPiCreated, InfoPiSkipped, + # InfoPiSummary +internal/cli/setup/cmd/root/run.go # case cfgHook.ToolPi branch +internal/cli/setup/cmd/root/doc.go # docstring update +tools/typecheck/pi/ # tsconfig.json + package.json + # (@earendil-works/pi-coding-agent + # types, tsc --noEmit) +.github/workflows/ci.yml # typecheck-pi-extension job +``` + +`internal/config/setup` needs no Pi entry (Pi has no MCP/global-config leg). + +## CLI surface + +No new cobra subcommand and no new `Use*` constant — tools are +`case cfgHook.Tool*` branches inside `root.Run()` (existing `--write` flag and +positional tool arg already exist): + +- `ctx setup pi` — prints the static `hook.pi` instruction text + (`writeSetup.InfoPi*` with `desc.Text(text.DescKeyHookPi)`), matching every + other integration's no-`--write` path. No filesystem-inspecting drift report + (that machinery does not exist for any integration and is out of scope). +- `ctx setup pi --write` — calls `corePi.Deploy(cmd)`. +- The YAML/DescKey legs (`hooks.yaml`, `write.yaml`, + `internal/config/embed/text/hook.go`) are enforced by + `TestDescKeyYAMLLinkage` — they are mandatory, not optional. + +## Error cases + +- `ctx` binary not found on PATH: extension no-ops (tolerated; matches the + OpenCode shim's subprocess tolerance). +- Managed-target validation follows **OpenCode semantics verbatim**: symlinks + and non-regular files at `.pi/extensions/ctx.ts` / + `.pi/skills/ctx-*/SKILL.md` are refused; a differing regular file at a + managed path is refreshed in place (managed paths are documented as + ctx-owned). There is no ownership sentinel — "drifted ctx file" and "foreign + file" are the same observable state, so the spec deliberately does not claim + foreign-target refusal. +- Deploy error semantics mirror `opencode.Deploy`: extension deploy failure is + fatal (returns error); `coreAgents.Deploy` (AGENTS.md) and skills failures + are `writeErr.WarnFile` warnings that do not halt; then `InfoPiSummary`. +- Project trust: `.pi/extensions` and `.pi/skills` load only after the project + is trusted; first `pi` launch in a fresh project prompts for trust + (`defaultProjectTrust` is configurable). Setup itself is filesystem work and + succeeds regardless; the trust gate gates *execution*. + +## Tests + +- `deploy_test.go` modeled on `internal/cli/setup/core/opencode/deploy_test.go`: + fresh deploy, refresh-in-place on drift (seed arbitrary content at the + managed path, assert overwrite), refuse on symlink/non-regular target, + deterministic skill ordering. +- Asset linkage: embedded Pi extension + skills present; deploy constants match + embedded paths (extend the existing assets/embed linkage test pattern). +- `TestDescKeyYAMLLinkage` green (YAML keys ↔ DescKey constants). +- Full validation suite before declaring complete: `make build`, `make lint`, + `go test ./...`. + +## Non-goals + +- No MCP registration (Pi has none by design; documented, not a gap). +- No `tool_call` blocking gate (dangerous-command interception) — carried over + as a *permanent* omission from the OpenCode integration (its `DECISIONS.md` + entry 2026-04-26-231517: a shim that shelled out to block-dangerous-commands + would block every shell command on installs without the wrapper). +- No `registerTool` / `registerCommand` in the extension v1 (keeps the shim + dependency-free, no typebox schemas). +- No global deploy (`~/.pi/agent/...`) in v1 — project-local only. +- No `pi install` package distribution in v1. +- No `.agents/skills/` deploy in v1 (project `.pi/skills/` only). +- No session-file parser for Pi's JSONL session format (context capture stays + on the `ctx system` nudge path). +- No `resources_discover` skill-path contribution (the deploy-files approach is + chosen over Pi's other skill-delivery channel). + +## Resolved open questions + +1. **Injected packet visibility:** `message.display: true` — the user sees the + ctx packet in the TUI (transparency; decision 2026-08-23). +2. **CI type-check for the extension:** in scope — a `tools/typecheck/pi/` + job mirroring `tools/typecheck/opencode/` (tsconfig + package.json pulling + `@earendil-works/pi-coding-agent` types, `tsc --noEmit`), wired into + `.github/workflows/ci.yml` as a `typecheck-pi-extension` job. + +## Verification + +1. `make build && make lint && go test ./...` green on the branch. +2. Scratch project: `ctx init`, `ctx setup pi --write`; confirm + `.pi/extensions/ctx.ts`, `.pi/skills/ctx-*/SKILL.md`, and AGENTS.md + land with the managed markers. +3. Launch `pi` in the scratch project: accept the project-trust prompt; + first prompt triggers ctx packet injection; a successful `git commit` + triggers the post-commit nudge (a *failed* commit does not); `/compact` + then next prompt re-injects the packet; skills are reachable as + `/skill:ctx-status` etc. +4. Independent sub-agent review against this spec + TASKS.md before declaring + complete (playbook requirement). diff --git a/tools/typecheck/pi/README.md b/tools/typecheck/pi/README.md new file mode 100644 index 000000000..a753b79c6 --- /dev/null +++ b/tools/typecheck/pi/README.md @@ -0,0 +1,70 @@ +# `tools/typecheck/pi/` + +Type-check gate for the embedded Pi extension. + +## What this is + +The Pi extension source at +`internal/assets/integrations/pi/extension/ctx.ts` is shipped inside +the ctx binary via `//go:embed` and deployed to the user's +`.pi/extensions/ctx.ts` at install time (see +`internal/assets/README.md` for the embed contract). + +Without a type-check gate, a typo or a drift from the +`@earendil-works/pi-coding-agent` extension API would ship as bytes +and fail only when Pi loads the extension on a user's machine. + +This directory holds the tooling that gates that risk: + +- `package.json`: declares dependencies on + `@earendil-works/pi-coding-agent` (for the `ExtensionAPI` / + `ExtensionContext` types), `@types/node` (for the + `node:child_process` import), and `typescript`. +- `tsconfig.json`: `noEmit: true`, strict, with `include` + pointing at the embedded TS file via relative path. +- `package-lock.json`: committed; pinned for reproducibility. + +The directory sits **outside** `internal/assets/` deliberately: it +is *about* the embedded payload, not part of it. If it lived +alongside the `.ts` source, it would either bloat the embed (the +file-by-file `//go:embed` directive does not currently glob this +dir, but the proximity is misleading) or invite the question every +time. + +## Run locally + +```sh +cd tools/typecheck/pi +npm ci # or: bun install +npx tsc --noEmit # or: bunx tsc --noEmit +``` + +Either toolchain works; `tsc` is the same compiler under both. +CI uses `npm ci` (matching the `editors/vscode/` and +`tools/typecheck/opencode/` conventions and the committed +`package-lock.json`); local contributors may use whichever they +have installed. The Pi extension itself runs under Bun at the +consumer's machine, but the type-check tool only needs `tsc`, the +`@earendil-works/pi-coding-agent` type declarations, and +`@types/node` for the Node built-ins. + +## What this does **not** check + +- **Runtime behavior:** `tsc --noEmit` is a *static* check. It + catches type errors, not logic bugs. Runtime issues still + surface only when Pi loads the deployed extension. +- **The other embedded TypeScript assets:** the OpenCode plugin is + covered by `tools/typecheck/opencode/`. If new `.ts` assets are + added under `internal/assets/integrations/pi/`, extend the + `include` glob in `tsconfig.json` to cover them. +- **Embed coverage:** that lives in `internal/assets/embed_test.go`. + The two checks are complementary: this verifies the bytes are + valid TypeScript; that verifies the bytes are actually + embedded. + +## Maintenance + +Bump `@earendil-works/pi-coding-agent` when Pi releases a version +that changes extension event or type signatures. The extension +source itself documents the Pi API surface it targets in its own +header comment. Keep that comment and this dependency in sync. diff --git a/tools/typecheck/pi/package-lock.json b/tools/typecheck/pi/package-lock.json new file mode 100644 index 000000000..147f19c39 --- /dev/null +++ b/tools/typecheck/pi/package-lock.json @@ -0,0 +1,1998 @@ +{ + "name": "ctx-pi-extension-typecheck", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ctx-pi-extension-typecheck", + "devDependencies": { + "@earendil-works/pi-coding-agent": "^0.84.2", + "@types/node": "^20.0.0", + "typescript": "^5.6.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent": { + "version": "0.84.2", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.84.2.tgz", + "integrity": "sha512-l4E+B7hgXKWddRo8bC/eSue2aWZjEgJ9xIpf5p0Og+lq8a2TArCwJ0HCoCPCgaBP/tN4zbYH/wOwvx9pJpeLCA==", + "dev": true, + "hasShrinkwrap": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-agent-core": "^0.84.2", + "@earendil-works/pi-ai": "^0.84.2", + "@earendil-works/pi-client": "^0.84.2", + "@earendil-works/pi-protocol": "^0.84.2", + "@earendil-works/pi-tui": "^0.84.2", + "@silvia-odwyer/photon-node": "0.3.4", + "chalk": "5.6.2", + "cross-spawn": "7.0.6", + "diff": "8.0.4", + "glob": "13.0.6", + "grok-mermaid": "0.2.2", + "highlight.js": "10.7.3", + "hosted-git-info": "9.0.3", + "ignore": "7.0.5", + "jiti": "2.7.0", + "minimatch": "10.2.5", + "proper-lockfile": "4.1.2", + "semver": "7.8.0", + "typebox": "1.3.7", + "undici": "8.9.0", + "yaml": "2.9.0" + }, + "bin": { + "pi": "dist/cli.js" + }, + "engines": { + "node": ">=22.19.0" + }, + "optionalDependencies": { + "@mariozechner/clipboard": "0.3.9" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@anthropic-ai/sdk": { + "version": "0.91.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", + "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1048.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1048.0.tgz", + "integrity": "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/credential-provider-node": "^3.972.42", + "@aws-sdk/eventstream-handler-node": "^3.972.16", + "@aws-sdk/middleware-eventstream": "^3.972.12", + "@aws-sdk/middleware-websocket": "^3.972.19", + "@aws-sdk/token-providers": "3.1048.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/node-http-handler": "^4.7.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/core": { + "version": "3.974.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.11.tgz", + "integrity": "sha512-QpnINq5FZH6EOaDEkmHdT7eUunbvD27pDNQypaWjFyYz7Zl1q3UCMQErBZxpmfGfI7MvI2TlK8KTkgNpv8b1ug==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/xml-builder": "^3.972.24", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/core": "^3.24.2", + "@smithy/signature-v4": "^5.4.2", + "@smithy/types": "^4.14.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.37.tgz", + "integrity": "sha512-/jpPvEh6f7ntmIzf7dNxoNX6Q8vt8UpesCjbW6mFfk4V1NW6bIy9qxcQ6WbA8As5yQhsZOe+xeNd4xHX8kdY2Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.39", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.39.tgz", + "integrity": "sha512-pIgTpisWyWg7X1bUbzSjuUYosYTD0Ghz2M0hkSTmb3a6i3qV3uU+NYJPI/E2XSC0HcsZh5rsLPzeXrkb2DS0Cg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/node-http-handler": "^4.7.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.41.tgz", + "integrity": "sha512-u2tyjaxJJzW8UtW4SM1ZcPMDwO6y+kV+llvou+Adts0FAKyzes5jG4izQN+KX3yE8ZROpS5y1LJ//xL2iSf76w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/credential-provider-env": "^3.972.37", + "@aws-sdk/credential-provider-http": "^3.972.39", + "@aws-sdk/credential-provider-login": "^3.972.41", + "@aws-sdk/credential-provider-process": "^3.972.37", + "@aws-sdk/credential-provider-sso": "^3.972.41", + "@aws-sdk/credential-provider-web-identity": "^3.972.41", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/credential-provider-imds": "^4.3.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.41.tgz", + "integrity": "sha512-0LBitxXiAiaE5nlFPfpNIww/8FRY/I7WIndWsc9GmNFOM7cE1wNpVNQEGEk9Outg5l8xl+3vybxFyUy4l9q/LQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.42", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.42.tgz", + "integrity": "sha512-D4oon2zbqqsWOJUM99Gm3/ZyJ0IJvTXVN3PyloGb3kQEyI36fjCZheZj422lAgTWWd6TSHgiImLt3RIaLdv3dQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.37", + "@aws-sdk/credential-provider-http": "^3.972.39", + "@aws-sdk/credential-provider-ini": "^3.972.41", + "@aws-sdk/credential-provider-process": "^3.972.37", + "@aws-sdk/credential-provider-sso": "^3.972.41", + "@aws-sdk/credential-provider-web-identity": "^3.972.41", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/credential-provider-imds": "^4.3.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.37.tgz", + "integrity": "sha512-7nVaHBUaWIddASYfVaA9O4D5ZVjewU3sCol9WqZPGfW0nR+0WqE0xHZnD/U2L33PlOB8KNXGKZ6wOES/QijKzg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.41.tgz", + "integrity": "sha512-IOWAWEHe5LkjSKkkUUX9ciV6Y1scHTsnfEkdt5yyC4Slrc7AGbkLPrpntjqh18ksJAMOaVhoBsO8p2WyTcY2wQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/token-providers": "3.1048.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.41.tgz", + "integrity": "sha512-mbACk9Yypa8nm4iGZLs0PofOXEcTDOUw6wDnsPXNDNSd2WNXs1tSo+6nc/fh0jLYdfVZThhBL98PHW4aXFsG5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.16.tgz", + "integrity": "sha512-yedpPgKftqjU5SlPFHfqWpOw6xSCRieWRG1euWOlXn4WJxt2VX92VprCa2PpSOXjVCAeK6dTjW9eJRXVig9yGA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.12", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.12.tgz", + "integrity": "sha512-tHTHHCHNrq6XklQvlzHBDJG4Iuhh7NVPRdtmvP+nHFA+5sxPlIDzlAHHgfoYHGvT3NXP1yVP/L5c3opUn6T3Qg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.19.tgz", + "integrity": "sha512-mkEhOGYozqKQkbFaVrjwr0faiwwZza1v5/jSY6Tucm3bD+uKTazIUH/4Yo6aMnQD2ua2W9cMP6s8mvwTcjtqHw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/signature-v4": "^5.4.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/nested-clients": { + "version": "3.997.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.9.tgz", + "integrity": "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/signature-v4-multi-region": "^3.996.27", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/node-http-handler": "^4.7.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.27.tgz", + "integrity": "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/signature-v4": "^5.4.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/token-providers": { + "version": "3.1048.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1048.0.tgz", + "integrity": "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/types": { + "version": "3.973.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", + "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/xml-builder": { + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.24.tgz", + "integrity": "sha512-V8z5YcDPfsvzrBlj0xR1vhRtocblhYbqdreCJB/voGd4Sr5zjNAeWxexbnqVtskTJe0vFb5KMqbSL++ePl+zRw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@nodable/entities": "2.1.0", + "@smithy/types": "^4.14.1", + "fast-xml-parser": "5.7.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": { + "version": "0.84.2", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.84.2.tgz", + "dev": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-ai": "^0.84.2", + "@earendil-works/pi-telemetry": "^0.84.2", + "diff": "8.0.4", + "ignore": "7.0.5", + "typebox": "1.3.7", + "yaml": "2.9.0" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": { + "version": "0.84.2", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.84.2.tgz", + "dev": true, + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "0.91.1", + "@aws-sdk/client-bedrock-runtime": "3.1048.0", + "@earendil-works/pi-telemetry": "^0.84.2", + "@google/genai": "1.52.0", + "@opentelemetry/api": "1.9.0", + "@smithy/node-http-handler": "4.7.3", + "http-proxy-agent": "7.0.2", + "https-proxy-agent": "7.0.6", + "openai": "6.40.0", + "partial-json": "0.1.7", + "typebox": "1.3.7" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-client": { + "version": "0.84.2", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-client/-/pi-client-0.84.2.tgz", + "dev": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-protocol": "^0.84.2" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-protocol": { + "version": "0.84.2", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-protocol/-/pi-protocol-0.84.2.tgz", + "dev": true, + "license": "MIT", + "dependencies": { + "typebox": "1.3.7" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-telemetry": { + "version": "0.84.2", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-telemetry/-/pi-telemetry-0.84.2.tgz", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": { + "version": "0.84.2", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.84.2.tgz", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "1.6.0", + "marked": "18.0.5" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@google/genai": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", + "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.9.tgz", + "integrity": "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@mariozechner/clipboard-darwin-arm64": "0.3.9", + "@mariozechner/clipboard-darwin-universal": "0.3.9", + "@mariozechner/clipboard-darwin-x64": "0.3.9", + "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", + "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-x64-musl": "0.3.9", + "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", + "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-arm64": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.9.tgz", + "integrity": "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-universal": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.9.tgz", + "integrity": "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-x64": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.9.tgz", + "integrity": "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.9.tgz", + "integrity": "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-musl": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.9.tgz", + "integrity": "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.9.tgz", + "integrity": "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.9.tgz", + "integrity": "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-musl": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.9.tgz", + "integrity": "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-arm64-msvc": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.9.tgz", + "integrity": "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-x64-msvc": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.9.tgz", + "integrity": "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/core": { + "version": "3.24.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz", + "integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/credential-provider-imds": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.3.tgz", + "integrity": "sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/fetch-http-handler": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.3.tgz", + "integrity": "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/node-http-handler": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz", + "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/signature-v4": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.3.tgz", + "integrity": "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/types": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", + "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-builder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-parser": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", + "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.7", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/grok-mermaid": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/grok-mermaid/-/grok-mermaid-0.2.2.tgz", + "integrity": "sha512-XcJEP5dDC8liHBh52mlLjU18fNvu1ckFsu0QpIG3+APZ270fsj9wxpiA6cOURmbUEuoMVgjbC2+UYgTdCqqgzA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/hosted-git-info": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/lru-cache": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz", + "integrity": "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/marked": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/openai": { + "version": "6.40.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.40.0.tgz", + "integrity": "sha512-MWtTjd/gQt4jpbji61NTgFWJLoY/PdRJ6wG9/ZDRMYNMlBKrCrSlkLI+KgHP1vR1qT6LKSAyAqIxno6lcK9JiA==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry/node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/protobufjs": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/strnum": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", + "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/typebox": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.7.tgz", + "integrity": "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/undici": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz", + "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/tools/typecheck/pi/package.json b/tools/typecheck/pi/package.json new file mode 100644 index 000000000..3479ba4bc --- /dev/null +++ b/tools/typecheck/pi/package.json @@ -0,0 +1,13 @@ +{ + "name": "ctx-pi-extension-typecheck", + "private": true, + "description": "Type-check gate for the embedded Pi extension at internal/assets/integrations/pi/extension/ctx.ts. Not published; not embedded; CI-only dev tooling.", + "scripts": { + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@earendil-works/pi-coding-agent": "^0.84.2", + "@types/node": "^20.0.0", + "typescript": "^5.6.0" + } +} diff --git a/tools/typecheck/pi/tsconfig.json b/tools/typecheck/pi/tsconfig.json new file mode 100644 index 000000000..883f74a14 --- /dev/null +++ b/tools/typecheck/pi/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ESNext"], + "types": ["node"], + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "isolatedModules": true, + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "baseUrl": ".", + "typeRoots": ["./node_modules/@types", "./node_modules"] + }, + "include": [ + "../../../internal/assets/integrations/pi/extension/**/*.ts" + ] +} diff --git a/zensical.toml b/zensical.toml index a95aa4e96..d5c27e1f8 100644 --- a/zensical.toml +++ b/zensical.toml @@ -40,6 +40,7 @@ nav = [ { "Get Started" = [ "home/getting-started.md", "home/opencode.md", + "home/pi.md", "home/vscode.md", "home/first-session.md", "home/common-workflows.md",