diff --git a/README.md b/README.md index b91cdc01..823df637 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ # Aether Agent +image **A terminal coding agent built around verifiable results and portable work.** diff --git a/assets/aether-agent-hero.png b/assets/aether-agent-hero.png new file mode 100644 index 00000000..f79ec52a Binary files /dev/null and b/assets/aether-agent-hero.png differ diff --git a/docs/releases/OPERATOR-PACKET-v0.3.0.md b/docs/releases/OPERATOR-PACKET-v0.3.0.md index 48514404..32d32a33 100644 --- a/docs/releases/OPERATOR-PACKET-v0.3.0.md +++ b/docs/releases/OPERATOR-PACKET-v0.3.0.md @@ -18,7 +18,7 @@ created or changed by those probes. | Historical candidate | `fb96ee44b03f37a386954a32412728fa7e98a046` (PR-local evidence commit; historical only, not reachable from current `main`) | | Historical archive | `aether-agents-0.3.0.tgz` — 739,977 bytes packed / 3,022,168 unpacked / 575 entries | | Historical archive sha256 | `70a48aca8baa8b63f551980256eafa42531cd22fc5ca1146829d31f8b4bd2e4d` | -| Current exact-head dry run | 672 entries / 4 workflows | +| Current exact-head dry run | 673 entries / 4 workflows | | GitHub-hosted Ubuntu value | 3,688,966 unpacked bytes — exact-head CI run `33160594500` passed | | GitHub-hosted Windows value | 3,690,927 unpacked bytes — exact-head CI run `33160594500` passed | | Current local Windows default-checkout measurement | 3,690,927 unpacked bytes / 836,234 predicted packed bytes | @@ -279,19 +279,20 @@ catch a dropped feature, because it does not know what the notes promised. ### Current dry-run packaged file manifest -The exact-head candidate dry runs reported 672 entries on clean Linux/LF and +The exact-head candidate dry runs reported 673 entries on clean Linux/LF and Windows/default checkouts. Exact-head CI confirmed their 3,688,966 and 3,690,927 unpacked-byte results on the corresponding hosted platforms; local default and LF checkouts measured 3,690,927 and 3,688,966 respectively because byte totals can move with checkout line -endings and toolchain metadata. Five files are at the package root, four -generated public documents are under `docs/`, and everything else is under -`dist/src/`. The publication-base candidate produced the archive recorded at +endings and toolchain metadata. Five files are at the package root, one owned +asset is under `assets/`, four generated public documents are under `docs/`, +and everything else is under `dist/src/`. The publication-base candidate produced the archive recorded at the top of this packet from exactly this membership. | Path | Entries | |---|---:| | `COMMANDS.md`, `LICENSE`, `NOTICE.md`, `README.md`, `package.json` | 5 | +| `assets/**` | 1 | | `docs/generated/**`, `docs/model-catalogue/**` | 4 | | `dist/src/core/**` | 378 | | `dist/src/ui/**` | 117 | @@ -301,7 +302,7 @@ the top of this packet from exactly this membership. | `dist/src/{index,main,types,version}.*` | 12 | By extension: 215 `.js`, 215 `.d.ts`, 215 `.js.map`, 14 `.json`, 11 `.md`, 1 -`.html`, 1 extensionless. Source maps ship, as they did in 0.1.0; that is +`.html`, 1 `.png`, 1 extensionless. Source maps ship, as they did in 0.1.0; that is existing policy, unchanged by this release. No compiled tests, no `.env`, no `.tgz`, no `dist/scripts`. `verify-production` diff --git a/package.json b/package.json index 44e13944..fa945314 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "files": [ "dist/src", "README.md", + "assets/aether-agent-hero.png", "COMMANDS.md", "docs/generated/commands.md", "docs/generated/model-catalogue.md", diff --git a/scripts/verify-production.ts b/scripts/verify-production.ts index b7c1bace..99f3199e 100644 --- a/scripts/verify-production.ts +++ b/scripts/verify-production.ts @@ -49,6 +49,8 @@ export const REQUIRED_GENERATED_DOCS = [ "docs/model-catalogue/index.html", ] as const; const ALLOWED_GENERATED_DOCS = new Set(REQUIRED_GENERATED_DOCS); +const REQUIRED_PUBLIC_ASSETS = ["assets/aether-agent-hero.png"] as const; +const ALLOWED_PUBLIC_ASSETS = new Set(REQUIRED_PUBLIC_ASSETS); const MAX_UNPACKED_BYTES = 5_000_000; @@ -84,6 +86,7 @@ export function validateManifest(manifest: PackageManifest, expectedTag?: string const files = Array.isArray(manifest.files) ? manifest.files : []; if (!files.includes("dist/src")) errors.push("package files must include dist/src"); for (const path of REQUIRED_GENERATED_DOCS) if (!files.includes(path)) errors.push(`package files must include generated public document ${path}`); + for (const path of REQUIRED_PUBLIC_ASSETS) if (!files.includes(path)) errors.push(`package files must include public asset ${path}`); if (files.includes("dist") || files.some((value) => typeof value === "string" && value.includes("test"))) { errors.push("package files must not include compiled tests"); } @@ -104,6 +107,9 @@ export function validatePack(report: PackReport, manifest: PackageManifest): str for (const required of REQUIRED_GENERATED_DOCS) { if (!paths.has(required)) errors.push(`package is missing generated public document ${required}`); } + for (const required of REQUIRED_PUBLIC_ASSETS) { + if (!paths.has(required)) errors.push(`package is missing public asset ${required}`); + } for (const required of [manifest.main, manifest.types]) { if (typeof required === "string" && !paths.has(required.replace(/^\.\//, ""))) { errors.push(`package is missing entrypoint ${required}`); @@ -117,7 +123,7 @@ export function validatePack(report: PackReport, manifest: PackageManifest): str } for (const path of paths) { - const allowed = REQUIRED_ROOT_FILES.has(path) || ALLOWED_GENERATED_DOCS.has(path) || path.startsWith("dist/src/"); + const allowed = REQUIRED_ROOT_FILES.has(path) || ALLOWED_GENERATED_DOCS.has(path) || ALLOWED_PUBLIC_ASSETS.has(path) || path.startsWith("dist/src/"); if (!allowed) errors.push(`unexpected package content: ${path}`); if (/(^|\/)(test|tests|_loopstate)(\/|$)/i.test(path) || /(^|\/)\.env(?:\.|$)/i.test(path)) { errors.push(`sensitive or non-runtime package content: ${path}`); diff --git a/test/production_hardening.test.ts b/test/production_hardening.test.ts index cd0d8f9b..7c71ae4f 100644 --- a/test/production_hardening.test.ts +++ b/test/production_hardening.test.ts @@ -19,7 +19,7 @@ const manifest = { types: "dist/src/index.d.ts", bin: { aether: "dist/src/main.js" }, files: [ - "dist/src", "README.md", "COMMANDS.md", "LICENSE", "NOTICE.md", + "dist/src", "README.md", "assets/aether-agent-hero.png", "COMMANDS.md", "LICENSE", "NOTICE.md", "docs/generated/commands.md", "docs/generated/model-catalogue.md", "docs/model-catalogue/catalogue.json", "docs/model-catalogue/index.html", ], @@ -40,6 +40,7 @@ const pack: PackReport = { "LICENSE", "NOTICE.md", "README.md", + "assets/aether-agent-hero.png", "package.json", "docs/generated/commands.md", "docs/generated/model-catalogue.md", diff --git a/test/public_docs_truth.test.ts b/test/public_docs_truth.test.ts index 97ae44ca..986b61cb 100644 --- a/test/public_docs_truth.test.ts +++ b/test/public_docs_truth.test.ts @@ -43,6 +43,10 @@ test("README has durable assets and canonical public repository URLs", () => { assert.doesNotMatch(readme, /github\.com\/AetherAI3\/aether-agent-cli/i); assert.match(readme, /git clone https:\/\/github\.com\/AetherAI3\/aether-agent\.git/); assert.match(readme, /https:\/\/www\.npmjs\.com\/package\/aether-agents/); + const packageManifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { files?: unknown }; + assert.ok(Array.isArray(packageManifest.files)); + assert.ok(packageManifest.files.includes("assets/aether-agent-hero.png")); + assert.ok(existsSync(join(root, "assets", "aether-agent-hero.png"))); for (const match of readme.matchAll(/!?\[[^\]]*\]\(([^)]+)\)/g)) { const target = match[1]!; diff --git a/test/release_coherence.test.ts b/test/release_coherence.test.ts index 2b9715b2..9776ddd3 100644 --- a/test/release_coherence.test.ts +++ b/test/release_coherence.test.ts @@ -266,6 +266,7 @@ test( const count = (predicate: (path: string) => boolean): number => paths.filter(predicate).length; const groups = [ ["`COMMANDS.md`, `LICENSE`, `NOTICE.md`, `README.md`, `package.json`", count((path) => !path.includes("/"))], + ["`assets/**`", count((path) => path.startsWith("assets/"))], ["`docs/generated/**`, `docs/model-catalogue/**`", count((path) => path.startsWith("docs/"))], ["`dist/src/core/**`", count((path) => path.startsWith("dist/src/core/"))], ["`dist/src/ui/**`", count((path) => path.startsWith("dist/src/ui/"))], @@ -296,6 +297,7 @@ test( json: count((path) => path.endsWith(".json")), md: count((path) => path.endsWith(".md")), html: count((path) => path.endsWith(".html")), + png: count((path) => path.endsWith(".png")), extensionless: count((path) => !/\.[^/]+$/.test(path)), }; const normalizedPacket = packet.replace(/\s+/g, " "); @@ -304,6 +306,7 @@ test( `By extension: ${extensionCounts.js} \`.js\`, ${extensionCounts.dts} \`.d.ts\`, ` + `${extensionCounts.map} \`.js.map\`, ${extensionCounts.json} \`.json\`, ` + `${extensionCounts.md} \`.md\`, ${extensionCounts.html} \`.html\`, ` + + `${extensionCounts.png} \`.png\`, ` + `${extensionCounts.extensionless} extensionless.`, ), "the packet has a stale extension manifest",