Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

### Changed

- Peer dependencies on `@earendil-works/pi-*` are `>=0.87.1` instead of `*`. 0.87.1 is the oldest pi the extension is verified against: typecheck and the full test suite pass with every `@earendil-works` package at 0.87.1.
- Declare the four `@earendil-works/pi-*` host packages and `typebox` as `*` peer dependencies supplied by Pi. 0.87.1 is the oldest previously tested host, not an enforced peer version constraint.
- Mark all host peers optional to prevent npm from automatically installing another copy of Pi. Move `typebox` from runtime dependencies to an exact 1.3.36 development dependency; `diff` is the sole runtime dependency.
- CI: the Dependabot `bun.lock` refresh approves the CI run its own push starts, so the required checks attach to the pull request, and falls back to dispatching `ci.yml` if that run cannot be approved.
- CI: the publish workflow no longer has an npm publish step; the package is distributed via git. A release now fails when its tag does not match `package.json`.

Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,17 @@ senpi install git:github.com/code-yeongyu/pi-apply-patch

# 2. Manual placement
git clone https://github.com/code-yeongyu/pi-apply-patch ~/.pi/agent/extensions/pi-apply-patch
cd ~/.pi/agent/extensions/pi-apply-patch && npm install
cd ~/.pi/agent/extensions/pi-apply-patch
npm install --omit=dev --legacy-peer-deps

# 3. Dev / one-shot test
pi -e /path/to/pi-apply-patch/src/index.ts
```

Pi supplies `@earendil-works/pi-agent-core`, `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, `@earendil-works/pi-tui`, and `typebox` at runtime. These host modules are declared as optional peers with `*` ranges so npm does not install another copy of Pi. The extension installs only `diff` as a runtime dependency.

0.87.1 is the oldest previously tested Pi host, not an enforced peer version constraint. The `*` ranges defer module provision to Pi; they do not guarantee compatibility with future breaking API changes.

After installation, restart pi or run `/reload` inside an interactive session.

## Development
Expand All @@ -69,7 +74,7 @@ npm pack --dry-run
pi -e ./src/index.ts
```

Development and CI use Bun 1.4.2. `package-lock.json` is kept alongside `bun.lock` so `npm ci && npm test` keeps working for npm consumers.
Development and CI use Bun 1.4.2 and pinned SDK development dependencies: the four `@earendil-works/pi-*` packages at 1.0.4 and `typebox` at 1.3.36. `package-lock.json` is kept alongside `bun.lock` so `npm ci && npm test` keeps working for npm consumers.

The test suite uses vitest. TypeScript is strict, Node-only, and uses ESM imports with `.js` suffixes.

Expand Down
18 changes: 13 additions & 5 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 25 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 24 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,28 @@
"check": "tsgo --noEmit && biome check ."
},
"peerDependencies": {
"@earendil-works/pi-agent-core": ">=0.87.1",
"@earendil-works/pi-ai": ">=0.87.1",
"@earendil-works/pi-coding-agent": ">=0.87.1",
"@earendil-works/pi-tui": ">=0.87.1"
"@earendil-works/pi-agent-core": "*",
"@earendil-works/pi-ai": "*",
"@earendil-works/pi-coding-agent": "*",
"@earendil-works/pi-tui": "*",
"typebox": "*"
},
"peerDependenciesMeta": {
"@earendil-works/pi-agent-core": {
"optional": true
},
"@earendil-works/pi-ai": {
"optional": true
},
"@earendil-works/pi-coding-agent": {
"optional": true
},
"@earendil-works/pi-tui": {
"optional": true
},
"typebox": {
"optional": true
}
},
"devDependencies": {
"@biomejs/biome": "2.5.15",
Expand All @@ -56,14 +74,14 @@
"@earendil-works/pi-tui": "1.0.4",
"@types/node": "26.6.4",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"typebox": "1.3.36",
"typescript": "7.0.2",
"vitest": "5.0.3"
},
"engines": {
"node": ">=22.19.0"
},
"dependencies": {
"diff": "^9.0.0",
"typebox": "^1.3.36"
"diff": "^9.0.0"
}
}
83 changes: 83 additions & 0 deletions test/package-manifest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";

type DependencyMap = Record<string, string>;
type BundledDependencies = string[] | boolean;
interface PackageManifest {
dependencies?: DependencyMap;
optionalDependencies?: DependencyMap;
devDependencies?: DependencyMap;
peerDependencies?: DependencyMap;
peerDependenciesMeta?: Record<string, { optional?: boolean }>;
bundledDependencies?: BundledDependencies;
bundleDependencies?: BundledDependencies;
}

const manifest: PackageManifest = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
const lockfile: { packages: Record<string, PackageManifest> } = JSON.parse(
readFileSync(new URL("../package-lock.json", import.meta.url), "utf8"),
);
const hostPackages = [
"@earendil-works/pi-agent-core",
"@earendil-works/pi-ai",
"@earendil-works/pi-coding-agent",
"@earendil-works/pi-tui",
"typebox",
];
const dependencySections = [
"dependencies",
"optionalDependencies",
"devDependencies",
"peerDependencies",
"peerDependenciesMeta",
"bundledDependencies",
"bundleDependencies",
] as const;

function bundledPackages(bundle: BundledDependencies | undefined): string[] {
if (bundle === true) {
return Object.keys({ ...manifest.dependencies, ...manifest.optionalDependencies });
}
return Array.isArray(bundle) ? bundle : [];
}

describe("package manifest host dependencies", () => {
it.each(hostPackages)("#given host package %s #when declaring peers #then its range is *", (hostPackage) => {
expect(manifest.peerDependencies?.[hostPackage]).toBe("*");
});

it.each(hostPackages)("#given host package %s #when npm resolves peers #then it is optional", (hostPackage) => {
expect(manifest.peerDependenciesMeta?.[hostPackage]?.optional).toBe(true);
});

it.each(hostPackages)(
"#given host package %s #when installing production dependencies #then it is absent from runtime sections and bundles",
(hostPackage) => {
expect(manifest.dependencies ?? {}).not.toHaveProperty(hostPackage);
expect(manifest.optionalDependencies ?? {}).not.toHaveProperty(hostPackage);
expect(bundledPackages(manifest.bundledDependencies)).not.toContain(hostPackage);
expect(bundledPackages(manifest.bundleDependencies)).not.toContain(hostPackage);
},
);

it.each(hostPackages)(
"#given host package %s #when developing standalone #then it is a development dependency",
(hostPackage) => {
expect(manifest.devDependencies?.[hostPackage]).toEqual(expect.any(String));
expect(manifest.devDependencies?.[hostPackage]).not.toBe("");
},
);
});

describe("package-lock root dependency declarations", () => {
it("#given the npm lockfile #when reading root metadata #then the package entry exists", () => {
expect(lockfile.packages[""]).toBeDefined();
});

it.each(dependencySections)(
"#given the real manifest #when comparing lockfile root %s #then the declarations match",
(section) => {
expect(lockfile.packages[""]?.[section]).toEqual(manifest[section]);
},
);
});