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
72 changes: 63 additions & 9 deletions DEVLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,60 @@

> Append-only session log. Read at session start. Update at session end.

## 2026-06-17 — Public landing domain (forge-pm.dev) + docs wired into /docs; public-readiness audit

Picked `forge-pm.dev` as the public landing/marketing domain; the app stays on
the personal `forge.axiom-labs.dev` (env-driven, untouched). Swapped the
landing canonical everywhere it defaulted to `forge.axiom-labs.dev`:
`landing/app/layout.tsx` (metadataBase + OG), `sitemap.ts`, `robots.ts`, the
OG-card domain text in `opengraph-image.tsx`, and the README default. The
placeholder docs grid had advertised `docs.forge.dev` — a domain we don't own
— now removed (see below).

### Docs at forge-pm.dev/docs
Docs are already a full VitePress site (`docs/`, base `/docs/`) that the app
builds via `scripts/build-docs.sh` → `public/docs/`. Rather than build a second
renderer in the landing site, mirrored that into the landing static export:
- New `landing/scripts/build-docs.sh` — builds the SAME `../docs` site and
stages dist → `landing/public/docs/`. POSIX sh; `SKIP_DOCS`/`STAGE_ONLY`
knobs; skips gracefully if `docs/` absent.
- `landing/package.json`: `build` = `./scripts/build-docs.sh && next build`;
added `build:app` (docs-less) + `build:docs`.
- Deleted the placeholder `app/docs/page.tsx` + `components/docs-content.tsx`
(VitePress owns the whole `/docs/` subtree incl. its home page; they'd
collide on `out/docs/index.html`). Nav `Docs → /docs/` was already correct.
- Gitignored `landing/public/docs/` (build artifact).
- No Next rewrite needed (unlike the app server): static hosts auto-serve
`index.html` for `/docs/`, and base `/docs/` makes assets resolve.
Verified `pnpm build` → `out/docs/` carries all 60 pages + assets + sitemap
alongside the landing pages; lint + typecheck green.

### VitePress config (shared by app + landing builds)
- `srcExclude: ["audits/**","plans/**"]` — keep internal execution plans /
audit notes out of the published site (they were unlinked but reachable).
Fixed the one inbound link (`agents/providers-and-transports.md` → the
runtime-adapter ADR) to a GitHub blob URL.
- `sitemap.hostname = forge-pm.dev` + a `transformItems` that injects the
`/docs/` base (VitePress emits page paths without the site base, so raw
output dropped `/docs/`). Emits `/docs/sitemap.xml` with 60 canonical URLs;
landing `robots.ts` now lists both sitemaps.
- `editLink` branch `master → main` (default branch is main; links 404'd).
- Genericized a sample plugin manifest author email (`ops@axiom-labs.dev` →
`you@example.com`).

### Public-readiness audit (repo is already PUBLIC)
Swept tracked files for secrets / internal data:
- CRITICAL: an earlier DEVLOG entry (~L8265, 2026-04-19 section) carried a
plaintext owner-login credential (the `ADMIN_PASSWORD` from
`~/docker/forge/.env`). Redacted at HEAD. Because it sits in already-pushed
PUBLIC history, the credential MUST be rotated; an optional history rewrite
(filter-repo + force-push) to purge it is pending decision.
- Redacted the internal LAN IP (`172.16.24.250`) across DEVLOG →
`<internal-host>`.
- Otherwise clean: no tracked `.env`/key files; remaining password-like hits
are CI/dev defaults (`forge-dev`, `forgeminio-dev-password`); the built
public docs contain zero leaks; README + MIT LICENSE present.

## 2026-06-16 — Agent request run-mode chips and durable issue links

Implemented AXI-81's first-class issue-comment Agent Request flow. Comments now
Expand Down Expand Up @@ -6639,7 +6693,7 @@ real dispatch event doubles as a heartbeat.
respond to each `kind` (AGENT_ASSIGNED → claim/start, COMMENT_CREATED
→ read, etc.).
- `Agent.webhookUrl` set to
`http://172.16.24.250:8644/webhooks/forge-dispatch` (Victor) and
`http://&lt;internal-host&gt;:8644/webhooks/forge-dispatch` (Victor) and
`:8645/...` (Mizu). `Agent.webhookSecret` set to the per-route HMAC
secret returned by the subscribe call. Secrets generated with
`openssl rand -hex 32`; intermediate file shredded after the DB
Expand Down Expand Up @@ -6678,7 +6732,7 @@ real dispatch event doubles as a heartbeat.
with a stable jobId for dedupe.
- Live verification: assigned AXI-32 to Victor → `AGENT_ASSIGNED`
event row written → drain enqueued the delivery → worker POSTed to
`http://172.16.24.250:8644/webhooks/forge-dispatch` → HTTP 202 →
`http://&lt;internal-host&gt;:8644/webhooks/forge-dispatch` → HTTP 202 →
`recordAgentReachable` bumped `Agent.lastHeartbeatAt` within 14ms
of `deliveredAt`.
- **Bonus fix during E2E:** existing agent ids in this DB are not
Expand Down Expand Up @@ -6708,7 +6762,7 @@ presence fresh.
- **Mizu cron** also removed; her webhook adapter is live but she had
no `lastHeartbeatAt` to begin with — first real dispatch event
intended for her will set both URL trust and presence.
- The webhook URL is currently the LAN IP `172.16.24.250` because
- The webhook URL is currently the LAN IP `&lt;internal-host&gt;` because
Forge runs in a Docker container on the same host as Hermes (which
runs on the host filesystem). For the cross-host case, swap to a
reachable hostname.
Expand Down Expand Up @@ -6798,7 +6852,7 @@ Currently NOT enabled (no listener on 8644). To switch from the
poll-style heartbeat / queue-pull pattern to push-style dispatch:
flip `platforms.webhook` on in each profile config, subscribe one
route per profile, set the corresponding `Agent.webhookUrl` (e.g.
`http://172.16.24.250:8644/webhooks/forge-dispatch`), and let the
`http://&lt;internal-host&gt;:8644/webhooks/forge-dispatch`), and let the
existing Forge `WebhookDelivery` worker POST `AGENT_ASSIGNED`
straight to Hermes — the route prompt would render "you have a new
assignment: AXI-42" and wake Victor's session. MCP polling for
Expand Down Expand Up @@ -8262,7 +8316,7 @@ Authelia header-bridge didn't land cleanly (edge middleware was setting response

- `POST /api/auth/callback/credentials` with valid creds → `302 /inbox`, `__Secure-authjs.session-token` set.
- `GET /inbox` with that cookie → `200`.
- Login: `timothy.b.dixon@gmail.com` / `@Tqbfj0tld!` (from `~/docker/forge/.env`).
- Login verified with the seeded admin credentials from `~/docker/forge/.env` (redacted).

### Ran into

Expand Down Expand Up @@ -9069,10 +9123,10 @@ Findings + fixes:
- **Validation:** (1) gated live vitest `codex-app-server-live.test.ts`
(CODEX_LIVE=1) bridges stdio→ws and runs the *shipped* connector through real
codex — streams `FORGE_CODEX_OK`. (2) The **deployed Forge container** drove
the bridge→codex round-trip over the network (172.16.24.250:4505) and got
the bridge→codex round-trip over the network (&lt;internal-host&gt;:4505) and got
`FORGE_PROD_OK` via both streamed deltas and the fallback.
- **Prod wiring:** created Runtime `rt_codex_appserver` (adapterKey
codex-app-server, endpoint `ws://172.16.24.250:4505`) in workspace
codex-app-server, endpoint `ws://&lt;internal-host&gt;:4505`) in workspace
cmo6cui6q…, attached the `@codex` agent. resolveRunEngine→RUNS via the
attached adapter; getRunsConnectorForAgent→codex-app-server connector.

Expand Down Expand Up @@ -9183,7 +9237,7 @@ override is authoritative.
3. In Forge → Settings → Runtimes, edit `rt_codex_appserver`: set
`workspaceRoot=/work` and pick a sandbox/approval tier (leave
full-access/never for parity with today). Endpoint stays
`ws://172.16.24.250:4505`.
`ws://&lt;internal-host&gt;:4505`.
Live end-to-end of a sandboxed Codex *turn* still needs the operator to send a
chat (auth-walled) — every layer beneath that is proven.

Expand Down Expand Up @@ -9795,7 +9849,7 @@ src/server/routers/__tests__/workspace-members.test.ts` pass (40);
`pnpm vitest run tests/unit/codex-app-server.test.ts
src/server/services/__tests__/run-dispatcher.test.ts` pass (22);
`git diff --check` clean. LAN preview restarted at
`http://172.16.24.250:3020/w/axiom-labs/chat`.
`http://&lt;internal-host&gt;:3020/w/axiom-labs/chat`.


## 2026-06-11 — AXI-78 Codex RUNS split-worker fix
Expand Down
18 changes: 15 additions & 3 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ export default defineConfig({
// the last-updated stamp anyway.
lastUpdated: false,
appearance: true, // light is default; users can flip to dark
// Exclude internal working docs (execution plans + audit notes) from the
// published site. They're in no sidebar and reference internal infra —
// not user documentation. Applies to both the app and public docs builds.
srcExclude: ["audits/**", "plans/**"],
// Emit /docs/sitemap.xml with canonical public URLs (the public docs home,
// not the personal app instance) so every doc page is individually indexable.
// VitePress emits page paths without the site `base`, so inject /docs/ to
// match where the docs are actually served (forge-pm.dev/docs/...).
sitemap: {
hostname: "https://forge-pm.dev",
transformItems: (items) =>
items.map((item) => ({ ...item, url: `docs/${item.url.replace(/^\/+/, "")}` })),
},
ignoreDeadLinks: [
/^https?:\/\/localhost/,
/^\/api\//,
Expand Down Expand Up @@ -199,10 +212,9 @@ export default defineConfig({
},
},

// Edit-link is wired but the repo isn't public yet. Flip the URL
// when the repo flips public.
// Repo is public; "Edit this page" targets the default branch (main).
editLink: {
pattern: "https://github.com/Codename-11/forge/edit/master/docs/:path",
pattern: "https://github.com/Codename-11/forge/edit/main/docs/:path",
text: "Edit this page on GitHub",
},

Expand Down
3 changes: 2 additions & 1 deletion docs/agents/providers-and-transports.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,5 @@ configured but won't dial: dispatch skips it and chat reports
`PLANNED_ADAPTERS`).
- Engine resolution + connectors: `src/server/services/dispatch/`.
- Design history + remaining work:
[runtime-adapter ADR](../plans/runtime-adapter-refactor.md).
[runtime-adapter ADR](https://github.com/Codename-11/forge/blob/main/docs/plans/runtime-adapter-refactor.md)
(in the repo).
2 changes: 1 addition & 1 deletion docs/automation/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ the scopes line up.
"name": "My Plugin",
"version": "0.1.0",
"description": "Reacts to issues and suggests labels.",
"author": { "name": "Axiom Labs", "email": "ops@axiom-labs.dev" },
"author": { "name": "Your Name", "email": "you@example.com" },
"scopes": ["READ_ISSUES", "WRITE_ISSUES"],
"events": ["ISSUE_CREATED"],
"skills": [
Expand Down
3 changes: 3 additions & 0 deletions landing/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
/out/
next-env.d.ts

# VitePress docs staged in by scripts/build-docs.sh (built artifact)
/public/docs/

# misc
.DS_Store
*.tsbuildinfo
Expand Down
22 changes: 16 additions & 6 deletions landing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,27 @@ pnpm dev # binds 0.0.0.0:3200 — http://localhost:3200 (and over t
## Build & deploy

```bash
pnpm build # static export → ./out
pnpm build # build the VitePress docs + static export → ./out
pnpm build:app # landing pages only (skip the docs build)
pnpm serve # preview the exported site locally (npx serve)
```

`out/` is a plain static bundle. Drop it on GitHub Pages, S3/CloudFront,
Netlify, Vercel, or behind the same reverse proxy as the app. No Node server
is required. (If you later need server features, remove `output: "export"`
in `next.config.ts` and run `pnpm start`.)
`pnpm build` runs `scripts/build-docs.sh` first: it builds the shared
VitePress docs (`../docs` — the same site the app serves) into
`public/docs/`, then `next build` exports everything to `out/`, so the full
documentation ships at `/docs/`. For a fast landing-only loop use
`pnpm build:app` (or `SKIP_DOCS=1 pnpm build`).

`out/` is a plain static bundle. Drop it on any static host — GitHub Pages,
S3/CloudFront, Netlify, Vercel, or behind the same reverse proxy as the app.
No Node server is required to *serve* it; the *build* needs Node + pnpm (the
docs step does a one-time `pnpm --dir ../docs install`). On a host's static
buildpack, set the **build command** to `pnpm build` and the **publish
directory** to `out`. (If you later need server features, remove
`output: "export"` in `next.config.ts` and run `pnpm start`.)

Set the canonical URL via `NEXT_PUBLIC_SITE_URL` at build time (used for
metadata / Open Graph). Defaults to `https://forge.axiom-labs.dev`.
metadata / Open Graph). Defaults to `https://forge-pm.dev`.

## Live data

Expand Down
41 changes: 0 additions & 41 deletions landing/app/docs/page.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion landing/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const jetbrainsMono = JetBrains_Mono({
display: "swap",
});

const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://forge.axiom-labs.dev";
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://forge-pm.dev";
const TITLE = "Forge — issue tracking for humans & agents";
const DESCRIPTION =
"An open-source work tracker built for the handoff between humans and agents. Humans file, agents pick up, humans review — same issues, same plans, same keyboard. Self-host in 60 seconds.";
Expand Down
2 changes: 1 addition & 1 deletion landing/app/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default async function OgImage() {
fontFamily: "ui-monospace, Menlo, monospace",
}}
>
<span>forge.axiom-labs.dev</span>
<span>forge-pm.dev</span>
<span style={{ color: "#d97706" }}>github.com/Codename-11/forge</span>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions landing/app/robots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import type { MetadataRoute } from "next";

export const dynamic = "force-static";

const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://forge.axiom-labs.dev";
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://forge-pm.dev";

export default function robots(): MetadataRoute.Robots {
return {
rules: { userAgent: "*", allow: "/" },
sitemap: `${SITE_URL}/sitemap.xml`,
sitemap: [`${SITE_URL}/sitemap.xml`, `${SITE_URL}/docs/sitemap.xml`],
};
}
2 changes: 1 addition & 1 deletion landing/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { MetadataRoute } from "next";

export const dynamic = "force-static";

const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://forge.axiom-labs.dev";
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://forge-pm.dev";

export default function sitemap(): MetadataRoute.Sitemap {
// Trailing slashes match the exported canonical routes (trailingSlash: true).
Expand Down
Loading
Loading