Skip to content
Merged
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
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- `src/next-config.ts` owns production-only source-map configuration.
- `src/*.test.ts` and `src/*.property.test.ts` hold deterministic regressions and general laws.
- `scripts/` builds isolated exports and verifies package, bundle, and public boundaries.
- `portfolio-inventory.json` records the package publication boundary in the shared fleet contract.
- `.github/workflows/` runs read-only continuous integration and publishes only a verified immutable release.
- `.agents/skills/` contains the seven portable knowledge and phased-execution workflows.
- `kb/` contains authored repository rationale, maintained synthesis, and implementation plans.
Expand All @@ -25,6 +26,7 @@
- Treat source-map credentials as build-time secrets. Upload only for an exact production deployment with a supported PostHog UI host and a commit release identifier.
- Keep Direct deterministic compositions development-only and outside every production dependency graph and published export.
- Freeze package interfaces before parallel lanes begin. Give exports, manifests, lockfiles, generated output, and other convergence surfaces one owner while lanes edit disjoint paths.
- Keep `portfolio-inventory.json` generated from `package.json`; the checked inventory must match the public package name, version, repository, and Hraness dependency edges exactly.
- Keep mandatory rules in the closest `AGENTS.md`, executable contracts in types and tests, and pull-based rationale, evidence, synthesis, and plans in `kb/`.
- Use Bun 1.3.14 for installs, builds, and tests. Verify every installed export with genuine Node 24.
- Run `bun run check` before release handoff. The release workflow may write only the verified immutable GitHub Release.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,14 @@
"kb:catalog": "bunx --bun github:hraness/kb#v0.15.1 catalog --root kb",
"build": "bun run ./scripts/build.ts",
"check:boundaries": "bun run ./scripts/check-bundle-boundaries.ts",
"check:portfolio-inventory": "bun run ./scripts/check-portfolio-inventory.ts",
"check:public-boundary": "bun run ./scripts/check-public-boundary.ts",
"lint": "eslint .",
"typecheck": "tsc --noEmit",
"test": "bun test ./src",
"test:property": "bun test ./src/*.property.test.ts",
"test:package": "bun run ./scripts/package-smoke.ts",
"check": "bun run check:public-boundary && bun run lint && bun run typecheck && bun run build && bun run check:boundaries && bun run test && bun run test:property && bun run test:package && bun run kb:check",
"check": "bun run check:portfolio-inventory && bun run check:public-boundary && bun run lint && bun run typecheck && bun run build && bun run check:boundaries && bun run test && bun run test:property && bun run test:package && bun run kb:check",
"prepack": "bun run check"
},
"dependencies": {
Expand Down
24 changes: 24 additions & 0 deletions portfolio-inventory.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"contract": "hraness.portfolio-inventory/v1",
"formatVersion": 1,
"repository": "hraness/posthog",
"components": [
{
"kind": "package",
"name": "@hraness/posthog",
"path": ".",
"visibility": "public",
"version": "0.1.0"
}
],
"dependencies": [],
"deployments": [],
"brands": [],
"publications": [
{
"component": "@hraness/posthog",
"packageName": "@hraness/posthog",
"repository": "hraness/posthog"
}
]
}
2 changes: 2 additions & 0 deletions scripts/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
- `check-bundle-boundaries.ts` proves runtime dependency isolation between pure, browser, React, Node, and build-time exports.
- `check-public-boundary.ts` rejects private provenance and credential-like values.
- `package-smoke.ts` installs the packed artifact, imports every built export with Node 24, and checks source types from a consumer.
- `check-portfolio-inventory.ts` derives and verifies the canonical public package inventory.

# Guidelines

- Keep verification deterministic, cross-platform where practical, and free of network writes beyond dependency installation.
- Derive fleet inventory from package metadata; never maintain a second hand-authored dependency graph.
- Verify the packed artifact rather than relying only on source imports.
- Update bundle allowlists only when an intentional public dependency boundary changes.
- Keep release mutation in the release workflow. Ordinary checks remain read-only.
103 changes: 103 additions & 0 deletions scripts/check-portfolio-inventory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";

type DependencyScope = "development" | "optional" | "peer" | "runtime";

function record(value: unknown, label: string): Record<string, unknown> {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
throw new Error(`${label} must be an object`);
}
return value as Record<string, unknown>;
}

function stringField(value: Record<string, unknown>, key: string): string {
const field = value[key];
if (typeof field !== "string" || field.length === 0) {
throw new Error(`package.json ${key} must be a non-empty string`);
}
return field;
}

function repositorySlug(value: unknown): string {
const repository = record(value, "package.json repository");
const url = stringField(repository, "url");
const match = /github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/u.exec(url);
if (match?.[1] === undefined) {
throw new Error(
"package.json repository.url must identify a GitHub repository",
);
}
return match[1];
}

function asciiCompare(left: string, right: string): number {
return left < right ? -1 : left > right ? 1 : 0;
}

const repositoryRoot = resolve(import.meta.dir, "..");
const packageManifest = record(
JSON.parse(
await readFile(resolve(repositoryRoot, "package.json"), "utf8"),
) as unknown,
"package.json",
);
const packageName = stringField(packageManifest, "name");
const version = stringField(packageManifest, "version");
const repository = repositorySlug(packageManifest.repository);
const dependencySections = [
["devDependencies", "development"],
["optionalDependencies", "optional"],
["peerDependencies", "peer"],
["dependencies", "runtime"],
] as const satisfies readonly (readonly [string, DependencyScope])[];
const dependencies = dependencySections.flatMap(([section, scope]) => {
const value = packageManifest[section];
if (value === undefined) return [];
const entries = record(value, `package.json ${section}`);
return Object.entries(entries)
.filter(([name]) => name.startsWith("@hraness/"))
.map(([name, specifier]) => {
if (typeof specifier !== "string" || specifier.length === 0) {
throw new Error(
`package.json ${section}.${name} must be a non-empty string`,
);
}
return { from: packageName, scope, specifier, to: name };
});
}).toSorted((left, right) =>
asciiCompare(left.from, right.from)
|| asciiCompare(left.to, right.to)
|| asciiCompare(left.scope, right.scope)
|| asciiCompare(left.specifier, right.specifier));

const expected = {
contract: "hraness.portfolio-inventory/v1",
formatVersion: 1,
repository,
components: [{
kind: "package",
name: packageName,
path: ".",
visibility: "public",
version,
}],
dependencies,
deployments: [],
brands: [],
publications: [{
component: packageName,
packageName,
repository,
}],
};
const expectedBytes = `${JSON.stringify(expected, null, 2)}\n`;
const actualBytes = await readFile(
resolve(repositoryRoot, "portfolio-inventory.json"),
"utf8",
);

if (actualBytes !== expectedBytes) {
throw new Error(
"portfolio-inventory.json does not match the canonical package inventory",
);
}