diff --git a/README.md b/README.md
index b91cdc01..823df637 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
# Aether Agent
+
**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",