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
704 changes: 704 additions & 0 deletions .github/workflows/dev-snapshot-release.yml

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ concurrency:

jobs:
release:
if: github.ref == 'refs/heads/main'
name: Version or Publish
runs-on: ubuntu-latest
permissions:
Expand Down Expand Up @@ -133,3 +134,12 @@ jobs:
CAPLETS_RUNTIME_SENTRY_DSN=${{ secrets.CAPLETS_RUNTIME_SENTRY_DSN }}
CAPLETS_SENTRY_RELEASE=caplets-runtime@${{ github.sha }}
CAPLETS_SENTRY_ENVIRONMENT=production
dev_snapshot:
name: Release dev snapshots
needs: release
if: github.ref == 'refs/heads/main'
uses: ./.github/workflows/dev-snapshot-release.yml
permissions:
contents: read
id-token: write
packages: write
8 changes: 8 additions & 0 deletions CONCEPTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,11 @@ Operator Clients can approve or revoke remote clients, administer Caplets and ca
A host-owned record of sensitive Operator Client actions performed through the dashboard or operator admin surfaces.

Operator Activity Log answers what changed, when it changed, and which Operator Client performed the action. It is narrower than a compliance audit system and separate from daemon or protocol logs.

## Release

### Dev Snapshot Channel

A non-production release channel that publishes traceable package snapshots for dogfooding and release confidence before stable package promotion.

Dev Snapshot Channel publishes immutable snapshot versions first, validates each promoted snapshot line with the required proof for that package set — real `caplets` bootstrap for CLI/core lines, exact package-level install or import smoke for non-CLI-only lines — and only then advances the floating development tag. It is separate from stable release versioning, changelog generation, npm `latest`, and production image tags.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
"prepare": "husky",
"release": "turbo build --force && node scripts/check-package-runtime.mjs && changeset publish",
"release:publish": "pnpm telemetry:prepare-release-env && pnpm release",
"dev-snapshot:manifest": "node scripts/dev-snapshot-release.mjs status",
"dev-snapshot:patch-config": "node scripts/dev-snapshot-release.mjs patch-snapshot-config",
"dev-snapshot:rewrite-closure": "node scripts/dev-snapshot-release.mjs rewrite-closure",
"dev-snapshot:bootstrap-check": "node scripts/check-dev-snapshot-bootstrap.mjs plan",
"schema:check": "tsx ./scripts/generate-config-schema.ts --check",
"schema:generate": "tsx ./scripts/generate-config-schema.ts",
"test": "vitest run",
Expand Down
1 change: 1 addition & 0 deletions packages/core/test/attach-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ describe("caplets attach CLI", () => {
runCli(["attach", "--remote-url", "https://caplets.example.com/caplets", "--user", "alice"], {
env: { CAPLETS_MODE: "remote" },
attachServe: async () => undefined,
writeErr: () => undefined,
} as never),
).rejects.toThrow(/unknown option '--user'/u);
});
Expand Down
243 changes: 243 additions & 0 deletions packages/core/test/dev-snapshot-bootstrap.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
assertInstalledSnapshotLine,
buildValidationCommands,
derivePackageOnlyTargets,
createIsolatedValidationEnv,
deriveValidationPlan,
type BootstrapManifest,
} from "../../../scripts/check-dev-snapshot-bootstrap.mjs";

const tempPaths: string[] = [];

afterEach(() => {
for (const path of tempPaths.splice(0)) {
rmSync(path, { recursive: true, force: true });
}
});

function createTempDirectory(prefix: string) {
const path = mkdtempSync(join(tmpdir(), prefix));
tempPaths.push(path);
return path;
}

function writeInstalledPackage(
installRoot: string,
packageName: string,
manifest: Record<string, unknown>,
parentPackageName?: string,
) {
const packageDirectory = parentPackageName
? join(installRoot, "lib", "node_modules", parentPackageName, "node_modules", packageName)
: join(installRoot, "lib", "node_modules", packageName);
mkdirSync(packageDirectory, { recursive: true });
writeFileSync(join(packageDirectory, "package.json"), `${JSON.stringify(manifest, null, 2)}\n`);
}

function fullCliSnapshotManifest(): BootstrapManifest {
return {
validation: {
kind: "cli-bootstrap",
packages: ["caplets", "@caplets/core", "@caplets/pi", "@caplets/opencode"],
},
releases: [
{ name: "caplets", newVersion: "1.0.0-dev-fixture-20260708120000" },
{ name: "@caplets/core", newVersion: "2.0.0-dev-fixture-20260708120000" },
{ name: "@caplets/pi", newVersion: "3.0.0-dev-fixture-20260708120000" },
{ name: "@caplets/opencode", newVersion: "4.0.0-dev-fixture-20260708120000" },
],
};
}

describe("dev snapshot bootstrap helpers", () => {
it("installs every promoted CLI closure package after bootstrapping caplets", () => {
const manifest = fullCliSnapshotManifest();

expect(deriveValidationPlan(manifest)).toEqual({
kind: "cli-bootstrap",
cliPackage: "caplets",
expectedCorePackage: "@caplets/core",
packages: ["caplets", "@caplets/core", "@caplets/pi", "@caplets/opencode"],
});
expect(buildValidationCommands(manifest)).toEqual([
"npm install -g caplets@1.0.0-dev-fixture-20260708120000",
"caplets --version",
"caplets setup mcp-client --output ${INSTALL_ROOT}/caplets.mcp.json --format json",
"npm install -g @caplets/pi@3.0.0-dev-fixture-20260708120000",
"npm install -g @caplets/opencode@4.0.0-dev-fixture-20260708120000",
]);
});

it("propagates peer declarations from a synthetic public package", () => {
const root = createTempDirectory("caplets-dev-snapshot-workspace-");
const packageDirectory = join(root, "packages", "peer-host");
mkdirSync(packageDirectory, { recursive: true });
writeFileSync(
join(packageDirectory, "package.json"),
`${JSON.stringify(
{
name: "@fixture/peer-host",
version: "1.0.0",
publishConfig: { access: "public" },
peerDependencies: { "@fixture/agent-api": ">=2" },
},
null,
2,
)}\n`,
);
const manifest: BootstrapManifest = {
validation: { kind: "package-only", packages: ["@fixture/peer-host"] },
releases: [{ name: "@fixture/peer-host", newVersion: "1.0.1-dev-fixture" }],
};

expect(derivePackageOnlyTargets(manifest, root)).toEqual([
{
name: "@fixture/peer-host",
peerDependencies: ["@fixture/agent-api"],
validationKind: "install-only",
},
]);
expect(buildValidationCommands(manifest)).toEqual([
"npm install -g @fixture/peer-host@1.0.1-dev-fixture",
]);
});

it("accepts npm's shallow global CLI layout with nested core and root closure packages", () => {
const manifest = fullCliSnapshotManifest();
const installRoot = createTempDirectory("caplets-dev-install-root-");
const targets = new Map(
derivePackageOnlyTargets(manifest).map((target) => [target.name, target]),
);
const installedManifest = (packageName: string, version: string) => ({
name: packageName,
version,
peerDependencies: Object.fromEntries(
(targets.get(packageName)?.peerDependencies ?? []).map((peerDependency) => [
peerDependency,
"*",
]),
),
});

writeInstalledPackage(
installRoot,
"caplets",
installedManifest("caplets", "1.0.0-dev-fixture-20260708120000"),
);
writeInstalledPackage(
installRoot,
"@caplets/core",
installedManifest("@caplets/core", "2.0.0-dev-fixture-20260708120000"),
"caplets",
);
writeInstalledPackage(
installRoot,
"@caplets/pi",
installedManifest("@caplets/pi", "3.0.0-dev-fixture-20260708120000"),
);
writeInstalledPackage(
installRoot,
"@caplets/opencode",
installedManifest("@caplets/opencode", "4.0.0-dev-fixture-20260708120000"),
);

expect(assertInstalledSnapshotLine(manifest, installRoot)).toEqual([]);
});

it("rejects CLI manifests that omit an expected caplets or core snapshot version", () => {
const completeManifest = fullCliSnapshotManifest();
for (const missingPackage of ["caplets", "@caplets/core"]) {
const installRoot = createTempDirectory("caplets-dev-install-root-");
writeInstalledPackage(installRoot, "caplets", {
name: "caplets",
version: "1.0.0-dev-fixture-20260708120000",
});
writeInstalledPackage(
installRoot,
"@caplets/core",
{
name: "@caplets/core",
version: "2.0.0-dev-fixture-20260708120000",
},
"caplets",
);
const manifest: BootstrapManifest = {
...completeManifest,
releases: completeManifest.releases.filter((release) => release.name !== missingPackage),
};

const errors = assertInstalledSnapshotLine(manifest, installRoot);
expect(errors.join("\n")).toMatch(
new RegExp(
`${missingPackage.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}.*(?:version|release)|(?:version|release).*${missingPackage.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`,
"i",
),
);
}
});

it("keeps a caller-supplied validation root isolated across npm and user state", () => {
const root = createTempDirectory("caplets-dev-validation-root-");
const isolated = createIsolatedValidationEnv(root);

const isolatedPaths = [
isolated.env.HOME,
isolated.env.USERPROFILE,
isolated.env.npm_config_prefix,
isolated.env.npm_config_cache,
isolated.env.XDG_CONFIG_HOME,
isolated.env.XDG_STATE_HOME,
isolated.env.CAPLETS_CONFIG,
];
expect(
isolatedPaths.every(
(path): path is string => typeof path === "string" && path.startsWith(root),
),
).toBe(true);
expect(isolated.env.NO_COLOR).toBe("1");
});

it("reports the missing locations for globally installed CLI packages", () => {
const installRoot = createTempDirectory("caplets-dev-install-root-");
const errors = assertInstalledSnapshotLine(fullCliSnapshotManifest(), installRoot);

expect(errors).toEqual(
expect.arrayContaining([
expect.stringContaining(
`caplets is not installed at ${join(installRoot, "lib", "node_modules", "caplets", "package.json")}`,
),
expect.stringContaining(
`@caplets/core is not installed at ${join(installRoot, "lib", "node_modules", "caplets", "node_modules", "@caplets", "core", "package.json")}`,
),
]),
);
});

it("reports installed peer hosts that omit the package manifest's peer declarations", () => {
const packageName = "@caplets/pi";
const installRoot = createTempDirectory("caplets-dev-install-root-");
const expectedPeers = Object.keys(
JSON.parse(readFileSync(join(import.meta.dirname, "..", "..", "pi", "package.json"), "utf8"))
.peerDependencies ?? {},
);
const manifest: BootstrapManifest = {
validation: { kind: "package-only", packages: [packageName] },
releases: [{ name: packageName, newVersion: "3.0.0-dev-fixture-20260708120000" }],
};
writeInstalledPackage(installRoot, packageName, {
name: packageName,
version: "3.0.0-dev-fixture-20260708120000",
});

expect(assertInstalledSnapshotLine(manifest, installRoot)).toEqual(
expectedPeers.map(
(peerDependency) =>
`${packageName} is missing peer dependency declaration for ${peerDependency}.`,
),
);
});
Comment on lines +220 to +242

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Guard against vacuous peer-mismatch coverage.

expectedPeers is derived from the real packages/pi/package.json. If @caplets/pi ever loses its peerDependencies, expectedPeers becomes [] and the assertion degrades to [] equals [] — a silent pass with zero coverage for peer mismatch detection. Add a guard to fail loudly instead.

🛡️ Proposed guard
     const expectedPeers = Object.keys(
       JSON.parse(readFileSync(join(import.meta.dirname, "..", "..", "pi", "package.json"), "utf8"))
         .peerDependencies ?? {},
     );
+    expect(expectedPeers.length).toBeGreaterThan(0);
     const manifest: BootstrapManifest = {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("reports installed peer hosts that omit the package manifest's peer declarations", () => {
const packageName = "@caplets/pi";
const installRoot = createTempDirectory("caplets-dev-install-root-");
const expectedPeers = Object.keys(
JSON.parse(readFileSync(join(import.meta.dirname, "..", "..", "pi", "package.json"), "utf8"))
.peerDependencies ?? {},
);
const manifest: BootstrapManifest = {
validation: { kind: "package-only", packages: [packageName] },
releases: [{ name: packageName, newVersion: "3.0.0-dev-fixture-20260708120000" }],
};
writeInstalledPackage(installRoot, packageName, {
name: packageName,
version: "3.0.0-dev-fixture-20260708120000",
});
expect(assertInstalledSnapshotLine(manifest, installRoot)).toEqual(
expectedPeers.map(
(peerDependency) =>
`${packageName} is missing peer dependency declaration for ${peerDependency}.`,
),
);
});
it("reports installed peer hosts that omit the package manifest's peer declarations", () => {
const packageName = "`@caplets/pi`";
const installRoot = createTempDirectory("caplets-dev-install-root-");
const expectedPeers = Object.keys(
JSON.parse(readFileSync(join(import.meta.dirname, "..", "..", "pi", "package.json"), "utf8"))
.peerDependencies ?? {},
);
expect(expectedPeers.length).toBeGreaterThan(0);
const manifest: BootstrapManifest = {
validation: { kind: "package-only", packages: [packageName] },
releases: [{ name: packageName, newVersion: "3.0.0-dev-fixture-20260708120000" }],
};
writeInstalledPackage(installRoot, packageName, {
name: packageName,
version: "3.0.0-dev-fixture-20260708120000",
});
expect(assertInstalledSnapshotLine(manifest, installRoot)).toEqual(
expectedPeers.map(
(peerDependency) =>
`${packageName} is missing peer dependency declaration for ${peerDependency}.`,
),
);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/test/dev-snapshot-bootstrap.test.ts` around lines 220 - 242,
The peer-mismatch test in assertInstalledSnapshotLine can silently pass if the
referenced package manifest has no peerDependencies, because expectedPeers
becomes empty and the assertion compares two empty arrays. Add an explicit guard
in the test setup near the expectedPeers calculation to fail fast when no peer
dependencies are present, so the coverage for `@caplets/pi` peer declaration
validation cannot become vacuous.

});
Loading