You are implementing the initial release of
@sionic-ai/pi-justbash-sandbox (repo: sionic-ai/pi-justbash-sandbox) from
scratch. Scaffolding + implementation + CI must all be produced in this repo
as atomic, reviewable commits. No scaffolding-from-a-tool magic: hand-write
and verify every file.
The end-to-end problem space is documented in docs/RESEARCH.md. Read it
first. It contains verified facts about the upstream APIs (pi-mono,
just-bash). Do not re-research; only follow up if the docs are incomplete.
- Language: TypeScript,
"strict": true,exactOptionalPropertyTypes,noUncheckedIndexedAccess,verbatimModuleSyntax,moduleResolution: bundler. - Type-checker:
@typescript/native-preview(tsgo), invoked viatsgo -p tsconfig.build.jsonfor builds andtsgo --noEmitfor CI type-check. Keep atsconfig.jsonthat extends the build config for editor tooling. - Linter / formatter: Biome (
@biomejs/biome). Config inbiome.json.lint,format, andcheckscripts must exist. - Test runner: Vitest (matches pi-mono upstream). Required suites: unit + integration.
- Dual runtime support for local dev: pnpm + bun must both work for
install,test,build,lint. CI must exercise both. - Strict TDD: no production code is added in the same commit as its first
failing test. Sequence per feature:
- commit: "test(x): add failing test"
- commit: "feat(x): make it pass"
- commit: "refactor(x): ..." (optional)
- Atomic commits — one logical change per commit, conventional-commit style
(
feat:,fix:,test:,chore:,docs:,refactor:,build:,ci:). - Fully documented: every exported symbol has a TSDoc block.
README.mdhas install + pi config + usage sections.docs/ARCHITECTURE.mdmirrors the architecture section ofRESEARCH.mdfor future readers. - Never downgrade TypeScript, Biome, tsgo, or Vitest without a documented reason in the commit body.
- Do not invoke
pibinaries or network services from tests. All tests run hermetically in Node (or Bun) with the extension loaded viaDefaultResourceLoader({ extensionFactories: [...] })from@earendil-works/pi-coding-agent.
pi-justbash-sandbox/
├── .github/workflows/ci.yml
├── .gitignore
├── .editorconfig
├── biome.json
├── package.json
├── pnpm-workspace.yaml # (even single package — keeps pnpm happy)
├── tsconfig.json
├── tsconfig.build.json
├── vitest.config.ts
├── README.md
├── LICENSE
├── docs/
│ ├── RESEARCH.md # already present
│ ├── AGENT_BRIEF.md # already present
│ └── ARCHITECTURE.md
├── src/
│ ├── index.ts # default export = ExtensionAPI factory
│ ├── session/
│ │ ├── sandbox-session.ts # SandboxSession class
│ │ ├── session-registry.ts # per-pi-session map
│ │ └── orphan-reaper.ts
│ ├── fs/
│ │ └── create-sandbox-fs.ts # ReadWriteFs wrapper + defaults
│ ├── adapters/
│ │ ├── bash-adapter.ts # BashOperations impl
│ │ ├── read-adapter.ts # ReadOperations impl
│ │ ├── write-adapter.ts # WriteOperations impl
│ │ └── edit-adapter.ts
│ ├── tools/
│ │ └── register-tools.ts # binds adapters via pi-mono factories
│ └── config/
│ ├── options.ts # zod or typebox schema for flags
│ └── defaults.ts
└── test/
├── unit/
│ ├── sandbox-session.test.ts
│ ├── adapters.bash.test.ts
│ ├── adapters.read.test.ts
│ ├── adapters.write.test.ts
│ ├── adapters.edit.test.ts
│ └── orphan-reaper.test.ts
└── integration/
├── extension-bootstrap.test.ts
└── tool-call-through-pi.test.ts
Each bullet = one commit unless otherwise stated.
chore(git): add .gitignore, .editorconfig, LICENSE (MIT)build(package): add package.json with scripts (pnpm+bun)build(ts): add tsconfig.json + tsconfig.build.json + tsgo in devDepsbuild(biome): add biome.json + scriptstest(vitest): add vitest.config.ts + smoke testci: add GitHub Actions (pnpm + bun matrix, lint + typecheck + test + build)docs: seed README.md + docs/ARCHITECTURE.md stub
After Phase A, pnpm lint, pnpm typecheck, pnpm test, pnpm build, and
the same commands under bun all succeed with an empty src/index.ts.
For each slice below, emit the failing test commit first, then the impl commit.
test(session): SandboxSession creates isolated root under base dirfeat(session): implement SandboxSession with ensure/cleanuptest(session): cleanup removes root + nested filesfeat(session): harden cleanup (retries, symlink-safe)test(registry): per-pi-session registry maps sessionId → SandboxSessionfeat(registry): implement session-registrytest(reaper): removes stale dirs older than TTL; leaves fresh onesfeat(reaper): implement orphan-reaper
test(adapter.bash): exec echo via just-bash yields stdout + exit 0feat(adapter.bash): implement BashAdapter on top of just-bash Bashtest(adapter.bash): stderr and non-zero exit code propagatefeat(adapter.bash): wire stderr streaming + exitCodetest(adapter.bash): timeout aborts long-running commandfeat(adapter.bash): respect ExecOptions timeout + signaltest(adapter.read): reads UTF-8 file through ReadWriteFsfeat(adapter.read): implement ReadAdaptertest(adapter.read): image MIME detection for small binary filesfeat(adapter.read): add detectImageMimeType supporttest(adapter.write): writes create parent dirs and persist contentfeat(adapter.write): implement WriteAdaptertest(adapter.edit): read-then-write via edit tool worksfeat(adapter.edit): implement EditAdapter
test(tools): extension registers replacement bash/read/write/editfeat(tools): implement register-tools
test(lifecycle): session_start creates a new sandbox rootfeat(lifecycle): bind session_start handlertest(lifecycle): session_shutdown cleans up sandbox rootfeat(lifecycle): bind session_shutdown handlertest(lifecycle): session_before_switch / fork triggers appropriate cleanup or copyfeat(lifecycle): implement switch + fork cleanuptest(lifecycle): SIGINT/SIGTERM cleans all registered sessionsfeat(lifecycle): install signal handlers
test(integration): extension boots via DefaultResourceLoader + factoryfeat(ext): export default factory in src/index.tstest(integration): pi-driven tool_call for bash/read/write runs inside sandboxfeat(integration): harness test that drives a mock AgentSessiondocs: finalize README with install + pi config snippets + troubleshootingdocs: finalize docs/ARCHITECTURE.md
chore: add CHANGELOG.md + 0.0.0 entryci: cache pnpm + bun; add concurrency groupchore(release): prepare publish-ready package.json (exports, files, types)
If any step turns out to be impossible as described, stop and document
the blocker in docs/OPEN_QUESTIONS.md with the exact upstream symbol you
tried to reach for; do not silently skip it.
Required scripts (use these exact names):
Runtime deps:
just-bash(pin^3.0.1at first; document in README if bumped).@earendil-works/pi-coding-agent— pin a recent version (0.75.1+) in peerDependencies. Declare bothdependenciesentry (for local dev) and peerDependencies entry.
Dev deps must include: @biomejs/biome, @typescript/native-preview (tsgo),
typescript (latest stable, used only for editor language service),
vitest, @vitest/coverage-v8, @types/node.
engines: { "node": ">=22.19.0", "pnpm": ">=9", "bun": ">=1.1" }.
Publish settings: type: "module", ESM only, exports map with
import/types, files: ["dist"], publishConfig.access: "public".
- Trigger: pushes to
main+ all PRs. - Matrix:
{ runner: [ubuntu-latest, macos-latest], pkg: [pnpm, bun] } - Jobs:
lint,typecheck,test,build— all four must pass. concurrency: { group: ${{ github.ref }}, cancel-in-progress: true }.- Cache pnpm store and bun's
~/.bun/install/cache. actions/checkout@v4,actions/setup-node@v4(node 20),pnpm/action-setuppinned,oven-sh/setup-bun@v2pinned.- No network access beyond the package registry in tests (block via
NETWORK_ACCESS=denyif you introduce a test proxy; otherwise rely on offline-friendly unit tests).
- One file-pattern per commit when reasonable (e.g. don't lump
package.json- src in one commit unless they are inseparable).
- No
WIP, nofix: typoat the end of a feature commit — squash locally before pushing. - Commit messages: conventional commits, ≤72-char subject, body explains
why, references the phase (
Phase B.2), and lists manual verification if any (e.g.Verified: pnpm test && bun test).
mainhas Phases A–F merged.pnpm lint && pnpm typecheck && pnpm test && pnpm buildgreen.bun run lint && bun run typecheck && bun run test && bun run buildgreen.- GitHub Actions green on
main. - README install + usage snippet copy-paste works against a pi-mono
sandbox project (verify by writing a smoke script under
examples/). - No
TODOmarkers in shipped source (search forTODO:insrc/).
Ping the owner in the PR description once A–F are done; Phase G can follow in a separate PR. Do not self-merge.
{ "scripts": { "build": "tsgo -p tsconfig.build.json", "dev": "tsgo -p tsconfig.build.json --watch", "typecheck": "tsgo --noEmit", "lint": "biome check .", "lint:fix": "biome check --write .", "format": "biome format --write .", "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", "clean": "rm -rf dist coverage" } }